Open-source project
hust-open-atom-club/oh-dsh avatar
hust-open-atom-club/oh-dsh

oh-dsh: a curl to bash install pointed at main, and a root manifest named after the desktop package

一套 DSH runtime,Desktop、Web 与 TUI 三种开发体验。

326 stars34 forksTypeScriptMIT

At a glance

What is it?
An agent runtime that ships the same workspace, terminal, browser and plugin state across a Desktop, a Web and a TUI surface, with three upstreams underneath and a build whose default target checks out git submodules. The install path pipes a script from the default branch rather than a tag, which is the detail worth deciding about first.
Who is it for?
oh-dsh suits a team that wants one agent session reachable from an editor, a browser and an SSH session, and that is willing to treat three upstreams as one product. It does not suit anyone who wants a reproducible install, because the installer is fetched from a branch rather than a tag and the root manifest publishes no artefact of its own.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The recommended install pipes a script from main, not from a tag

The preferred path on Linux and macOS is one line:

sh
curl -fsSL \
  https://raw.githubusercontent.com/hust-open-atom-club/oh-dsh/main/install.sh \
  | bash

The Windows equivalent pipes a PowerShell script into `iex` from the same branch. Both URLs end in `main`, so the script you run is whatever is on the default branch at the moment you run it, not a fixed artefact.

That sits next to a real safety property. The installer is said to verify the published SHA-256 digests before it touches an existing installation, and to leave the previous installation usable if a download fails, a digest does not match, or an extraction is interrupted. Running the same command again is an in-place upgrade, and `--uninstall` removes by surface.

So the digests that protect your installation are checksums of the release assets, verified by a script that is itself unpinned. That is a defensible split, and it is the one thing to be deliberate about: if you want the script itself fixed, fetch it from a commit hash rather than from the branch.

Surfaces are chosen with a flag. No argument installs the terminal interface and registers the `ohdsh` command into `~/.local/bin`, which is why the documentation tells you to open a new terminal afterwards. `--surface web` and `--surface desktop` install the other two shapes.

Four names for the Desktop surface and one for the terminal

One launcher starts everything, and it only starts what is installed. The three commands are `ohdsh tui`, `ohdsh web` and `ohdsh desktop`.

The Desktop surface has more names than that. There is an alias, `ohdsh gui`. There is a direct entry point that is kept for compatibility, `oh-dsh-desktop`. And the root manifest carries a desktop entry filename, `oh-dsh-desktop.desktop`, which is the Linux launcher file.

So four identifiers point at one surface: the subcommand, the alias, the retained direct binary and the desktop entry file. The terminal interface has one, `ohdsh tui`.

Two of those are configuration rather than commands. `desktopName` in the manifest is what a Linux desktop environment reads to place a launcher, and the direct entry point exists because renaming the launcher would break whatever users had already bookmarked. That is normal migration debt rather than a defect, but it means a reader has to guess which of the four is current, and the documentation names three of them in three different sentences with no statement about which is preferred.

The other documented commands are narrower. The web surface takes a port, as in `ohdsh web --port 3080`, and both the web and terminal surfaces have their own help output. Nothing says whether `ohdsh desktop` accepts flags at all.

The root manifest is the desktop package and ships a launcher it never declares

The root `package.json` is named `@oh-dsh/desktop` with a product name of Oh-DSH Desktop, in a repository that ships three surfaces and carries a workspace configuration. It is also marked `private`, so it is not published.

Inside it, four fields do not agree with each other.

`main` points at `dist/main.js`. That path appears neither in the `exports` map nor in the `files` array, so the file the manifest's main field advertises is neither exposed as an export nor included in a package build.

`exports` has four entries. The root entry resolves to `./dist/plugin.js`, which is the important one: the package is consumed as a plugin rather than as a library, and there are also entries for a client module, the patch configuration, and the manifest itself.

`files` lists seven paths, and `bin` declares one of them. The `bin` map registers `ohdsh` pointing at `bin/ohdsh`, but `files` ships both `bin/ohdsh` and `bin/ohdsh.cmd`, so the Windows launcher travels in the package without being registered as an executable anywhere in the manifest.

Two more fields are worth a look. The author is the literal string `dsh-external`, which is a placeholder rather than a person or an organisation. And there is a custom top-level key, `ohDshRuntimeContract`, set to 1, which is not part of any package manifest specification, so nothing outside this repository's own tooling reads it.

