@os-eco/overstory-cli 0.6.1 → 0.6.4
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 +7 -6
- package/package.json +12 -4
- package/src/agents/hooks-deployer.test.ts +94 -16
- package/src/agents/hooks-deployer.ts +18 -0
- package/src/agents/manifest.test.ts +86 -0
- package/src/commands/agents.test.ts +3 -3
- package/src/commands/agents.ts +59 -88
- package/src/commands/clean.test.ts +31 -46
- package/src/commands/clean.ts +28 -49
- package/src/commands/completions.ts +14 -0
- package/src/commands/coordinator.test.ts +131 -24
- package/src/commands/coordinator.ts +100 -63
- package/src/commands/costs.test.ts +2 -2
- package/src/commands/costs.ts +96 -75
- package/src/commands/dashboard.test.ts +2 -2
- package/src/commands/dashboard.ts +73 -93
- package/src/commands/doctor.test.ts +2 -2
- package/src/commands/doctor.ts +92 -79
- package/src/commands/errors.test.ts +2 -2
- package/src/commands/errors.ts +56 -50
- package/src/commands/feed.test.ts +2 -2
- package/src/commands/feed.ts +86 -83
- package/src/commands/group.ts +167 -177
- package/src/commands/hooks.test.ts +2 -2
- package/src/commands/hooks.ts +52 -42
- package/src/commands/init.test.ts +19 -19
- package/src/commands/init.ts +7 -16
- package/src/commands/inspect.test.ts +2 -2
- package/src/commands/inspect.ts +54 -57
- package/src/commands/log.test.ts +5 -10
- package/src/commands/log.ts +90 -84
- package/src/commands/logs.test.ts +1 -1
- package/src/commands/logs.ts +101 -104
- package/src/commands/mail.ts +157 -169
- package/src/commands/merge.test.ts +20 -58
- package/src/commands/merge.ts +13 -43
- package/src/commands/metrics.test.ts +2 -2
- package/src/commands/metrics.ts +33 -34
- package/src/commands/monitor.test.ts +3 -3
- package/src/commands/monitor.ts +56 -61
- package/src/commands/nudge.ts +41 -89
- package/src/commands/prime.test.ts +15 -47
- package/src/commands/prime.ts +7 -44
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +79 -86
- package/src/commands/run.ts +97 -77
- package/src/commands/sling.test.ts +196 -0
- package/src/commands/sling.ts +24 -54
- package/src/commands/spec.test.ts +13 -39
- package/src/commands/spec.ts +30 -99
- package/src/commands/status.ts +46 -42
- package/src/commands/stop.test.ts +21 -39
- package/src/commands/stop.ts +18 -33
- package/src/commands/supervisor.test.ts +3 -5
- package/src/commands/supervisor.ts +136 -157
- package/src/commands/trace.test.ts +9 -9
- package/src/commands/trace.ts +54 -77
- package/src/commands/watch.test.ts +2 -2
- package/src/commands/watch.ts +38 -45
- package/src/commands/worktree.test.ts +8 -8
- package/src/commands/worktree.ts +63 -46
- package/src/config.test.ts +96 -0
- package/src/doctor/databases.test.ts +22 -2
- package/src/doctor/databases.ts +16 -0
- package/src/doctor/dependencies.test.ts +55 -1
- package/src/doctor/dependencies.ts +113 -18
- package/src/e2e/init-sling-lifecycle.test.ts +6 -6
- package/src/index.ts +223 -213
- package/src/logging/color.test.ts +74 -91
- package/src/logging/color.ts +52 -46
- package/src/logging/reporter.test.ts +10 -10
- package/src/logging/reporter.ts +6 -5
- package/src/merge/queue.test.ts +66 -0
- package/src/merge/queue.ts +15 -0
- package/src/schema-consistency.test.ts +239 -0
- package/src/sessions/compat.ts +1 -1
- package/src/sessions/store.test.ts +37 -0
- package/src/sessions/store.ts +11 -0
- package/src/worktree/tmux.test.ts +98 -9
- package/src/worktree/tmux.ts +18 -0
package/src/commands/merge.ts
CHANGED
|
@@ -19,21 +19,12 @@ import { createMergeResolver } from "../merge/resolver.ts";
|
|
|
19
19
|
import { createMulchClient } from "../mulch/client.ts";
|
|
20
20
|
import type { MergeEntry, MergeResult } from "../types.ts";
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
return args[idx + 1];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Check if a boolean flag is present in the args. */
|
|
35
|
-
function hasFlag(args: string[], flag: string): boolean {
|
|
36
|
-
return args.includes(flag);
|
|
22
|
+
export interface MergeOptions {
|
|
23
|
+
branch?: string;
|
|
24
|
+
all?: boolean;
|
|
25
|
+
into?: string;
|
|
26
|
+
dryRun?: boolean;
|
|
27
|
+
json?: boolean;
|
|
37
28
|
}
|
|
38
29
|
|
|
39
30
|
/**
|
|
@@ -135,35 +126,14 @@ function formatDryRun(entry: MergeEntry): string {
|
|
|
135
126
|
/**
|
|
136
127
|
* Entry point for `overstory merge [flags]`.
|
|
137
128
|
*
|
|
138
|
-
*
|
|
139
|
-
* --branch <name> Merge a specific branch
|
|
140
|
-
* --all Merge all pending branches in the queue
|
|
141
|
-
* --dry-run Check for conflicts without actually merging
|
|
142
|
-
* --json Output results as JSON
|
|
129
|
+
* @param opts - Command options
|
|
143
130
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
--all Merge all pending branches in the queue
|
|
151
|
-
--into <branch> Target branch to merge into (default: config canonicalBranch)
|
|
152
|
-
--dry-run Check for conflicts without actually merging
|
|
153
|
-
--json Output results as JSON
|
|
154
|
-
--help, -h Show this help`;
|
|
155
|
-
|
|
156
|
-
export async function mergeCommand(args: string[]): Promise<void> {
|
|
157
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
158
|
-
process.stdout.write(`${MERGE_HELP}\n`);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const branchName = getFlag(args, "--branch");
|
|
163
|
-
const all = hasFlag(args, "--all");
|
|
164
|
-
const into = getFlag(args, "--into");
|
|
165
|
-
const dryRun = hasFlag(args, "--dry-run");
|
|
166
|
-
const json = hasFlag(args, "--json");
|
|
131
|
+
export async function mergeCommand(opts: MergeOptions): Promise<void> {
|
|
132
|
+
const branchName = opts.branch;
|
|
133
|
+
const all = opts.all ?? false;
|
|
134
|
+
const into = opts.into;
|
|
135
|
+
const dryRun = opts.dryRun ?? false;
|
|
136
|
+
const json = opts.json ?? false;
|
|
167
137
|
|
|
168
138
|
if (!branchName && !all) {
|
|
169
139
|
throw new ValidationError("Either --branch <name> or --all is required for overstory merge", {
|
|
@@ -77,7 +77,7 @@ describe("metricsCommand", () => {
|
|
|
77
77
|
await metricsCommand(["--help"]);
|
|
78
78
|
const out = output();
|
|
79
79
|
|
|
80
|
-
expect(out).toContain("
|
|
80
|
+
expect(out).toContain("metrics");
|
|
81
81
|
expect(out).toContain("--last <n>");
|
|
82
82
|
expect(out).toContain("--json");
|
|
83
83
|
expect(out).toContain("--help");
|
|
@@ -87,7 +87,7 @@ describe("metricsCommand", () => {
|
|
|
87
87
|
await metricsCommand(["-h"]);
|
|
88
88
|
const out = output();
|
|
89
89
|
|
|
90
|
-
expect(out).toContain("
|
|
90
|
+
expect(out).toContain("metrics");
|
|
91
91
|
expect(out).toContain("--last <n>");
|
|
92
92
|
});
|
|
93
93
|
|
package/src/commands/metrics.ts
CHANGED
|
@@ -6,22 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
+
import { Command } from "commander";
|
|
9
10
|
import { loadConfig } from "../config.ts";
|
|
10
11
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function getFlag(args: string[], flag: string): string | undefined {
|
|
16
|
-
const idx = args.indexOf(flag);
|
|
17
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
return args[idx + 1];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function hasFlag(args: string[], flag: string): boolean {
|
|
24
|
-
return args.includes(flag);
|
|
13
|
+
interface MetricsOpts {
|
|
14
|
+
last?: string;
|
|
15
|
+
json?: boolean;
|
|
25
16
|
}
|
|
26
17
|
|
|
27
18
|
/**
|
|
@@ -39,27 +30,9 @@ function formatDuration(ms: number): string {
|
|
|
39
30
|
return `${hours}h ${remainMin}m`;
|
|
40
31
|
}
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const METRICS_HELP = `overstory metrics — Show session metrics
|
|
46
|
-
|
|
47
|
-
Usage: overstory metrics [--last <n>] [--json]
|
|
48
|
-
|
|
49
|
-
Options:
|
|
50
|
-
--last <n> Number of recent sessions to show (default: 20)
|
|
51
|
-
--json Output as JSON
|
|
52
|
-
--help, -h Show this help`;
|
|
53
|
-
|
|
54
|
-
export async function metricsCommand(args: string[]): Promise<void> {
|
|
55
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
56
|
-
process.stdout.write(`${METRICS_HELP}\n`);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const lastStr = getFlag(args, "--last");
|
|
61
|
-
const limit = lastStr ? Number.parseInt(lastStr, 10) : 20;
|
|
62
|
-
const json = hasFlag(args, "--json");
|
|
33
|
+
async function executeMetrics(opts: MetricsOpts): Promise<void> {
|
|
34
|
+
const limit = opts.last ? Number.parseInt(opts.last, 10) : 20;
|
|
35
|
+
const json = opts.json ?? false;
|
|
63
36
|
|
|
64
37
|
const cwd = process.cwd();
|
|
65
38
|
const config = await loadConfig(cwd);
|
|
@@ -141,3 +114,29 @@ export async function metricsCommand(args: string[]): Promise<void> {
|
|
|
141
114
|
store.close();
|
|
142
115
|
}
|
|
143
116
|
}
|
|
117
|
+
|
|
118
|
+
export function createMetricsCommand(): Command {
|
|
119
|
+
return new Command("metrics")
|
|
120
|
+
.description("Show session metrics")
|
|
121
|
+
.option("--last <n>", "Number of recent sessions to show (default: 20)")
|
|
122
|
+
.option("--json", "Output as JSON")
|
|
123
|
+
.action(async (opts: MetricsOpts) => {
|
|
124
|
+
await executeMetrics(opts);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function metricsCommand(args: string[]): Promise<void> {
|
|
129
|
+
const cmd = createMetricsCommand();
|
|
130
|
+
cmd.exitOverride();
|
|
131
|
+
try {
|
|
132
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
133
|
+
} catch (err: unknown) {
|
|
134
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
135
|
+
const code = (err as { code: string }).code;
|
|
136
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -89,7 +89,7 @@ describe("monitorCommand", () => {
|
|
|
89
89
|
test("--help prints help text containing 'overstory monitor'", async () => {
|
|
90
90
|
await monitorCommand(["--help"]);
|
|
91
91
|
const output = stdoutWrites.join("");
|
|
92
|
-
expect(output).toContain("
|
|
92
|
+
expect(output).toContain("monitor");
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
test("--help prints help text containing 'start'", async () => {
|
|
@@ -113,13 +113,13 @@ describe("monitorCommand", () => {
|
|
|
113
113
|
test("-h prints help text", async () => {
|
|
114
114
|
await monitorCommand(["-h"]);
|
|
115
115
|
const output = stdoutWrites.join("");
|
|
116
|
-
expect(output).toContain("
|
|
116
|
+
expect(output).toContain("monitor");
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
test("empty args [] shows help (same as --help)", async () => {
|
|
120
120
|
await monitorCommand([]);
|
|
121
121
|
const output = stdoutWrites.join("");
|
|
122
|
-
expect(output).toContain("
|
|
122
|
+
expect(output).toContain("monitor");
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
test("unknown subcommand 'restart' throws ValidationError", async () => {
|
package/src/commands/monitor.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { mkdir } from "node:fs/promises";
|
|
17
17
|
import { join } from "node:path";
|
|
18
|
+
import { Command } from "commander";
|
|
18
19
|
import { deployHooks } from "../agents/hooks-deployer.ts";
|
|
19
20
|
import { createIdentity, loadIdentity } from "../agents/identity.ts";
|
|
20
21
|
import { createManifestLoader, resolveModel } from "../agents/manifest.ts";
|
|
@@ -50,15 +51,6 @@ export function buildMonitorBeacon(): string {
|
|
|
50
51
|
return parts.join(" — ");
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
-
* Determine whether to auto-attach to the tmux session after starting.
|
|
55
|
-
*/
|
|
56
|
-
function resolveAttach(args: string[], isTTY: boolean): boolean {
|
|
57
|
-
if (args.includes("--attach")) return true;
|
|
58
|
-
if (args.includes("--no-attach")) return false;
|
|
59
|
-
return isTTY;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
54
|
/**
|
|
63
55
|
* Start the monitor agent.
|
|
64
56
|
*
|
|
@@ -70,9 +62,8 @@ function resolveAttach(args: string[], isTTY: boolean): boolean {
|
|
|
70
62
|
* 6. Send startup beacon
|
|
71
63
|
* 7. Record session in SessionStore (sessions.db)
|
|
72
64
|
*/
|
|
73
|
-
async function startMonitor(
|
|
74
|
-
const json =
|
|
75
|
-
const shouldAttach = resolveAttach(args, !!process.stdout.isTTY);
|
|
65
|
+
async function startMonitor(opts: { json: boolean; attach: boolean }): Promise<void> {
|
|
66
|
+
const { json, attach: shouldAttach } = opts;
|
|
76
67
|
|
|
77
68
|
if (isRunningAsRoot()) {
|
|
78
69
|
throw new AgentError(
|
|
@@ -118,8 +109,6 @@ async function startMonitor(args: string[]): Promise<void> {
|
|
|
118
109
|
}
|
|
119
110
|
|
|
120
111
|
// Deploy monitor-specific hooks to the project root's .claude/ directory.
|
|
121
|
-
// The monitor gets the same structural enforcement as other non-implementation
|
|
122
|
-
// agents (Write/Edit/NotebookEdit blocked, dangerous bash commands blocked).
|
|
123
112
|
await deployHooks(projectRoot, MONITOR_NAME, "monitor");
|
|
124
113
|
|
|
125
114
|
// Create monitor identity if first run
|
|
@@ -146,7 +135,6 @@ async function startMonitor(args: string[]): Promise<void> {
|
|
|
146
135
|
const { model, env } = resolveModel(config, manifest, "monitor", "sonnet");
|
|
147
136
|
|
|
148
137
|
// Spawn tmux session at project root with Claude Code (interactive mode).
|
|
149
|
-
// Inject the monitor base definition via --append-system-prompt.
|
|
150
138
|
const agentDefPath = join(projectRoot, ".overstory", "agent-defs", "monitor.md");
|
|
151
139
|
const agentDefFile = Bun.file(agentDefPath);
|
|
152
140
|
let claudeCmd = `claude --model ${model} --dangerously-skip-permissions`;
|
|
@@ -226,8 +214,8 @@ async function startMonitor(args: string[]): Promise<void> {
|
|
|
226
214
|
* 2. Kill the tmux session (with process tree cleanup)
|
|
227
215
|
* 3. Mark session as completed in SessionStore
|
|
228
216
|
*/
|
|
229
|
-
async function stopMonitor(
|
|
230
|
-
const json =
|
|
217
|
+
async function stopMonitor(opts: { json: boolean }): Promise<void> {
|
|
218
|
+
const { json } = opts;
|
|
231
219
|
const cwd = process.cwd();
|
|
232
220
|
const config = await loadConfig(cwd);
|
|
233
221
|
const projectRoot = config.project.root;
|
|
@@ -273,8 +261,8 @@ async function stopMonitor(args: string[]): Promise<void> {
|
|
|
273
261
|
*
|
|
274
262
|
* Checks session registry and tmux liveness to report actual state.
|
|
275
263
|
*/
|
|
276
|
-
async function statusMonitor(
|
|
277
|
-
const json =
|
|
264
|
+
async function statusMonitor(opts: { json: boolean }): Promise<void> {
|
|
265
|
+
const { json } = opts;
|
|
278
266
|
const cwd = process.cwd();
|
|
279
267
|
const config = await loadConfig(cwd);
|
|
280
268
|
const projectRoot = config.project.root;
|
|
@@ -301,8 +289,6 @@ async function statusMonitor(args: string[]): Promise<void> {
|
|
|
301
289
|
const alive = await isSessionAlive(session.tmuxSession);
|
|
302
290
|
|
|
303
291
|
// Reconcile state: if session says active but tmux is dead, update.
|
|
304
|
-
// We already filtered out completed/zombie states above, so if tmux is dead
|
|
305
|
-
// this session needs to be marked as zombie.
|
|
306
292
|
if (!alive) {
|
|
307
293
|
store.updateState(MONITOR_NAME, "zombie");
|
|
308
294
|
store.updateLastActivity(MONITOR_NAME);
|
|
@@ -335,56 +321,65 @@ async function statusMonitor(args: string[]): Promise<void> {
|
|
|
335
321
|
}
|
|
336
322
|
}
|
|
337
323
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
324
|
+
export function createMonitorCommand(): Command {
|
|
325
|
+
const cmd = new Command("monitor").description("Manage the persistent Tier 2 monitor agent");
|
|
326
|
+
|
|
327
|
+
cmd
|
|
328
|
+
.command("start")
|
|
329
|
+
.description("Start the monitor (spawns Claude Code at project root)")
|
|
330
|
+
.option("--attach", "Always attach to tmux session after start")
|
|
331
|
+
.option("--no-attach", "Never attach to tmux session after start")
|
|
332
|
+
.option("--json", "Output as JSON")
|
|
333
|
+
.action(async (opts: { attach?: boolean; json?: boolean }) => {
|
|
334
|
+
// opts.attach = true if --attach, false if --no-attach, undefined if neither
|
|
335
|
+
const shouldAttach = opts.attach !== undefined ? opts.attach : !!process.stdout.isTTY;
|
|
336
|
+
await startMonitor({ json: opts.json ?? false, attach: shouldAttach });
|
|
337
|
+
});
|
|
346
338
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
339
|
+
cmd
|
|
340
|
+
.command("stop")
|
|
341
|
+
.description("Stop the monitor (kills tmux session)")
|
|
342
|
+
.option("--json", "Output as JSON")
|
|
343
|
+
.action(async (opts: { json?: boolean }) => {
|
|
344
|
+
await stopMonitor({ json: opts.json ?? false });
|
|
345
|
+
});
|
|
351
346
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
347
|
+
cmd
|
|
348
|
+
.command("status")
|
|
349
|
+
.description("Show monitor state")
|
|
350
|
+
.option("--json", "Output as JSON")
|
|
351
|
+
.action(async (opts: { json?: boolean }) => {
|
|
352
|
+
await statusMonitor({ json: opts.json ?? false });
|
|
353
|
+
});
|
|
355
354
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
- Sending progressive nudges to stalled agents
|
|
359
|
-
- Escalating unresponsive agents to the coordinator
|
|
360
|
-
- Producing periodic health summaries`;
|
|
355
|
+
return cmd;
|
|
356
|
+
}
|
|
361
357
|
|
|
362
358
|
/**
|
|
363
359
|
* Entry point for `overstory monitor <subcommand>`.
|
|
364
360
|
*/
|
|
365
361
|
export async function monitorCommand(args: string[]): Promise<void> {
|
|
366
|
-
|
|
367
|
-
|
|
362
|
+
const cmd = createMonitorCommand();
|
|
363
|
+
cmd.exitOverride();
|
|
364
|
+
|
|
365
|
+
if (args.length === 0) {
|
|
366
|
+
process.stdout.write(cmd.helpInformation());
|
|
368
367
|
return;
|
|
369
368
|
}
|
|
370
369
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
throw new ValidationError(
|
|
386
|
-
`Unknown monitor subcommand: ${subcommand}. Run 'overstory monitor --help' for usage.`,
|
|
387
|
-
{ field: "subcommand", value: subcommand },
|
|
388
|
-
);
|
|
370
|
+
try {
|
|
371
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
372
|
+
} catch (err: unknown) {
|
|
373
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
374
|
+
const code = (err as { code: string }).code;
|
|
375
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (code === "commander.unknownCommand") {
|
|
379
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
380
|
+
throw new ValidationError(message, { field: "subcommand" });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
throw err;
|
|
389
384
|
}
|
|
390
385
|
}
|
package/src/commands/nudge.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
-
import {
|
|
13
|
+
import { Command } from "commander";
|
|
14
|
+
import { AgentError } from "../errors.ts";
|
|
14
15
|
import { createEventStore } from "../events/store.ts";
|
|
15
16
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
16
17
|
import type { EventStore } from "../types.ts";
|
|
@@ -21,44 +22,6 @@ const MAX_RETRIES = 3;
|
|
|
21
22
|
const RETRY_DELAY_MS = 500;
|
|
22
23
|
const DEBOUNCE_MS = 500;
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
* Parse a named flag value from args.
|
|
26
|
-
*/
|
|
27
|
-
function getFlag(args: string[], flag: string): string | undefined {
|
|
28
|
-
const idx = args.indexOf(flag);
|
|
29
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
return args[idx + 1];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Boolean flags that do NOT consume the next arg. */
|
|
36
|
-
const BOOLEAN_FLAGS = new Set(["--json", "--force", "--help", "-h"]);
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Extract positional arguments, skipping flag-value pairs.
|
|
40
|
-
*/
|
|
41
|
-
function getPositionalArgs(args: string[]): string[] {
|
|
42
|
-
const positional: string[] = [];
|
|
43
|
-
let i = 0;
|
|
44
|
-
while (i < args.length) {
|
|
45
|
-
const arg = args[i];
|
|
46
|
-
if (arg?.startsWith("-")) {
|
|
47
|
-
if (BOOLEAN_FLAGS.has(arg)) {
|
|
48
|
-
i += 1;
|
|
49
|
-
} else {
|
|
50
|
-
i += 2;
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
if (arg !== undefined) {
|
|
54
|
-
positional.push(arg);
|
|
55
|
-
}
|
|
56
|
-
i += 1;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return positional;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
25
|
/**
|
|
63
26
|
* Load the orchestrator's registered tmux session name.
|
|
64
27
|
*
|
|
@@ -317,56 +280,45 @@ export async function nudgeAgent(
|
|
|
317
280
|
/**
|
|
318
281
|
* Entry point for `overstory nudge <agent-name> [message]`.
|
|
319
282
|
*/
|
|
320
|
-
const NUDGE_HELP = `overstory nudge — Send a text nudge to an agent
|
|
321
|
-
|
|
322
|
-
Usage: overstory nudge <agent-name> [message]
|
|
323
|
-
|
|
324
|
-
Arguments:
|
|
325
|
-
<agent-name> Name of the agent to nudge
|
|
326
|
-
[message] Text to send (default: "${DEFAULT_MESSAGE}")
|
|
327
|
-
|
|
328
|
-
Options:
|
|
329
|
-
--from <name> Sender name for the nudge prefix (default: orchestrator)
|
|
330
|
-
--force Skip debounce check
|
|
331
|
-
--json Output result as JSON
|
|
332
|
-
--help, -h Show this help`;
|
|
333
|
-
|
|
334
283
|
export async function nudgeCommand(args: string[]): Promise<void> {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
284
|
+
const program = new Command();
|
|
285
|
+
program
|
|
286
|
+
.name("overstory nudge")
|
|
287
|
+
.description("Send a text nudge to an agent")
|
|
288
|
+
.argument("<agent-name>", "Name of the agent to nudge")
|
|
289
|
+
.argument("[message...]", "Text to send (default: check mail prompt)")
|
|
290
|
+
.option("--from <name>", "Sender name", "orchestrator")
|
|
291
|
+
.option("--force", "Skip debounce check")
|
|
292
|
+
.option("--json", "Output result as JSON")
|
|
293
|
+
.exitOverride()
|
|
294
|
+
.action(
|
|
295
|
+
async (
|
|
296
|
+
agentName: string,
|
|
297
|
+
messageParts: string[],
|
|
298
|
+
opts: { from: string; force?: boolean; json?: boolean },
|
|
299
|
+
) => {
|
|
300
|
+
// Build the nudge message: prefix with sender, use custom or default text
|
|
301
|
+
const customMessage = messageParts.join(" ");
|
|
302
|
+
const rawMessage = customMessage.length > 0 ? customMessage : DEFAULT_MESSAGE;
|
|
303
|
+
const message = `[NUDGE from ${opts.from}] ${rawMessage}`;
|
|
304
|
+
|
|
305
|
+
// Resolve project root
|
|
306
|
+
const { resolveProjectRoot } = await import("../config.ts");
|
|
307
|
+
const projectRoot = await resolveProjectRoot(process.cwd());
|
|
308
|
+
|
|
309
|
+
const result = await nudgeAgent(projectRoot, agentName, message, opts.force ?? false);
|
|
310
|
+
|
|
311
|
+
if (opts.json) {
|
|
312
|
+
process.stdout.write(
|
|
313
|
+
`${JSON.stringify({ agentName, delivered: result.delivered, reason: result.reason })}\n`,
|
|
314
|
+
);
|
|
315
|
+
} else if (result.delivered) {
|
|
316
|
+
process.stdout.write(`📢 Nudged "${agentName}"\n`);
|
|
317
|
+
} else {
|
|
318
|
+
throw new AgentError(`Nudge failed: ${result.reason}`, { agentName });
|
|
319
|
+
}
|
|
320
|
+
},
|
|
366
321
|
);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
} else {
|
|
370
|
-
throw new AgentError(`Nudge failed: ${result.reason}`, { agentName });
|
|
371
|
-
}
|
|
322
|
+
|
|
323
|
+
await program.parseAsync(["node", "overstory-nudge", ...args]);
|
|
372
324
|
}
|