@os-eco/overstory-cli 0.6.1

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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +381 -0
  3. package/agents/builder.md +137 -0
  4. package/agents/coordinator.md +263 -0
  5. package/agents/lead.md +301 -0
  6. package/agents/merger.md +160 -0
  7. package/agents/monitor.md +214 -0
  8. package/agents/reviewer.md +140 -0
  9. package/agents/scout.md +119 -0
  10. package/agents/supervisor.md +423 -0
  11. package/package.json +47 -0
  12. package/src/agents/checkpoint.test.ts +88 -0
  13. package/src/agents/checkpoint.ts +101 -0
  14. package/src/agents/hooks-deployer.test.ts +2040 -0
  15. package/src/agents/hooks-deployer.ts +607 -0
  16. package/src/agents/identity.test.ts +603 -0
  17. package/src/agents/identity.ts +384 -0
  18. package/src/agents/lifecycle.test.ts +196 -0
  19. package/src/agents/lifecycle.ts +183 -0
  20. package/src/agents/manifest.test.ts +746 -0
  21. package/src/agents/manifest.ts +354 -0
  22. package/src/agents/overlay.test.ts +676 -0
  23. package/src/agents/overlay.ts +308 -0
  24. package/src/beads/client.test.ts +217 -0
  25. package/src/beads/client.ts +202 -0
  26. package/src/beads/molecules.test.ts +338 -0
  27. package/src/beads/molecules.ts +198 -0
  28. package/src/commands/agents.test.ts +322 -0
  29. package/src/commands/agents.ts +287 -0
  30. package/src/commands/clean.test.ts +670 -0
  31. package/src/commands/clean.ts +618 -0
  32. package/src/commands/completions.test.ts +342 -0
  33. package/src/commands/completions.ts +887 -0
  34. package/src/commands/coordinator.test.ts +1530 -0
  35. package/src/commands/coordinator.ts +733 -0
  36. package/src/commands/costs.test.ts +1119 -0
  37. package/src/commands/costs.ts +564 -0
  38. package/src/commands/dashboard.test.ts +308 -0
  39. package/src/commands/dashboard.ts +838 -0
  40. package/src/commands/doctor.test.ts +294 -0
  41. package/src/commands/doctor.ts +213 -0
  42. package/src/commands/errors.test.ts +647 -0
  43. package/src/commands/errors.ts +248 -0
  44. package/src/commands/feed.test.ts +578 -0
  45. package/src/commands/feed.ts +361 -0
  46. package/src/commands/group.test.ts +262 -0
  47. package/src/commands/group.ts +511 -0
  48. package/src/commands/hooks.test.ts +458 -0
  49. package/src/commands/hooks.ts +253 -0
  50. package/src/commands/init.test.ts +347 -0
  51. package/src/commands/init.ts +650 -0
  52. package/src/commands/inspect.test.ts +670 -0
  53. package/src/commands/inspect.ts +431 -0
  54. package/src/commands/log.test.ts +1454 -0
  55. package/src/commands/log.ts +724 -0
  56. package/src/commands/logs.test.ts +379 -0
  57. package/src/commands/logs.ts +546 -0
  58. package/src/commands/mail.test.ts +1270 -0
  59. package/src/commands/mail.ts +771 -0
  60. package/src/commands/merge.test.ts +670 -0
  61. package/src/commands/merge.ts +355 -0
  62. package/src/commands/metrics.test.ts +444 -0
  63. package/src/commands/metrics.ts +143 -0
  64. package/src/commands/monitor.test.ts +191 -0
  65. package/src/commands/monitor.ts +390 -0
  66. package/src/commands/nudge.test.ts +230 -0
  67. package/src/commands/nudge.ts +372 -0
  68. package/src/commands/prime.test.ts +470 -0
  69. package/src/commands/prime.ts +381 -0
  70. package/src/commands/replay.test.ts +741 -0
  71. package/src/commands/replay.ts +360 -0
  72. package/src/commands/run.test.ts +431 -0
  73. package/src/commands/run.ts +351 -0
  74. package/src/commands/sling.test.ts +657 -0
  75. package/src/commands/sling.ts +661 -0
  76. package/src/commands/spec.test.ts +203 -0
  77. package/src/commands/spec.ts +168 -0
  78. package/src/commands/status.test.ts +430 -0
  79. package/src/commands/status.ts +398 -0
  80. package/src/commands/stop.test.ts +420 -0
  81. package/src/commands/stop.ts +151 -0
  82. package/src/commands/supervisor.test.ts +187 -0
  83. package/src/commands/supervisor.ts +535 -0
  84. package/src/commands/trace.test.ts +745 -0
  85. package/src/commands/trace.ts +325 -0
  86. package/src/commands/watch.test.ts +145 -0
  87. package/src/commands/watch.ts +247 -0
  88. package/src/commands/worktree.test.ts +786 -0
  89. package/src/commands/worktree.ts +311 -0
  90. package/src/config.test.ts +822 -0
  91. package/src/config.ts +829 -0
  92. package/src/doctor/agents.test.ts +454 -0
  93. package/src/doctor/agents.ts +396 -0
  94. package/src/doctor/config-check.test.ts +190 -0
  95. package/src/doctor/config-check.ts +183 -0
  96. package/src/doctor/consistency.test.ts +651 -0
  97. package/src/doctor/consistency.ts +294 -0
  98. package/src/doctor/databases.test.ts +290 -0
  99. package/src/doctor/databases.ts +218 -0
  100. package/src/doctor/dependencies.test.ts +184 -0
  101. package/src/doctor/dependencies.ts +175 -0
  102. package/src/doctor/logs.test.ts +251 -0
  103. package/src/doctor/logs.ts +295 -0
  104. package/src/doctor/merge-queue.test.ts +216 -0
  105. package/src/doctor/merge-queue.ts +144 -0
  106. package/src/doctor/structure.test.ts +291 -0
  107. package/src/doctor/structure.ts +198 -0
  108. package/src/doctor/types.ts +37 -0
  109. package/src/doctor/version.test.ts +136 -0
  110. package/src/doctor/version.ts +129 -0
  111. package/src/e2e/init-sling-lifecycle.test.ts +277 -0
  112. package/src/errors.ts +217 -0
  113. package/src/events/store.test.ts +660 -0
  114. package/src/events/store.ts +369 -0
  115. package/src/events/tool-filter.test.ts +330 -0
  116. package/src/events/tool-filter.ts +126 -0
  117. package/src/index.ts +316 -0
  118. package/src/insights/analyzer.test.ts +466 -0
  119. package/src/insights/analyzer.ts +203 -0
  120. package/src/logging/color.test.ts +142 -0
  121. package/src/logging/color.ts +71 -0
  122. package/src/logging/logger.test.ts +813 -0
  123. package/src/logging/logger.ts +266 -0
  124. package/src/logging/reporter.test.ts +259 -0
  125. package/src/logging/reporter.ts +109 -0
  126. package/src/logging/sanitizer.test.ts +190 -0
  127. package/src/logging/sanitizer.ts +57 -0
  128. package/src/mail/broadcast.test.ts +203 -0
  129. package/src/mail/broadcast.ts +92 -0
  130. package/src/mail/client.test.ts +773 -0
  131. package/src/mail/client.ts +223 -0
  132. package/src/mail/store.test.ts +705 -0
  133. package/src/mail/store.ts +387 -0
  134. package/src/merge/queue.test.ts +359 -0
  135. package/src/merge/queue.ts +231 -0
  136. package/src/merge/resolver.test.ts +1345 -0
  137. package/src/merge/resolver.ts +645 -0
  138. package/src/metrics/store.test.ts +667 -0
  139. package/src/metrics/store.ts +445 -0
  140. package/src/metrics/summary.test.ts +398 -0
  141. package/src/metrics/summary.ts +178 -0
  142. package/src/metrics/transcript.test.ts +356 -0
  143. package/src/metrics/transcript.ts +175 -0
  144. package/src/mulch/client.test.ts +671 -0
  145. package/src/mulch/client.ts +332 -0
  146. package/src/sessions/compat.test.ts +280 -0
  147. package/src/sessions/compat.ts +104 -0
  148. package/src/sessions/store.test.ts +873 -0
  149. package/src/sessions/store.ts +494 -0
  150. package/src/test-helpers.test.ts +124 -0
  151. package/src/test-helpers.ts +126 -0
  152. package/src/tracker/beads.ts +56 -0
  153. package/src/tracker/factory.test.ts +80 -0
  154. package/src/tracker/factory.ts +64 -0
  155. package/src/tracker/seeds.ts +182 -0
  156. package/src/tracker/types.ts +52 -0
  157. package/src/types.ts +724 -0
  158. package/src/watchdog/daemon.test.ts +1975 -0
  159. package/src/watchdog/daemon.ts +671 -0
  160. package/src/watchdog/health.test.ts +431 -0
  161. package/src/watchdog/health.ts +264 -0
  162. package/src/watchdog/triage.test.ts +164 -0
  163. package/src/watchdog/triage.ts +179 -0
  164. package/src/worktree/manager.test.ts +439 -0
  165. package/src/worktree/manager.ts +198 -0
  166. package/src/worktree/tmux.test.ts +1009 -0
  167. package/src/worktree/tmux.ts +509 -0
  168. package/templates/CLAUDE.md.tmpl +89 -0
  169. package/templates/hooks.json.tmpl +105 -0
  170. package/templates/overlay.md.tmpl +81 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Mail client for inter-agent messaging.
