@hheei/pi-ext-ui 0.2.0 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hheei/pi-ext-ui",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Collapsible tool rendering frame and tool renderer pilot for Pi.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,15 +36,15 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@hheei/pi-ext-core": "^0.3.0"
39
+ "@hheei/pi-ext-core": "^0.4.0"
40
40
  },
41
41
  "peerDependencies": {
42
- "@earendil-works/pi-coding-agent": ">=1.2.0",
43
- "@earendil-works/pi-tui": ">=1.2.0"
42
+ "@earendil-works/pi-coding-agent": ">=1.3.0",
43
+ "@earendil-works/pi-tui": ">=1.3.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@earendil-works/pi-coding-agent": "1.2.0",
47
- "@earendil-works/pi-tui": "1.2.0"
46
+ "@earendil-works/pi-coding-agent": "1.3.0",
47
+ "@earendil-works/pi-tui": "1.3.0"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "node -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json"
@@ -1,10 +0,0 @@
1
- import type { Theme } from "@earendil-works/pi-coding-agent";
2
- /**
3
- * Display contract mirror of pi-ext-tools' MUTATION_GLYPH/MUTATION_TONE: how one
4
- * mutation outcome is drawn. The persisted `ApplyPatchToolDetails` carries the
5
- * outcome facts; pi-ext-ui never imports pi-ext-tools, so the glyph vocabulary is
6
- * restated here as part of the rendering contract it consumes.
7
- */
8
- export declare const APPLY_PATCH_GLYPH: Record<string, string>;
9
- export declare const APPLY_PATCH_TONE: Record<string, "success" | "warning" | "dim" | "error">;
10
- export declare function applyPatchGlyph(status: string, theme: Theme): string;
@@ -1,29 +0,0 @@
1
- /**
2
- * Display contract mirror of pi-ext-tools' MUTATION_GLYPH/MUTATION_TONE: how one
3
- * mutation outcome is drawn. The persisted `ApplyPatchToolDetails` carries the
4
- * outcome facts; pi-ext-ui never imports pi-ext-tools, so the glyph vocabulary is
5
- * restated here as part of the rendering contract it consumes.
6
- */
7
- export const APPLY_PATCH_GLYPH = {
8
- pending: "⋯",
9
- applied: "✓",
10
- partial: "▲",
11
- fuzzy: "▲",
12
- unconfirmed: "▲",
13
- not_applied: "·",
14
- rejected: "✗",
15
- };
16
- export const APPLY_PATCH_TONE = {
17
- pending: "dim",
18
- applied: "success",
19
- partial: "warning",
20
- fuzzy: "warning",
21
- unconfirmed: "warning",
22
- not_applied: "dim",
23
- rejected: "error",
24
- };
25
- export function applyPatchGlyph(status, theme) {
26
- const glyph = APPLY_PATCH_GLYPH[status] ?? "✗";
27
- const tone = APPLY_PATCH_TONE[status] ?? "error";
28
- return theme.fg(tone, glyph);
29
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Display-only recognition of V4A operation headers in a patch that is still
3
- * streaming. Mirrors the recognition rules pi-ext-tools' parser applies, but
4
- * only as far as drawing `󰄰 create|modify|delete path +A -D` preview rows
5
- * requires: a line is a candidate header, a payload line, or stops the preview.
6
- */
7
- export interface V4aPreviewOperationView {
8
- readonly kind: "add" | "delete" | "update";
9
- readonly path: string;
10
- readonly addedLines: number;
11
- readonly removedLines: number;
12
- }
13
- /** Opaque cursor stored in the render state across streamed updates. */
14
- export interface V4aPreviewCursor {
15
- consumed: string;
16
- operations: MutablePreviewOperation[];
17
- stopped: boolean;
18
- }
19
- type MutablePreviewOperation = {
20
- kind: "add" | "delete" | "update";
21
- path: string;
22
- addedLines: number;
23
- removedLines: number;
24
- };
25
- /**
26
- * Incremental preview of the operation headers completed so far. The cursor
27
- * lives in the render state so each streamed update only processes new lines.
28
- */
29
- export declare function previewV4aOperations(input: string, argsComplete: boolean, cursor: V4aPreviewCursor | undefined): readonly V4aPreviewOperationView[];
30
- export {};
@@ -1,89 +0,0 @@
1
- const ADD = "*** Add File: ";
2
- const DELETE = "*** Delete File: ";
3
- const UPDATE = "*** Update File: ";
4
- const MOVE = "*** Move to: ";
5
- const BEGIN = "*** Begin Patch";
6
- const END = "*** End Patch";
7
- const MAX_PREVIEW_OPERATIONS = 128;
8
- function parseHeader(text) {
9
- if (text.startsWith(ADD))
10
- return { kind: "add", path: text.slice(ADD.length) };
11
- if (text.startsWith(DELETE))
12
- return { kind: "delete", path: text.slice(DELETE.length) };
13
- if (text.startsWith(UPDATE))
14
- return { kind: "update", path: text.slice(UPDATE.length) };
15
- return undefined;
16
- }
17
- function previewablePath(path) {
18
- return path !== "" && !path.includes("\0") && path !== BEGIN && path !== END;
19
- }
20
- function applyPreviewLine(state, text) {
21
- if (text === BEGIN || text === END)
22
- return true;
23
- const header = parseHeader(text);
24
- if (header !== undefined) {
25
- if (!previewablePath(header.path) || state.operations.length >= MAX_PREVIEW_OPERATIONS)
26
- return false;
27
- state.operations.push({ kind: header.kind, path: header.path, addedLines: 0, removedLines: 0 });
28
- return true;
29
- }
30
- const current = state.operations.at(-1);
31
- if (current === undefined)
32
- return true;
33
- if (current.kind === "update" && text.startsWith(MOVE)) {
34
- const moveTo = text.slice(MOVE.length);
35
- if (!previewablePath(moveTo))
36
- return false;
37
- current.path = moveTo;
38
- return true;
39
- }
40
- if (current.kind === "add") {
41
- if (!text.startsWith("+"))
42
- return false;
43
- if (text.slice(1).length > 0)
44
- current.addedLines += 1;
45
- return true;
46
- }
47
- if (current.kind === "delete")
48
- return false;
49
- if (text.startsWith("@@") || text.startsWith(" "))
50
- return true;
51
- if (text.startsWith("+")) {
52
- current.addedLines += 1;
53
- return true;
54
- }
55
- if (text.startsWith("-")) {
56
- current.removedLines += 1;
57
- return true;
58
- }
59
- return false;
60
- }
61
- /**
62
- * Incremental preview of the operation headers completed so far. The cursor
63
- * lives in the render state so each streamed update only processes new lines.
64
- */
65
- export function previewV4aOperations(input, argsComplete, cursor) {
66
- const state = cursor ?? { consumed: "", operations: [], stopped: false };
67
- if (!input.startsWith(state.consumed)) {
68
- state.consumed = "";
69
- state.operations = [];
70
- state.stopped = false;
71
- }
72
- if (state.stopped)
73
- return state.operations.map((operation) => ({ ...operation }));
74
- const lines = (input.slice(state.consumed.length).match(/[^\n]*\n?/g) ?? []).filter((line) => line !== "");
75
- for (const [index, raw] of lines.entries()) {
76
- const newline = raw.endsWith("\n") ? "\n" : "";
77
- const text = newline === "" ? raw : raw.slice(0, -1).replace(/\r$/, "");
78
- const complete = newline !== "" || (argsComplete && index === lines.length - 1);
79
- if (!complete)
80
- break;
81
- const applied = applyPreviewLine(state, text);
82
- if (!applied) {
83
- state.stopped = true;
84
- break;
85
- }
86
- state.consumed += raw;
87
- }
88
- return state.operations.map((operation) => ({ ...operation }));
89
- }
@@ -1,47 +0,0 @@
1
- import type { Theme } from "@earendil-works/pi-coding-agent";
2
- /**
3
- * Display-side view of pi-ext-tools' persisted `ApplyPatchToolDetails`.
4
- * The details are the contract; every field is read defensively at this
5
- * untrusted boundary and dropped when it does not match the contract.
6
- */
7
- export interface ApplyPatchOperationView {
8
- readonly operationIndex: number;
9
- readonly kind: "add" | "delete" | "update";
10
- readonly path: string;
11
- readonly addedLines: number;
12
- readonly removedLines: number;
13
- readonly status: string;
14
- readonly appliedHunks: number | undefined;
15
- readonly totalHunks: number | undefined;
16
- readonly partialReason: string | undefined;
17
- }
18
- export interface ApplyPatchSnapshotView {
19
- readonly path: string;
20
- readonly startLine: number;
21
- readonly before: readonly string[];
22
- readonly after: readonly string[];
23
- }
24
- export interface ApplyPatchRejectionView {
25
- readonly paths: readonly string[];
26
- readonly error: string;
27
- readonly diagnostics: readonly {
28
- readonly text: string;
29
- }[];
30
- }
31
- export interface ApplyPatchFacts {
32
- readonly status: "success" | "partial" | "failed";
33
- readonly operations: readonly ApplyPatchOperationView[];
34
- readonly snapshots: readonly ApplyPatchSnapshotView[];
35
- readonly rejected: readonly ApplyPatchRejectionView[];
36
- readonly unconfirmed: readonly ApplyPatchRejectionView[];
37
- readonly notApplied: readonly ApplyPatchRejectionView[];
38
- readonly addedLines: number;
39
- readonly removedLines: number;
40
- readonly changedPaths: readonly string[];
41
- readonly target: string | undefined;
42
- readonly inProgress: boolean;
43
- }
44
- /** Reads only the persisted apply_patch details; other shapes return undefined. */
45
- export declare function applyPatchFacts(details: unknown): ApplyPatchFacts | undefined;
46
- /** One operation row: glyph, verb, path, delta, fuzzy marker, hunk summary. */
47
- export declare function formatApplyPatchOperationRow(operation: ApplyPatchOperationView, theme: Theme, host: string | undefined): string;
@@ -1,198 +0,0 @@
1
- import { applyPatchGlyph } from "./apply-patch-glyph.js";
2
- const PATCH_STATUSES = ["success", "partial", "failed"];
3
- function isPatchStatus(value) {
4
- return PATCH_STATUSES.includes(value);
5
- }
6
- const OPERATION_STATUSES = new Set([
7
- "pending",
8
- "applied",
9
- "partial",
10
- "fuzzy",
11
- "unconfirmed",
12
- "not_applied",
13
- "rejected",
14
- ]);
15
- function isRecord(value) {
16
- return typeof value === "object" && value !== null && !Array.isArray(value);
17
- }
18
- function operationView(value) {
19
- const path = typeof value.path === "string" ? value.path : undefined;
20
- if (path === undefined)
21
- return undefined;
22
- const kind = value.kind === "add" || value.kind === "delete" || value.kind === "update"
23
- ? value.kind
24
- : undefined;
25
- if (kind === undefined)
26
- return undefined;
27
- const status = typeof value.status === "string" && OPERATION_STATUSES.has(value.status)
28
- ? value.status
29
- : undefined;
30
- if (status === undefined)
31
- return undefined;
32
- return {
33
- operationIndex: typeof value.operationIndex === "number" ? value.operationIndex : 0,
34
- kind,
35
- path,
36
- addedLines: typeof value.addedLines === "number" ? value.addedLines : 0,
37
- removedLines: typeof value.removedLines === "number" ? value.removedLines : 0,
38
- status,
39
- appliedHunks: typeof value.appliedHunks === "number" ? value.appliedHunks : undefined,
40
- totalHunks: typeof value.totalHunks === "number" ? value.totalHunks : undefined,
41
- partialReason: typeof value.partialReason === "string" ? value.partialReason : undefined,
42
- };
43
- }
44
- function snapshotView(value) {
45
- const path = typeof value.path === "string" ? value.path : undefined;
46
- if (path === undefined)
47
- return undefined;
48
- const before = Array.isArray(value.before)
49
- ? value.before.filter((line) => typeof line === "string")
50
- : undefined;
51
- const after = Array.isArray(value.after)
52
- ? value.after.filter((line) => typeof line === "string")
53
- : undefined;
54
- if (before === undefined || after === undefined)
55
- return undefined;
56
- return {
57
- path,
58
- startLine: typeof value.startLine === "number" && value.startLine > 0 ? value.startLine : 1,
59
- before,
60
- after,
61
- };
62
- }
63
- function rejectionView(value) {
64
- const error = typeof value.error === "string" ? value.error : undefined;
65
- if (error === undefined)
66
- return undefined;
67
- const paths = Array.isArray(value.paths)
68
- ? value.paths.filter((path) => typeof path === "string")
69
- : [];
70
- const diagnostics = Array.isArray(value.diagnostics)
71
- ? value.diagnostics
72
- .filter(isRecord)
73
- .map((diagnostic) => {
74
- if (diagnostic.kind === "context_not_found") {
75
- return typeof diagnostic.hint === "string"
76
- ? `context not found (${diagnostic.hint})`
77
- : "context not found";
78
- }
79
- if (diagnostic.kind === "ambiguous_exact") {
80
- const candidates = Array.isArray(diagnostic.candidateStartLines)
81
- ? diagnostic.candidateStartLines.filter((line) => typeof line === "number")
82
- : [];
83
- return `exact context is ambiguous at lines ${candidates.join(", ")}`;
84
- }
85
- return undefined;
86
- })
87
- .filter((text) => text !== undefined)
88
- .map((text) => ({ text }))
89
- : [];
90
- return { paths, error, diagnostics };
91
- }
92
- function rejectionList(value) {
93
- if (!Array.isArray(value))
94
- return [];
95
- const views = [];
96
- for (const entry of value) {
97
- if (!isRecord(entry))
98
- continue;
99
- const view = rejectionView(entry);
100
- if (view !== undefined)
101
- views.push(view);
102
- }
103
- return views;
104
- }
105
- /** Reads only the persisted apply_patch details; other shapes return undefined. */
106
- export function applyPatchFacts(details) {
107
- if (!isRecord(details))
108
- return undefined;
109
- if (typeof details.status !== "string" || !isPatchStatus(details.status))
110
- return undefined;
111
- const status = details.status;
112
- const operations = Array.isArray(details.operations)
113
- ? details.operations
114
- .filter(isRecord)
115
- .map(operationView)
116
- .filter((operation) => operation !== undefined)
117
- : [];
118
- // Older results carry applied/rejected lists instead of operations; the operation
119
- // rows are rebuilt from them without hunk counts.
120
- const applied = Array.isArray(details.applied) ? details.applied.filter(isRecord) : [];
121
- const rejected = rejectionList(details.rejected);
122
- if (operations.length === 0) {
123
- for (const operation of applied) {
124
- const path = typeof operation.paths === "object" &&
125
- operation.paths !== null &&
126
- Array.isArray(operation.paths) &&
127
- typeof operation.paths[0] === "string"
128
- ? operation.paths[0]
129
- : undefined;
130
- if (path === undefined)
131
- continue;
132
- const view = operationView({
133
- operationIndex: operation.operationIndex,
134
- kind: operation.kind,
135
- path,
136
- addedLines: 0,
137
- removedLines: 0,
138
- status: "applied",
139
- });
140
- if (view !== undefined)
141
- operations.push(view);
142
- }
143
- for (const [index, rejection] of rejected.entries()) {
144
- const view = operationView({
145
- operationIndex: index,
146
- kind: "update",
147
- path: rejection.paths[0] ?? "<unknown>",
148
- addedLines: 0,
149
- removedLines: 0,
150
- status: "rejected",
151
- });
152
- if (view !== undefined)
153
- operations.push(view);
154
- }
155
- }
156
- const snapshots = Array.isArray(details.applied)
157
- ? details.applied.filter(isRecord).flatMap((operation) => Array.isArray(operation.snapshots)
158
- ? operation.snapshots
159
- .filter(isRecord)
160
- .map(snapshotView)
161
- .filter((snapshot) => snapshot !== undefined)
162
- : [])
163
- : [];
164
- return {
165
- status,
166
- operations,
167
- snapshots,
168
- rejected,
169
- unconfirmed: rejectionList(details.unconfirmed),
170
- notApplied: rejectionList(details.notApplied),
171
- addedLines: typeof details.addedLines === "number" ? details.addedLines : 0,
172
- removedLines: typeof details.removedLines === "number" ? details.removedLines : 0,
173
- changedPaths: Array.isArray(details.changedPaths)
174
- ? details.changedPaths.filter((path) => typeof path === "string")
175
- : [],
176
- target: typeof details.target === "string" && details.target !== "local" ? details.target : undefined,
177
- inProgress: isRecord(details.progress),
178
- };
179
- }
180
- /** One operation row: glyph, verb, path, delta, fuzzy marker, hunk summary. */
181
- export function formatApplyPatchOperationRow(operation, theme, host) {
182
- const glyph = applyPatchGlyph(operation.status, theme);
183
- const kind = operation.kind === "add" ? "create" : operation.kind === "delete" ? "delete" : "modify";
184
- const path = host === undefined ? operation.path : `${theme.fg("warning", `${host}:`)}${operation.path}`;
185
- const deltaParts = [];
186
- if (operation.addedLines > 0)
187
- deltaParts.push(theme.fg("success", `+${String(operation.addedLines)}`));
188
- if (operation.removedLines > 0)
189
- deltaParts.push(theme.fg("error", `-${String(operation.removedLines)}`));
190
- const delta = deltaParts.length > 0 ? ` (${deltaParts.join(" ")})` : "";
191
- const score = operation.status === "fuzzy" ? ` ${theme.fg("dim", "fuzzy")}` : "";
192
- const hunks = operation.status === "partial" &&
193
- operation.appliedHunks !== undefined &&
194
- operation.totalHunks !== undefined
195
- ? ` ${theme.fg("dim", `(${String(operation.appliedHunks)}/${String(operation.totalHunks)} hunks applied${operation.partialReason === undefined ? "" : `; ${operation.partialReason}`})`)}`
196
- : "";
197
- return `${glyph} ${theme.fg("toolTitle", kind)} ${path}${delta}${score}${hunks}`.trimEnd();
198
- }