@opencoven/coven-code 0.0.3 → 0.0.6

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 (115) hide show
  1. package/README.md +29 -130
  2. package/bin/coven-code +26 -0
  3. package/install.js +117 -0
  4. package/package.json +26 -23
  5. package/bin/coven-code-sdk.mjs +0 -12
  6. package/bin/coven-code.mjs +0 -19
  7. package/docs/CLI.md +0 -256
  8. package/docs/CONFIGURATION.md +0 -107
  9. package/docs/DEMO.md +0 -453
  10. package/docs/DEVELOPMENT.md +0 -104
  11. package/docs/DOGFOOD-PROTOCOL.md +0 -263
  12. package/docs/MCP-SKILLS-PLUGINS.md +0 -127
  13. package/docs/README.md +0 -39
  14. package/docs/RELEASE.md +0 -33
  15. package/docs/SDK.md +0 -107
  16. package/docs/superpowers/plans/2026-05-25-coven-code-panel-tui.md +0 -904
  17. package/docs/superpowers/plans/2026-05-25-coven-code-rebrand.md +0 -670
  18. package/docs/superpowers/specs/2026-05-25-coven-code-panel-tui-design.md +0 -235
  19. package/docs/superpowers/specs/2026-05-26-slash-first-tui-review.md +0 -63
  20. package/src/agent/fixture.mjs +0 -95
  21. package/src/agent/lane.mjs +0 -136
  22. package/src/cli/dispatch.mjs +0 -66
  23. package/src/cli/execute.mjs +0 -590
  24. package/src/cli/help.mjs +0 -58
  25. package/src/cli/interactive-core.mjs +0 -28
  26. package/src/cli/interactive-io.mjs +0 -101
  27. package/src/cli/interactive-slash.mjs +0 -184
  28. package/src/cli/notifications.mjs +0 -13
  29. package/src/cli/parse.mjs +0 -83
  30. package/src/cli/reasoning.mjs +0 -45
  31. package/src/cli/refs.mjs +0 -162
  32. package/src/cli/repl.mjs +0 -60
  33. package/src/cli/slash-commands.mjs +0 -375
  34. package/src/cli/stream-json.mjs +0 -116
  35. package/src/cli/tui-actions.mjs +0 -72
  36. package/src/cli/tui-blessed.mjs +0 -198
  37. package/src/cli/tui-keys.mjs +0 -80
  38. package/src/cli/tui-lane.mjs +0 -73
  39. package/src/cli/tui-render.mjs +0 -169
  40. package/src/cli/tui-submit.mjs +0 -82
  41. package/src/cli/tui.mjs +0 -174
  42. package/src/commands/agents.mjs +0 -53
  43. package/src/commands/config.mjs +0 -27
  44. package/src/commands/ide.mjs +0 -17
  45. package/src/commands/login.mjs +0 -84
  46. package/src/commands/mcp.mjs +0 -176
  47. package/src/commands/permissions-eval.mjs +0 -122
  48. package/src/commands/permissions-rules.mjs +0 -53
  49. package/src/commands/permissions-text.mjs +0 -112
  50. package/src/commands/permissions.mjs +0 -62
  51. package/src/commands/plugins.mjs +0 -86
  52. package/src/commands/review.mjs +0 -74
  53. package/src/commands/skill.mjs +0 -23
  54. package/src/commands/threads.mjs +0 -165
  55. package/src/commands/tools.mjs +0 -77
  56. package/src/commands/update.mjs +0 -31
  57. package/src/commands/usage.mjs +0 -34
  58. package/src/constants.mjs +0 -52
  59. package/src/main.mjs +0 -87
  60. package/src/mcp/discover.mjs +0 -154
  61. package/src/mcp/local.mjs +0 -55
  62. package/src/mcp/parsers.mjs +0 -46
  63. package/src/mcp/permissions.mjs +0 -52
  64. package/src/mcp/probe.mjs +0 -85
  65. package/src/mcp/registry.mjs +0 -96
  66. package/src/mcp/remote-oauth.mjs +0 -55
  67. package/src/mcp/remote-session.mjs +0 -54
  68. package/src/mcp/remote-sse.mjs +0 -82
  69. package/src/mcp/remote.mjs +0 -74
  70. package/src/plugins/api.mjs +0 -187
  71. package/src/plugins/configuration.mjs +0 -124
  72. package/src/plugins/discover.mjs +0 -84
  73. package/src/plugins/helpers.mjs +0 -187
  74. package/src/plugins/subsystems.mjs +0 -198
  75. package/src/plugins/validators.mjs +0 -142
  76. package/src/sdk-execute.mjs +0 -82
  77. package/src/sdk-install.mjs +0 -187
  78. package/src/sdk-settings.mjs +0 -88
  79. package/src/sdk.mjs +0 -163
  80. package/src/settings/load.mjs +0 -134
  81. package/src/settings/paths.mjs +0 -101
  82. package/src/skills/builtin/building-skills/SKILL.md +0 -20
  83. package/src/skills/discover.mjs +0 -95
  84. package/src/threads/store.mjs +0 -176
  85. package/src/tools/builtin/bash.mjs +0 -110
  86. package/src/tools/builtin/create-file.mjs +0 -66
  87. package/src/tools/builtin/edit-file.mjs +0 -76
  88. package/src/tools/builtin/finder.mjs +0 -73
  89. package/src/tools/builtin/glob.mjs +0 -74
  90. package/src/tools/builtin/grep.mjs +0 -82
  91. package/src/tools/builtin/index.mjs +0 -83
  92. package/src/tools/builtin/librarian.mjs +0 -97
  93. package/src/tools/builtin/look-at.mjs +0 -92
  94. package/src/tools/builtin/mcp.mjs +0 -51
  95. package/src/tools/builtin/mermaid.mjs +0 -59
  96. package/src/tools/builtin/oracle.mjs +0 -56
  97. package/src/tools/builtin/painter.mjs +0 -81
  98. package/src/tools/builtin/plugin-tool.mjs +0 -53
  99. package/src/tools/builtin/read-mcp-resource.mjs +0 -63
  100. package/src/tools/builtin/read-web-page.mjs +0 -72
  101. package/src/tools/builtin/read.mjs +0 -59
  102. package/src/tools/builtin/runtime-content.mjs +0 -31
  103. package/src/tools/builtin/runtime-decisions.mjs +0 -115
  104. package/src/tools/builtin/runtime.mjs +0 -85
  105. package/src/tools/builtin/task.mjs +0 -63
  106. package/src/tools/builtin/toolbox-tool.mjs +0 -57
  107. package/src/tools/builtin/undo-edit.mjs +0 -97
  108. package/src/tools/builtin/web-search.mjs +0 -128
  109. package/src/tools/toolbox.mjs +0 -273
  110. package/src/util/fs.mjs +0 -13
  111. package/src/util/glob.mjs +0 -46
  112. package/src/util/html.mjs +0 -21
  113. package/src/util/media.mjs +0 -13
  114. package/src/util/shell.mjs +0 -24
  115. package/src/util/table.mjs +0 -11