3
+ *
4
+ * Wraps the low-level MailStore with higher-level operations:
5
+ * send, check, checkInject (hook format), list, markRead, reply.
6
+ * Synchronous by design (bun:sqlite is sync, ~1-5ms per query).
7
+ */
8
+
9
+ import { MailError } from "../errors.ts";
10
+ import type { MailMessage, MailPayloadMap, MailProtocolType } from "../types.ts";
11
+ import type { MailStore } from "./store.ts";
12
+
13
+ export interface MailClient {
14
+ /** Send a new message. Returns the assigned message ID. */
15
+ send(msg: {
16
+ from: string;
17
+ to: string;
18
+ subject: string;
19
+ body: string;
20
+ type?: MailMessage["type"];
21
+ priority?: MailMessage["priority"];
22
+ threadId?: string;
23
+ payload?: string;
24
+ }): string;
25
+
26
+ /** Send a typed protocol message with structured payload. Returns the message ID. */
27
+ sendProtocol<T extends MailProtocolType>(msg: {
28
+ from: string;
29
+ to: string;
30
+ subject: string;
31
+ body: string;
32
+ type: T;
33
+ priority?: MailMessage["priority"];
34
+ threadId?: string;
35
+ payload: MailPayloadMap[T];
36
+ }): string;
37
+
38
+ /** Get unread messages for an agent. Marks them as read. */
39
+ check(agentName: string): MailMessage[];
40
+
41
+ /** Get unread messages formatted for hook injection (human-readable string). */
42
+ checkInject(agentName: string): string;
43
+
44
+ /** List messages with optional filters. */
45
+ list(filters?: { from?: string; to?: string; unread?: boolean }): MailMessage[];
46
+
47
+ /** Mark a message as read by ID. Returns whether the message was already read. */
48
+ markRead(id: string): { alreadyRead: boolean };
49
+
50
+ /** Reply to a message. Returns the new message ID. */
51
+ reply(messageId: string, body: string, from: string): string;
52
+
53
+ /** Close the underlying store. */
54
+ close(): void;
55
+ }
56
+
57
+ /**
58
+ * Parse a JSON payload from a mail message, returning the typed object.
59
+ * Returns null if the message has no payload or if parsing fails.
60
+ */
61
+ export function parsePayload<T extends MailProtocolType>(
62
+ message: MailMessage,
63
+ _expectedType: T,
64
+ ): MailPayloadMap[T] | null {
65
+ if (message.payload === null) {
66
+ return null;
67
+ }
68
+ try {
69
+ return JSON.parse(message.payload) as MailPayloadMap[T];
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /** Protocol types that represent structured coordination messages. */
76
+ const PROTOCOL_TYPES = new Set<string>([
77
+ "worker_done",
78
+ "merge_ready",
79
+ "merged",
80
+ "merge_failed",
81
+ "escalation",
82
+ "health_check",
83
+ "dispatch",
84
+ "assign",
85
+ ]);
86
+
87
+ /**
88
+ * Format messages for hook injection.
89
+ *
90
+ * Produces a human-readable block that gets injected into the agent's
91
+ * context via the UserPromptSubmit hook.
92
+ */
93
+ function formatForInjection(messages: MailMessage[]): string {
94
+ if (messages.length === 0) {
95
+ return "";
96
+ }
97
+
98
+ const lines: string[] = [
99
+ `📬 You have ${messages.length} new message${messages.length === 1 ? "" : "s"}:`,
100
+ "",
101
+ ];
102
+
103
+ for (const msg of messages) {
104
+ const priorityTag = msg.priority !== "normal" ? ` [${msg.priority.toUpperCase()}]` : "";
105
+ lines.push(`--- From: ${msg.from}${priorityTag} (${msg.type}) ---`);
106
+ lines.push(`Subject: ${msg.subject}`);
107
+ lines.push(msg.body);
108
+ if (msg.payload !== null && PROTOCOL_TYPES.has(msg.type)) {
109
+ lines.push(`Payload: ${msg.payload}`);
110
+ }
111
+ lines.push(`[Reply with: overstory mail reply ${msg.id} --body "..."]`);
112
+ lines.push("");
113
+ }
114
+
115
+ return lines.join("\n");
116
+ }
117
+
118
+ /**
119
+ * Create a MailClient wrapping the given MailStore.
120
+ *
121
+ * @param store - The underlying MailStore for persistence
122
+ * @returns A MailClient with send, check, checkInject, list, markRead, reply
123
+ */
124
+ export function createMailClient(store: MailStore): MailClient {
125
+ return {
126
+ send(msg): string {
127
+ const message = store.insert({
128
+ id: "",
129
+ from: msg.from,
130
+ to: msg.to,
131
+ subject: msg.subject,
132
+ body: msg.body,
133
+ type: msg.type ?? "status",
134
+ priority: msg.priority ?? "normal",
135
+ threadId: msg.threadId ?? null,
136
+ payload: msg.payload ?? null,
137
+ });
138
+ return message.id;
139
+ },
140
+
141
+ sendProtocol(msg): string {
142
+ const message = store.insert({
143
+ id: "",
144
+ from: msg.from,
145
+ to: msg.to,
146
+ subject: msg.subject,
147
+ body: msg.body,
148
+ type: msg.type,
149
+ priority: msg.priority ?? "normal",
150
+ threadId: msg.threadId ?? null,
151
+ payload: JSON.stringify(msg.payload),
152
+ });
153
+ return message.id;
154
+ },
155
+
156
+ check(agentName): MailMessage[] {
157
+ const messages = store.getUnread(agentName);
158
+ for (const msg of messages) {
159
+ store.markRead(msg.id);
160
+ }
161
+ return messages;
162
+ },
163
+
164
+ checkInject(agentName): string {
165
+ const messages = store.getUnread(agentName);
166
+ for (const msg of messages) {
167
+ store.markRead(msg.id);
168
+ }
169
+ return formatForInjection(messages);
170
+ },
171
+
172
+ list(filters): MailMessage[] {
173
+ return store.getAll(filters);
174
+ },
175
+
176
+ markRead(id): { alreadyRead: boolean } {
177
+ const msg = store.getById(id);
178
+ if (!msg) {
179
+ throw new MailError(`Message not found: ${id}`, {
180
+ messageId: id,
181
+ });
182
+ }
183
+ if (msg.read) {
184
+ return { alreadyRead: true };
185
+ }
186
+ store.markRead(id);
187
+ return { alreadyRead: false };
188
+ },
189
+
190
+ reply(messageId, body, from): string {
191
+ const original = store.getById(messageId);
192
+ if (!original) {
193
+ throw new MailError(`Message not found: ${messageId}`, {
194
+ messageId,
195
+ });
196
+ }
197
+
198
+ const threadId = original.threadId ?? original.id;
199
+
200
+ // Determine the correct recipient: reply goes to "the other side"
201
+ // If the replier is the original sender, reply goes to the original recipient.
202
+ // If the replier is the original recipient (or anyone else), reply goes to the original sender.
203
+ const to = from === original.from ? original.to : original.from;
204
+
205
+ const reply = store.insert({
206
+ id: "",
207
+ from,
208
+ to,
209
+ subject: `Re: ${original.subject}`,
210
+ body,
211
+ type: original.type,
212
+ priority: original.priority,
213
+ threadId,
214
+ payload: null,
215
+ });
216
+ return reply.id;
217
+ },
218
+
219
+ close(): void {
220
+ store.close();
221
+ },
222
+ };
223
+ }