The client injection is five internal packages, hardcoded to the web platform

Under a `dsh` key the manifest carries two sub-objects. The bundle section points at a patch file, `./dist/cordis.patch.yml`, which is how the runtime is modified rather than forked. The patch configuration also exists at the repository root as `cordis.patch.yml`, so there is a source copy and a built copy.

The client section is the more opinionated part. It declares an injection list of five packages: a desktop frame, panel controls, a pinned summary, a sidebar, and a plugin marketplace. Then it sets `platform` to `web` and `immediately` to true.

So five internal packages are injected into the client with no condition other than the surface being web. There is no list of conditions, no opt out flag and no way to remove one from configuration. A workspace whose panels are meant to be collapsible, pinnable, split or full screen is not assembling that behaviour at runtime from a theme, it is having five packages injected before anything else runs.

The manifest does not say what happens on the other two surfaces. Whether the Desktop shell receives the same injection, whether the terminal surface ignores it entirely, and what the frame package does on a surface with no window are all unstated.

The three client-side capabilities the README advertises, skins, the plugin marketplace and the workspace panels, are therefore wired through two different mechanisms: a package for cross surface theming, and a hardcoded injection list for panels.

A bare make checks out three submodules and recompiles one of them

The Makefile sets its default goal to `build`, and `build` depends on a target named `upstream`. So running `make` with no arguments in a fresh clone performs a git operation before any build happens.

That target runs `git submodule update --init --recursive` against three named paths: a sidebar plugin, a terminal plugin and a context plugin. It then recompiles the terminal plugin, but only when its checked out revision differs from a stamp file at `.stage/tui-compile.stamp`. The stamp records `git rev-parse HEAD` of the terminal submodule, so an unchanged revision skips both the install and the compile.

A stamp file inside the build directory is doing version control's job. Deleting `.stage` forces a recompile, editing the stamp suppresses one, and a submodule moved to a dirty working tree produces a revision string that will not match, which is the safe direction.

The target then calls `ensure-upstream-context.mjs`, and a comment above it notes that the same guard also lives inside that script, which `pnpm run build` invokes for the continuous integration and packaging paths. So the submodule check runs twice by two different routes.

The README's source instructions say `git submodule update --init --recursive` with no paths, which updates every submodule in the file, while the Makefile names three. Two different invocations of the same command in the same repository.

Three make targets, three different launch mechanisms, none of them the documented one

Each surface target stages only its own package, which the README explains, and then launches it a different way. The terminal target runs `node dist/ohdsh.js tui --inline` plus whatever is in `ARGS`. The web target runs `node dist/web.js` plus `ARGS`. The desktop target runs `pnpm exec electron .` plus `ARGS`.

So the documented entry point, the `ohdsh` command, is used by one of the three targets. The other two bypass it: one reaches into a built file directly and the other launches Electron against the working directory.

The inline flag is the other thing the Make targets add. The terminal target passes `--inline` by default and takes `ARGS="--fullscreen"` to get the alternate screen instead, so the default development experience continues rendering from the cursor position of the current terminal rather than taking it over.

Four variables sit at the top of the file and all four are overridable: `PNPM`, `NODE`, `ARGS` and `OH_DSH_HOME`, the last defaulting to `$(HOME)/.ohdsh` and exported so every child process sees it. The README notes that the same override works on the command line, as in `OH_DSH_HOME=/tmp/ohdsh make tui`.

The shell is pinned to `/bin/sh` and the home directory is read as `$(HOME)`, so on a context where neither is set the default data directory becomes a path relative to the filesystem root. The file has no Windows path at all, which is a small inconsistency for a project with a Windows installer.

Four install mechanisms, a Nix flake, and an upstream pin file nobody mentions

The documented ways in number four. There is the shell installer, there is the PowerShell installer, and there are release assets in two shapes: a disk image on macOS, an installer or a portable unzip on Windows, and an AppImage or a deb package on Linux. The script is called the recommended entry and the assets are described as being for people who need to pick a package by hand or distribute offline.

A fifth is present in the repository and named nowhere. The root holds `flake.nix`, `flake.lock`, an `nix/` directory and an `.envrc`, which is a Nix flake and a direnv file. A project installed by piping a script into a shell also ships a Nix development and packaging environment.

