@opencoven/coven-code 0.0.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 (86) hide show
  1. package/README.md +145 -0
  2. package/bin/coven-code-sdk.mjs +12 -0
  3. package/bin/coven-code.mjs +19 -0
  4. package/docs/CLI.md +192 -0
  5. package/docs/CONFIGURATION.md +107 -0
  6. package/docs/DEVELOPMENT.md +104 -0
  7. package/docs/DOGFOOD-PROTOCOL.md +263 -0
  8. package/docs/MCP-SKILLS-PLUGINS.md +127 -0
  9. package/docs/README.md +38 -0
  10. package/docs/RELEASE.md +33 -0
  11. package/docs/SDK.md +107 -0
  12. package/docs/superpowers/plans/2026-05-25-coven-code-panel-tui.md +904 -0
  13. package/docs/superpowers/plans/2026-05-25-coven-code-rebrand.md +670 -0
  14. package/docs/superpowers/specs/2026-05-25-coven-code-panel-tui-design.md +235 -0
  15. package/docs/superpowers/specs/2026-05-26-slash-first-tui-review.md +63 -0
  16. package/package.json +36 -0
  17. package/src/agent/lane.mjs +136 -0
  18. package/src/agent/local.mjs +95 -0
  19. package/src/cli/dispatch.mjs +66 -0
  20. package/src/cli/execute.mjs +588 -0
  21. package/src/cli/help.mjs +58 -0
  22. package/src/cli/interactive-core.mjs +302 -0
  23. package/src/cli/notifications.mjs +13 -0
  24. package/src/cli/parse.mjs +83 -0
  25. package/src/cli/reasoning.mjs +45 -0
  26. package/src/cli/refs.mjs +162 -0
  27. package/src/cli/repl.mjs +61 -0
  28. package/src/cli/slash-commands.mjs +357 -0
  29. package/src/cli/stream-json.mjs +116 -0
  30. package/src/cli/tui.mjs +757 -0
  31. package/src/commands/agents.mjs +53 -0
  32. package/src/commands/config.mjs +27 -0
  33. package/src/commands/ide.mjs +17 -0
  34. package/src/commands/login.mjs +84 -0
  35. package/src/commands/mcp.mjs +176 -0
  36. package/src/commands/permissions.mjs +328 -0
  37. package/src/commands/plugins.mjs +86 -0
  38. package/src/commands/review.mjs +74 -0
  39. package/src/commands/skill.mjs +23 -0
  40. package/src/commands/threads.mjs +165 -0
  41. package/src/commands/tools.mjs +77 -0
  42. package/src/commands/update.mjs +31 -0
  43. package/src/commands/usage.mjs +34 -0
  44. package/src/constants.mjs +46 -0
  45. package/src/main.mjs +87 -0
  46. package/src/mcp/discover.mjs +154 -0
  47. package/src/mcp/permissions.mjs +52 -0
  48. package/src/mcp/probe.mjs +424 -0
  49. package/src/mcp/registry.mjs +96 -0
  50. package/src/plugins/discover.mjs +880 -0
  51. package/src/sdk-install.mjs +187 -0
  52. package/src/sdk.mjs +314 -0
  53. package/src/settings/load.mjs +134 -0
  54. package/src/settings/paths.mjs +101 -0
  55. package/src/skills/builtin/building-skills/SKILL.md +20 -0
  56. package/src/skills/discover.mjs +95 -0
  57. package/src/threads/store.mjs +176 -0
  58. package/src/tools/builtin/bash.mjs +110 -0
  59. package/src/tools/builtin/create-file.mjs +66 -0
  60. package/src/tools/builtin/edit-file.mjs +76 -0
  61. package/src/tools/builtin/finder.mjs +73 -0
  62. package/src/tools/builtin/glob.mjs +74 -0
  63. package/src/tools/builtin/grep.mjs +82 -0
  64. package/src/tools/builtin/index.mjs +83 -0
  65. package/src/tools/builtin/librarian.mjs +97 -0
  66. package/src/tools/builtin/look-at.mjs +92 -0
  67. package/src/tools/builtin/mcp.mjs +51 -0
  68. package/src/tools/builtin/mermaid.mjs +59 -0
  69. package/src/tools/builtin/oracle.mjs +56 -0
  70. package/src/tools/builtin/painter.mjs +81 -0
  71. package/src/tools/builtin/plugin-tool.mjs +53 -0
  72. package/src/tools/builtin/read-mcp-resource.mjs +63 -0
  73. package/src/tools/builtin/read-web-page.mjs +72 -0
  74. package/src/tools/builtin/read.mjs +59 -0
  75. package/src/tools/builtin/runtime.mjs +215 -0
  76. package/src/tools/builtin/task.mjs +63 -0
  77. package/src/tools/builtin/toolbox-tool.mjs +57 -0
  78. package/src/tools/builtin/undo-edit.mjs +97 -0
  79. package/src/tools/builtin/web-search.mjs +128 -0
  80. package/src/tools/toolbox.mjs +273 -0
  81. package/src/util/fs.mjs +13 -0
  82. package/src/util/glob.mjs +46 -0
  83. package/src/util/html.mjs +21 -0
  84. package/src/util/media.mjs +13 -0
  85. package/src/util/shell.mjs +24 -0
  86. package/src/util/table.mjs +11 -0
