claws-code 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/claws-auto.md +90 -0
- package/.claude/commands/claws-bin.md +28 -0
- package/.claude/commands/claws-cleanup.md +28 -0
- package/.claude/commands/claws-do.md +82 -0
- package/.claude/commands/claws-fix.md +40 -0
- package/.claude/commands/claws-goal.md +111 -0
- package/.claude/commands/claws-help.md +54 -0
- package/.claude/commands/claws-plan.md +103 -0
- package/.claude/commands/claws-report.md +29 -0
- package/.claude/commands/claws-status.md +37 -0
- package/.claude/commands/claws-update.md +32 -0
- package/.claude/commands/claws.md +64 -0
- package/.claude/rules/claws-default-behavior.md +76 -0
- package/.claude/settings.json +112 -0
- package/.claude/settings.local.json +19 -0
- package/.claude/skills/claws-auto-engine/SKILL.md +97 -0
- package/.claude/skills/claws-goal-tracker/SKILL.md +106 -0
- package/.claude/skills/claws-prompt-templates/SKILL.md +203 -0
- package/.claude/skills/claws-wave-lead/SKILL.md +126 -0
- package/.claude/skills/claws-wave-subworker/SKILL.md +60 -0
- package/CHANGELOG.md +1949 -0
- package/LICENSE +21 -0
- package/README.md +420 -0
- package/bin/cli.js +84 -0
- package/cli.js +223 -0
- package/docs/ARCHITECTURE.md +511 -0
- package/docs/event-protocol.md +588 -0
- package/docs/features.md +562 -0
- package/docs/guide.md +891 -0
- package/docs/index.html +716 -0
- package/docs/protocol.md +323 -0
- package/extension/.vscodeignore +15 -0
- package/extension/CHANGELOG.md +1906 -0
- package/extension/LICENSE +21 -0
- package/extension/README.md +137 -0
- package/extension/docs/features.md +424 -0
- package/extension/docs/protocol.md +197 -0
- package/extension/esbuild.mjs +25 -0
- package/extension/icon.png +0 -0
- package/extension/native/.metadata.json +10 -0
- package/extension/native/node-pty/LICENSE +69 -0
- package/extension/native/node-pty/README.md +165 -0
- package/extension/native/node-pty/lib/conpty_console_list_agent.js +16 -0
- package/extension/native/node-pty/lib/conpty_console_list_agent.js.map +1 -0
- package/extension/native/node-pty/lib/eventEmitter2.js +47 -0
- package/extension/native/node-pty/lib/eventEmitter2.js.map +1 -0
- package/extension/native/node-pty/lib/index.js +52 -0
- package/extension/native/node-pty/lib/index.js.map +1 -0
- package/extension/native/node-pty/lib/interfaces.js +7 -0
- package/extension/native/node-pty/lib/interfaces.js.map +1 -0
- package/extension/native/node-pty/lib/shared/conout.js +11 -0
- package/extension/native/node-pty/lib/shared/conout.js.map +1 -0
- package/extension/native/node-pty/lib/terminal.js +190 -0
- package/extension/native/node-pty/lib/terminal.js.map +1 -0
- package/extension/native/node-pty/lib/types.js +7 -0
- package/extension/native/node-pty/lib/types.js.map +1 -0
- package/extension/native/node-pty/lib/unixTerminal.js +346 -0
- package/extension/native/node-pty/lib/unixTerminal.js.map +1 -0
- package/extension/native/node-pty/lib/utils.js +39 -0
- package/extension/native/node-pty/lib/utils.js.map +1 -0
- package/extension/native/node-pty/lib/windowsConoutConnection.js +125 -0
- package/extension/native/node-pty/lib/windowsConoutConnection.js.map +1 -0
- package/extension/native/node-pty/lib/windowsPtyAgent.js +320 -0
- package/extension/native/node-pty/lib/windowsPtyAgent.js.map +1 -0
- package/extension/native/node-pty/lib/windowsTerminal.js +199 -0
- package/extension/native/node-pty/lib/windowsTerminal.js.map +1 -0
- package/extension/native/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/extension/native/node-pty/lib/worker/conoutSocketWorker.js.map +1 -0
- package/extension/native/node-pty/package.json +64 -0
- package/extension/native/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/extension/native/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
- package/extension/package-lock.json +605 -0
- package/extension/package.json +343 -0
- package/extension/scripts/bundle-native.mjs +104 -0
- package/extension/scripts/deploy-dev.mjs +60 -0
- package/extension/src/ansi-strip.ts +52 -0
- package/extension/src/backends/vscode/claws-pty.ts +483 -0
- package/extension/src/backends/vscode/status-bar.ts +99 -0
- package/extension/src/backends/vscode/vscode-backend.ts +282 -0
- package/extension/src/capture-store.ts +125 -0
- package/extension/src/event-log.ts +629 -0
- package/extension/src/event-schemas.ts +478 -0
- package/extension/src/extension.js +492 -0
- package/extension/src/extension.ts +873 -0
- package/extension/src/lifecycle-engine.ts +60 -0
- package/extension/src/lifecycle-rules.ts +171 -0
- package/extension/src/lifecycle-store.ts +506 -0
- package/extension/src/peer-registry.ts +176 -0
- package/extension/src/pipeline-registry.ts +82 -0
- package/extension/src/platform.ts +64 -0
- package/extension/src/protocol.ts +532 -0
- package/extension/src/server-config.ts +98 -0
- package/extension/src/server.ts +2210 -0
- package/extension/src/task-registry.ts +51 -0
- package/extension/src/terminal-backend.ts +211 -0
- package/extension/src/terminal-manager.ts +395 -0
- package/extension/src/topic-registry.ts +70 -0
- package/extension/src/topic-utils.ts +46 -0
- package/extension/src/transport.ts +45 -0
- package/extension/src/uninstall-cleanup.ts +232 -0
- package/extension/src/wave-registry.ts +314 -0
- package/extension/src/websocket-transport.ts +153 -0
- package/extension/tsconfig.json +23 -0
- package/lib/capabilities.js +145 -0
- package/lib/dry-run.js +43 -0
- package/lib/install.js +1018 -0
- package/lib/mcp-setup.js +92 -0
- package/lib/platform.js +240 -0
- package/lib/preflight.js +152 -0
- package/lib/shell-hook.js +343 -0
- package/lib/uninstall.js +162 -0
- package/lib/verify.js +166 -0
- package/mcp_server.js +3529 -0
- package/package.json +48 -0
- package/rules/claws-default-behavior.md +72 -0
- package/scripts/_helpers/atomic-file.mjs +137 -0
- package/scripts/_helpers/fix-repair.js +64 -0
- package/scripts/_helpers/json-safe.mjs +218 -0
- package/scripts/bump-version.sh +84 -0
- package/scripts/codegen/gen-docs.mjs +61 -0
- package/scripts/codegen/gen-json-schema.mjs +62 -0
- package/scripts/codegen/gen-mcp-tools.mjs +358 -0
- package/scripts/codegen/gen-types.mjs +172 -0
- package/scripts/codegen/index.mjs +42 -0
- package/scripts/dev-hooks/check-extension-dirs.js +77 -0
- package/scripts/dev-hooks/check-open-claws-terminals.js +70 -0
- package/scripts/dev-hooks/check-stale-main.js +55 -0
- package/scripts/dev-hooks/check-tag-pushed.js +51 -0
- package/scripts/dev-hooks/check-tag-vs-main.js +56 -0
- package/scripts/dev-vsix-install.sh +60 -0
- package/scripts/fix.sh +702 -0
- package/scripts/gen-client-types.mjs +81 -0
- package/scripts/git-hooks/pre-commit +31 -0
- package/scripts/hooks/lifecycle-state.js +61 -0
- package/scripts/hooks/package.json +4 -0
- package/scripts/hooks/post-tool-use-claws.js +292 -0
- package/scripts/hooks/pre-bash-no-verify-block.js +72 -0
- package/scripts/hooks/pre-tool-use-claws.js +206 -0
- package/scripts/hooks/session-start-claws.js +97 -0
- package/scripts/hooks/stop-claws.js +88 -0
- package/scripts/inject-claude-md.js +205 -0
- package/scripts/inject-dev-hooks.js +96 -0
- package/scripts/inject-global-claude-md.js +140 -0
- package/scripts/inject-settings-hooks.js +370 -0
- package/scripts/install.ps1 +146 -0
- package/scripts/install.sh +1729 -0
- package/scripts/monitor-arm-watch.js +155 -0
- package/scripts/rebuild-node-pty.sh +245 -0
- package/scripts/report.sh +232 -0
- package/scripts/shell-hook.fish +164 -0
- package/scripts/shell-hook.ps1 +33 -0
- package/scripts/shell-hook.sh +232 -0
- package/scripts/stream-events.js +399 -0
- package/scripts/terminal-wrapper.sh +36 -0
- package/scripts/test-enforcement.sh +132 -0
- package/scripts/test-install.sh +174 -0
- package/scripts/test-installer-parity.sh +135 -0
- package/scripts/test-template-enforcement.sh +76 -0
- package/scripts/uninstall.sh +143 -0
- package/scripts/update.sh +337 -0
- package/scripts/verify-release.sh +323 -0
- package/scripts/verify-wrapped.sh +194 -0
- package/templates/CLAUDE.global.md +135 -0
- package/templates/CLAUDE.project.md +37 -0
package/docs/guide.md
ADDED
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
# The Complete Claws Guide
|
|
2
|
+
|
|
3
|
+
A comprehensive course-level guide to mastering terminal control with Claws — from first install to production AI orchestration fleets.
|
|
4
|
+
|
|
5
|
+
> **Note:** The Python client shown in later chapters is **optional**. The MCP server (Node.js) provides terminal control natively in Claude Code with zero install. If you are using Claude Code, everything works through MCP tool calls and slash commands out of the box.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Slash Commands (quick reference)
|
|
10
|
+
|
|
11
|
+
After install, these commands are available inside Claude Code sessions in your project:
|
|
12
|
+
|
|
13
|
+
| Command | What it does |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `/claws` | Live status dashboard — forwards to `/claws-do` |
|
|
16
|
+
| `/claws-do <task>` | Universal verb: auto-classifies into shell / worker / fleet / wave |
|
|
17
|
+
| `/claws-plan` | Read task context, audit relevant code, produce a structured plan doc |
|
|
18
|
+
| `/claws-auto <goal>` | Autonomous orchestration loop — plan → dispatch waves → audit → repeat until goal met |
|
|
19
|
+
| `/claws-goal <goal>` | Break a high-level goal into subtasks, track progress, surface blockers |
|
|
20
|
+
| `/claws-status` | Live terminal + lifecycle state table |
|
|
21
|
+
| `/claws-help` | Full command and MCP tool reference |
|
|
22
|
+
| `/claws-cleanup` | Close all worker terminals you own |
|
|
23
|
+
| `/claws-fix` | Diagnose and auto-repair a broken Claws install |
|
|
24
|
+
| `/claws-report` | Bundle logs and diagnostics for a bug report |
|
|
25
|
+
| `/claws-update` | Pull the latest version |
|
|
26
|
+
| `/claws-bin [name]` | View or change the worker binary (for multi-account setups) |
|
|
27
|
+
|
|
28
|
+
**Typical flows:**
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
# Run one thing
|
|
32
|
+
/claws-do run my tests
|
|
33
|
+
|
|
34
|
+
# Parallelize
|
|
35
|
+
/claws-do lint, test, and build in parallel
|
|
36
|
+
|
|
37
|
+
# Spawn a worker to fix code
|
|
38
|
+
/claws-do fix the auth bug in src/auth.ts
|
|
39
|
+
|
|
40
|
+
# Plan before acting
|
|
41
|
+
/claws-plan refactor the auth module
|
|
42
|
+
|
|
43
|
+
# Fully autonomous (best for well-defined goals)
|
|
44
|
+
/claws-auto add unit tests to every file in src/utils/
|
|
45
|
+
|
|
46
|
+
# Track a larger goal interactively
|
|
47
|
+
/claws-goal ship the v0.9 release
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Bug reports: https://github.com/neunaha/claws/issues
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Table of Contents
|
|
55
|
+
|
|
56
|
+
1. [Chapter 1 — Getting Started](#chapter-1--getting-started)
|
|
57
|
+
2. [Chapter 2 — Your First Terminal Control](#chapter-2--your-first-terminal-control)
|
|
58
|
+
3. [Chapter 3 — Wrapped Terminals Deep Dive](#chapter-3--wrapped-terminals-deep-dive)
|
|
59
|
+
4. [Chapter 4 — Command Execution Patterns](#chapter-4--command-execution-patterns)
|
|
60
|
+
5. [Chapter 5 — The Safety Gate](#chapter-5--the-safety-gate)
|
|
61
|
+
6. [Chapter 6 — Event Streaming and Monitoring](#chapter-6--event-streaming-and-monitoring)
|
|
62
|
+
7. [Chapter 7 — AI Pair Programming](#chapter-7--ai-pair-programming)
|
|
63
|
+
8. [Chapter 8 — Parallel Worker Fleets](#chapter-8--parallel-worker-fleets)
|
|
64
|
+
9. [Chapter 9 — Advanced Patterns](#chapter-9--advanced-patterns)
|
|
65
|
+
10. [Chapter 10 — Cross-Device Control](#chapter-10--cross-device-control)
|
|
66
|
+
11. [Chapter 11 — Troubleshooting](#chapter-11--troubleshooting)
|
|
67
|
+
12. [Chapter 12 — Architecture Internals](#chapter-12--architecture-internals)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Chapter 1 — Getting Started
|
|
72
|
+
|
|
73
|
+
### Prerequisites
|
|
74
|
+
|
|
75
|
+
- VS Code 1.93.0 or later
|
|
76
|
+
- macOS, Linux, or Windows (all three supported as of v0.8)
|
|
77
|
+
- Node.js 18+ (bundled with most systems; ships with VS Code)
|
|
78
|
+
|
|
79
|
+
### Installation
|
|
80
|
+
|
|
81
|
+
**Three platform paths:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Mac / Linux — npm (recommended)
|
|
85
|
+
npx claws-code install
|
|
86
|
+
|
|
87
|
+
# Mac / Linux — one-liner
|
|
88
|
+
curl -fsSL https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.sh | bash
|
|
89
|
+
|
|
90
|
+
# Windows — PowerShell (run from your project root)
|
|
91
|
+
iwr https://raw.githubusercontent.com/neunaha/claws/main/scripts/install.ps1 | iex
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**VS Code Marketplace** (v0.8+): Extensions panel → search **Claws** → install `neunaha.claws`. Then `npx claws-code install` from your project root to add the project-local MCP config.
|
|
95
|
+
|
|
96
|
+
Run the installer **from the project root you want Claws in**. Each project gets its own independent Claws setup.
|
|
97
|
+
|
|
98
|
+
Open VS Code. Press `Cmd+Shift+P` → "Developer: Reload Window".
|
|
99
|
+
|
|
100
|
+
### Verification
|
|
101
|
+
|
|
102
|
+
Open the Output panel (`Cmd+Shift+U`) and select "Claws" from the dropdown. You should see:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
[claws] activating
|
|
106
|
+
[claws] listening on /your/workspace/.claws/claws.sock
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If you see this, Claws is running. Try listing terminals:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
echo '{"id":1,"cmd":"list"}' | nc -U .claws/claws.sock
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
You should get back a JSON response with all your open terminals.
|
|
116
|
+
|
|
117
|
+
### Installing the Python Client
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cd claws
|
|
121
|
+
pip install -e clients/python
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Verify:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
python3 -c "from claws import ClawsClient; print('OK')"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Chapter 2 — Your First Terminal Control
|
|
133
|
+
|
|
134
|
+
### Listing Terminals
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from claws import ClawsClient
|
|
138
|
+
|
|
139
|
+
client = ClawsClient(".claws/claws.sock")
|
|
140
|
+
for t in client.list():
|
|
141
|
+
print(f"{t.id} {t.name} pid={t.pid} active={t.active}")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Every terminal gets a stable numeric ID. This ID persists for the terminal's lifetime — use it for all subsequent commands.
|
|
145
|
+
|
|
146
|
+
### Creating a Terminal
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
term = client.create("my-first-terminal")
|
|
150
|
+
print(f"Created: id={term.id}")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A new terminal tab appears in VS Code. You can see it in the terminal panel.
|
|
154
|
+
|
|
155
|
+
### Sending Text
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
client.send(term.id, "echo hello from Claws")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Switch to VS Code — you'll see the command typed and executed in the terminal you created.
|
|
162
|
+
|
|
163
|
+
### Closing a Terminal
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
client.close(term.id)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The terminal tab disappears from VS Code. Clean up after yourself.
|
|
170
|
+
|
|
171
|
+
### The Full Loop
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from claws import ClawsClient
|
|
175
|
+
import time
|
|
176
|
+
|
|
177
|
+
client = ClawsClient(".claws/claws.sock")
|
|
178
|
+
|
|
179
|
+
# Create
|
|
180
|
+
term = client.create("demo")
|
|
181
|
+
time.sleep(1) # let shell initialize
|
|
182
|
+
|
|
183
|
+
# Execute
|
|
184
|
+
result = client.exec(term.id, "echo hello && date && whoami")
|
|
185
|
+
print(f"exit {result.exit_code}")
|
|
186
|
+
print(result.output)
|
|
187
|
+
|
|
188
|
+
# Close
|
|
189
|
+
client.close(term.id)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This is the fundamental pattern: create → use → close. Everything else builds on this.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Chapter 3 — Wrapped Terminals Deep Dive
|
|
197
|
+
|
|
198
|
+
### The Problem with Regular Terminals
|
|
199
|
+
|
|
200
|
+
When you `send` text into a regular terminal, it goes in — but nothing comes back through the Claws API. You're writing into a black box. The `exec` command works around this with file-based capture, but that only captures individual commands. You can't read an ongoing interactive session.
|
|
201
|
+
|
|
202
|
+
### What Wrapping Does
|
|
203
|
+
|
|
204
|
+
A wrapped terminal runs your shell inside `script(1)`:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
Your Terminal Tab
|
|
208
|
+
└── script(1) — logs every byte to .claws/terminals/claws-N.log
|
|
209
|
+
└── /bin/zsh — your actual shell
|
|
210
|
+
└── whatever you run
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Every character that appears on screen is recorded in the log file. Claws can then read this file back with ANSI escapes stripped, giving you clean text.
|
|
214
|
+
|
|
215
|
+
### Creating a Wrapped Terminal
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
term = client.create("worker", wrapped=True)
|
|
219
|
+
print(f"Log: {term.log_path}")
|
|
220
|
+
# Log: /your/workspace/.claws/terminals/claws-5.log
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Or from the VS Code UI: click the dropdown arrow next to `+` in the terminal panel → "Claws Wrapped Terminal".
|
|
224
|
+
|
|
225
|
+
### Reading the Log
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
import time
|
|
229
|
+
|
|
230
|
+
# Send some commands
|
|
231
|
+
client.send(term.id, "ls -la")
|
|
232
|
+
time.sleep(1)
|
|
233
|
+
client.send(term.id, "git status")
|
|
234
|
+
time.sleep(1)
|
|
235
|
+
|
|
236
|
+
# Read back everything
|
|
237
|
+
log = client.read_log(term.id, lines=30)
|
|
238
|
+
print(log)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Output (ANSI-stripped):
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
$ ls -la
|
|
245
|
+
total 96
|
|
246
|
+
drwxr-xr-x 15 user staff 480 Apr 18 01:00 .
|
|
247
|
+
-rw-r--r-- 1 user staff 9067 Apr 18 00:30 CLAUDE.md
|
|
248
|
+
...
|
|
249
|
+
$ git status
|
|
250
|
+
On branch main
|
|
251
|
+
nothing to commit, working tree clean
|
|
252
|
+
$
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Incremental Tailing
|
|
256
|
+
|
|
257
|
+
For real-time monitoring, use offset-based reading:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
cursor = 0
|
|
261
|
+
while True:
|
|
262
|
+
# This is the raw socket approach; the Python client wraps this
|
|
263
|
+
resp = client._send({
|
|
264
|
+
"cmd": "readLog",
|
|
265
|
+
"id": term.id,
|
|
266
|
+
"offset": cursor,
|
|
267
|
+
"strip": True,
|
|
268
|
+
})
|
|
269
|
+
if resp["nextOffset"] > cursor:
|
|
270
|
+
new_text = resp["bytes"]
|
|
271
|
+
print(new_text, end="")
|
|
272
|
+
cursor = resp["nextOffset"]
|
|
273
|
+
time.sleep(1)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Reading TUI Sessions
|
|
277
|
+
|
|
278
|
+
The real power of wrapped terminals: reading interactive programs that shell integration can't capture.
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
# Launch Claude Code in a wrapped terminal
|
|
282
|
+
term = client.create("ai-session", wrapped=True)
|
|
283
|
+
time.sleep(2)
|
|
284
|
+
client.send(term.id, "claude")
|
|
285
|
+
time.sleep(5)
|
|
286
|
+
|
|
287
|
+
# Read the Claude Code welcome screen
|
|
288
|
+
log = client.read_log(term.id, lines=30)
|
|
289
|
+
print(log)
|
|
290
|
+
# Shows: Claude Code banner, model info, session state
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
This works for vim, htop, python3 REPL, node REPL, irb — anything that renders to a terminal.
|
|
294
|
+
|
|
295
|
+
### Log Buffering
|
|
296
|
+
|
|
297
|
+
`script(1)` buffers output before writing to disk (~1-2 second delay). This is intentional — aggressive flushing (`-F` flag) corrupts Ink-based TUI renderers like Claude Code. Accept the delay; it doesn't affect usability.
|
|
298
|
+
|
|
299
|
+
### Detecting Wrapped Terminals
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
for t in client.list():
|
|
303
|
+
if t.log_path:
|
|
304
|
+
print(f"{t.id} {t.name} — WRAPPED (log: {t.log_path})")
|
|
305
|
+
else:
|
|
306
|
+
print(f"{t.id} {t.name} — regular")
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Environment Variable
|
|
310
|
+
|
|
311
|
+
Inside a wrapped terminal, `CLAWS_WRAPPED=1` is set in the environment. Your scripts can detect this:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
if [ "${CLAWS_WRAPPED:-}" = "1" ]; then
|
|
315
|
+
echo "I'm in a Claws-wrapped terminal"
|
|
316
|
+
fi
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Chapter 4 — Command Execution Patterns
|
|
322
|
+
|
|
323
|
+
### exec — Structured Output
|
|
324
|
+
|
|
325
|
+
`exec` is for when you need the output back programmatically:
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
result = client.exec(term.id, "python3 -c 'import sys; print(sys.version)'")
|
|
329
|
+
assert result.exit_code == 0
|
|
330
|
+
print(result.output) # "3.11.9 (main, ...)\n"
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### send — Fire and Forget
|
|
334
|
+
|
|
335
|
+
`send` is for when you just need text to arrive at the terminal's input:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
client.send(term.id, "npm start")
|
|
339
|
+
# No output captured — use readLog to see what happened
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### When to Use Which
|
|
343
|
+
|
|
344
|
+
| Scenario | Use |
|
|
345
|
+
|---|---|
|
|
346
|
+
| Run a command, check the output | `exec` |
|
|
347
|
+
| Type into a shell prompt | `send` |
|
|
348
|
+
| Type into a TUI (vim, claude, REPL) | `send` |
|
|
349
|
+
| Send Ctrl+C / Ctrl+D | `send` with `\x03` / `\x04` |
|
|
350
|
+
| Run a long-running process | `send` + monitor with `readLog` |
|
|
351
|
+
| Run a command in a specific directory | `exec` with `cd /path && cmd` |
|
|
352
|
+
|
|
353
|
+
### Multi-line Commands
|
|
354
|
+
|
|
355
|
+
Claws auto-wraps multi-line text in bracketed paste mode:
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
script = """
|
|
359
|
+
for i in range(5):
|
|
360
|
+
print(f"Line {i}")
|
|
361
|
+
"""
|
|
362
|
+
client.send(term.id, f"python3 -c '{script}'")
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The shell receives this as one atomic paste, not 3 separate Enter presses.
|
|
366
|
+
|
|
367
|
+
### Timeouts
|
|
368
|
+
|
|
369
|
+
`exec` waits up to 180 seconds by default. For longer commands:
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
result = client.exec(term.id, "npm run build", timeout_ms=600000) # 10 minutes
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
If it times out, you get a `ClawsError` with whatever partial output was captured.
|
|
376
|
+
|
|
377
|
+
### Auto-Created Exec Terminal
|
|
378
|
+
|
|
379
|
+
If you call `exec` without a terminal ID, Claws creates (or reuses) a terminal named `claws-work`:
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
# No need to create a terminal first
|
|
383
|
+
result = client.exec(None, "echo quick one-off")
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Chapter 5 — The Safety Gate
|
|
389
|
+
|
|
390
|
+
### What It Protects Against
|
|
391
|
+
|
|
392
|
+
Imagine you have a terminal running vim. You send `ls -la` via Claws, intending it as a shell command. Instead, vim receives "ls -la" as normal-mode keystrokes — jumping to line `l`, then `s` enters insert mode, then `- la` types literal characters. Your file is corrupted.
|
|
393
|
+
|
|
394
|
+
The safety gate prevents this by checking what's actually running in the terminal before sending.
|
|
395
|
+
|
|
396
|
+
### How It Works
|
|
397
|
+
|
|
398
|
+
1. Claws reads the terminal's shell PID from VS Code
|
|
399
|
+
2. Runs `pgrep -P <pid>` to find the foreground child process
|
|
400
|
+
3. If the child is a known shell (bash, zsh, fish, sh, dash, ksh) → safe
|
|
401
|
+
4. If it's anything else (claude, vim, less, python3, node) → warning
|
|
402
|
+
|
|
403
|
+
### Default Behavior (warn + proceed)
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
# Terminal is running vim
|
|
407
|
+
client.send(term.id, "hello")
|
|
408
|
+
# Response includes: [warning: foreground is 'vim' (not a shell)]
|
|
409
|
+
# But the text IS sent — Claws doesn't block by default
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Strict Mode (block)
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
# Block sends into non-shell TUIs
|
|
416
|
+
client._send({
|
|
417
|
+
"cmd": "send",
|
|
418
|
+
"id": term.id,
|
|
419
|
+
"text": "hello",
|
|
420
|
+
"strict": True,
|
|
421
|
+
})
|
|
422
|
+
# Returns: {"ok": false, "error": "BLOCKED (strict): ..."}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Why Warn Instead of Block
|
|
426
|
+
|
|
427
|
+
The primary use case for Claws is AI pair programming — where you **intentionally** send prompts into Claude Code's TUI. Blocking that defeats the purpose. The warning is for accidental sends; intentional TUI interaction should proceed.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Chapter 6 — Event Streaming and Monitoring
|
|
432
|
+
|
|
433
|
+
### poll — Shell Integration Events
|
|
434
|
+
|
|
435
|
+
VS Code's shell integration fires an event when a command finishes. Claws captures these in a ring buffer:
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
events, cursor = client.poll(since=0)
|
|
439
|
+
for e in events:
|
|
440
|
+
print(f"[{e['terminalName']}] $ {e['commandLine']}")
|
|
441
|
+
print(f" exit={e['exitCode']}, {len(e['output'])} bytes output")
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Save the cursor and pass it next time to get only new events:
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
cursor = 0
|
|
448
|
+
while True:
|
|
449
|
+
events, cursor = client.poll(since=cursor)
|
|
450
|
+
for e in events:
|
|
451
|
+
print(f"[{e['terminalName']}] {e['commandLine']} → {e['exitCode']}")
|
|
452
|
+
time.sleep(2)
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### poll Limitations
|
|
456
|
+
|
|
457
|
+
- Requires VS Code shell integration to be active
|
|
458
|
+
- Doesn't fire in wrapped terminals (shell integration doesn't inject through script(1))
|
|
459
|
+
- Doesn't fire for TUI sessions
|
|
460
|
+
- Ring buffer holds last 500 events — old events are dropped
|
|
461
|
+
|
|
462
|
+
For reliable observation, use `readLog` on wrapped terminals instead.
|
|
463
|
+
|
|
464
|
+
### The Monitor Pattern
|
|
465
|
+
|
|
466
|
+
The preferred observation pattern in v0.8+ is the **bus-stream** approach — subscribe to the claws/2 event bus directly using `stream-events.js`. This is SIGPIPE-safe and sub-100ms latency, unlike `tail -F | grep` which gets killed by VS Code's process supervisor within ~30 s of inactivity.
|
|
467
|
+
|
|
468
|
+
Every `claws_worker` spawn response includes a ready-to-use `monitor_arm_command`. Copy it into a `Monitor(...)` call:
|
|
469
|
+
|
|
470
|
+
```
|
|
471
|
+
Monitor(
|
|
472
|
+
command="node /path/to/.claws-bin/scripts/stream-events.js --wait <correlation_id>",
|
|
473
|
+
description="claws monitor | term=<id>",
|
|
474
|
+
timeout_ms=600000,
|
|
475
|
+
persistent=false
|
|
476
|
+
)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
`stream-events.js --wait <correlation_id>` filters the bus stream to one worker and self-exits on `system.worker.completed`. Use the exact `monitor_arm_command` string from the spawn response — it has the right paths and correlation ID pre-filled.
|
|
480
|
+
|
|
481
|
+
**Legacy `tail -F` pattern** (still works, but avoid for long-running observation):
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
tail -F .claws/terminals/claws-5.log \
|
|
485
|
+
| perl -pe 'BEGIN{$|=1} s/\e\[[0-9;?]*[a-zA-Z]//g' \
|
|
486
|
+
| grep --line-buffered -E 'Error|DONE|FAIL|exit'
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Chapter 7 — AI Pair Programming
|
|
492
|
+
|
|
493
|
+
This is what Claws was built for. One AI session controls multiple terminal sessions — spawning workers, sending mission prompts, monitoring progress, reacting to errors.
|
|
494
|
+
|
|
495
|
+
### The Basic Pattern
|
|
496
|
+
|
|
497
|
+
```python
|
|
498
|
+
from claws import ClawsClient
|
|
499
|
+
import time
|
|
500
|
+
|
|
501
|
+
client = ClawsClient(".claws/claws.sock")
|
|
502
|
+
|
|
503
|
+
# 1. Create a wrapped terminal
|
|
504
|
+
worker = client.create("ai-worker", wrapped=True)
|
|
505
|
+
time.sleep(1.5)
|
|
506
|
+
|
|
507
|
+
# 2. Launch Claude Code inside it
|
|
508
|
+
client.send(worker.id, "claude --dangerously-skip-permissions")
|
|
509
|
+
time.sleep(5)
|
|
510
|
+
|
|
511
|
+
# 3. Send a mission prompt
|
|
512
|
+
mission = (
|
|
513
|
+
"fix the failing test in src/utils.test.ts. "
|
|
514
|
+
"read the error first, then fix the implementation. "
|
|
515
|
+
"commit when green. print MISSION_COMPLETE when done."
|
|
516
|
+
)
|
|
517
|
+
client.send(worker.id, mission)
|
|
518
|
+
time.sleep(0.3)
|
|
519
|
+
# Submit with raw CR (Claude Code needs explicit Enter)
|
|
520
|
+
client.send(worker.id, "\r", newline=False)
|
|
521
|
+
|
|
522
|
+
# 4. Monitor progress
|
|
523
|
+
while True:
|
|
524
|
+
log = client.read_log(worker.id, lines=20)
|
|
525
|
+
if "MISSION_COMPLETE" in log:
|
|
526
|
+
break
|
|
527
|
+
if "Error" in log:
|
|
528
|
+
print("Worker hit an error — check the log")
|
|
529
|
+
time.sleep(10)
|
|
530
|
+
|
|
531
|
+
# 5. Read final state
|
|
532
|
+
full_log = client.read_log(worker.id, lines=200)
|
|
533
|
+
print(full_log)
|
|
534
|
+
|
|
535
|
+
# 6. Clean up
|
|
536
|
+
client.close(worker.id)
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Writing Good Mission Prompts
|
|
540
|
+
|
|
541
|
+
A mission prompt should have:
|
|
542
|
+
|
|
543
|
+
1. **Context** — what the worker needs to know
|
|
544
|
+
2. **Objective** — one clear sentence
|
|
545
|
+
3. **Steps** — numbered if order matters
|
|
546
|
+
4. **Constraints** — explicit prohibitions
|
|
547
|
+
5. **Completion marker** — `MISSION_COMPLETE` for machine parsing
|
|
548
|
+
6. **Imperative close** — "go."
|
|
549
|
+
|
|
550
|
+
```
|
|
551
|
+
fix the type error in pipeline/committee.py line 234.
|
|
552
|
+
read the error, trace the root cause, apply a minimal fix.
|
|
553
|
+
do not refactor surrounding code. do not commit.
|
|
554
|
+
print MISSION_COMPLETE when the fix is verified. go.
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Sending Follow-ups
|
|
558
|
+
|
|
559
|
+
After reading the worker's state, you can send a follow-up prompt:
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
# Read what the worker did
|
|
563
|
+
log = client.read_log(worker.id, lines=30)
|
|
564
|
+
|
|
565
|
+
# If it's stuck, redirect
|
|
566
|
+
if "I need clarification" in log:
|
|
567
|
+
client.send(worker.id, "use option B. the database is PostgreSQL 15.")
|
|
568
|
+
client.send(worker.id, "\r", newline=False)
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
This is true pair programming — read, think, respond.
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Chapter 8 — Parallel Worker Fleets
|
|
576
|
+
|
|
577
|
+
### Spawning Multiple Workers
|
|
578
|
+
|
|
579
|
+
```python
|
|
580
|
+
tasks = {
|
|
581
|
+
"lint": "npm run lint",
|
|
582
|
+
"test": "npm test",
|
|
583
|
+
"typecheck": "npx tsc --noEmit",
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
workers = {}
|
|
587
|
+
for name, cmd in tasks.items():
|
|
588
|
+
term = client.create(f"worker-{name}", wrapped=True)
|
|
589
|
+
workers[name] = term
|
|
590
|
+
time.sleep(0.5) # stagger to avoid shell init race
|
|
591
|
+
|
|
592
|
+
# Fire all commands
|
|
593
|
+
for name, cmd in tasks.items():
|
|
594
|
+
client.send(workers[name].id, cmd)
|
|
595
|
+
|
|
596
|
+
# Wait and collect
|
|
597
|
+
import time
|
|
598
|
+
time.sleep(15)
|
|
599
|
+
for name, term in workers.items():
|
|
600
|
+
log = client.read_log(term.id, lines=10)
|
|
601
|
+
passed = "PASS" in log or "0 errors" in log
|
|
602
|
+
status = "PASS" if passed else "FAIL"
|
|
603
|
+
print(f"{status} {name}")
|
|
604
|
+
client.close(term.id)
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### AI Agent Fleet
|
|
608
|
+
|
|
609
|
+
Spawn multiple Claude Code sessions with different missions:
|
|
610
|
+
|
|
611
|
+
```python
|
|
612
|
+
missions = [
|
|
613
|
+
("audit-a", "analyze pipeline latency from artifact mtimes. write to /tmp/latency.md"),
|
|
614
|
+
("audit-b", "analyze token consumption from budget_ledger.jsonl. write to /tmp/tokens.md"),
|
|
615
|
+
("audit-c", "analyze runbook dependencies and parallelism. write to /tmp/critpath.md"),
|
|
616
|
+
]
|
|
617
|
+
|
|
618
|
+
workers = {}
|
|
619
|
+
for name, mission in missions:
|
|
620
|
+
term = client.create(name, wrapped=True)
|
|
621
|
+
time.sleep(1.5)
|
|
622
|
+
client.send(term.id, "claude --dangerously-skip-permissions")
|
|
623
|
+
time.sleep(5)
|
|
624
|
+
client.send(term.id, mission + " print MISSION_COMPLETE when done. go.")
|
|
625
|
+
time.sleep(0.3)
|
|
626
|
+
client.send(term.id, "\r", newline=False)
|
|
627
|
+
workers[name] = term
|
|
628
|
+
|
|
629
|
+
# Monitor all workers
|
|
630
|
+
while workers:
|
|
631
|
+
for name, term in list(workers.items()):
|
|
632
|
+
log = client.read_log(term.id, lines=5)
|
|
633
|
+
if "MISSION_COMPLETE" in log:
|
|
634
|
+
print(f"{name} — DONE")
|
|
635
|
+
client.close(term.id)
|
|
636
|
+
del workers[name]
|
|
637
|
+
time.sleep(15)
|
|
638
|
+
|
|
639
|
+
print("All workers complete.")
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Auto-Cleanup
|
|
643
|
+
|
|
644
|
+
Every terminal you create must be closed. Implement a cleanup handler:
|
|
645
|
+
|
|
646
|
+
```python
|
|
647
|
+
import atexit
|
|
648
|
+
|
|
649
|
+
created_terminals = []
|
|
650
|
+
|
|
651
|
+
def create_worker(name, **kwargs):
|
|
652
|
+
term = client.create(name, **kwargs)
|
|
653
|
+
created_terminals.append(term.id)
|
|
654
|
+
return term
|
|
655
|
+
|
|
656
|
+
@atexit.register
|
|
657
|
+
def cleanup():
|
|
658
|
+
for tid in created_terminals:
|
|
659
|
+
try:
|
|
660
|
+
client.close(tid)
|
|
661
|
+
except:
|
|
662
|
+
pass
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## Chapter 9 — Advanced Patterns
|
|
668
|
+
|
|
669
|
+
### Pattern: Orchestrator with Monitor
|
|
670
|
+
|
|
671
|
+
Use `tail -F` as a real-time event source instead of polling:
|
|
672
|
+
|
|
673
|
+
```bash
|
|
674
|
+
# Terminal 1: monitor
|
|
675
|
+
tail -F .claws/terminals/claws-5.log \
|
|
676
|
+
| perl -pe 'BEGIN{$|=1} s/\e\[[0-9;?]*[a-zA-Z]//g; s/\e\][^\a]*\a//g; s/[\x00-\x08\x0b-\x1a\x1c-\x1f\x7f]//g' \
|
|
677
|
+
| grep --line-buffered -E '(Read|Write|Edit|Bash)\([^)]{3,}|MISSION_COMPLETE|Error|Traceback'
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
This gives you a filtered stream of tool calls and errors from the worker, arriving in real-time.
|
|
681
|
+
|
|
682
|
+
### Pattern: Conditional Branching
|
|
683
|
+
|
|
684
|
+
Read the worker's state and make decisions:
|
|
685
|
+
|
|
686
|
+
```python
|
|
687
|
+
log = client.read_log(worker.id, lines=20)
|
|
688
|
+
|
|
689
|
+
if "test failed" in log.lower():
|
|
690
|
+
client.send(worker.id, "revert the last change and try approach B instead")
|
|
691
|
+
elif "permission denied" in log.lower():
|
|
692
|
+
client.send(worker.id, "use sudo for that command")
|
|
693
|
+
elif "MISSION_COMPLETE" in log:
|
|
694
|
+
print("Success!")
|
|
695
|
+
client.close(worker.id)
|
|
696
|
+
else:
|
|
697
|
+
print("Still working...")
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Pattern: Pipeline Stages
|
|
701
|
+
|
|
702
|
+
Chain workers — output of one feeds the next:
|
|
703
|
+
|
|
704
|
+
```python
|
|
705
|
+
# Stage 1: analyze
|
|
706
|
+
analyzer = client.create("stage-1-analyze", wrapped=True)
|
|
707
|
+
client.send(analyzer.id, "python3 analyze.py > /tmp/analysis.json")
|
|
708
|
+
time.sleep(10)
|
|
709
|
+
client.close(analyzer.id)
|
|
710
|
+
|
|
711
|
+
# Stage 2: transform (reads stage 1 output)
|
|
712
|
+
transformer = client.create("stage-2-transform", wrapped=True)
|
|
713
|
+
client.send(transformer.id, "python3 transform.py /tmp/analysis.json > /tmp/result.json")
|
|
714
|
+
time.sleep(10)
|
|
715
|
+
client.close(transformer.id)
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Pattern: Watchdog
|
|
719
|
+
|
|
720
|
+
Monitor a long-running process and restart if it crashes:
|
|
721
|
+
|
|
722
|
+
```python
|
|
723
|
+
import time
|
|
724
|
+
|
|
725
|
+
while True:
|
|
726
|
+
term = client.create("server", wrapped=True)
|
|
727
|
+
client.send(term.id, "npm start")
|
|
728
|
+
|
|
729
|
+
while True:
|
|
730
|
+
time.sleep(30)
|
|
731
|
+
log = client.read_log(term.id, lines=5)
|
|
732
|
+
if "EADDRINUSE" in log or "FATAL" in log or "crashed" in log.lower():
|
|
733
|
+
print("Server crashed — restarting...")
|
|
734
|
+
client.close(term.id)
|
|
735
|
+
time.sleep(2)
|
|
736
|
+
break
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Pattern: Multi-Language Raw Socket
|
|
740
|
+
|
|
741
|
+
You don't need Python. Any language works:
|
|
742
|
+
|
|
743
|
+
**bash:**
|
|
744
|
+
```bash
|
|
745
|
+
echo '{"id":1,"cmd":"list"}' | nc -U .claws/claws.sock
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Node.js:**
|
|
749
|
+
```javascript
|
|
750
|
+
const net = require('net');
|
|
751
|
+
const sock = net.createConnection('.claws/claws.sock');
|
|
752
|
+
sock.write('{"id":1,"cmd":"list"}\n');
|
|
753
|
+
sock.on('data', d => console.log(JSON.parse(d.toString())));
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
**Go:**
|
|
757
|
+
```go
|
|
758
|
+
conn, _ := net.Dial("unix", ".claws/claws.sock")
|
|
759
|
+
conn.Write([]byte(`{"id":1,"cmd":"list"}` + "\n"))
|
|
760
|
+
buf := make([]byte, 65536)
|
|
761
|
+
n, _ := conn.Read(buf)
|
|
762
|
+
fmt.Println(string(buf[:n]))
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
---
|
|
766
|
+
|
|
767
|
+
## Chapter 10 — Cross-Device Control
|
|
768
|
+
|
|
769
|
+
### Today: SSH Tunnel
|
|
770
|
+
|
|
771
|
+
You can control a remote VS Code instance right now using SSH port forwarding:
|
|
772
|
+
|
|
773
|
+
```bash
|
|
774
|
+
# On your local machine — forward the remote socket
|
|
775
|
+
ssh -L /tmp/remote-claws.sock:/remote/workspace/.claws/claws.sock user@remote-host
|
|
776
|
+
|
|
777
|
+
# Connect to the forwarded socket
|
|
778
|
+
from claws import ClawsClient
|
|
779
|
+
client = ClawsClient("/tmp/remote-claws.sock")
|
|
780
|
+
terminals = client.list() # shows remote VS Code terminals
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### Planned: WebSocket Transport
|
|
784
|
+
|
|
785
|
+
v0.3 will add WebSocket alongside the Unix socket:
|
|
786
|
+
|
|
787
|
+
1. Enable in VS Code settings: `"claws.enableWebSocket": true`
|
|
788
|
+
2. Claws starts a WebSocket server on port 9876
|
|
789
|
+
3. Connect from anywhere: `ws://remote-host:9876`
|
|
790
|
+
4. Token auth required (token shown in Output panel)
|
|
791
|
+
5. TLS support for encrypted connections
|
|
792
|
+
|
|
793
|
+
### Planned: Team Configuration
|
|
794
|
+
|
|
795
|
+
Named devices with per-terminal access control:
|
|
796
|
+
|
|
797
|
+
```json
|
|
798
|
+
{
|
|
799
|
+
"team": {
|
|
800
|
+
"dev-laptop": { "role": "controller", "access": "read-write" },
|
|
801
|
+
"build-server": { "role": "worker", "access": "read-write" },
|
|
802
|
+
"dashboard": { "role": "observer", "access": "read-only" }
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
## Chapter 11 — Troubleshooting
|
|
810
|
+
|
|
811
|
+
### "No socket found"
|
|
812
|
+
|
|
813
|
+
The extension isn't running. Check:
|
|
814
|
+
1. Is VS Code open?
|
|
815
|
+
2. Is the Claws extension installed? (`Cmd+Shift+X` → search "Claws")
|
|
816
|
+
3. Did you reload? (`Cmd+Shift+P` → "Developer: Reload Window")
|
|
817
|
+
4. Check the Output panel → "Claws" for errors
|
|
818
|
+
|
|
819
|
+
### "Terminal not wrapped"
|
|
820
|
+
|
|
821
|
+
You called `readLog` on a regular terminal. Only terminals created with `wrapped: true` have pty logs. Create a new one:
|
|
822
|
+
|
|
823
|
+
```python
|
|
824
|
+
term = client.create("name", wrapped=True)
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### Wrapped terminal shows visual glitches
|
|
828
|
+
|
|
829
|
+
You're probably running a TUI (Claude Code, vim) and the wrapper script has the `-F` flag enabled. Remove it — default `script` buffering is correct for TUI sessions. Check `scripts/terminal-wrapper.sh`.
|
|
830
|
+
|
|
831
|
+
### Multi-line send fragments into separate commands
|
|
832
|
+
|
|
833
|
+
This happens when bracketed paste isn't working. Claws auto-wraps multi-line text in `\x1b[200~...\x1b[201~`. If your shell doesn't support bracketed paste, each `\n` becomes a separate Enter. Fix: upgrade your shell or send the text as a file: `cat /tmp/prompt.txt`.
|
|
834
|
+
|
|
835
|
+
### exec times out but the command is running
|
|
836
|
+
|
|
837
|
+
`exec` polls for a `.done` marker file. If the command runs but doesn't produce the marker (e.g., it runs in a subshell that doesn't inherit the wrapper), the marker never appears. Check `/tmp/claws-exec/` for orphaned `.out` files.
|
|
838
|
+
|
|
839
|
+
### Socket permission denied
|
|
840
|
+
|
|
841
|
+
The socket is created with `chmod 600`. Only the user who started VS Code can connect. If you're running your script as a different user, that's the problem.
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Chapter 12 — Architecture Internals
|
|
846
|
+
|
|
847
|
+
### Extension Lifecycle
|
|
848
|
+
|
|
849
|
+
1. VS Code loads the extension on `onStartupFinished`
|
|
850
|
+
2. `activate()` runs:
|
|
851
|
+
- Creates the output channel
|
|
852
|
+
- Attaches shell integration listeners
|
|
853
|
+
- Starts the socket server
|
|
854
|
+
- Registers the terminal profile provider
|
|
855
|
+
- Registers commands
|
|
856
|
+
3. On each request: parse JSON → dispatch to handler → write response
|
|
857
|
+
4. `deactivate()` closes the server and cleans up the socket file
|
|
858
|
+
|
|
859
|
+
### Socket Server
|
|
860
|
+
|
|
861
|
+
- Standard Node.js `net.createServer`
|
|
862
|
+
- Each connection gets its own buffer for newline-delimited framing
|
|
863
|
+
- Requests are handled asynchronously — multiple concurrent requests work
|
|
864
|
+
- Socket file created with `chmod 600` for security
|
|
865
|
+
|
|
866
|
+
### Terminal ID Assignment
|
|
867
|
+
|
|
868
|
+
- `WeakMap<Terminal, string>` maps VS Code Terminal objects to stable string IDs
|
|
869
|
+
- IDs are monotonically increasing integers as strings ("1", "2", "3", ...)
|
|
870
|
+
- The WeakMap ensures IDs are garbage-collected when terminals are disposed
|
|
871
|
+
- For wrapped terminals created via the profile provider, the ID is pre-reserved before the Terminal object exists
|
|
872
|
+
|
|
873
|
+
### Wrapped Terminal Mechanics
|
|
874
|
+
|
|
875
|
+
1. `createTerminal({ shellPath: "scripts/terminal-wrapper.sh", env: { CLAWS_TERM_LOG: path } })`
|
|
876
|
+
2. The wrapper script runs: `exec script -q "$CLAWS_TERM_LOG" /bin/zsh -il`
|
|
877
|
+
3. `script(1)` creates a pseudo-terminal pair and records all output to the log file
|
|
878
|
+
4. The user's shell runs inside this pseudo-terminal — completely transparent
|
|
879
|
+
5. `readLog` opens the log file, reads a byte range, strips ANSI via regex, returns clean text
|
|
880
|
+
|
|
881
|
+
### ANSI Stripping
|
|
882
|
+
|
|
883
|
+
Two regex patterns applied sequentially:
|
|
884
|
+
- CSI sequences: `[\x1b\x9b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-ntqry=><]`
|
|
885
|
+
- Control characters: `[\x00-\x08\x0b-\x1a\x1c-\x1f\x7f]`
|
|
886
|
+
|
|
887
|
+
This removes colors, cursor positioning, screen clearing, and control chars. Newlines (`\n`) and tabs (`\t`) are preserved.
|
|
888
|
+
|
|
889
|
+
### Configuration Resolution
|
|
890
|
+
|
|
891
|
+
Settings are read via `vscode.workspace.getConfiguration('claws')` on every request. No caching — changes take effect immediately without reload. Defaults are hardcoded constants that match `package.json` defaults.
|