octomux 1.1.0 → 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.
- package/README.md +114 -169
- package/cli/dist/client.js +3 -0
- package/cli/dist/commands/emit.js +43 -0
- package/cli/dist/commands/loop-start.js +46 -0
- package/cli/dist/index.js +4 -0
- package/dist/assets/AgentEditor-DVCWGh2b.js +1 -0
- package/dist/assets/ChatPage-DXvFd2xr.js +2 -0
- package/dist/assets/CommentsSidePanel-BKIMCwSJ.js +1 -0
- package/dist/assets/DiffViewer-77QVL2bZ.js +2 -0
- package/dist/assets/GridMonitor-BIe8TDEF.js +1 -0
- package/dist/assets/IntegrationsPage-BFb-byxK.js +1 -0
- package/dist/assets/LoopDetailPage-l9TDCPhI.js +1 -0
- package/dist/assets/LoopsPage-DDWQV0_h.js +1 -0
- package/dist/assets/{OrchestratorPage-B6-5Ymz_.js → OrchestratorPage-iUSULr-Y.js} +2 -2
- package/dist/assets/ReviewDetailPage-54jOke8o.js +1 -0
- package/dist/assets/SetupPage-BnbDbJDk.js +1 -0
- package/dist/assets/SkillEditor-BlqQO6gb.js +1 -0
- package/dist/assets/TaskDetail-Bte65fAQ.js +2 -0
- package/dist/assets/TerminalView-wW5iq4Iq.js +4 -0
- package/dist/assets/WorkspaceDetailPage-D-xhW6XQ.js +1 -0
- package/dist/assets/WorkspacesPage-tp4IaNJ8.js +1 -0
- package/dist/assets/index-BhwDhOat.js +20 -0
- package/dist/assets/index-Ct_vkUxD.css +1 -0
- package/dist/assets/loopApi-DsQuudoF.js +1 -0
- package/dist/index.html +2 -5
- package/dist-server/{agents-45L5TJD3.js → agents-VYS62A6H.js} +1 -1
- package/dist-server/{approval-timeout-NYVPHUHG.js → approval-timeout-OPTH3SZG.js} +1 -1
- package/dist-server/{chunk-U55T3B3U.js → chunk-5OMOAZI3.js} +1 -1
- package/dist-server/{chunk-S67AOMGA.js → chunk-7QEE4QK5.js} +2 -2
- package/dist-server/{chunk-R466QRBH.js → chunk-CNLQE5QM.js} +1 -1
- package/dist-server/{chunk-SFMUA6HF.js → chunk-D4UO62ED.js} +3 -3
- package/dist-server/{chunk-FQ324ITT.js → chunk-H5475QQG.js} +92 -90
- package/dist-server/{chunk-NLRGIOB5.js → chunk-HSYWKRQH.js} +1 -1
- package/dist-server/chunk-IVNXMVZM.js +19 -0
- package/dist-server/{chunk-COWASN3D.js → chunk-JOI5VEEJ.js} +1 -1
- package/dist-server/chunk-KPCVZLAA.js +1 -0
- package/dist-server/{chunk-YZSABAAX.js → chunk-M7J44Q6F.js} +1 -1
- package/dist-server/{chunk-OIRUBP4J.js → chunk-MLCNFNCF.js} +1 -1
- package/dist-server/{chunk-3CVFZVVG.js → chunk-SS2365DW.js} +50 -22
- package/dist-server/{chunk-JACWMK5E.js → chunk-XQ3CJFA4.js} +1 -1
- package/dist-server/{chunk-CZT76WRN.js → chunk-ZC3XFIMO.js} +1 -1
- package/dist-server/claude-code-VZI5GZ37.js +1 -0
- package/dist-server/cli-review.js +1 -1
- package/dist-server/{harnesses-2PWYFS77.js → harnesses-AOHWBLSJ.js} +1 -1
- package/dist-server/{hook-settings-KCX3NSNL.js → hook-settings-BE6Q7CXD.js} +1 -1
- package/dist-server/index.js +23 -24
- package/dist-server/orchestrator/mcp/server.js +1 -1
- package/dist-server/{preflight-TC52EHUN.js → preflight-6BB6CXWJ.js} +1 -1
- package/dist-server/{publish-review-QWKNLWKW.js → publish-review-R66GB4OG.js} +1 -1
- package/dist-server/{settings-YFZ7BOZK.js → settings-E5ZOHEFR.js} +1 -1
- package/dist-server/{setup-status-54WC6S2N.js → setup-status-PIO3H5GT.js} +1 -1
- package/dist-server/{store-PRUWQM53.js → store-FBJOBU7X.js} +1 -1
- package/dist-server/{teams-GIVRUA2J.js → teams-BJCOLHUO.js} +1 -1
- package/package.json +1 -1
- package/dist/assets/AgentEditor-DO3dt4ri.js +0 -1
- package/dist/assets/ChatPage-Be7NRaz5.js +0 -2
- package/dist/assets/IntegrationsPage-o2qrU4Ly.js +0 -1
- package/dist/assets/ReviewDetailPage-DvnR-g2n.js +0 -1
- package/dist/assets/SetupPage-CmzzPZne.js +0 -1
- package/dist/assets/SkillEditor-BYTCGSV_.js +0 -1
- package/dist/assets/WorkspaceDetailPage-ZCgJpIyZ.js +0 -1
- package/dist/assets/WorkspacesPage-8IwI9uiq.js +0 -1
- package/dist/assets/index-CQh8m5qn.css +0 -1
- package/dist/assets/index-D4uuZdW5.js +0 -34
- package/dist-server/chunk-B36SOK6W.js +0 -1
- package/dist-server/chunk-TKX326LX.js +0 -13
- package/dist-server/claude-code-M4NFPKSN.js +0 -1
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
[](https://github.com/ShreyPaharia/octomux/actions/workflows/ci.yml)
|
|
1
2
|
[](https://www.npmjs.com/package/octomux)
|
|
2
3
|
[](LICENSE)
|
|
3
4
|
[](https://github.com/ShreyPaharia/octomux)
|
|
@@ -6,25 +7,27 @@
|
|
|
6
7
|
|
|
7
8
|
> **Coding got faster. Managing agents didn't.**
|
|
8
9
|
|
|
9
|
-
|
|
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 [
|
|
16
|
+
Open **[localhost:7777](http://localhost:7777)**, describe a task, pick **Claude Code** or **Cursor**, and watch it work.
|
|
16
17
|
|
|
17
18
|

|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
---
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
## What you get
|
|
22
23
|
|
|
23
|
-
|
|
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 window — from prompt to merged PR:
|
|
26
25
|
|
|
27
|
-
|
|
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,203 +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 |  |
|
|
34
37
|
| **Command center** — kanban from backlog → done |  |
|
|
35
|
-
| **
|
|
36
|
-
| **Task cockpit** — agent tabs, live Claude session, Ship, Done |  |
|
|
38
|
+
| **Task cockpit** — agent tabs, live session, Ship, Done |  |
|
|
37
39
|
| **Diff review** — file tree, reviewed state, inline comments |  |
|
|
38
40
|
|
|
39
41
|
## Features
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- **
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
46
|
-
- **
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **Mobile-ready dashboard** — bottom nav, responsive pages, and mobile-friendly terminal scroll buttons; check the fleet and answer prompts from your phone.
|
|
54
|
-
- **Integrations** — Jira and Linear sync (status push + comment-back, composer prefill) plus orchestrator skills for GitHub / auto-review intake.
|
|
55
|
-
- **CLI ↔ dashboard parity** — `octomux create-task`, `send-message`, `resume-task` — same tasks the UI shows.
|
|
56
|
-
- **Reboot-proof** — WAL SQLite + preserved worktrees across restarts.
|
|
57
|
-
- **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.
|
|
58
55
|
|
|
59
56
|
## Patterns
|
|
60
57
|
|
|
61
58
|
Three workflows octomux makes one-click:
|
|
62
59
|
|
|
63
|
-
|
|
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.
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
## How it compares
|
|
66
65
|
|
|
67
|
-
|
|
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 |
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
<sub>\* Bloop, the company behind vibe-kanban, wound down in early 2026; it continues as a community project.</sub>
|
|
70
79
|
|
|
71
|
-
|
|
80
|
+
## Why octomux
|
|
72
81
|
|
|
73
|
-
|
|
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.
|
|
74
83
|
|
|
75
|
-
|
|
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.
|
|
76
85
|
|
|
77
|
-
|
|
86
|
+
## Requirements
|
|
78
87
|
|
|
79
|
-
|
|
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`)
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
npm install -g @anthropic-ai/claude-code # and/or Cursor CLI
|
|
84
|
-
npm install -g octomux
|
|
85
|
-
octomux init
|
|
86
|
-
cd your-project
|
|
87
|
-
octomux start
|
|
88
|
-
```
|
|
92
|
+
<details>
|
|
93
|
+
<summary><b>Full CLI reference</b></summary>
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
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:
|
|
92
116
|
|
|
93
117
|
```bash
|
|
94
|
-
octomux
|
|
95
|
-
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
|
|
96
119
|
```
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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`.
|
|
103
126
|
|
|
104
|
-
|
|
127
|
+
</details>
|
|
105
128
|
|
|
106
|
-
|
|
129
|
+
<details>
|
|
130
|
+
<summary><b>Built to extend</b></summary>
|
|
107
131
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
```
|
|
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:
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
| **Inbox** | Every permission prompt or question collects in one place |
|
|
118
|
-
| **Review** | Diff tab, lazygit terminal, mark files reviewed, **Ship** / **Done** |
|
|
119
|
-
| **Merge** | PR poller links branches; tasks close when their PRs merge |
|
|
120
|
-
| _Recovery_ | DB + worktrees survive reboot — `octomux start` picks up where you left off |
|
|
121
|
-
|
|
122
|
-
## CLI
|
|
123
|
-
|
|
124
|
-
| Command | Description |
|
|
125
|
-
| ---------------------------------------- | ------------------------------------------------------------------------- |
|
|
126
|
-
| `octomux start` | Dashboard at `:7777` |
|
|
127
|
-
| `octomux init` | Defaults wizard (Jira, base branch, harness prefs) |
|
|
128
|
-
| `octomux create-task` | New task (`--harness cursor` optional) |
|
|
129
|
-
| `octomux list-tasks` / `get-task` | Inspect tasks |
|
|
130
|
-
| `octomux close-task` / `delete-task` | Stop or fully remove |
|
|
131
|
-
| `octomux resume-task` | Resume a closed task |
|
|
132
|
-
| `octomux add-agent` | Another agent window (`--skeleton`, `--model`, `--notify-agent` optional) |
|
|
133
|
-
| `octomux send-message` | Message a running agent — course-correct without restart |
|
|
134
|
-
| `octomux team run` / `schedule` / `list` | Run or schedule an agent crew from `.octomux/team.yaml` |
|
|
135
|
-
|
|
136
|
-
## Architecture
|
|
137
|
-
|
|
138
|
-
```mermaid
|
|
139
|
-
flowchart LR
|
|
140
|
-
subgraph intake [Intake]
|
|
141
|
-
C[Composer]
|
|
142
|
-
CLI[CLI / skills]
|
|
143
|
-
end
|
|
144
|
-
subgraph core [octomux]
|
|
145
|
-
API[API + SQLite]
|
|
146
|
-
IN[Inbox]
|
|
147
|
-
BC[Command center]
|
|
148
|
-
end
|
|
149
|
-
subgraph run [Per task]
|
|
150
|
-
WT[Worktree]
|
|
151
|
-
TM[tmux]
|
|
152
|
-
H[Claude or Cursor]
|
|
153
|
-
end
|
|
154
|
-
C --> API
|
|
155
|
-
CLI --> API
|
|
156
|
-
API --> WT
|
|
157
|
-
API --> TM
|
|
158
|
-
TM --> H
|
|
159
|
-
H -->|hooks| API
|
|
160
|
-
API --> IN
|
|
161
|
-
API --> BC
|
|
162
|
-
WT --> DIFF[Diff review]
|
|
163
|
-
H --> GH[GitHub PR]
|
|
164
|
-
GH -->|poller| API
|
|
165
|
-
```
|
|
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.
|
|
166
140
|
|
|
167
|
-
|
|
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).
|
|
168
144
|
|
|
169
|
-
|
|
170
|
-
- `git` (`tmux` ships bundled — installed automatically, no manual step)
|
|
171
|
-
- At least one harness: **Claude Code** (`claude`) and/or **Cursor CLI** (`cursor-agent`)
|
|
172
|
-
- Recommended: `lazygit`, `neovim`
|
|
173
|
-
- macOS desktop app (`.dmg`) available on [GitHub Releases](https://github.com/ShreyPaharia/octomux/releases) as an alternative to the npm CLI
|
|
174
|
-
|
|
175
|
-
> First run flags only the deps you're actually missing — install what the setup banner asks for and you're good. Jira, Linear, and other integrations are configured later from the in-app **Integrations** page.
|
|
176
|
-
|
|
177
|
-
## Configuration
|
|
178
|
-
|
|
179
|
-
| Variable / flag | Purpose |
|
|
180
|
-
| ------------------------- | -------------------------------- |
|
|
181
|
-
| `OCTOMUX_PORT` / `--port` | Dashboard port (default `7777`) |
|
|
182
|
-
| `OCTOMUX_URL` | CLI → API base URL |
|
|
183
|
-
| `OCTOMUX_DB_PATH` | Override task DB path |
|
|
184
|
-
| `OCTOMUX_GITHUB_LOGIN` | Reviewer-request polling account |
|
|
185
|
-
|
|
186
|
-
## Remote access over Tailscale
|
|
187
|
-
|
|
188
|
-
octomux binds to `127.0.0.1` by default — reachable only from the host machine. To view
|
|
189
|
-
and control sessions from your other devices (phone, second laptop), put them on a
|
|
190
|
-
[Tailscale](https://tailscale.com) tailnet and enable remote mode:
|
|
191
|
-
|
|
192
|
-
1. Install Tailscale on the host and each device; run `tailscale up` on each. Enable
|
|
193
|
-
MagicDNS so the host is reachable by name.
|
|
194
|
-
2. Start octomux in remote mode:
|
|
195
|
-
```bash
|
|
196
|
-
octomux start --bind 0.0.0.0
|
|
197
|
-
# or: OCTOMUX_BIND=0.0.0.0 octomux start
|
|
198
|
-
```
|
|
199
|
-
On first start a random access token is generated and its file path is logged
|
|
200
|
-
(`~/.octomux/data/remote-token`). Override it with `OCTOMUX_REMOTE_TOKEN=<secret>`.
|
|
201
|
-
3. (Optional) Restrict the accepted `Host` header to your tailnet name:
|
|
202
|
-
`OCTOMUX_ALLOWED_HOSTS=mybox.your-tailnet.ts.net`. The `100.64.0.0/10` tailnet IP range
|
|
203
|
-
is accepted automatically.
|
|
204
|
-
4. From a device on the tailnet, open `http://<host-magicdns-name>:7777` and sign in once
|
|
205
|
-
with the token. Local (loopback) access never requires the token.
|
|
206
|
-
|
|
207
|
-
**Security notes:** only devices on your tailnet can reach the port; the token is a second
|
|
208
|
-
factor so a single compromised tailnet device cannot silently drive your agents. Binding
|
|
209
|
-
`0.0.0.0` also exposes the port on any other LAN the host is on — the token gates those out,
|
|
210
|
-
and you can add a host firewall rule limiting port 7777 to the `100.64.0.0/10` range for
|
|
211
|
-
belt-and-suspenders. For HTTPS, front octomux with `tailscale serve`.
|
|
145
|
+
</details>
|
|
212
146
|
|
|
213
147
|
## FAQ
|
|
214
148
|
|
|
215
|
-
**
|
|
216
|
-
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.
|
|
217
150
|
|
|
218
|
-
**
|
|
219
|
-
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.
|
|
220
152
|
|
|
221
|
-
**
|
|
222
|
-
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.
|
|
223
154
|
|
|
224
|
-
**What if my laptop reboots
|
|
225
|
-
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.
|
|
226
156
|
|
|
227
|
-
|
|
228
|
-
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
|
|
229
158
|
|
|
230
|
-
|
|
159
|
+
Issues and PRs are welcome — the roadmap is shaped in the open.
|
|
231
160
|
|
|
232
|
-
|
|
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.
|
|
172
|
+
|
|
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
|
|
233
178
|
|
|
234
|
-
|
|
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/cli/dist/client.js
CHANGED
|
@@ -44,6 +44,9 @@ export function createClient(serverUrl) {
|
|
|
44
44
|
stopAgent(taskId, agentId) {
|
|
45
45
|
return request(`/tasks/${encodeURIComponent(taskId)}/agents/${encodeURIComponent(agentId)}`, { method: 'DELETE' });
|
|
46
46
|
},
|
|
47
|
+
startLoop(data) {
|
|
48
|
+
return request('/loops', { method: 'POST', body: JSON.stringify(data) });
|
|
49
|
+
},
|
|
47
50
|
sendMessage(taskId, agentId, message) {
|
|
48
51
|
return request(`/tasks/${encodeURIComponent(taskId)}/agents/${encodeURIComponent(agentId)}/message`, { method: 'POST', body: JSON.stringify({ message }) });
|
|
49
52
|
},
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Option } from 'commander';
|
|
2
|
+
import { errorMessage, success } from '../format.js';
|
|
3
|
+
const EMIT_STATUSES = ['done', 'blocked', 'needs_human'];
|
|
4
|
+
/**
|
|
5
|
+
* octomux emit — the loop-harness completion callback. Reads the base URL and
|
|
6
|
+
* bearer token from the same OCTOMUX_ACTION_* env vars the orchestrator's
|
|
7
|
+
* other hook-calling code (server/orchestrator/mcp/write.ts) reads, since
|
|
8
|
+
* they're set into the loop agent's shell the same way.
|
|
9
|
+
*/
|
|
10
|
+
export function registerEmit(program) {
|
|
11
|
+
program
|
|
12
|
+
.command('emit')
|
|
13
|
+
.description('Report loop-run completion status back to octomux')
|
|
14
|
+
.requiredOption('-r, --run <run-id>', 'loop run ID')
|
|
15
|
+
.addOption(new Option('-s, --status <status>', 'completion status')
|
|
16
|
+
.choices(EMIT_STATUSES)
|
|
17
|
+
.makeOptionMandatory())
|
|
18
|
+
.requiredOption('--reason <text>', 'reason for the status')
|
|
19
|
+
.action(async (opts) => {
|
|
20
|
+
const baseUrl = process.env.OCTOMUX_ACTION_BASE_URL;
|
|
21
|
+
const token = process.env.OCTOMUX_ACTION_TOKEN;
|
|
22
|
+
if (!baseUrl || !token) {
|
|
23
|
+
errorMessage('octomux emit is not configured (missing OCTOMUX_ACTION_BASE_URL / OCTOMUX_ACTION_TOKEN)');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const res = await fetch(`${baseUrl}/api/loops/${encodeURIComponent(opts.run)}/emit`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
Authorization: `Bearer ${token}`,
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({ status: opts.status, reason: opts.reason }),
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
const text = await res.text().catch(() => '');
|
|
37
|
+
errorMessage(`emit failed (HTTP ${res.status}): ${text}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
success(`Emitted ${opts.status} for loop run ${opts.run}`);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { getContext } from '../action.js';
|
|
3
|
+
import { outputJson, label, success } from '../format.js';
|
|
4
|
+
function resolvePrompt(raw) {
|
|
5
|
+
if (raw.startsWith('@')) {
|
|
6
|
+
return fs.readFileSync(raw.slice(1), 'utf-8');
|
|
7
|
+
}
|
|
8
|
+
return raw;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* octomux loop start — begin a fresh-context Ralph loop against a running
|
|
12
|
+
* task. Uses --stall-after (not --no-progress): commander treats any flag
|
|
13
|
+
* literally starting with `no-` as a boolean negation, which cannot carry a
|
|
14
|
+
* value, so a `--no-progress <n>` flag would silently misparse.
|
|
15
|
+
*/
|
|
16
|
+
export function registerLoopStart(program) {
|
|
17
|
+
program
|
|
18
|
+
.command('loop-start')
|
|
19
|
+
.description('Start a fresh-context Ralph loop against a running task')
|
|
20
|
+
.requiredOption('--task <id>', 'task ID to loop')
|
|
21
|
+
.requiredOption('--prompt <text|@file>', 'loop prompt, or @path to read it from a file')
|
|
22
|
+
.requiredOption('--verify <cmd>', 'shell command that must exit 0 for the loop to be done')
|
|
23
|
+
.requiredOption('--max-iterations <n>', 'maximum number of iterations', (v) => parseInt(v, 10))
|
|
24
|
+
.option('--budget-tokens <n>', 'token budget ceiling', (v) => parseInt(v, 10))
|
|
25
|
+
.option('--stall-after <n>', 'stop after N consecutive no-op iterations (maps to spec.noProgress.afterIters)', (v) => parseInt(v, 10))
|
|
26
|
+
.action(async (opts, cmd) => {
|
|
27
|
+
const { client, json } = getContext(cmd);
|
|
28
|
+
const run = await client.startLoop({
|
|
29
|
+
taskId: opts.task,
|
|
30
|
+
spec: {
|
|
31
|
+
prompt: resolvePrompt(opts.prompt),
|
|
32
|
+
verify: opts.verify,
|
|
33
|
+
maxIterations: opts.maxIterations,
|
|
34
|
+
...(opts.budgetTokens != null ? { budget: { tokens: opts.budgetTokens } } : {}),
|
|
35
|
+
...(opts.stallAfter != null ? { noProgress: { afterIters: opts.stallAfter } } : {}),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
if (json) {
|
|
39
|
+
outputJson(run);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
success(`Started loop run ${run.id}`);
|
|
43
|
+
console.log(label('Task', run.task_id));
|
|
44
|
+
console.log(label('Status', run.status));
|
|
45
|
+
});
|
|
46
|
+
}
|
package/cli/dist/index.js
CHANGED
|
@@ -30,6 +30,8 @@ import { registerListIntegrations } from './commands/list-integrations.js';
|
|
|
30
30
|
import { registerInit } from './commands/init.js';
|
|
31
31
|
import { registerTeam } from './commands/team.js';
|
|
32
32
|
import { registerFiles } from './commands/files.js';
|
|
33
|
+
import { registerEmit } from './commands/emit.js';
|
|
34
|
+
import { registerLoopStart } from './commands/loop-start.js';
|
|
33
35
|
const program = new Command();
|
|
34
36
|
program
|
|
35
37
|
.name('octomux')
|
|
@@ -65,6 +67,8 @@ registerListIntegrations(program);
|
|
|
65
67
|
registerInit(program);
|
|
66
68
|
registerTeam(program);
|
|
67
69
|
registerFiles(program);
|
|
70
|
+
registerEmit(program);
|
|
71
|
+
registerLoopStart(program);
|
|
68
72
|
program.hook('preAction', (thisCommand) => {
|
|
69
73
|
const opts = thisCommand.optsWithGlobals();
|
|
70
74
|
const client = createClient(opts.serverUrl);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as a,j as e}from"./vendor-react-BQxe05fg.js";import{K as m,a1 as f}from"./index-BhwDhOat.js";import{c as E,b as R}from"./vendor-router-BxmO8ihp.js";import"./vendor-ui-48-ysyeR.js";function B(){const{name:t}=E(),u=R(),[l,d]=a.useState(""),[c,v]=a.useState(""),[h,x]=a.useState(!1),[b,j]=a.useState(!0),[g,N]=a.useState(null),[o,p]=a.useState(!1),i=a.useRef(""),n=l!==i.current;a.useEffect(()=>{if(!t)return;let s=!1;return m.getAgent(t).then(r=>{s||(d(r.content),v(r.defaultContent),x(r.isCustom),i.current=r.content)}).catch(r=>{s||N(r.message)}).finally(()=>{s||j(!1)}),()=>{s=!0}},[t]);const C=a.useCallback(async()=>{if(!(!t||!n||o)){p(!0);try{await m.saveAgent(t,l),i.current=l,x(!0),f("success","SAVED",`Agent "${t}" saved`)}catch(s){f("error","ERROR",s instanceof Error?s.message:"Failed to save")}finally{p(!1)}}},[t,l,n,o]),y=a.useCallback(async()=>{if(t&&window.confirm(`Reset "${t}" to default? Your customizations will be lost.`))try{await m.resetAgent(t),d(c),i.current=c,x(!1),f("success","RESET",`Agent "${t}" reset to default`)}catch(s){f("error","ERROR",s instanceof Error?s.message:"Failed to reset")}},[t,c]),w=a.useCallback(()=>{n&&!window.confirm("You have unsaved changes. Discard them?")||u("/settings")},[n,u]);return b?e.jsx("div",{className:"flex h-full items-center justify-center",children:e.jsx("span",{className:"text-[#6a6a6a]",children:"Loading..."})}):g?e.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-3",children:[e.jsx("p",{className:"text-red-400",children:g}),e.jsx("button",{onClick:()=>u("/settings"),className:"text-xs text-[#3B82F6] hover:underline",children:"Back to Settings"})]}):e.jsxs("div",{className:"flex h-full flex-col",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[#2f2f2f] px-6 py-4",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("button",{onClick:w,className:"text-sm text-[#6a6a6a] hover:text-white",children:"←"}),e.jsx("span",{className:"font-mono text-lg font-bold",children:t}),h&&e.jsx("span",{className:"text-xs text-[#FFB800]",children:"custom"}),n&&e.jsx("span",{className:"text-xs text-[#FFB800]",children:"unsaved"})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[h&&c&&e.jsx("button",{onClick:y,className:"px-3 py-2 text-xs text-red-400 hover:text-red-300",children:"Reset to Default"}),e.jsx("button",{onClick:C,disabled:!n||o,className:"bg-[#3B82F6] px-4 py-2 text-xs text-white disabled:opacity-50",children:o?"Saving...":"Save"})]})]}),e.jsx("div",{className:"flex-1 p-6",children:e.jsx("textarea",{value:l,onChange:s=>d(s.target.value),className:"h-full min-h-[500px] w-full resize-none border border-[#2f2f2f] bg-[#0A0A0A] p-4 font-mono text-sm leading-relaxed text-white outline-none focus:border-[#3B82F6]",spellCheck:!1})})]})}export{B as default};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/TerminalView-wW5iq4Iq.js","assets/vendor-react-BQxe05fg.js","assets/vendor-xterm-DvXGiZvM.js","assets/vendor-xterm-DYP7pi_n.css","assets/index-BhwDhOat.js","assets/vendor-router-BxmO8ihp.js","assets/vendor-ui-48-ysyeR.js","assets/index-Ct_vkUxD.css"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{_ as c}from"./index-BhwDhOat.js";import{b as r,j as e}from"./vendor-react-BQxe05fg.js";import{c as d}from"./vendor-router-BxmO8ihp.js";import"./vendor-ui-48-ysyeR.js";const m=r.lazy(()=>c(()=>import("./TerminalView-wW5iq4Iq.js"),__vite__mapDeps([0,1,2,3,4,5,6,7])).then(a=>({default:a.TerminalView})));function p(){const{id:a}=d(),[t,i]=r.useState(null),[l,o]=r.useState(null);return r.useEffect(()=>{if(!a)return;let n=!1;return fetch(`/api/chats/${a}`).then(s=>s.ok?s.json():Promise.reject(new Error(`${s.status}`))).then(s=>{n||i(s)}).catch(s=>{n||o(s.message)}),()=>{n=!0}},[a]),l?e.jsxs("div",{className:"flex h-full items-center justify-center text-[#6a6a6a]",children:["Chat not found (",l,")"]}):t?e.jsxs("div",{className:"octomux-agent-session flex h-full min-h-0 flex-col overflow-hidden",children:[e.jsxs("div",{className:"flex shrink-0 items-center gap-2 border-b border-border px-4 py-2 md:gap-3 md:px-6 md:py-3",children:[e.jsx("span",{className:"text-[10px] font-bold uppercase tracking-wider text-[#6a6a6a]",children:"// CHAT"}),e.jsx("span",{className:"text-sm font-medium text-white",children:t.label}),t.agent&&e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-mono",style:{backgroundColor:"rgba(245, 158, 11, 0.12)",borderColor:"rgba(245, 158, 11, 0.4)",color:"#F59E0B"},title:`Running as agent: ${t.agent}`,children:["🤖 ",t.agent]}),t.status==="running"&&e.jsx("span",{className:"h-2 w-2 animate-pulse bg-[#22C55E]"})]}),e.jsx("div",{className:"min-h-0 flex-1",children:e.jsx(r.Suspense,{fallback:e.jsx("div",{className:"flex h-full items-center justify-center text-[#6a6a6a]",children:"Loading terminal..."}),children:e.jsx(m,{wsUrl:`/ws/terminal/chat/${t.id}`,visible:!0})})})]}):e.jsx("div",{className:"flex h-full items-center justify-center text-[#6a6a6a]",children:"Loading..."})}export{p as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as n,j as e}from"./vendor-react-BQxe05fg.js";import{G as _,b as k,J as o,a3 as A,D as F}from"./index-BhwDhOat.js";import{u as E,c as j}from"./DiffViewer-77QVL2bZ.js";const x=500;function I({agents:u,filesInDiff:y,rangeIsBase:d,onJumpTo:N,onClose:p,className:v}){const f=E(),[a,w]=n.useState("all"),i=n.useMemo(()=>Array.from(f.byId.values()),[f.byId]),c=n.useMemo(()=>{let t=i;return a==="unresolved"&&(t=t.filter(s=>!s.resolved_at)),a==="outdated"&&(t=t.filter(s=>s.outdated&&d)),t.sort((s,l)=>s.file_path.localeCompare(l.file_path)||s.line-l.line||s.created_at.localeCompare(l.created_at))},[i,a,d]),h=c.length>x,g=h?c.slice(0,x):c,b=n.useMemo(()=>{const t=new Map;for(const s of g){const l=t.get(s.file_path);l?l.push(s):t.set(s.file_path,[s])}return Array.from(t.entries())},[g]),m=({id:t,label:s})=>e.jsx("button",{type:"button",onClick:()=>w(t),"data-active":a===t?"true":void 0,className:o("rounded-md border px-2 py-0.5 text-[11px] font-medium",a===t?F:"border-glass-edge text-muted-foreground hover:bg-glass-l2/50 hover:text-foreground"),children:s});return e.jsxs(_,{"data-testid":"comments-side-panel",chrome:!0,className:o("flex w-80 flex-col border-l border-glass-edge",v),children:[e.jsxs("header",{className:"flex items-center justify-between gap-2 border-b border-glass-edge px-3 py-2",children:[e.jsxs("span",{className:"text-sm font-medium",children:["Comments (",i.length,")"]}),p?e.jsx(k,{variant:"ghost",size:"xs","aria-label":"Close comments panel",onClick:p,children:"×"}):null]}),e.jsxs("div",{className:"flex items-center gap-1 border-b border-glass-edge px-3 py-2",children:[e.jsx(m,{id:"all",label:"All"}),e.jsx(m,{id:"unresolved",label:"Unresolved"}),e.jsx(m,{id:"outdated",label:"Outdated"})]}),e.jsx("ul",{className:"flex-1 overflow-auto",children:b.length===0?e.jsx("li",{className:"p-4 text-xs text-muted-foreground",children:"No comments"}):b.map(([t,s])=>{const l=y.has(t);return e.jsxs("li",{children:[e.jsxs("header",{className:"diff-pane-header sticky top-0 z-10 flex items-center justify-between gap-2 px-3 py-1.5",children:[e.jsx("span",{className:"truncate font-mono text-[11px] text-muted-foreground",children:t}),l?null:e.jsx("span",{title:"File no longer in diff",className:"text-[10px] font-medium text-muted-soft",children:"Not in diff"})]}),e.jsx("ul",{children:s.map(r=>{const C=()=>{l&&N(r.file_path,r.line,r.side,r.id)};return e.jsx("li",{"data-testid":`side-panel-comment-${r.id}`,className:"border-b border-glass-edge/50",children:e.jsxs("button",{type:"button",disabled:!l,onClick:C,className:o("flex w-full flex-col items-start gap-1 px-3 py-2 text-left text-xs",l?"hover:bg-glass-l2/40":"cursor-not-allowed opacity-60"),children:[e.jsxs("span",{className:"flex items-center gap-2 text-[11px]",children:[e.jsx("span",{className:o("inline-flex h-4 w-4 items-center justify-center rounded-full font-mono text-[9px] font-bold",r.agent_id?"bg-success/15 text-success":"bg-primary/15 text-primary"),"aria-hidden":!0,children:j(r,u).slice(0,1).toUpperCase()}),e.jsx("span",{className:"font-medium",children:j(r,u)}),e.jsxs("span",{className:"text-muted-foreground",children:[t,":",r.line]}),e.jsx("span",{className:"ml-auto text-muted-foreground",children:A(r.created_at)})]}),e.jsx("span",{className:"line-clamp-2 text-foreground",style:{wordBreak:"break-word"},children:r.body}),e.jsxs("span",{className:"flex items-center gap-1",children:[r.resolved_at?e.jsx("span",{className:"rounded border border-success/40 bg-success/10 px-1.5 py-0.5 text-[9px] font-medium text-success",children:"Resolved"}):null,d&&r.outdated?e.jsx("span",{className:"rounded border border-warning/40 bg-warning/10 px-1.5 py-0.5 text-[9px] font-medium text-warning",children:"Outdated"}):null]})]})},r.id)})})]},t)})}),h?e.jsxs("footer",{className:"border-t border-glass-edge px-3 py-2 text-[10px] text-muted-foreground",children:["Showing first ",x," — refine filters to see more"]}):null]})}export{I as C};
|