@bastani/atomic 0.5.17 → 0.5.18-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 +14 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +50 -54
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +17 -36
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +64 -44
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.d.ts +43 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +17 -39
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +21 -2
- package/src/commands/cli/session.test.ts +223 -0
- package/src/commands/cli/session.ts +117 -1
- package/src/completions/bash.ts +3 -3
- package/src/completions/fish.ts +13 -7
- package/src/completions/powershell.ts +3 -0
- package/src/completions/zsh.ts +2 -1
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +260 -157
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +224 -125
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +2 -2
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +428 -469
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.ts +115 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +249 -137
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
sessionListCommand,
|
|
7
7
|
sessionConnectCommand,
|
|
8
8
|
sessionPickerCommand,
|
|
9
|
+
sessionKillCommand,
|
|
9
10
|
} from "./session.ts";
|
|
10
11
|
import type { SessionDeps } from "./session.ts";
|
|
11
12
|
import type { TmuxSession } from "../../sdk/runtime/tmux.ts";
|
|
@@ -287,6 +288,9 @@ const tmuxMocks = {
|
|
|
287
288
|
spawnMuxAttach: mock(() => ({ exited: Promise.resolve(0) }) as never),
|
|
288
289
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
289
290
|
select: mock<(...args: any[]) => Promise<string | symbol>>(() => Promise.resolve("my-session")),
|
|
291
|
+
killSession: mock<(name: string) => void>(() => {}),
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
293
|
+
confirm: mock<(...args: any[]) => Promise<boolean | symbol>>(() => Promise.resolve(true)),
|
|
290
294
|
isCancel: ((v: unknown) => typeof v === "symbol") as SessionDeps["isCancel"],
|
|
291
295
|
};
|
|
292
296
|
|
|
@@ -305,6 +309,8 @@ function resetTmuxMocks(): void {
|
|
|
305
309
|
tmuxMocks.detachAndAttachAtomic.mockReset();
|
|
306
310
|
tmuxMocks.spawnMuxAttach.mockReset().mockReturnValue({ exited: Promise.resolve(0) } as never);
|
|
307
311
|
tmuxMocks.select.mockReset().mockResolvedValue("my-session");
|
|
312
|
+
tmuxMocks.killSession.mockReset();
|
|
313
|
+
tmuxMocks.confirm.mockReset().mockResolvedValue(true);
|
|
308
314
|
}
|
|
309
315
|
|
|
310
316
|
// ─── sessionListCommand ─────────────────────────────────────────────────
|
|
@@ -489,3 +495,220 @@ describe("sessionPickerCommand", () => {
|
|
|
489
495
|
expect(tmuxMocks.spawnMuxAttach).not.toHaveBeenCalled();
|
|
490
496
|
});
|
|
491
497
|
});
|
|
498
|
+
|
|
499
|
+
// ─── sessionKillCommand ──────────────────────────────────────────────────
|
|
500
|
+
|
|
501
|
+
describe("sessionKillCommand", () => {
|
|
502
|
+
beforeEach(resetTmuxMocks);
|
|
503
|
+
|
|
504
|
+
// (a) tmux not installed → stdout "no sessions running" + "tmux is not installed", return 0
|
|
505
|
+
test("returns 0 with 'no sessions' when tmux is not installed", async () => {
|
|
506
|
+
tmuxMocks.isTmuxInstalled.mockReturnValue(false);
|
|
507
|
+
const chunks: string[] = [];
|
|
508
|
+
const origWrite = process.stdout.write;
|
|
509
|
+
process.stdout.write = ((c: string) => { chunks.push(c); return true; }) as typeof process.stdout.write;
|
|
510
|
+
try {
|
|
511
|
+
const code = await sessionKillCommand(undefined, [], "all", makeDeps());
|
|
512
|
+
expect(code).toBe(0);
|
|
513
|
+
const output = chunks.join("");
|
|
514
|
+
expect(output).toContain("no sessions running");
|
|
515
|
+
expect(output).toContain("tmux is not installed");
|
|
516
|
+
} finally {
|
|
517
|
+
process.stdout.write = origWrite;
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// (b) named id, session not in scope → stderr error, return 1
|
|
522
|
+
test("returns 1 with error when named session does not exist", async () => {
|
|
523
|
+
// listSessions returns empty, so the target won't be found
|
|
524
|
+
tmuxMocks.listSessions.mockReturnValue([]);
|
|
525
|
+
const chunks: string[] = [];
|
|
526
|
+
const origWrite = process.stderr.write;
|
|
527
|
+
process.stderr.write = ((c: string) => { chunks.push(c); return true; }) as typeof process.stderr.write;
|
|
528
|
+
try {
|
|
529
|
+
const code = await sessionKillCommand("ghost-session", [], "all", makeDeps());
|
|
530
|
+
expect(code).toBe(1);
|
|
531
|
+
const output = chunks.join("");
|
|
532
|
+
expect(output).toContain("ghost-session");
|
|
533
|
+
} finally {
|
|
534
|
+
process.stderr.write = origWrite;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// (c) named id, session exists but is out of scope → return 1
|
|
539
|
+
test("returns 1 when named session exists but is out of scope", async () => {
|
|
540
|
+
const now = new Date().toISOString();
|
|
541
|
+
// listSessions has a chat session, but we request scope=workflow
|
|
542
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
543
|
+
{ name: "atomic-chat-claude-aaa11111", windows: 1, created: now, attached: false, type: "chat" as const, agent: "claude" },
|
|
544
|
+
]);
|
|
545
|
+
const chunks: string[] = [];
|
|
546
|
+
const origWrite = process.stderr.write;
|
|
547
|
+
process.stderr.write = ((c: string) => { chunks.push(c); return true; }) as typeof process.stderr.write;
|
|
548
|
+
try {
|
|
549
|
+
const code = await sessionKillCommand("atomic-chat-claude-aaa11111", [], "workflow", makeDeps());
|
|
550
|
+
expect(code).toBe(1);
|
|
551
|
+
const output = chunks.join("");
|
|
552
|
+
expect(output).toContain("atomic-chat-claude-aaa11111");
|
|
553
|
+
} finally {
|
|
554
|
+
process.stderr.write = origWrite;
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// (d) named id found, user confirms → killSession called, return 0
|
|
559
|
+
test("prompts and calls killSession on confirm for named session", async () => {
|
|
560
|
+
const now = new Date().toISOString();
|
|
561
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
562
|
+
{ name: "target-session", windows: 1, created: now, attached: false, type: "chat" as const },
|
|
563
|
+
]);
|
|
564
|
+
tmuxMocks.confirm.mockResolvedValue(true);
|
|
565
|
+
const chunks: string[] = [];
|
|
566
|
+
const origWrite = process.stdout.write;
|
|
567
|
+
process.stdout.write = ((c: string) => { chunks.push(c); return true; }) as typeof process.stdout.write;
|
|
568
|
+
try {
|
|
569
|
+
const code = await sessionKillCommand("target-session", [], "all", makeDeps());
|
|
570
|
+
expect(code).toBe(0);
|
|
571
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledTimes(1);
|
|
572
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledWith("target-session");
|
|
573
|
+
} finally {
|
|
574
|
+
process.stdout.write = origWrite;
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// (e) named id found, user declines → killSession NOT called, return 0
|
|
579
|
+
test("does NOT call killSession when user declines named kill", async () => {
|
|
580
|
+
const now = new Date().toISOString();
|
|
581
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
582
|
+
{ name: "target-session", windows: 1, created: now, attached: false, type: "chat" as const },
|
|
583
|
+
]);
|
|
584
|
+
tmuxMocks.confirm.mockResolvedValue(false);
|
|
585
|
+
const origWrite = process.stdout.write;
|
|
586
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
587
|
+
try {
|
|
588
|
+
const code = await sessionKillCommand("target-session", [], "all", makeDeps());
|
|
589
|
+
expect(code).toBe(0);
|
|
590
|
+
expect(tmuxMocks.killSession).not.toHaveBeenCalled();
|
|
591
|
+
} finally {
|
|
592
|
+
process.stdout.write = origWrite;
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// (f) named id found, user cancels (symbol) → killSession NOT called, return 0
|
|
597
|
+
test("does NOT call killSession when user cancels named kill", async () => {
|
|
598
|
+
const now = new Date().toISOString();
|
|
599
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
600
|
+
{ name: "target-session", windows: 1, created: now, attached: false, type: "chat" as const },
|
|
601
|
+
]);
|
|
602
|
+
tmuxMocks.confirm.mockResolvedValue(Symbol("cancel"));
|
|
603
|
+
const origWrite = process.stdout.write;
|
|
604
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
605
|
+
try {
|
|
606
|
+
const code = await sessionKillCommand("target-session", [], "all", makeDeps());
|
|
607
|
+
expect(code).toBe(0);
|
|
608
|
+
expect(tmuxMocks.killSession).not.toHaveBeenCalled();
|
|
609
|
+
} finally {
|
|
610
|
+
process.stdout.write = origWrite;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// (g) omitted id, no sessions → empty state on stdout, return 0, confirm NOT called
|
|
615
|
+
test("omitted id with no sessions prints empty state and returns 0 without prompt", async () => {
|
|
616
|
+
tmuxMocks.listSessions.mockReturnValue([]);
|
|
617
|
+
const chunks: string[] = [];
|
|
618
|
+
const origWrite = process.stdout.write;
|
|
619
|
+
process.stdout.write = ((c: string) => { chunks.push(c); return true; }) as typeof process.stdout.write;
|
|
620
|
+
try {
|
|
621
|
+
const code = await sessionKillCommand(undefined, [], "all", makeDeps());
|
|
622
|
+
expect(code).toBe(0);
|
|
623
|
+
const output = chunks.join("");
|
|
624
|
+
expect(output).toContain("no sessions running");
|
|
625
|
+
expect(tmuxMocks.confirm).not.toHaveBeenCalled();
|
|
626
|
+
} finally {
|
|
627
|
+
process.stdout.write = origWrite;
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// (h) omitted id, N sessions, user confirms → killSession called for each, return 0
|
|
632
|
+
test("omitted id prompts and kills all sessions on confirm", async () => {
|
|
633
|
+
const now = new Date().toISOString();
|
|
634
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
635
|
+
{ name: "session-a", windows: 1, created: now, attached: false, type: "chat" as const, agent: "claude" },
|
|
636
|
+
{ name: "session-b", windows: 1, created: now, attached: false, type: "workflow" as const, agent: "opencode" },
|
|
637
|
+
{ name: "session-c", windows: 1, created: now, attached: false, type: "chat" as const, agent: "copilot" },
|
|
638
|
+
]);
|
|
639
|
+
tmuxMocks.confirm.mockResolvedValue(true);
|
|
640
|
+
const origWrite = process.stdout.write;
|
|
641
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
642
|
+
try {
|
|
643
|
+
const code = await sessionKillCommand(undefined, [], "all", makeDeps());
|
|
644
|
+
expect(code).toBe(0);
|
|
645
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledTimes(3);
|
|
646
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledWith("session-a");
|
|
647
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledWith("session-b");
|
|
648
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledWith("session-c");
|
|
649
|
+
} finally {
|
|
650
|
+
process.stdout.write = origWrite;
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// (i) scope=chat, only chat sessions killed when id omitted
|
|
655
|
+
test("scope=chat only kills chat sessions when id omitted", async () => {
|
|
656
|
+
const now = new Date().toISOString();
|
|
657
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
658
|
+
{ name: "chat-session", windows: 1, created: now, attached: false, type: "chat" as const, agent: "claude" },
|
|
659
|
+
{ name: "wf-session", windows: 1, created: now, attached: false, type: "workflow" as const, agent: "opencode" },
|
|
660
|
+
]);
|
|
661
|
+
tmuxMocks.confirm.mockResolvedValue(true);
|
|
662
|
+
const origWrite = process.stdout.write;
|
|
663
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
664
|
+
try {
|
|
665
|
+
const code = await sessionKillCommand(undefined, [], "chat", makeDeps());
|
|
666
|
+
expect(code).toBe(0);
|
|
667
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledTimes(1);
|
|
668
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledWith("chat-session");
|
|
669
|
+
expect(tmuxMocks.killSession).not.toHaveBeenCalledWith("wf-session");
|
|
670
|
+
} finally {
|
|
671
|
+
process.stdout.write = origWrite;
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// (j) scope=workflow, only workflow sessions killed when id omitted
|
|
676
|
+
test("scope=workflow only kills workflow sessions when id omitted", async () => {
|
|
677
|
+
const now = new Date().toISOString();
|
|
678
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
679
|
+
{ name: "chat-session", windows: 1, created: now, attached: false, type: "chat" as const, agent: "claude" },
|
|
680
|
+
{ name: "wf-session", windows: 1, created: now, attached: false, type: "workflow" as const, agent: "opencode" },
|
|
681
|
+
]);
|
|
682
|
+
tmuxMocks.confirm.mockResolvedValue(true);
|
|
683
|
+
const origWrite = process.stdout.write;
|
|
684
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
685
|
+
try {
|
|
686
|
+
const code = await sessionKillCommand(undefined, [], "workflow", makeDeps());
|
|
687
|
+
expect(code).toBe(0);
|
|
688
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledTimes(1);
|
|
689
|
+
expect(tmuxMocks.killSession).toHaveBeenCalledWith("wf-session");
|
|
690
|
+
expect(tmuxMocks.killSession).not.toHaveBeenCalledWith("chat-session");
|
|
691
|
+
} finally {
|
|
692
|
+
process.stdout.write = origWrite;
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// (k) user declines kill-all → killSession NOT called
|
|
697
|
+
test("does NOT kill any sessions when user declines kill-all", async () => {
|
|
698
|
+
const now = new Date().toISOString();
|
|
699
|
+
tmuxMocks.listSessions.mockReturnValue([
|
|
700
|
+
{ name: "session-x", windows: 1, created: now, attached: false, type: "chat" as const },
|
|
701
|
+
{ name: "session-y", windows: 1, created: now, attached: false, type: "workflow" as const },
|
|
702
|
+
]);
|
|
703
|
+
tmuxMocks.confirm.mockResolvedValue(false);
|
|
704
|
+
const origWrite = process.stdout.write;
|
|
705
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
706
|
+
try {
|
|
707
|
+
const code = await sessionKillCommand(undefined, [], "all", makeDeps());
|
|
708
|
+
expect(code).toBe(0);
|
|
709
|
+
expect(tmuxMocks.killSession).not.toHaveBeenCalled();
|
|
710
|
+
} finally {
|
|
711
|
+
process.stdout.write = origWrite;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* tmux directly.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { select, isCancel, cancel } from "@clack/prompts";
|
|
10
|
+
import { select, confirm, isCancel, cancel } from "@clack/prompts";
|
|
11
11
|
import { createPainter, type PaletteKey } from "../../theme/colors.ts";
|
|
12
12
|
import {
|
|
13
13
|
listSessions as _listSessions,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
switchClient as _switchClient,
|
|
19
19
|
spawnMuxAttach as _spawnMuxAttach,
|
|
20
20
|
detachAndAttachAtomic as _detachAndAttachAtomic,
|
|
21
|
+
killSession as _killSession,
|
|
21
22
|
SOCKET_NAME,
|
|
22
23
|
} from "../../sdk/workflows/index.ts";
|
|
23
24
|
import type { TmuxSession, SessionType } from "../../sdk/runtime/tmux.ts";
|
|
@@ -36,8 +37,11 @@ export interface SessionDeps {
|
|
|
36
37
|
switchClient: (name: string) => void;
|
|
37
38
|
spawnMuxAttach: (name: string) => Subprocess;
|
|
38
39
|
detachAndAttachAtomic: (name: string) => void;
|
|
40
|
+
killSession: (name: string) => void;
|
|
39
41
|
/** Prompt function for the session picker — defaults to @clack/prompts select. */
|
|
40
42
|
select: typeof select;
|
|
43
|
+
/** Prompt function for yes/no confirmations — defaults to @clack/prompts confirm. */
|
|
44
|
+
confirm: typeof confirm;
|
|
41
45
|
isCancel: typeof isCancel;
|
|
42
46
|
}
|
|
43
47
|
|
|
@@ -51,7 +55,9 @@ const defaultDeps: SessionDeps = {
|
|
|
51
55
|
switchClient: _switchClient,
|
|
52
56
|
spawnMuxAttach: _spawnMuxAttach,
|
|
53
57
|
detachAndAttachAtomic: _detachAndAttachAtomic,
|
|
58
|
+
killSession: _killSession,
|
|
54
59
|
select,
|
|
60
|
+
confirm,
|
|
55
61
|
isCancel,
|
|
56
62
|
};
|
|
57
63
|
|
|
@@ -263,3 +269,113 @@ export async function sessionPickerCommand(agents: string[] = [], scope: Session
|
|
|
263
269
|
|
|
264
270
|
return sessionConnectCommand(selected as string, deps);
|
|
265
271
|
}
|
|
272
|
+
|
|
273
|
+
// ─── Session kill command ────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Kill a named session or all sessions matching the given scope and agents.
|
|
277
|
+
*
|
|
278
|
+
* - If `sessionId` is provided: confirm and kill that one session.
|
|
279
|
+
* - If `sessionId` is omitted: confirm and kill all sessions in scope.
|
|
280
|
+
*/
|
|
281
|
+
export async function sessionKillCommand(
|
|
282
|
+
sessionId: string | undefined,
|
|
283
|
+
agents: string[] = [],
|
|
284
|
+
scope: SessionScope = "all",
|
|
285
|
+
deps: SessionDeps = defaultDeps,
|
|
286
|
+
): Promise<number> {
|
|
287
|
+
const paint = createPainter();
|
|
288
|
+
|
|
289
|
+
if (!deps.isTmuxInstalled()) {
|
|
290
|
+
process.stdout.write(
|
|
291
|
+
"\n " + paint("text", "no sessions running", { bold: true }) +
|
|
292
|
+
"\n\n " + paint("dim", "tmux is not installed") + "\n\n",
|
|
293
|
+
);
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ── Named kill path ───────────────────────────────────────────────────────
|
|
298
|
+
if (sessionId !== undefined) {
|
|
299
|
+
const inScope = filterByScope(deps.listSessions(), scope);
|
|
300
|
+
const target = inScope.find((s) => s.name === sessionId);
|
|
301
|
+
|
|
302
|
+
if (!target) {
|
|
303
|
+
const scopeLabel = scope === "all" ? "" : ` in ${scope} scope`;
|
|
304
|
+
process.stderr.write(
|
|
305
|
+
paint("error", `Error: session '${sessionId}' not found${scopeLabel}.`) + "\n",
|
|
306
|
+
);
|
|
307
|
+
if (inScope.length > 0) {
|
|
308
|
+
process.stderr.write(
|
|
309
|
+
"\n" + paint("dim", "Available sessions:") + "\n",
|
|
310
|
+
);
|
|
311
|
+
for (const s of inScope) {
|
|
312
|
+
process.stderr.write(
|
|
313
|
+
" " + paint("dim", "○") + " " + paint("text", s.name) + "\n",
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
process.stderr.write("\n");
|
|
317
|
+
}
|
|
318
|
+
return 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const answer = await deps.confirm({
|
|
322
|
+
message: `Kill session '${sessionId}'?`,
|
|
323
|
+
initialValue: false,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (deps.isCancel(answer)) {
|
|
327
|
+
cancel("Cancelled.");
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (answer === true) {
|
|
332
|
+
deps.killSession(sessionId);
|
|
333
|
+
process.stdout.write(
|
|
334
|
+
"\n " + paint("success", "✓") + " killed " + paint("text", sessionId) + "\n\n",
|
|
335
|
+
);
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// answer === false
|
|
340
|
+
process.stdout.write(
|
|
341
|
+
"\n " + paint("dim", "Cancelled.") + "\n\n",
|
|
342
|
+
);
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Kill-all path ─────────────────────────────────────────────────────────
|
|
347
|
+
const targets = filterByAgent(filterByScope(deps.listSessions(), scope), agents);
|
|
348
|
+
|
|
349
|
+
if (targets.length === 0) {
|
|
350
|
+
process.stdout.write(renderSessionList([]));
|
|
351
|
+
return 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const noun = targets.length === 1 ? "session" : "sessions";
|
|
355
|
+
const scopePrefix = scope === "all" ? "" : `${scope} `;
|
|
356
|
+
const answer = await deps.confirm({
|
|
357
|
+
message: `Kill all ${targets.length} ${scopePrefix}${noun}?`,
|
|
358
|
+
initialValue: false,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
if (deps.isCancel(answer)) {
|
|
362
|
+
cancel("Cancelled.");
|
|
363
|
+
return 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (answer === true) {
|
|
367
|
+
for (const t of targets) {
|
|
368
|
+
deps.killSession(t.name);
|
|
369
|
+
}
|
|
370
|
+
process.stdout.write(
|
|
371
|
+
"\n " + paint("success", "✓") + " killed " + paint("text", String(targets.length)) + " " + paint("dim", noun) + "\n\n",
|
|
372
|
+
);
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// answer === false
|
|
377
|
+
process.stdout.write(
|
|
378
|
+
"\n " + paint("dim", "Cancelled.") + "\n\n",
|
|
379
|
+
);
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
package/src/completions/bash.ts
CHANGED
|
@@ -58,7 +58,7 @@ _atomic_completions() {
|
|
|
58
58
|
COMPREPLY=( $(compgen -W "session -a --agent -h --help" -- "$cur") )
|
|
59
59
|
elif [[ "$cmd2" == "session" ]]; then
|
|
60
60
|
if [[ -z "$cmd3" ]]; then
|
|
61
|
-
COMPREPLY=( $(compgen -W "list connect -h --help" -- "$cur") )
|
|
61
|
+
COMPREPLY=( $(compgen -W "list connect kill -h --help" -- "$cur") )
|
|
62
62
|
else
|
|
63
63
|
COMPREPLY=( $(compgen -W "-a --agent -h --help" -- "$cur") )
|
|
64
64
|
fi
|
|
@@ -71,7 +71,7 @@ _atomic_completions() {
|
|
|
71
71
|
COMPREPLY=( $(compgen -W "-a --agent -h --help" -- "$cur") )
|
|
72
72
|
elif [[ "$cmd2" == "session" ]]; then
|
|
73
73
|
if [[ -z "$cmd3" ]]; then
|
|
74
|
-
COMPREPLY=( $(compgen -W "list connect -h --help" -- "$cur") )
|
|
74
|
+
COMPREPLY=( $(compgen -W "list connect kill -h --help" -- "$cur") )
|
|
75
75
|
else
|
|
76
76
|
COMPREPLY=( $(compgen -W "-a --agent -h --help" -- "$cur") )
|
|
77
77
|
fi
|
|
@@ -79,7 +79,7 @@ _atomic_completions() {
|
|
|
79
79
|
;;
|
|
80
80
|
session)
|
|
81
81
|
if [[ -z "$cmd2" ]]; then
|
|
82
|
-
COMPREPLY=( $(compgen -W "list connect -h --help" -- "$cur") )
|
|
82
|
+
COMPREPLY=( $(compgen -W "list connect kill -h --help" -- "$cur") )
|
|
83
83
|
else
|
|
84
84
|
COMPREPLY=( $(compgen -W "-a --agent -h --help" -- "$cur") )
|
|
85
85
|
fi
|
package/src/completions/fish.ts
CHANGED
|
@@ -56,7 +56,7 @@ end
|
|
|
56
56
|
|
|
57
57
|
function __atomic_needs_subcmd_of
|
|
58
58
|
__atomic_using_cmd $argv
|
|
59
|
-
and not __fish_seen_subcommand_from list connect set
|
|
59
|
+
and not __fish_seen_subcommand_from list connect kill set
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# ── Global options ──────────────────────────────────────────────────────────
|
|
@@ -86,10 +86,12 @@ complete -c atomic -n '__atomic_using_cmd chat; and not __fish_seen_subcommand_f
|
|
|
86
86
|
complete -c atomic -n '__atomic_using_cmd chat; and not __fish_seen_subcommand_from session' -a session -d 'Manage running chat sessions'
|
|
87
87
|
|
|
88
88
|
# chat session
|
|
89
|
-
complete -c atomic -n '__atomic_using_cmd chat session; and not __fish_seen_subcommand_from list connect' -a list -d 'List running sessions'
|
|
90
|
-
complete -c atomic -n '__atomic_using_cmd chat session; and not __fish_seen_subcommand_from list connect' -a connect -d 'Attach to a running session'
|
|
89
|
+
complete -c atomic -n '__atomic_using_cmd chat session; and not __fish_seen_subcommand_from list connect kill' -a list -d 'List running sessions'
|
|
90
|
+
complete -c atomic -n '__atomic_using_cmd chat session; and not __fish_seen_subcommand_from list connect kill' -a connect -d 'Attach to a running session'
|
|
91
|
+
complete -c atomic -n '__atomic_using_cmd chat session; and not __fish_seen_subcommand_from list connect kill' -a kill -d 'Kill a running session (omit id to kill all)'
|
|
91
92
|
complete -c atomic -n '__atomic_using_cmd chat session list' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
92
93
|
complete -c atomic -n '__atomic_using_cmd chat session connect' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
94
|
+
complete -c atomic -n '__atomic_using_cmd chat session kill' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
93
95
|
|
|
94
96
|
# ── workflow ────────────────────────────────────────────────────────────────
|
|
95
97
|
|
|
@@ -102,17 +104,21 @@ complete -c atomic -n '__atomic_using_cmd workflow; and not __fish_seen_subcomma
|
|
|
102
104
|
complete -c atomic -n '__atomic_using_cmd workflow list' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
103
105
|
|
|
104
106
|
# workflow session
|
|
105
|
-
complete -c atomic -n '__atomic_using_cmd workflow session; and not __fish_seen_subcommand_from list connect' -a list -d 'List running sessions'
|
|
106
|
-
complete -c atomic -n '__atomic_using_cmd workflow session; and not __fish_seen_subcommand_from list connect' -a connect -d 'Attach to a running session'
|
|
107
|
+
complete -c atomic -n '__atomic_using_cmd workflow session; and not __fish_seen_subcommand_from list connect kill' -a list -d 'List running sessions'
|
|
108
|
+
complete -c atomic -n '__atomic_using_cmd workflow session; and not __fish_seen_subcommand_from list connect kill' -a connect -d 'Attach to a running session'
|
|
109
|
+
complete -c atomic -n '__atomic_using_cmd workflow session; and not __fish_seen_subcommand_from list connect kill' -a kill -d 'Kill a running session (omit id to kill all)'
|
|
107
110
|
complete -c atomic -n '__atomic_using_cmd workflow session list' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
108
111
|
complete -c atomic -n '__atomic_using_cmd workflow session connect' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
112
|
+
complete -c atomic -n '__atomic_using_cmd workflow session kill' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
109
113
|
|
|
110
114
|
# ── session (top-level) ────────────────────────────────────────────────────
|
|
111
115
|
|
|
112
|
-
complete -c atomic -n '__atomic_using_cmd session; and not __fish_seen_subcommand_from list connect' -a list -d 'List running sessions'
|
|
113
|
-
complete -c atomic -n '__atomic_using_cmd session; and not __fish_seen_subcommand_from list connect' -a connect -d 'Attach to a running session'
|
|
116
|
+
complete -c atomic -n '__atomic_using_cmd session; and not __fish_seen_subcommand_from list connect kill' -a list -d 'List running sessions'
|
|
117
|
+
complete -c atomic -n '__atomic_using_cmd session; and not __fish_seen_subcommand_from list connect kill' -a connect -d 'Attach to a running session'
|
|
118
|
+
complete -c atomic -n '__atomic_using_cmd session; and not __fish_seen_subcommand_from list connect kill' -a kill -d 'Kill a running session (omit id to kill all)'
|
|
114
119
|
complete -c atomic -n '__atomic_using_cmd session list' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
115
120
|
complete -c atomic -n '__atomic_using_cmd session connect' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
121
|
+
complete -c atomic -n '__atomic_using_cmd session kill' -s a -l agent -d 'Filter by agent' -r -a "$agents"
|
|
116
122
|
|
|
117
123
|
# ── config ──────────────────────────────────────────────────────────────────
|
|
118
124
|
|
|
@@ -96,6 +96,7 @@ Register-ArgumentCompleter -Native -CommandName atomic -ScriptBlock {
|
|
|
96
96
|
$completions = @(
|
|
97
97
|
@{ text = 'list'; tip = 'List running sessions' }
|
|
98
98
|
@{ text = 'connect'; tip = 'Attach to a running session' }
|
|
99
|
+
@{ text = 'kill'; tip = 'Kill a running session (omit id to kill all)' }
|
|
99
100
|
)
|
|
100
101
|
} else {
|
|
101
102
|
$completions = @(
|
|
@@ -125,6 +126,7 @@ Register-ArgumentCompleter -Native -CommandName atomic -ScriptBlock {
|
|
|
125
126
|
$completions = @(
|
|
126
127
|
@{ text = 'list'; tip = 'List running sessions' }
|
|
127
128
|
@{ text = 'connect'; tip = 'Attach to a running session' }
|
|
129
|
+
@{ text = 'kill'; tip = 'Kill a running session (omit id to kill all)' }
|
|
128
130
|
)
|
|
129
131
|
} else {
|
|
130
132
|
$completions = @(
|
|
@@ -139,6 +141,7 @@ Register-ArgumentCompleter -Native -CommandName atomic -ScriptBlock {
|
|
|
139
141
|
$completions = @(
|
|
140
142
|
@{ text = 'list'; tip = 'List running sessions' }
|
|
141
143
|
@{ text = 'connect'; tip = 'Attach to a running session' }
|
|
144
|
+
@{ text = 'kill'; tip = 'Kill a running session (omit id to kill all)' }
|
|
142
145
|
)
|
|
143
146
|
} else {
|
|
144
147
|
$completions = @(
|
package/src/completions/zsh.ts
CHANGED
|
@@ -125,12 +125,13 @@ _atomic_session() {
|
|
|
125
125
|
local -a subs=(
|
|
126
126
|
'list:List running sessions'
|
|
127
127
|
'connect:Attach to a running session'
|
|
128
|
+
'kill:Kill a running session (omit id to kill all)'
|
|
128
129
|
)
|
|
129
130
|
_describe 'subcommand' subs
|
|
130
131
|
;;
|
|
131
132
|
subargs)
|
|
132
133
|
case "\${words[1]}" in
|
|
133
|
-
list|connect)
|
|
134
|
+
list|connect|kill)
|
|
134
135
|
_arguments \\
|
|
135
136
|
'*'{-a,--agent}'[Filter by agent]:agent:(claude opencode copilot)' \\
|
|
136
137
|
'(-h --help)'{-h,--help}'[Show help]'
|