@sylix/coworker 1.3.0 → 1.4.0

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 (84) hide show
  1. package/dist/cli/index.d.ts.map +1 -1
  2. package/dist/cli/index.js +184 -292
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/slash/advanced.d.ts +3 -0
  5. package/dist/commands/slash/advanced.d.ts.map +1 -0
  6. package/dist/commands/slash/advanced.js +225 -0
  7. package/dist/commands/slash/advanced.js.map +1 -0
  8. package/dist/commands/slash/config.d.ts +3 -0
  9. package/dist/commands/slash/config.d.ts.map +1 -0
  10. package/dist/commands/slash/config.js +161 -0
  11. package/dist/commands/slash/config.js.map +1 -0
  12. package/dist/commands/slash/context.d.ts +3 -0
  13. package/dist/commands/slash/context.d.ts.map +1 -0
  14. package/dist/commands/slash/context.js +127 -0
  15. package/dist/commands/slash/context.js.map +1 -0
  16. package/dist/commands/slash/core.d.ts +3 -0
  17. package/dist/commands/slash/core.d.ts.map +1 -0
  18. package/dist/commands/slash/core.js +112 -0
  19. package/dist/commands/slash/core.js.map +1 -0
  20. package/dist/commands/slash/developer.d.ts +3 -0
  21. package/dist/commands/slash/developer.d.ts.map +1 -0
  22. package/dist/commands/slash/developer.js +174 -0
  23. package/dist/commands/slash/developer.js.map +1 -0
  24. package/dist/commands/slash/files.d.ts +3 -0
  25. package/dist/commands/slash/files.d.ts.map +1 -0
  26. package/dist/commands/slash/files.js +216 -0
  27. package/dist/commands/slash/files.js.map +1 -0
  28. package/dist/commands/slash/registry.d.ts +36 -0
  29. package/dist/commands/slash/registry.d.ts.map +1 -0
  30. package/dist/commands/slash/registry.js +69 -0
  31. package/dist/commands/slash/registry.js.map +1 -0
  32. package/dist/commands/slash/session.d.ts +3 -0
  33. package/dist/commands/slash/session.d.ts.map +1 -0
  34. package/dist/commands/slash/session.js +144 -0
  35. package/dist/commands/slash/session.js.map +1 -0
  36. package/dist/core/CoWorkerAgent.d.ts +8 -5
  37. package/dist/core/CoWorkerAgent.d.ts.map +1 -1
  38. package/dist/core/CoWorkerAgent.js +171 -52
  39. package/dist/core/CoWorkerAgent.js.map +1 -1
  40. package/dist/session/SessionManager.js +10 -5
  41. package/dist/session/SessionManager.js.map +1 -1
  42. package/dist/skills/HookAndSkillManager.d.ts +7 -2
  43. package/dist/skills/HookAndSkillManager.d.ts.map +1 -1
  44. package/dist/skills/HookAndSkillManager.js +35 -8
  45. package/dist/skills/HookAndSkillManager.js.map +1 -1
  46. package/dist/skills/defaults/brainstorming.md +43 -0
  47. package/dist/skills/defaults/commit.md +37 -0
  48. package/dist/skills/defaults/executing-plans.md +41 -0
  49. package/dist/skills/defaults/finishing-a-development-branch.md +35 -0
  50. package/dist/skills/defaults/receiving-code-review.md +40 -0
  51. package/dist/skills/defaults/requesting-code-review.md +43 -0
  52. package/dist/skills/defaults/review.md +50 -0
  53. package/dist/skills/defaults/subagent-driven-development.md +55 -0
  54. package/dist/skills/defaults/systematic-debugging.md +93 -0
  55. package/dist/skills/defaults/test-driven-development.md +97 -0
  56. package/dist/skills/defaults/using-git-worktrees.md +36 -0
  57. package/dist/skills/defaults/verification-before-completion.md +47 -0
  58. package/dist/skills/defaults/writing-plans.md +84 -0
  59. package/dist/skills/defaults/writing-skills.md +46 -0
  60. package/dist/tools/NativeTools.d.ts +0 -4
  61. package/dist/tools/NativeTools.d.ts.map +1 -1
  62. package/dist/tools/NativeTools.js +50 -10
  63. package/dist/tools/NativeTools.js.map +1 -1
  64. package/dist/utils/conversations.d.ts +14 -0
  65. package/dist/utils/conversations.d.ts.map +1 -0
  66. package/dist/utils/conversations.js +100 -0
  67. package/dist/utils/conversations.js.map +1 -0
  68. package/dist/utils/inputbar.d.ts +87 -0
  69. package/dist/utils/inputbar.d.ts.map +1 -0
  70. package/dist/utils/inputbar.js +263 -0
  71. package/dist/utils/inputbar.js.map +1 -0
  72. package/dist/utils/output.d.ts +48 -42
  73. package/dist/utils/output.d.ts.map +1 -1
  74. package/dist/utils/output.js +245 -186
  75. package/dist/utils/output.js.map +1 -1
  76. package/dist/utils/palette.d.ts +25 -0
  77. package/dist/utils/palette.d.ts.map +1 -0
  78. package/dist/utils/palette.js +92 -0
  79. package/dist/utils/palette.js.map +1 -0
  80. package/dist/utils/welcome.d.ts +2 -0
  81. package/dist/utils/welcome.d.ts.map +1 -0
  82. package/dist/utils/welcome.js +130 -0
  83. package/dist/utils/welcome.js.map +1 -0
  84. package/package.json +2 -2
