@milanglacier/pi-plan-mode 0.5.4 → 0.5.5
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 +1 -1
- package/qna/index.ts +0 -1
- package/qna/qna-tui.ts +13 -36
- package/qna/scroll-select.ts +1 -42
- package/request-user-input.ts +2 -4
- package/state.ts +1 -9
- package/qna/pi-tui-loader.ts +0 -92
package/package.json
CHANGED
package/qna/index.ts
CHANGED
package/qna/qna-tui.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Editor, Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
2
|
|
|
3
3
|
type Component = {
|
|
4
4
|
handleInput: (data: string) => void;
|
|
@@ -18,35 +18,6 @@ type EditorTheme = {
|
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
function getPiTui() {
|
|
22
|
-
return requirePiTuiModule() as {
|
|
23
|
-
Editor: new (
|
|
24
|
-
tui: TUI,
|
|
25
|
-
theme: EditorTheme,
|
|
26
|
-
) => {
|
|
27
|
-
disableSubmit?: boolean;
|
|
28
|
-
onChange?: () => void;
|
|
29
|
-
setText: (text: string) => void;
|
|
30
|
-
getText: () => string;
|
|
31
|
-
render: (width: number) => string[];
|
|
32
|
-
handleInput: (data: string) => void;
|
|
33
|
-
};
|
|
34
|
-
Key: {
|
|
35
|
-
enter: string;
|
|
36
|
-
tab: string;
|
|
37
|
-
escape: string;
|
|
38
|
-
up: string;
|
|
39
|
-
down: string;
|
|
40
|
-
ctrl: (key: string) => string;
|
|
41
|
-
shift: (key: string) => string;
|
|
42
|
-
};
|
|
43
|
-
matchesKey: (input: string, key: string) => boolean;
|
|
44
|
-
truncateToWidth: (text: string, width: number) => string;
|
|
45
|
-
visibleWidth: (text: string) => number;
|
|
46
|
-
wrapTextWithAnsi: (text: string, width: number) => string[];
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
21
|
export interface QnAOption {
|
|
51
22
|
label: string;
|
|
52
23
|
description: string;
|
|
@@ -341,8 +312,18 @@ export class QnATuiComponent<TQuestion extends QnAQuestion> implements Component
|
|
|
341
312
|
},
|
|
342
313
|
};
|
|
343
314
|
|
|
344
|
-
const
|
|
345
|
-
|
|
315
|
+
const TuiEditor = Editor as unknown as new (
|
|
316
|
+
tui: TUI,
|
|
317
|
+
theme: EditorTheme,
|
|
318
|
+
) => {
|
|
319
|
+
disableSubmit?: boolean;
|
|
320
|
+
onChange?: () => void;
|
|
321
|
+
setText: (text: string) => void;
|
|
322
|
+
getText: () => string;
|
|
323
|
+
render: (width: number) => string[];
|
|
324
|
+
handleInput: (data: string) => void;
|
|
325
|
+
};
|
|
326
|
+
this.editor = new TuiEditor(tui, editorTheme);
|
|
346
327
|
this.editor.disableSubmit = true;
|
|
347
328
|
this.editor.onChange = () => {
|
|
348
329
|
this.saveCurrentResponse();
|
|
@@ -510,8 +491,6 @@ export class QnATuiComponent<TQuestion extends QnAQuestion> implements Component
|
|
|
510
491
|
}
|
|
511
492
|
|
|
512
493
|
handleInput(data: string): void {
|
|
513
|
-
const { Key, matchesKey } = getPiTui();
|
|
514
|
-
|
|
515
494
|
if (this.showingConfirmation) {
|
|
516
495
|
if (matchesKey(data, Key.enter)) {
|
|
517
496
|
this.submit();
|
|
@@ -640,8 +619,6 @@ export class QnATuiComponent<TQuestion extends QnAQuestion> implements Component
|
|
|
640
619
|
}
|
|
641
620
|
|
|
642
621
|
render(width: number): string[] {
|
|
643
|
-
const { truncateToWidth, visibleWidth, wrapTextWithAnsi } = getPiTui();
|
|
644
|
-
|
|
645
622
|
if (this.cachedLines && this.cachedWidth === width) {
|
|
646
623
|
return this.cachedLines;
|
|
647
624
|
}
|
package/qna/scroll-select.ts
CHANGED
|
@@ -1,41 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
let cachedPiTui:
|
|
4
|
-
| {
|
|
5
|
-
Key: {
|
|
6
|
-
enter: string;
|
|
7
|
-
escape: string;
|
|
8
|
-
up: string;
|
|
9
|
-
down: string;
|
|
10
|
-
ctrl: (key: string) => string;
|
|
11
|
-
};
|
|
12
|
-
matchesKey: (input: string, key: string) => boolean;
|
|
13
|
-
truncateToWidth: (text: string, width: number) => string;
|
|
14
|
-
visibleWidth: (text: string) => number;
|
|
15
|
-
wrapTextWithAnsi: (text: string, width: number) => string[];
|
|
16
|
-
}
|
|
17
|
-
| undefined;
|
|
18
|
-
|
|
19
|
-
function getPiTui() {
|
|
20
|
-
if (cachedPiTui) {
|
|
21
|
-
return cachedPiTui;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
cachedPiTui = requirePiTuiModule() as {
|
|
25
|
-
Key: {
|
|
26
|
-
enter: string;
|
|
27
|
-
escape: string;
|
|
28
|
-
up: string;
|
|
29
|
-
down: string;
|
|
30
|
-
ctrl: (key: string) => string;
|
|
31
|
-
};
|
|
32
|
-
matchesKey: (input: string, key: string) => boolean;
|
|
33
|
-
truncateToWidth: (text: string, width: number) => string;
|
|
34
|
-
visibleWidth: (text: string) => number;
|
|
35
|
-
wrapTextWithAnsi: (text: string, width: number) => string[];
|
|
36
|
-
};
|
|
37
|
-
return cachedPiTui;
|
|
38
|
-
}
|
|
1
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
39
2
|
|
|
40
3
|
export interface ScrollSelectOption<T> {
|
|
41
4
|
value: T;
|
|
@@ -132,7 +95,6 @@ class ScrollSelectComponent<T> {
|
|
|
132
95
|
}
|
|
133
96
|
|
|
134
97
|
render(width: number): string[] {
|
|
135
|
-
const { truncateToWidth, visibleWidth, wrapTextWithAnsi } = getPiTui();
|
|
136
98
|
const safeWidth = Math.max(20, width);
|
|
137
99
|
const contentWidth = Math.max(12, safeWidth - 2);
|
|
138
100
|
const lines: string[] = [];
|
|
@@ -193,8 +155,6 @@ class ScrollSelectComponent<T> {
|
|
|
193
155
|
}
|
|
194
156
|
|
|
195
157
|
handleInput(data: string): void {
|
|
196
|
-
const { Key, matchesKey } = getPiTui();
|
|
197
|
-
|
|
198
158
|
if (matchesKey(data, Key.escape) || data === "q") {
|
|
199
159
|
this.done(null);
|
|
200
160
|
return;
|
|
@@ -225,7 +185,6 @@ class ScrollSelectComponent<T> {
|
|
|
225
185
|
dispose(): void {}
|
|
226
186
|
|
|
227
187
|
private renderSurfaceLine(line: string, contentWidth: number, options: { selected?: boolean } = {}): string {
|
|
228
|
-
const { visibleWidth } = getPiTui();
|
|
229
188
|
const paddedLine = `${line}${" ".repeat(Math.max(0, contentWidth - visibleWidth(line)))}`;
|
|
230
189
|
const boxedLine = ` ${paddedLine} `;
|
|
231
190
|
if (typeof this.theme.bg !== "function") {
|
package/request-user-input.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { QnAResponse, QnAResult } from "./qna";
|
|
|
2
2
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
3
3
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
6
|
+
import { QnATuiComponent } from "./qna";
|
|
6
7
|
|
|
7
8
|
import type {
|
|
8
9
|
NormalizedRequestUserInputQuestion,
|
|
@@ -16,9 +17,6 @@ import type {
|
|
|
16
17
|
import { findDuplicateId } from "./utils";
|
|
17
18
|
|
|
18
19
|
function createText(text: string) {
|
|
19
|
-
const { Text } = requirePiTuiModule() as {
|
|
20
|
-
Text: new (text: string, x: number, y: number) => unknown;
|
|
21
|
-
};
|
|
22
20
|
return new Text(text, 0, 0);
|
|
23
21
|
}
|
|
24
22
|
|
package/state.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
4
4
|
|
|
5
5
|
import type { PlanModeState } from "./types";
|
|
6
6
|
|
|
7
7
|
import { resolveActivePlanFilePath } from "./plan-files";
|
|
8
8
|
import { createInactivePlanModeState, isPlanModeState } from "./utils";
|
|
9
9
|
|
|
10
|
-
function getPiTui() {
|
|
11
|
-
return requirePiTuiModule() as {
|
|
12
|
-
truncateToWidth: (text: string, width: number) => string;
|
|
13
|
-
wrapTextWithAnsi: (text: string, width: number) => string[];
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
10
|
export const STATE_ENTRY_TYPE = "pi-plan:state";
|
|
18
11
|
export const CONTEXT_ENTRY_TYPE = "pi-plan:context";
|
|
19
12
|
const BANNER_WIDGET_KEY = "pi-plan-banner";
|
|
@@ -104,7 +97,6 @@ export function createPlanModeStateManager(pi: ExtensionAPI) {
|
|
|
104
97
|
(_tui, theme) => ({
|
|
105
98
|
invalidate: () => {},
|
|
106
99
|
render: (width: number) => {
|
|
107
|
-
const { truncateToWidth, wrapTextWithAnsi } = getPiTui();
|
|
108
100
|
const safeWidth = Math.max(1, width);
|
|
109
101
|
const activePlanFilePath = resolveActivePlanFilePath(ctx, state.planFilePath);
|
|
110
102
|
const lines = [
|
package/qna/pi-tui-loader.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
<!-- {=sharedQnaPiTuiLoaderOverview} -->
|
|
3
|
-
|
|
4
|
-
`local qna helpers` centralizes `@earendil-works/pi-tui` loading so first-party packages reuse one
|
|
5
|
-
fallback strategy instead of embedding Bun-global lookup logic in multiple runtime modules.
|
|
6
|
-
|
|
7
|
-
The shared loader returns pi's extension-provided `@earendil-works/pi-tui` module by default.
|
|
8
|
-
Custom require functions can still exercise the normal package resolution path and Bun global
|
|
9
|
-
fallback locations for tests or standalone callers running outside pi's extension loader.
|
|
10
|
-
|
|
11
|
-
<!-- {/sharedQnaPiTuiLoaderOverview} -->
|
|
12
|
-
*/
|
|
13
|
-
import * as piTuiModule from "@earendil-works/pi-tui";
|
|
14
|
-
import { createRequire } from "node:module";
|
|
15
|
-
import os from "node:os";
|
|
16
|
-
import path from "node:path";
|
|
17
|
-
|
|
18
|
-
export type PiTuiRequire = (specifier: string) => unknown;
|
|
19
|
-
|
|
20
|
-
export interface PiTuiLoaderOptions {
|
|
21
|
-
homeDir?: string;
|
|
22
|
-
bunInstallDir?: string | undefined;
|
|
23
|
-
requireFn?: PiTuiRequire;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
<!-- {=sharedQnaGetPiTuiFallbackPathsDocs} -->
|
|
28
|
-
|
|
29
|
-
Return the ordered list of Bun global fallback paths to try for `@earendil-works/pi-tui`.
|
|
30
|
-
|
|
31
|
-
The list prefers an explicit `BUN_INSTALL` root when provided and always includes the default
|
|
32
|
-
`~/.bun/install/global/node_modules/@earendil-works/pi-tui` fallback without duplicates.
|
|
33
|
-
|
|
34
|
-
<!-- {/sharedQnaGetPiTuiFallbackPathsDocs} -->
|
|
35
|
-
*/
|
|
36
|
-
export function getPiTuiFallbackPaths(options: Omit<PiTuiLoaderOptions, "requireFn"> = {}): string[] {
|
|
37
|
-
const homeDir = options.homeDir ?? os.homedir();
|
|
38
|
-
const roots = new Set<string>();
|
|
39
|
-
if (options.bunInstallDir) {
|
|
40
|
-
roots.add(options.bunInstallDir);
|
|
41
|
-
}
|
|
42
|
-
roots.add(path.join(homeDir, ".bun"));
|
|
43
|
-
return [...roots].map((root) => path.join(root, "install", "global", "node_modules", "@earendil-works", "pi-tui"));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
<!-- {=sharedQnaRequirePiTuiModuleDocs} -->
|
|
48
|
-
|
|
49
|
-
Load `@earendil-works/pi-tui` with a shared fallback strategy.
|
|
50
|
-
|
|
51
|
-
Pi exposes `@earendil-works/pi-tui` as an available extension import. Returning the static import
|
|
52
|
-
keeps us on pi's module resolver; using `createRequire(import.meta.url)` in that path bypasses pi's
|
|
53
|
-
resolver and can crash render callbacks when the peer dependency is not locally installed.
|
|
54
|
-
|
|
55
|
-
When a custom `requireFn` or fallback path options are provided, the loader keeps the older
|
|
56
|
-
standalone behavior: try normal package resolution, then walk Bun-global fallback locations, and
|
|
57
|
-
finally throw a helpful error that names every checked location when none resolve.
|
|
58
|
-
|
|
59
|
-
<!-- {/sharedQnaRequirePiTuiModuleDocs} -->
|
|
60
|
-
*/
|
|
61
|
-
export function requirePiTuiModule(options: PiTuiLoaderOptions = {}): unknown {
|
|
62
|
-
if (!options.requireFn && !options.homeDir && !options.bunInstallDir) {
|
|
63
|
-
return piTuiModule;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const requireFn = options.requireFn ?? createRequire(import.meta.url);
|
|
67
|
-
try {
|
|
68
|
-
return requireFn("@earendil-works/pi-tui");
|
|
69
|
-
} catch (error) {
|
|
70
|
-
const code = (error as { code?: string }).code;
|
|
71
|
-
if (code !== "MODULE_NOT_FOUND") {
|
|
72
|
-
throw error;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const fallbackPaths = getPiTuiFallbackPaths(options);
|
|
76
|
-
for (const fallbackPath of fallbackPaths) {
|
|
77
|
-
try {
|
|
78
|
-
return requireFn(fallbackPath);
|
|
79
|
-
} catch (fallbackError) {
|
|
80
|
-
const fallbackCode = (fallbackError as { code?: string }).code;
|
|
81
|
-
if (fallbackCode !== "MODULE_NOT_FOUND") {
|
|
82
|
-
throw fallbackError;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
throw new Error(
|
|
88
|
-
`Unable to load @earendil-works/pi-tui. Checked the local dependency and Bun global fallbacks: ${fallbackPaths.join(", ")}`,
|
|
89
|
-
{ cause: error },
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
}
|