@bahulam/code 2.6.13 → 2.6.15

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.
@@ -29,6 +29,7 @@ import {
29
29
  toolDisplaySummary,
30
30
  formatShellCommand,
31
31
  shellCommandDisplay,
32
+ shellCommandProfile,
32
33
  } from '../terminal/tool-display.mjs';
33
34
 
34
35
  // ── Family → label colorizer ─────────────────────────────────────────────
@@ -50,8 +51,10 @@ function formatArgs(tool, args, cwd) {
50
51
  const summary = toolDisplaySummary(tool, args || {}, { cwd });
51
52
  if (!summary) return '';
52
53
  if (tool === 'shell') {
54
+ const profile = shellCommandProfile(summary, { cwd });
55
+ if (profile.compact) return compactShellProfile(profile);
53
56
  const display = shellCommandDisplay(summary, { cwd });
54
- const command = formatShellCommand(display.command, paintShellAdapter);
57
+ const command = `${paint.text.dim('$')} ${formatShellCommand(display.command, paintShellAdapter)}`;
55
58
  return display.cwdLabel
56
59
  ? `${command} ${paint.text.dim('in')} ${paint.brand.data(display.cwdLabel)}`
57
60
  : command;
@@ -150,6 +153,10 @@ export function summarizeResult(tool, data) {
150
153
  if (exit != null && exit !== 0) {
151
154
  return { text: `exit ${exit}`, tone: 'danger' };
152
155
  }
156
+ if (tool === 'shell') {
157
+ const structured = structuredOutputSummary(data.output_preview || data.output);
158
+ if (structured) return structured;
159
+ }
153
160
  const head = firstOutputLine(data).slice(0, 100);
154
161
  return { text: head || 'ok', tone: 'success' };
155
162
  }
@@ -179,34 +186,92 @@ export function summarizeResult(tool, data) {
179
186
  }
180
187
  }
181
188
 
189
+ function structuredOutputSummary(output) {
190
+ const raw = String(output || '').trim();
191
+ if (!raw || !/^[\[{]/.test(raw)) return null;
192
+ let value;
193
+ try {
194
+ value = JSON.parse(raw);
195
+ } catch {
196
+ const first = raw.split('\n').find(line => /^[\[{]/.test(line.trim()));
197
+ if (!first) return null;
198
+ try { value = JSON.parse(first.trim()); } catch { return null; }
199
+ }
200
+ return summarizeJsonOutput(value);
201
+ }
202
+
203
+ function summarizeJsonOutput(value) {
204
+ if (Array.isArray(value)) {
205
+ return { text: `json array · ${value.length} item${value.length === 1 ? '' : 's'}`, tone: 'success' };
206
+ }
207
+ if (!value || typeof value !== 'object') return null;
208
+
209
+ if ('service' in value && 'profile' in value && 'inSync' in value) {
210
+ const service = String(value.service || 'service');
211
+ const profile = value.profile ? ` · ${value.profile}` : '';
212
+ const status = value.inSync === true ? 'in sync'
213
+ : value.inSync === false ? 'out of sync'
214
+ : 'sync status unknown';
215
+ const diffs = Array.isArray(value.diff) ? value.diff
216
+ : Array.isArray(value.diffs) ? value.diffs
217
+ : [];
218
+ const diffText = diffs.length ? ` · ${diffs.length} diff${diffs.length === 1 ? '' : 's'}` : '';
219
+ return {
220
+ text: `${service} ${status}${profile}${diffText}`,
221
+ tone: value.inSync === false ? 'warn' : 'success',
222
+ };
223
+ }
224
+
225
+ const keys = Object.keys(value).slice(0, 4);
226
+ return {
227
+ text: keys.length ? `json · ${keys.join(', ')}` : 'json object',
228
+ tone: 'success',
229
+ };
230
+ }
231
+
182
232
  export function formatCompactFileDiff(result, {
183
233
  indent = ' ',
184
- maxLines = 14,
185
- maxFiles = 2,
234
+ maxLines = Infinity,
235
+ maxFiles = Infinity,
186
236
  columns = term().columns || 120,
237
+ showFileHeader = false,
187
238
  } = {}) {
188
- const diffs = fileDiffs(result).filter(diff => diff?.hunks?.length);
239
+ const diffs = fileDiffs(result)
240
+ .map(normalizeFileDiff)
241
+ .filter(diff => diff && (diff.redacted || diff?.hunks?.length));
189
242
  if (!diffs.length) return '';
190
243
 
191
244
  const out = [];
192
245
  let shown = 0;
193
246
  let truncated = false;
247
+ const lineLimit = Number.isFinite(maxLines) ? Math.max(0, Math.floor(maxLines)) : Infinity;
248
+ const fileLimit = Number.isFinite(maxFiles) ? Math.max(0, Math.floor(maxFiles)) : diffs.length;
194
249
  const lineBudget = Math.max(40, columns - visibleWidth(indent) - 4);
195
250
 
196
- for (const diff of diffs.slice(0, maxFiles)) {
197
- if (diffs.length > 1) {
198
- if (shown >= maxLines) { truncated = true; break; }
251
+ for (const diff of diffs.slice(0, fileLimit)) {
252
+ if (showFileHeader || diffs.length > 1) {
253
+ if (shown >= lineLimit) { truncated = true; break; }
199
254
  out.push(`${indent}${paint.brand.primary(diff.relative_path || diff.path || 'file')} ${paint.text.dim(diffDelta(diff))}`);
200
255
  shown++;
201
256
  }
202
257
 
258
+ if (diff.redacted) {
259
+ if (shown >= lineLimit) { truncated = true; break; }
260
+ const subject = (showFileHeader || diffs.length > 1)
261
+ ? ''
262
+ : `${[diff.relative_path || diff.path || 'file', diffDelta(diff)].filter(Boolean).join(' ')} · `;
263
+ out.push(`${indent}${paint.text.dim(`${subject}diff redacted for sensitive config`)}`);
264
+ shown++;
265
+ continue;
266
+ }
267
+
203
268
  for (const hunk of diff.hunks || []) {
204
- if (shown >= maxLines) { truncated = true; break; }
269
+ if (shown >= lineLimit) { truncated = true; break; }
205
270
  out.push(`${indent}${paint.text.dim(`@@ -${hunk.old_start},${hunk.old_count} +${hunk.new_start},${hunk.new_count} @@`)}`);
206
271
  shown++;
207
272
 
208
273
  for (const line of hunk.lines || []) {
209
- if (shown >= maxLines) { truncated = true; break; }
274
+ if (shown >= lineLimit) { truncated = true; break; }
210
275
  out.push(`${indent}${paintDiffLine(line, lineBudget)}`);
211
276
  shown++;
212
277
  }
@@ -215,7 +280,7 @@ export function formatCompactFileDiff(result, {
215
280
  if (truncated) break;
216
281
  }
217
282
 
218
- if (diffs.length > maxFiles) truncated = true;
283
+ if (diffs.length > fileLimit) truncated = true;
219
284
  if (truncated) out.push(`${indent}${paint.text.dim('… diff preview truncated; use /last to expand')}`);
220
285
  return out.join('\n');
221
286
  }
@@ -224,9 +289,112 @@ function fileDiffs(result) {
224
289
  if (!result) return [];
225
290
  if (Array.isArray(result.file_diffs)) return result.file_diffs;
226
291
  if (result.file_diff) return [result.file_diff];
292
+ if (result.type === 'file_diff' || result.hunks || result.unified) return [result];
227
293
  return [];
228
294
  }
229
295
 
296
+ function normalizeFileDiff(diff) {
297
+ if (!diff) return null;
298
+ return {
299
+ ...diff,
300
+ hunks: normalizeHunks(diff),
301
+ };
302
+ }
303
+
304
+ function normalizeHunks(diff) {
305
+ if (Array.isArray(diff?.hunks) && diff.hunks.length) {
306
+ return diff.hunks.map(normalizeHunk).filter(hunk => hunk.lines.length);
307
+ }
308
+ if (diff?.unified) return parseUnifiedHunks(diff.unified);
309
+ return [];
310
+ }
311
+
312
+ function normalizeHunk(hunk = {}) {
313
+ const lines = Array.isArray(hunk.lines)
314
+ ? hunk.lines.map(normalizeDiffLine).filter(Boolean)
315
+ : typeof hunk.body === 'string'
316
+ ? parseDiffBody(hunk.body)
317
+ : [];
318
+ const oldCount = hunk.old_count ?? hunk.old_lines ?? hunk.oldCount ?? countDiffLines(lines, 'old');
319
+ const newCount = hunk.new_count ?? hunk.new_lines ?? hunk.newCount ?? countDiffLines(lines, 'new');
320
+ return {
321
+ ...hunk,
322
+ old_start: hunk.old_start ?? hunk.oldStart ?? 1,
323
+ old_count: oldCount,
324
+ new_start: hunk.new_start ?? hunk.newStart ?? 1,
325
+ new_count: newCount,
326
+ lines,
327
+ };
328
+ }
329
+
330
+ function normalizeDiffLine(line) {
331
+ if (typeof line === 'string') return parseUnifiedLine(line);
332
+ if (!line || typeof line !== 'object') return null;
333
+ const rawType = String(line.type || line.kind || '').toLowerCase();
334
+ const text = String(line.text ?? line.content ?? line.value ?? '');
335
+ if (rawType === 'add' || rawType === 'added' || rawType === '+') return { ...line, type: 'add', text };
336
+ if (rawType === 'remove' || rawType === 'removed' || rawType === 'delete' || rawType === '-') return { ...line, type: 'remove', text };
337
+ if (rawType === 'context' || rawType === 'same' || rawType === ' ') return { ...line, type: 'context', text };
338
+ return parseUnifiedLine(text);
339
+ }
340
+
341
+ function parseDiffBody(body) {
342
+ return String(body || '')
343
+ .replace(/\r\n?/g, '\n')
344
+ .split('\n')
345
+ .filter(line => line && !line.startsWith('@@'))
346
+ .map(parseUnifiedLine)
347
+ .filter(Boolean);
348
+ }
349
+
350
+ function parseUnifiedHunks(unified) {
351
+ const hunks = [];
352
+ let current = null;
353
+ for (const raw of String(unified || '').replace(/\r\n?/g, '\n').split('\n')) {
354
+ if (raw.startsWith('--- ') || raw.startsWith('+++ ')) continue;
355
+ const header = raw.match(/^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/);
356
+ if (header) {
357
+ current = {
358
+ old_start: Number(header[1]) || 1,
359
+ old_count: Number(header[2] || 1),
360
+ new_start: Number(header[3]) || 1,
361
+ new_count: Number(header[4] || 1),
362
+ lines: [],
363
+ };
364
+ hunks.push(current);
365
+ continue;
366
+ }
367
+ if (!current) {
368
+ if (!raw || (!raw.startsWith('+') && !raw.startsWith('-') && !raw.startsWith(' '))) continue;
369
+ current = { old_start: 1, old_count: 0, new_start: 1, new_count: 0, lines: [] };
370
+ hunks.push(current);
371
+ }
372
+ const line = parseUnifiedLine(raw);
373
+ if (line) current.lines.push(line);
374
+ }
375
+ for (const hunk of hunks) {
376
+ if (!hunk.old_count) hunk.old_count = countDiffLines(hunk.lines, 'old');
377
+ if (!hunk.new_count) hunk.new_count = countDiffLines(hunk.lines, 'new');
378
+ }
379
+ return hunks.filter(hunk => hunk.lines.length);
380
+ }
381
+
382
+ function parseUnifiedLine(raw) {
383
+ const line = String(raw ?? '');
384
+ if (!line && raw !== '') return null;
385
+ if (line.startsWith('+') && !line.startsWith('+++')) return { type: 'add', text: line.slice(1) };
386
+ if (line.startsWith('-') && !line.startsWith('---')) return { type: 'remove', text: line.slice(1) };
387
+ if (line.startsWith(' ')) return { type: 'context', text: line.slice(1) };
388
+ return { type: 'context', text: line };
389
+ }
390
+
391
+ function countDiffLines(lines, side) {
392
+ return lines.filter(line => {
393
+ if (side === 'old') return line.type !== 'add';
394
+ return line.type !== 'remove';
395
+ }).length;
396
+ }
397
+
230
398
  function paintDiffLine(line, maxWidth) {
231
399
  const text = truncatePlain(String(line?.text ?? ''), Math.max(20, maxWidth - 2));
232
400
  if (line?.type === 'add') return paint.state.success(`+ ${text}`);
@@ -303,20 +471,41 @@ function tone(text, t) {
303
471
  export function formatCardHead(tool, args, opts = {}) {
304
472
  const cwd = opts.cwd || safeCwd();
305
473
  const cols = opts.columns || term().columns || 120;
306
- const indent = opts.indent || ' ';
474
+ const indent = opts.indent ?? (tool === 'shell' ? '' : ' ');
307
475
 
308
476
  const label = toolDisplayLabel(tool);
309
477
  const argsText = formatArgs(tool, args, cwd);
478
+ const leadText = formatHeadLead(tool, label);
310
479
 
311
- const leadVisible = visibleWidth(`${indent}${label}`);
480
+ const leadVisible = visibleWidth(`${indent}${leadText}`);
312
481
  const budget = Math.max(20, cols - leadVisible - 4);
313
482
 
483
+ if (tool === 'shell') {
484
+ const profile = shellCommandProfile(toolDisplaySummary(tool, args || {}, { cwd }), { cwd });
485
+ if (profile.compact) {
486
+ const head = `${indent}${leadText}`;
487
+ if (profile.preview) {
488
+ const fullArgs = compactShellProfile(profile);
489
+ if (visibleWidth(fullArgs) <= budget) return `${head} ${fullArgs}`;
490
+ const previewTail = `${paint.text.dim(' · preview:')} ${paint.text.primary(profile.preview)}`;
491
+ const baseArgs = compactShellProfile(profile, { includePreview: false, includeDetails: false });
492
+ const baseBudget = budget - visibleWidth(previewTail);
493
+ if (baseBudget >= 12) {
494
+ const baseTruncated = truncateEndVisible(baseArgs, baseBudget);
495
+ return `${head} ${baseTruncated}${previewTail}`;
496
+ }
497
+ }
498
+ const argsTruncated = truncateMiddle(argsText, budget);
499
+ return argsTruncated ? `${head} ${argsTruncated}` : head;
500
+ }
501
+ }
502
+
314
503
  if (tool === 'shell' && visibleWidth(argsText) > budget) {
315
504
  const wrapWidth = Math.max(32, cols - visibleWidth(indent) - 4);
316
505
  const display = shellCommandDisplay(toolDisplaySummary(tool, args || {}, { cwd }), { cwd });
317
506
  const commandLines = wrapCommand(display.command, wrapWidth)
318
- .map(line => `${indent}${paint.text.dim(' ')}${formatShellCommand(line, paintShellAdapter)}`);
319
- const head = `${indent}${paintLabel(tool, label)}`;
507
+ .map((line, index) => `${indent}${paint.text.dim(index === 0 ? '$ ' : '> ')}${formatShellCommand(line, paintShellAdapter)}`);
508
+ const head = `${indent}${leadText}`;
320
509
  const cwdLine = display.cwdLabel
321
510
  ? `\n${indent}${paint.text.dim(' in ')}${paint.brand.data(display.cwdLabel)}`
322
511
  : '';
@@ -325,10 +514,27 @@ export function formatCardHead(tool, args, opts = {}) {
325
514
 
326
515
  const argsTruncated = truncateMiddle(argsText, budget);
327
516
 
328
- const head = `${indent}${paintLabel(tool, label)}`;
517
+ const head = `${indent}${leadText}`;
329
518
  return argsTruncated ? `${head} ${argsTruncated}` : head;
330
519
  }
331
520
 
521
+ function formatHeadLead(tool, label) {
522
+ if (tool !== 'shell') return paintLabel(tool, label);
523
+ return `${paint.text.dim('• shell ·')} ${paintLabel(tool, label)}`;
524
+ }
525
+
526
+ function compactShellProfile(profile, { includePreview = true, includeDetails = true } = {}) {
527
+ const previewSuffix = profile.preview ? ` · preview: ${profile.preview}` : '';
528
+ const summary = previewSuffix && profile.summary.endsWith(previewSuffix)
529
+ ? profile.summary.slice(0, -previewSuffix.length)
530
+ : profile.summary;
531
+ const parts = [`${paint.text.dim('$')} ${paint.text.primary(summary)}`];
532
+ if (profile.cwdLabel) parts.push(`${paint.text.dim('in')} ${paint.brand.data(profile.cwdLabel)}`);
533
+ if (includeDetails) parts.push(paint.text.dim(profile.detailHint || 'details: F2 or /last'));
534
+ if (includePreview && profile.preview) parts.push(`${paint.text.dim('preview:')} ${paint.text.primary(profile.preview)}`);
535
+ return parts.join(' · ');
536
+ }
537
+
332
538
  /**
333
539
  * Render a full card with outcome.
334
540
  *
@@ -346,7 +552,7 @@ export function formatCard({ tool, args, result, durationMs, indent, columns, cw
346
552
 
347
553
  if (!summary.text && !duration) return head;
348
554
 
349
- const arrow = paint.text.dim('—');
555
+ const arrow = outcomeLead(tool);
350
556
  const body = summary.text ? tone(summary.text, summary.tone) : '';
351
557
  // Hide the duration tail when the tool was effectively instant (<200ms).
352
558
  // For fast reads, "1ms" / "0ms" was noise that broke the prose feel.
@@ -374,6 +580,19 @@ export function formatCard({ tool, args, result, durationMs, indent, columns, cw
374
580
  return `${head}\n${gutterIndent}${arrow} ${body}${tail}`;
375
581
  }
376
582
 
583
+ function outcomeLead(tool) {
584
+ return isShellOutcomeTool(tool)
585
+ ? `${paint.text.dim('result')} ${paint.text.dim('—')}`
586
+ : paint.text.dim('—');
587
+ }
588
+
589
+ function isShellOutcomeTool(tool) {
590
+ return [
591
+ 'shell', 'run_tests', 'validate_build', 'lint_check',
592
+ 'validate_file', 'validate_structure',
593
+ ].includes(String(tool || '').toLowerCase());
594
+ }
595
+
377
596
  function isInlineOutcomeTool(tool) {
378
597
  return [
379
598
  'read_file', 'read_files', 'read_batch', 'get_file_info',
@@ -393,26 +612,38 @@ function truncateMiddle(text, max) {
393
612
  return paint.text.muted(`${head}…${tail}`);
394
613
  }
395
614
 
615
+ function truncateEndVisible(text, max) {
616
+ if (!text) return '';
617
+ if (visibleWidth(text) <= max) return text;
618
+ const plain = text.replace(/\x1b\[[0-9;]*m/g, '');
619
+ const limit = Math.max(1, Math.floor(max));
620
+ if (limit <= 1) return '';
621
+ return paint.text.muted(`${plain.slice(0, limit - 1)}…`);
622
+ }
623
+
396
624
  function wrapCommand(command, width) {
397
625
  const text = String(command || '');
398
626
  if (!text) return ['(empty command)'];
399
627
  const lines = [];
400
- let line = '';
401
- for (const token of text.match(/\S+\s*/g) || [text]) {
402
- const next = line + token;
403
- if (line && visibleWidth(next.trimEnd()) > width) {
404
- lines.push(line.trimEnd());
405
- line = token;
406
- continue;
407
- }
408
- if (!line && visibleWidth(token.trimEnd()) > width) {
409
- lines.push(...chunkLongToken(token.trimEnd(), width));
410
- line = '';
411
- continue;
628
+ for (const physicalLine of text.replace(/\r\n?/g, '\n').split('\n')) {
629
+ let line = '';
630
+ for (const token of physicalLine.match(/\S+\s*/g) || [physicalLine]) {
631
+ const next = line + token;
632
+ if (line && visibleWidth(next.trimEnd()) > width) {
633
+ lines.push(line.trimEnd());
634
+ line = token;
635
+ continue;
636
+ }
637
+ if (!line && visibleWidth(token.trimEnd()) > width) {
638
+ lines.push(...chunkLongToken(token.trimEnd(), width));
639
+ line = '';
640
+ continue;
641
+ }
642
+ line = next;
412
643
  }
413
- line = next;
644
+ if (line.trimEnd()) lines.push(line.trimEnd());
645
+ else if (!physicalLine.trim()) lines.push('');
414
646
  }
415
- if (line.trimEnd()) lines.push(line.trimEnd());
416
647
  return lines.length ? lines : ['(empty command)'];
417
648
  }
418
649