@janvitos/pi-plan-build 0.1.12 → 0.1.14

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 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**, while active autocomplete dropdowns retain Pi's normal Tab completion.
11
- - The composer uses OpenCode prompt-inspired blue/orange mode colors on a rounded `╭─` top border and `│` rail, plus a mode/model/thinking metadata row and Pi's bottom border. The model is shown as `model-id [provider]` (for example, `gpt-5.6-luna [openai]`), with the provider in the footer's dim text color. The footer keeps the remaining path and usage stats without duplicating model metadata.
11
+ - The composer uses OpenCode prompt-inspired blue/orange mode colors on the top and left borders, complemented by Pi's border color on the right and bottom borders; rounded corners close the frame while matching their adjoining horizontal border colors. It also includes a mode/model/thinking metadata row. The model is shown as `model-id [provider]` (for example, `gpt-5.6-luna [openai]`), with the model ID inheriting the terminal foreground like unselected entries in `/model` and the provider using the footer's dim text color. The footer keeps the remaining path and usage stats without duplicating model metadata.
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, truncateToWidth } from "@earendil-works/pi-tui";
5
+ import { Key, Markdown, matchesKey, Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
6
6
  import { Type } from "typebox";
7
7
  import { registerQuestionTool } from "./question-ui.ts";
8
8
  import {
@@ -490,18 +490,21 @@ export default function planBuildModes(pi: ExtensionAPI): void {
490
490
  const lines = super.render(width);
491
491
  if (paddingWidth !== railWidth) return lines;
492
492
 
493
- const rail = `${formatModeRail(selectedMode)} `;
493
+ const leftRail = `${formatModeRail(selectedMode)} `;
494
+ const rightRail = this.borderColor("│");
494
495
  const metadata = truncateToWidth(
495
496
  formatModeMetadata(selectedMode, pi.getThinkingLevel(), ctx.ui.theme, this.borderColor, {
496
497
  modelName: ctx.model?.id ?? "no-model",
497
498
  modelProvider: ctx.model?.provider,
498
- modelColor: (text) => ctx.ui.theme.fg("text", text),
499
499
  }),
500
- width,
500
+ width - 1,
501
501
  "",
502
502
  );
503
503
  const topBorder = formatModeTopBorder(selectedMode, width);
504
- return renderModeComposer(lines, topBorder, rail, metadata, railWidth);
504
+ return renderModeComposer(lines, topBorder, leftRail, rightRail, metadata, railWidth, width, {
505
+ truncate: (line, maxWidth) => truncateToWidth(line, maxWidth, ""),
506
+ measure: visibleWidth,
507
+ });
505
508
  }
506
509
 
507
510
  override handleInput(data: string): void {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@janvitos/pi-plan-build",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Plan safely, approve explicitly, then implement here or in a clean session.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/utils.ts CHANGED
@@ -9,14 +9,12 @@ const MODE_LABELS: Record<Mode, { color: string; text: string }> = {
9
9
  };
10
10
 
11
11
  export interface ModeStatusTheme {
12
- bold(text: string): string;
13
12
  fg(color: "dim", text: string): string;
14
13
  }
15
14
 
16
15
  export interface PromptMetadataOptions {
17
16
  modelName: string;
18
17
  modelProvider?: string;
19
- modelColor: (text: string) => string;
20
18
  }
21
19
 
22
20
  function formatModeColor(mode: Mode, text: string): string {
@@ -28,8 +26,8 @@ export function formatModeRail(mode: Mode): string {
28
26
  }
29
27
 
30
28
  export function formatModeTopBorder(mode: Mode, width: number): string {
31
- if (width <= 0) return "";
32
- return formatModeColor(mode, `╭${"─".repeat(width - 1)}`);
29
+ if (width <= 1) return "";
30
+ return formatModeColor(mode, `╭${"─".repeat(width - 2)}╮`);
33
31
  }
34
32
 
35
33
  export function formatModeMetadata(
@@ -40,9 +38,9 @@ export function formatModeMetadata(
40
38
  options?: PromptMetadataOptions,
41
39
  ): string {
42
40
  const label = MODE_LABELS[mode];
43
- const modeText = `\x1b[${label.color}m${theme.bold(label.text)}${ANSI_RESET}`;
41
+ const modeText = `\x1b[${label.color}m${label.text}${ANSI_RESET}`;
44
42
  const modelText = options
45
- ? `${theme.fg("dim", " • ")}${options.modelColor(options.modelName)}${
43
+ ? `${theme.fg("dim", " • ")}${options.modelName}${
46
44
  options.modelProvider ? theme.fg("dim", ` [${options.modelProvider}]`) : ""
47
45
  }`
48
46
  : "";
@@ -69,29 +67,41 @@ export function formatFooterCwd(cwd: string, home: string | undefined): string {
69
67
  return relativeToHome === "" ? "~" : `~${sep}${relativeToHome}`;
70
68
  }
71
69
 
70
+ export interface LineWidthTools {
71
+ truncate(line: string, width: number): string;
72
+ measure(line: string): number;
73
+ }
74
+
72
75
  export function renderModeComposer(
73
76
  lines: string[],
74
77
  topBorder: string,
75
- railPrefix: string,
78
+ leftRailPrefix: string,
79
+ rightRail: string,
76
80
  metadata: string,
77
81
  reservedWidth: number,
82
+ width: number,
83
+ lineWidth: LineWidthTools,
78
84
  ): string[] {
79
- if (reservedWidth <= 0 || lines.length < 3) return lines;
85
+ if (reservedWidth <= 0 || width <= 1 || lines.length < 3) return lines;
80
86
  const reservedPrefix = " ".repeat(reservedWidth);
81
87
  const bottomBorderIndex = lines.findIndex((line, index) => index > 0 && !line.startsWith(reservedPrefix));
82
88
  if (bottomBorderIndex < 2) return lines;
89
+
90
+ const addRightRail = (line: string): string => {
91
+ const content = lineWidth.truncate(line, width - 1);
92
+ return `${content}${" ".repeat(Math.max(0, width - 1 - lineWidth.measure(content)))}${rightRail}`;
93
+ };
83
94
  const promptLines = lines
84
95
  .slice(1, bottomBorderIndex)
85
- .map((line) => railPrefix + line.slice(reservedPrefix.length));
86
- const bottomBorder = lines[bottomBorderIndex]!.replace(
87
- /^((?:\x1b\[[0-?]*[ -/]*[@-~])*)./u,
88
- "$1 ",
89
- );
96
+ .map((line) => addRightRail(leftRailPrefix + line.slice(reservedPrefix.length)));
97
+ const bottomBorder = lineWidth.truncate(lines[bottomBorderIndex]!, width)
98
+ .replace(/^((?:\x1b\[[0-?]*[ -/]*[@-~])*)./u, "$1╰")
99
+ .replace(/.(?=(?:\x1b\[[0-?]*[ -/]*[@-~])*$)/u, "╯");
90
100
  return [
91
101
  topBorder,
92
102
  ...promptLines,
93
- railPrefix,
94
- metadata,
103
+ addRightRail(leftRailPrefix),
104
+ addRightRail(metadata),
95
105
  bottomBorder,
96
106
  "",
97
107
  ...lines.slice(bottomBorderIndex + 1),