@@ -33,14 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.Timer = exports.ThinkingAnimation = exports.theme = exports.CW_VERSION = void 0;
36
+ exports.Timer = exports.StreamRenderer = exports.ThinkingAnimation = exports.theme = exports.CW_VERSION = void 0;
37
37
  exports.getTermWidth = getTermWidth;
38
38
  exports.showBanner = showBanner;
39
39
  exports.showStaticBanner = showStaticBanner;
40
40
  exports.showSessionInfo = showSessionInfo;
41
- exports.renderMarkdown = renderMarkdown;
42
41
  exports.printResponseHeader = printResponseHeader;
43
- exports.printResponseLine = printResponseLine;
44
42
  exports.printResponseFooter = printResponseFooter;
45
43
  exports.streamResponseChunk = streamResponseChunk;
46
44
  exports.printUserPrompt = printUserPrompt;
@@ -54,24 +52,21 @@ exports.printToolError = printToolError;
54
52
  exports.printScoreCard = printScoreCard;
55
53
  exports.printCommitBox = printCommitBox;
56
54
  exports.printDiff = printDiff;
55
+ exports.checkWorkspaceTrust = checkWorkspaceTrust;
57
56
  exports.printHelp = printHelp;
58
57
  exports.printCompact = printCompact;
59
- exports.getSlashCompletions = getSlashCompletions;
60
- exports.renderSlashMenu = renderSlashMenu;
61
58
  exports.printBox = printBox;
59
+ exports.renderMarkdown = renderMarkdown;
62
60
  const chalk = __importStar(require("chalk"));
63
61
  /**
64
62
  * ═══════════════════════════════════════════════════════════════════
65
63
  * CoWorker v1.3.0 by Sylix — Premium Output System
66
64
  * ═══════════════════════════════════════════════════════════════════
67
65
  *
68
- * Full Claude Code-level terminal rendering:
69
- * - Animated line-by-line banner with word-by-word tagline
70
- * - Thinking animation with rotating messages
71
- * - Markdown-aware response rendering (code blocks, bold, inline code)
72
- * - Score cards, commit boxes, diff previews
66
+ * Premium terminal rendering with true streaming support.
67
+ * All response text renders inside ╷│╵ borders as it arrives.
73
68
  */
74
- exports.CW_VERSION = '1.3.0';
69
+ exports.CW_VERSION = '1.4.0';
75
70
  // ============================================================================
76
71
  // THEME SYSTEM
77
72
  // ============================================================================
@@ -88,16 +83,17 @@ function hex(color) {
88
83
  }
89
84
  }
90
85
  exports.theme = {
91
- brand: hex('#00D4FF'), // Electric cyan — Sylix brand
92
- accent: hex('#7C3AED'), // Deep purple — highlights
93
- success: hex('#10B981'), // Emerald green
94
- error: hex('#EF4444'), // Clean red
95
- warning: hex('#F59E0B'), // Amber
96
- ai: hex('#A78BFA'), // Soft purple — AI responses
97
- dim: hex('#6B7280'), // Gray — secondary info
98
- white: hex('#F9FAFB'), // Off white
99
- code: hex('#FCD34D'), // Yellow — code and commands
100
- purple: hex('#A78BFA'), // Violet for tagline
86
+ brand: hex('#00D4FF'),
87
+ accent: hex('#7C3AED'),
88
+ success: hex('#10B981'),
89
+ error: hex('#EF4444'),
90
+ warning: hex('#F59E0B'),
91
+ ai: hex('#A78BFA'),
92
+ dim: hex('#6B7280'),
93
+ border: hex('#4B5563'),
94
+ white: hex('#F9FAFB'),
95
+ code: hex('#FCD34D'),
96
+ purple: hex('#A78BFA'),
101
97
  };
