@janvitos/pi-plan-build 0.1.2 → 0.1.4
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 +1 -1
- package/index.ts +31 -6
- package/package.json +1 -1
- package/utils.ts +11 -9
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ A global [Pi coding agent](https://github.com/badlogic/pi-mono) extension that a
|
|
|
8
8
|
|
|
9
9
|
- New sessions start in **Build** mode.
|
|
10
10
|
- Bare `Tab` cycles **Build → Plan → Build** and replaces normal Tab autocomplete.
|
|
11
|
-
-
|
|
11
|
+
- Bracketed **build** (blue) and **plan** (yellow) indicators rendered as bold prompt prefixes inside Pi's text input box.
|
|
12
12
|
- `/plan`, `/build`, and the `--plan` startup flag.
|
|
13
13
|
- Per-session plans at `~/.pi/agent/plans/<session-id>.md`.
|
|
14
14
|
- In Plan mode, built-in `edit` and `write` are restricted to the exact plan file.
|
package/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { CustomEditor, getAgentDir, getMarkdownTheme, type EntryRenderer, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { Key, Markdown, matchesKey, Text } from "@earendil-works/pi-tui";
|
|
5
|
+
import { Key, Markdown, matchesKey, Text, visibleWidth } from "@earendil-works/pi-tui";
|
|
6
6
|
import { Type } from "typebox";
|
|
7
7
|
import { registerQuestionTool } from "./question-ui.ts";
|
|
8
8
|
import {
|
|
@@ -64,6 +64,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
64
64
|
let planPath = "";
|
|
65
65
|
let toolsBeforeModes: string[] = [];
|
|
66
66
|
let currentContext: ExtensionContext | undefined;
|
|
67
|
+
let requestEditorRender: (() => void) | undefined;
|
|
67
68
|
let freshImplementationPlan: string | undefined;
|
|
68
69
|
|
|
69
70
|
pi.registerFlag("plan", {
|
|
@@ -94,8 +95,10 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
94
95
|
pi.appendEntry(STATE_TYPE, stateData());
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
function
|
|
98
|
-
|
|
98
|
+
function updateModeIndicator(ctx: ExtensionContext): void {
|
|
99
|
+
// Clear the legacy footer status; the mode is now rendered inside the editor.
|
|
100
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
101
|
+
requestEditorRender?.();
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
function discoverUnmanagedTools(): void {
|
|
@@ -134,7 +137,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
134
137
|
pendingReminder = undefined;
|
|
135
138
|
applyTools(mode);
|
|
136
139
|
}
|
|
137
|
-
|
|
140
|
+
updateModeIndicator(ctx);
|
|
138
141
|
persist();
|
|
139
142
|
}
|
|
140
143
|
|
|
@@ -360,7 +363,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
360
363
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
361
364
|
runMode = undefined;
|
|
362
365
|
applyTools(selectedMode);
|
|
363
|
-
|
|
366
|
+
updateModeIndicator(ctx);
|
|
364
367
|
});
|
|
365
368
|
|
|
366
369
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -383,11 +386,31 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
383
386
|
planPath = makePlanPath(path.join(getAgentDir(), "plans"), ctx.sessionManager.getSessionId());
|
|
384
387
|
if (selectedMode === "plan") await ensurePlanDirectory();
|
|
385
388
|
applyTools(selectedMode);
|
|
386
|
-
|
|
389
|
+
updateModeIndicator(ctx);
|
|
387
390
|
|
|
388
391
|
if (ctx.mode === "tui") {
|
|
389
392
|
class ModeEditor extends CustomEditor {
|
|
390
393
|
onCycle?: () => void;
|
|
394
|
+
|
|
395
|
+
requestModeRender(): void {
|
|
396
|
+
this.tui.requestRender();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
override render(width: number): string[] {
|
|
400
|
+
const badge = formatModeStatus(selectedMode, ctx.ui.theme, this.borderColor);
|
|
401
|
+
const prompt = `${badge} `;
|
|
402
|
+
const promptWidth = visibleWidth(prompt);
|
|
403
|
+
const paddingWidth = Math.min(promptWidth, Math.max(0, Math.floor((width - 1) / 2)));
|
|
404
|
+
if (this.getPaddingX() !== paddingWidth) this.setPaddingX(paddingWidth);
|
|
405
|
+
|
|
406
|
+
const lines = super.render(width);
|
|
407
|
+
const reservedPrefix = " ".repeat(paddingWidth);
|
|
408
|
+
if (paddingWidth === promptWidth && lines[1]?.startsWith(reservedPrefix)) {
|
|
409
|
+
lines[1] = prompt + lines[1].slice(reservedPrefix.length);
|
|
410
|
+
}
|
|
411
|
+
return lines;
|
|
412
|
+
}
|
|
413
|
+
|
|
391
414
|
override handleInput(data: string): void {
|
|
392
415
|
if (matchesKey(data, Key.tab)) {
|
|
393
416
|
this.onCycle?.();
|
|
@@ -398,6 +421,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
398
421
|
}
|
|
399
422
|
ctx.ui.setEditorComponent((tui, theme, keybindings) => {
|
|
400
423
|
const editor = new ModeEditor(tui, theme, keybindings);
|
|
424
|
+
requestEditorRender = () => editor.requestModeRender();
|
|
401
425
|
editor.onCycle = () => {
|
|
402
426
|
if (currentContext) void selectMode(nextMode(selectedMode), currentContext, "manual");
|
|
403
427
|
};
|
|
@@ -409,6 +433,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
409
433
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
410
434
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
411
435
|
ctx.ui.setEditorComponent(undefined);
|
|
436
|
+
requestEditorRender = undefined;
|
|
412
437
|
currentContext = undefined;
|
|
413
438
|
});
|
|
414
439
|
}
|
package/package.json
CHANGED
package/utils.ts
CHANGED
|
@@ -3,17 +3,19 @@ import path from "node:path";
|
|
|
3
3
|
export type Mode = "build" | "plan";
|
|
4
4
|
|
|
5
5
|
const ANSI_RESET = "\x1b[0m";
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
plan: { background: "48;2;255;215;0", text: " PLAN ", reservedSpace: "\u00a0" },
|
|
10
|
-
build: { background: "48;2;59;130;246", text: " BUILD ", reservedSpace: "" },
|
|
6
|
+
const MODE_LABELS: Record<Mode, { color: string; text: string }> = {
|
|
7
|
+
plan: { color: "38;2;255;215;0", text: "plan" },
|
|
8
|
+
build: { color: "38;2;59;130;246", text: "build" },
|
|
11
9
|
};
|
|
12
10
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
export interface ModeStatusTheme {
|
|
12
|
+
bold(text: string): string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatModeStatus(mode: Mode, theme: ModeStatusTheme, borderColor: (text: string) => string): string {
|
|
16
|
+
const label = MODE_LABELS[mode];
|
|
17
|
+
const modeText = `\x1b[${label.color}m${theme.bold(label.text)}${ANSI_RESET}`;
|
|
18
|
+
return borderColor("[") + modeText + borderColor("]");
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export const PLAN_EXIT_APPROVE_CHOICE = "Switch to Build and implement here";
|