octomux 1.0.34 → 1.2.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.
Files changed (125) hide show
  1. package/README.md +116 -159
  2. package/bin/octomux.js +20 -0
  3. package/cli/dist/client.js +53 -57
  4. package/cli/dist/commands/close-task.js +1 -1
  5. package/cli/dist/commands/create-task.js +9 -10
  6. package/cli/dist/commands/emit.js +43 -0
  7. package/cli/dist/commands/files.js +67 -0
  8. package/cli/dist/commands/get-task.js +2 -2
  9. package/cli/dist/commands/list-tasks.js +3 -3
  10. package/cli/dist/commands/loop-start.js +46 -0
  11. package/cli/dist/commands/resume-task.js +3 -3
  12. package/cli/dist/commands/task-move.js +4 -11
  13. package/cli/dist/commands/task-ref-add.js +4 -5
  14. package/cli/dist/commands/task-updates.js +4 -15
  15. package/cli/dist/format.js +11 -0
  16. package/cli/dist/index.js +6 -0
  17. package/dist/assets/AgentEditor-DVCWGh2b.js +1 -0
  18. package/dist/assets/ChatPage-DXvFd2xr.js +2 -0
  19. package/dist/assets/CommentsSidePanel-BKIMCwSJ.js +1 -0
  20. package/dist/assets/DiffViewer-77QVL2bZ.js +2 -0
  21. package/dist/assets/GridMonitor-BIe8TDEF.js +1 -0
  22. package/dist/assets/IntegrationsPage-BFb-byxK.js +1 -0
  23. package/dist/assets/LoopDetailPage-l9TDCPhI.js +1 -0
  24. package/dist/assets/LoopsPage-DDWQV0_h.js +1 -0
  25. package/dist/assets/OrchestratorPage-iUSULr-Y.js +29 -0
  26. package/dist/assets/ReviewDetailPage-54jOke8o.js +1 -0
  27. package/dist/assets/SetupPage-BnbDbJDk.js +1 -0
  28. package/dist/assets/SkillEditor-BlqQO6gb.js +1 -0
  29. package/dist/assets/TaskDetail-Bte65fAQ.js +2 -0
  30. package/dist/assets/TerminalView-wW5iq4Iq.js +4 -0
  31. package/dist/assets/WorkspaceDetailPage-D-xhW6XQ.js +1 -0
  32. package/dist/assets/WorkspacesPage-tp4IaNJ8.js +1 -0
  33. package/dist/assets/index-BhwDhOat.js +20 -0
  34. package/dist/assets/index-Ct_vkUxD.css +1 -0
  35. package/dist/assets/loopApi-DsQuudoF.js +1 -0
  36. package/dist/assets/{vendor-monaco-CdNyTACs.js → vendor-monaco-BpPnCqoL.js} +1 -1
  37. package/dist/assets/{vendor-react-Dd26lTa7.js → vendor-react-BQxe05fg.js} +1 -1
  38. package/dist/assets/{vendor-router-yJbofmar.js → vendor-router-BxmO8ihp.js} +1 -1
  39. package/dist/assets/{vendor-ui-ChI1tM4e.js → vendor-ui-48-ysyeR.js} +1 -1
  40. package/dist/index.html +5 -8
  41. package/dist-server/agent-session/mcp/submit-result-server.js +8 -0
  42. package/dist-server/agents-VYS62A6H.js +1 -0
  43. package/dist-server/approval-timeout-OPTH3SZG.js +1 -0
  44. package/dist-server/chunk-5OMOAZI3.js +4 -0
  45. package/dist-server/chunk-7QEE4QK5.js +14 -0
  46. package/dist-server/chunk-CNLQE5QM.js +2 -0
  47. package/dist-server/chunk-CNU4N4AY.js +1 -0
  48. package/dist-server/chunk-D4UO62ED.js +166 -0
  49. package/dist-server/chunk-H5475QQG.js +370 -0
  50. package/dist-server/chunk-HSYWKRQH.js +2 -0
  51. package/dist-server/chunk-IVNXMVZM.js +19 -0
  52. package/dist-server/chunk-JOI5VEEJ.js +1 -0
  53. package/dist-server/chunk-KPCVZLAA.js +1 -0
  54. package/dist-server/{chunk-OSXDDM27.js → chunk-M7J44Q6F.js} +1 -1
  55. package/dist-server/chunk-MLCNFNCF.js +1 -0
  56. package/dist-server/chunk-PC2DITVE.js +1 -0
  57. package/dist-server/chunk-SS2365DW.js +451 -0
  58. package/dist-server/chunk-UWUVS7F4.js +2 -0
  59. package/dist-server/chunk-WLB7K3JL.js +1 -0
  60. package/dist-server/chunk-XQ3CJFA4.js +4 -0
  61. package/dist-server/{chunk-4GEKZIZ6.js → chunk-XQ7EQ3DU.js} +1 -1
  62. package/dist-server/chunk-ZC3XFIMO.js +1 -0
  63. package/dist-server/claude-code-VZI5GZ37.js +1 -0
  64. package/dist-server/cli-review.js +94 -0
  65. package/dist-server/harnesses-AOHWBLSJ.js +1 -0
  66. package/dist-server/hook-settings-BE6Q7CXD.js +1 -0
  67. package/dist-server/hooks-install-KBBZ2HEZ.js +1 -0
  68. package/dist-server/index.js +25 -279
  69. package/dist-server/orchestrator/mcp/server.js +2 -0
  70. package/dist-server/prefill-QQYRVBGG.js +1 -0
  71. package/dist-server/preflight-6BB6CXWJ.js +2 -0
  72. package/dist-server/publish-review-R66GB4OG.js +5 -0
  73. package/dist-server/registry-Q5TISXTP.js +1 -0
  74. package/dist-server/resolve-env-YZ3RWEBZ.js +1 -0
  75. package/dist-server/settings-E5ZOHEFR.js +1 -0
  76. package/dist-server/setup-status-PIO3H5GT.js +1 -0
  77. package/dist-server/startup.js +1 -1
  78. package/dist-server/store-FBJOBU7X.js +1 -0
  79. package/dist-server/teams-BJCOLHUO.js +6 -0
  80. package/package.json +28 -6
  81. package/scripts/record-fixtures.ts +57 -0
  82. package/scripts/spike/transcript-tail.ts +241 -0
  83. package/skills/add-agent/SKILL.md +44 -8
  84. package/skills/create-task/SKILL.md +23 -5
  85. package/skills/review-deep/SKILL.md +184 -0
  86. package/skills/review-walkthrough/SKILL.md +107 -0
  87. package/skills/send-agent-message/SKILL.md +56 -16
  88. package/skills/update-task-status/SKILL.md +38 -7
  89. package/dist/assets/AgentEditor-bmu3IToQ.js +0 -1
  90. package/dist/assets/ChatPage-DoNLt9yc.js +0 -2
  91. package/dist/assets/IntegrationsPage-D3iTxzhN.js +0 -1
  92. package/dist/assets/ReviewDetailPage-DxCK8Zq4.js +0 -1
  93. package/dist/assets/SetupPage-CJYmbm6K.js +0 -1
  94. package/dist/assets/SkillEditor-BXV-a19B.js +0 -1
  95. package/dist/assets/WorkspaceDetailPage-Cns6VZKt.js +0 -1
  96. package/dist/assets/WorkspacesPage-DI_Ilx7o.js +0 -1
  97. package/dist/assets/index-D2mLE9DX.css +0 -1
  98. package/dist/assets/index-DKTtIYvz.js +0 -35
  99. package/dist-server/agents-75BBHCTY.js +0 -1
  100. package/dist-server/chunk-3PYPZRQV.js +0 -1
  101. package/dist-server/chunk-4DGDU2NU.js +0 -49
  102. package/dist-server/chunk-BTEWK6EA.js +0 -333
  103. package/dist-server/chunk-BYZCD2AS.js +0 -16
  104. package/dist-server/chunk-GAXJZSQT.js +0 -2
  105. package/dist-server/chunk-MCI4HOI7.js +0 -2
  106. package/dist-server/chunk-MYIYBV6O.js +0 -16
  107. package/dist-server/chunk-NHDUWYXX.js +0 -2
  108. package/dist-server/chunk-NUVE5API.js +0 -23
  109. package/dist-server/chunk-RFWGJ3HT.js +0 -1
  110. package/dist-server/chunk-YKFMOBXG.js +0 -2
  111. package/dist-server/chunk-ZV3CXK7O.js +0 -1
  112. package/dist-server/claude-code-SUTWME7T.js +0 -1
  113. package/dist-server/harnesses-ACMXKLJO.js +0 -1
  114. package/dist-server/hook-settings-VQOP567H.js +0 -1
  115. package/dist-server/hooks-install-5WIG6NCP.js +0 -1
  116. package/dist-server/prefill-IKIJSGHJ.js +0 -14
  117. package/dist-server/preflight-W4357G4A.js +0 -13
  118. package/dist-server/publish-review-3X766STN.js +0 -7
  119. package/dist-server/registry-QDC56XWP.js +0 -1
  120. package/dist-server/resolve-env-GWFARUZQ.js +0 -1
  121. package/dist-server/settings-VDSTIV6M.js +0 -1
  122. package/dist-server/setup-status-TDHBDU7E.js +0 -1
  123. package/dist-server/store-7ATWFPHP.js +0 -1
  124. package/dist-server/teams-DOJN7H42.js +0 -22
  125. package/skills/review-orchestrator/SKILL.md +0 -205