102
98
  // ============================================================================
103
99
  // TERMINAL UTILS
@@ -105,9 +101,6 @@ exports.theme = {
105
101
  function getTermWidth() {
106
102
  return process.stdout.columns || 80;
107
103
  }
108
- function isNarrow() {
109
- return getTermWidth() < 60;
110
- }
111
104
  function sleep(ms) {
112
105
  return new Promise(r => setTimeout(r, ms));
113
106
  }
@@ -120,7 +113,7 @@ function showCursor() {
120
113
  process.stdout.write('\x1b[?25h');
121
114
  }
122
115
  // ============================================================================
123
- // PART 1: ANIMATED BANNER — Line-by-line slide-in, word-by-word tagline
116
+ // ANIMATED BANNER
124
117
  // ============================================================================
125
118
  const LOGO_LINES = [
126
119
  ' ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗██████╗ ',
@@ -130,29 +123,18 @@ const LOGO_LINES = [
130
123
  '╚██████╗╚██████╔╝╚███╔███╔╝╚██████╔╝██║ ██║██║ ██╗███████╗██║ ██║',
131
124
  ' ╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝',
132
125
  ];
133
- /**
134
- * Full animated startup banner for CoWorker v1.3.0.
135
- *
136
- * 1. Logo slides in line-by-line (60ms between lines)
137
- * 2. Tagline fades in word-by-word with semantic coloring
138
- * 3. Divider draws left-to-right (3ms/char)
139
- * 4. Info line fades in
140
- * 5. Session info appears with 50ms delay between lines
141
- * Total: under 1.5 seconds
142
- */
143
126
  async function showBanner(version, userEmail, model = 'helix-1.2') {
144
127
  if (IS_PIPE || process.env.CW_NO_BANNER === '1')
145
128
  return;
146
129
  hideCursor();
147
130
  console.log('');
148
131
  try {
149
- // Step 1: Logo — line by line, each types across rapidly
132
+ // Step 1: Logo — line by line, each types across
150
133
  for (const line of LOGO_LINES) {
151
134
  const chars = [...line];
152
135
  let buffer = '';
153
136
  for (let i = 0; i < chars.length; i++) {
154
137
  buffer += chars[i];
155
- // Batch 6 chars at a time for speed
156
138
  if (i % 6 === 5 || i === chars.length - 1) {
157
139
  process.stdout.write(exports.theme.brand(buffer));
158
140
  buffer = '';
@@ -160,9 +142,9 @@ async function showBanner(version, userEmail, model = 'helix-1.2') {
160
142
  }
161
143
  }
162
144
  process.stdout.write('\n');
163
- await sleep(60); // 60ms between lines
145
+ await sleep(60);
164
146
  }
165
- // Step 2: Tagline — word by word, each with its own color
147
+ // Step 2: Tagline — word by word with semantic colors
166
148
  console.log('');
167
149
  const taglineWords = [
168
150
  { text: 'by', color: exports.theme.dim },
@@ -179,7 +161,7 @@ async function showBanner(version, userEmail, model = 'helix-1.2') {
179
161
  await sleep(100);
180
162
  }
181
163
  console.log('');
182
- // Step 3: Divider draws left to right, char by char
164
+ // Step 3: Divider draws left to right
183
165
  console.log('');
184
166
  const lineWidth = Math.min(getTermWidth() - 4, 60);
185
167
  process.stdout.write(' ');
@@ -189,7 +171,7 @@ async function showBanner(version, userEmail, model = 'helix-1.2') {
189
171
  await sleep(3);
190
172
  }
191
173
  console.log('');
192
- // Step 4: Info line — fades in all at once
174
+ // Step 4: Info line
193
175
  console.log('');
194
176
  const userStr = userEmail ? `Signed in as ${userEmail}` : 'Not signed in';
195
177
  console.log(` ${exports.theme.brand('◆')} ${exports.theme.dim(`${userStr} · Model: ${model} · v${version}`)}`);
@@ -199,9 +181,6 @@ async function showBanner(version, userEmail, model = 'helix-1.2') {
199
181
  showCursor();
200
182
  }
201
183
  }
202
- /**
203
- * Static banner for non-TTY / piped / quick modes.
204
- */
205
184
  function showStaticBanner(version) {
206
185
  console.log('');
207
186
  for (const line of LOGO_LINES) {
@@ -209,9 +188,6 @@ function showStaticBanner(version) {
209
188
  }
210
189
  console.log(exports.theme.dim(`\n by Sylix · v${version}\n`));
211
190
  }
212
- /**
213
- * Session info lines — appear with delay between each.
214
- */
215
191
  async function showSessionInfo(cwd, sessionId) {
216
192
  if (IS_PIPE) {
217
193
  console.log(` cwd: ${cwd}`);
@@ -231,7 +207,7 @@ async function showSessionInfo(cwd, sessionId) {
231
207
  console.log('');
232
208
  }
233
209
  // ============================================================================
234
- // PART 2: THINKING ANIMATION — Rotating messages with pulsing dots
210
+ // THINKING ANIMATION — Rotating messages with pulsing dots
235
211
  // ============================================================================
236
212
  const THINKING_MESSAGES = [
237
213
  'working',
@@ -239,8 +215,6 @@ const THINKING_MESSAGES = [
239
215
  'reading codebase',
240
216
  'analyzing',
241
217
  'on it',
242
- 'crafting something',
243
- 'processing',
244
218
  ];
245
219
  class ThinkingAnimation {
246
220
  constructor(customMessage) {
@@ -253,11 +227,10 @@ class ThinkingAnimation {
253
227
  return;
254
228
  hideCursor();
255
229
  this.interval = setInterval(() => {
256
- // Pulsing dots: working· working·· working··· working
257
230
  const dotCount = (this.frame % 4);
258
231
  const dots = '.'.repeat(dotCount);
259
- const pad = '.'.repeat(3 - dotCount);
260
- process.stdout.write(`\r ${exports.theme.brand('◆')} ${exports.theme.dim(this.message + dots)}${exports.theme.dim(pad)}` + ' '.repeat(10));
232
+ const pad = ' '.repeat(3 - dotCount);
233
+ process.stdout.write(`\r ${exports.theme.brand('◆')} ${exports.theme.dim(this.message + dots)}${pad}` + ' '.repeat(10));
261
234
  this.frame++;
262
235
  }, 400);
263
236
  }
@@ -266,130 +239,141 @@ class ThinkingAnimation {
266
239
  clearInterval(this.interval);
267
240
  this.interval = null;
268
241
  }
269
- // Clear the line
270
242
  process.stdout.write('\r' + ' '.repeat(getTermWidth()) + '\r');
271
243
  showCursor();
272
244
  }
273
245
  }
274
246
  exports.ThinkingAnimation = ThinkingAnimation;
275
247
  // ============================================================================
276
- // PART 3: MARKDOWN-AWARE RESPONSE RENDERING
248
+ // STREAMING RESPONSE RENDERER
277
249
  // ============================================================================
278
250
  /**
279
- * Renders a block of AI response text with markdown awareness.
280
- * Handles: code blocks, **bold**, `inline code`, ## headers, - lists
251
+ * Manages the response box state during streaming.
252
+ *
253
+ * Correct output:
254
+ * ╷
255
+ * │ Hi! I'm CoWorker, your AI coding assistant.
256
+ * │
257
+ * │ I can help you with:
258
+ * │ · Writing and debugging code
259
+ * │
260
+ * ╵ ✦ 1.2s · 34 tokens
261
+ *
262
+ * Rules:
263
+ * - ALL text inside ╷│╵ border
264
+ * - Border chars (╷ │ ╵) in dim gray #4B5563
265
+ * - Response text in purple #A78BFA
266
+ * - Code blocks switch to yellow #FCD34D
267
+ * - On every \n → print "\n │ "
281
268
  */
282
- function renderMarkdown(text) {
283
- const lines = text.split('\n');
284
- const rendered = [];
285
- let inCodeBlock = false;
286
- let codeLanguage = '';
287
- let codeBuffer = [];
288
- for (const line of lines) {
289
- // Code block start
290
- if (line.trim().startsWith('```') && !inCodeBlock) {
291
- inCodeBlock = true;
292
- codeLanguage = line.trim().replace('```', '').trim() || 'code';
293
- codeBuffer = [];
294
- continue;
269
+ class StreamRenderer {
270
+ constructor() {
271
+ this.started = false;
272
+ this.inCodeBlock = false;
273
+ this.charCount = 0;
274
+ this.startTime = Date.now();
275
+ }
276
+ /**
277
+ * Call this for EACH chunk as it arrives from the SSE stream.
278
+ * Renders immediately — no buffering.
279
+ */
280
+ write(chunk) {
281
+ if (!this.started) {
282
+ // Open the response border
283
+ console.log(exports.theme.border(' ╷'));
284
+ process.stdout.write(exports.theme.border(' │') + ' ');
285
+ this.started = true;
295
286
  }
296
- // Code block end
297
- if (line.trim() === '```' && inCodeBlock) {
298
- inCodeBlock = false;
299
- // Render the code box
300
- const maxLen = Math.max(...codeBuffer.map(l => l.length), codeLanguage.length + 4);
301
- const boxWidth = Math.min(maxLen + 4, getTermWidth() - 10);
302
- rendered.push('');
303
- rendered.push(` ${exports.theme.dim(`┌─ ${codeLanguage} ${'─'.repeat(Math.max(0, boxWidth - codeLanguage.length - 5))}┐`)}`);
304
- for (const codeLine of codeBuffer) {
305
- const padded = codeLine + ' '.repeat(Math.max(0, boxWidth - codeLine.length - 4));
306
- rendered.push(` ${exports.theme.dim('│')} ${exports.theme.code(padded)}${exports.theme.dim('│')}`);
287
+ let i = 0;
288
+ while (i < chunk.length) {
289
+ const char = chunk[i];
290
+ this.charCount++;
291
+ if (char === '\n') {
292
+ // Start a new bordered line
293
+ process.stdout.write('\n' + exports.theme.border(' │') + ' ');
294
+ i++;
295
+ continue;
307
296
  }
308
- rendered.push(` ${exports.theme.dim(`└${'─'.repeat(boxWidth - 2)}┘`)}`);
309
- rendered.push('');
310
- codeBuffer = [];
311
- continue;
312
- }
313
- // Inside code block — collect
314
- if (inCodeBlock) {
315
- codeBuffer.push(line);
316
- continue;
317
- }
318
- // ## Headers
319
- if (line.match(/^#{1,3}\s+/)) {
320
- const headerText = line.replace(/^#{1,3}\s+/, '');
321
- rendered.push('');
322
- rendered.push(exports.theme.brand(headerText));
323
- continue;
297
+ // ANSI Escape Code Detection
298
+ if (char === '\x1b' && chunk[i + 1] === '[') {
299
+ let esc = '\x1b[';
300
+ let j = i + 2;
301
+ while (j < chunk.length && !/[a-zA-Z]/.test(chunk[j])) {
302
+ esc += chunk[j];
303
+ j++;
304
+ }
305
+ if (j < chunk.length) {
306
+ esc += chunk[j];
307
+ process.stdout.write(esc);
308
+ i = j + 1;
309
+ continue;
310
+ }
311
+ }
312
+ // Choose color based on whether we're in a code block
313
+ const color = this.inCodeBlock ? exports.theme.code : exports.theme.ai;
314
+ process.stdout.write(color(char));
315
+ i++;
324
316
  }
325
- // - List items
326
- if (line.match(/^\s*[-*]\s+/)) {
327
- const listText = line.replace(/^\s*[-*]\s+/, '');
328
- rendered.push(` ${exports.theme.dim('·')} ${renderInline(listText)}`);
329
- continue;
317
+ // Check for code block toggles in the chunk
318
+ const backtickMatches = chunk.match(/```/g);
319
+ if (backtickMatches) {
320
+ for (const _match of backtickMatches) {
321
+ this.inCodeBlock = !this.inCodeBlock;
322
+ }
330
323
  }
331
- // Regular text with inline formatting
332
- rendered.push(renderInline(line));
333
324
  }
334
- return rendered.join('\n');
335
- }
336
- /**
337
- * Renders inline markdown: **bold**, `code`
338
- */
339
- function renderInline(text) {
340
- // **bold** bright white
341
- let result = text.replace(/\*\*(.+?)\*\*/g, (_, content) => exports.theme.white(content));
342
- // `inline code` yellow
343
- result = result.replace(/`(.+?)`/g, (_, content) => exports.theme.code(content));
344
- return result;
345
- }
346
- // ── Response box rendering ──
347
- function printResponseHeader() {
348
- console.log(exports.theme.dim(' ╷'));
349
- }
350
- function printResponseLine(text) {
351
- // Wrap text to terminal width
352
- const maxWidth = getTermWidth() - 6;
353
- const words = text.split(' ');
354
- let currentLine = '';
355
- for (const word of words) {
356
- if (currentLine.length + word.length + 1 > maxWidth && currentLine.length > 0) {
357
- console.log(`${exports.theme.dim(' │')} ${exports.theme.ai(currentLine)}`);
358
- currentLine = word;
359
- }
360
- else {
361
- currentLine = currentLine ? `${currentLine} ${word}` : word;
362
- }
325
+ /**
326
+ * Close the response box and print footer.
327
+ */
328
+ finish() {
329
+ if (!this.started)
330
+ return;
331
+ const elapsed = Date.now() - this.startTime;
332
+ const elapsedStr = elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`;
333
+ const tokenEst = Math.ceil(this.charCount / 4);
334
+ console.log(''); // end the last │ line
335
+ console.log(exports.theme.border(` ╵`) + exports.theme.dim(` ✦ ${elapsedStr} · ${tokenEst} tokens`));
336
+ console.log('');
337
+ }
338
+ /** Returns the estimated token count. */
339
+ getTokenEstimate() {
340
+ return Math.ceil(this.charCount / 4);
363
341
  }
364
- if (currentLine) {
365
- console.log(`${exports.theme.dim(' │')} ${exports.theme.ai(currentLine)}`);
342
+ /** Returns elapsed ms. */
343
+ getElapsed() {
344
+ return Date.now() - this.startTime;
366
345
  }
367
346
  }
347
+ exports.StreamRenderer = StreamRenderer;
348
+ // ============================================================================
349
+ // LEGACY COMPAT — printResponseHeader/Footer for tool outputs
350
+ // ============================================================================
351
+ function printResponseHeader() {
352
+ console.log(exports.theme.border(' ╷'));
353
+ process.stdout.write(exports.theme.border(' │') + ' ');
354
+ }
368
355
  function printResponseFooter(elapsed, tokenEstimate) {
369
356
  const elapsedStr = elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`;
370
357
  const tokenStr = tokenEstimate ? ` · ${tokenEstimate} tokens` : '';
371
- console.log(exports.theme.dim(` ✦ ${elapsedStr}${tokenStr}`));
358
+ console.log(exports.theme.border(` ╵`) + exports.theme.dim(` ✦ ${elapsedStr}${tokenStr}`));
372
359
  }
373
- /**
374
- * Streams AI response text into the response box with markdown rendering.
375
- * Call printResponseHeader() first, then stream chunks, then printResponseFooter().
376
- */
377
360
  function streamResponseChunk(chunk) {
378
- // Split by newlines and render each part within the box
379
361
  const parts = chunk.split('\n');
380
362
  for (let i = 0; i < parts.length; i++) {
381
363
  if (i > 0) {
382
- // Newline within the chunk — start a new box line
383
- process.stdout.write(`\n${exports.theme.dim(' │')} `);
364
+ process.stdout.write('\n' + exports.theme.border(' │') + ' ');
384
365
  }
385
366
  process.stdout.write(exports.theme.ai(parts[i]));
386
367
  }
387
368
  }
388
369
  // ============================================================================
389
- // PROMPT RENDERING
370
+ // PROMPT RENDERING — Only called ONCE after user hits Enter
390
371
  // ============================================================================
391
372
  function printUserPrompt(input) {
392
- console.log(`\n ${exports.theme.dim('you')} ${exports.theme.brand('›')} ${input}\n`);
373
+ // NOTE: Do NOT call this if readline already echoed the prompt.
374
+ // In the REPL, readline shows "you › input" automatically.
375
+ // This is ONLY for re-printing in non-readline contexts.
376
+ console.log(` ${exports.theme.dim('you')} ${exports.theme.brand('›')} ${input}`);
393
377
  }
394
378
  // ============================================================================
395
379
  // ERROR / SUCCESS / WARNING
@@ -418,7 +402,7 @@ function printWarning(message) {
418
402
  console.log(exports.theme.warning(` ⚠ ${message}`));
419
403
  }
420
404
  // ============================================================================
421
- // EXIT MESSAGE
405
+ // EXIT
422
406
  // ============================================================================
423
407
  function printExit() {
424
408
  if (IS_PIPE)
@@ -441,12 +425,9 @@ function printToolError(toolName, error) {
441
425
  process.stdout.write(`\r ${exports.theme.error('✗')} ${exports.theme.code(toolName)} ${exports.theme.dim(error.substring(0, 60))}` + ' '.repeat(10) + '\n');
442
426
  }
443
427
  // ============================================================================
444
- // SCORE CARD (coworker review)
428
+ // SCORE CARD
445
429
  // ============================================================================
446
430
  function printScoreCard(fileName, scores) {
447
- const w = Math.min(getTermWidth() - 4, 50);
448
- const iw = w - 2;
449
- const title = `Code Review · ${fileName}`;
450
431
  console.log('');
451
432
  console.log(exports.theme.dim(` ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄`));
452
433
  for (const { label, score, max = 10 } of scores) {
@@ -461,7 +442,7 @@ function printScoreCard(fileName, scores) {
461
442
  console.log('');
462
443
  }
463
444
  // ============================================================================
464
- // COMMIT BOX (coworker commit)
445
+ // COMMIT BOX
465
446
  // ============================================================================
466
447
  function printCommitBox(commitMessage) {
467
448
  const w = Math.min(getTermWidth() - 2, 55);
@@ -470,7 +451,6 @@ function printCommitBox(commitMessage) {
470
451
  console.log('');
471
452
  console.log(exports.theme.brand(` ╭─ ${label} ${'─'.repeat(Math.max(0, headerPad))}╮`));
472
453
  console.log(exports.theme.brand(` │`) + ' '.repeat(w - 2) + exports.theme.brand(`│`));
473
- // Wrap message to fit box
474
454
  const maxMsgWidth = w - 6;
475
455
  const msgLines = commitMessage.match(new RegExp(`.{1,${maxMsgWidth}}`, 'g')) || [commitMessage];
476
456
  for (const msgLine of msgLines) {
@@ -482,7 +462,7 @@ function printCommitBox(commitMessage) {
482
462
  console.log('');
483
463
  }
484
464
  // ============================================================================
485
- // DIFF PREVIEW (coworker edit)
465
+ // DIFF PREVIEW
486
466
  // ============================================================================
487
467
  function printDiff(fileName, changes) {
488
468
  const w = Math.min(getTermWidth() - 4, 60);
@@ -505,7 +485,80 @@ function printDiff(fileName, changes) {
505
485
  console.log('');
506
486
  }
507
487
  // ============================================================================
508
- // INTERACTIVE HELPUpdated with new commands
488
+ // TRUST PROMPTBranded CoWorker workspace verification
489
+ // ============================================================================
490
+ const fs = __importStar(require("fs"));
491
+ const path = __importStar(require("path"));
492
+ const os = __importStar(require("os"));
493
+ const readline = __importStar(require("readline"));
494
+ const TRUSTED_PATH = path.join(os.homedir(), '.coworker', 'trusted.json');
495
+ function loadTrustedPaths() {
496
+ try {
497
+ if (fs.existsSync(TRUSTED_PATH)) {
498
+ const data = JSON.parse(fs.readFileSync(TRUSTED_PATH, 'utf8'));
499
+ return Array.isArray(data?.trustedPaths) ? data.trustedPaths : [];
500
+ }
501
+ }
502
+ catch { /* ignore */ }
503
+ return [];
504
+ }
505
+ function saveTrustedPaths(paths) {
506
+ try {
507
+ const dir = path.dirname(TRUSTED_PATH);
508
+ if (!fs.existsSync(dir)) {
509
+ fs.mkdirSync(dir, { recursive: true });
510
+ }
511
+ fs.writeFileSync(TRUSTED_PATH, JSON.stringify({ trustedPaths: paths }, null, 2));
512
+ }
513
+ catch { /* ignore */ }
514
+ }
515
+ /**
516
+ * Shows the CoWorker branded workspace trust prompt.
517
+ * Returns true if user trusts, false if they want to exit.
518
+ * Skips silently if the directory is already trusted.
519
+ */
520
+ async function checkWorkspaceTrust(cwd) {
521
+ if (IS_PIPE)
522
+ return true; // Non-interactive mode — skip
523
+ const trustedPaths = loadTrustedPaths();
524
+ const normalizedCwd = cwd.replace(/\\/g, '/').toLowerCase();
525
+ // Check if already trusted
526
+ const isTrusted = trustedPaths.some(p => {
527
+ const normalized = p.replace(/\\/g, '/').toLowerCase();
528
+ return normalizedCwd.startsWith(normalized) || normalizedCwd === normalized;
529
+ });
530
+ if (isTrusted)
531
+ return true;
532
+ // Show the branded trust prompt
533
+ const lineWidth = Math.min(getTermWidth() - 4, 50);
534
+ console.log('');
535
+ console.log(exports.theme.dim(` ${'─'.repeat(lineWidth)}`));
536
+ console.log(` ${exports.theme.brand('◆')} ${exports.theme.brand('Workspace:')} ${exports.theme.white(cwd)}`);
537
+ console.log(exports.theme.dim(` ${'─'.repeat(lineWidth)}`));
538
+ console.log('');
539
+ console.log(exports.theme.dim(' CoWorker can read, edit, and run files here.'));
540
+ console.log(exports.theme.dim(' Only use in folders you own or trust.'));
541
+ console.log('');
542
+ console.log(` ${exports.theme.brand('›')} ${exports.theme.white('1. Got it, let\'s go')}`);
543
+ console.log(` ${exports.theme.dim('2. Exit')}`);
544
+ console.log('');
545
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
546
+ const answer = await new Promise((resolve) => {
547
+ rl.question(exports.theme.dim(' Enter to confirm · Esc to cancel › '), (a) => {
548
+ resolve(a.trim());
549
+ rl.close();
550
+ });
551
+ });
552
+ if (answer === '2' || answer.toLowerCase() === 'exit') {
553
+ return false;
554
+ }
555
+ // Trust this directory
556
+ trustedPaths.push(cwd);
557
+ saveTrustedPaths(trustedPaths);
558
+ return true;
559
+ }
560
+ // ============================================================================
561
+ // HELP + COMPACT
509
562
  // ============================================================================
510
563
  function printHelp() {
511
564
  console.log(`
@@ -531,36 +584,8 @@ function printCompact(msgCount, tokenEst, sessionId) {
531
584
  exports.theme.dim(` │ Session: ${sessionId.substring(0, 8)}`));
532
585
  console.log('');
533
586
  }
534
- const BUILTIN_COMMANDS = [
535
- { name: '/help', desc: 'show all commands' },
536
- { name: '/clear', desc: 'clear the screen' },
537
- { name: '/review', desc: 'review current branch' },
538
- { name: '/commit', desc: 'generate commit message' },
539
- { name: '/status', desc: 'show auth & session' },
540
- { name: '/compact', desc: 'context window usage' },
541
- { name: '/exit', desc: 'end session' },
542
- ];
543
- /**
544
- * Returns matching slash commands for a partial input.
545
- */
546
- function getSlashCompletions(partial) {
547
- if (!partial.startsWith('/'))
548
- return [];
549
- return BUILTIN_COMMANDS.filter(cmd => cmd.name.startsWith(partial));
550
- }
551
- /**
552
- * Renders the slash completion menu below the input.
553
- */
554
- function renderSlashMenu(matches) {
555
- const lines = [];
556
- for (let i = 0; i < matches.length; i++) {
557
- const prefix = i === matches.length - 1 ? '└' : '├';
558
- lines.push(` ${exports.theme.dim(prefix)} ${exports.theme.code(matches[i].name.padEnd(12))} ${exports.theme.dim(matches[i].desc)}`);
559
- }
560
- return lines.join('\n');
561
- }
562
587
  // ============================================================================
563
- // GENERIC BOX
588
+ // GENERIC BOX + TIMER
564
589
  // ============================================================================
565
590
  function printBox(label, lines) {
566
591
  const w = Math.min(getTermWidth() - 2, 70);
@@ -572,9 +597,6 @@ function printBox(label, lines) {
572
597
  }
573
598
  console.log(exports.theme.brand(` ╰${'─'.repeat(w - 3)}╯`));
574
599
  }
575
- // ============================================================================
576
- // TIMER
577
- // ============================================================================
578
600
  class Timer {
579
601
  constructor() {
580
602
  this.start = Date.now();
@@ -588,4 +610,41 @@ class Timer {
588
610
  }
589
611
  }
590
612
  exports.Timer = Timer;
613
+ // ============================================================================
614
+ // RENDER MARKDOWN (for buffered final rendering)
615
+ // ============================================================================
616
+ function renderMarkdown(text) {
617
+ const lines = text.split('\n');
618
+ const rendered = [];
619
+ let inCodeBlock = false;
620
+ for (const line of lines) {
621
+ if (line.trim().startsWith('```') && !inCodeBlock) {
622
+ inCodeBlock = true;
623
+ const lang = line.trim().replace('```', '').trim() || 'code';
624
+ rendered.push(` ${exports.theme.dim(`┌─ ${lang} ──`)}`);
625
+ continue;
626
+ }
627
+ if (line.trim() === '```' && inCodeBlock) {
628
+ inCodeBlock = false;
629
+ rendered.push(` ${exports.theme.dim('└──')}`);
630
+ continue;
631
+ }
632
+ if (inCodeBlock) {
633
+ rendered.push(` ${exports.theme.dim('│')} ${exports.theme.code(line)}`);
634
+ continue;
635
+ }
636
+ if (line.match(/^#{1,3}\s+/)) {
637
+ rendered.push(exports.theme.brand(line.replace(/^#{1,3}\s+/, '')));
638
+ continue;
639
+ }
640
+ if (line.match(/^\s*[-*]\s+/)) {
641
+ rendered.push(`${exports.theme.dim('·')} ${line.replace(/^\s*[-*]\s+/, '')}`);
642
+ continue;
643
+ }
644
+ let result = line.replace(/\*\*(.+?)\*\*/g, (_, c) => exports.theme.white(c));
645
+ result = result.replace(/`(.+?)`/g, (_, c) => exports.theme.code(c));
646
+ rendered.push(result);
647
+ }
648
+ return rendered.join('\n');
649
+ }
591
650
  //# sourceMappingURL=output.js.map