@@ -0,0 +1,588 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { BUILTIN_TOOLS } from '../constants.mjs';
5
+ import { estimateUsage, localAgentResponse } from '../agent/local.mjs';
6
+ import {
7
+ listActiveMcpServerEntries,
8
+ } from '../mcp/discover.mjs';
9
+ import { discoverMcpToolRows } from '../mcp/probe.mjs';
10
+ import {
11
+ loadPlugins,
12
+ runPluginEventHandlers,
13
+ } from '../plugins/discover.mjs';
14
+ import { discoverAgentFiles, firstGuidanceInDir } from '../commands/agents.mjs';
15
+ import {
16
+ isToolDisabled,
17
+ listToolboxTools,
18
+ toolKindForName,
19
+ } from '../tools/toolbox.mjs';
20
+ import { executePromptToolRequest } from '../tools/builtin/index.mjs';
21
+ import { toolResultContent } from '../tools/builtin/runtime.mjs';
22
+ import { persistThreadMessages, threadContinuationPrompt } from '../threads/store.mjs';
23
+ import { readEffectiveSettings } from '../settings/load.mjs';
24
+ import { expandFileReferences, expandThreadReferences } from './refs.mjs';
25
+ import { UsageError } from './parse.mjs';
26
+ import { reasoningEffortForMode } from './reasoning.mjs';
27
+ import { notifyAgentComplete } from './notifications.mjs';
28
+ import { globToRegex } from '../util/glob.mjs';
29
+ import { displayCwd, emitJson } from '../util/fs.mjs';
30
+ import {
31
+ streamJsonInputMessages,
32
+ streamJsonOutputUserContent,
33
+ streamJsonPermissionDenials,
34
+ streamJsonResultMessage,
35
+ streamJsonTurnCount,
36
+ toolRunParent,
37
+ } from './stream-json.mjs';
38
+
39
+ const MAX_AGENT_CONTINUATIONS = 8;
40
+
41
+ export async function runExecute(parsed, stdin, options = {}) {
42
+ const started = Date.now();
43
+ if (parsed.streamJsonInput && parsed.streamJson) {
44
+ return runStreamJsonInputExecute(parsed, stdin, options, started);
45
+ }
46
+
47
+ const prompt = combinePrompt(parsed.prompt, stdin, parsed.streamJsonInput);
48
+ const sessionId = options.thread?.id ?? `T-${randomUUID()}`;
49
+ const plugins = await loadPlugins(process.cwd());
50
+ await runSessionStartHandlers(plugins, sessionId);
51
+ const sequence = await runExecuteTurnSequence({
52
+ initialPrompt: prompt,
53
+ stdin,
54
+ sessionId,
55
+ parsed,
56
+ plugins,
57
+ thread: options.thread,
58
+ });
59
+ const { turns, errorSubtype } = sequence;
60
+ const result = turns.at(-1)?.result ?? '';
61
+
62
+ if (parsed.streamJson) {
63
+ const activeMcpServers = listActiveMcpServerEntries(parsed, turns.map((turn) => turn.prompt).join('\n\n'));
64
+ const tools = [
65
+ ...BUILTIN_TOOLS.map(([name]) => name),
66
+ ...listToolboxTools(parsed).map((tool) => tool.name),
67
+ ...plugins.tools.map((tool) => tool.name),
68
+ ...(await discoverMcpToolRows(activeMcpServers)).map(([name]) => name),
69
+ ].filter((name) => !isToolDisabled(name, toolKindForName(name), parsed));
70
+ emitJson({
71
+ type: 'system',
72
+ subtype: 'init',
73
+ cwd: displayCwd(),
74
+ session_id: sessionId,
75
+ tools,
76
+ mcp_servers: activeMcpServers.map(({ name }) => ({ name, status: 'connected' })),
77
+ agent_mode: parsed.mode,
78
+ reasoning_effort: reasoningEffortForMode(parsed.mode, parsed.reasoningEffort),
79
+ });
80
+ for (const turn of turns) {
81
+ emitJson({
82
+ type: 'user',
83
+ message: { role: 'user', content: [{ type: 'text', text: turn.prompt }] },
84
+ parent_tool_use_id: null,
85
+ session_id: sessionId,
86
+ });
87
+ if (turn.toolRun?.toolUse) {
88
+ emitJson({
89
+ type: 'assistant',
90
+ message: {
91
+ type: 'message',
92
+ role: 'assistant',
93
+ content: [turn.toolRun.toolUse],
94
+ stop_reason: 'tool_use',
95
+ usage: estimateUsage(turn.prompt, JSON.stringify(turn.toolRun.toolUse.input)),
96
+ },
97
+ parent_tool_use_id: null,
98
+ session_id: sessionId,
99
+ });
100
+ for (const subagentMessage of turn.toolRun.subagentMessages ?? []) {
101
+ emitJson({
102
+ type: 'assistant',
103
+ message: {
104
+ type: 'message',
105
+ role: 'assistant',
106
+ content: assistantStreamContent(subagentMessage.text ?? '', parsed),
107
+ stop_reason: 'end_turn',
108
+ usage: estimateUsage(turn.prompt, subagentMessage.text ?? ''),
109
+ },
110
+ parent_tool_use_id: turn.toolRun.toolUse.id,
111
+ session_id: sessionId,
112
+ });
113
+ }
114
+ emitJson({
115
+ type: 'user',
116
+ message: {
117
+ role: 'user',
118
+ content: [{
119
+ type: 'tool_result',
120
+ tool_use_id: turn.toolRun.toolUse.id,
121
+ content: toolResultContent(turn.toolRun),
122
+ is_error: turn.toolRun.exitCode !== 0,
123
+ }],
124
+ },
125
+ parent_tool_use_id: toolRunParent(turn.toolRun, 'toolResultParentToolUseId', null),
126
+ session_id: sessionId,
127
+ });
128
+ }
129
+ emitJson({
130
+ type: 'assistant',
131
+ message: {
132
+ type: 'message',
133
+ role: 'assistant',
134
+ content: assistantStreamContent(turn.result, parsed),
135
+ stop_reason: 'end_turn',
136
+ usage: estimateUsage(turn.prompt, turn.result),
137
+ },
138
+ parent_tool_use_id: turn.toolRun ? toolRunParent(turn.toolRun, 'finalParentToolUseId', null) : null,
139
+ session_id: sessionId,
140
+ });
141
+ }
142
+ emitJson(streamJsonResultMessage({
143
+ started,
144
+ isError: errorSubtype !== undefined || turns.some((turn) => (turn.toolRun?.exitCode ?? 0) !== 0),
145
+ errorSubtype,
146
+ numTurns: streamJsonTurnCount(turns),
147
+ result: errorSubtype === 'error_max_turns' ? 'Maximum agent continuation turns exceeded' : result,
148
+ sessionId,
149
+ usage: estimateUsage(turns.map((turn) => turn.prompt).join('\n'), result),
150
+ permissionDenials: streamJsonPermissionDenials(turns.flatMap((turn) => turn.toolRun?.permissionDenials ?? [])),
151
+ }));
152
+ const thread = await persistThreadMessages(sessionId, turnMessages(turns), parsed.mode, options.thread, parsed);
153
+ notifyAgentComplete(parsed);
154
+ return thread;
155
+ }
156
+
157
+ process.stdout.write(result.endsWith('\n') ? result : `${result}\n`);
158
+ const thread = await persistThreadMessages(sessionId, turnMessages(turns), parsed.mode, options.thread, parsed);
159
+ notifyAgentComplete(parsed);
160
+ return thread;
161
+ }
162
+
163
+ async function runExecuteTurnSequence({ initialPrompt, stdin, sessionId, parsed, plugins, thread }) {
164
+ const turns = [];
165
+ let prompt = initialPrompt;
166
+ let continuationCount = 0;
167
+ let errorSubtype;
168
+ while (prompt !== undefined) {
169
+ const turnStdin = turns.length === 0 ? stdin : '';
170
+ const turn = await runExecuteTurn(prompt, turnStdin, sessionId, parsed, plugins, thread, turns);
171
+ turns.push(turn);
172
+ const nextPrompt = agentContinuationMessage(turn.endDecision);
173
+ if (!nextPrompt) break;
174
+ continuationCount += 1;
175
+ if (continuationCount > MAX_AGENT_CONTINUATIONS) {
176
+ errorSubtype = 'error_max_turns';
177
+ process.exitCode = 1;
178
+ break;
179
+ }
180
+ prompt = nextPrompt;
181
+ }
182
+ return { turns, errorSubtype };
183
+ }
184
+
185
+ async function runExecuteTurn(prompt, stdin, sessionId, parsed, plugins, thread, priorTurns = []) {
186
+ const messageId = createThreadMessageID();
187
+ const startDecision = await runPluginEventHandlers(plugins.handlers['agent.start'], {
188
+ message: prompt,
189
+ id: messageId,
190
+ thread: { id: sessionId },
191
+ });
192
+ const promptWithPluginContext = appendAgentStartMessage(prompt, startDecision);
193
+ const expandedPrompt = expandFileReferences(promptWithPluginContext, { parsed });
194
+ const guidancePrompt = expandAgentGuidanceReferences(expandedPrompt, parsed);
195
+ const turnPrompt = guidancePrompt ? `${guidancePrompt}\n${expandedPrompt}` : expandedPrompt;
196
+ const contextThread = threadContextForTurn(thread, sessionId, priorTurns);
197
+ const modelPrompt = contextThread
198
+ ? threadContinuationPrompt(contextThread, turnPrompt)
199
+ : expandThreadReferences(turnPrompt);
200
+ const toolRun = await executePromptToolRequest(prompt, stdin, sessionId, parsed, plugins);
201
+ const result = toolRun?.output ?? localAgentResponse(applySystemPrompt(modelPrompt, parsed), stdin);
202
+ const endDecision = await runPluginEventHandlers(plugins.handlers['agent.end'], {
203
+ message: prompt,
204
+ id: messageId,
205
+ result,
206
+ status: agentTurnStatus(toolRun),
207
+ messages: pluginMessagesForTurn(prompt, result, toolRun, messageId),
208
+ thread: { id: sessionId },
209
+ });
210
+ return { prompt, result, toolRun, endDecision };
211
+ }
212
+
213
+ function agentTurnStatus(toolRun) {
214
+ return (toolRun?.exitCode ?? 0) === 0 ? 'done' : 'error';
215
+ }
216
+
217
+ function createThreadMessageID() {
218
+ return `msg_${randomUUID()}`;
219
+ }
220
+
221
+ function appendAgentStartMessage(prompt, decision = {}) {
222
+ validateAgentStartDecision(decision);
223
+ const content = decision.message?.content.trim() ?? '';
224
+ if (!content) return prompt;
225
+ return prompt ? `${prompt}\n\n${content}` : content;
226
+ }
227
+
228
+ function validateAgentStartDecision(decision = {}) {
229
+ if (!decision || typeof decision !== 'object' || !Object.hasOwn(decision, 'message')) return;
230
+ if (Object.keys(decision).some((key) => key !== 'message')) {
231
+ throw new Error('plugin agent.start fields must match the documented shape');
232
+ }
233
+ if (!decision.message || typeof decision.message !== 'object' || Array.isArray(decision.message)) {
234
+ throw new Error('plugin agent.start message must be an object');
235
+ }
236
+ if (Object.keys(decision.message).some((key) => key !== 'content' && key !== 'display')) {
237
+ throw new Error('plugin agent.start message fields must be content and display');
238
+ }
239
+ if (typeof decision.message?.content !== 'string') {
240
+ throw new Error('plugin agent.start message content must be a string');
241
+ }
242
+ if (decision.message.display !== true) {
243
+ throw new Error('plugin agent.start message display must be true');
244
+ }
245
+ }
246
+
247
+ function threadContextForTurn(thread, sessionId, priorTurns = []) {
248
+ if (!thread && priorTurns.length === 0) return undefined;
249
+ return {
250
+ id: thread?.id ?? sessionId,
251
+ messages: [
252
+ ...(thread?.messages ?? []),
253
+ ...turnMessages(priorTurns),
254
+ ],
255
+ };
256
+ }
257
+
258
+ function turnMessages(turns) {
259
+ return turns.flatMap((turn) => [
260
+ { role: 'user', content: turn.prompt },
261
+ { role: 'assistant', content: turn.result },
262
+ ]);
263
+ }
264
+
265
+ function pluginMessagesForTurn(prompt, result, toolRun, messageId = createThreadMessageID()) {
266
+ const messages = [{ role: 'user', id: messageId, content: textContent(prompt) }];
267
+ if (toolRun?.toolUse) {
268
+ messages.push({ role: 'assistant', id: createThreadMessageID(), content: [toolRun.toolUse] });
269
+ messages.push({
270
+ role: 'user',
271
+ id: createThreadMessageID(),
272
+ content: [{
273
+ type: 'tool_result',
274
+ toolUseID: toolRun.toolUse.id,
275
+ output: toolResultContent(toolRun),
276
+ status: toolRun.exitCode !== 0 ? 'error' : 'done',
277
+ }],
278
+ });
279
+ }
280
+ messages.push({ role: 'assistant', id: createThreadMessageID(), content: textContent(result) });
281
+ return messages;
282
+ }
283
+
284
+ function textContent(text) {
285
+ return [{ type: 'text', text: String(text ?? '') }];
286
+ }
287
+
288
+ function agentContinuationMessage(decision) {
289
+ validateAgentEndDecision(decision);
290
+ if (decision?.action !== 'continue') return undefined;
291
+ return typeof decision.userMessage === 'string' && decision.userMessage.trim()
292
+ ? decision.userMessage
293
+ : undefined;
294
+ }
295
+
296
+ function validateAgentEndDecision(decision) {
297
+ if (decision === undefined) return;
298
+ if (decision?.action === 'allow') return;
299
+ if (!decision || typeof decision !== 'object' || decision.action !== 'continue') {
300
+ throw new Error('plugin agent.end action must be continue');
301
+ }
302
+ if (typeof decision.userMessage !== 'string') {
303
+ throw new Error('plugin agent.end continue userMessage must be a string');
304
+ }
305
+ }
306
+
307
+ async function runStreamJsonInputExecute(parsed, stdin, options = {}, started = Date.now()) {
308
+ const sessionId = options.thread?.id ?? `T-${randomUUID()}`;
309
+ const inputMessages = streamJsonInputMessages(parsed.prompt, stdin);
310
+ const combinedPrompt = inputMessages.map((message) => message.text).filter(Boolean).join('\n\n');
311
+ const plugins = await loadPlugins(process.cwd());
312
+ await runSessionStartHandlers(plugins, sessionId);
313
+ const activeMcpServers = listActiveMcpServerEntries(parsed, combinedPrompt);
314
+ const tools = [
315
+ ...BUILTIN_TOOLS.map(([name]) => name),
316
+ ...listToolboxTools(parsed).map((tool) => tool.name),
317
+ ...plugins.tools.map((tool) => tool.name),
318
+ ...(await discoverMcpToolRows(activeMcpServers)).map(([name]) => name),
319
+ ].filter((name) => !isToolDisabled(name, toolKindForName(name), parsed));
320
+ emitJson({
321
+ type: 'system',
322
+ subtype: 'init',
323
+ cwd: displayCwd(),
324
+ session_id: sessionId,
325
+ tools,
326
+ mcp_servers: activeMcpServers.map(({ name }) => ({ name, status: 'connected' })),
327
+ agent_mode: parsed.mode,
328
+ reasoning_effort: reasoningEffortForMode(parsed.mode, parsed.reasoningEffort),
329
+ });
330
+
331
+ const transcript = [];
332
+ const persistedMessages = [];
333
+ const permissionDenials = [];
334
+ let result = '';
335
+ let parentToolUseId = null;
336
+ let numTurns = 0;
337
+ let isError = false;
338
+
339
+ for (const input of inputMessages) {
340
+ const messageId = createThreadMessageID();
341
+ const startDecision = await runPluginEventHandlers(plugins.handlers['agent.start'], {
342
+ message: input.text,
343
+ id: messageId,
344
+ thread: { id: sessionId },
345
+ });
346
+ const inputTextWithPluginContext = appendAgentStartMessage(input.text, startDecision);
347
+ const expandedPrompt = expandFileReferences(inputTextWithPluginContext, { parsed });
348
+ const guidancePrompt = expandAgentGuidanceReferences(expandedPrompt, parsed);
349
+ const turnPrompt = guidancePrompt ? `${guidancePrompt}\n${expandedPrompt}` : expandedPrompt;
350
+ const modelPrompt = modelPromptWithTranscript(turnPrompt, transcript, options.thread, parsed);
351
+ const toolRun = await executePromptToolRequest(input.text, '', sessionId, parsed, plugins);
352
+ result = toolRun?.output ?? localAgentResponse(modelPrompt, '');
353
+ numTurns += streamJsonTurnCount([{ toolRun }]);
354
+ if (toolRun?.permissionDenials) permissionDenials.push(...toolRun.permissionDenials);
355
+ if ((toolRun?.exitCode ?? 0) !== 0) isError = true;
356
+
357
+ emitJson({
358
+ type: 'user',
359
+ ...(input.steer ? { steer: true } : {}),
360
+ message: { role: 'user', content: streamJsonOutputUserContent(input.content) },
361
+ parent_tool_use_id: null,
362
+ session_id: sessionId,
363
+ });
364
+ if (toolRun?.toolUse) {
365
+ emitJson({
366
+ type: 'assistant',
367
+ message: {
368
+ type: 'message',
369
+ role: 'assistant',
370
+ content: [toolRun.toolUse],
371
+ stop_reason: 'tool_use',
372
+ usage: estimateUsage(input.text, JSON.stringify(toolRun.toolUse.input)),
373
+ },
374
+ parent_tool_use_id: null,
375
+ session_id: sessionId,
376
+ });
377
+ for (const subagentMessage of toolRun.subagentMessages ?? []) {
378
+ emitJson({
379
+ type: 'assistant',
380
+ message: {
381
+ type: 'message',
382
+ role: 'assistant',
383
+ content: assistantStreamContent(subagentMessage.text ?? '', parsed),
384
+ stop_reason: 'end_turn',
385
+ usage: estimateUsage(input.text, subagentMessage.text ?? ''),
386
+ },
387
+ parent_tool_use_id: toolRun.toolUse.id,
388
+ session_id: sessionId,
389
+ });
390
+ }
391
+ emitJson({
392
+ type: 'user',
393
+ message: {
394
+ role: 'user',
395
+ content: [{
396
+ type: 'tool_result',
397
+ tool_use_id: toolRun.toolUse.id,
398
+ content: toolResultContent(toolRun),
399
+ is_error: toolRun.exitCode !== 0,
400
+ }],
401
+ },
402
+ parent_tool_use_id: toolRunParent(toolRun, 'toolResultParentToolUseId', null),
403
+ session_id: sessionId,
404
+ });
405
+ parentToolUseId = toolRunParent(toolRun, 'finalParentToolUseId', null);
406
+ }
407
+ emitJson({
408
+ type: 'assistant',
409
+ message: {
410
+ type: 'message',
411
+ role: 'assistant',
412
+ content: assistantStreamContent(result, parsed),
413
+ stop_reason: 'end_turn',
414
+ usage: estimateUsage(input.text, result),
415
+ },
416
+ parent_tool_use_id: parentToolUseId,
417
+ session_id: sessionId,
418
+ });
419
+ await runPluginEventHandlers(plugins.handlers['agent.end'], {
420
+ message: input.text,
421
+ id: messageId,
422
+ result,
423
+ status: agentTurnStatus(toolRun),
424
+ messages: pluginMessagesForTurn(input.text, result, toolRun, messageId),
425
+ thread: { id: sessionId },
426
+ });
427
+ transcript.push({ user: input.text, assistant: result });
428
+ persistedMessages.push(
429
+ { role: 'user', content: input.text },
430
+ { role: 'assistant', content: result },
431
+ );
432
+ }
433
+
434
+ emitJson(streamJsonResultMessage({
435
+ started,
436
+ isError,
437
+ numTurns,
438
+ result,
439
+ sessionId,
440
+ usage: estimateUsage(combinedPrompt, result),
441
+ permissionDenials: streamJsonPermissionDenials(permissionDenials),
442
+ }));
443
+ const thread = await persistThreadMessages(sessionId, persistedMessages, parsed.mode, options.thread, parsed);
444
+ notifyAgentComplete(parsed);
445
+ return thread;
446
+ }
447
+
448
+ async function runSessionStartHandlers(plugins, sessionId) {
449
+ await runPluginEventHandlers(plugins.handlers['session.start'], {
450
+ thread: { id: sessionId },
451
+ });
452
+ }
453
+
454
+ function assistantStreamContent(result, parsed = {}) {
455
+ if (parsed.streamJsonThinking && readEffectiveSettings(parsed)['covenCode.thinking.enabled'] !== false) {
456
+ return [{ type: 'thinking', thinking: 'Using the local deterministic recreation.' }, { type: 'text', text: result }];
457
+ }
458
+ return [{ type: 'text', text: result }];
459
+ }
460
+
461
+ function modelPromptWithTranscript(turnPrompt, transcript, thread, parsed = {}) {
462
+ const context = transcript.length > 0
463
+ ? `[conversation:${thread?.id ?? 'stream-json-input'}]\n${transcript
464
+ .map((entry) => `user: ${entry.user}\nassistant: ${entry.assistant}`)
465
+ .join('\n')}\n[/conversation]\n`
466
+ : '';
467
+ const prompt = `${context}${turnPrompt}`;
468
+ const modelPrompt = thread ? threadContinuationPrompt(thread, prompt) : expandThreadReferences(prompt);
469
+ return applySystemPrompt(modelPrompt, parsed);
470
+ }
471
+
472
+ function applySystemPrompt(prompt, parsed = {}) {
473
+ const systemPrompt = readEffectiveSettings(parsed)['covenCode.systemPrompt'];
474
+ if (typeof systemPrompt !== 'string' || systemPrompt.trim() === '') return prompt;
475
+ return `[system]\n${systemPrompt.trim()}\n[/system]\n${prompt}`;
476
+ }
477
+
478
+ function expandAgentGuidanceReferences(expandedPrompt, parsed = {}) {
479
+ const readFiles = filesInPrompt(expandedPrompt);
480
+ const agentFiles = [
481
+ ...discoverAgentFiles(process.cwd()),
482
+ ...discoverSubtreeAgentFiles(readFiles),
483
+ ];
484
+ const blocks = [...new Set(agentFiles)]
485
+ .map((filePath) => guidanceBlock(filePath, readFiles, parsed))
486
+ .filter(Boolean);
487
+ return blocks.join('\n');
488
+ }
489
+
490
+ function discoverSubtreeAgentFiles(readFiles) {
491
+ const cwd = path.resolve(process.cwd());
492
+ const files = [];
493
+ for (const filePath of readFiles) {
494
+ let current = path.dirname(path.resolve(filePath));
495
+ while (current.startsWith(cwd) && current !== cwd && current !== path.dirname(current)) {
496
+ const guidance = firstGuidanceInDir(current);
497
+ if (guidance) files.push(guidance);
498
+ current = path.dirname(current);
499
+ }
500
+ }
501
+ return files;
502
+ }
503
+
504
+ function guidanceBlock(filePath, readFiles, parsed = {}) {
505
+ try {
506
+ const content = readFileSync(filePath, 'utf8');
507
+ const expandable = stripFencedCodeBlocks(content);
508
+ const expanded = expandFileReferences(expandable, {
509
+ baseDir: path.dirname(filePath),
510
+ parsed,
511
+ includeFile: (mentionedFile, mentionedContent) => guidanceMentionApplies(mentionedFile, mentionedContent, readFiles),
512
+ });
513
+ return `[agent:${filePath}]\n${expanded}\n[/agent]`;
514
+ } catch {
515
+ return undefined;
516
+ }
517
+ }
518
+
519
+ function filesInPrompt(prompt) {
520
+ return [...prompt.matchAll(/\[file:([^\]\r\n]+)\]/g)].map((match) => path.resolve(match[1]));
521
+ }
522
+
523
+ function guidanceMentionApplies(mentionedFile, content, readFiles) {
524
+ const globs = frontmatterGlobs(content);
525
+ if (globs.length === 0) return true;
526
+ return readFiles.some((filePath) => globs.some((glob) => guidanceGlobMatches(glob, filePath, mentionedFile)));
527
+ }
528
+
529
+ function frontmatterGlobs(content) {
530
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
531
+ if (!match) return [];
532
+ const frontmatter = match[1];
533
+ const inline = frontmatter.match(/^globs:\s*\[(.*)\]\s*$/m);
534
+ if (inline) {
535
+ return inline[1]
536
+ .split(',')
537
+ .map((entry) => entry.trim().replace(/^["']|["']$/g, ''))
538
+ .filter(Boolean);
539
+ }
540
+ const lines = frontmatter.split(/\r?\n/);
541
+ const globs = [];
542
+ let inGlobs = false;
543
+ for (const line of lines) {
544
+ if (/^globs:\s*$/.test(line)) {
545
+ inGlobs = true;
546
+ continue;
547
+ }
548
+ if (inGlobs) {
549
+ const entry = line.match(/^\s*-\s*(.+?)\s*$/);
550
+ if (entry) globs.push(entry[1].replace(/^["']|["']$/g, ''));
551
+ else if (/^\S/.test(line)) break;
552
+ }
553
+ }
554
+ return globs.filter(Boolean);
555
+ }
556
+
557
+ function guidanceGlobMatches(pattern, readFile, guidanceFile) {
558
+ const normalized = path.normalize(pattern);
559
+ if (normalized.startsWith(`..${path.sep}`) || normalized.startsWith(`.${path.sep}`)) {
560
+ const absolutePattern = path.resolve(path.dirname(guidanceFile), normalized);
561
+ return globToRegex(path.normalize(absolutePattern)).test(path.normalize(readFile));
562
+ }
563
+ const relative = path.relative(process.cwd(), readFile);
564
+ const candidates = normalized.startsWith(`**${path.sep}`)
565
+ ? [normalized]
566
+ : [normalized, `**${path.sep}${normalized}`];
567
+ return candidates.some((candidate) => globToRegex(path.normalize(candidate)).test(path.normalize(relative)));
568
+ }
569
+
570
+ function stripFencedCodeBlocks(text) {
571
+ return text.replace(/```[\s\S]*?```/g, '');
572
+ }
573
+
574
+ function combinePrompt(prompt, stdin, streamJsonInput) {
575
+ if (streamJsonInput && stdin.trim()) {
576
+ return streamJsonInputMessages(prompt, stdin)
577
+ .map((message) => message.text)
578
+ .filter(Boolean)
579
+ .join('\n');
580
+ }
581
+
582
+ const processedStdin = stdin;
583
+ if (prompt && processedStdin.trim()) return `${prompt}\n\n${processedStdin.trimEnd()}`;
584
+ if (prompt) return prompt;
585
+ return processedStdin.trimEnd();
586
+ }
587
+
588
+ export { UsageError };
@@ -0,0 +1,58 @@
1
+ import { CLI_NAME, PRODUCT_NAME } from '../constants.mjs';
2
+
3
+ export function printHelp() {
4
+ console.log(`${PRODUCT_NAME}
5
+
6
+ Usage: ${CLI_NAME} [options] [command]
7
+
8
+ Run with no arguments in a terminal to enter the panel TUI. Set
9
+ COVEN_CODE_REPL=1 to use the classic readline REPL. Piped stdin becomes the
10
+ first interactive message when stdout is a TTY. Pass --execute or redirect
11
+ stdout to run a single turn and exit.
12
+
13
+ Options:
14
+ --execute, -x [prompt] Run one agent turn, print the final answer, and exit
15
+ --stream-json Emit structured JSONL in execute mode
16
+ --stream-json-thinking Include thinking blocks in stream JSON output
17
+ --stream-json-input Read one or more user messages as JSONL from stdin
18
+ --dangerously-allow-all Allow tool calls that would otherwise require approval
19
+ --mcp-config <json> Add an inline MCP server config for this run
20
+ --settings-file <path> Read user settings from a specific file
21
+ --mode <name> Agent mode: smart, deep, rush, or large
22
+ --reasoning-effort <level>
23
+ Set reasoning effort for the active mode
24
+ --label <name> Add a label to the created or continued thread
25
+ --visibility <level> Thread visibility: private, public, workspace, group, or unlisted
26
+ --archive Archive the thread after the execute turn
27
+ --continue [thread-id] Continue the latest active thread or the specified thread
28
+ --toolbox <path> Use a PATH-like toolbox root for this run
29
+ --skills <path> Use a PATH-like skill root for this run
30
+ --jetbrains Connect to a JetBrains IDE
31
+ -h, --help Show help
32
+ -v, --version Show version
33
+
34
+ Commands:
35
+ login Print local login instructions
36
+ update Check for updates
37
+ usage Show local usage estimates
38
+ review Run configured local review checks
39
+ tools list|make|show|use Manage built-in and toolbox tools
40
+ permissions list Show permission policy rules
41
+ config edit [--workspace] Open settings in $EDITOR
42
+ ide connect Connect or inspect local IDE integration
43
+ mcp add|list|doctor|approve|oauth
44
+ Manage MCP server settings
45
+ skill list|show Inspect discovered Coven Code agent skills
46
+ plugins list|reload Show and reload project and user plugin files
47
+ threads list|show|search|archive|visibility|continue|map|report
48
+ Manage local thread records
49
+ agents-md list Show AGENTS.md guidance files used for this cwd
50
+ agents list Alias for agents-md list
51
+
52
+ TUI lane commands:
53
+ /lane refresh Refresh worktree, branch, changed files, and diff summary
54
+ /lane harness <name|next> Select smart, deep, rush, or large for the lane
55
+ /lane verify Run the detected verification command for the lane
56
+ /lane status|diff Show lane status or diff summary
57
+ `);
58
+ }