@sylix/coworker 1.2.6 → 1.3.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.
@@ -33,14 +33,16 @@ 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 = void 0;
36
+ exports.Timer = 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
- exports.boxTop = boxTop;
41
- exports.boxBottom = boxBottom;
40
+ exports.showSessionInfo = showSessionInfo;
41
+ exports.renderMarkdown = renderMarkdown;
42
42
  exports.printResponseHeader = printResponseHeader;
43
+ exports.printResponseLine = printResponseLine;
43
44
  exports.printResponseFooter = printResponseFooter;
45
+ exports.streamResponseChunk = streamResponseChunk;
44
46
  exports.printUserPrompt = printUserPrompt;
45
47
  exports.printError = printError;
46
48
  exports.printSuccess = printSuccess;
@@ -54,16 +56,22 @@ exports.printCommitBox = printCommitBox;
54
56
  exports.printDiff = printDiff;
55
57
  exports.printHelp = printHelp;
56
58
  exports.printCompact = printCompact;
59
+ exports.getSlashCompletions = getSlashCompletions;
60
+ exports.renderSlashMenu = renderSlashMenu;
57
61
  exports.printBox = printBox;
58
62
  const chalk = __importStar(require("chalk"));
59
63
  /**
60
64
  * ═══════════════════════════════════════════════════════════════════
61
- * CoWorker by Sylix — Premium Output System
65
+ * CoWorker v1.3.0 by Sylix — Premium Output System
62
66
  * ═══════════════════════════════════════════════════════════════════
63
67
  *
64
- * Claude Code meets hacker terminal meets luxury product.
65
- * Handles all terminal rendering: animations, themes, boxes, streaming.
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
73
  */
74
+ exports.CW_VERSION = '1.3.0';
67
75
  // ============================================================================
68
76
  // THEME SYSTEM
69
77
  // ============================================================================
@@ -89,6 +97,7 @@ exports.theme = {
89
97
  dim: hex('#6B7280'), // Gray — secondary info
90
98
  white: hex('#F9FAFB'), // Off white
91
99
  code: hex('#FCD34D'), // Yellow — code and commands
100
+ purple: hex('#A78BFA'), // Violet for tagline
92
101
  };
93
102
  // ============================================================================
94
103
  // TERMINAL UTILS
@@ -111,7 +120,7 @@ function showCursor() {
111
120
  process.stdout.write('\x1b[?25h');
112
121
  }
113
122
  // ============================================================================
114
- // STARTUP BANNER — Char-by-char typewriter
123
+ // PART 1: ANIMATED BANNER — Line-by-line slide-in, word-by-word tagline
115
124
  // ============================================================================
