@quandev104/pi-style 0.1.4 → 0.1.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 (34) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +10 -6
  3. package/dist/extensions/pi-style.js +3939 -1431
  4. package/dist/extensions/pi-style.js.map +1 -1
  5. package/extension-src/pi-style/app/command-service.ts +2 -0
  6. package/extension-src/pi-style/domain/config-authorization.ts +6 -3
  7. package/extension-src/pi-style/domain/config-normalization.ts +21 -5
  8. package/extension-src/pi-style/domain/config-presets.ts +1 -1
  9. package/extension-src/pi-style/domain/config-types.ts +7 -3
  10. package/extension-src/pi-style/domain/theme.ts +6 -1
  11. package/extension-src/pi-style/features/editor/index.ts +169 -27
  12. package/extension-src/pi-style/features/messages/index.ts +66 -0
  13. package/extension-src/pi-style/features/tools/bash-execution.ts +112 -0
  14. package/extension-src/pi-style/features/tools/boxed/bash.ts +154 -130
  15. package/extension-src/pi-style/features/tools/boxed/batch.ts +50 -27
  16. package/extension-src/pi-style/features/tools/boxed/command-shape.ts +136 -0
  17. package/extension-src/pi-style/features/tools/boxed/find.ts +2 -2
  18. package/extension-src/pi-style/features/tools/boxed/gh.ts +1012 -0
  19. package/extension-src/pi-style/features/tools/boxed/git.ts +1960 -0
  20. package/extension-src/pi-style/features/tools/boxed/grep.ts +2 -2
  21. package/extension-src/pi-style/features/tools/boxed/output-tree.ts +9 -10
  22. package/extension-src/pi-style/features/tools/boxed/read.ts +3 -3
  23. package/extension-src/pi-style/features/tools/boxed/write.ts +2 -1
  24. package/extension-src/pi-style/pi/compatibility-coordinator.ts +32 -11
  25. package/extension-src/pi-style/pi/compatibility-probe.ts +341 -205
  26. package/extension-src/pi-style/pi/compatibility-registry.ts +19 -3
  27. package/extension-src/pi-style/pi/index.ts +21 -3
  28. package/extension-src/pi-style/pi/session-coordinator.ts +24 -0
  29. package/extension-src/pi-style/shared/box.ts +8 -4
  30. package/extension-src/pi-style/shared/split-diff.ts +9 -9
  31. package/package.json +9 -9
  32. package/themes/titanium-light.json +82 -0
  33. package/themes/titanium.json +79 -0
  34. package/themes/.gitkeep +0 -0
@@ -13,7 +13,7 @@
13
13
 
14
14
  import type { Component } from "@earendil-works/pi-tui";
15
15
  import { stripAnsi } from "../../../shared/ansi.js";
16
- import { type BoxTheme, getTextOutput, shortenPath } from "../../../shared/box.js";
16
+ import { type BoxTheme, dimLine, getTextOutput, shortenPath } from "../../../shared/box.js";
17
17
  import { safeTruncateToWidth } from "../../../shared/render-budget.js";