package/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![CI](https://github.com/ShreyPaharia/octomux/actions/workflows/ci.yml/badge.svg)](https://github.com/ShreyPaharia/octomux/actions/workflows/ci.yml)
1
2
  [![npm version](https://img.shields.io/npm/v/octomux)](https://www.npmjs.com/package/octomux)
2
3
  [![license](https://img.shields.io/github/license/ShreyPaharia/octomux)](LICENSE)
3
4
  [![GitHub stars](https://img.shields.io/github/stars/ShreyPaharia/octomux)](https://github.com/ShreyPaharia/octomux)
@@ -6,25 +7,27 @@
6
7
 
7
8
  > **Coding got faster. Managing agents didn't.**
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
+ octomux is a **local dashboard for running many Claude Code and Cursor agents in parallel.** Each agent works in its own git worktree; you get **one inbox** for every "allow this tool?" prompt, a **live grid** of the whole fleet, and **in-app diff review** with a Ship button. Runs on your machine — no cloud, no telemetry, MIT.
10
11
 
11
12
  ```bash
12
13
  npm install -g octomux && octomux init && cd your-repo && octomux start
13
14
  ```
14
15
 
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.
16
+ Open **[localhost:7777](http://localhost:7777)**, describe a task, pick **Claude Code** or **Cursor**, and watch it work.
16
17
 
17
18
  ![octomux demo](assets/demo.gif)
18
19
 
19
- ## From prompt to merged PR
20
+ ---
20
21
 
21
- Three phases, one window:
22
+ ## What you get
22
23
 
23
- - **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 clickor paste a whole list and **bulk-create** a worktree, branch, and agent for each.
24
- - **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. The **Monitor** view tiles every running agent into one grid so you can scan the whole fleet at a glance. When an agent needs permission, the prompt lands in your inbox so you don't have to babysit every pane.
25
- - **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.
24
+ Three phases, one windowfrom prompt to merged PR:
26
25
 
27
- Code never leaves your laptop. No telemetry, no cloud sync. Crash, reboot, close the lid `octomux start` restores every task, branch, and session.
26
+ - **① Dispatch** Type a task (or paste a Jira/Linear/GitHub link, or a whole list). Each one gets its own worktree, branch, and agent. Pick the model per task.
27
+ - **② Watch** — Every agent's live terminal, the diff as it grows, and a **Monitor grid** of the whole fleet. Permission prompts land in one **inbox** instead of scattered across panes.
28
+ - **③ Review & Ship** — Diff review in the same window: mark files reviewed, leave inline comments, send them back to the agent to fix, then **Ship** to open the PR — which auto-closes the task when it merges.
29
+
30
+ Crash, reboot, close the lid — `octomux start` restores every task, branch, and session.
28
31
 
29
32
  ## Screenshots
30
33
 
@@ -32,191 +35,145 @@ Code never leaves your laptop. No telemetry, no cloud sync. Crash, reboot, close
32
35
  | ----------------------------------------------------------------------------- | -------------------------------------------------------- |
33
36
  | **Home inbox + composer** — permission prompts, recent activity, dispatch bar | ![Home](assets/screenshots/dashboard-hero.png) |
34
37
  | **Command center** — kanban from backlog → done | ![Command center](assets/screenshots/command-center.png) |
35
- | **Settings** — default harness, Cursor model & `--force` | ![Settings](assets/screenshots/settings-harnesses.png) |
36
- | **Task cockpit** — agent tabs, live Claude session, Ship, Done | ![Task detail](assets/screenshots/task-detail.png) |
38
+ | **Task cockpit** — agent tabs, live session, Ship, Done | ![Task detail](assets/screenshots/task-detail.png) |
37
39
  | **Diff review** — file tree, reviewed state, inline comments | ![Diff](assets/screenshots/diff-review.png) |
38
40
 
39
41
  ## Features
40
42
 
41
- - **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.
42
- - **Command center** — kanban for backlog → done; drag status, archive, workflow from draft → ship. Empty board? A first-run guide shows you where to start.
43
- - **Monitor grid** — every running agent tiled into one auto-sized grid; scan the whole fleet's activity (active / idle / waiting) without opening each task.
44
- - **Bulk dispatch** — paste a list of prompts or a GitHub issue list and spin up one task worktree, branch, agent per line in a single shot.
45
- - **In-app diff review** — compare to `main`, mark files reviewed, queue inline comments, open lazygit in-editor.
46
- - **Dual harnesses** — run **Claude Code** (`claude`) or **Cursor** (`cursor-agent`) per task; mix agents on one task via **Add agent**.
47
- - **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.
48
- - **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.
49
- - **Agents that dispatch agents** — `/create-task`, `/list-tasks`, `/send-agent-message` skills work inside any Claude Code window; recursive dispatch from inside an agent.
50
- - **Agent teams** reusable crews defined as code in `<repo>/.octomux/team.yaml`: a lead spawns workers from a roster, each with its own model. Run on demand (`octomux team run`) or on a cron schedule.
51
- - **Per-task model** — pin any task or added agent to a specific model (`--model claude-opus-4-8`); mix models across a fleet so the right one lands on each job.
52
- - **Integrations** — Jira wiring plus orchestrator skills for GitHub / auto-review intake.
53
- - **CLI ↔ dashboard parity** — `octomux create-task`, `send-message`, `resume-task` — same tasks the UI shows.
54
- - **Reboot-proof** — WAL SQLite + preserved worktrees across restarts.
55
- - **Local-only** — no telemetry, no cloud sync, no analytics. Your `.env` stays on the host.
43
+ Each screen is a lens over one managed agent backend:
44
+
45
+ - **Sessions inbox** — every permission prompt across every agent in one place; reply once, agents keep going. Tab title shows `(N) octomux` when something needs you.
46
+ - **Command center** — kanban across the real workflow (backlog planned in progress review PR done), with filter-to-attention and a restore grace period on delete.
47
+ - **Monitor grid** — every running agent's terminal tiled into one live wall; spot the stuck one instantly.
48
+ - **Orchestrator view** — watch an agent that dispatches agents: the parent planning, its children coding, who's blocked the whole tree at once.
49
+ - **Review workstation** an agent drafts a walkthrough + inline comments (grounded against the real diff, no invented line numbers); nothing hits GitHub until you accept it, then it posts as one batched review. Reject a comment with a reason and it remembers for next time.
50
+ - **Chats, Workspaces, Skill & Agent editors** — detach a quick spike as its own session, manage the reusable worktrees behind your tasks, and author your Claude Code skills and subagents in the browser.
51
+ - **Agent teams** — reusable crews as code in `<repo>/.octomux/team.yaml`; a lead spawns workers (each with its own model) on demand or on a schedule.
52
+ - **Worktrees keep agents off each other** five agents can edit `auth.ts` at once without conflicts on your main tree.
53
+ - **Run it anywhere** — npm CLI, a **macOS desktop app** ([`.dmg`](https://github.com/ShreyPaharia/octomux/releases)), or hosted on a box and reached from your **phone over Tailscale** (the UI is mobile-ready).
54
+ - **Local-only** — no telemetry, no cloud sync. Your `.env` stays on the host.
56
55
 
57
56
  ## Patterns
58
57
 
59
58
  Three workflows octomux makes one-click:
60
59
 
61
- ### Verifier — two agents, two opinions
60
+ - **Verifier — two agents, two opinions.** Claude wrote it; drop Cursor on the same branch for a second pass. A different model reads the diff without inheriting the first's assumptions, catching the bugs that pass type-checking but break in prod.
61
+ - **Sweep — five PRs by lunch.** Paste a Jira filter or GitHub issue list; each ticket gets its own worktree and agent. Come back from standup to a kanban of ready-to-review PRs.
62
+ - **Operator — one prompt becomes an epic.** Give an agent the orchestrator skills; it plans a spec, breaks it into subtasks, and dispatches each into its own worktree. You supervise from the Orchestrator view.
62
63
 
63
- 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.
64
+ ## How it compares
64
65
 
65
- > Finish a task with Claude → hit **Add agent** pick Cursor → reviews land as inline comments → you arbitrate, Ship.
66
+ | | **octomux** | vibe-kanban | Conductor | Emdash |
67
+ | -------------------------------------- | ----------------- | ----------------- | ------------- | --------------- |
68
+ | License | MIT, open source | MIT (community\*) | Closed | Open source |
69
+ | Fully local, no cloud | Yes | Now local\* | Cloud account | Yes |
70
+ | One permission inbox | **Yes** | No | No | No |
71
+ | Monitor grid (all agents at once) | **Yes** | No | No | No |
72
+ | Automated review + human-gated publish | **Yes** | Partial | Partial | No |
73
+ | Recursive orchestration | **Yes** | No | No | No |
74
+ | Reach it from your phone | **Yes** (tailnet) | No | No | Partial (SSH) |
75
+ | Claude Code + Cursor | Yes | Yes (10+) | Yes | Yes (20+) |
76
+ | Platform | macOS + Linux | macOS/Linux/Win | macOS only | macOS/Linux/Win |
66
77
 
67
- ### Sweep five PRs by lunch
78
+ <sub>\* Bloop, the company behind vibe-kanban, wound down in early 2026; it continues as a community project.</sub>
68
79
 
69
- 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.
80
+ ## Why octomux
70
81
 
71
- ### Operator one prompt becomes an epic
82
+ The editor was built around a human typing one file at a time. That's not the job anymore. The job is directing a fleet — and the hard part moved from _writing_ code to _reviewing_ it, _unblocking_ it, and _knowing what's happening_ across ten sessions.
72
83
 
73
- 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.
84
+ octomux is a bet on what that surface should look like: not a chat box bolted onto a file tree, but a control deck. It handles the ugly backend of running agents and puts the human's job — the inbox, the fleet grid, the review workstation, the orchestrator front and center. It's early and opinionated, and the roadmap is shaped in the open.
74
85
 
75
- > `/create-task`, `/list-tasks`, `/send-agent-message` skills inside any Claude Code window. Recursive dispatch.
86
+ ## Requirements
76
87
 
77
- ## Quick start
88
+ - macOS (arm64/x64) or Linux for the CLI; macOS for the desktop app
89
+ - Node.js 20+ · `git` (`tmux` ships bundled)
90
+ - At least one harness: **Claude Code** (`claude`) and/or **Cursor CLI** (`cursor-agent`)
78
91
 
79
- ```bash
80
- brew install tmux git
81
- npm install -g @anthropic-ai/claude-code # and/or Cursor CLI
82
- npm install -g octomux
83
- octomux init
84
- cd your-project
85
- octomux start
86
- ```
92
+ <details>
93
+ <summary><b>Full CLI reference</b></summary>
94
+
95
+ | Command | Description |
96
+ | ---------------------------------------- | ---------------------------------------------------------- |
97
+ | `octomux start` | Dashboard at `:7777` (add `--bind 0.0.0.0` for remote) |
98
+ | `octomux init` | Defaults wizard (Jira/Linear, base branch, harness prefs) |
99
+ | `octomux create-task` | New task (`--harness`, `--model`, `--mode`, `--fork-from`) |
100
+ | `octomux list-tasks` / `get-task` | Inspect tasks |
101
+ | `octomux close-task` / `delete-task` | Stop or fully remove |
102
+ | `octomux resume-task` | Resume a closed task |
103
+ | `octomux add-agent` | Another agent window (`--model`, `--notify-agent`) |
104
+ | `octomux send-message` | Message a running agent — course-correct without restart |
105
+ | `octomux team run` / `schedule` / `list` | Run or schedule an agent crew from `.octomux/team.yaml` |
106
+
107
+ Full setup, Jira/Linear, and orchestrator skills: **[ONBOARDING.md](./ONBOARDING.md)**.
108
+
109
+ </details>
110
+
111
+ <details>
112
+ <summary><b>Remote access from your phone (Tailscale)</b></summary>
113
+
114
+ octomux binds to `127.0.0.1` by default. To reach it from another device, put them on a
115
+ [Tailscale](https://tailscale.com) tailnet and start in remote mode:
87
116
 
88
117
  ```bash
89
- octomux create-task -t "Add OAuth login" -r .
90
- octomux create-task -t "Spike with Cursor" -r . --harness cursor
118
+ octomux start --bind 0.0.0.0 # or: OCTOMUX_BIND=0.0.0.0 octomux start
91
119
  ```
92
120
 
93
- Step-by-step setup, Jira, and orchestrator skills: [ONBOARDING.md](./ONBOARDING.md)
121
+ A random access token is generated on first start (path logged to
122
+ `~/.octomux/data/remote-token`; override with `OCTOMUX_REMOTE_TOKEN`). Open
123
+ `http://<host-magicdns-name>:7777` from a tailnet device and sign in once. Only tailnet
124
+ devices can reach the port; the token is a second factor. For HTTPS, front it with
125
+ `tailscale serve`.
94
126
 
95
- ## How it works
127
+ </details>
96
128
 
97
- ```
98
- DISPATCH BRANCH → CODE → INBOX → REVIEW → MERGE
99
- ```
129
+ <details>
130
+ <summary><b>Built to extend</b></summary>
100
131
 
101
- | Phase | What happens |
102
- | ------------ | --------------------------------------------------------------------------- |
103
- | **Dispatch** | Composer, CLI, orchestrator skills, or Jira/GitHub drafts |
104
- | **Branch** | Automatic git worktree + `agents/<task-id>` branch |
105
- | **Code** | tmux session per task; harness launches `claude` or `cursor-agent` |
106
- | **Inbox** | Every permission prompt or question collects in one place |
107
- | **Review** | Diff tab, lazygit terminal, mark files reviewed, **Ship** / **Done** |
108
- | **Merge** | PR poller links branches; tasks close when their PRs merge |
109
- | _Recovery_ | DB + worktrees survive reboot — `octomux start` picks up where you left off |
110
-
111
- ## CLI
112
-
113
- | Command | Description |
114
- | ---------------------------------------- | -------------------------------------------------------- |
115
- | `octomux start` | Dashboard at `:7777` |
116
- | `octomux init` | Defaults wizard (Jira, base branch, harness prefs) |
117
- | `octomux create-task` | New task (`--harness cursor` optional) |
118
- | `octomux list-tasks` / `get-task` | Inspect tasks |
119
- | `octomux close-task` / `delete-task` | Stop or fully remove |
120
- | `octomux resume-task` | Resume a closed task |
121
- | `octomux add-agent` | Another agent window (`--skeleton`, `--model` optional) |
122
- | `octomux send-message` | Message a running agent — course-correct without restart |
123
- | `octomux team run` / `schedule` / `list` | Run or schedule an agent crew from `.octomux/team.yaml` |
124
-
125
- ## Architecture
126
-
127
- ```mermaid
128
- flowchart LR
129
- subgraph intake [Intake]
130
- C[Composer]
131
- CLI[CLI / skills]
132
- end
133
- subgraph core [octomux]
134
- API[API + SQLite]
135
- IN[Inbox]
136
- BC[Command center]
137
- end
138
- subgraph run [Per task]
139
- WT[Worktree]
140
- TM[tmux]
141
- H[Claude or Cursor]
142
- end
143
- C --> API
144
- CLI --> API
145
- API --> WT
146
- API --> TM
147
- TM --> H
148
- H -->|hooks| API
149
- API --> IN
150
- API --> BC
151
- WT --> DIFF[Diff review]
152
- H --> GH[GitHub PR]
153
- GH -->|poller| API
154
- ```
132
+ octomux keeps a clean line between the **agent backend** (done for you) and the **views**
133
+ (where the value is). Building blocks available today:
155
134
 
156
- ## Requirements
135
+ - **REST API** (~95 endpoints) over tasks, agents, diffs, reviews, chats, workspaces, skills.
136
+ - **Two live WebSocket channels** — `/ws/events` for task/chat/review events, `/ws/terminal/*` for bidirectional xterm ↔ tmux.
137
+ - **A queryable SQLite schema** — tasks, agents, permission prompts, review runs, comments, learnings.
138
+ - **A pluggable harness interface** — add a new agent backend by implementing one interface and registering it.
139
+ - **User hook scripts** — drop executables in `~/.octomux/hooks` to fire on task-lifecycle events.
157
140
 
158
- - macOS (ARM64 or x64), Node.js 20+ (24 LTS recommended)
159
- - `tmux`, `git`
160
- - At least one harness: **Claude Code** (`claude`) and/or **Cursor CLI** (`cursor-agent`)
161
- - Recommended: `lazygit`, `neovim`
162
-
163
- > First run flags only the deps you're actually missing — install what the setup banner asks for and you're good. Jira and other integrations are configured later from the in-app **Integrations** page.
164
-
165
- ## Configuration
166
-
167
- | Variable / flag | Purpose |
168
- | ------------------------- | -------------------------------- |
169
- | `OCTOMUX_PORT` / `--port` | Dashboard port (default `7777`) |
170
- | `OCTOMUX_URL` | CLI → API base URL |
171
- | `OCTOMUX_DB_PATH` | Override task DB path |
172
- | `OCTOMUX_GITHUB_LOGIN` | Reviewer-request polling account |
173
-
174
- ## Remote access over Tailscale
175
-
176
- octomux binds to `127.0.0.1` by default — reachable only from the host machine. To view
177
- and control sessions from your other devices (phone, second laptop), put them on a
178
- [Tailscale](https://tailscale.com) tailnet and enable remote mode:
179
-
180
- 1. Install Tailscale on the host and each device; run `tailscale up` on each. Enable
181
- MagicDNS so the host is reachable by name.
182
- 2. Start octomux in remote mode:
183
- ```bash
184
- octomux start --bind 0.0.0.0
185
- # or: OCTOMUX_BIND=0.0.0.0 octomux start
186
- ```
187
- On first start a random access token is generated and its file path is logged
188
- (`~/.octomux/data/remote-token`). Override it with `OCTOMUX_REMOTE_TOKEN=<secret>`.
189
- 3. (Optional) Restrict the accepted `Host` header to your tailnet name:
190
- `OCTOMUX_ALLOWED_HOSTS=mybox.your-tailnet.ts.net`. The `100.64.0.0/10` tailnet IP range
191
- is accepted automatically.
192
- 4. From a device on the tailnet, open `http://<host-magicdns-name>:7777` and sign in once
193
- with the token. Local (loopback) access never requires the token.
194
-
195
- **Security notes:** only devices on your tailnet can reach the port; the token is a second
196
- factor so a single compromised tailnet device cannot silently drive your agents. Binding
197
- `0.0.0.0` also exposes the port on any other LAN the host is on — the token gates those out,
198
- and you can add a host firewall rule limiting port 7777 to the `100.64.0.0/10` range for
199
- belt-and-suspenders. For HTTPS, front octomux with `tailscale serve`.
141
+ There isn't a drop-in plugin API for custom UI views yet adding one means building against
142
+ these blocks in the codebase. A first-class way to author and share views is the direction
143
+ we're building toward; if that's what you want, [open an issue](https://github.com/ShreyPaharia/octomux/issues).
144
+
145
+ </details>
200
146
 
201
147
  ## FAQ
202
148
 
203
- **What's the difference between octomux and just running tmux + Claude Code?**
204
- octomux adds the kanban, the inbox, and diff review on top. tmux is just plumbing underneath.
149
+ **How is this different from tmux + Claude Code?** octomux adds the inbox, the fleet grid, the review workstation, and the orchestrator view on top. tmux is plumbing underneath.
205
150
 
206
- **Does it work with Cursor?**
207
- Yes. Pick Claude Code or Cursor per task. Mix them on the same task with **Add agent**.
151
+ **What if two agents touch the same file?** They can't — each task runs in its own git worktree on its own branch.
208
152
 
209
- **What happens if two agents touch the same file?**
210
- 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.
153
+ **Can I use it from my phone?** Yes — host it on a tailnet box and open the mobile-ready dashboard from any device on the tailnet.
211
154
 
212
- **What if my laptop reboots or crashes?**
213
- Run `octomux start`. Tasks, branches, terminals, and review state all come back.
155
+ **What if my laptop reboots?** Run `octomux start`; tasks, branches, terminals, and review state come back.
214
156
 
215
- **How do I track what each agent is costing me?**
216
- 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.
157
+ ## Contributing
217
158
 
218
- ## Links
159
+ Issues and PRs are welcome — the roadmap is shaped in the open.
160
+
161
+ ```bash
162
+ git clone https://github.com/ShreyPaharia/octomux && cd octomux
163
+ bun install
164
+ bun run dev # Express :7777 + Vite
165
+ bun run test # vitest
166
+ ```
167
+
168
+ Then open a PR **against `next`** with a short description of the change. See
169
+ **[CONTRIBUTING.md](./CONTRIBUTING.md)** for architecture and testing patterns, and
170
+ [good first issues](https://github.com/ShreyPaharia/octomux/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
171
+ to get started. We try to respond to PRs within a couple of days.
219
172
 
220
- - [GitHub](https://github.com/ShreyPaharia/octomux) · [npm](https://www.npmjs.com/package/octomux) · [octomux.dev](https://octomux.dev)
173
+ ## Star it
174
+
175
+ If octomux saves you an afternoon of babysitting agents, a ⭐ helps other people find it — and tells me which parts to build next. Thanks for trying it.
176
+
177
+ ## Links
221
178
 
222
- Issues and PRs welcome.
179
+ [GitHub](https://github.com/ShreyPaharia/octomux) · [npm](https://www.npmjs.com/package/octomux) · [octomux.com](https://octomux.com) · [Releases](https://github.com/ShreyPaharia/octomux/releases)
package/bin/octomux.js CHANGED
@@ -69,6 +69,7 @@ async function runStart(startArgs) {
69
69
 
70
70
  // Install bundled skills and configs
71
71
  installSkills();
72
+ installWorkflows();
72
73
  installLazygitConfig();
73
74
 
74
75
  // Ensure cli/ dependencies are installed
@@ -268,3 +269,22 @@ function installSkills() {
268
269
  console.log('Installed octomux skills for Claude Code');
269
270
  }
270
271
  }
272
+
273
+ function installWorkflows() {
274
+ const src = path.join(__dirname, '..', 'workflows');
275
+ const target = path.join(os.homedir(), '.claude', 'workflows');
276
+ if (!existsSync(src)) return;
277
+
278
+ mkdirSync(target, { recursive: true });
279
+ let installed = false;
280
+ for (const file of readdirSync(src)) {
281
+ if (!file.endsWith('.js')) continue;
282
+ // Overwrite so workflow updates actually ship (unlike skills, which only
283
+ // install when missing). The review-deep skill invokes these by scriptPath.
284
+ cpSync(path.join(src, file), path.join(target, file));
285
+ installed = true;
286
+ }
287
+ if (installed) {
288
+ console.log('Installed octomux review workflows for Claude Code');
289
+ }
290
+ }
@@ -1,154 +1,150 @@
1
- function qs(params) {
2
- const entries = Object.entries(params).filter(([, v]) => v !== undefined);
3
- if (entries.length === 0)
4
- return '';
5
- return '?' + entries.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
6
- }
7
- async function request(baseUrl, path, options) {
8
- let res;
9
- try {
10
- res = await fetch(`${baseUrl}${path}`, {
11
- headers: { 'Content-Type': 'application/json' },
12
- ...options,
13
- });
14
- }
15
- catch (err) {
16
- if (err.cause &&
17
- err.cause.code === 'ECONNREFUSED') {
18
- throw new Error(`Cannot connect to octomux server at ${baseUrl.replace('/api', '')}\nStart it with: octomux start`);
19
- }
20
- throw err;
1
+ import { createRequestCore, qs } from '@octomux/api-client';
2
+ function cliFetchError(err, { baseUrl }) {
3
+ if (err.cause &&
4
+ err.cause.code === 'ECONNREFUSED') {
5
+ throw new Error(`Cannot connect to octomux server at ${baseUrl.replace('/api', '')}\nStart it with: octomux start`);
21
6
  }
22
- if (!res.ok) {
23
- const body = await res.json().catch(() => ({ error: res.statusText }));
24
- throw new Error(body.error || res.statusText);
25
- }
26
- if (res.status === 204)
27
- return undefined;
28
- return res.json();
7
+ throw err;
29
8
  }
30
9
  export function createClient(serverUrl) {
31
10
  const baseUrl = serverUrl.replace(/\/$/, '') + '/api';
11
+ const { request } = createRequestCore({
12
+ baseUrl,
13
+ alwaysJsonContentType: true,
14
+ onFetchError: cliFetchError,
15
+ });
32
16
  return {
33
17
  listIntegrations() {
34
- return request(baseUrl, '/integrations');
18
+ return request('/integrations');
35
19
  },
36
20
  createTask(data) {
37
- return request(baseUrl, '/tasks', { method: 'POST', body: JSON.stringify(data) });
21
+ return request('/tasks', { method: 'POST', body: JSON.stringify(data) });
38
22
  },
39
23
  listTasks(params) {
40
- return request(baseUrl, `/tasks${qs({ repo_path: params?.repo_path })}`);
24
+ return request(`/tasks${qs({ repo_path: params?.repo_path })}`);
41
25
  },
42
26
  getTask(id) {
43
- return request(baseUrl, `/tasks/${encodeURIComponent(id)}`);
27
+ return request(`/tasks/${encodeURIComponent(id)}`);
44
28
  },
45
29
  updateTask(id, data) {
46
- return request(baseUrl, `/tasks/${encodeURIComponent(id)}`, {
30
+ return request(`/tasks/${encodeURIComponent(id)}`, {
47
31
  method: 'PATCH',
48
32
  body: JSON.stringify(data),
49
33
  });
50
34
  },
51
35
  deleteTask(id) {
52
- return request(baseUrl, `/tasks/${encodeURIComponent(id)}`, { method: 'DELETE' });
36
+ return request(`/tasks/${encodeURIComponent(id)}`, { method: 'DELETE' });
53
37
  },
54
38
  addAgent(taskId, data) {
55
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/agents`, {
39
+ return request(`/tasks/${encodeURIComponent(taskId)}/agents`, {
56
40
  method: 'POST',
57
41
  body: JSON.stringify(data || {}),
58
42
  });
59
43
  },
60
44
  stopAgent(taskId, agentId) {
61
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/agents/${encodeURIComponent(agentId)}`, { method: 'DELETE' });
45
+ return request(`/tasks/${encodeURIComponent(taskId)}/agents/${encodeURIComponent(agentId)}`, { method: 'DELETE' });
46
+ },
47
+ startLoop(data) {
48
+ return request('/loops', { method: 'POST', body: JSON.stringify(data) });
62
49
  },
63
50
  sendMessage(taskId, agentId, message) {
64
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/agents/${encodeURIComponent(agentId)}/message`, { method: 'POST', body: JSON.stringify({ message }) });
51
+ return request(`/tasks/${encodeURIComponent(taskId)}/agents/${encodeURIComponent(agentId)}/message`, { method: 'POST', body: JSON.stringify({ message }) });
65
52
  },
66
53
  listSkills() {
67
- return request(baseUrl, '/skills');
54
+ return request('/skills');
68
55
  },
69
56
  getSkill(name) {
70
- return request(baseUrl, `/skills/${encodeURIComponent(name)}`);
57
+ return request(`/skills/${encodeURIComponent(name)}`);
71
58
  },
72
59
  createSkill(data) {
73
- return request(baseUrl, '/skills', {
60
+ return request('/skills', {
74
61
  method: 'POST',
75
62
  body: JSON.stringify(data),
76
63
  });
77
64
  },
78
65
  deleteSkill(name) {
79
- return request(baseUrl, `/skills/${encodeURIComponent(name)}`, { method: 'DELETE' });
66
+ return request(`/skills/${encodeURIComponent(name)}`, { method: 'DELETE' });
80
67
  },
81
68
  recentRepos() {
82
- return request(baseUrl, '/recent-repos');
69
+ return request('/recent-repos');
83
70
  },
84
71
  defaultBranch(repoPath) {
85
- return request(baseUrl, `/default-branch${qs({ repo_path: repoPath })}`);
72
+ return request(`/default-branch${qs({ repo_path: repoPath })}`);
86
73
  },
87
74
  getRepoConfig(repoPath) {
88
- return request(baseUrl, `/repo-config${qs({ repo_path: repoPath })}`);
75
+ return request(`/repo-config${qs({ repo_path: repoPath })}`);
89
76
  },
90
77
  postComment(taskId, data) {
91
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/comments`, {
78
+ return request(`/tasks/${encodeURIComponent(taskId)}/comments`, {
92
79
  method: 'POST',
93
80
  body: JSON.stringify(data),
94
81
  });
95
82
  },
96
83
  listComments(taskId, file) {
97
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/comments${qs({ file })}`);
84
+ return request(`/tasks/${encodeURIComponent(taskId)}/comments${qs({ file })}`);
98
85
  },
99
86
  updateComment(taskId, commentId, data) {
100
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/comments/${encodeURIComponent(commentId)}`, { method: 'PATCH', body: JSON.stringify(data) });
87
+ return request(`/tasks/${encodeURIComponent(taskId)}/comments/${encodeURIComponent(commentId)}`, { method: 'PATCH', body: JSON.stringify(data) });
101
88
  },
102
89
  deleteComment(taskId, commentId) {
103
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/comments/${encodeURIComponent(commentId)}`, { method: 'DELETE' });
90
+ return request(`/tasks/${encodeURIComponent(taskId)}/comments/${encodeURIComponent(commentId)}`, { method: 'DELETE' });
104
91
  },
105
92
  moveTask(taskId, data) {
106
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/move`, {
93
+ return request(`/tasks/${encodeURIComponent(taskId)}/move`, {
107
94
  method: 'POST',
108
95
  body: JSON.stringify(data),
109
96
  });
110
97
  },
111
98
  postTaskSummary(taskId, data) {
112
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/summary`, {
99
+ return request(`/tasks/${encodeURIComponent(taskId)}/summary`, {
113
100
  method: 'POST',
114
101
  body: JSON.stringify(data),
115
102
  });
116
103
  },
117
104
  postTaskNote(taskId, data) {
118
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/note`, {
105
+ return request(`/tasks/${encodeURIComponent(taskId)}/note`, {
119
106
  method: 'POST',
120
107
  body: JSON.stringify(data),
121
108
  });
122
109
  },
123
110
  addTaskRef(taskId, data) {
124
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/refs`, {
111
+ return request(`/tasks/${encodeURIComponent(taskId)}/refs`, {
125
112
  method: 'POST',
126
113
  body: JSON.stringify(data),
127
114
  });
128
115
  },
129
116
  deleteTaskRef(taskId, integration) {
130
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/refs/${encodeURIComponent(integration)}`, { method: 'DELETE' });
117
+ return request(`/tasks/${encodeURIComponent(taskId)}/refs/${encodeURIComponent(integration)}`, { method: 'DELETE' });
131
118
  },
132
119
  getTaskUpdates(taskId) {
133
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/updates`);
120
+ return request(`/tasks/${encodeURIComponent(taskId)}/updates`);
134
121
  },
135
122
  getTaskRefs(taskId) {
136
- return request(baseUrl, `/tasks/${encodeURIComponent(taskId)}/refs`);
123
+ return request(`/tasks/${encodeURIComponent(taskId)}/refs`);
137
124
  },
138
125
  teamRun(data) {
139
- return request(baseUrl, '/teams/run', {
126
+ return request('/teams/run', {
140
127
  method: 'POST',
141
128
  body: JSON.stringify(data),
142
129
  });
143
130
  },
144
131
  teamSchedule(data) {
145
- return request(baseUrl, '/teams/schedule', {
132
+ return request('/teams/schedule', {
146
133
  method: 'POST',
147
134
  body: JSON.stringify(data),
148
135
  });
149
136
  },
150
137
  listTeams() {
151
- return request(baseUrl, '/teams');
138
+ return request('/teams');
139
+ },
140
+ listSavedFiles(repoPath) {
141
+ return request(`/repos/${encodeURIComponent(repoPath)}/files`);
142
+ },
143
+ getSavedFile(repoPath, relPath) {
144
+ return request(`/repos/${encodeURIComponent(repoPath)}/files/content${qs({ path: relPath })}`);
145
+ },
146
+ putSavedFile(repoPath, relPath, content) {
147
+ return request(`/repos/${encodeURIComponent(repoPath)}/files/content${qs({ path: relPath })}`, { method: 'PUT', body: JSON.stringify({ content }) });
152
148
  },
153
149
  };
154
150
  }
@@ -6,7 +6,7 @@ export function registerCloseTask(program) {
6
6
  .description('Close a running task')
7
7
  .action(async (id, _opts, cmd) => {
8
8
  const { client, json } = getContext(cmd);
9
- const task = await client.updateTask(id, { status: 'closed' });
9
+ const task = await client.updateTask(id, { runtime_state: 'idle' });
10
10
  if (json) {
11
11
  outputJson(task);
12
12
  return;