@owlburtoe/pi-cc-tools 1.0.1 → 1.1.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.
package/README.md CHANGED
@@ -32,6 +32,7 @@ Set in `.pi/settings.json` or `~/.pi/settings.json`:
32
32
  "previewLines": 8,
33
33
  "bashOutputMode": "opencode",
34
34
  "bashCollapsedLines": 10,
35
+ "bashStackConsecutive": true,
35
36
  "diffCollapsedLines": 24,
36
37
  "themeAdaptive": true,
37
38
  "diffTheme": "github-dark",
@@ -135,6 +136,20 @@ Color selections are persisted as `spinnerColor` / `spinnerStatusColor` in `~/.p
135
136
  | `mcpOutputMode` | `hidden`, `summary`, `preview` | `preview` |
136
137
  | `bashOutputMode` | `opencode`, `summary`, `preview` | `opencode` |
137
138
 
139
+ `bashOutputMode` behavior:
140
+
141
+ | Value | Behavior |
142
+ |-------|----------|
143
+ | `opencode` | Compact status while collapsed, with `Ctrl+O` hint for output preview |
144
+ | `summary` | Status only; no output preview or expansion hint |
145
+ | `preview` | Show a small output preview even while collapsed |
146
+
147
+ ### Boolean settings
148
+
149
+ | Setting | Default | Description |
150
+ |---------|---------|-------------|
151
+ | `bashStackConsecutive` | `true` | Remove the extra synthetic spacer between adjacent bash tool rows so command bursts render as a tight stack |
152
+
138
153
  ### Numeric settings
139
154
 
140
155
  | Setting | Default | Description |
@@ -7,6 +7,7 @@
7
7
  "expandedPreviewMaxLines": 4000,
8
8
  "bashOutputMode": "opencode",
9
9
  "bashCollapsedLines": 10,
10
+ "bashStackConsecutive": true,
10
11
  "diffCollapsedLines": 24,
11
12
  "showTruncationHints": false,
12
13
  "themeAdaptive": true,
@@ -84,6 +84,8 @@ interface SettingsFile {
84
84
  expandedPreviewMaxLines?: number;
85
85
  bashOutputMode?: "opencode" | "summary" | "preview";
86
86
  bashCollapsedLines?: number;
87
+ /** When true (default), consecutive bash tool rows render without the extra inter-tool spacer. */
88
+ bashStackConsecutive?: boolean;
87
89
  showTruncationHints?: boolean;
88
90
  diffCollapsedLines?: number;
89
91
  diffTheme?: string;
@@ -329,6 +331,44 @@ function isToolExecutionLike(value: unknown): value is { toolName: string; toolC
329
331
  return typeof candidate.toolName === "string" && typeof candidate.toolCallId === "string";
330
332
  }
331
333
 
334
+ function isBashToolExecution(value: unknown): boolean {
335
+ return isToolExecutionLike(value) && (value as any).toolName === "bash";
336
+ }
337
+
338
+ function shouldStackConsecutiveBash(): boolean {
339
+ return readSettings().bashStackConsecutive !== false;
340
+ }
341
+
342
+ function hasConsecutiveBashToolChildren(children: unknown[]): boolean {
343
+ let previousWasBash = false;
344
+ for (const child of children) {
345
+ const currentIsBash = isBashToolExecution(child);
346
+ if (currentIsBash && previousWasBash) return true;
347
+ previousWasBash = currentIsBash;
348
+ }
349
+ return false;
350
+ }
351
+
352
+ function dropLeadingSpacerLine(lines: string[]): string[] {
353
+ return lines.length > 0 && isBlankLine(lines[0]) ? lines.slice(1) : lines;
354
+ }
355
+
356
+ function renderWithStackedConsecutiveBash(container: any, width: number): string[] | null {
357
+ if (!shouldStackConsecutiveBash()) return null;
358
+ const children = Array.isArray(container?.children) ? container.children : null;
359
+ if (!children || !hasConsecutiveBashToolChildren(children)) return null;
360
+
361
+ const rendered: string[] = [];
362
+ let previousWasBash = false;
363
+ for (const child of children) {
364
+ const currentIsBash = isBashToolExecution(child);
365
+ const childLines = typeof child?.render === "function" ? child.render(width) : [];
366
+ rendered.push(...(currentIsBash && previousWasBash ? dropLeadingSpacerLine(childLines) : childLines));
367
+ previousWasBash = currentIsBash;
368
+ }
369
+ return rendered;
370
+ }
371
+
332
372
  function isTerminalImageLine(line: string): boolean {
333
373
  return line.includes(KITTY_IMAGE_PREFIX) || line.includes(ITERM2_IMAGE_PREFIX);
334
374
  }
@@ -359,6 +399,11 @@ function patchGlobalToolBorders(): void {
359
399
 
360
400
  const originalRender = proto.render;
361
401
  proto.render = function patchedContainerRender(width: number): string[] {
402
+ if (!isToolExecutionLike(this)) {
403
+ const stacked = renderWithStackedConsecutiveBash(this, width);
404
+ if (stacked) return stacked;
405
+ }
406
+
362
407
  if (isToolExecutionLike(this)) {
363
408
  const cached = (this as any)[TOOL_RENDER_CACHE];
364
409
  if (cached?.width === width && cached?.mode === toolBackgroundMode) {
@@ -1324,6 +1369,10 @@ function bashCollapsedLimit(): number {
1324
1369
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : 10;
1325
1370
  }
1326
1371
 
1372
+ function bashOutputMode(): "opencode" | "summary" | "preview" {
1373
+ return getMode(readSettings().bashOutputMode, ["opencode", "summary", "preview"] as const, "opencode");
1374
+ }
1375
+
1327
1376
  function diffCollapsedLimit(): number {
1328
1377
  const value = readSettings().diffCollapsedLines;
1329
1378
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 24;
@@ -4245,15 +4294,26 @@ export default function (pi: ExtensionAPI) {
4245
4294
  }
4246
4295
  clearBlinkTimer(ctx);
4247
4296
  setToolStatus(ctx, ctx.isError ? "error" : "success");
4248
- const exitMatch = output.match(/exit code: (\d+)/);
4297
+ const exitMatch = output.match(/(?:exit code:|exited with code)\s+(\d+)/i);
4249
4298
  const exitCode = exitMatch ? Number.parseInt(exitMatch[1], 10) : null;
4250
- let text = exitCode === null || exitCode === 0 ? theme.fg("success", "Done") : theme.fg("error", `Exit ${exitCode}`);
4299
+ const isError = ctx.isError || (exitCode !== null && exitCode !== 0);
4300
+ let text = isError
4301
+ ? theme.fg("error", exitCode !== null ? `Exit ${exitCode}` : "Failed")
4302
+ : theme.fg("success", "Done");
4251
4303
  text += theme.fg("muted", ` (${nonEmpty.length} lines)`);
4252
4304
  if (details?.truncation?.truncated) text += theme.fg("warning", " [truncated]");
4305
+
4306
+ const mode = bashOutputMode();
4307
+ if (mode === "summary") return makeText(ctx.lastComponent, withBranch(text, theme));
4308
+ if (mode === "preview") {
4309
+ if (nonEmpty.length === 0) return makeText(ctx.lastComponent, withBranch(text, theme));
4310
+ const preview = buildPreviewText(nonEmpty.map((line) => theme.fg(isError ? "error" : "dim", line)), expanded, theme, bashCollapsedLimit());
4311
+ return makeText(ctx.lastComponent, withBranch(`${text}\n${preview}`, theme));
4312
+ }
4313
+
4253
4314
  if (!expanded && nonEmpty.length > 0) return makeText(ctx.lastComponent, withBranch(`${text}${theme.fg("muted", " • Ctrl+O to expand")}`, theme));
4254
4315
  if (!expanded) return makeText(ctx.lastComponent, withBranch(text, theme));
4255
- const collapsed = bashCollapsedLimit();
4256
- text += `\n${buildPreviewText(nonEmpty.map((line) => theme.fg("dim", line)), false, theme, collapsed)}`;
4316
+ text += `\n${buildPreviewText(nonEmpty.map((line) => theme.fg(isError ? "error" : "dim", line)), true, theme, bashCollapsedLimit())}`;
4257
4317
  return makeText(ctx.lastComponent, withBranch(text, theme));
4258
4318
  },
4259
4319
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlburtoe/pi-cc-tools",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Claude Code-style tool rows for pi with Ctrl+O image previews and consistent built-in, MCP, and custom tool rendering",
5
5
  "keywords": [
6
6
  "pi-package",