Alongside those, `dsh-source.json` sits at the root and is not mentioned in the README. Given the Makefile's submodule pinning and the stamp file, that file is the natural place to record which upstream revision is expected, which makes its absence from the documentation the notable part rather than its presence in the tree.

The README is explicit about the layering. Oh-DSH keeps upstream implementations and attribution and adds a unified launcher, profiles, a data directory, cross surface skins, interface adaptation and release packaging. The terminal plugin is named as the direct upstream of the terminal surface, and two further upstreams are credited for the runtime and plugin loader and for the sidebar, file and terminal host capabilities. `THIRD_PARTY_NOTICES.md` is in the root and the design document is linked for the boundary, which is where to look before assuming a behaviour is this project's own.

The marketplace says an install can succeed everywhere and work on one surface

The plugin section is where the documentation is most candid. All three surfaces can search, preview and install plugins, they share one transaction and recovery state, and the catalogue marks which interface a plugin actually takes effect in. The stated reason is explicit: installation may succeed on every terminal while a plugin only works on the web or desktop surface and not in the terminal interface, and the interface distinguishes the two cases visibly.

That is a hard problem stated honestly. A plugin system where the unit of installation is not the unit of capability needs per surface truth, and providing it means the marketplace knows something the installer cannot promise.

The agent presets are the other place where the three surfaces are described as sharing state. Desktop, web and terminal use one set of presets, and the named preset keeps a minimal two tool mode on the first turn, opens the full tool catalogue after the first tool call, and re-anchors after compaction. It is chosen from a settings page on the two graphical surfaces and with a slash command, `/preset liangshen`, on the terminal one. Two different selection mechanisms for one setting, which is the same pattern the plugin catalogue uses.

Image input is the one capability described as needing no bridge at all. It is handled by the runtime's own multimodal model, with copy, paste, thumbnail and submission handled by a native attachment rail, and the documentation states no extra vision plugin and no separate API key are required. A model name is given as an example.

Editorial conclusion

oh-dsh suits a team that wants one agent session reachable from an editor, a browser and an SSH session, and that is willing to treat three upstreams as one product. It does not suit anyone who wants a reproducible install, because the installer is fetched from a branch rather than a tag and the root manifest publishes no artefact of its own. Before you install, pin the installer to a commit yourself, read `THIRD_PARTY_NOTICES.md` and the design document for the upstream boundary, and check which of the three surfaces each plugin you rely on actually supports, since the marketplace itself says an install can succeed everywhere and work on one.

Frequently asked questions

How do I install oh-dsh on Linux or macOS?

Pipe the installer from the repository into a shell, which installs the terminal surface by default and registers `ohdsh` in `~/.local/bin`. Add `--surface web` or `--surface desktop` for the other shapes. The script URL points at the `main` branch rather than at a tag, and the installer verifies the published SHA-256 digests before touching an existing installation.

What is the difference between `ohdsh desktop` and `ohdsh gui`?

They are the same surface under two names, `gui` being an alias for `desktop`. The manifest also retains a direct entry point named `oh-dsh-desktop` and registers a Linux desktop entry file called `oh-dsh-desktop.desktop`. The launcher only starts surfaces that are already installed.

Do oh-dsh plugins work on all three surfaces?

Not necessarily, and the project says so. Installation may succeed on every surface while a plugin takes effect only on the web or desktop surface and not in the terminal interface. The catalogue marks which interface a plugin actually works in, and the three surfaces share one transaction and recovery state.

Where does oh-dsh keep its data?

All three surfaces share `~/.ohdsh` for caches, configuration, sessions, credentials and plugin state. Setting `OH_DSH_HOME` moves the whole data directory, and the same override works for the Make targets, as in `OH_DSH_HOME=/tmp/ohdsh make tui`.

What does running `make` in the oh-dsh repository actually do?

The default goal is `build`, which depends on `upstream`. That target runs `git submodule update --init --recursive` against three named submodule paths, recompiles the terminal submodule only when its checked out revision differs from a stamp file, then calls `ensure-upstream-context.mjs`. Only after that does the build script run.

Official sources

  1. hust-open-atom-club/oh-dsh on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hust-open-atom-club-oh-dsh.svg)](https://hysenlabs.com/projects/hust-open-atom-club-oh-dsh)