@quandev104/pi-style 0.1.1

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 (69) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +193 -0
  4. package/dist/extensions/pi-style.js +7897 -0
  5. package/dist/extensions/pi-style.js.map +1 -0
  6. package/extension-src/pi-style/app/command-service.ts +244 -0
  7. package/extension-src/pi-style/app/commands.ts +12 -0
  8. package/extension-src/pi-style/app/config-storage.ts +98 -0
  9. package/extension-src/pi-style/app/doctor.ts +53 -0
  10. package/extension-src/pi-style/app/index.ts +322 -0
  11. package/extension-src/pi-style/app/providers.ts +268 -0
  12. package/extension-src/pi-style/app/render-scheduler.ts +48 -0
  13. package/extension-src/pi-style/app/runtime.ts +404 -0
  14. package/extension-src/pi-style/app/snapshot.ts +15 -0
  15. package/extension-src/pi-style/domain/capabilities.ts +34 -0
  16. package/extension-src/pi-style/domain/config-authorization.ts +20 -0
  17. package/extension-src/pi-style/domain/config-diagnostics.ts +20 -0
  18. package/extension-src/pi-style/domain/config-diff.ts +30 -0
  19. package/extension-src/pi-style/domain/config-migrations.ts +52 -0
  20. package/extension-src/pi-style/domain/config-normalization.ts +567 -0
  21. package/extension-src/pi-style/domain/config-presets.ts +51 -0
  22. package/extension-src/pi-style/domain/config-types.ts +115 -0
  23. package/extension-src/pi-style/domain/providers.ts +37 -0
  24. package/extension-src/pi-style/domain/status-presets.ts +60 -0
  25. package/extension-src/pi-style/domain/status-renderer.ts +142 -0
  26. package/extension-src/pi-style/domain/status.ts +430 -0
  27. package/extension-src/pi-style/domain/theme.ts +232 -0
  28. package/extension-src/pi-style/features/.gitkeep +0 -0
  29. package/extension-src/pi-style/features/editor/index.ts +304 -0
  30. package/extension-src/pi-style/features/messages/boxed-block.ts +74 -0
  31. package/extension-src/pi-style/features/messages/index.ts +145 -0
  32. package/extension-src/pi-style/features/messages/special-blocks.ts +281 -0
  33. package/extension-src/pi-style/features/startup/index.ts +564 -0
  34. package/extension-src/pi-style/features/startup/logo.ts +151 -0
  35. package/extension-src/pi-style/features/status-line/index.ts +319 -0
  36. package/extension-src/pi-style/features/tools/boxed/bash.ts +347 -0
  37. package/extension-src/pi-style/features/tools/boxed/edit.ts +113 -0
  38. package/extension-src/pi-style/features/tools/boxed/fallback.ts +67 -0
  39. package/extension-src/pi-style/features/tools/boxed/find.ts +65 -0
  40. package/extension-src/pi-style/features/tools/boxed/grep.ts +108 -0
  41. package/extension-src/pi-style/features/tools/boxed/index.ts +64 -0
  42. package/extension-src/pi-style/features/tools/boxed/ls.ts +65 -0
  43. package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +190 -0
  44. package/extension-src/pi-style/features/tools/boxed/read.ts +204 -0
  45. package/extension-src/pi-style/features/tools/boxed/session-config.ts +45 -0
  46. package/extension-src/pi-style/features/tools/boxed/shared.ts +157 -0
  47. package/extension-src/pi-style/features/tools/boxed/write.ts +89 -0
  48. package/extension-src/pi-style/features/tools/index.ts +480 -0
  49. package/extension-src/pi-style/pi/commands.ts +17 -0
  50. package/extension-src/pi-style/pi/compatibility-coordinator.ts +195 -0
  51. package/extension-src/pi-style/pi/compatibility-probe.ts +645 -0
  52. package/extension-src/pi-style/pi/compatibility-registry.ts +329 -0
  53. package/extension-src/pi-style/pi/config-host.ts +52 -0
  54. package/extension-src/pi-style/pi/config-session.ts +105 -0
  55. package/extension-src/pi-style/pi/index.ts +77 -0
  56. package/extension-src/pi-style/pi/operational-state.ts +29 -0
  57. package/extension-src/pi-style/pi/session-coordinator.ts +187 -0
  58. package/extension-src/pi-style/pi/session-usage.ts +62 -0
  59. package/extension-src/pi-style/pi/startup-resources.ts +70 -0
  60. package/extension-src/pi-style/shared/.gitkeep +0 -0
  61. package/extension-src/pi-style/shared/ansi.ts +386 -0
  62. package/extension-src/pi-style/shared/box.ts +805 -0
  63. package/extension-src/pi-style/shared/disposable-store.ts +28 -0
  64. package/extension-src/pi-style/shared/elapsed.ts +88 -0
  65. package/extension-src/pi-style/shared/render-budget.ts +367 -0
  66. package/extension-src/pi-style/shared/split-diff.ts +692 -0
  67. package/extension-src/pi-style/shared/theme-extras.ts +292 -0
  68. package/package.json +68 -0
  69. package/themes/.gitkeep +0 -0
