octomux 1.0.26 → 1.0.29
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/README.md +137 -139
- package/bin/octomux-hook-bridge.js +183 -0
- package/bin/octomux-hook-bridge.test.ts +331 -0
- package/bin/octomux.js +48 -9
- package/cli/dist/client.js +51 -0
- package/cli/dist/commands/add-agent.js +16 -6
- package/cli/dist/commands/create-task.js +109 -7
- package/cli/dist/commands/hooks-install.js +109 -0
- package/cli/dist/commands/hooks-list.js +177 -0
- package/cli/dist/commands/init.js +144 -0
- package/cli/dist/commands/list-integrations.js +46 -0
- package/cli/dist/commands/post-review.js +56 -0
- package/cli/dist/commands/task-move.js +35 -0
- package/cli/dist/commands/task-note.js +20 -0
- package/cli/dist/commands/task-ref-add.js +45 -0
- package/cli/dist/commands/task-ref-rm.js +16 -0
- package/cli/dist/commands/task-summary.js +22 -0
- package/cli/dist/commands/task-updates.js +39 -0
- package/cli/dist/index.js +22 -0
- package/dist/assets/AgentEditor-QUlkrdA5.js +1 -0
- package/dist/assets/ChatPage-CALzjRsY.js +2 -0
- package/dist/assets/IntegrationsPage-DkG_es-f.js +1 -0
- package/dist/assets/ReviewDetailPage-C7fP9lo8.js +1 -0
- package/dist/assets/SetupPage-DfjpuFVM.js +1 -0
- package/dist/assets/SkillEditor-D3Nw8VdK.js +1 -0
- package/dist/assets/WorkspaceDetailPage-CspHhuC-.js +1 -0
- package/dist/assets/WorkspacesPage-DiKDrVBL.js +1 -0
- package/dist/assets/editor.worker-DyuDvpVp.js +26 -0
- package/dist/assets/index-CjGE0FA2.css +1 -0
- package/dist/assets/index-rvTUZ746.js +30 -0
- package/dist/assets/vendor-monaco-CdNyTACs.js +11 -0
- package/dist/assets/{vendor-react-B7E9rUeC.js → vendor-react-Dd26lTa7.js} +9 -9
- package/dist/assets/vendor-router-yJbofmar.js +12 -0
- package/dist/assets/vendor-ui-ChI1tM4e.js +17 -0
- package/dist/index.html +8 -5
- package/dist-server/agents-7NORWYDI.js +1 -0
- package/dist-server/chunk-5TX2AE6V.js +2 -0
- package/dist-server/chunk-6RXAD73K.js +312 -0
- package/dist-server/chunk-GBD6IX6H.js +1 -0
- package/dist-server/chunk-JJNLZ7U4.js +2 -0
- package/dist-server/chunk-L74QMQAV.js +1 -0
- package/dist-server/chunk-OJAAM2XK.js +1 -0
- package/dist-server/chunk-PE6WB7WG.js +36 -0
- package/dist-server/chunk-RGKFIAYK.js +2 -0
- package/dist-server/chunk-UZUF5CZR.js +1 -0
- package/dist-server/chunk-WFZH7745.js +2 -0
- package/dist-server/chunk-WSL26W3H.js +16 -0
- package/dist-server/chunk-XMCSUNYM.js +1 -0
- package/dist-server/claude-code-H6WHSYXB.js +1 -0
- package/dist-server/harnesses-DH3SOF4F.js +1 -0
- package/dist-server/hook-settings-ENUKEL6E.js +1 -0
- package/dist-server/hooks-install-KIDJPWBY.js +1 -0
- package/dist-server/index.js +326 -117
- package/dist-server/prefill-3FPWBCW4.js +14 -0
- package/dist-server/preflight-7M4CZVOF.js +13 -0
- package/dist-server/publish-review-7GG3AEZI.js +7 -0
- package/dist-server/registry-QDC56XWP.js +1 -0
- package/dist-server/resolve-env-GWFARUZQ.js +1 -0
- package/dist-server/settings-EYM2VUM7.js +1 -0
- package/dist-server/setup-status-SRJSWBUH.js +1 -0
- package/dist-server/startup.js +1 -1
- package/dist-server/store-6VCU4ZDL.js +1 -0
- package/package.json +7 -2
- package/scripts/demo-claude-welcome.sh +33 -0
- package/scripts/patch-docs-screenshots.ts +90 -0
- package/scripts/rollup-to-feat.sh +99 -0
- package/scripts/seed-docs-demo.ts +219 -0
- package/skills/add-agent/SKILL.md +71 -0
- package/skills/create-pr/SKILL.md +1 -1
- package/skills/create-task/SKILL.md +90 -6
- package/skills/review-orchestrator/SKILL.md +192 -0
- package/skills/update-task-status/SKILL.md +132 -0
- package/templates/hooks/jira-status/jira-status +25 -0
- package/templates/hooks/jira-status/jira-status.config.json +6 -0
- package/templates/hooks/jira-status/template.json +4 -0
- package/dist/assets/AgentEditor-BMirVLY6.js +0 -1
- package/dist/assets/OrchestratorPage-CdDThrSZ.js +0 -2
- package/dist/assets/SettingsPage-CMS7vQBd.js +0 -9
- package/dist/assets/SkillEditor-QV9JvpQN.js +0 -1
- package/dist/assets/TaskDetail-nrpoIbuk.js +0 -2
- package/dist/assets/TerminalView-CwdJ4h_g.js +0 -3
- package/dist/assets/index-CUA5BI9p.js +0 -2
- package/dist/assets/index-CjY4eMm_.css +0 -1
- package/dist/assets/use-editor-shortcuts-CMkuYcgB.js +0 -1
- package/dist/assets/vendor-monaco-Bz7hO8Jn.js +0 -11
- package/dist/assets/vendor-router-CDRByCDK.js +0 -12
- package/dist/assets/vendor-ui-Diw4ZhZG.js +0 -31
package/README.md
CHANGED
|
@@ -4,185 +4,183 @@
|
|
|
4
4
|
|
|
5
5
|
# octomux
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
> **Coding got faster. Managing agents didn't.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
A local web app to **dispatch, watch, and review** parallel **Claude Code** and **Cursor** agents from one place. Kanban for fleet status. One inbox for every "allow this tool?" prompt. In-app diff review with **Ship**. No cloud. MIT.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## The Loop
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
1. INTAKE Pull Jira tickets or GitHub issues — auto-creates tasks with prompts
|
|
17
|
-
2. EXECUTE Each task gets its own worktree, branch, and Claude Code agents
|
|
18
|
-
3. SUPERVISE Live terminals + notifications when agents finish or need attention
|
|
19
|
-
4. REVIEW Built-in lazygit & lazyvim — check diffs without leaving octomux
|
|
20
|
-
5. MERGE PRs auto-detected and linked — tasks auto-close when PRs merge
|
|
21
|
-
6. RESUME Close your laptop. Reboot. Everything picks back up automatically.
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g octomux && octomux init && cd your-repo && octomux start
|
|
22
13
|
```
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
## Features
|
|
27
|
-
|
|
28
|
-
### Orchestrator: agents managing agents
|
|
15
|
+
Open [http://localhost:7777](http://localhost:7777) — describe a task in the composer, pick **Claude Code** or **Cursor**, and watch agents work in place.
|
|
29
16
|
|
|
30
|
-
|
|
17
|
+
## From prompt to merged PR
|
|
31
18
|
|
|
32
|
-
|
|
19
|
+
Three phases, one window:
|
|
33
20
|
|
|
34
|
-
|
|
21
|
+
- **01 — Dispatch.** Type a task. Pick Claude Code or Cursor. Hit go. The composer takes plain English, Jira links, or GitHub issue URLs. Drop a second agent on the same branch with one click.
|
|
22
|
+
- **02 — Watch.** See every agent work, live. Each task streams its own view — files the agent is editing, the diff as it grows, terminal output as it runs. When an agent needs permission, the prompt lands in your inbox so you don't have to babysit every pane.
|
|
23
|
+
- **03 — Review & Ship.** Diff review in the same window. File tree, per-file reviewed state, inline comments. Hit **Ship** and the PR auto-links to the task — closes itself when the PR merges.
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
Code never leaves your laptop. No telemetry, no cloud sync. Crash, reboot, close the lid — `octomux start` restores every task, branch, and session.
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
- Works with Jira MCP, GitHub CLI, or anything your agent can read
|
|
40
|
-
- Auto-generates task names and initial prompts from ticket context
|
|
41
|
-
- Draft tasks: create in draft mode, edit title/prompt/branch before starting
|
|
27
|
+
## Screenshots
|
|
42
28
|
|
|
43
|
-
|
|
29
|
+
| | |
|
|
30
|
+
| ----------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
31
|
+
| **Home inbox + composer** — permission prompts, recent activity, dispatch bar |  |
|
|
32
|
+
| **Command center** — kanban from backlog → done |  |
|
|
33
|
+
| **Settings** — default harness, Cursor model & `--force` |  |
|
|
34
|
+
| **Task cockpit** — agent tabs, live Claude session, Ship, Done |  |
|
|
35
|
+
| **Diff review** — file tree, reviewed state, inline comments |  |
|
|
44
36
|
|
|
45
|
-
|
|
46
|
-
appears in `octomux recent-repos`), octomux stages a **draft** review task
|
|
47
|
-
prefilled with `/review-pr <url>` and PR context
|
|
48
|
-
- Tasks stay in draft until you approve — no agents start automatically
|
|
49
|
-
- If you haven't approved and the request is withdrawn (review submitted / PR
|
|
50
|
-
closed / reviewer removed), the draft is cleaned up on the next poll
|
|
51
|
-
- If the review is already running when new commits arrive, the existing agent
|
|
52
|
-
is nudged via a tmux message — no duplicate task is created
|
|
53
|
-
- Owner identity is resolved once via `gh api user` and cached; override with
|
|
54
|
-
`OCTOMUX_GITHUB_LOGIN` if you want octomux to watch a different account
|
|
55
|
-
|
|
56
|
-
### Isolated execution
|
|
37
|
+
## Features
|
|
57
38
|
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
39
|
+
- **Sessions inbox** — every permission prompt and question lands in one place; reply once, agents keep going. Tab title shows `(N) octomux` when something needs you.
|
|
40
|
+
- **Command center** — kanban for backlog → done; drag status, archive, workflow from draft → ship.
|
|
41
|
+
- **In-app diff review** — compare to `main`, mark files reviewed, queue inline comments, open lazygit in-editor.
|
|
42
|
+
- **Dual harnesses** — run **Claude Code** (`claude`) or **Cursor** (`cursor-agent`) per task; mix agents on one task via **Add agent**.
|
|
43
|
+
- **Worktrees keep agents off each other** — each task gets its own git worktree and `agents/<task-id>` branch; five agents can edit `auth.ts` at the same time without conflicts on your main tree.
|
|
44
|
+
- **Live task view** — see every agent work in real time: files edited, diff growing, terminal output streaming via xterm.js. Attach the same session from the CLI if you prefer.
|
|
45
|
+
- **Agents that dispatch agents** — `/create-task`, `/list-tasks`, `/send-agent-message` skills work inside any Claude Code window; recursive dispatch from inside an agent.
|
|
46
|
+
- **Integrations** — Jira wiring plus orchestrator skills for GitHub / auto-review intake.
|
|
47
|
+
- **CLI ↔ dashboard parity** — `octomux create-task`, `send-message`, `resume-task` — same tasks the UI shows.
|
|
48
|
+
- **Reboot-proof** — WAL SQLite + preserved worktrees across restarts.
|
|
49
|
+
- **Local-only** — no telemetry, no cloud sync, no analytics. Your `.env` stays on the host.
|
|
62
50
|
|
|
63
|
-
|
|
51
|
+
## Patterns
|
|
64
52
|
|
|
65
|
-
|
|
66
|
-
- Color-coded agent activity: green (active), gray (idle), amber (waiting for input)
|
|
67
|
-
- Notifications when agents finish, stop unexpectedly, or hit permission prompts
|
|
68
|
-
- Browser tab shows `(N) octomux` + red favicon dot when tasks need attention
|
|
69
|
-
- Toast notifications with "View" button — one click to the relevant agent
|
|
70
|
-
- Send messages to running agents via `octomux send-message`
|
|
71
|
-
- Smart status: tasks needing attention surface before idle tasks
|
|
53
|
+
Three workflows octomux makes one-click:
|
|
72
54
|
|
|
73
|
-
|
|
55
|
+
### Verifier — two agents, two opinions
|
|
74
56
|
|
|
75
|
-
|
|
57
|
+
Claude wrote it. Drop Cursor on the same branch for a second pass. Same-model self-review is just self-confirmation — a different model reads the diff without inheriting the first agent's assumptions. Catches missing nonce checks, off-by-one TTLs, and the kind of mistakes that pass type-checking but break in prod.
|
|
76
58
|
|
|
77
|
-
|
|
78
|
-
- Open ad-hoc shell terminals in any task's worktree from the dashboard
|
|
79
|
-
- No context switching to a separate terminal or IDE
|
|
80
|
-
- Clean branches ready to push and PR when you're satisfied
|
|
59
|
+
> Finish a task with Claude → hit **Add agent** → pick Cursor → reviews land as inline comments → you arbitrate, Ship.
|
|
81
60
|
|
|
82
|
-
|
|
61
|
+
### Sweep — five PRs by lunch
|
|
83
62
|
|
|
84
|
-
|
|
63
|
+
Paste a Jira filter or GitHub issue list into the composer. Each ticket gets its own worktree, branch, and agent. Mix Claude and Cursor across the batch so the model best at each kind of task ends up on it. Come back from standup to a kanban of ready-to-review PRs.
|
|
85
64
|
|
|
86
|
-
|
|
87
|
-
- PR URLs auto-linked in task cards — visible from the dashboard
|
|
88
|
-
- Tasks auto-close when their PRs are merged
|
|
89
|
-
- Zero manual status updates from task creation to merged code
|
|
65
|
+
### Operator — one prompt becomes an epic
|
|
90
66
|
|
|
91
|
-
|
|
67
|
+
Give an agent the orchestrator skills. It plans the work, breaks down the spec, and dispatches subtasks — each one gets its own worktree, mergeable independently. Inside a Claude Code window, the agent itself becomes the user of octomux. You supervise from the dashboard.
|
|
92
68
|
|
|
93
|
-
-
|
|
94
|
-
- On next `octomux start`, running tasks are recovered and agent sessions resume
|
|
95
|
-
- Close your laptop, come back tomorrow, run `octomux start`, keep going
|
|
69
|
+
> `/create-task`, `/list-tasks`, `/send-agent-message` skills inside any Claude Code window. Recursive dispatch.
|
|
96
70
|
|
|
97
|
-
|
|
71
|
+
## Quick start
|
|
98
72
|
|
|
99
|
-
|
|
73
|
+
```bash
|
|
74
|
+
brew install tmux git
|
|
75
|
+
npm install -g @anthropic-ai/claude-code # and/or Cursor CLI
|
|
76
|
+
npm install -g octomux
|
|
77
|
+
octomux init
|
|
78
|
+
cd your-project
|
|
79
|
+
octomux start
|
|
80
|
+
```
|
|
100
81
|
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
82
|
+
```bash
|
|
83
|
+
octomux create-task -t "Add OAuth login" -r .
|
|
84
|
+
octomux create-task -t "Spike with Cursor" -r . --harness cursor
|
|
85
|
+
```
|
|
104
86
|
|
|
105
|
-
|
|
87
|
+
Step-by-step setup, Jira, and orchestrator skills: [ONBOARDING.md](./ONBOARDING.md)
|
|
106
88
|
|
|
107
|
-
##
|
|
89
|
+
## How it works
|
|
108
90
|
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
cd your-project
|
|
112
|
-
octomux start # opens dashboard at localhost:7777
|
|
113
|
-
octomux create-task -t "Add auth flow" -r . # create a task from the CLI
|
|
91
|
+
```
|
|
92
|
+
DISPATCH → BRANCH → CODE → INBOX → REVIEW → MERGE
|
|
114
93
|
```
|
|
115
94
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
| `octomux
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
| `octomux
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
| Task completion | Manual status updates | Auto-closes on PR merge |
|
|
141
|
-
| After a reboot | Start over | Auto-resumes |
|
|
142
|
-
| Reviewing changes | Switch to terminal + git | Built-in lazygit |
|
|
143
|
-
| Agent safety | Hope for the best | Graduated trust: denied/allowed/prompted |
|
|
144
|
-
| Lifecycle management | None | draft → running → closed |
|
|
145
|
-
|
|
146
|
-
## How It Works
|
|
95
|
+
| Phase | What happens |
|
|
96
|
+
| ------------ | --------------------------------------------------------------------------- |
|
|
97
|
+
| **Dispatch** | Composer, CLI, orchestrator skills, or Jira/GitHub drafts |
|
|
98
|
+
| **Branch** | Automatic git worktree + `agents/<task-id>` branch |
|
|
99
|
+
| **Code** | tmux session per task; harness launches `claude` or `cursor-agent` |
|
|
100
|
+
| **Inbox** | Every permission prompt or question collects in one place |
|
|
101
|
+
| **Review** | Diff tab, lazygit terminal, mark files reviewed, **Ship** / **Done** |
|
|
102
|
+
| **Merge** | PR poller links branches; tasks close when their PRs merge |
|
|
103
|
+
| _Recovery_ | DB + worktrees survive reboot — `octomux start` picks up where you left off |
|
|
104
|
+
|
|
105
|
+
## CLI
|
|
106
|
+
|
|
107
|
+
| Command | Description |
|
|
108
|
+
| ------------------------------------ | -------------------------------------------------------- |
|
|
109
|
+
| `octomux start` | Dashboard at `:7777` |
|
|
110
|
+
| `octomux init` | Defaults wizard (Jira, base branch, harness prefs) |
|
|
111
|
+
| `octomux create-task` | New task (`--harness cursor` optional) |
|
|
112
|
+
| `octomux list-tasks` / `get-task` | Inspect tasks |
|
|
113
|
+
| `octomux close-task` / `delete-task` | Stop or fully remove |
|
|
114
|
+
| `octomux resume-task` | Resume a closed task |
|
|
115
|
+
| `octomux add-agent` | Another agent window |
|
|
116
|
+
| `octomux send-message` | Message a running agent — course-correct without restart |
|
|
117
|
+
|
|
118
|
+
## Architecture
|
|
147
119
|
|
|
148
120
|
```mermaid
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
121
|
+
flowchart LR
|
|
122
|
+
subgraph intake [Intake]
|
|
123
|
+
C[Composer]
|
|
124
|
+
CLI[CLI / skills]
|
|
125
|
+
end
|
|
126
|
+
subgraph core [octomux]
|
|
127
|
+
API[API + SQLite]
|
|
128
|
+
IN[Inbox]
|
|
129
|
+
BC[Command center]
|
|
130
|
+
end
|
|
131
|
+
subgraph run [Per task]
|
|
132
|
+
WT[Worktree]
|
|
133
|
+
TM[tmux]
|
|
134
|
+
H[Claude or Cursor]
|
|
135
|
+
end
|
|
136
|
+
C --> API
|
|
137
|
+
CLI --> API
|
|
138
|
+
API --> WT
|
|
139
|
+
API --> TM
|
|
140
|
+
TM --> H
|
|
141
|
+
H -->|hooks| API
|
|
142
|
+
API --> IN
|
|
143
|
+
API --> BC
|
|
144
|
+
WT --> DIFF[Diff review]
|
|
145
|
+
H --> GH[GitHub PR]
|
|
146
|
+
GH -->|poller| API
|
|
161
147
|
```
|
|
162
148
|
|
|
163
149
|
## Requirements
|
|
164
150
|
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
- **
|
|
168
|
-
-
|
|
169
|
-
- **Claude Code CLI**: `npm install -g @anthropic-ai/claude-code`
|
|
170
|
-
|
|
171
|
-
> Xcode Command Line Tools (`xcode-select --install`) may be needed if native dependencies (`better-sqlite3`, `node-pty`) require local compilation.
|
|
151
|
+
- macOS (ARM64 or x64), Node.js 20+
|
|
152
|
+
- `tmux`, `git`
|
|
153
|
+
- At least one harness: **Claude Code** (`claude`) and/or **Cursor CLI** (`cursor-agent`)
|
|
154
|
+
- Recommended: `lazygit`, `neovim`
|
|
172
155
|
|
|
173
156
|
## Configuration
|
|
174
157
|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
| `--port
|
|
178
|
-
|
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
158
|
+
| Variable / flag | Purpose |
|
|
159
|
+
| ------------------------- | -------------------------------- |
|
|
160
|
+
| `OCTOMUX_PORT` / `--port` | Dashboard port (default `7777`) |
|
|
161
|
+
| `OCTOMUX_URL` | CLI → API base URL |
|
|
162
|
+
| `OCTOMUX_DB_PATH` | Override task DB path |
|
|
163
|
+
| `OCTOMUX_GITHUB_LOGIN` | Reviewer-request polling account |
|
|
164
|
+
|
|
165
|
+
## FAQ
|
|
166
|
+
|
|
167
|
+
**What's the difference between octomux and just running tmux + Claude Code?**
|
|
168
|
+
octomux adds the kanban, the inbox, and diff review on top. tmux is just plumbing underneath.
|
|
169
|
+
|
|
170
|
+
**Does it work with Cursor?**
|
|
171
|
+
Yes. Pick Claude Code or Cursor per task. Mix them on the same task with **Add agent**.
|
|
172
|
+
|
|
173
|
+
**What happens if two agents touch the same file?**
|
|
174
|
+
They can't — each task runs in its own git worktree on its own branch. Five agents can edit `auth.ts` at the same time without conflicts on your main tree.
|
|
175
|
+
|
|
176
|
+
**What if my laptop reboots or crashes?**
|
|
177
|
+
Run `octomux start`. Tasks, branches, terminals, and review state all come back.
|
|
178
|
+
|
|
179
|
+
**How do I track what each agent is costing me?**
|
|
180
|
+
Each agent's tmux session has its own session log; Claude Code and Cursor both emit token usage there. A first-class cost view in the dashboard is on the roadmap.
|
|
181
181
|
|
|
182
182
|
## Links
|
|
183
183
|
|
|
184
|
-
- GitHub
|
|
185
|
-
- npm: [npmjs.com/package/octomux](https://www.npmjs.com/package/octomux)
|
|
186
|
-
- Landing page: [octomux.com](https://octomux.com)
|
|
184
|
+
- [GitHub](https://github.com/ShreyPaharia/octomux) · [npm](https://www.npmjs.com/package/octomux) · [octomux.dev](https://octomux.dev)
|
|
187
185
|
|
|
188
|
-
|
|
186
|
+
Issues and PRs welcome.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* octomux-hook-bridge.js — Cursor hook bridge script
|
|
5
|
+
*
|
|
6
|
+
* Self-contained Node.js script (no npm dependencies). Copied into each
|
|
7
|
+
* worktree's .octomux-hooks/ directory alongside a config.json that carries
|
|
8
|
+
* {baseUrl, token}. Cursor invokes this as a hook command with event JSON
|
|
9
|
+
* on stdin; the bridge forwards it to the octomux server and writes the
|
|
10
|
+
* response JSON to stdout.
|
|
11
|
+
*
|
|
12
|
+
* Fail-open principle: any error (network, parse, missing config) writes '{}'
|
|
13
|
+
* to stdout and exits 0 so Cursor is never blocked by bridge failures.
|
|
14
|
+
*
|
|
15
|
+
* The bridge NEVER exits with code 2. Deny is communicated via stdout JSON
|
|
16
|
+
* {"permission":"deny",...} with exit 0.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import http from 'node:http';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
// Hardcoded denylist — mirrors Claude's DENIED_TOOLS destructive set.
|
|
25
|
+
// These regexes are checked against event.command for beforeShellExecution.
|
|
26
|
+
const DENYLIST = [
|
|
27
|
+
{ re: /^\s*rm\s+-rf(\s|$)/, label: 'rm -rf' },
|
|
28
|
+
{ re: /^\s*git\s+push\s+--force(\s|$)/, label: 'git push --force' },
|
|
29
|
+
{ re: /^\s*git\s+reset\s+--hard(\s|$)/, label: 'git reset --hard' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function writeStdout(obj) {
|
|
33
|
+
process.stdout.write(JSON.stringify(obj));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* POST JSON to a local http:// URL. Returns a promise that resolves with the
|
|
38
|
+
* response status code on success, or rejects on network/timeout error.
|
|
39
|
+
*/
|
|
40
|
+
function postJson(url, token, body) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const parsed = new URL(url);
|
|
43
|
+
const qs = '?token=' + encodeURIComponent(token);
|
|
44
|
+
|
|
45
|
+
const options = {
|
|
46
|
+
protocol: parsed.protocol,
|
|
47
|
+
hostname: parsed.hostname,
|
|
48
|
+
port: parsed.port || 80,
|
|
49
|
+
path: parsed.pathname + qs,
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const payload = JSON.stringify(body);
|
|
57
|
+
|
|
58
|
+
const req = http.request(options, (res) => {
|
|
59
|
+
// Consume response body to free socket
|
|
60
|
+
res.resume();
|
|
61
|
+
res.on('end', () => resolve(res.statusCode));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// 5-second timeout — fail-open on expiry
|
|
65
|
+
req.setTimeout(5000, () => {
|
|
66
|
+
req.destroy(new Error('Hook bridge request timed out'));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
req.on('error', reject);
|
|
70
|
+
req.write(payload);
|
|
71
|
+
req.end();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function main() {
|
|
76
|
+
// Step 1: read all stdin synchronously. fd 0 = stdin.
|
|
77
|
+
let stdinText = '';
|
|
78
|
+
try {
|
|
79
|
+
stdinText = fs.readFileSync(0, 'utf8');
|
|
80
|
+
} catch {
|
|
81
|
+
// No stdin available — treat as empty
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Step 2: parse stdin JSON. Malformed input → fail-open.
|
|
85
|
+
let event;
|
|
86
|
+
try {
|
|
87
|
+
event = JSON.parse(stdinText);
|
|
88
|
+
} catch {
|
|
89
|
+
writeStdout({});
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Step 3: resolve and read sibling config.json.
|
|
94
|
+
let baseUrl, token;
|
|
95
|
+
try {
|
|
96
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
97
|
+
const configPath = path.join(scriptDir, 'config.json');
|
|
98
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
99
|
+
baseUrl = config.baseUrl;
|
|
100
|
+
token = config.token;
|
|
101
|
+
if (!baseUrl || !token) throw new Error('config.json missing baseUrl or token');
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('[octomux-hook-bridge] Failed to load config.json:', err);
|
|
104
|
+
writeStdout({});
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Step 4: branch on hook_event_name.
|
|
109
|
+
const eventName = event.hook_event_name;
|
|
110
|
+
|
|
111
|
+
if (eventName === 'sessionStart') {
|
|
112
|
+
try {
|
|
113
|
+
await postJson(baseUrl + '/api/hooks/session-start', token, {
|
|
114
|
+
conversation_id: event.conversation_id,
|
|
115
|
+
session_id: event.session_id,
|
|
116
|
+
is_background_agent: event.is_background_agent ?? false,
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
// HTTP error — fail-open
|
|
120
|
+
console.error('[octomux-hook-bridge] sessionStart HTTP error:', err);
|
|
121
|
+
}
|
|
122
|
+
writeStdout({});
|
|
123
|
+
} else if (eventName === 'beforeSubmitPrompt') {
|
|
124
|
+
try {
|
|
125
|
+
await postJson(baseUrl + '/api/hooks/user-prompt-submit', token, {
|
|
126
|
+
conversation_id: event.conversation_id,
|
|
127
|
+
prompt: event.prompt,
|
|
128
|
+
});
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error('[octomux-hook-bridge] beforeSubmitPrompt HTTP error:', err);
|
|
131
|
+
}
|
|
132
|
+
writeStdout({ continue: true });
|
|
133
|
+
} else if (eventName === 'beforeShellExecution') {
|
|
134
|
+
// Apply denylist locally — no HTTP call.
|
|
135
|
+
const command = event.command ?? '';
|
|
136
|
+
for (const rule of DENYLIST) {
|
|
137
|
+
if (rule.re.test(command)) {
|
|
138
|
+
writeStdout({
|
|
139
|
+
permission: 'deny',
|
|
140
|
+
user_message: 'Blocked by octomux denylist: ' + rule.label,
|
|
141
|
+
});
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
writeStdout({ permission: 'allow' });
|
|
146
|
+
} else if (eventName === 'postToolUse' || eventName === 'afterFileEdit') {
|
|
147
|
+
// Normalize both events to the {tool_name, tool_input} shape that
|
|
148
|
+
// /api/hooks/post-tool-use expects (Claude's PostToolUse shape).
|
|
149
|
+
// - postToolUse already carries tool_name + tool_input, pass through.
|
|
150
|
+
// - afterFileEdit carries file_path at top-level; synthesize a tool_use
|
|
151
|
+
// so the server's current_summary deriver picks up the file path.
|
|
152
|
+
let body;
|
|
153
|
+
if (eventName === 'afterFileEdit') {
|
|
154
|
+
body = {
|
|
155
|
+
conversation_id: event.conversation_id,
|
|
156
|
+
tool_name: 'Edit',
|
|
157
|
+
tool_input: { file_path: event.file_path },
|
|
158
|
+
};
|
|
159
|
+
} else {
|
|
160
|
+
body = {
|
|
161
|
+
conversation_id: event.conversation_id,
|
|
162
|
+
tool_name: event.tool_name,
|
|
163
|
+
tool_input: event.tool_input,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
await postJson(baseUrl + '/api/hooks/post-tool-use', token, body);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error('[octomux-hook-bridge] postToolUse/afterFileEdit HTTP error:', err);
|
|
170
|
+
}
|
|
171
|
+
writeStdout({});
|
|
172
|
+
} else {
|
|
173
|
+
// Unknown event — fail-open
|
|
174
|
+
writeStdout({});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main().catch((err) => {
|
|
179
|
+
// Top-level safety net — any unhandled error must not crash Cursor
|
|
180
|
+
console.error('[octomux-hook-bridge] Unhandled error:', err);
|
|
181
|
+
writeStdout({});
|
|
182
|
+
process.exit(0);
|
|
183
|
+
});
|