@@ -1,198 +0,0 @@
1
- import { CLI_NAME } from '../constants.mjs';
2
- import { runInteractive } from './repl.mjs';
3
- import { formatSlashCommandDetails } from './slash-commands.mjs';
4
- import {
5
- PALETTE_ACTIONS,
6
- currentSlashMatches,
7
- renderCompactStatus,
8
- renderComposerLines,
9
- renderTabContent,
10
- renderTabLine,
11
- } from './tui-render.mjs';
12
-
13
- export async function runLiveTui(model, session, handleKey) {
14
- try {
15
- const blessedModule = await import('neo-blessed');
16
- return runBlessedTui(blessedModule.default ?? blessedModule, model, session, handleKey);
17
- } catch (error) {
18
- console.error(`${CLI_NAME}: unable to start panel TUI, falling back to classic REPL: ${error?.message ?? error}`);
19
- return runInteractive(session.parsed, '');
20
- }
21
- }
22
-
23
- function runBlessedTui(blessed, model, session, handleKey) {
24
- return new Promise((resolve) => {
25
- const screen = blessed.screen({
26
- smartCSR: true,
27
- fullUnicode: true,
28
- title: `${CLI_NAME} ${model.version}`,
29
- });
30
- const header = blessed.box({
31
- top: 0,
32
- left: 0,
33
- width: '100%',
34
- height: 4,
35
- padding: { left: 1, right: 1 },
36
- style: { fg: 'white', bg: 'black' },
37
- });
38
- const transcript = blessed.box({
39
- top: 4,
40
- left: 0,
41
- width: '100%',
42
- bottom: 4,
43
- scrollable: true,
44
- alwaysScroll: true,
45
- keys: true,
46
- vi: true,
47
- padding: { left: 1, right: 1 },
48
- scrollbar: { ch: ' ', style: { bg: 'white' } },
49
- });
50
- const composer = blessed.box({
51
- bottom: 1,
52
- left: 0,
53
- width: '100%',
54
- height: 3,
55
- label: ' message ',
56
- border: 'line',
57
- padding: { left: 1, right: 1 },
58
- style: {
59
- border: { fg: 'green' },
60
- },
61
- });
62
- const status = blessed.box({
63
- bottom: 0,
64
- left: 0,
65
- width: '100%',
66
- height: 1,
67
- style: { fg: 'white', bg: 'black' },
68
- });
69
- const palette = blessed.list({
70
- top: 'center',
71
- left: 'center',
72
- width: '60%',
73
- height: Math.min(13, PALETTE_ACTIONS.length + 2),
74
- label: ' Command Palette ',
75
- border: 'line',
76
- hidden: true,
77
- keys: true,
78
- mouse: true,
79
- items: PALETTE_ACTIONS.map(([label, command]) => `${label} ${command}`),
80
- style: {
81
- border: { fg: 'yellow' },
82
- selected: { bg: 'blue', fg: 'white' },
83
- },
84
- });
85
- const slashList = blessed.list({
86
- bottom: 4,
87
- left: 0,
88
- width: '36%',
89
- height: '50%',
90
- label: ' Slash commands ',
91
- border: 'line',
92
- hidden: true,
93
- keys: true,
94
- mouse: true,
95
- style: {
96
- border: { fg: 'cyan' },
97
- selected: { bg: 'blue', fg: 'white' },
98
- },
99
- });
100
- const slashDetails = blessed.box({
101
- bottom: 4,
102
- right: 0,
103
- width: '64%',
104
- height: '50%',
105
- label: ' Details ',
106
- border: 'line',
107
- hidden: true,
108
- padding: { left: 1, right: 1 },
109
- style: {
110
- border: { fg: 'cyan' },
111
- },
112
- });
113
-
114
- screen.append(header);
115
- screen.append(transcript);
116
- screen.append(composer);
117
- screen.append(status);
118
- screen.append(palette);
119
- screen.append(slashList);
120
- screen.append(slashDetails);
121
-
122
- let settled = false;
123
- const cleanup = () => {
124
- if (settled) return;
125
- settled = true;
126
- screen.destroy();
127
- resolve();
128
- };
129
- const sync = () => {
130
- const width = Number(screen.width) || 80;
131
- header.setContent(`Coven Code ${model.version}\n${model.cwd}\n${renderTabLine(model)} mode: ${model.mode} effort: ${model.reasoningEffort}`);
132
- transcript.setContent(renderTabContent(model, Math.max(1, Number(transcript.height) - 2), Math.max(20, width - 4)).join('\n'));
133
- composer.setContent(renderComposerLines(model, width - 4, { cursor: true }).join('\n'));
134
- status.setContent(renderCompactStatus(model));
135
- if (model.paletteOpen) {
136
- palette.show();
137
- palette.select(model.paletteIndex);
138
- palette.focus();
139
- } else {
140
- palette.hide();
141
- }
142
- if (model.slashOpen) {
143
- const matches = currentSlashMatches(model);
144
- const selected = matches[model.slashIndex] ?? matches[0];
145
- slashList.setItems(matches.map((entry) => `${entry.command} ${entry.title}`));
146
- slashList.show();
147
- slashList.select(model.slashIndex);
148
- slashDetails.setContent(formatSlashCommandDetails(selected).join('\n'));
149
- slashDetails.show();
150
- } else {
151
- slashList.hide();
152
- slashDetails.hide();
153
- }
154
- screen.render();
155
- };
156
- const dispatchKey = async (key) => {
157
- await handleKey(model, session, normalizeBlessedKey(key));
158
- sync();
159
- if (model.status === 'done') cleanup();
160
- };
161
-
162
- screen.on('keypress', async (chunk, key = {}) => {
163
- if (settled) return;
164
- const normalized = normalizeBlessedKey({
165
- ...key,
166
- sequence: isPrintableChunk(chunk, key) ? chunk : key.sequence,
167
- });
168
- await dispatchKey(normalized);
169
- });
170
-
171
- screen.on('resize', sync);
172
- sync();
173
- });
174
- }
175
-
176
- function normalizeBlessedKey(key = {}) {
177
- if (key.name === 'return') return { ...key, name: 'enter' };
178
- return key;
179
- }
180
-
181
- export function isPrintableKey(key = {}) {
182
- return isPrintableChunk(key.sequence, key);
183
- }
184
-
185
- function isPrintableChunk(chunk, key = {}) {
186
- return typeof chunk === 'string'
187
- && chunk.length > 0
188
- && !key.ctrl
189
- && !key.meta
190
- && key.name !== 'return'
191
- && key.name !== 'enter'
192
- && key.name !== 'tab'
193
- && key.name !== 'escape'
194
- && key.name !== 'up'
195
- && key.name !== 'down'
196
- && key.name !== 'left'
197
- && key.name !== 'right';
198
- }
@@ -1,80 +0,0 @@
1
- import { isPrintableKey } from './tui-blessed.mjs';
2
- import {
3
- acceptSlashSelection,
4
- closeSlashMenu,
5
- completeSlashSelection,
6
- deleteComposerText,
7
- insertComposerText,
8
- } from './tui-actions.mjs';
9
- import { PALETTE_ACTIONS } from './tui-render.mjs';
10
- import { submitTuiText } from './tui-submit.mjs';
11
-
12
- export function handleComposerKey(model, key) {
13
- if (isPrintableKey(key)) {
14
- insertComposerText(model, key.sequence);
15
- return true;
16
- }
17
- if (key.name === 'backspace' || key.name === 'delete') {
18
- deleteComposerText(model, key.name);
19
- return true;
20
- }
21
- if (key.name === 'left') {
22
- model.composerCursor = Math.max(0, model.composerCursor - 1);
23
- return true;
24
- }
25
- if (key.name === 'right') {
26
- model.composerCursor = Math.min(model.composer.length, model.composerCursor + 1);
27
- return true;
28
- }
29
- if (key.name === 'home') {
30
- model.composerCursor = 0;
31
- return true;
32
- }
33
- if (key.name === 'end') {
34
- model.composerCursor = model.composer.length;
35
- return true;
36
- }
37
- return false;
38
- }
39
-
40
- export async function handleSlashMenuKey(model, session, key) {
41
- if (key.name === 'escape') {
42
- closeSlashMenu(model);
43
- return true;
44
- }
45
- if (key.name === 'down') {
46
- model.slashIndex = (model.slashIndex + 1) % Math.max(1, model.slashMatches.length);
47
- return true;
48
- }
49
- if (key.name === 'up') {
50
- model.slashIndex = (model.slashIndex + Math.max(1, model.slashMatches.length) - 1) % Math.max(1, model.slashMatches.length);
51
- return true;
52
- }
53
- if (key.name === 'tab') {
54
- completeSlashSelection(model);
55
- return true;
56
- }
57
- if (key.name === 'enter') {
58
- await acceptSlashSelection(model, session);
59
- return true;
60
- }
61
- return false;
62
- }
63
-
64
- export async function handlePaletteKey(model, session, key) {
65
- if (key.name === 'enter') {
66
- const [, command] = PALETTE_ACTIONS[model.paletteIndex ?? 0] ?? PALETTE_ACTIONS[0];
67
- model.paletteOpen = false;
68
- await submitTuiText(model, session, command);
69
- return true;
70
- }
71
- if (key.name === 'down') {
72
- model.paletteIndex = (model.paletteIndex + 1) % PALETTE_ACTIONS.length;
73
- return true;
74
- }
75
- if (key.name === 'up') {
76
- model.paletteIndex = (model.paletteIndex + PALETTE_ACTIONS.length - 1) % PALETTE_ACTIONS.length;
77
- return true;
78
- }
79
- return false;
80
- }
@@ -1,73 +0,0 @@
1
- import { CLI_NAME } from '../constants.mjs';
2
- import {
3
- inspectLane,
4
- nextLaneHarness,
5
- normalizeLaneHarness,
6
- runLaneVerification,
7
- } from '../agent/lane.mjs';
8
- import { splitShellWords } from '../util/shell.mjs';
9
- import { renderLaneLines } from './tui-render.mjs';
10
-
11
- export function isLaneCommand(text) {
12
- return /^\/lane(?:\s|$)/.test(text);
13
- }
14
-
15
- export async function handleTuiLaneCommand(model, session, text) {
16
- try {
17
- const [, subcommand = 'status', ...rest] = splitShellWords(text.slice(1));
18
- if (subcommand === 'refresh') {
19
- const inspector = session.laneInspector ?? inspectLane;
20
- model.lane = await inspector({
21
- cwd: process.cwd(),
22
- harness: model.lane.harness,
23
- verification: model.lane.verification,
24
- });
25
- model.activeTab = 'lane';
26
- return laneCommandResult(`lane refreshed: ${model.lane.branch}`);
27
- }
28
- if (subcommand === 'harness') {
29
- const requested = rest[0] === 'next' ? nextLaneHarness(model.lane.harness) : rest[0];
30
- const harness = normalizeLaneHarness(requested);
31
- model.lane = { ...model.lane, harness };
32
- model.activeTab = 'lane';
33
- return laneCommandResult(`harness: ${harness}`);
34
- }
35
- if (subcommand === 'verify') {
36
- const verifier = session.laneVerifier ?? runLaneVerification;
37
- model.lane = await verifier(model.lane);
38
- model.activeTab = 'lane';
39
- return laneCommandResult(`verification: ${model.lane.verification.status}`);
40
- }
41
- if (subcommand === 'diff') {
42
- model.activeTab = 'lane';
43
- return laneCommandResult(model.lane.diffSummary || 'no diff summary');
44
- }
45
- if (subcommand === 'status') {
46
- model.activeTab = 'lane';
47
- return laneCommandResult(renderLaneLines(model, 40, 120).join('\n'));
48
- }
49
- return laneCommandResult(`${CLI_NAME}: Unknown lane command: ${subcommand}`, 'error');
50
- } catch (error) {
51
- return laneCommandResult(`${CLI_NAME}: ${error?.message ?? error}`, 'error');
52
- }
53
- }
54
-
55
- function laneCommandResult(text, kind = 'command') {
56
- return {
57
- result: { kind, lines: [text] },
58
- stdout: '',
59
- stderr: '',
60
- };
61
- }
62
-
63
- export function rememberLaneTerminal(model, stdout, stderr, resultLines = []) {
64
- const lines = [stdout, stderr, resultLines.join('\n')]
65
- .flatMap((text) => String(text ?? '').split(/\r?\n/))
66
- .map((line) => line.trimEnd())
67
- .filter(Boolean);
68
- if (lines.length === 0) return;
69
- model.lane = {
70
- ...model.lane,
71
- terminalLines: [...(model.lane.terminalLines ?? []), ...lines].slice(-40),
72
- };
73
- }
@@ -1,169 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { readEffectiveSettings } from '../settings/load.mjs';
3
- import { findWorkspaceSettingsFile, settingsFile } from '../settings/paths.mjs';
4
- import { listThreads } from '../threads/store.mjs';
5
- import {
6
- buildStaticSlashCommandCatalog,
7
- builtinToolSummaryLines,
8
- filterSlashCommands,
9
- formatSlashCommandDetails,
10
- formatSlashHelpLines,
11
- } from './slash-commands.mjs';
12
-
13
- export const TABS = ['chat', 'lane', 'tools', 'threads', 'config', 'help'];
14
-
15
- export const PALETTE_ACTIONS = [
16
- ['New thread', '/new'],
17
- ['Continue latest thread', '/continue'],
18
- ['Refresh lane', '/lane refresh'],
19
- ['Cycle harness', '/lane harness next'],
20
- ['Run verification', '/lane verify'],
21
- ['Open help', '/help'],
22
- ['List tools', '/tools list'],
23
- ['List skills', '/skill: list'],
24
- ['List plugins', '/plugins: list'],
25
- ['Open editor', '/editor'],
26
- ['Edit previous prompt', '/edit'],
27
- ['Archive and quit', '/thread: archive and quit'],
28
- ];
29
-
30
- export function renderTabContent(model, limit, width) {
31
- if (model.activeTab === 'help') return clipLines(formatSlashHelpLines(model.slashCatalog), limit, width);
32
- if (model.activeTab === 'lane') return renderLaneLines(model, limit, width);
33
- if (model.activeTab === 'tools') return clipLines(model.panels.tools, limit, width);
34
- if (model.activeTab === 'threads') return clipLines(model.panels.threads, limit, width);
35
- if (model.activeTab === 'config') return clipLines(model.panels.config, limit, width);
36
- return renderTranscript(model, limit, width);
37
- }
38
-
39
- export function renderTranscript(model, limit, width) {
40
- const entries = model.transcript.slice(-Math.max(1, limit)).flatMap((entry) => [
41
- `${entry.role}:`,
42
- ...String(entry.text).split(/\r?\n/),
43
- ]);
44
- return clipLines(entries.length > 0 ? entries.slice(-limit) : ['Ready. Type a prompt or /help.'], limit, width);
45
- }
46
-
47
- export function renderComposerLines(model, width, { cursor = false } = {}) {
48
- const prompt = model.composer || '';
49
- const text = cursor ? insertComposerCursor(prompt, model.composerCursor ?? prompt.length) : prompt;
50
- const lines = String(text).split('\n');
51
- return lines.map((line, index) => `${index === 0 ? '> ' : ' '}${line}`.slice(0, width));
52
- }
53
-
54
- function insertComposerCursor(prompt, cursor) {
55
- const position = Math.max(0, Math.min(prompt.length, cursor));
56
- const before = prompt.slice(0, position);
57
- const at = prompt.slice(position, position + 1);
58
- const after = prompt.slice(position + 1);
59
- if (at === '\n' || at === '') return `${before}█${at}${after}`;
60
- return `${before}█${after}`;
61
- }
62
-
63
- export function renderSlashOverlay(model, width, limit) {
64
- const listWidth = Math.min(34, Math.floor(width * 0.38));
65
- const detailWidth = width - listWidth - 3;
66
- const matches = currentSlashMatches(model);
67
- const selected = matches[model.slashIndex] ?? matches[0];
68
- const listLines = ['Slash commands', ...matches.map((entry, index) => {
69
- const marker = index === model.slashIndex ? '>' : ' ';
70
- const status = entry.availability?.type === 'disabled' ? ' disabled' : '';
71
- return `${marker} ${entry.command}${status}`;
72
- })];
73
- const detailLines = ['Details', ...formatSlashCommandDetails(selected)];
74
- const count = Math.min(limit, Math.max(listLines.length, detailLines.length));
75
- const rows = [];
76
- for (let index = 0; index < count; index += 1) {
77
- const left = (listLines[index] ?? '').padEnd(listWidth).slice(0, listWidth);
78
- const right = (detailLines[index] ?? '').slice(0, detailWidth);
79
- rows.push(`${left} | ${right}`);
80
- }
81
- return rows;
82
- }
83
-
84
- export function renderCompactStatus(model) {
85
- return [
86
- `thread: ${shortThreadId(model.threadId)}`,
87
- `lane: ${model.lane.branch}`,
88
- `harness: ${model.lane.harness}`,
89
- `queued: ${model.queueCount}`,
90
- `tools: ${model.toolCount}`,
91
- `tab: ${model.activeTab}`,
92
- `status: ${model.status}`,
93
- ].join(' | ');
94
- }
95
-
96
- export function buildPanelSummaries(parsed = {}, slashCatalog = buildStaticSlashCommandCatalog(), cwd = process.cwd()) {
97
- const threads = listThreads().slice(0, 8);
98
- const latest = threads
99
- .filter((thread) => !thread.archived)
100
- .sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))[0];
101
- const settingsPath = settingsFile(parsed);
102
- const workspacePath = findWorkspaceSettingsFile(cwd);
103
- const settings = readEffectiveSettings(parsed, { cwd });
104
- return {
105
- tools: [
106
- ...builtinToolSummaryLines(),
107
- '',
108
- 'Slash command sources:',
109
- `commands: ${slashCatalog.length}`,
110
- ],
111
- threads: [
112
- `Recent threads: ${threads.length}`,
113
- `Latest: ${latest?.id ?? 'none'}`,
114
- ...threads.map((thread) => `${thread.id} ${thread.archived ? 'archived' : 'active'} ${thread.title}`),
115
- ],
116
- config: [
117
- 'Settings',
118
- `user: ${settingsPath}${existsSync(settingsPath) ? '' : ' (not created)'}`,
119
- `workspace: ${workspacePath ?? 'none'}`,
120
- `visibility: ${settings['covenCode.defaultVisibility'] ?? 'private'}`,
121
- `updates: ${settings['covenCode.updates.mode'] ?? 'default'}`,
122
- ],
123
- };
124
- }
125
-
126
- export function renderTabLine(model) {
127
- return TABS.map((tab) => tab === model.activeTab ? `[${tab}]` : ` ${tab} `).join(' ');
128
- }
129
-
130
- function clipLines(lines, limit, width) {
131
- return lines
132
- .slice(0, Math.max(0, limit))
133
- .map((line) => String(line).slice(0, width));
134
- }
135
-
136
- function shortThreadId(threadId) {
137
- if (!threadId || threadId === 'new thread') return 'new';
138
- return String(threadId).slice(0, 14);
139
- }
140
-
141
- export function renderLaneLines(model, limit, width) {
142
- const lane = model.lane;
143
- const changedFiles = lane.changedFiles.length > 0 ? lane.changedFiles : ['none'];
144
- const lines = [
145
- `worktree: ${lane.worktree}`,
146
- `branch: ${lane.branch}`,
147
- `base: ${lane.baseBranch}`,
148
- `harness: ${lane.harness}`,
149
- `status: ${lane.status}`,
150
- `verify: ${lane.verification.status} (${lane.verification.command})`,
151
- `PR: ${lane.pullRequest}`,
152
- `merge: ${lane.merge}`,
153
- `cleanup: ${lane.cleanup}`,
154
- '',
155
- 'Changed files',
156
- ...changedFiles.map((file) => ` ${file}`),
157
- '',
158
- 'Diff',
159
- lane.diffSummary || ' no diff summary',
160
- '',
161
- 'Terminal',
162
- ...(lane.terminalLines.length > 0 ? lane.terminalLines : [' no lane terminal output yet']),
163
- ];
164
- return lines.slice(0, limit).map((line) => line.slice(0, width));
165
- }
166
-
167
- export function currentSlashMatches(model) {
168
- return model.slashMatches.length > 0 ? model.slashMatches : filterSlashCommands(model.slashCatalog, model.slashQuery);
169
- }
@@ -1,82 +0,0 @@
1
- import { handleInteractiveInput } from './interactive-core.mjs';
2
- import { buildPanelSummaries } from './tui-render.mjs';
3
- import { handleTuiLaneCommand, isLaneCommand, rememberLaneTerminal } from './tui-lane.mjs';
4
-
5
- export async function submitTuiText(model, session, text) {
6
- if (!text) return;
7
- model.transcript.push({ role: 'you', text });
8
- model.status = 'running';
9
- let result;
10
- let stdout = '';
11
- let stderr = '';
12
- if (isLaneCommand(text)) {
13
- ({ result, stdout, stderr } = await handleTuiLaneCommand(model, session, text));
14
- } else {
15
- const priorThreadId = session.thread?.id;
16
- const priorMessageCount = session.thread?.messages?.length ?? 0;
17
- ({ result, stderr } = await captureTerminalOutput(() => handleInteractiveInput(session, text)));
18
- stdout = newAssistantText(session.thread, priorThreadId, priorMessageCount);
19
- }
20
- model.mode = session.parsed.mode;
21
- model.reasoningEffort = session.parsed.reasoningEffort ?? model.reasoningEffort;
22
- model.threadId = session.thread?.id ?? 'new thread';
23
- model.queueCount = session.queuedMessages.length;
24
- rememberLaneTerminal(model, stdout, stderr, result.lines);
25
- model.panels = buildPanelSummaries(session.parsed, model.slashCatalog, model.workspaceCwd);
26
- if (stderr.trim()) {
27
- model.transcript.push({ role: 'error', text: stderr.trim() });
28
- }
29
- if (stdout.trim()) {
30
- model.transcript.push({ role: 'coven', text: stdout.trim() });
31
- }
32
- if (result.lines.length > 0) {
33
- model.transcript.push({
34
- role: result.kind === 'error' ? 'error' : 'coven',
35
- text: result.lines.join('\n'),
36
- });
37
- }
38
- model.status = result.kind === 'exit' ? 'done' : 'idle';
39
- }
40
-
41
- function newAssistantText(thread, priorThreadId, priorMessageCount) {
42
- if (!thread?.messages?.length) return '';
43
- const startIndex = thread.id === priorThreadId ? priorMessageCount : 0;
44
- return thread.messages
45
- .slice(startIndex)
46
- .filter((message) => message.role === 'assistant' && typeof message.content === 'string')
47
- .map((message) => message.content.trim())
48
- .filter(Boolean)
49
- .join('\n');
50
- }
51
-
52
- async function captureTerminalOutput(fn) {
53
- let stderr = '';
54
- const originalStdoutWrite = process.stdout.write;
55
- const originalStderrWrite = process.stderr.write;
56
- process.stdout.write = function tuiStdoutWrite(chunk, encoding, callback) {
57
- callWriteCallback(encoding, callback);
58
- return true;
59
- };
60
- process.stderr.write = function tuiStderrWrite(chunk, encoding, callback) {
61
- stderr += normalizeWriteChunk(chunk, encoding);
62
- callWriteCallback(encoding, callback);
63
- return true;
64
- };
65
- try {
66
- const result = await fn();
67
- return { result, stderr };
68
- } finally {
69
- process.stdout.write = originalStdoutWrite;
70
- process.stderr.write = originalStderrWrite;
71
- }
72
- }
73
-
74
- function normalizeWriteChunk(chunk, encoding) {
75
- if (Buffer.isBuffer(chunk)) return chunk.toString(typeof encoding === 'string' ? encoding : 'utf8');
76
- return String(chunk);
77
- }
78
-
79
- function callWriteCallback(encoding, callback) {
80
- if (typeof encoding === 'function') encoding();
81
- else if (typeof callback === 'function') callback();
82
- }