@@ -0,0 +1,157 @@
1
+ // Shared context/view types + common helpers for the boxed tool renderers.
2
+
3
+ import type { Component } from "@earendil-works/pi-tui";
4
+ import type { BoxTheme } from "../../../shared/box.js";
5
+ import {
6
+ boxedToolWidthKey,
7
+ clearCompactBoxedFooter,
8
+ formatBoxedFooter,
9
+ formatBoxedFooterFromValues,
10
+ getTextOutput,
11
+ renderCompactBoxedFooter,
12
+ renderCompactBoxedToolCall,
13
+ resolveRelativePath,
14
+ shortenPath,
15
+ } from "../../../shared/box.js";
16
+ import { getStateElapsedMs, recordExecutionStarted } from "./session-config.js";
17
+
18
+ /** Renderer context delivered by Pi's ToolExecutionComponent (getRenderContext). */
19
+ export interface BoxedToolContext {
20
+ readonly args: Record<string, unknown>;
21
+ readonly toolCallId: string;
22
+ readonly state: Record<string, unknown>;
23
+ readonly cwd: string;
24
+ readonly executionStarted: boolean;
25
+ readonly argsComplete: boolean;
26
+ readonly isPartial: boolean;
27
+ readonly expanded: boolean;
28
+ readonly showImages: boolean;
29
+ readonly isError: boolean;
30
+ readonly lastComponent?: unknown;
31
+ }
32
+
33
+ /** Result view delivered to result renderers: { content, details }. */
34
+ export interface BoxedToolResult {
35
+ readonly content?: readonly unknown[];
36
+ readonly details?: unknown;
37
+ }
38
+
39
+ export type BoxedCallRenderer = (
40
+ args: Record<string, unknown>,
41
+ theme: BoxTheme,
42
+ context: BoxedToolContext,
43
+ ) => Component;
44
+ export type BoxedResultRenderer = (
45
+ result: BoxedToolResult,
46
+ options: { expanded: boolean; isPartial: boolean },
47
+ theme: BoxTheme,
48
+ context: BoxedToolContext,
49
+ ) => Component;
50
+
51
+ export interface BoxedToolDefinition {
52
+ readonly call: BoxedCallRenderer;
53
+ readonly result: BoxedResultRenderer;
54
+ }
55
+
56
+ export function pendingFlag(context: BoxedToolContext): boolean {
57
+ return Boolean(context.isPartial);
58
+ }
59
+
60
+ /** Normalized display path: shortens HOME and resolves relative to the session cwd. */
61
+ export function displayPath(rawPath: string, context: BoxedToolContext): string {
62
+ const path = String(rawPath ?? "");
63
+ if (!path) return "(unknown)";
64
+ return shortenPath(resolveRelativePath(path, context.cwd));
65
+ }
66
+
67
+ export function pathRangeDetail(rawPath: string, offset: unknown, limit: unknown, context: BoxedToolContext): string {
68
+ const path = displayPath(rawPath, context);
69
+ let range = "";
70
+ if (offset !== undefined || limit !== undefined) {
71
+ const start = offset ?? 1;
72
+ const end = limit !== undefined ? Number(start) + Number(limit) - 1 : "";
73
+ range = `:${start}${end ? `-${end}` : ""}`;
74
+ }
75
+ return path ? `${path}${range}` : "(unknown)";
76
+ }
77
+
78
+ /** Compact boxed call header for summary-style tools (read/write/ls/find/grep). */
79
+ export function compactCall(
80
+ theme: BoxTheme,
81
+ toolName: string,
82
+ detailLine: string,
83
+ options: { detailKey: string; context: BoxedToolContext },
84
+ ): Component {
85
+ return renderCompactBoxedToolCall(theme, toolName, detailLine, {
86
+ widthKey: boxedToolWidthKey(toolName, options.detailKey),
87
+ state: options.context.state,
88
+ isError: Boolean(options.context.isError),
89
+ isPartial: Boolean(options.context.isPartial),
90
+ isPending: pendingFlag(options.context),
91
+ });
92
+ }
93
+
94
+ /** Record wall-clock start when execution begins (first render with executionStarted). */
95
+ export function noteExecutionStart(context: BoxedToolContext): void {
96
+ recordExecutionStarted(context.state, context.executionStarted);
97
+ }
98
+
99
+ export function stateElapsedMs(context: BoxedToolContext): number | undefined {
100
+ return getStateElapsedMs(context.state);
101
+ }
102
+
103
+ /** Footer parts with state-based elapsed when result.details lacks timing. */
104
+ export function boxedFooterWithState(
105
+ theme: BoxTheme,
106
+ result: BoxedToolResult | undefined,
107
+ context: BoxedToolContext,
108
+ extraParts: string[] = [],
109
+ ): string {
110
+ return formatBoxedFooterFromValues(theme, stateElapsedMs(context), getTextOutput(result), extraParts);
111
+ }
112
+
113
+ export function compactFooterWithState(
114
+ theme: BoxTheme,
115
+ result: BoxedToolResult,
116
+ context: BoxedToolContext,
117
+ options: { isError?: boolean; isPartial?: boolean } = {},
118
+ ): Component {
119
+ const elapsedMs = stateElapsedMs(context);
120
+ return renderCompactBoxedFooter(theme, result, {
121
+ state: context.state,
122
+ isError: Boolean(options.isError ?? context.isError),
123
+ isPartial: Boolean(options.isPartial ?? context.isPartial),
124
+ ...(elapsedMs === undefined ? {} : { elapsedMs }),
125
+ });
126
+ }
127
+
128
+ export function resultFooterLines(
129
+ theme: BoxTheme,
130
+ result: BoxedToolResult,
131
+ context: BoxedToolContext,
132
+ extraParts: string[] = [],
133
+ ): string[] {
134
+ return [formatBoxedFooter(theme, result, extraParts, stateElapsedMs(context))];
135
+ }
136
+
137
+ export function clearFooterState(context: BoxedToolContext): void {
138
+ clearCompactBoxedFooter(context.state);
139
+ }
140
+
141
+ /** Result-details truncation line count when the native tool truncated output. */
142
+ export function truncationOutputLines(result: BoxedToolResult | undefined): number | undefined {
143
+ if (!result) return undefined;
144
+ const details = result.details as { truncation?: { outputLines?: number } } | undefined;
145
+ const value = details?.truncation?.outputLines;
146
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
147
+ }
148
+
149
+ /** Result-details grep match-limit reached counter. */
150
+ export function matchLimitReached(result: BoxedToolResult | undefined): number | undefined {
151
+ if (!result) return undefined;
152
+ const details = result.details as { matchLimitReached?: number } | undefined;
153
+ const value = details?.matchLimitReached;
154
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
155
+ }
156
+
157
+ export { boxedToolWidthKey, getTextOutput, resolveRelativePath, shortenPath };
@@ -0,0 +1,89 @@
1
+ // Boxed write tool renderer
2
+ // (renderCall/renderResult only).
3
+
4
+ import { stripAnsi } from "../../../shared/ansi.js";
5
+ import { boxedToolWidthKey, getTextOutput, renderBoxedToolResult, stripTrailingNotice } from "../../../shared/box.js";
6
+ import {
7
+ type BoxedToolDefinition,
8
+ clearFooterState,
9
+ compactCall,
10
+ compactFooterWithState,
11
+ displayPath,
12
+ noteExecutionStart,
13
+ resultFooterLines,
14
+ } from "./shared.js";
15
+
16
+ function parseWriteSummary(output: string): string | undefined {
17
+ const normalized = stripTrailingNotice(stripAnsi(output ?? "")).trim();
18
+ if (!normalized) return undefined;
19
+
20
+ const byteMatch = normalized.match(/\bwrote\s+(\d+)\s+bytes?\b/i);
21
+ if (byteMatch) {
22
+ const bytes = Number(byteMatch[1]);
23
+ if (Number.isFinite(bytes)) {
24
+ return `↳ Wrote ${bytes} ${bytes === 1 ? "byte" : "bytes"}.`;
25
+ }
26
+ }
27
+
28
+ const lineMatch = normalized.match(/\bwrote\s+(\d+)\s+lines?\b/i);
29
+ if (lineMatch) {
30
+ const count = Number(lineMatch[1]);
31
+ if (Number.isFinite(count)) {
32
+ return `↳ Wrote ${count} ${count === 1 ? "line" : "lines"}.`;
33
+ }
34
+ }
35
+
36
+ return undefined;
37
+ }
38
+
39
+ export const writeTool: BoxedToolDefinition = {
40
+ call(args, theme, context) {
41
+ noteExecutionStart(context);
42
+ const detail = displayPath(String(args?.path ?? args?.file_path ?? ""), context);
43
+ return compactCall(theme, "Write", `${theme.fg("dim", "Path: ")}${detail}`, {
44
+ detailKey: detail,
45
+ context,
46
+ });
47
+ },
48
+ result(result, options, theme, context) {
49
+ clearFooterState(context);
50
+ const output = getTextOutput(result);
51
+ const detail = displayPath(String(context?.args?.path ?? context?.args?.file_path ?? ""), context);
52
+ const widthKey = boxedToolWidthKey("Write", detail);
53
+
54
+ if (context.isError) {
55
+ return renderBoxedToolResult(theme, () => [theme.fg("error", stripAnsi(output).trim() || "Error")], {
56
+ widthKey,
57
+ footerLines: resultFooterLines(theme, result, context),
58
+ isError: true,
59
+ });
60
+ }
61
+
62
+ if (!options.expanded) return compactFooterWithState(theme, result, context);
63
+
64
+ const content = String(context?.args?.content ?? "");
65
+ const lineCount = content ? content.split("\n").length : 0;
66
+ if (lineCount > 0) {
67
+ const summary = `↳ Wrote ${lineCount} ${lineCount === 1 ? "line" : "lines"}.`;
68
+ return renderBoxedToolResult(theme, () => [theme.fg("dim", summary)], {
69
+ widthKey,
70
+ footerLines: resultFooterLines(theme, result, context),
71
+ });
72
+ }
73
+
74
+ const summary = parseWriteSummary(output);
75
+ if (summary) {
76
+ return renderBoxedToolResult(theme, () => [theme.fg("dim", summary)], {
77
+ widthKey,
78
+ footerLines: resultFooterLines(theme, result, context),
79
+ });
80
+ }
81
+
82
+ const normalized = stripTrailingNotice(stripAnsi(output)).trim();
83
+ const fallback = normalized ? `↳ ${normalized}` : "↳ Wrote file.";
84
+ return renderBoxedToolResult(theme, () => [theme.fg("dim", fallback)], {
85
+ widthKey,
86
+ footerLines: resultFooterLines(theme, result, context),
87
+ });
88
+ },
89
+ };