@os-eco/overstory-cli 0.6.1 → 0.6.5
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 +8 -7
- package/package.json +12 -4
- package/src/agents/checkpoint.test.ts +2 -2
- package/src/agents/hooks-deployer.test.ts +131 -16
- package/src/agents/hooks-deployer.ts +33 -1
- package/src/agents/identity.test.ts +27 -27
- package/src/agents/identity.ts +10 -10
- package/src/agents/lifecycle.test.ts +6 -6
- package/src/agents/lifecycle.ts +2 -2
- package/src/agents/manifest.test.ts +86 -0
- package/src/agents/overlay.test.ts +9 -9
- package/src/agents/overlay.ts +4 -4
- package/src/commands/agents.test.ts +8 -8
- package/src/commands/agents.ts +62 -91
- package/src/commands/clean.test.ts +36 -51
- package/src/commands/clean.ts +28 -49
- package/src/commands/completions.ts +14 -0
- package/src/commands/coordinator.test.ts +133 -26
- package/src/commands/coordinator.ts +101 -64
- package/src/commands/costs.test.ts +47 -47
- package/src/commands/costs.ts +96 -75
- package/src/commands/dashboard.test.ts +2 -2
- package/src/commands/dashboard.ts +75 -95
- 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 +18 -18
- package/src/commands/inspect.ts +55 -58
- package/src/commands/log.test.ts +26 -31
- package/src/commands/log.ts +97 -91
- package/src/commands/logs.test.ts +1 -1
- package/src/commands/logs.ts +101 -104
- package/src/commands/mail.test.ts +5 -5
- package/src/commands/mail.ts +157 -169
- package/src/commands/merge.test.ts +28 -66
- package/src/commands/merge.ts +21 -51
- package/src/commands/metrics.test.ts +8 -8
- package/src/commands/metrics.ts +34 -35
- package/src/commands/monitor.test.ts +3 -3
- package/src/commands/monitor.ts +57 -62
- package/src/commands/nudge.test.ts +1 -1
- package/src/commands/nudge.ts +41 -89
- package/src/commands/prime.test.ts +19 -51
- package/src/commands/prime.ts +13 -50
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +79 -86
- package/src/commands/run.test.ts +1 -1
- package/src/commands/run.ts +97 -77
- package/src/commands/sling.test.ts +201 -5
- package/src/commands/sling.ts +37 -64
- package/src/commands/spec.test.ts +14 -40
- package/src/commands/spec.ts +32 -101
- package/src/commands/status.test.ts +97 -1
- package/src/commands/status.ts +63 -58
- package/src/commands/stop.test.ts +22 -40
- package/src/commands/stop.ts +18 -33
- package/src/commands/supervisor.test.ts +12 -14
- package/src/commands/supervisor.ts +144 -165
- package/src/commands/trace.test.ts +15 -15
- package/src/commands/trace.ts +59 -82
- package/src/commands/watch.test.ts +2 -2
- package/src/commands/watch.ts +38 -45
- package/src/commands/worktree.test.ts +213 -37
- package/src/commands/worktree.ts +110 -55
- package/src/config.test.ts +96 -0
- package/src/doctor/consistency.test.ts +14 -14
- 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/doctor/merge-queue.test.ts +4 -4
- package/src/e2e/init-sling-lifecycle.test.ts +8 -8
- package/src/errors.ts +1 -1
- 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/mail/broadcast.test.ts +1 -1
- package/src/mail/client.test.ts +6 -6
- package/src/mail/store.test.ts +3 -3
- package/src/merge/queue.test.ts +73 -7
- package/src/merge/queue.ts +17 -2
- package/src/merge/resolver.test.ts +159 -7
- package/src/merge/resolver.ts +46 -2
- package/src/metrics/store.test.ts +44 -44
- package/src/metrics/store.ts +2 -2
- package/src/metrics/summary.test.ts +35 -35
- package/src/mulch/client.test.ts +1 -1
- package/src/schema-consistency.test.ts +239 -0
- package/src/sessions/compat.test.ts +3 -3
- package/src/sessions/compat.ts +2 -2
- package/src/sessions/store.test.ts +41 -4
- package/src/sessions/store.ts +13 -2
- package/src/types.ts +14 -14
- package/src/watchdog/daemon.test.ts +10 -10
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -1
- package/src/worktree/manager.test.ts +20 -20
- package/src/worktree/manager.ts +120 -4
- package/src/worktree/tmux.test.ts +98 -9
- package/src/worktree/tmux.ts +18 -0
package/src/commands/mail.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI command: overstory mail send/check/list/read/reply
|
|
3
3
|
*
|
|
4
|
-
* Parses CLI args and delegates to the mail client.
|
|
4
|
+
* Parses CLI args via Commander.js and delegates to the mail client.
|
|
5
5
|
* Supports --inject for hook context injection, --json for machine output,
|
|
6
6
|
* and various filters for listing messages.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
+
import { Command } from "commander";
|
|
10
11
|
import { resolveProjectRoot } from "../config.ts";
|
|
11
|
-
import {
|
|
12
|
+
import { ValidationError } from "../errors.ts";
|
|
12
13
|
import { createEventStore } from "../events/store.ts";
|
|
13
14
|
import { isGroupAddress, resolveGroupAddress } from "../mail/broadcast.ts";
|
|
14
15
|
import { createMailClient } from "../mail/client.ts";
|
|
@@ -29,54 +30,6 @@ const AUTO_NUDGE_TYPES: ReadonlySet<MailMessageType> = new Set([
|
|
|
29
30
|
"merge_failed",
|
|
30
31
|
]);
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
-
* Parse a named flag value from an args array.
|
|
34
|
-
* Returns the value after the flag, or undefined if not present.
|
|
35
|
-
*/
|
|
36
|
-
function getFlag(args: string[], flag: string): string | undefined {
|
|
37
|
-
const idx = args.indexOf(flag);
|
|
38
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
return args[idx + 1];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Check if a boolean flag is present in the args. */
|
|
45
|
-
function hasFlag(args: string[], flag: string): boolean {
|
|
46
|
-
return args.includes(flag);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Boolean flags that do NOT consume the next arg as a value. */
|
|
50
|
-
const BOOLEAN_FLAGS = new Set(["--json", "--inject", "--unread", "--all", "--help", "-h"]);
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Extract positional arguments from an args array, skipping flag-value pairs.
|
|
54
|
-
*
|
|
55
|
-
* Iterates through args, skipping `--flag value` pairs for value-bearing flags
|
|
56
|
-
* and lone boolean flags. Everything else is a positional arg.
|
|
57
|
-
*/
|
|
58
|
-
function getPositionalArgs(args: string[]): string[] {
|
|
59
|
-
const positional: string[] = [];
|
|
60
|
-
let i = 0;
|
|
61
|
-
while (i < args.length) {
|
|
62
|
-
const arg = args[i];
|
|
63
|
-
if (arg?.startsWith("-")) {
|
|
64
|
-
// It's a flag. If it's boolean, skip just it; otherwise skip it + its value.
|
|
65
|
-
if (BOOLEAN_FLAGS.has(arg)) {
|
|
66
|
-
i += 1;
|
|
67
|
-
} else {
|
|
68
|
-
i += 2; // skip flag + its value
|
|
69
|
-
}
|
|
70
|
-
} else {
|
|
71
|
-
if (arg !== undefined) {
|
|
72
|
-
positional.push(arg);
|
|
73
|
-
}
|
|
74
|
-
i += 1;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return positional;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
33
|
/** Format a single message for human-readable output. */
|
|
81
34
|
function formatMessage(msg: MailMessage): string {
|
|
82
35
|
const readMarker = msg.read ? " " : "*";
|
|
@@ -255,17 +208,58 @@ function openClient(cwd: string) {
|
|
|
255
208
|
return client;
|
|
256
209
|
}
|
|
257
210
|
|
|
211
|
+
// === Typed option interfaces for each subcommand ===
|
|
212
|
+
|
|
213
|
+
interface SendOpts {
|
|
214
|
+
to: string;
|
|
215
|
+
subject: string;
|
|
216
|
+
body: string;
|
|
217
|
+
from?: string;
|
|
218
|
+
agent?: string;
|
|
219
|
+
type?: string;
|
|
220
|
+
priority?: string;
|
|
221
|
+
payload?: string;
|
|
222
|
+
json?: boolean;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface CheckOpts {
|
|
226
|
+
agent?: string;
|
|
227
|
+
inject?: boolean;
|
|
228
|
+
json?: boolean;
|
|
229
|
+
debounce?: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
interface ListOpts {
|
|
233
|
+
from?: string;
|
|
234
|
+
to?: string;
|
|
235
|
+
agent?: string;
|
|
236
|
+
unread?: boolean;
|
|
237
|
+
json?: boolean;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
interface ReplyOpts {
|
|
241
|
+
body: string;
|
|
242
|
+
from?: string;
|
|
243
|
+
agent?: string;
|
|
244
|
+
json?: boolean;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
interface PurgeOpts {
|
|
248
|
+
all?: boolean;
|
|
249
|
+
days?: string;
|
|
250
|
+
agent?: string;
|
|
251
|
+
json?: boolean;
|
|
252
|
+
}
|
|
253
|
+
|
|
258
254
|
/** overstory mail send */
|
|
259
|
-
async function handleSend(
|
|
260
|
-
const to =
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
const from = getFlag(args, "--agent") ?? getFlag(args, "--from") ?? "orchestrator";
|
|
264
|
-
const rawPayload = getFlag(args, "--payload");
|
|
255
|
+
async function handleSend(opts: SendOpts, cwd: string): Promise<void> {
|
|
256
|
+
const { to, subject, body } = opts;
|
|
257
|
+
const from = opts.agent ?? opts.from ?? "orchestrator";
|
|
258
|
+
const rawPayload = opts.payload;
|
|
265
259
|
const VALID_PRIORITIES = ["low", "normal", "high", "urgent"] as const;
|
|
266
260
|
|
|
267
|
-
const rawType =
|
|
268
|
-
const rawPriority =
|
|
261
|
+
const rawType = opts.type ?? "status";
|
|
262
|
+
const rawPriority = opts.priority ?? "normal";
|
|
269
263
|
|
|
270
264
|
if (!MAIL_MESSAGE_TYPES.includes(rawType as MailMessage["type"])) {
|
|
271
265
|
throw new ValidationError(
|
|
@@ -297,16 +291,6 @@ async function handleSend(args: string[], cwd: string): Promise<void> {
|
|
|
297
291
|
}
|
|
298
292
|
}
|
|
299
293
|
|
|
300
|
-
if (!to) {
|
|
301
|
-
throw new ValidationError("--to is required for mail send", { field: "to" });
|
|
302
|
-
}
|
|
303
|
-
if (!subject) {
|
|
304
|
-
throw new ValidationError("--subject is required for mail send", { field: "subject" });
|
|
305
|
-
}
|
|
306
|
-
if (!body) {
|
|
307
|
-
throw new ValidationError("--body is required for mail send", { field: "body" });
|
|
308
|
-
}
|
|
309
|
-
|
|
310
294
|
// Handle broadcast messages (group addresses)
|
|
311
295
|
if (isGroupAddress(to)) {
|
|
312
296
|
const overstoryDir = join(cwd, ".overstory");
|
|
@@ -383,7 +367,7 @@ async function handleSend(args: string[], cwd: string): Promise<void> {
|
|
|
383
367
|
}
|
|
384
368
|
|
|
385
369
|
// Output broadcast summary
|
|
386
|
-
if (
|
|
370
|
+
if (opts.json) {
|
|
387
371
|
process.stdout.write(
|
|
388
372
|
`${JSON.stringify({ messageIds, recipientCount: recipients.length })}\n`,
|
|
389
373
|
);
|
|
@@ -442,7 +426,7 @@ async function handleSend(args: string[], cwd: string): Promise<void> {
|
|
|
442
426
|
// Event recording failure is non-fatal
|
|
443
427
|
}
|
|
444
428
|
|
|
445
|
-
if (
|
|
429
|
+
if (opts.json) {
|
|
446
430
|
process.stdout.write(`${JSON.stringify({ id })}\n`);
|
|
447
431
|
} else {
|
|
448
432
|
process.stdout.write(`✉️ Sent message ${id} to ${to}\n`);
|
|
@@ -463,7 +447,7 @@ async function handleSend(args: string[], cwd: string): Promise<void> {
|
|
|
463
447
|
subject,
|
|
464
448
|
messageId: id,
|
|
465
449
|
});
|
|
466
|
-
if (!
|
|
450
|
+
if (!opts.json) {
|
|
467
451
|
process.stdout.write(
|
|
468
452
|
`📢 Queued nudge for "${to}" (${nudgeReason}, delivered on next prompt)\n`,
|
|
469
453
|
);
|
|
@@ -507,11 +491,11 @@ async function handleSend(args: string[], cwd: string): Promise<void> {
|
|
|
507
491
|
}
|
|
508
492
|
|
|
509
493
|
/** overstory mail check */
|
|
510
|
-
async function handleCheck(
|
|
511
|
-
const agent =
|
|
512
|
-
const inject =
|
|
513
|
-
const json =
|
|
514
|
-
const debounceFlag =
|
|
494
|
+
async function handleCheck(opts: CheckOpts, cwd: string): Promise<void> {
|
|
495
|
+
const agent = opts.agent ?? "orchestrator";
|
|
496
|
+
const inject = opts.inject ?? false;
|
|
497
|
+
const json = opts.json ?? false;
|
|
498
|
+
const debounceFlag = opts.debounce;
|
|
515
499
|
|
|
516
500
|
// Parse debounce interval if provided
|
|
517
501
|
let debounceMs: number | undefined;
|
|
@@ -578,12 +562,12 @@ async function handleCheck(args: string[], cwd: string): Promise<void> {
|
|
|
578
562
|
}
|
|
579
563
|
|
|
580
564
|
/** overstory mail list */
|
|
581
|
-
function handleList(
|
|
582
|
-
const from =
|
|
583
|
-
// --agent is an alias for
|
|
584
|
-
const to =
|
|
585
|
-
const unread =
|
|
586
|
-
const json =
|
|
565
|
+
function handleList(opts: ListOpts, cwd: string): void {
|
|
566
|
+
const from = opts.from;
|
|
567
|
+
// --to takes precedence over --agent (agent is an alias for recipient filtering)
|
|
568
|
+
const to = opts.to ?? opts.agent;
|
|
569
|
+
const unread = opts.unread ? true : undefined;
|
|
570
|
+
const json = opts.json ?? false;
|
|
587
571
|
|
|
588
572
|
const client = openClient(cwd);
|
|
589
573
|
try {
|
|
@@ -607,13 +591,7 @@ function handleList(args: string[], cwd: string): void {
|
|
|
607
591
|
}
|
|
608
592
|
|
|
609
593
|
/** overstory mail read */
|
|
610
|
-
function handleRead(
|
|
611
|
-
const positional = getPositionalArgs(args);
|
|
612
|
-
const id = positional[0];
|
|
613
|
-
if (!id) {
|
|
614
|
-
throw new ValidationError("Message ID is required for mail read", { field: "id" });
|
|
615
|
-
}
|
|
616
|
-
|
|
594
|
+
function handleRead(id: string, cwd: string): void {
|
|
617
595
|
const client = openClient(cwd);
|
|
618
596
|
try {
|
|
619
597
|
const { alreadyRead } = client.markRead(id);
|
|
@@ -628,24 +606,15 @@ function handleRead(args: string[], cwd: string): void {
|
|
|
628
606
|
}
|
|
629
607
|
|
|
630
608
|
/** overstory mail reply */
|
|
631
|
-
function handleReply(
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
const body = getFlag(args, "--body");
|
|
635
|
-
const from = getFlag(args, "--agent") ?? getFlag(args, "--from") ?? "orchestrator";
|
|
636
|
-
|
|
637
|
-
if (!id) {
|
|
638
|
-
throw new ValidationError("Message ID is required for mail reply", { field: "id" });
|
|
639
|
-
}
|
|
640
|
-
if (!body) {
|
|
641
|
-
throw new ValidationError("--body is required for mail reply", { field: "body" });
|
|
642
|
-
}
|
|
609
|
+
function handleReply(id: string, opts: ReplyOpts, cwd: string): void {
|
|
610
|
+
const body = opts.body;
|
|
611
|
+
const from = opts.agent ?? opts.from ?? "orchestrator";
|
|
643
612
|
|
|
644
613
|
const client = openClient(cwd);
|
|
645
614
|
try {
|
|
646
615
|
const replyId = client.reply(id, body, from);
|
|
647
616
|
|
|
648
|
-
if (
|
|
617
|
+
if (opts.json) {
|
|
649
618
|
process.stdout.write(`${JSON.stringify({ id: replyId })}\n`);
|
|
650
619
|
} else {
|
|
651
620
|
process.stdout.write(`✉️ Reply sent: ${replyId}\n`);
|
|
@@ -656,11 +625,11 @@ function handleReply(args: string[], cwd: string): void {
|
|
|
656
625
|
}
|
|
657
626
|
|
|
658
627
|
/** overstory mail purge */
|
|
659
|
-
function handlePurge(
|
|
660
|
-
const all =
|
|
661
|
-
const daysStr =
|
|
662
|
-
const agent =
|
|
663
|
-
const json =
|
|
628
|
+
function handlePurge(opts: PurgeOpts, cwd: string): void {
|
|
629
|
+
const all = opts.all ?? false;
|
|
630
|
+
const daysStr = opts.days;
|
|
631
|
+
const agent = opts.agent;
|
|
632
|
+
const json = opts.json ?? false;
|
|
664
633
|
|
|
665
634
|
if (!all && daysStr === undefined && agent === undefined) {
|
|
666
635
|
throw new ValidationError(
|
|
@@ -699,73 +668,92 @@ function handlePurge(args: string[], cwd: string): void {
|
|
|
699
668
|
* Entry point for `overstory mail <subcommand> [args...]`.
|
|
700
669
|
*
|
|
701
670
|
* Subcommands: send, check, list, read, reply, purge.
|
|
671
|
+
* Uses Commander.js for subcommand routing and option parsing.
|
|
702
672
|
*/
|
|
703
|
-
const MAIL_HELP = `overstory mail — Agent messaging system
|
|
704
|
-
|
|
705
|
-
Usage: overstory mail <subcommand> [args...]
|
|
706
|
-
|
|
707
|
-
Subcommands:
|
|
708
|
-
send Send a message
|
|
709
|
-
--to <agent> --subject <text> --body <text>
|
|
710
|
-
[--from <name>] [--agent <name> (alias for --from)]
|
|
711
|
-
[--type <type>] [--priority <low|normal|high|urgent>]
|
|
712
|
-
[--payload <json>] [--json]
|
|
713
|
-
Types: status, question, result, error (semantic)
|
|
714
|
-
worker_done, merge_ready, merged, merge_failed,
|
|
715
|
-
escalation, health_check, dispatch, assign (protocol)
|
|
716
|
-
check Check inbox (unread messages)
|
|
717
|
-
[--agent <name>] [--inject] [--json]
|
|
718
|
-
list List messages with filters
|
|
719
|
-
[--from <name>] [--to <name>] [--agent <name> (alias for --to)]
|
|
720
|
-
[--unread] [--json]
|
|
721
|
-
read Mark a message as read
|
|
722
|
-
<message-id>
|
|
723
|
-
reply Reply to a message
|
|
724
|
-
<message-id> --body <text> [--from <name>]
|
|
725
|
-
[--agent <name> (alias for --from)] [--json]
|
|
726
|
-
purge Delete old messages
|
|
727
|
-
--all | --days <n> | --agent <name>
|
|
728
|
-
[--json]
|
|
729
|
-
|
|
730
|
-
Options:
|
|
731
|
-
--help, -h Show this help`;
|
|
732
|
-
|
|
733
673
|
export async function mailCommand(args: string[]): Promise<void> {
|
|
734
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
735
|
-
process.stdout.write(`${MAIL_HELP}\n`);
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
const subcommand = args[0];
|
|
740
|
-
const subArgs = args.slice(1);
|
|
741
|
-
|
|
742
674
|
// Resolve the actual project root (handles git worktrees).
|
|
743
675
|
// Mail commands may run from agent worktrees via hooks, so we must
|
|
744
676
|
// resolve up to the main project root where .overstory/mail.db lives.
|
|
745
677
|
const root = await resolveProjectRoot(process.cwd());
|
|
746
678
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
679
|
+
const program = new Command();
|
|
680
|
+
program.name("overstory mail").description("Agent messaging system").exitOverride();
|
|
681
|
+
|
|
682
|
+
program
|
|
683
|
+
.command("send")
|
|
684
|
+
.description("Send a message")
|
|
685
|
+
.requiredOption("--to <agent>", "Recipient agent name")
|
|
686
|
+
.requiredOption("--subject <text>", "Message subject")
|
|
687
|
+
.requiredOption("--body <text>", "Message body")
|
|
688
|
+
.option("--from <name>", "Sender name")
|
|
689
|
+
.option("--agent <name>", "Alias for --from")
|
|
690
|
+
.option("--type <type>", "Message type", "status")
|
|
691
|
+
.option("--priority <level>", "Priority level", "normal")
|
|
692
|
+
.option("--payload <json>", "Structured JSON payload")
|
|
693
|
+
.option("--json", "Output as JSON")
|
|
694
|
+
.exitOverride()
|
|
695
|
+
.action(async (opts: SendOpts) => {
|
|
696
|
+
await handleSend(opts, root);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
program
|
|
700
|
+
.command("check")
|
|
701
|
+
.description("Check inbox (unread messages)")
|
|
702
|
+
.option("--agent <name>", "Agent name")
|
|
703
|
+
.option("--inject", "Inject format for hook context")
|
|
704
|
+
.option("--json", "Output as JSON")
|
|
705
|
+
.option("--debounce <ms>", "Debounce interval in milliseconds")
|
|
706
|
+
.exitOverride()
|
|
707
|
+
.action(async (opts: CheckOpts) => {
|
|
708
|
+
await handleCheck(opts, root);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
program
|
|
712
|
+
.command("list")
|
|
713
|
+
.description("List messages with filters")
|
|
714
|
+
.option("--from <name>", "Filter by sender")
|
|
715
|
+
.option("--to <name>", "Filter by recipient")
|
|
716
|
+
.option("--agent <name>", "Alias for --to (filter by recipient)")
|
|
717
|
+
.option("--unread", "Show only unread messages")
|
|
718
|
+
.option("--json", "Output as JSON")
|
|
719
|
+
.exitOverride()
|
|
720
|
+
.action((opts: ListOpts) => {
|
|
721
|
+
handleList(opts, root);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
program
|
|
725
|
+
.command("read")
|
|
726
|
+
.description("Mark a message as read")
|
|
727
|
+
.argument("<message-id>", "Message ID")
|
|
728
|
+
.exitOverride()
|
|
729
|
+
.action((id: string) => {
|
|
730
|
+
handleRead(id, root);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
program
|
|
734
|
+
.command("reply")
|
|
735
|
+
.description("Reply to a message")
|
|
736
|
+
.argument("<message-id>", "Message ID to reply to")
|
|
737
|
+
.requiredOption("--body <text>", "Reply body")
|
|
738
|
+
.option("--from <name>", "Sender name")
|
|
739
|
+
.option("--agent <name>", "Alias for --from")
|
|
740
|
+
.option("--json", "Output as JSON")
|
|
741
|
+
.exitOverride()
|
|
742
|
+
.action((id: string, opts: ReplyOpts) => {
|
|
743
|
+
handleReply(id, opts, root);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
program
|
|
747
|
+
.command("purge")
|
|
748
|
+
.description("Delete old messages")
|
|
749
|
+
.option("--all", "Purge all messages")
|
|
750
|
+
.option("--days <n>", "Purge messages older than N days")
|
|
751
|
+
.option("--agent <name>", "Purge messages for specific agent")
|
|
752
|
+
.option("--json", "Output as JSON")
|
|
753
|
+
.exitOverride()
|
|
754
|
+
.action((opts: PurgeOpts) => {
|
|
755
|
+
handlePurge(opts, root);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
await program.parseAsync(["node", "overstory-mail", ...args]);
|
|
771
759
|
}
|
|
@@ -63,50 +63,12 @@ merge:
|
|
|
63
63
|
await runGitInDir(dir, ["checkout", defaultBranch]);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
describe("
|
|
67
|
-
test("
|
|
68
|
-
let output = "";
|
|
69
|
-
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
70
|
-
process.stdout.write = (chunk: unknown): boolean => {
|
|
71
|
-
output += String(chunk);
|
|
72
|
-
return true;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
await mergeCommand(["--help"]);
|
|
77
|
-
} finally {
|
|
78
|
-
process.stdout.write = originalWrite;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
expect(output).toContain("overstory merge");
|
|
82
|
-
expect(output).toContain("--branch");
|
|
83
|
-
expect(output).toContain("--all");
|
|
84
|
-
expect(output).toContain("--dry-run");
|
|
85
|
-
expect(output).toContain("--into");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("-h prints help", async () => {
|
|
89
|
-
let output = "";
|
|
90
|
-
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
91
|
-
process.stdout.write = (chunk: unknown): boolean => {
|
|
92
|
-
output += String(chunk);
|
|
93
|
-
return true;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
await mergeCommand(["-h"]);
|
|
98
|
-
} finally {
|
|
99
|
-
process.stdout.write = originalWrite;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
expect(output).toContain("overstory merge");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("no flags throws ValidationError mentioning '--branch' and '--all'", async () => {
|
|
66
|
+
describe("validation", () => {
|
|
67
|
+
test("no branch/all throws ValidationError mentioning '--branch' and '--all'", async () => {
|
|
106
68
|
await setupProject(repoDir, defaultBranch);
|
|
107
69
|
|
|
108
70
|
try {
|
|
109
|
-
await mergeCommand(
|
|
71
|
+
await mergeCommand({});
|
|
110
72
|
expect(true).toBe(false); // Should not reach here
|
|
111
73
|
} catch (err: unknown) {
|
|
112
74
|
expect(err).toBeInstanceOf(ValidationError);
|
|
@@ -122,7 +84,7 @@ merge:
|
|
|
122
84
|
await setupProject(repoDir, defaultBranch);
|
|
123
85
|
|
|
124
86
|
try {
|
|
125
|
-
await mergeCommand(
|
|
87
|
+
await mergeCommand({ branch: "nonexistent-branch" });
|
|
126
88
|
expect(true).toBe(false); // Should not reach here
|
|
127
89
|
} catch (err: unknown) {
|
|
128
90
|
expect(err).toBeInstanceOf(ValidationError);
|
|
@@ -144,7 +106,7 @@ merge:
|
|
|
144
106
|
};
|
|
145
107
|
|
|
146
108
|
try {
|
|
147
|
-
await mergeCommand(
|
|
109
|
+
await mergeCommand({ branch: branchName, dryRun: true });
|
|
148
110
|
} finally {
|
|
149
111
|
process.stdout.write = originalWrite;
|
|
150
112
|
}
|
|
@@ -170,7 +132,7 @@ merge:
|
|
|
170
132
|
};
|
|
171
133
|
|
|
172
134
|
try {
|
|
173
|
-
await mergeCommand(
|
|
135
|
+
await mergeCommand({ branch: branchName, dryRun: true, json: true });
|
|
174
136
|
} finally {
|
|
175
137
|
process.stdout.write = originalWrite;
|
|
176
138
|
}
|
|
@@ -191,7 +153,7 @@ merge:
|
|
|
191
153
|
};
|
|
192
154
|
|
|
193
155
|
try {
|
|
194
|
-
await mergeCommand(
|
|
156
|
+
await mergeCommand({ branch: branchName });
|
|
195
157
|
} finally {
|
|
196
158
|
process.stdout.write = originalWrite;
|
|
197
159
|
}
|
|
@@ -215,7 +177,7 @@ merge:
|
|
|
215
177
|
};
|
|
216
178
|
|
|
217
179
|
try {
|
|
218
|
-
await mergeCommand(
|
|
180
|
+
await mergeCommand({ branch: branchName, json: true });
|
|
219
181
|
} finally {
|
|
220
182
|
process.stdout.write = originalWrite;
|
|
221
183
|
}
|
|
@@ -238,14 +200,14 @@ merge:
|
|
|
238
200
|
};
|
|
239
201
|
|
|
240
202
|
try {
|
|
241
|
-
await mergeCommand(
|
|
203
|
+
await mergeCommand({ branch: branchName, dryRun: true, json: true });
|
|
242
204
|
} finally {
|
|
243
205
|
process.stdout.write = originalWrite;
|
|
244
206
|
}
|
|
245
207
|
|
|
246
208
|
const parsed = JSON.parse(output);
|
|
247
209
|
expect(parsed.agentName).toBe("my-builder");
|
|
248
|
-
expect(parsed.
|
|
210
|
+
expect(parsed.taskId).toBe("bead-xyz");
|
|
249
211
|
});
|
|
250
212
|
});
|
|
251
213
|
|
|
@@ -261,7 +223,7 @@ merge:
|
|
|
261
223
|
};
|
|
262
224
|
|
|
263
225
|
try {
|
|
264
|
-
await mergeCommand(
|
|
226
|
+
await mergeCommand({ all: true });
|
|
265
227
|
} finally {
|
|
266
228
|
process.stdout.write = originalWrite;
|
|
267
229
|
}
|
|
@@ -280,7 +242,7 @@ merge:
|
|
|
280
242
|
};
|
|
281
243
|
|
|
282
244
|
try {
|
|
283
|
-
await mergeCommand(
|
|
245
|
+
await mergeCommand({ all: true, json: true });
|
|
284
246
|
} finally {
|
|
285
247
|
process.stdout.write = originalWrite;
|
|
286
248
|
}
|
|
@@ -302,13 +264,13 @@ merge:
|
|
|
302
264
|
const queue = createMergeQueue(queuePath);
|
|
303
265
|
queue.enqueue({
|
|
304
266
|
branchName: branch1,
|
|
305
|
-
|
|
267
|
+
taskId: "bead-001",
|
|
306
268
|
agentName: "agent1",
|
|
307
269
|
filesModified: [`src/${branch1}.ts`],
|
|
308
270
|
});
|
|
309
271
|
queue.enqueue({
|
|
310
272
|
branchName: branch2,
|
|
311
|
-
|
|
273
|
+
taskId: "bead-002",
|
|
312
274
|
agentName: "agent2",
|
|
313
275
|
filesModified: [`src/${branch2}.ts`],
|
|
314
276
|
});
|
|
@@ -322,7 +284,7 @@ merge:
|
|
|
322
284
|
};
|
|
323
285
|
|
|
324
286
|
try {
|
|
325
|
-
await mergeCommand(
|
|
287
|
+
await mergeCommand({ all: true, dryRun: true });
|
|
326
288
|
} finally {
|
|
327
289
|
process.stdout.write = originalWrite;
|
|
328
290
|
}
|
|
@@ -344,13 +306,13 @@ merge:
|
|
|
344
306
|
const queue = createMergeQueue(queuePath);
|
|
345
307
|
queue.enqueue({
|
|
346
308
|
branchName: branch1,
|
|
347
|
-
|
|
309
|
+
taskId: "bead-100",
|
|
348
310
|
agentName: "builder1",
|
|
349
311
|
filesModified: [`src/${branch1}.ts`],
|
|
350
312
|
});
|
|
351
313
|
queue.enqueue({
|
|
352
314
|
branchName: branch2,
|
|
353
|
-
|
|
315
|
+
taskId: "bead-200",
|
|
354
316
|
agentName: "builder2",
|
|
355
317
|
filesModified: [`src/${branch2}.ts`],
|
|
356
318
|
});
|
|
@@ -364,7 +326,7 @@ merge:
|
|
|
364
326
|
};
|
|
365
327
|
|
|
366
328
|
try {
|
|
367
|
-
await mergeCommand(
|
|
329
|
+
await mergeCommand({ all: true });
|
|
368
330
|
} finally {
|
|
369
331
|
process.stdout.write = originalWrite;
|
|
370
332
|
}
|
|
@@ -389,7 +351,7 @@ merge:
|
|
|
389
351
|
const queue = createMergeQueue(queuePath);
|
|
390
352
|
queue.enqueue({
|
|
391
353
|
branchName: branch1,
|
|
392
|
-
|
|
354
|
+
taskId: "bead-300",
|
|
393
355
|
agentName: "builder3",
|
|
394
356
|
filesModified: [`src/${branch1}.ts`],
|
|
395
357
|
});
|
|
@@ -403,7 +365,7 @@ merge:
|
|
|
403
365
|
};
|
|
404
366
|
|
|
405
367
|
try {
|
|
406
|
-
await mergeCommand(
|
|
368
|
+
await mergeCommand({ all: true, json: true });
|
|
407
369
|
} finally {
|
|
408
370
|
process.stdout.write = originalWrite;
|
|
409
371
|
}
|
|
@@ -439,7 +401,7 @@ merge:
|
|
|
439
401
|
};
|
|
440
402
|
|
|
441
403
|
try {
|
|
442
|
-
await mergeCommand(
|
|
404
|
+
await mergeCommand({ branch: branchName, into: "develop", json: true });
|
|
443
405
|
} finally {
|
|
444
406
|
process.stdout.write = originalWrite;
|
|
445
407
|
}
|
|
@@ -487,13 +449,13 @@ merge:
|
|
|
487
449
|
const queue = createMergeQueue(queuePath);
|
|
488
450
|
queue.enqueue({
|
|
489
451
|
branchName: branch1,
|
|
490
|
-
|
|
452
|
+
taskId: "bead-into-all-1",
|
|
491
453
|
agentName: "agent1",
|
|
492
454
|
filesModified: [`src/${branch1}.ts`],
|
|
493
455
|
});
|
|
494
456
|
queue.enqueue({
|
|
495
457
|
branchName: branch2,
|
|
496
|
-
|
|
458
|
+
taskId: "bead-into-all-2",
|
|
497
459
|
agentName: "agent2",
|
|
498
460
|
filesModified: [`src/${branch2}.ts`],
|
|
499
461
|
});
|
|
@@ -507,7 +469,7 @@ merge:
|
|
|
507
469
|
};
|
|
508
470
|
|
|
509
471
|
try {
|
|
510
|
-
await mergeCommand(
|
|
472
|
+
await mergeCommand({ all: true, into: "staging", json: true });
|
|
511
473
|
} finally {
|
|
512
474
|
process.stdout.write = originalWrite;
|
|
513
475
|
}
|
|
@@ -534,7 +496,7 @@ merge:
|
|
|
534
496
|
};
|
|
535
497
|
|
|
536
498
|
try {
|
|
537
|
-
await mergeCommand(
|
|
499
|
+
await mergeCommand({ branch: branchName, json: true });
|
|
538
500
|
} finally {
|
|
539
501
|
process.stdout.write = originalWrite;
|
|
540
502
|
}
|
|
@@ -574,7 +536,7 @@ merge:
|
|
|
574
536
|
|
|
575
537
|
try {
|
|
576
538
|
// No --into flag — should read session-branch.txt
|
|
577
|
-
await mergeCommand(
|
|
539
|
+
await mergeCommand({ branch: branchName, json: true });
|
|
578
540
|
} finally {
|
|
579
541
|
process.stdout.write = originalWrite;
|
|
580
542
|
}
|
|
@@ -621,7 +583,7 @@ merge:
|
|
|
621
583
|
|
|
622
584
|
try {
|
|
623
585
|
// --into overrides session-branch.txt
|
|
624
|
-
await mergeCommand(
|
|
586
|
+
await mergeCommand({ branch: branchName, into: "explicit-target", json: true });
|
|
625
587
|
} finally {
|
|
626
588
|
process.stdout.write = originalWrite;
|
|
627
589
|
}
|
|
@@ -657,7 +619,7 @@ merge:
|
|
|
657
619
|
};
|
|
658
620
|
|
|
659
621
|
try {
|
|
660
|
-
await mergeCommand(
|
|
622
|
+
await mergeCommand({ branch: branchName });
|
|
661
623
|
} finally {
|
|
662
624
|
process.stdout.write = originalWrite;
|
|
663
625
|
}
|