116
125
  const LOGO_LINES = [
117
126
  ' ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗██████╗ ',
@@ -122,58 +131,76 @@ const LOGO_LINES = [
122
131
  ' ╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝',
123
132
  ];
124
133
  /**
125
- * Full animated startup banner.
126
- * Step 1: Char-by-char typewriter ASCII logo
127
- * Step 2: Tagline fade-in word by word
128
- * Step 3: Animated thin line draw
129
- * Step 4: User/model info in dim text
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
130
142
  */
131
143
  async function showBanner(version, userEmail, model = 'helix-1.2') {
132
- if (IS_PIPE)
144
+ if (IS_PIPE || process.env.CW_NO_BANNER === '1')
133
145
  return;
134
146
  hideCursor();
135
147
  console.log('');
136
- // Step 1: Typewriter ASCII art — char by char (batched 4 chars at 2ms for speed)
137
- for (const line of LOGO_LINES) {
138
- const chars = [...line];
139
- let buffer = '';
140
- for (let i = 0; i < chars.length; i++) {
141
- buffer += chars[i];
142
- if (i % 4 === 3 || i === chars.length - 1) {
143
- process.stdout.write(exports.theme.brand(buffer));
144
- buffer = '';
145
- await sleep(2);
148
+ try {
149
+ // Step 1: Logo — line by line, each types across rapidly
150
+ for (const line of LOGO_LINES) {
151
+ const chars = [...line];
152
+ let buffer = '';
153
+ for (let i = 0; i < chars.length; i++) {
154
+ buffer += chars[i];
155
+ // Batch 6 chars at a time for speed
156
+ if (i % 6 === 5 || i === chars.length - 1) {
157
+ process.stdout.write(exports.theme.brand(buffer));
158
+ buffer = '';
159
+ await sleep(1);
160
+ }
146
161
  }
162
+ process.stdout.write('\n');
163
+ await sleep(60); // 60ms between lines
147
164
  }
148
- process.stdout.write('\n');
149
- }
150
- // Step 2: Fade in tagline one word at a time
151
- console.log('');
152
- const taglineWords = [' by', 'Sylix', ' ·', ' Your', 'AI', 'Coding', 'Partner'];
153
- for (const word of taglineWords) {
154
- process.stdout.write(exports.theme.dim(word + ' '));
155
- await sleep(80);
165
+ // Step 2: Tagline — word by word, each with its own color
166
+ console.log('');
167
+ const taglineWords = [
168
+ { text: 'by', color: exports.theme.dim },
169
+ { text: 'Sylix', color: exports.theme.brand },
170
+ { text: '·', color: exports.theme.dim },
171
+ { text: 'Your', color: exports.theme.white },
172
+ { text: 'AI', color: exports.theme.purple },
173
+ { text: 'Coding', color: exports.theme.white },
174
+ { text: 'Partner', color: exports.theme.white },
175
+ ];
176
+ process.stdout.write(' ');
177
+ for (const word of taglineWords) {
178
+ process.stdout.write(word.color(word.text) + ' ');
179
+ await sleep(100);
180
+ }
181
+ console.log('');
182
+ // Step 3: Divider — draws left to right, char by char
183
+ console.log('');
184
+ const lineWidth = Math.min(getTermWidth() - 4, 60);
185
+ process.stdout.write(' ');
186
+ for (let i = 0; i < lineWidth; i++) {
187
+ process.stdout.write(exports.theme.dim('━'));
188
+ if (i % 3 === 0)
189
+ await sleep(3);
190
+ }
191
+ console.log('');
192
+ // Step 4: Info line — fades in all at once
193
+ console.log('');
194
+ const userStr = userEmail ? `Signed in as ${userEmail}` : 'Not signed in';
195
+ console.log(` ${exports.theme.brand('◆')} ${exports.theme.dim(`${userStr} · Model: ${model} · v${version}`)}`);
196
+ console.log('');
156
197
  }
157
- console.log('');
158
- // Step 3: Animated thin line draw across terminal
159
- console.log('');
160
- const lineWidth = Math.min(getTermWidth() - 4, 60);
161
- process.stdout.write(' ');
162
- for (let i = 0; i < lineWidth; i++) {
163
- process.stdout.write(exports.theme.brand('━'));
164
- if (i % 3 === 0)
165
- await sleep(3);
198
+ finally {
199
+ showCursor();
166
200
  }
167
- console.log('');
168
- // Step 4: User + model + version
169
- console.log('');
170
- const userStr = userEmail ? `Signed in as ${userEmail}` : 'Not signed in';
171
- console.log(exports.theme.dim(` ◆ ${userStr} · Model: ${model} · v${version}`));
172
- console.log('');
173
- showCursor();
174
201
  }
175
202
  /**
176
- * Static banner for non-TTY / piped / --no-banner mode.
203
+ * Static banner for non-TTY / piped / quick modes.
177
204
  */
178
205
  function showStaticBanner(version) {
179
206
  console.log('');
@@ -182,81 +209,190 @@ function showStaticBanner(version) {
182
209
  }
183
210
  console.log(exports.theme.dim(`\n by Sylix · v${version}\n`));
184
211
  }
212
+ /**
213
+ * Session info lines — appear with delay between each.
214
+ */
215
+ async function showSessionInfo(cwd, sessionId) {
216
+ if (IS_PIPE) {
217
+ console.log(` cwd: ${cwd}`);
218
+ console.log(` session: ${sessionId.substring(0, 8)}...`);
219
+ console.log(` tips: /help for commands, Ctrl+C to exit\n`);
220
+ return;
221
+ }
222
+ const lines = [
223
+ exports.theme.dim(` cwd: ${cwd}`),
224
+ exports.theme.dim(` session: ${sessionId.substring(0, 8)}...`),
225
+ exports.theme.dim(` tips: /help for commands, Ctrl+C to exit`),
226
+ ];
227
+ for (const line of lines) {
228
+ console.log(line);
229
+ await sleep(50);
230
+ }
231
+ console.log('');
232
+ }
185
233
  // ============================================================================
186
- // THINKING / LOADING ANIMATION — Pulsing dots
234
+ // PART 2: THINKING ANIMATION — Rotating messages with pulsing dots
187
235
  // ============================================================================
188
- const LOADING_MESSAGES = [
189
- '◆ Sylix brain activated...',
190
- '◆ Reading your codebase...',
191
- ' Thinking really hard...',
192
- '◆ Consulting the AI oracle...',
193
- ' Crafting something brilliant...',
194
- ' CoWorker is on it...',
236
+ const THINKING_MESSAGES = [
237
+ 'working',
238
+ 'thinking',
239
+ 'reading codebase',
240
+ 'analyzing',
241
+ 'on it',
242
+ 'crafting something',
243
+ 'processing',
195
244
  ];
196
245
  class ThinkingAnimation {
197
- constructor() {
246
+ constructor(customMessage) {
198
247
  this.interval = null;
199
248
  this.frame = 0;
200
- this.message = LOADING_MESSAGES[Math.floor(Math.random() * LOADING_MESSAGES.length)];
249
+ this.message = customMessage || THINKING_MESSAGES[Math.floor(Math.random() * THINKING_MESSAGES.length)];
201
250
  }
202
251
  start() {
203
252
  if (IS_PIPE)
204
253
  return;
205
254
  hideCursor();
206
- const dots = ['◉ ○ ○', '○ ◉ ○', '○ ○ ◉'];
207
255
  this.interval = setInterval(() => {
208
- const dot = dots[this.frame % dots.length];
209
- process.stdout.write(`\r ${exports.theme.brand(dot)} ${exports.theme.dim(this.message)}` + ' '.repeat(10));
256
+ // Pulsing dots: working· working·· working··· working
257
+ const dotCount = (this.frame % 4);
258
+ 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));
210
261
  this.frame++;
211
- }, 300);
262
+ }, 400);
212
263
  }
213
264
  stop() {
214
265
  if (this.interval) {
215
266
  clearInterval(this.interval);
216
267
  this.interval = null;
217
268
  }
269
+ // Clear the line
218
270
  process.stdout.write('\r' + ' '.repeat(getTermWidth()) + '\r');
219
271
  showCursor();
220
272
  }
221
273
  }
222
274
  exports.ThinkingAnimation = ThinkingAnimation;
223
275
  // ============================================================================
224
- // BOXED AI RESPONSE RENDERING
276
+ // PART 3: MARKDOWN-AWARE RESPONSE RENDERING
225
277
  // ============================================================================
226
- function boxTop(label = 'CoWorker') {
227
- const w = Math.min(getTermWidth() - 2, 70);
228
- if (isNarrow())
229
- return exports.theme.brand(`─ ${label} ─`);
230
- const inner = w - 6 - label.length;
231
- return exports.theme.brand(` ╭─ ${label} ${''.repeat(Math.max(0, inner))}╮`);
278
+ /**
279
+ * Renders a block of AI response text with markdown awareness.
280
+ * Handles: code blocks, **bold**, `inline code`, ## headers, - lists
281
+ */
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;
295
+ }
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('│')}`);
307
+ }
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;
324
+ }
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;
330
+ }
331
+ // Regular text with inline formatting
332
+ rendered.push(renderInline(line));
333
+ }
334
+ return rendered.join('\n');
232
335
  }
233
- function boxBottom() {
234
- const w = Math.min(getTermWidth() - 2, 70);
235
- if (isNarrow())
236
- return exports.theme.brand('─'.repeat(20));
237
- return exports.theme.brand(` ╰${'─'.repeat(w - 4)}╯`);
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;
238
345
  }
346
+ // ── Response box rendering ──
239
347
  function printResponseHeader() {
240
- console.log(boxTop('CoWorker'));
241
- console.log(exports.theme.brand(' │'));
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
+ }
363
+ }
364
+ if (currentLine) {
365
+ console.log(`${exports.theme.dim(' │')} ${exports.theme.ai(currentLine)}`);
366
+ }
242
367
  }
243
368
  function printResponseFooter(elapsed, tokenEstimate) {
244
- console.log(exports.theme.brand(' │'));
245
- console.log(boxBottom());
246
- console.log('');
247
369
  const elapsedStr = elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`;
248
- const tokenStr = tokenEstimate ? ` · tokens: ~${tokenEstimate}` : '';
249
- console.log(exports.theme.dim(` Done · sylix.ai · ${elapsedStr}${tokenStr}`));
370
+ const tokenStr = tokenEstimate ? ` · ${tokenEstimate} tokens` : '';
371
+ console.log(exports.theme.dim(`✦ ${elapsedStr}${tokenStr}`));
372
+ }
373
+ /**
374
+ * Streams AI response text into the response box with markdown rendering.
375
+ * Call printResponseHeader() first, then stream chunks, then printResponseFooter().
376
+ */
377
+ function streamResponseChunk(chunk) {
378
+ // Split by newlines and render each part within the box
379
+ const parts = chunk.split('\n');
380
+ for (let i = 0; i < parts.length; i++) {
381
+ if (i > 0) {
382
+ // Newline within the chunk — start a new box line
383
+ process.stdout.write(`\n${exports.theme.dim(' │')} `);
384
+ }
385
+ process.stdout.write(exports.theme.ai(parts[i]));
386
+ }
250
387
  }
251
388
  // ============================================================================
252
389
  // PROMPT RENDERING
253
390
  // ============================================================================
254
391
  function printUserPrompt(input) {
255
- console.log(` ${exports.theme.white('You')} ${exports.theme.dim('›')} ${input}`);
256
- console.log('');
392
+ console.log(`\n ${exports.theme.dim('you')} ${exports.theme.brand('›')} ${input}\n`);
257
393
  }
258
394
  // ============================================================================
259
- // ERROR / SUCCESS / WARNING STYLING
395
+ // ERROR / SUCCESS / WARNING
260
396
  // ============================================================================
261
397
  function printError(title, details, hint) {
262
398
  console.log('');
@@ -305,72 +441,49 @@ function printToolError(toolName, error) {
305
441
  process.stdout.write(`\r ${exports.theme.error('✗')} ${exports.theme.code(toolName)} ${exports.theme.dim(error.substring(0, 60))}` + ' '.repeat(10) + '\n');
306
442
  }
307
443
  // ============================================================================
308
- // COMMAND-SPECIFIC STYLING — Score Card (coworker review)
444
+ // SCORE CARD (coworker review)
309
445
  // ============================================================================
310
- /**
311
- * ┌─────────────────────────────────┐
312
- * │ Code Review · src/index.ts │
313
- * ├─────────────────────────────────┤
314
- * │ Quality ████████░░ 8/10 │
315
- * │ Security ██████████ 10/10 │
316
- * └─────────────────────────────────┘
317
- */
318
446
  function printScoreCard(fileName, scores) {
319
447
  const w = Math.min(getTermWidth() - 4, 50);
320
- const iw = w - 2; // inner width
448
+ const iw = w - 2;
321
449
  const title = `Code Review · ${fileName}`;
322
450
  console.log('');
323
- console.log(exports.theme.brand(` ┌${'─'.repeat(iw)}┐`));
324
- console.log(exports.theme.brand(` │ `) + exports.theme.white(title) + ' '.repeat(Math.max(0, iw - title.length - 2)) + exports.theme.brand(`│`));
325
- console.log(exports.theme.brand(` ├${'─'.repeat(iw)}┤`));
451
+ console.log(exports.theme.dim(` ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄`));
326
452
  for (const { label, score, max = 10 } of scores) {
327
453
  const barWidth = 10;
328
454
  const filled = Math.round((score / max) * barWidth);
329
455
  const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
330
- const scoreStr = ` ${score}/${max}`;
331
- const barColor = score >= 8 ? exports.theme.success : score >= 5 ? exports.theme.warning : exports.theme.error;
332
- const pad = 15 - label.length;
333
- const content = `${label}${' '.repeat(Math.max(1, pad))}${bar}${scoreStr}`;
334
- const rowPad = iw - content.length - 2;
335
- console.log(exports.theme.brand(` │ `) + exports.theme.dim(label) + ' '.repeat(Math.max(1, pad)) + barColor(bar) + exports.theme.dim(scoreStr) + ' '.repeat(Math.max(0, rowPad)) + exports.theme.brand(`│`));
456
+ const barColor = score >= 8 ? exports.theme.brand : score >= 5 ? exports.theme.warning : exports.theme.error;
457
+ const dots = '·'.repeat(Math.max(1, 16 - label.length));
458
+ console.log(` ${exports.theme.dim(label)} ${exports.theme.dim(dots)} ${exports.theme.white(String(score))} ${barColor(bar)}`);
336
459
  }
337
- console.log(exports.theme.brand(` └${'─'.repeat(iw)}┘`));
460
+ console.log(exports.theme.dim(` ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄`));
338
461
  console.log('');
339
462
  }
340
463
  // ============================================================================
341
- // COMMAND-SPECIFIC STYLING — Commit Box (coworker commit)
464
+ // COMMIT BOX (coworker commit)
342
465
  // ============================================================================
343
- /**
344
- * ╭─ Suggested Commit ──────────────────╮
345
- * │ ✨ feat: add user auth middleware │
346
- * │ │
347
- * │ Use this commit message? (y/n) › │
348
- * ╰─────────────────────────────────────╯
349
- */
350
466
  function printCommitBox(commitMessage) {
351
467
  const w = Math.min(getTermWidth() - 2, 55);
352
- const label = 'Suggested Commit';
468
+ const label = 'suggested commit';
353
469
  const headerPad = w - 6 - label.length;
354
- const msgLine = `✨ ${commitMessage}`;
355
- const promptLine = 'Use this commit message? (y/n) ›';
356
470
  console.log('');
357
471
  console.log(exports.theme.brand(` ╭─ ${label} ${'─'.repeat(Math.max(0, headerPad))}╮`));
358
- console.log(exports.theme.brand(` │ `) + exports.theme.success(msgLine) + ' '.repeat(Math.max(0, w - 5 - msgLine.length)) + exports.theme.brand(`│`));
359
472
  console.log(exports.theme.brand(` │`) + ' '.repeat(w - 2) + exports.theme.brand(`│`));
360
- console.log(exports.theme.brand(` │ `) + exports.theme.dim(promptLine) + ' '.repeat(Math.max(0, w - 5 - promptLine.length)) + exports.theme.brand(`│`));
473
+ // Wrap message to fit box
474
+ const maxMsgWidth = w - 6;
475
+ const msgLines = commitMessage.match(new RegExp(`.{1,${maxMsgWidth}}`, 'g')) || [commitMessage];
476
+ for (const msgLine of msgLines) {
477
+ const pad = w - 5 - msgLine.length;
478
+ console.log(exports.theme.brand(` │ `) + exports.theme.success(msgLine) + ' '.repeat(Math.max(0, pad)) + exports.theme.brand(`│`));
479
+ }
480
+ console.log(exports.theme.brand(` │`) + ' '.repeat(w - 2) + exports.theme.brand(`│`));
361
481
  console.log(exports.theme.brand(` ╰${'─'.repeat(w - 2)}╯`));
362
482
  console.log('');
363
483
  }
364
484
  // ============================================================================
365
- // COMMAND-SPECIFIC STYLING — Diff Preview (coworker edit)
485
+ // DIFF PREVIEW (coworker edit)
366
486
  // ============================================================================
367
- /**
368
- * ── Changes to src/api/sylix.ts ──────────
369
- * - const url = "old-endpoint" ← red
370
- * + const url = "new-endpoint" ← green
371
- * ─────────────────────────────────────────
372
- * Apply these changes? (y/n) ›
373
- */
374
487
  function printDiff(fileName, changes) {
375
488
  const w = Math.min(getTermWidth() - 4, 60);
376
489
  const header = `Changes to ${fileName}`;
@@ -389,20 +502,21 @@ function printDiff(fileName, changes) {
389
502
  }
390
503
  }
391
504
  console.log(exports.theme.dim(` ${'─'.repeat(w)}`));
392
- console.log(exports.theme.dim(` Apply these changes? (y/n) ›`));
393
505
  console.log('');
394
506
  }
395
507
  // ============================================================================
396
- // INTERACTIVE HELP
508
+ // INTERACTIVE HELP — Updated with new commands
397
509
  // ============================================================================
398
510
  function printHelp() {
399
511
  console.log(`
400
512
  ${exports.theme.brand(' Slash Commands:')}
401
- ${exports.theme.code('/help')} ${exports.theme.dim('Show this help')}
402
- ${exports.theme.code('/clear')} ${exports.theme.dim('Clear the screen')}
403
- ${exports.theme.code('/status')} ${exports.theme.dim('Show auth & session status')}
404
- ${exports.theme.code('/compact')} ${exports.theme.dim('Show context window usage')}
405
- ${exports.theme.code('/exit')} ${exports.theme.dim('Exit CoWorker')}
513
+ ${exports.theme.code('/help')} ${exports.theme.dim('Show this help')}
514
+ ${exports.theme.code('/clear')} ${exports.theme.dim('Clear the screen')}
515
+ ${exports.theme.code('/review')} ${exports.theme.dim('Code review current branch')}
516
+ ${exports.theme.code('/commit')} ${exports.theme.dim('Generate commit message from diff')}
517
+ ${exports.theme.code('/status')} ${exports.theme.dim('Show auth & session status')}
518
+ ${exports.theme.code('/compact')} ${exports.theme.dim('Show context window usage')}
519
+ ${exports.theme.code('/exit')} ${exports.theme.dim('Exit CoWorker')}
406
520
  `);
407
521
  }
408
522
  function printCompact(msgCount, tokenEst, sessionId) {
@@ -412,11 +526,41 @@ function printCompact(msgCount, tokenEst, sessionId) {
412
526
  const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
413
527
  const color = usage > 80 ? exports.theme.error : usage > 50 ? exports.theme.warning : exports.theme.success;
414
528
  console.log('');
415
- console.log(exports.theme.dim(` Messages: ${msgCount} │ Context: `) + color(`[${bar}] ${usage}%`) + exports.theme.dim(` │ Session: ${sessionId.substring(0, 8)}`));
529
+ console.log(exports.theme.dim(` Messages: ${msgCount} │ Context: `) +
530
+ color(`[${bar}] ${usage}%`) +
531
+ exports.theme.dim(` │ Session: ${sessionId.substring(0, 8)}`));
416
532
  console.log('');
417
533
  }
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
+ }
418
562
  // ============================================================================
419
- // GENERIC STYLED BOX (reusable for any content)
563
+ // GENERIC BOX
420
564
  // ============================================================================
421
565
  function printBox(label, lines) {
422
566
  const w = Math.min(getTermWidth() - 2, 70);
@@ -429,7 +573,7 @@ function printBox(label, lines) {
429
573
  console.log(exports.theme.brand(` ╰${'─'.repeat(w - 3)}╯`));
430
574
  }
431
575
  // ============================================================================
432
- // ELAPSED TIME UTILITY
576
+ // TIMER
433
577
  // ============================================================================
434
578
  class Timer {
435
579
  constructor() {