@bastani/atomic 0.5.6-0 → 0.5.7-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 +105 -54
- package/dist/sdk/components/orchestrator-panel-store.d.ts +0 -10
- package/dist/sdk/components/orchestrator-panel-store.d.ts.map +1 -1
- package/dist/sdk/components/session-graph-panel.d.ts.map +1 -1
- package/dist/sdk/runtime/tmux.d.ts +5 -6
- package/dist/sdk/runtime/tmux.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +2 -1
- package/src/completions/zsh.ts +1 -1
- package/src/lib/spawn.ts +9 -223
- package/src/sdk/components/orchestrator-panel-store.test.ts +0 -82
- package/src/sdk/components/orchestrator-panel-store.ts +0 -19
- package/src/sdk/components/session-graph-panel.tsx +55 -29
- package/src/sdk/runtime/tmux.ts +11 -8
- package/src/services/system/auto-sync.ts +15 -29
- package/src/services/system/install-ui.ts +3 -4
- package/src/services/system/skills.ts +11 -6
package/README.md
CHANGED
|
@@ -9,33 +9,59 @@
|
|
|
9
9
|
[](./package.json)
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
|
|
12
|
-
Atomic is an open-source **
|
|
12
|
+
Atomic is an open-source **TypeScript SDK** for building **any harness you want** around your coding agent — **Claude Code**, **OpenCode**, or **GitHub Copilot CLI**. Chain sessions into pipelines, add human-in-the-loop approval gates, plug in CI and notifications, dispatch **12 specialized sub-agents**, and tap **58 built-in skills** — then ship it as TypeScript your whole team runs.
|
|
13
13
|
|
|
14
|
-
>
|
|
14
|
+
> Define how your agent works. Start for yourself, scale to your team.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
## Why Atomic
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Coding agents keep getting more capable — better reasoning, larger context windows, more reliable tool use. But a more capable model doesn't reduce the need for structure around it. It **increases** it.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
- **Context degrades in long sessions.** A single agent asked to research, plan, implement, and review in one session produces increasingly unreliable output as its context window fills up. There's no built-in mechanism to isolate concerns across sessions.
|
|
24
|
-
- **Agent-specific configuration is fragmented.** Claude Code, OpenCode, and Copilot CLI each have their own config directories, skill formats, and agent definitions. Building a workflow that works across agents means maintaining three separate configurations.
|
|
25
|
-
- **Team processes live in wikis, not in code.** Every team has a process — triage bugs this way, ship features that way, review PRs with these checks. But those processes are prose in a wiki, not executable code that an agent can follow.
|
|
26
|
-
- **Autonomous execution is unsafe without isolation.** Agents run shell commands, delete files, and execute arbitrary code. Running them autonomously on your host system is a risk most teams won't take.
|
|
27
|
-
- **Specialized work requires specialized agents.** A single general-purpose agent juggling file search, code analysis, web research, and implementation will lose track of details. There's no framework for dispatching purpose-built sub-agents with scoped tools and isolated context windows.
|
|
28
|
-
- **Agent workflows aren't deterministic.** Even when you do chain sessions together, there's no guarantee they'll execute in the same order, pass data the same way, or produce an inspectable record. Without strict ordering and controlled data flow, workflows become unpredictable — hard to debug, impossible to reproduce.
|
|
22
|
+
The bottleneck is shifting from "can my agent write this code?" to "can my agent follow my process?" Every team has a process — how code gets reviewed, what checks run before merging, who approves deployments, how production gets monitored. That process lives in wikis nobody reads, in one senior engineer's head, or nowhere at all. A powerful agent without a defined process is just a faster way to ship unreviewed code.
|
|
29
23
|
|
|
30
|
-
|
|
24
|
+
**Harnesses are what turn a capable agent into a reliable part of your engineering workflow.** A harness encodes your process — research, then implement, then review, then run CI, then create a PR, then notify the right person, then wait for approval, then merge. Without one, you're prompting manually and copy-pasting between terminal sessions. With one, you run a single command and the process executes itself.
|
|
25
|
+
|
|
26
|
+
Better models make harnesses **more** important, not less. The more you can trust an agent to execute complex tasks, the more value you get from defining exactly **what** it should execute, in **what order**, with **what checks** along the way. The harness is the durable layer — models will keep improving underneath it, but your process stays the same.
|
|
27
|
+
|
|
28
|
+
Atomic gives you the SDK to build that harness:
|
|
29
|
+
|
|
30
|
+
- **Start for yourself.** Automate the repetitive parts of your own workflow — research a codebase, add monitoring, generate specs. One developer, one afternoon, one TypeScript file.
|
|
31
|
+
- **Scale to your team.** Encode your team's review process, deployment gates, and quality checks as TypeScript that every team member runs identically. Your process becomes versioned, testable, and reproducible — not tribal knowledge.
|
|
32
|
+
- **Work across agents.** Write a harness once, run it on Claude Code, OpenCode, or Copilot CLI with a flag change. The harness is the constant; the agent is swappable.
|
|
33
|
+
|
|
34
|
+
### What You Can Build
|
|
35
|
+
|
|
36
|
+
**Add production monitoring to your codebase.** Build a harness that researches your current observability setup, identifies gaps in metrics, health checks, and alerting, implements the missing pieces, and reviews the changes — all in one run.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
atomic workflow -n add-monitoring -a claude "add Prometheus metrics and health checks to all API endpoints"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Automate your team's review-to-merge pipeline.** Encode your exact process: review code changes → run security scans and linting in parallel → create a PR → notify the team lead on Slack → wait for human approval → merge. The [human-in-the-loop gate](#workflow-sdk--build-your-own-deterministic-harness) pauses execution until the right person approves. New team members inherit the same pipeline on day one.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
atomic workflow -n review-to-merge -a claude
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Run parallel UX testing with 50 personas.** Spin up 50 agents — each with a distinct user persona (first-time user, power user, accessibility-dependent user, non-technical stakeholder) — each using [Playwright](#built-in-skills) to navigate your app and report usability issues from their perspective. Batch in groups, aggregate findings, and get feedback at a scale no manual process can match.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
atomic workflow -n ux-personas -a claude
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Each of these is a `.ts` file using Atomic's [Workflow SDK](#workflow-sdk--build-your-own-deterministic-harness). See [Build a Workflow](#5-build-a-workflow) for a working example, or read the full SDK reference below.
|
|
31
55
|
|
|
32
56
|
---
|
|
33
57
|
|
|
34
58
|
## Table of Contents
|
|
35
59
|
|
|
60
|
+
- [Why Atomic](#why-atomic)
|
|
61
|
+
- [What You Can Build](#what-you-can-build)
|
|
36
62
|
- [Quick Start](#quick-start)
|
|
37
63
|
- [Core Features](#core-features)
|
|
38
|
-
- [Multi-Agent
|
|
64
|
+
- [Multi-Agent Support](#multi-agent-support)
|
|
39
65
|
- [Workflow SDK — Build Your Own Deterministic Harness](#workflow-sdk--build-your-own-deterministic-harness)
|
|
40
66
|
- [Deep Codebase Research](#deep-codebase-research)
|
|
41
67
|
- [Autonomous Execution (Ralph)](#autonomous-execution-ralph)
|
|
@@ -185,54 +211,78 @@ Create a workflow project, install the SDK, and add your workflow file:
|
|
|
185
211
|
|
|
186
212
|
```bash
|
|
187
213
|
bun init && bun add @bastani/atomic
|
|
188
|
-
mkdir -p .atomic/workflows/
|
|
214
|
+
mkdir -p .atomic/workflows/review-to-merge/claude
|
|
189
215
|
```
|
|
190
216
|
|
|
217
|
+
Here's one of the [canonical use cases](#what-you-can-build) — a team pipeline that reviews code, runs checks in parallel, creates a PR, notifies on Slack, waits for human approval, and merges:
|
|
218
|
+
|
|
191
219
|
```ts
|
|
192
|
-
// .atomic/workflows/
|
|
220
|
+
// .atomic/workflows/review-to-merge/claude/index.ts
|
|
193
221
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
194
222
|
|
|
195
223
|
export default defineWorkflow<"claude">({
|
|
196
|
-
name: "
|
|
197
|
-
description: "
|
|
224
|
+
name: "review-to-merge",
|
|
225
|
+
description: "Review → CI → PR → Notify → Approve → Merge",
|
|
198
226
|
})
|
|
199
227
|
.run(async (ctx) => {
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const prompt = ctx.inputs.prompt ?? "";
|
|
204
|
-
|
|
205
|
-
const research = await ctx.stage(
|
|
206
|
-
{ name: "research", description: "Analyze the codebase" },
|
|
207
|
-
{}, {},
|
|
208
|
-
async (s) => {
|
|
209
|
-
await s.session.query(`/research-codebase ${prompt}`);
|
|
210
|
-
s.save(s.sessionId);
|
|
211
|
-
},
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
await ctx.stage(
|
|
215
|
-
{ name: "implement", description: "Implement based on research" },
|
|
228
|
+
// Step 1: Review the changes
|
|
229
|
+
const review = await ctx.stage(
|
|
230
|
+
{ name: "review", description: "Review code changes" },
|
|
216
231
|
{}, {},
|
|
217
232
|
async (s) => {
|
|
218
|
-
const transcript = await s.transcript(research);
|
|
219
233
|
await s.session.query(
|
|
220
|
-
|
|
234
|
+
"Review all uncommitted changes. Flag issues with correctness, security, and style.",
|
|
221
235
|
);
|
|
222
236
|
s.save(s.sessionId);
|
|
223
237
|
},
|
|
224
238
|
);
|
|
225
239
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
{}, {},
|
|
229
|
-
|
|
230
|
-
await s.session.query(
|
|
231
|
-
"Review all uncommitted changes. Flag any issues with correctness, tests, or style.",
|
|
232
|
-
);
|
|
240
|
+
// Step 2: Run security and CI checks in parallel
|
|
241
|
+
await Promise.all([
|
|
242
|
+
ctx.stage({ name: "security-scan" }, {}, {}, async (s) => {
|
|
243
|
+
await s.session.query("Run `bun audit` and scan for leaked secrets or credentials.");
|
|
233
244
|
s.save(s.sessionId);
|
|
234
|
-
},
|
|
235
|
-
|
|
245
|
+
}),
|
|
246
|
+
ctx.stage({ name: "ci-checks" }, {}, {}, async (s) => {
|
|
247
|
+
await s.session.query("Run `bun lint` and `bun test`. Report any failures.");
|
|
248
|
+
s.save(s.sessionId);
|
|
249
|
+
}),
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
// Step 3: Create a PR with the review summary
|
|
253
|
+
await ctx.stage({ name: "create-pr" }, {}, {}, async (s) => {
|
|
254
|
+
const transcript = await s.transcript(review);
|
|
255
|
+
await s.session.query(
|
|
256
|
+
`Read the review at ${transcript.path}. Create a pull request summarizing the changes.`,
|
|
257
|
+
);
|
|
258
|
+
s.save(s.sessionId);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Step 4: Notify on Slack, then wait for human approval before merging.
|
|
262
|
+
// Stage callbacks are plain Bun code — fetch(), Bun.spawn(), and any
|
|
263
|
+
// Node API work here alongside agent session queries.
|
|
264
|
+
await ctx.stage({ name: "notify-and-merge" }, {}, {}, async (s) => {
|
|
265
|
+
await fetch("https://slack.com/api/chat.postMessage", {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: {
|
|
268
|
+
Authorization: `Bearer ${process.env.SLACK_TOKEN}`,
|
|
269
|
+
"Content-Type": "application/json",
|
|
270
|
+
},
|
|
271
|
+
body: JSON.stringify({
|
|
272
|
+
channel: "#code-review",
|
|
273
|
+
text: "New PR ready for review — please approve in GitHub.",
|
|
274
|
+
}),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Human-in-the-loop: AskUserQuestion pauses the session until the
|
|
278
|
+
// user responds. The agent won't merge until approval is given.
|
|
279
|
+
await s.session.query(
|
|
280
|
+
"The team has been notified on Slack. Ask the user to confirm the PR " +
|
|
281
|
+
"is approved, then merge it with `gh pr merge --squash`.",
|
|
282
|
+
{ allowedTools: ["Bash", "Read", "AskUserQuestion"] },
|
|
283
|
+
);
|
|
284
|
+
s.save(s.sessionId);
|
|
285
|
+
});
|
|
236
286
|
})
|
|
237
287
|
.compile();
|
|
238
288
|
```
|
|
@@ -240,10 +290,10 @@ export default defineWorkflow<"claude">({
|
|
|
240
290
|
Run it:
|
|
241
291
|
|
|
242
292
|
```bash
|
|
243
|
-
atomic workflow -n
|
|
293
|
+
atomic workflow -n review-to-merge -a claude
|
|
244
294
|
```
|
|
245
295
|
|
|
246
|
-
|
|
296
|
+
This single file demonstrates multi-step pipelines, parallel stages (`Promise.all`), transcript passing between sessions, external API calls (`fetch`), and human-in-the-loop approval — all in plain TypeScript. Swap `-a claude` for `-a opencode` or `-a copilot` to run the same harness on a different agent. See [Workflow SDK — Build Your Own Harness](#workflow-sdk--build-your-own-deterministic-harness) for the full API and more examples.
|
|
247
297
|
|
|
248
298
|
> **Want something that works out of the box?** Atomic ships with `ralph`, a built-in workflow that plans, implements, reviews, and debugs autonomously — see [Autonomous Execution (Ralph)](#autonomous-execution-ralph).
|
|
249
299
|
|
|
@@ -251,15 +301,15 @@ Add a spec phase, parallelize independent sessions, swap in a different agent
|
|
|
251
301
|
|
|
252
302
|
## Core Features
|
|
253
303
|
|
|
254
|
-
### Multi-Agent
|
|
304
|
+
### Multi-Agent Support
|
|
255
305
|
|
|
256
|
-
Atomic
|
|
306
|
+
Atomic works across **three production coding agents** — switch between them with a flag and your workflows, skills, and sub-agents carry over.
|
|
257
307
|
|
|
258
|
-
| Agent |
|
|
259
|
-
| ------------------ |
|
|
260
|
-
| Claude Code |
|
|
261
|
-
| OpenCode |
|
|
262
|
-
| GitHub Copilot CLI |
|
|
308
|
+
| Agent | Command |
|
|
309
|
+
| ------------------ | ------------------------- |
|
|
310
|
+
| Claude Code | `atomic chat -a claude` |
|
|
311
|
+
| OpenCode | `atomic chat -a opencode` |
|
|
312
|
+
| GitHub Copilot CLI | `atomic chat -a copilot` |
|
|
263
313
|
|
|
264
314
|
Each agent gets its own configuration directory (`.claude/`, `.opencode/`, `.github/`), skills, and context files — all managed by Atomic. Write a workflow once, run it on any agent.
|
|
265
315
|
|
|
@@ -815,6 +865,7 @@ During `atomic chat`, there is no Atomic-owned TUI — `atomic chat -a <agent>`
|
|
|
815
865
|
| `atomic init` | Interactive project setup (agent selection, SCM choice, config sync) |
|
|
816
866
|
| `atomic chat` | Spawn the native agent CLI inside a tmux/psmux session |
|
|
817
867
|
| `atomic workflow` | Run a multi-session agent workflow with the Atomic orchestrator panel |
|
|
868
|
+
| `atomic workflow list` | List available workflows, grouped by source |
|
|
818
869
|
| `atomic session list` | List all running sessions on the atomic tmux socket |
|
|
819
870
|
| `atomic session connect [name]` | Attach to a session (interactive picker when no name given) |
|
|
820
871
|
| `atomic completions <shell>` | Output shell completion script (bash, zsh, fish, powershell) |
|
|
@@ -886,7 +937,6 @@ atomic chat -a claude --verbose # Forward --verbose to claude
|
|
|
886
937
|
| -------------------------- | ------------------------------------------------------------------- |
|
|
887
938
|
| `-n, --name <name>` | Workflow name (matches directory under `.atomic/workflows/<name>/`) |
|
|
888
939
|
| `-a, --agent <name>` | Agent: `claude`, `opencode`, `copilot` |
|
|
889
|
-
| `-l, --list` | List available workflows, grouped by source |
|
|
890
940
|
| `--<field>=<value>` | Structured input for workflows that declare an `inputs` schema (also accepts `--<field> <value>`) |
|
|
891
941
|
| `[prompt...]` | Positional prompt for free-form workflows (rejected on workflows with a declared schema) |
|
|
892
942
|
|
|
@@ -894,7 +944,8 @@ The workflow command supports four invocation shapes:
|
|
|
894
944
|
|
|
895
945
|
```bash
|
|
896
946
|
# 1. List every workflow available to you, grouped by source
|
|
897
|
-
atomic workflow
|
|
947
|
+
atomic workflow list
|
|
948
|
+
atomic workflow list -a claude # filter by agent
|
|
898
949
|
|
|
899
950
|
# 2. Launch the interactive picker for an agent (no -n) — fuzzy-search
|
|
900
951
|
# the list, fill the form rendered from the workflow's declared inputs,
|
|
@@ -34,16 +34,6 @@ export declare class PanelStore {
|
|
|
34
34
|
* Switching to "graph" clears the active agent.
|
|
35
35
|
*/
|
|
36
36
|
setViewMode(mode: ViewMode, agentId?: string): void;
|
|
37
|
-
/**
|
|
38
|
-
* Return non-orchestrator agents that have started (not pending).
|
|
39
|
-
* Used for the tmux status bar agent count and active-agent index.
|
|
40
|
-
*/
|
|
41
|
-
getSubagents(): SessionData[];
|
|
42
|
-
/**
|
|
43
|
-
* Return the 0-based index of the active agent within the subagent list,
|
|
44
|
-
* or -1 if not found.
|
|
45
|
-
*/
|
|
46
|
-
getActiveAgentIndex(): number;
|
|
47
37
|
/** Safely invoke exitResolve at most once, guarding against rapid repeated calls. */
|
|
48
38
|
resolveExit(): void;
|
|
49
39
|
/** Safely invoke abortResolve at most once to signal mid-execution quit. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator-panel-store.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/orchestrator-panel-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAiB,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAExG,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AAE3B,qBAAa,UAAU;IACrB,OAAO,SAAK;IACZ,YAAY,SAAM;IAClB,KAAK,SAAM;IACX,MAAM,SAAM;IACZ,QAAQ,EAAE,WAAW,EAAE,CAAM;IAC7B,cAAc,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;IAChF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IACjC,iBAAiB,UAAS;IAC1B,WAAW,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAQ;IACxC,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAQ;IAEzC,0EAA0E;IAC1E,QAAQ,EAAE,QAAQ,CAAW;IAC7B,4FAA4F;IAC5F,aAAa,SAAM;IAEnB,OAAO,CAAC,SAAS,CAAuB;IAExC,SAAS,GAAI,IAAI,QAAQ,KAAG,CAAC,MAAM,IAAI,CAAC,CAGtC;IAEF,OAAO,CAAC,IAAI;IAKZ,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,YAAY,EAAE,EACxB,MAAM,EAAE,MAAM,GACb,IAAI;IAuBP,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQhC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQnC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAS9C,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAKtC,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI;IAUlE,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAWpC;;;;OAIG;IACH,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAMnD
|
|
1
|
+
{"version":3,"file":"orchestrator-panel-store.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/orchestrator-panel-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAiB,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAExG,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AAE3B,qBAAa,UAAU;IACrB,OAAO,SAAK;IACZ,YAAY,SAAM;IAClB,KAAK,SAAM;IACX,MAAM,SAAM;IACZ,QAAQ,EAAE,WAAW,EAAE,CAAM;IAC7B,cAAc,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;IAChF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IACjC,iBAAiB,UAAS;IAC1B,WAAW,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAQ;IACxC,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAQ;IAEzC,0EAA0E;IAC1E,QAAQ,EAAE,QAAQ,CAAW;IAC7B,4FAA4F;IAC5F,aAAa,SAAM;IAEnB,OAAO,CAAC,SAAS,CAAuB;IAExC,SAAS,GAAI,IAAI,QAAQ,KAAG,CAAC,MAAM,IAAI,CAAC,CAGtC;IAEF,OAAO,CAAC,IAAI;IAKZ,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,YAAY,EAAE,EACxB,MAAM,EAAE,MAAM,GACb,IAAI;IAuBP,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQhC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQnC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAS9C,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAKtC,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI;IAUlE,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAWpC;;;;OAIG;IACH,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAMnD,qFAAqF;IACrF,WAAW,IAAI,IAAI;IAQnB,4EAA4E;IAC5E,YAAY,IAAI,IAAI;IAQpB,gFAAgF;IAChF,WAAW,IAAI,IAAI;IAQnB,qBAAqB,IAAI,IAAI;CAI9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-graph-panel.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/session-graph-panel.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC;;;GAGG;
|
|
1
|
+
{"version":3,"file":"session-graph-panel.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/session-graph-panel.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC;;;GAGG;AAoDH,wBAAgB,iBAAiB,8BAgchC"}
|
|
@@ -20,12 +20,11 @@ export declare const TMUX_DEFAULT_STATUS_LEFT = " ";
|
|
|
20
20
|
export declare const TMUX_DEFAULT_STATUS_LEFT_LENGTH = "10";
|
|
21
21
|
export declare const TMUX_DEFAULT_STATUS_RIGHT = " #{session_name} | %H:%M ";
|
|
22
22
|
export declare const TMUX_DEFAULT_STATUS_RIGHT_LENGTH = "60";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export declare function escapeTmuxFormat(value: string): string;
|
|
23
|
+
export declare const TMUX_ATTACHED_STATUS_RIGHT = "#[fg=#cdd6f4]ctrl+g #[fg=#6c7086]graph #[fg=#585b70]\u00B7 #[fg=#cdd6f4]ctrl+\\ #[fg=#6c7086]next ";
|
|
24
|
+
export declare const TMUX_ATTACHED_STATUS_RIGHT_LENGTH = "40";
|
|
25
|
+
export declare const TMUX_ATTACHED_WINDOW_FMT = "#{?#{==:#{window_index},0},, #W }";
|
|
26
|
+
export declare const TMUX_ATTACHED_WINDOW_STYLE = "fg=#6c7086";
|
|
27
|
+
export declare const TMUX_ATTACHED_WINDOW_CURRENT_STYLE = "fg=#cdd6f4,bold";
|
|
29
28
|
/**
|
|
30
29
|
* Resolve the terminal multiplexer binary for the current platform.
|
|
31
30
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/tmux.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAMtC,4FAA4F;AAC5F,eAAO,MAAM,WAAW,WAAW,CAAC;AAKpC,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAWlC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,+BAA+B,OAAO,CAAC;AACpD,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AACrE,eAAO,MAAM,gCAAgC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/tmux.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -327,7 +327,8 @@ async function main(): Promise<void> {
|
|
|
327
327
|
argv.includes("--version") ||
|
|
328
328
|
argv.includes("-v") ||
|
|
329
329
|
argv.includes("--help") ||
|
|
330
|
-
argv.includes("-h")
|
|
330
|
+
argv.includes("-h") ||
|
|
331
|
+
argv[0] === "completions";
|
|
331
332
|
|
|
332
333
|
if (!isInfoCommand) {
|
|
333
334
|
const { autoSyncIfStale } = await import("./services/system/auto-sync.ts");
|
package/src/completions/zsh.ts
CHANGED
package/src/lib/spawn.ts
CHANGED
|
@@ -99,228 +99,19 @@ export interface EnsureOptions {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* No-op when npm is already on PATH.
|
|
106
|
-
*/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
async function installNodeViaFnm(quiet: boolean): Promise<boolean> {
|
|
110
|
-
const inherit = !quiet;
|
|
111
|
-
// Install fnm if not present.
|
|
112
|
-
if (!Bun.which("fnm")) {
|
|
113
|
-
let installed = false;
|
|
114
|
-
// macOS: prefer Homebrew
|
|
115
|
-
if (process.platform === "darwin" && Bun.which("brew")) {
|
|
116
|
-
const brew = await runCommand(
|
|
117
|
-
[Bun.which("brew")!, "install", "fnm"],
|
|
118
|
-
{ inherit },
|
|
119
|
-
);
|
|
120
|
-
installed = brew.success;
|
|
121
|
-
}
|
|
122
|
-
// Windows: prefer winget
|
|
123
|
-
if (!installed && process.platform === "win32" && Bun.which("winget")) {
|
|
124
|
-
const winget = await runCommand(
|
|
125
|
-
[Bun.which("winget")!, "install", "Schniz.fnm"],
|
|
126
|
-
{ inherit },
|
|
127
|
-
);
|
|
128
|
-
if (winget.success) {
|
|
129
|
-
// Refresh PATH — winget installs to a location on the user PATH.
|
|
130
|
-
const userPath = process.env.LOCALAPPDATA
|
|
131
|
-
? join(process.env.LOCALAPPDATA, "Microsoft", "WinGet", "Links")
|
|
132
|
-
: null;
|
|
133
|
-
if (userPath) prependPath(userPath);
|
|
134
|
-
}
|
|
135
|
-
installed = winget.success;
|
|
136
|
-
}
|
|
137
|
-
// Linux / fallback: use the curl installer (requires a shell)
|
|
138
|
-
if (!installed) {
|
|
139
|
-
const shell = Bun.which("bash") ?? Bun.which("sh");
|
|
140
|
-
if (!shell) return false;
|
|
141
|
-
|
|
142
|
-
const curl = await runCommand(
|
|
143
|
-
[shell, "-lc", "curl -fsSL https://fnm.vercel.app/install | bash -s -- --skip-shell"],
|
|
144
|
-
{ inherit },
|
|
145
|
-
);
|
|
146
|
-
if (!curl.success) return false;
|
|
147
|
-
|
|
148
|
-
// Add fnm to PATH for the current session.
|
|
149
|
-
const home = getHomeDir() ?? "/tmp";
|
|
150
|
-
const fnmDir = process.env.FNM_DIR ?? join(home, ".local", "share", "fnm");
|
|
151
|
-
prependPath(fnmDir);
|
|
152
|
-
// Some systems install to ~/.fnm instead
|
|
153
|
-
prependPath(join(home, ".fnm"));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const fnmPath = Bun.which("fnm");
|
|
158
|
-
if (!fnmPath) return false;
|
|
159
|
-
|
|
160
|
-
// Install LTS Node.js via fnm.
|
|
161
|
-
const fnmInstall = await runCommand(
|
|
162
|
-
[fnmPath, "install", "--lts"],
|
|
163
|
-
{ inherit },
|
|
164
|
-
);
|
|
165
|
-
if (!fnmInstall.success) return false;
|
|
166
|
-
|
|
167
|
-
// Activate the installed version by adding its bin dir to PATH.
|
|
168
|
-
const envShell = process.platform === "win32" ? "cmd" : "bash";
|
|
169
|
-
const envProc = Bun.spawn({
|
|
170
|
-
cmd: [fnmPath, "env", "--shell", envShell],
|
|
171
|
-
stdout: "pipe",
|
|
172
|
-
stderr: "pipe",
|
|
173
|
-
});
|
|
174
|
-
const [envOutput, envExitCode] = await Promise.all([
|
|
175
|
-
new Response(envProc.stdout).text(),
|
|
176
|
-
envProc.exited,
|
|
177
|
-
]);
|
|
178
|
-
if (envExitCode === 0) {
|
|
179
|
-
if (process.platform === "win32") {
|
|
180
|
-
// cmd output: SET "PATH=C:\...\fnm_multishells\...;..."
|
|
181
|
-
const pathMatch = envOutput.match(/SET "PATH=([^"]+?)"/i);
|
|
182
|
-
if (pathMatch?.[1]) {
|
|
183
|
-
const firstEntry = pathMatch[1].split(";")[0];
|
|
184
|
-
if (firstEntry) prependPath(firstEntry);
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
// bash output: export PATH="/.../fnm_multishells/...:..."
|
|
188
|
-
const pathMatch = envOutput.match(/export PATH="([^"]+?):/);
|
|
189
|
-
if (pathMatch?.[1]) {
|
|
190
|
-
prependPath(pathMatch[1]);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return !!Bun.which("node");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export async function ensureNpmInstalled(options: EnsureOptions = {}): Promise<void> {
|
|
199
|
-
const quiet = options.quiet ?? false;
|
|
200
|
-
const inherit = !quiet;
|
|
201
|
-
|
|
202
|
-
if (Bun.which("npm")) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Buffer captured failure output so a thrown error can surface the tail
|
|
207
|
-
// through the spinner summary. Only populated when `quiet` is set.
|
|
208
|
-
let capturedDetails = "";
|
|
209
|
-
const record = (result: SpawnResult) => {
|
|
210
|
-
if (quiet && !result.success && result.details) {
|
|
211
|
-
capturedDetails = result.details;
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// Preferred: install via fnm (no root required, works on all platforms).
|
|
216
|
-
if (await installNodeViaFnm(quiet)) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (process.platform === "win32") {
|
|
221
|
-
// Fallback: direct Node.js installation via Windows package managers.
|
|
222
|
-
if (Bun.which("winget")) {
|
|
223
|
-
record(
|
|
224
|
-
await runCommand(
|
|
225
|
-
[
|
|
226
|
-
"winget",
|
|
227
|
-
"install",
|
|
228
|
-
"--id",
|
|
229
|
-
"OpenJS.NodeJS.LTS",
|
|
230
|
-
"-e",
|
|
231
|
-
"--silent",
|
|
232
|
-
"--accept-source-agreements",
|
|
233
|
-
"--accept-package-agreements",
|
|
234
|
-
],
|
|
235
|
-
{ inherit },
|
|
236
|
-
),
|
|
237
|
-
);
|
|
238
|
-
} else if (Bun.which("choco")) {
|
|
239
|
-
record(
|
|
240
|
-
await runCommand(
|
|
241
|
-
["choco", "install", "nodejs-lts", "-y", "--no-progress"],
|
|
242
|
-
{ inherit },
|
|
243
|
-
),
|
|
244
|
-
);
|
|
245
|
-
} else if (Bun.which("scoop")) {
|
|
246
|
-
record(
|
|
247
|
-
await runCommand(["scoop", "install", "nodejs-lts"], { inherit }),
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const programFiles = process.env.ProgramFiles;
|
|
252
|
-
if (programFiles) {
|
|
253
|
-
prependPath(join(programFiles, "nodejs"));
|
|
254
|
-
}
|
|
255
|
-
if (Bun.which("npm")) return;
|
|
256
|
-
throw new Error(
|
|
257
|
-
capturedDetails || "Could not install Node.js on Windows (no supported package manager found).",
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const shell = Bun.which("bash") ?? Bun.which("sh");
|
|
262
|
-
if (!shell) {
|
|
263
|
-
throw new Error("Neither bash nor sh is available to install Node.js.");
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Fallback: Homebrew, NodeSource, then system package managers.
|
|
267
|
-
const installers = [
|
|
268
|
-
'if command -v brew >/dev/null 2>&1; then brew install node && brew link --overwrite node 2>/dev/null; fi',
|
|
269
|
-
'if command -v apt-get >/dev/null 2>&1; then SUDO=""; [ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null 2>&1 && SUDO="sudo"; $SUDO apt-get update && $SUDO apt-get install -y nodejs npm; fi',
|
|
270
|
-
'if command -v dnf >/dev/null 2>&1; then if command -v sudo >/dev/null 2>&1; then sudo dnf install -y nodejs npm; elif [ "$(id -u)" -eq 0 ]; then dnf install -y nodejs npm; fi; fi',
|
|
271
|
-
'if command -v yum >/dev/null 2>&1; then if command -v sudo >/dev/null 2>&1; then sudo yum install -y nodejs npm; elif [ "$(id -u)" -eq 0 ]; then yum install -y nodejs npm; fi; fi',
|
|
272
|
-
'if command -v pacman >/dev/null 2>&1; then if command -v sudo >/dev/null 2>&1; then sudo pacman -Sy --noconfirm nodejs npm; elif [ "$(id -u)" -eq 0 ]; then pacman -Sy --noconfirm nodejs npm; fi; fi',
|
|
273
|
-
'if command -v zypper >/dev/null 2>&1; then if command -v sudo >/dev/null 2>&1; then sudo zypper --non-interactive install nodejs npm; elif [ "$(id -u)" -eq 0 ]; then zypper --non-interactive install nodejs npm; fi; fi',
|
|
274
|
-
'if command -v apk >/dev/null 2>&1; then if command -v sudo >/dev/null 2>&1; then sudo apk add --no-cache nodejs npm; elif [ "$(id -u)" -eq 0 ]; then apk add --no-cache nodejs npm; fi; fi',
|
|
275
|
-
];
|
|
276
|
-
|
|
277
|
-
for (const script of installers) {
|
|
278
|
-
if (Bun.which("npm")) {
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
record(await runCommand([shell, "-lc", script], { inherit }));
|
|
282
|
-
if (Bun.which("npm")) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
throw new Error(
|
|
288
|
-
capturedDetails || "Could not install Node.js — no supported package manager succeeded.",
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Upgrade npm to the latest version.
|
|
294
|
-
* Falls back to installing Node.js/npm if it is not yet present.
|
|
295
|
-
*/
|
|
296
|
-
export async function upgradeNpm(): Promise<void> {
|
|
297
|
-
const npmPath = Bun.which("npm");
|
|
298
|
-
if (!npmPath) {
|
|
299
|
-
await ensureNpmInstalled();
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
const result = await runCommand([npmPath, "install", "-g", "npm@latest"]);
|
|
303
|
-
if (!result.success) {
|
|
304
|
-
const hint =
|
|
305
|
-
result.details?.includes("EACCES") || result.details?.includes("permission")
|
|
306
|
-
? "\nIf this is a permissions issue, try: sudo npm install -g npm@latest"
|
|
307
|
-
: "";
|
|
308
|
-
throw new Error(`npm self-upgrade failed: ${result.details}${hint}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Upgrade a global npm package to the latest version.
|
|
102
|
+
* Install a global package via bun. Uses `--trust` to allow postinstall
|
|
103
|
+
* lifecycle scripts (required by packages like @playwright/cli).
|
|
314
104
|
*/
|
|
315
105
|
export async function upgradeGlobalPackage(pkg: string): Promise<void> {
|
|
316
106
|
const versionedPkg = pkg.includes("@latest") ? pkg : `${pkg}@latest`;
|
|
317
|
-
const
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
107
|
+
const bunPath = Bun.which("bun");
|
|
108
|
+
if (!bunPath) {
|
|
109
|
+
throw new Error(`bun is not available to install ${pkg}.`);
|
|
110
|
+
}
|
|
111
|
+
const result = await runCommand([bunPath, "install", "-g", "--trust", versionedPkg]);
|
|
112
|
+
if (!result.success) {
|
|
113
|
+
throw new Error(`Failed to install ${pkg}: ${result.details}`);
|
|
322
114
|
}
|
|
323
|
-
throw new Error(`npm is not available to upgrade ${pkg}.`);
|
|
324
115
|
}
|
|
325
116
|
|
|
326
117
|
/** Upgrade @playwright/cli to the latest version globally. */
|
|
@@ -455,11 +246,6 @@ export async function ensureBunInstalled(): Promise<void> {
|
|
|
455
246
|
if (result.success && Bun.which("bun")) return;
|
|
456
247
|
}
|
|
457
248
|
|
|
458
|
-
const npmPath = Bun.which("npm");
|
|
459
|
-
if (npmPath) {
|
|
460
|
-
const result = await runCommand([npmPath, "install", "-g", "bun"], { inherit: true });
|
|
461
|
-
if (result.success && Bun.which("bun")) return;
|
|
462
|
-
}
|
|
463
249
|
return;
|
|
464
250
|
}
|
|
465
251
|
|
|
@@ -756,86 +756,4 @@ describe("PanelStore", () => {
|
|
|
756
756
|
});
|
|
757
757
|
});
|
|
758
758
|
|
|
759
|
-
// ── getSubagents ───────────────────────────────────────────────────────────
|
|
760
|
-
|
|
761
|
-
describe("getSubagents", () => {
|
|
762
|
-
beforeEach(() => {
|
|
763
|
-
store.setWorkflowInfo("wf", "claude", [
|
|
764
|
-
{ name: "planner", parents: [] },
|
|
765
|
-
{ name: "writer", parents: ["planner"] },
|
|
766
|
-
{ name: "reviewer", parents: ["writer"] },
|
|
767
|
-
], "prompt");
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
test("returns empty when all non-orchestrator sessions are pending", () => {
|
|
771
|
-
expect(store.getSubagents()).toEqual([]);
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
test("excludes orchestrator from subagent list", () => {
|
|
775
|
-
store.startSession("planner");
|
|
776
|
-
const subs = store.getSubagents();
|
|
777
|
-
expect(subs.every((s) => s.name !== "orchestrator")).toBe(true);
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
test("includes running and completed sessions", () => {
|
|
781
|
-
store.startSession("planner");
|
|
782
|
-
store.completeSession("planner");
|
|
783
|
-
store.startSession("writer");
|
|
784
|
-
const subs = store.getSubagents();
|
|
785
|
-
expect(subs.map((s) => s.name)).toEqual(["planner", "writer"]);
|
|
786
|
-
});
|
|
787
|
-
|
|
788
|
-
test("includes errored sessions", () => {
|
|
789
|
-
store.startSession("planner");
|
|
790
|
-
store.failSession("planner", "timeout");
|
|
791
|
-
const subs = store.getSubagents();
|
|
792
|
-
expect(subs.map((s) => s.name)).toEqual(["planner"]);
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
test("excludes pending sessions", () => {
|
|
796
|
-
store.startSession("planner");
|
|
797
|
-
const subs = store.getSubagents();
|
|
798
|
-
expect(subs.map((s) => s.name)).toEqual(["planner"]);
|
|
799
|
-
expect(subs.some((s) => s.name === "writer")).toBe(false);
|
|
800
|
-
expect(subs.some((s) => s.name === "reviewer")).toBe(false);
|
|
801
|
-
});
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
// ── getActiveAgentIndex ────────────────────────────────────────────────────
|
|
805
|
-
|
|
806
|
-
describe("getActiveAgentIndex", () => {
|
|
807
|
-
beforeEach(() => {
|
|
808
|
-
store.setWorkflowInfo("wf", "claude", [
|
|
809
|
-
{ name: "planner", parents: [] },
|
|
810
|
-
{ name: "writer", parents: ["planner"] },
|
|
811
|
-
{ name: "reviewer", parents: ["writer"] },
|
|
812
|
-
], "prompt");
|
|
813
|
-
store.startSession("planner");
|
|
814
|
-
store.startSession("writer");
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
test("returns -1 when no agent is active", () => {
|
|
818
|
-
expect(store.getActiveAgentIndex()).toBe(-1);
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
test("returns correct index for first subagent", () => {
|
|
822
|
-
store.setViewMode("attached", "planner");
|
|
823
|
-
expect(store.getActiveAgentIndex()).toBe(0);
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
test("returns correct index for second subagent", () => {
|
|
827
|
-
store.setViewMode("attached", "writer");
|
|
828
|
-
expect(store.getActiveAgentIndex()).toBe(1);
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
test("returns -1 for orchestrator (not a subagent)", () => {
|
|
832
|
-
store.setViewMode("attached", "orchestrator");
|
|
833
|
-
expect(store.getActiveAgentIndex()).toBe(-1);
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
test("returns -1 for non-existent agent", () => {
|
|
837
|
-
store.setViewMode("attached", "nonexistent");
|
|
838
|
-
expect(store.getActiveAgentIndex()).toBe(-1);
|
|
839
|
-
});
|
|
840
|
-
});
|
|
841
759
|
});
|
|
@@ -124,25 +124,6 @@ export class PanelStore {
|
|
|
124
124
|
this.emit();
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
/**
|
|
128
|
-
* Return non-orchestrator agents that have started (not pending).
|
|
129
|
-
* Used for the tmux status bar agent count and active-agent index.
|
|
130
|
-
*/
|
|
131
|
-
getSubagents(): SessionData[] {
|
|
132
|
-
return this.sessions.filter(
|
|
133
|
-
(s) => s.name !== "orchestrator" && s.status !== "pending",
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Return the 0-based index of the active agent within the subagent list,
|
|
139
|
-
* or -1 if not found.
|
|
140
|
-
*/
|
|
141
|
-
getActiveAgentIndex(): number {
|
|
142
|
-
const subs = this.getSubagents();
|
|
143
|
-
return subs.findIndex((s) => s.name === this.activeAgentId);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
127
|
/** Safely invoke exitResolve at most once, guarding against rapid repeated calls. */
|
|
147
128
|
resolveExit(): void {
|
|
148
129
|
if (this.exitResolve) {
|
|
@@ -20,11 +20,15 @@ import {
|
|
|
20
20
|
} from "react";
|
|
21
21
|
import {
|
|
22
22
|
tmuxRun,
|
|
23
|
-
escapeTmuxFormat,
|
|
24
23
|
TMUX_DEFAULT_STATUS_LEFT,
|
|
25
24
|
TMUX_DEFAULT_STATUS_LEFT_LENGTH,
|
|
26
25
|
TMUX_DEFAULT_STATUS_RIGHT,
|
|
27
26
|
TMUX_DEFAULT_STATUS_RIGHT_LENGTH,
|
|
27
|
+
TMUX_ATTACHED_STATUS_RIGHT,
|
|
28
|
+
TMUX_ATTACHED_STATUS_RIGHT_LENGTH,
|
|
29
|
+
TMUX_ATTACHED_WINDOW_FMT,
|
|
30
|
+
TMUX_ATTACHED_WINDOW_STYLE,
|
|
31
|
+
TMUX_ATTACHED_WINDOW_CURRENT_STYLE,
|
|
28
32
|
} from "../runtime/tmux.ts";
|
|
29
33
|
import {
|
|
30
34
|
useStore,
|
|
@@ -353,52 +357,69 @@ export function SessionGraphPanel() {
|
|
|
353
357
|
}
|
|
354
358
|
}, [focusedId, focused, termW, termH, padX, padY, viewportH, layout.rowH]);
|
|
355
359
|
|
|
356
|
-
// ──
|
|
357
|
-
// Ctrl+G
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
|
|
360
|
+
// ── Track active tmux window ──────────────────────────
|
|
361
|
+
// Ctrl+G and Ctrl+\ are bound at the tmux level, so the React app
|
|
362
|
+
// never receives them. Poll the active window to sync viewMode
|
|
363
|
+
// with tmux-level navigation in both directions.
|
|
364
|
+
const hasStartedAgent = useMemo(
|
|
365
|
+
() => store.sessions.some((s) => s.name !== "orchestrator" && s.status !== "pending"),
|
|
366
|
+
[storeVersion],
|
|
367
|
+
);
|
|
368
|
+
|
|
361
369
|
useEffect(() => {
|
|
362
|
-
if (
|
|
370
|
+
if (!hasStartedAgent) return;
|
|
363
371
|
|
|
364
372
|
const check = () => {
|
|
365
373
|
const result = tmuxRun([
|
|
366
|
-
"display-message", "-t", tmuxSession, "-p", "#{window_index}",
|
|
374
|
+
"display-message", "-t", tmuxSession, "-p", "#{window_index} #{window_name}",
|
|
367
375
|
]);
|
|
368
|
-
if (result.ok
|
|
369
|
-
|
|
376
|
+
if (!result.ok) return;
|
|
377
|
+
|
|
378
|
+
const output = result.stdout.trim();
|
|
379
|
+
const spaceIdx = output.indexOf(" ");
|
|
380
|
+
const idx = spaceIdx >= 0 ? output.slice(0, spaceIdx) : output;
|
|
381
|
+
const windowName = spaceIdx >= 0 ? output.slice(spaceIdx + 1) : "";
|
|
382
|
+
|
|
383
|
+
if (idx === "0") {
|
|
384
|
+
if (store.viewMode !== "graph") {
|
|
385
|
+
store.setViewMode("graph");
|
|
386
|
+
}
|
|
387
|
+
} else if (store.viewMode !== "attached" || store.activeAgentId !== windowName) {
|
|
388
|
+
store.setViewMode("attached", windowName);
|
|
370
389
|
}
|
|
371
390
|
};
|
|
372
391
|
|
|
373
|
-
const id = setInterval(check,
|
|
392
|
+
const id = setInterval(check, 500);
|
|
374
393
|
return () => clearInterval(id);
|
|
375
|
-
}, [
|
|
394
|
+
}, [tmuxSession, hasStartedAgent]);
|
|
376
395
|
|
|
377
396
|
// ── Tmux status bar sync ──────────────────────────────
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
const subagentCount = store.getSubagents().length;
|
|
382
|
-
const activeAgentIdx = store.getActiveAgentIndex();
|
|
383
|
-
|
|
397
|
+
// Attached mode: use tmux's native window list to show agent names
|
|
398
|
+
// (the current window is highlighted automatically by tmux).
|
|
399
|
+
// Graph mode: restore the minimal defaults.
|
|
384
400
|
useEffect(() => {
|
|
385
|
-
if (store.viewMode === "attached"
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
tmuxRun(["set", "-g", "status-
|
|
391
|
-
tmuxRun(["set", "-g", "status-
|
|
392
|
-
tmuxRun(["set", "-g", "status-
|
|
393
|
-
tmuxRun(["set", "-g", "status-
|
|
401
|
+
if (store.viewMode === "attached") {
|
|
402
|
+
tmuxRun(["set", "-g", "status-left", " "]);
|
|
403
|
+
tmuxRun(["set", "-g", "status-left-length", "1"]);
|
|
404
|
+
tmuxRun(["set", "-g", "status-right", TMUX_ATTACHED_STATUS_RIGHT]);
|
|
405
|
+
tmuxRun(["set", "-g", "status-right-length", TMUX_ATTACHED_STATUS_RIGHT_LENGTH]);
|
|
406
|
+
tmuxRun(["set", "-g", "window-status-format", TMUX_ATTACHED_WINDOW_FMT]);
|
|
407
|
+
tmuxRun(["set", "-g", "window-status-current-format", TMUX_ATTACHED_WINDOW_FMT]);
|
|
408
|
+
tmuxRun(["set", "-g", "window-status-style", TMUX_ATTACHED_WINDOW_STYLE]);
|
|
409
|
+
tmuxRun(["set", "-g", "window-status-current-style", TMUX_ATTACHED_WINDOW_CURRENT_STYLE]);
|
|
410
|
+
tmuxRun(["set", "-g", "window-status-separator", ""]);
|
|
394
411
|
} else {
|
|
395
|
-
// Graph mode: restore defaults (constants from tmux.ts match tmux.conf)
|
|
396
412
|
tmuxRun(["set", "-g", "status-left", TMUX_DEFAULT_STATUS_LEFT]);
|
|
397
413
|
tmuxRun(["set", "-g", "status-left-length", TMUX_DEFAULT_STATUS_LEFT_LENGTH]);
|
|
398
414
|
tmuxRun(["set", "-g", "status-right", TMUX_DEFAULT_STATUS_RIGHT]);
|
|
399
415
|
tmuxRun(["set", "-g", "status-right-length", TMUX_DEFAULT_STATUS_RIGHT_LENGTH]);
|
|
416
|
+
tmuxRun(["set", "-gu", "window-status-format"]);
|
|
417
|
+
tmuxRun(["set", "-gu", "window-status-current-format"]);
|
|
418
|
+
tmuxRun(["set", "-gu", "window-status-style"]);
|
|
419
|
+
tmuxRun(["set", "-gu", "window-status-current-style"]);
|
|
420
|
+
tmuxRun(["set", "-gu", "window-status-separator"]);
|
|
400
421
|
}
|
|
401
|
-
}, [store.viewMode
|
|
422
|
+
}, [store.viewMode]);
|
|
402
423
|
|
|
403
424
|
// Restore default tmux status bar on unmount
|
|
404
425
|
useEffect(() => {
|
|
@@ -407,6 +428,11 @@ export function SessionGraphPanel() {
|
|
|
407
428
|
tmuxRun(["set", "-g", "status-left-length", TMUX_DEFAULT_STATUS_LEFT_LENGTH]);
|
|
408
429
|
tmuxRun(["set", "-g", "status-right", TMUX_DEFAULT_STATUS_RIGHT]);
|
|
409
430
|
tmuxRun(["set", "-g", "status-right-length", TMUX_DEFAULT_STATUS_RIGHT_LENGTH]);
|
|
431
|
+
tmuxRun(["set", "-gu", "window-status-format"]);
|
|
432
|
+
tmuxRun(["set", "-gu", "window-status-current-format"]);
|
|
433
|
+
tmuxRun(["set", "-gu", "window-status-style"]);
|
|
434
|
+
tmuxRun(["set", "-gu", "window-status-current-style"]);
|
|
435
|
+
tmuxRun(["set", "-gu", "window-status-separator"]);
|
|
410
436
|
};
|
|
411
437
|
}, []);
|
|
412
438
|
|
package/src/sdk/runtime/tmux.ts
CHANGED
|
@@ -40,14 +40,17 @@ export const TMUX_DEFAULT_STATUS_LEFT_LENGTH = "10";
|
|
|
40
40
|
export const TMUX_DEFAULT_STATUS_RIGHT = " #{session_name} | %H:%M ";
|
|
41
41
|
export const TMUX_DEFAULT_STATUS_RIGHT_LENGTH = "60";
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
// Attached-mode status bar — agent list via tmux window list + shortcut hints.
|
|
44
|
+
// The window-status formats hide window 0 (orchestrator) and style agent names.
|
|
45
|
+
// tmux natively highlights the current window, so no React state sync is needed
|
|
46
|
+
// for agent cycling via Ctrl+\.
|
|
47
|
+
export const TMUX_ATTACHED_STATUS_RIGHT =
|
|
48
|
+
"#[fg=#cdd6f4]ctrl+g #[fg=#6c7086]graph #[fg=#585b70]\u00b7 #[fg=#cdd6f4]ctrl+\\ #[fg=#6c7086]next ";
|
|
49
|
+
export const TMUX_ATTACHED_STATUS_RIGHT_LENGTH = "40";
|
|
50
|
+
export const TMUX_ATTACHED_WINDOW_FMT =
|
|
51
|
+
"#{?#{==:#{window_index},0},, #W }";
|
|
52
|
+
export const TMUX_ATTACHED_WINDOW_STYLE = "fg=#6c7086";
|
|
53
|
+
export const TMUX_ATTACHED_WINDOW_CURRENT_STYLE = "fg=#cdd6f4,bold";
|
|
51
54
|
|
|
52
55
|
// ---------------------------------------------------------------------------
|
|
53
56
|
// Core tmux primitives
|
|
@@ -11,19 +11,18 @@
|
|
|
11
11
|
* comparing the bundled `VERSION` constant against a marker file at
|
|
12
12
|
* `~/.atomic/.synced-version`. On a mismatch we run the same setup the
|
|
13
13
|
* production bootstrap installers (`install.sh` / `install.ps1`) provide,
|
|
14
|
-
*
|
|
14
|
+
* as a single parallel phase:
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* 1. tmux / psmux (terminal multiplexer for `chat` / `workflow`)
|
|
17
|
+
* 2. global agent configs (file copies — no network)
|
|
18
|
+
* 3. @playwright/cli (bun install -g)
|
|
19
|
+
* 4. @llamaindex/liteparse (bun install -g)
|
|
20
|
+
* 5. global skills (bunx skills add ...)
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* 6. global skills (npx skills add ...)
|
|
22
|
+
* All steps run concurrently using bun (already our runtime) for package
|
|
23
|
+
* installs and `bunx` for CLI tools, avoiding a ~48 s Node.js/npm
|
|
24
|
+
* download via fnm that previously gated Phase 2.
|
|
25
25
|
*
|
|
26
|
-
* Steps within each phase run concurrently; phases run sequentially.
|
|
27
26
|
* Failures are collected and reported as a summary at the end, but never
|
|
28
27
|
* abort the run — partial setup matches the production installer's
|
|
29
28
|
* "best-effort" semantics. The marker is written after every run (success
|
|
@@ -36,7 +35,6 @@ import { homedir } from "node:os";
|
|
|
36
35
|
import { VERSION } from "../../version.ts";
|
|
37
36
|
import { COLORS } from "../../theme/colors.ts";
|
|
38
37
|
import {
|
|
39
|
-
ensureNpmInstalled,
|
|
40
38
|
ensureTmuxInstalled,
|
|
41
39
|
upgradePlaywrightCli,
|
|
42
40
|
upgradeLiteparse,
|
|
@@ -93,33 +91,21 @@ export async function autoSyncIfStale(): Promise<void> {
|
|
|
93
91
|
`\n ${COLORS.dim}Setting up atomic ${COLORS.reset}${COLORS.bold}v${VERSION}${COLORS.reset}${COLORS.dim}…${COLORS.reset}`,
|
|
94
92
|
);
|
|
95
93
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// won't contend with tmux's apt-get/dnf install. Agent config
|
|
101
|
-
// copies are pure file I/O with no network or npm dependency.
|
|
102
|
-
//
|
|
103
|
-
// Phase 2 — npm-dependent tasks (run after Phase 1):
|
|
104
|
-
// @playwright/cli, @llamaindex/liteparse, and `npx skills` all
|
|
105
|
-
// need npm/npx. They install independent packages, so they can
|
|
106
|
-
// run concurrently.
|
|
94
|
+
// All steps run in a single parallel phase. bun (already our runtime)
|
|
95
|
+
// handles global package installs and `bunx` execution, so there is no
|
|
96
|
+
// need to install Node.js/npm first — eliminating a ~48 s fnm download
|
|
97
|
+
// that previously dominated the loading screen.
|
|
107
98
|
//
|
|
108
99
|
// Each step's failure is caught inside `runSteps` (not thrown), so
|
|
109
100
|
// subsequent steps still run even if one fails — matches install.sh's
|
|
110
101
|
// best-effort contract.
|
|
111
102
|
const results = await runSteps([
|
|
112
|
-
// Phase 1 — parallel
|
|
113
103
|
[
|
|
114
|
-
{ label: "Node.js / npm", fn: () => ensureNpmInstalled({ quiet: true }) },
|
|
115
104
|
{ label: "tmux / psmux", fn: () => ensureTmuxInstalled({ quiet: true }) },
|
|
116
105
|
{ label: "global agent configs", fn: installGlobalAgents },
|
|
117
|
-
|
|
118
|
-
// Phase 2 — parallel, after Phase 1
|
|
119
|
-
[
|
|
120
|
-
{ label: "@playwright/cli", fn: upgradePlaywrightCli },
|
|
106
|
+
{ label: "@playwright/cli", fn: upgradePlaywrightCli },
|
|
121
107
|
{ label: "@llamaindex/liteparse", fn: upgradeLiteparse },
|
|
122
|
-
{ label: "global skills",
|
|
108
|
+
{ label: "global skills", fn: installGlobalSkills },
|
|
123
109
|
],
|
|
124
110
|
]);
|
|
125
111
|
|
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
* that falls back gracefully through 256-color → basic ANSI.
|
|
12
12
|
*
|
|
13
13
|
* Steps are grouped into **phases**. Steps within a phase run in parallel
|
|
14
|
-
* (via `Promise.all`); phases themselves run sequentially
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* updates in real-time as individual steps complete within a phase.
|
|
14
|
+
* (via `Promise.all`); phases themselves run sequentially. The progress
|
|
15
|
+
* bar advances and the label updates in real-time as individual steps
|
|
16
|
+
* complete within a phase.
|
|
18
17
|
*
|
|
19
18
|
* A final summary (✓/✗ per step) is printed after all steps finish, and
|
|
20
19
|
* any captured stderr/stdout from a failed step is shown beneath it.
|
|
@@ -18,15 +18,20 @@ interface NpxSkillsResult {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async function runNpxSkills(args: string[]): Promise<NpxSkillsResult> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
// Prefer bunx (already available as our runtime) over npx to avoid
|
|
22
|
+
// depending on a full Node.js/npm installation.
|
|
23
|
+
const runner = Bun.which("bunx") ?? Bun.which("npx");
|
|
24
|
+
if (!runner) {
|
|
25
|
+
return { ok: false, details: "neither bunx nor npx found on PATH" };
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// Capture stdout/stderr so the outer spinner UI owns terminal output and
|
|
27
|
-
// can surface the tail of any failure.
|
|
28
|
-
|
|
29
|
-
const
|
|
29
|
+
// can surface the tail of any failure.
|
|
30
|
+
const isBunx = runner.endsWith("bunx");
|
|
31
|
+
const cmd = isBunx
|
|
32
|
+
? [runner, "skills", ...args]
|
|
33
|
+
: [runner, "--yes", "skills", ...args];
|
|
34
|
+
const proc = Bun.spawn(cmd, {
|
|
30
35
|
stdout: "pipe",
|
|
31
36
|
stderr: "pipe",
|
|
32
37
|
});
|