18
18
  import {
19
19
  type GrepMatch,
@@ -111,7 +111,7 @@ function renderErrorLines(theme: BoxTheme, errorText: string, width: number): st
111
111
  .map((line) => line.trim())
112
112
  .filter((line) => line.length > 0);
113
113
  if (raw.length === 0) return [];
114
- const prefix = `${TREE_INDENT}${theme.fg("borderMuted", "└─")} `;
114
+ const prefix = `${TREE_INDENT}${dimLine("└─")} `;
115
115
  const out = raw
116
116
  .slice(0, GREP_ERROR_LINES)
117
117
  .map((line) => safeTruncateToWidth(`${prefix}${theme.fg("error", line)}`, Math.max(1, width), "…"));
@@ -20,6 +20,7 @@
20
20
  // as one visual family.
21
21
 
22
22
  import type { BoxTheme } from "../../../shared/box.js";
23
+ import { dimLine } from "../../../shared/box.js";
23
24
  import { safeTruncateToWidth } from "../../../shared/render-budget.js";
24
25
 
25
26
  /** Indent for top-level tree rows; matches the quiet-tool batch panel. */
@@ -251,11 +252,11 @@ export function renderOutputTree(
251
252
  const lastIndex = visible.length - 1;
252
253
  for (let i = 0; i < visible.length; i++) {
253
254
  const branch = i < lastIndex || more > 0 ? "├─" : "└─";
254
- const line = `${indent}${theme.fg("borderMuted", branch)} ${theme.fg(entryColor, label(visible[i] ?? ""))}`;
255
+ const line = `${indent}${dimLine(branch)} ${theme.fg(entryColor, label(visible[i] ?? ""))}`;
255
256
  out.push(safeTruncateToWidth(line, safeWidth, "…"));
256
257
  }
257
258
  if (more > 0) {
258
- const line = `${indent}${theme.fg("borderMuted", "└─")} ${theme.fg("dim", `… ${more} more ${pluralForm(moreUnit, more)}`)}`;
259
+ const line = `${indent}${dimLine("└─")} ${theme.fg("dim", `… ${more} more ${pluralForm(moreUnit, more)}`)}`;
259
260
  out.push(safeTruncateToWidth(line, safeWidth, "…"));
260
261
  }
261
262
  return out;
@@ -274,7 +275,7 @@ function formatMatchRow(theme: BoxTheme, match: GrepMatch): string {
274
275
  // Match rows render in the output text color (not primary) so they read like
275
276
  // the matched code; only the file nodes carry the primary color.
276
277
  const label = theme.fg("toolOutput", `*${match.line}`);
277
- const sep = theme.fg("borderMuted", "│");
278
+ const sep = dimLine("│");
278
279
  return `${label}${sep} ${theme.fg("toolOutput", match.content)}`;
279
280
  }
280
281
 
@@ -313,7 +314,7 @@ export function renderGrepTree(
313
314
  if (singleFile) {
314
315
  budget.forEach((match, index) => {
315
316
  const isLast = index === totalVisible - 1 && !truncated;
316
- push(`${indent}${theme.fg("borderMuted", isLast ? "└─" : "├─")} ${formatMatchRow(theme, match)}`);
317
+ push(`${indent}${dimLine(isLast ? "└─" : "├─")} ${formatMatchRow(theme, match)}`);
317
318
  });
318
319
  } else {
319
320
  // Walk the budget, tracking position within each file group so the file
@@ -323,7 +324,7 @@ export function renderGrepTree(
323
324
  const group = groups[gi];
324
325
  if (!group) continue;
325
326
  const isLastGroup = gi === groups.length - 1;
326
- const trunk = isLastGroup ? " " : theme.fg("borderMuted", "│");
327
+ const trunk = isLastGroup ? " " : dimLine("│");
327
328
 
328
329
  const visibleHere: GrepMatch[] = [];
329
330
  for (const match of group.matches) {
@@ -336,22 +337,20 @@ export function renderGrepTree(
336
337
  const groupIsLastRendered = shown >= totalVisible && !truncated;
337
338
  const fileLabel = options.withIcons ? `${fileIcon(group.file)} ${group.file}` : group.file;
338
339
  // File nodes use the primary (accent) color, matching read/ls/find paths.
339
- push(`${indent}${theme.fg("borderMuted", groupIsLastRendered ? "└─" : "├─")} ${theme.fg("accent", fileLabel)}`);
340
+ push(`${indent}${dimLine(groupIsLastRendered ? "└─" : "├─")} ${theme.fg("accent", fileLabel)}`);
340
341
 
341
342
  visibleHere.forEach((match, index) => {
342
343
  const isLastInGroup = index === visibleHere.length - 1;
343
344
  const isLastOverall = groupIsLastRendered && isLastInGroup;
344
345
  push(
345
- `${indent}${trunk}${TREE_CHILD_INDENT}${theme.fg("borderMuted", isLastOverall ? "└─" : "├─")} ${formatMatchRow(theme, match)}`,
346
+ `${indent}${trunk}${TREE_CHILD_INDENT}${dimLine(isLastOverall ? "└─" : "├─")} ${formatMatchRow(theme, match)}`,
346
347
  );
347
348
  });
348
349
  }
349
350
  }
350
351
 
351
352
  if (truncated) {
352
- push(
353
- `${indent}${theme.fg("borderMuted", "└─")} ${theme.fg("dim", `… ${remaining} more ${pluralForm("match", remaining)}`)}`,
354
- );
353
+ push(`${indent}${dimLine("└─")} ${theme.fg("dim", `… ${remaining} more ${pluralForm("match", remaining)}`)}`);
355
354
  }
356
355
  return out;
357
356
  }
@@ -1,9 +1,9 @@
1
1
  // Boxed read tool renderer
2
2
  // (renderCall/renderResult only; no tool re-registration).
3
3
  //
4
- // Read calls render as a boxless tree panel — a lone read is a batch of one,
5
- // consecutive reads group into one panel (see batch.ts). There is no boxed
6
- // single-call special case.
4
+ // Read calls render boxless: a lone read is a single inline line
5
+ // (`➔ Read <path>`), consecutive reads group into one tree panel (see
6
+ // batch.ts).
7
7
 
8
8
  import { stripAnsi } from "../../../shared/ansi.js";
9
9
  import { getTextOutput } from "../../../shared/box.js";
@@ -15,6 +15,7 @@ import { stripAnsi } from "../../../shared/ansi.js";
15
15
  import {
16
16
  type BoxTheme,
17
17
  boxedToolWidthKey,
18
+ dimLine,
18
19
  getTextOutput,
19
20
  renderBoxedToolResult,
20
21
  renderCompactBoxedToolCall,
@@ -62,7 +63,7 @@ function numberedPreviewLines(content: string): NumberedLine[] {
62
63
 
63
64
  /** One boxed preview row: dim gutter + toolOutput content. */
64
65
  function formatNumberedLine(theme: BoxTheme, line: NumberedLine): string {
65
- return `${theme.fg("borderMuted", `${line.number} `)}${theme.fg("toolOutput", line.content)}`;
66
+ return `${dimLine(`${line.number} `)}${theme.fg("toolOutput", line.content)}`;
66
67
  }
67
68
 
68
69
  /** Compact write box: path header, numbered content preview, metrics footer. */
@@ -6,6 +6,7 @@ import {
6
6
  detectPiVersion,
7
7
  disposePiCompatibilityProbe,
8
8
  probePiCompatibility,
9
+ SUPPORTED_VERSION_RANGE,
9
10
  } from "./compatibility-probe.js";
10
11
 
11
12
  export interface CompatibilityCoordinator {
@@ -60,8 +61,10 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
60
61
  report?.unsupported.filter((item) =>
61
62
  feature === "messages" ? item.subtype.includes("message") : item.subtype.includes("tool"),
62
63
  ) ?? [];
63
- const failed = records.some((item) => /identity|failed|unsupported shape/i.test(item.reason));
64
- const fallback = records.some((item) => /fallback|authorization|disabled/i.test(item.reason));
64
+ // Identity drift degrades only the affected surface to native (graceful);
65
+ // a feature is only "failed" when an install/shape error occurred.
66
+ const failed = records.some((item) => /failed|rejected|rolled back|shape is not/i.test(item.reason));
67
+ const fallback = records.some((item) => /fallback|authorization|disabled|identity/i.test(item.reason));
65
68
  const authorized = Boolean(authorization?.core && surfaceAuthorized);
66
69
  const installedRecord = report?.recordSnapshots.some(
67
70
  (item) => item.feature === feature && !item.disposed && (subtype === undefined || item.subtype === subtype),
@@ -69,7 +72,7 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
69
72
  return {
70
73
  configured,
71
74
  authorized,
72
- installed: Boolean(installedRecord && !failed && authorized && configured),
75
+ installed: Boolean(installedRecord && authorized && configured),
73
76
  conflicted: records.some((item) => item.reason.includes("owner")),
74
77
  failed,
75
78
  cleanupPending,
@@ -81,12 +84,13 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
81
84
  configured: messagesConfigured || toolsConfigured,
82
85
  authorized: authorization?.core ?? false,
83
86
  installed: report !== undefined,
84
- conflicted: report?.unsupported.some((item) => item.reason.includes("identity")) ?? false,
85
- failed: report?.unsupported.some((item) => item.reason.includes("failed")) ?? false,
87
+ conflicted: report?.unsupported.some((item) => item.reason.includes("owner")) ?? false,
88
+ failed: report?.unsupported.some((item) => /failed|rejected|rolled back/i.test(item.reason)) ?? false,
86
89
  cleanupPending,
87
- nativeFallbacks: report?.unsupported.filter((item) => item.reason.includes("fallback")).length ?? 0,
90
+ nativeFallbacks: report?.unsupported.filter((item) => /fallback|identity/i.test(item.reason)).length ?? 0,
88
91
  piVersion: version.version ?? report?.piVersion ?? "unknown",
89
- versionRange: report?.versionRange ?? ">=0.83.0 <0.84.0",
92
+ versionRange: report?.versionRange ?? SUPPORTED_VERSION_RANGE,
93
+ supportedVersions: report?.supportedVersions ?? [],
90
94
  assistantMessage: surface(
91
95
  "messages",
92
96
  config.enabled && config.messages.enabled && config.messages.assistantPrefix,
@@ -105,11 +109,13 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
105
109
  const productDenied = productGate === "deny";
106
110
  if (cleanupPending && !report) cleanupPending = false;
107
111
  if (cleanupPending || !tui || !config.enabled || !authorization?.core || productDenied) return undefined;
108
- const certifiedHost = detectPiVersion().version === "0.83.0";
112
+ // Certification is per-surface identity (fingerprint) based, never pinned to
113
+ // a Pi version: authorization only needs the session flags + config. Version
114
+ // drift that preserves identities keeps working; changed identities degrade
115
+ // per-surface inside the probe.
109
116
  const assistantEnabled =
110
117
  authorization.assistant &&
111
118
  isTierCAuthorized({
112
- certifiedHost,
113
119
  coreFlag: authorization.core,
114
120
  surfaceFlag: true,
115
121
  surface: "assistantMessage",
@@ -118,16 +124,29 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
118
124
  const specialBlocksEnabled =
119
125
  authorization.specialBlocks &&
120
126
  isTierCAuthorized({
121
- certifiedHost,
122
127
  coreFlag: authorization.core,
123
128
  surfaceFlag: true,
124
129
  surface: "specialBlocks",
125
130
  config,
126
131
  });
127
132
  const messagesEnabled = (assistantEnabled || specialBlocksEnabled) && config.messages.enabled;
133
+ // The hidden-thinking collapse is an assistant-message surface patch: it needs
134
+ // the assistant flag and `messages.hideThinkingLabel`, independent of the
135
+ // assistant prefix feature.
136
+ const thinkingCollapseEnabled = Boolean(
137
+ authorization.assistant &&
138
+ config.messages.enabled &&
139
+ config.messages.hideThinkingLabel &&
140
+ isTierCAuthorized({
141
+ coreFlag: authorization.core,
142
+ surfaceFlag: true,
143
+ surface: "messages",
144
+ config,
145
+ }),
146
+ );
128
147
  const toolsEnabled =
129
148
  authorization.tools &&
130
- isTierCAuthorized({ certifiedHost, coreFlag: authorization.core, surfaceFlag: true, surface: "tools", config });
149
+ isTierCAuthorized({ coreFlag: authorization.core, surfaceFlag: true, surface: "tools", config });
131
150
  if (!messagesEnabled && !toolsEnabled) return undefined;
132
151
  const detected = detectPiVersion();
133
152
  report = probePiCompatibility(detected.version, {
@@ -137,6 +156,7 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
137
156
  ...config.messages,
138
157
  enabled: messagesEnabled,
139
158
  assistantPrefix: assistantEnabled,
159
+ hideThinkingLabel: thinkingCollapseEnabled,
140
160
  specialBlocks: messagesEnabled && config.messages.specialBlocks && specialBlocksEnabled,
141
161
  },
142
162
  tools: {
@@ -147,6 +167,7 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
147
167
  messageSnapshot: {
148
168
  assistantPrefix: authorization.ascii ? "[assistant] " : "│ ",
149
169
  assistantEnabled,
170
+ collapseHiddenThinking: thinkingCollapseEnabled,
150
171
  },
151
172
  toolSnapshot: {
152
173
  callMarker: authorization.ascii ? "[tool] " : "[tool] ",