Bundle
dsh-shell-command
DeepSeek Harness (DSH) plugin: a /! command trigger (run one command, analyze output, inspired by Claude Code's ! gesture) and a /terminal command (interactive PTY popup with on-demand history reference).
- Source
- CHplus0
- stars
- 5 stars
- License
- MIT
- Updated
- Updated 10 days ago
Readme
# dsh-shell-command
English | [中文](README.zh.md)
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that brings Claude Code's `!` gesture to DSH, split into two modes:
| Command | Behavior |
|---|---|
| `/! <command>` | Run a shell command, analyze its output. Inspired by Claude Code's `!` prefix — runs the command in the session workspace and delivers the output to the model for immediate analysis. |
| `/terminal` | Open an interactive terminal popup. Closing never sends anything to the model — if the session received input, the transcript persists and the panel jumps to a history tab (new entry pre-selected) where you can reference it into the model on demand. |
**Note**: DSH's input trigger system only supports `/` and `@` as trigger characters, so we use `/!` instead of a bare `!` prefix.
## Install
Requires the `dsh` CLI and Node `>= 22`.
```sh
# from a registry / npm
dsh plugin --profile web add dsh-shell-command
# from a local checkout (live link for development)
dsh plugin --profile web add "link:/path/to/dsh-shell-command"
```
Restart the web surface once (`dsh web`) so the host-side command loads. It appears in the input `/` menu automatically.
## Quick Start
### Your first `/!` command
Type in the input box:
```
/! df -h
```
The command runs immediately, and the output is delivered to the model for analysis. You'll see a message like:
> **Shell Command Output**
> Command: `df -h`
> [output here]
The model then analyzes the disk usage and may suggest actions if issues are found.
### Your first `/terminal` session
1. Type `/terminal` in the input box
2. A floating terminal panel opens with two tabs: **会话** (Session) and **历史** (History)
3. In the Session tab, try some commands:
```
pwd
ls -la
git status
```
4. Click **退出** (Exit) in the header to close the terminal
5. The panel stays open and automatically jumps to the **History** tab
6. Your just-closed session is pre-selected and expanded
7. Choose what to do:
- Click **引用并分析** to send it to the model for analysis
- Click **直接退出,本次不分析** to skip and close the panel
## Command Details
### `/! <command>` — Single-command analysis
Runs the command in your session workspace and immediately delivers the output to the model for analysis. This is the DSH equivalent of Claude Code's `!` prefix.
**When to use:**
- Quick status checks: `/! git status`, `/! npm test`
- System diagnostics: `/! df -h`, `/! free -h`, `/! top -bn1 | head -20`
- File searches: `/! find . -name "*.log" -mtime -1`
- One-off operations where you want immediate AI insight
**Examples:**
```bash
/! git log --oneline -10
# → Model summarizes recent commits and may spot patterns
/! npm run build
# → Model analyzes build output, flags warnings or errors
/! ps aux | grep node
# → Model explains running Node processes
```
**Technical details:**
- Output is bounded (`maxOutputBytes`, tail retained) and formatted for analysis
- The command text and lifecycle are logged to the trajectory (`command/run`/`command/done`)
- Requires an active model conversation (won't work on the first message before any model request)
### `/terminal` — Interactive terminal
An interactive PTY popup (persistent shell, live WebSocket stream). Best for:
- **Exploratory debugging**: run multiple commands, observe behavior
- **Iterative testing**: modify → test → modify cycles
- **Multi-step operations**: setup → execute → verify workflows
**How it works:**
Closing the terminal **never** sends anything to the model automatically:
- If the session never received input → no trace left (no file, no history entry)
- If the session received input → transcript persists to `<workspace>/.dsh-shell-transcripts/`, panel jumps to History tab with the new entry pre-selected
You decide later whether to reference it for analysis (see History Tab workflow below).
#### Session Tab Features
- **Line-oriented terminal**: monospace scrollback + input line
- **Control keys**: Ctrl+C/D/Z send raw control bytes to the shell
- **Command history**: ArrowUp/ArrowDown recall previously-submitted lines (client-side, max 500 entries)
- **Limitations**: Tab-completion not supported; full VT100/xterm (vim, htop) planned for future release
#### History Tab Workflow

When you close a terminal session that received input, the panel automatically switches to the History tab:
**UI Elements:**
1. **Checkbox** (left): Select one or more history entries for analysis
2. **Timestamp**: When each session was closed
3. **View/Collapse button** (right): Expand to preview the transcript
4. **Notes textarea** (bottom): Optional natural-language context for the model
5. **Default message hint**: Shows what the model receives if you leave notes empty: "请帮我分别分析这些历史终端记录。"
6. **Two action buttons**:
- **引用并分析** ("Reference & Analyze"): Send selected entries to the model for immediate analysis
- **直接退出,本次不分析** ("Exit without analyzing"): Close the panel without sending anything
**Typical workflow:**
```
1. Close a terminal session (click "退出")
2. Panel stays open, jumps to History tab
3. Just-closed entry is pre-selected and auto-expanded
4. Review the transcript preview
5. (Optional) Select additional historical entries for comparison
6. (Optional) Add context in the notes field: "Why did the second run fail?"
7. Click "引用并分析" → Model analyzes selected transcripts
OR click "直接退出,本次不分析" → Panel closes, nothing sent to model
```
**Advanced usage:**
- **Compare across runs**: Select 2-3 related sessions (e.g., before/after a fix) and ask: "What changed between these attempts?"
- **Guided analysis**: Use the notes field to direct the model's attention: "Focus on memory usage patterns"
- **Selective sharing**: Not every terminal session needs analysis — only reference what's relevant
**Storage:**
- Transcripts persist to `<workspace>/.dsh-shell-transcripts/`
- Each session gets: `<sessionId>-<timestamp>-<counter>.log` + `.meta.json` sidecar
- Retention: keeps last 10 sessions per session ID (configurable via `terminalTranscriptKeep`)
- Add `.dsh-shell-transcripts/` to your `.gitignore`
## Usage Scenarios
### Scenario 1: Quick diagnostic with `/!`
```
You: The build is failing. Let me check...
You: /! npm run build
[Output shows a TypeScript error]
Model: The error indicates a missing type import in auth.ts line 42...
```
### Scenario 2: Interactive debugging with `/terminal`
```
You: /terminal
Terminal: $ npm test
[tests fail]
Terminal: $ cat test/integration.spec.js | grep -A5 "failing test"
Terminal: $ ls -la test/fixtures/
Terminal: $ echo $NODE_ENV
[Found the issue: missing fixture file]
Terminal: [Click "退出"]
[Panel jumps to History tab, entry auto-selected]
You: [Add note: "Why did this test fail?"]
You: [Click "引用并分析"]
Model: Based on the transcript, the test failed because test/fixtures/user-data.json is missing...
```
### Scenario 3: Comparing multiple runs
```
[Run 1: /terminal → install dependencies → check build time → exit]
[Run 2: /terminal → same steps with cache enabled → exit]
[Run 3: /terminal → same steps with different Node version → exit]
[In History tab]
You: [Select all 3 entries]
You: [Add note: "Which configuration is fastest and why?"]
You: [Click "引用并分析"]
Model: Comparing the three build runs: Run 2 was 3x faster due to npm cache...
```
## Tips & Best Practices
- **Choose the right tool**: Use `/!` for single commands where you want immediate feedback; use `/terminal` for exploratory workflows
- **Empty sessions don't persist**: If you open `/terminal` but don't type anything, it won't create a history entry (by design)
- **Arrow keys are your friend**: In the terminal input line, ↑/↓ recalls your last 500 commands (client-side, session-scoped)
- **Notes add context**: A good note ("Compare error messages" or "Focus on performance metrics") helps the model analyze more effectively
- **Selective analysis**: You don't have to analyze every terminal session — only reference what's relevant to your current question
- **History is per-session**: Each DSH conversation has its own terminal history; they don't mix
## Configuration
Configure through the profile's `cordis.patch.yml` (the web settings UI does not expose third-party plugin settings):
```yaml
- insert:
- id: shell-command
name: dsh-shell-command
config:
shell: '' # '' → /bin/bash (POSIX) or cmd.exe (Windows)
shellArgs: [] # [] → -lc (POSIX) or /d /s /c (Windows)
timeoutMs: 60000 # hard deadline; process tree is terminated on expiry
maxOutputBytes: 32768 # output tail retention for /! command
graceMs: 3000
analysisPrompt: '' # '' → built-in prompt; supports {command} {cwd} {output}
terminalEnabled: true
terminalMaxTranscriptBytes: 1048576 # in-memory transcript cap (tail)
terminalTranscriptDir: '.dsh-shell-transcripts'
terminalTranscriptKeep: 10
```
## Safety
- Runs in the session's workspace directory (`agent.session.header.cwd`)
- Uses the harness `ctx.subprocess` seam when present: scrubbed environment (no `DEEPSEEK_API_KEY`/`DSH_*` leak), tree-scoped `SIGTERM → grace → SIGKILL` termination. Falls back to `node:child_process` with a scrubbed environment otherwise
- Output is bounded per stream (`maxOutputBytes`, tail retained) for the `/!` command
- Human-driven by design: command text is recorded in the trajectory (`command/run`), and there is no sandbox approval step — it runs with the host's own privileges, exactly like typing in your own terminal
## Tests
```sh
node test/unit.mjs # pure parse/format tests, no dependencies
node test/smoke.mjs # full apply() + handler against a mock context
node test/smoke-terminal.mjs # terminal registry + hasInput tracking + history-reference RPC (mock)
```
The smoke test imports the real `@deepseek-ai/*` peers; outside a profile, link them once:
```sh
mkdir -p node_modules/@deepseek-ai
ln -s "$HOME/.dsh/profiles/node_modules/@deepseek-ai/"* node_modules/@deepseek-ai/
```
## FAQ
**Q: When should I use `/!` vs `/terminal`?**
A: Use `/!` for single commands where you want immediate AI analysis (status checks, quick diagnostics). Use `/terminal` for interactive workflows where you need to run multiple commands and decide later what (if anything) to analyze.
**Q: Why didn't my terminal session appear in History?**
A: Empty sessions (no input received) don't persist. If you opened `/terminal` but didn't type any commands, it won't create a history entry. This is by design to avoid clutter.
**Q: Where are the history transcripts stored?**
A: In `<workspace>/.dsh-shell-transcripts/`, one `.log` file per session plus a `.meta.json` sidecar. Add this directory to your `.gitignore`. Old entries are automatically pruned (default: keep last 10 per session).
**Q: Can I delete history entries manually?**
A: Yes, just delete the `.log` and `.meta.json` files from `.dsh-shell-transcripts/`. The History tab reads from disk on each open.
**Q: Why doesn't vim/htop/ncurses work in `/terminal`?**
A: The current terminal is line-oriented (scrollback + input line), not a full VT100/xterm emulator. Full terminal emulation (using xterm.js) is planned for a future release — it requires introducing a build pipeline.
**Q: Do the arrow-key command recalls persist across sessions?**
A: The ↑/↓ history is client-side and resets when you close the browser tab or the panel. It's scoped to the current terminal window, not saved to disk.
**Q: What's the difference between "引用并分析" and just asking about the commands in chat?**
A: "引用并分析" sends the actual command transcripts (output, timestamps, exit codes) to the model as structured data. Asking in chat relies on your description, which may miss details. Use reference-and-analyze when you want the model to see the raw output.
**Q: Can I reference transcripts from a previous DSH conversation?**
A: No. Terminal history is session-scoped. Each DSH conversation has its own `.dsh-shell-transcripts/` namespace, and the History tab only shows entries from the current session ID.
**Q: What happens if output is very long?**
A: Both `/!` and `/terminal` bound output size (`maxOutputBytes` / `terminalMaxTranscriptBytes`). When exceeded, the **tail** is retained (not the head). This ensures recent output is always visible.
**Q: Why does `/!` say "No model request exists yet"?**
A: `/!` requires an active model conversation. Send at least one message to the model first (starting the conversation), then `/!` will work. This prevents a UI rendering timing issue.
## Limitations
- The `/!` output is delivered as a plugin-sourced message (visible in the conversation)
- Terminal mode: line-oriented display only (vim/htop require full xterm emulation, planned for a future release)
- API surface targets `@deepseek-ai/dsh` `0.1.0-rc.x`; pin peers and re-verify on DSH upgrades
## Future Roadmap
We're planning several enhancements for future releases:
### Full terminal emulation (xterm.js migration)
- **Current**: Line-oriented terminal (scrollback + input line)
- **Goal**: Full VT100/xterm emulation supporting vim, htop, ncurses apps, oh-my-posh, and other rich terminal UIs
- **Requires**: Introducing a build pipeline (webpack/vite) to bundle xterm.js and its addons
### Internationalization (i18n)
- Multi-language UI support (English, Chinese, etc.)
- Language detection from user preferences
- Configurable UI labels
### Additional improvements
- Syntax highlighting for command output
- Session export/import
- Search within terminal history
- Customizable keyboard shortcuts
## Contributing
Contributions are welcome! Feel free to:
- 🐛 [Report bugs or request features](https://github.com/CHplus0/dsh-shell-command/issues)
- 🔧 Submit pull requests
- 📖 Improve documentation
- 💡 Share your use cases and feedback
## License
MIT
Install
dsh plugin --profile web add github:CHplus0/dsh-shell-command
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-shell-command from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.