@gavin-hu/my-pi-agent 0.8.1 → 0.10.0
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 +35 -8
- package/README.zh-CN.md +33 -8
- package/assets/bytefly.schema.json +17 -1
- package/assets/chrome.png +0 -0
- package/assets/file-browse.png +0 -0
- package/assets/job.png +0 -0
- package/assets/plan-review.png +0 -0
- package/assets/question.png +0 -0
- package/assets/rewind.png +0 -0
- package/assets/todo.png +0 -0
- package/assets/tool-renderings.png +0 -0
- package/assets/tools/ask_user_question.png +0 -0
- package/assets/tools/bash.png +0 -0
- package/assets/tools/edit.png +0 -0
- package/assets/tools/enter_plan_mode.png +0 -0
- package/assets/tools/enter_worktree.png +0 -0
- package/assets/tools/exit_plan_mode.png +0 -0
- package/assets/tools/exit_worktree.png +0 -0
- package/assets/tools/find.png +0 -0
- package/assets/tools/goal.png +0 -0
- package/assets/tools/grep.png +0 -0
- package/assets/tools/job.png +0 -0
- package/assets/tools/list_worktrees.png +0 -0
- package/assets/tools/ls.png +0 -0
- package/assets/tools/memory.png +0 -0
- package/assets/tools/notice.png +0 -0
- package/assets/tools/open_path.png +0 -0
- package/assets/tools/prune_worktrees.png +0 -0
- package/assets/tools/read.png +0 -0
- package/assets/tools/read_doc.png +0 -0
- package/assets/tools/run_doctor.png +0 -0
- package/assets/tools/send_wechat.png +0 -0
- package/assets/tools/set_plan_steps.png +0 -0
- package/assets/tools/subagent.png +0 -0
- package/assets/tools/todo.png +0 -0
- package/assets/tools/turn-rule.png +0 -0
- package/assets/tools/user-message.png +0 -0
- package/assets/tools/watch.png +0 -0
- package/assets/tools/web_fetch.png +0 -0
- package/assets/tools/web_search.png +0 -0
- package/assets/tools/write.png +0 -0
- package/assets/tools/write_plan.png +0 -0
- package/extensions/bytefly/README.md +306 -68
- package/extensions/bytefly/block/component.ts +282 -0
- package/extensions/bytefly/block/config.ts +24 -0
- package/extensions/bytefly/block/fallback.ts +120 -0
- package/extensions/bytefly/block/register.ts +164 -0
- package/extensions/bytefly/chrome.ts +97 -0
- package/extensions/bytefly/doctor/format.ts +36 -10
- package/extensions/bytefly/doctor/tools.ts +10 -6
- package/extensions/bytefly/footer/config.ts +1 -1
- package/extensions/bytefly/footer/render/format.ts +5 -18
- package/extensions/bytefly/footer/render/layout.ts +2 -1
- package/extensions/bytefly/footer/render/segments.ts +51 -78
- package/extensions/bytefly/header/component.ts +49 -21
- package/extensions/bytefly/header/config.ts +28 -21
- package/extensions/bytefly/header/register.ts +6 -1
- package/extensions/bytefly/index.ts +32 -12
- package/extensions/bytefly/message/config.ts +15 -0
- package/extensions/bytefly/message/format.ts +42 -0
- package/extensions/bytefly/message/register.ts +15 -0
- package/extensions/bytefly/notice/component.ts +107 -0
- package/extensions/bytefly/notice/config.ts +41 -0
- package/extensions/bytefly/notice/register.ts +27 -0
- package/extensions/bytefly/pad.ts +42 -0
- package/extensions/bytefly/panel/commands.ts +69 -35
- package/extensions/bytefly/panel/format.ts +8 -8
- package/extensions/bytefly/panel/tui.ts +45 -70
- package/extensions/bytefly/separator/component.ts +2 -1
- package/extensions/bytefly/separator/config.ts +16 -8
- package/extensions/bytefly/separator/format.ts +18 -38
- package/extensions/bytefly/separator/register.ts +3 -2
- package/extensions/bytefly/separator/types.ts +0 -10
- package/extensions/bytefly/working/config.ts +20 -0
- package/extensions/bytefly/working/format.ts +25 -0
- package/extensions/bytefly/working/register.ts +88 -0
- package/extensions/document/README.md +12 -5
- package/extensions/document/paging.ts +10 -10
- package/extensions/document/render.ts +17 -16
- package/extensions/document/tool.ts +1 -1
- package/extensions/file-browse/README.md +54 -10
- package/extensions/file-browse/client.ts +110 -2
- package/extensions/file-browse/config.ts +12 -0
- package/extensions/file-browse/diff.ts +187 -0
- package/extensions/file-browse/git.ts +180 -8
- package/extensions/file-browse/html.ts +401 -25
- package/extensions/file-browse/index.ts +15 -4
- package/extensions/file-browse/markdown.ts +33 -0
- package/extensions/file-browse/router.ts +94 -2
- package/extensions/file-browse/server.ts +4 -1
- package/extensions/file-open/README.md +6 -0
- package/extensions/goal/README.md +16 -10
- package/extensions/goal/format.ts +13 -25
- package/extensions/goal/tools.ts +11 -22
- package/extensions/goal/tui.ts +12 -40
- package/extensions/job/README.md +22 -13
- package/extensions/job/format.ts +36 -13
- package/extensions/job/index.ts +26 -21
- package/extensions/job/tools.ts +15 -12
- package/extensions/job/tui.ts +148 -222
- package/extensions/job/ui.ts +24 -49
- package/extensions/memory/README.md +7 -1
- package/extensions/memory/format.ts +5 -3
- package/extensions/memory/tools.ts +4 -3
- package/extensions/memory/tui.ts +32 -36
- package/extensions/plan/README.md +86 -36
- package/extensions/plan/blocks.ts +44 -2
- package/extensions/plan/feedback.ts +20 -5
- package/extensions/plan/index.ts +1 -1
- package/extensions/plan/runtime.ts +3 -2
- package/extensions/plan/tools.ts +43 -27
- package/extensions/plan/tui.ts +21 -27
- package/extensions/plan/web-client.ts +386 -40
- package/extensions/plan/web-page.ts +138 -33
- package/extensions/plan/web-router.ts +46 -13
- package/extensions/plan/web.ts +63 -12
- package/extensions/question/README.md +14 -2
- package/extensions/question/answers.ts +25 -18
- package/extensions/question/tools.ts +22 -16
- package/extensions/question/tui.ts +132 -76
- package/extensions/rewind/README.md +3 -1
- package/extensions/rewind/format.ts +35 -2
- package/extensions/rewind/index.ts +3 -1
- package/extensions/rewind/rewind-tui.ts +66 -62
- package/extensions/rewind/snapshot-run.ts +1 -1
- package/extensions/rewind/status.ts +3 -2
- package/extensions/subagent/README.md +6 -0
- package/extensions/subagent/index.ts +1 -1
- package/extensions/subagent/render.ts +108 -66
- package/extensions/todo/README.md +20 -13
- package/extensions/todo/format.ts +47 -34
- package/extensions/todo/tools.ts +6 -4
- package/extensions/todo/tui.ts +46 -26
- package/extensions/watch/README.md +16 -8
- package/extensions/watch/format.ts +33 -13
- package/extensions/watch/index.ts +29 -24
- package/extensions/watch/tools.ts +16 -9
- package/extensions/watch/tui.ts +142 -196
- package/extensions/watch/ui.ts +24 -46
- package/extensions/web-access/README.md +16 -7
- package/extensions/web-access/fetch/tool.ts +12 -11
- package/extensions/web-access/search/tool.ts +16 -13
- package/extensions/wechat/README.md +8 -2
- package/extensions/wechat/index.ts +4 -3
- package/extensions/wechat/login-screen.ts +8 -5
- package/extensions/wechat/status-screen.ts +41 -22
- package/extensions/wechat/tools.ts +6 -5
- package/extensions/worktree/README.md +6 -0
- package/extensions/worktree/enter.ts +1 -1
- package/extensions/worktree/index.ts +1 -1
- package/extensions/worktree/runtime.ts +1 -1
- package/lib/README.md +11 -7
- package/lib/format.ts +17 -0
- package/lib/notice.ts +87 -0
- package/lib/requirements.ts +2 -2
- package/lib/{tui.ts → tui/chrome.ts} +17 -34
- package/lib/tui/columns.ts +96 -0
- package/lib/{list-cursor.ts → tui/cursor.ts} +58 -4
- package/lib/tui/gauge.ts +78 -0
- package/lib/{ui.ts → tui/glyphs.ts} +10 -11
- package/lib/tui/indent.ts +166 -0
- package/lib/tui/index.ts +27 -0
- package/lib/tui/line-pane.ts +173 -0
- package/lib/tui/rows.ts +143 -0
- package/lib/tui/rules.ts +94 -0
- package/lib/tui/status-controller.ts +107 -0
- package/lib/tui/tokens.ts +72 -0
- package/lib/web/base.ts +2 -1
- package/lib/web/components.ts +3 -1
- package/lib/web/layout.ts +5 -1
- package/package.json +7 -3
- package/themes/bytefly-dark.json +7 -8
- package/themes/bytefly-light.json +10 -11
- package/assets/bytefly-chrome.png +0 -0
package/extensions/plan/tools.ts
CHANGED
|
@@ -22,15 +22,16 @@ import { Type, type Static } from "typebox";
|
|
|
22
22
|
import { sanitizeLine } from "../../lib/format.ts";
|
|
23
23
|
import { askHuman } from "../../lib/interaction.ts";
|
|
24
24
|
import { TODO_TOOL } from "../../lib/tool-names.ts";
|
|
25
|
-
import { FULL_SCREEN_OVERLAY, reuseText, toolDetails } from "../../lib/tui.ts";
|
|
26
|
-
import {
|
|
25
|
+
import { FULL_SCREEN_OVERLAY, reuseText, toolDetails } from "../../lib/tui/chrome.ts";
|
|
26
|
+
import { paint } from "../../lib/tui/tokens.ts";
|
|
27
|
+
import { expandHint } from "../../lib/tui/glyphs.ts";
|
|
27
28
|
import { READ_ONLY_SUMMARY } from "./policy.ts";
|
|
28
|
-
import { feedbackSummary } from "./feedback.ts";
|
|
29
|
+
import { feedbackSummary, type PlanAnnotation } from "./feedback.ts";
|
|
29
30
|
import { ENTER_TOOL, EXIT_TOOL, SET_PLAN_STEPS_TOOL, WRITE_PLAN_TOOL, type PlanRuntime } from "./runtime.ts";
|
|
30
31
|
import { PLAN_OUTLINE, extractPlanSteps, type PlanStep, type StepUpdate } from "./steps.ts";
|
|
31
32
|
import { PlanViewComponent, sanitizePlanText, type PlanViewAction } from "./tui.ts";
|
|
32
33
|
import type { EnterPlanModeDetails, ExitPlanModeDetails, SetPlanStepsDetails, WritePlanDetails } from "./types.ts";
|
|
33
|
-
import { createPlanWebReview, type PlanWebReview } from "./web.ts";
|
|
34
|
+
import { createPlanWebReview, type PlanReviewResult, type PlanReviewSession, type PlanWebReview } from "./web.ts";
|
|
34
35
|
|
|
35
36
|
const ExitPlanModeParams = Type.Object({
|
|
36
37
|
plan_path: Type.String({
|
|
@@ -238,7 +239,7 @@ export function registerTools(
|
|
|
238
239
|
|
|
239
240
|
renderCall(_args, theme, context) {
|
|
240
241
|
const text = reuseText(context?.lastComponent);
|
|
241
|
-
text.setText(theme.fg("toolTitle", theme.bold(`${ENTER_TOOL} `)) + theme
|
|
242
|
+
text.setText(theme.fg("toolTitle", theme.bold(`${ENTER_TOOL} `)) + paint(theme, "meta", "requested plan mode"));
|
|
242
243
|
return text;
|
|
243
244
|
},
|
|
244
245
|
|
|
@@ -250,10 +251,10 @@ export function registerTools(
|
|
|
250
251
|
text.setText(first?.type === "text" ? first.text : "");
|
|
251
252
|
return text;
|
|
252
253
|
}
|
|
253
|
-
if (details.unavailable) text.setText(theme
|
|
254
|
-
else if (details.already) text.setText(theme
|
|
255
|
-
else if (details.entered) text.setText(theme
|
|
256
|
-
else text.setText(theme
|
|
254
|
+
if (details.unavailable) text.setText(paint(theme, "warn", "No UI to confirm plan mode"));
|
|
255
|
+
else if (details.already) text.setText(paint(theme, "meta", "Already in plan mode"));
|
|
256
|
+
else if (details.entered) text.setText(paint(theme, "ok", "✓ Plan mode enabled"));
|
|
257
|
+
else text.setText(paint(theme, "meta", "Stayed in normal mode"));
|
|
257
258
|
return text;
|
|
258
259
|
},
|
|
259
260
|
});
|
|
@@ -315,7 +316,7 @@ export function registerTools(
|
|
|
315
316
|
renderCall(args, theme, context) {
|
|
316
317
|
const text = reuseText(context?.lastComponent);
|
|
317
318
|
const title = (args as Partial<WritePlanArgs>).title;
|
|
318
|
-
const suffix = title ? theme
|
|
319
|
+
const suffix = title ? paint(theme, "meta", sanitizeLine(title)) : paint(theme, "meta", "plan");
|
|
319
320
|
text.setText(theme.fg("toolTitle", theme.bold(`${WRITE_PLAN_TOOL} `)) + suffix);
|
|
320
321
|
return text;
|
|
321
322
|
},
|
|
@@ -328,7 +329,7 @@ export function registerTools(
|
|
|
328
329
|
text.setText(first?.type === "text" ? first.text : "");
|
|
329
330
|
return text;
|
|
330
331
|
}
|
|
331
|
-
text.setText(theme
|
|
332
|
+
text.setText(paint(theme, "ok", "✓ Saved plan ") + paint(theme, "meta", details.relativePath));
|
|
332
333
|
return text;
|
|
333
334
|
},
|
|
334
335
|
});
|
|
@@ -393,6 +394,8 @@ export function registerTools(
|
|
|
393
394
|
}
|
|
394
395
|
|
|
395
396
|
let outcome = await reviewPlan(ctx, file);
|
|
397
|
+
// Comments carried by a browser approval, reported alongside the approval.
|
|
398
|
+
let browserComments: PlanAnnotation[] = [];
|
|
396
399
|
// Review, refine, and browser review all loop back here until the user
|
|
397
400
|
// approves, keeps planning, or a refinement is handed to the model. The
|
|
398
401
|
// dialog-based refine editor carries no text from the review screen, so an
|
|
@@ -400,15 +403,25 @@ export function registerTools(
|
|
|
400
403
|
// as "not approved".
|
|
401
404
|
for (;;) {
|
|
402
405
|
if (outcome.action === "browser") {
|
|
403
|
-
const session = await web
|
|
406
|
+
const session: PlanReviewSession | undefined = await web
|
|
407
|
+
.start(ctx.cwd, file, { canApprove: true })
|
|
408
|
+
.catch(() => undefined);
|
|
404
409
|
if (!session) {
|
|
405
410
|
ctx.ui.notify("Plan review: could not start the browser review server.", "error");
|
|
406
411
|
outcome = await reviewPlan(ctx, file);
|
|
407
412
|
continue;
|
|
408
413
|
}
|
|
409
414
|
ctx.ui.notify(`Plan review: opened ${session.url}`, "info");
|
|
410
|
-
const result = await session.done;
|
|
415
|
+
const result: PlanReviewResult = await session.done;
|
|
411
416
|
await session.close();
|
|
417
|
+
if (result.status === "approved") {
|
|
418
|
+
// Approve from the browser is the TUI's approve: keep the comments it
|
|
419
|
+
// carried and fall through to the shared approval path below.
|
|
420
|
+
file = result.plan;
|
|
421
|
+
browserComments = result.annotations;
|
|
422
|
+
outcome = { action: "approve" };
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
412
425
|
if (result.status === "saved") {
|
|
413
426
|
// The comments are already in the file; hand them back as a refinement so
|
|
414
427
|
// the model revises the same plan and calls exit_plan_mode again.
|
|
@@ -490,11 +503,14 @@ export function registerTools(
|
|
|
490
503
|
: steps.length > 0
|
|
491
504
|
? ` Could not record the steps in the todo list${error ? `: ${error}` : ""}. Keep these steps in mind and tick them off with ${SET_PLAN_STEPS_TOOL} as you go.`
|
|
492
505
|
: " If the todo tool is available, record the steps with it before you start.";
|
|
506
|
+
const browserNote = browserComments.length
|
|
507
|
+
? `\n\nThe user approved from the browser review with these comments:\n${feedbackSummary(browserComments)}`
|
|
508
|
+
: "";
|
|
493
509
|
return {
|
|
494
510
|
content: [
|
|
495
511
|
{
|
|
496
512
|
type: "text",
|
|
497
|
-
text: `Plan approved. Plan mode is off and write access is restored. The plan file ${file.relativePath} is the source of truth; follow its steps.${tail}${listing}`,
|
|
513
|
+
text: `Plan approved. Plan mode is off and write access is restored. The plan file ${file.relativePath} is the source of truth; follow its steps.${browserNote}${tail}${listing}`,
|
|
498
514
|
},
|
|
499
515
|
],
|
|
500
516
|
details: {
|
|
@@ -508,7 +524,7 @@ export function registerTools(
|
|
|
508
524
|
|
|
509
525
|
renderCall(_args, theme, context) {
|
|
510
526
|
const text = reuseText(context?.lastComponent);
|
|
511
|
-
text.setText(theme.fg("toolTitle", theme.bold(`${EXIT_TOOL} `)) + theme
|
|
527
|
+
text.setText(theme.fg("toolTitle", theme.bold(`${EXIT_TOOL} `)) + paint(theme, "meta", "submitted a plan"));
|
|
512
528
|
return text;
|
|
513
529
|
},
|
|
514
530
|
|
|
@@ -521,25 +537,25 @@ export function registerTools(
|
|
|
521
537
|
return text;
|
|
522
538
|
}
|
|
523
539
|
const heading = details.unavailable
|
|
524
|
-
? theme
|
|
540
|
+
? paint(theme, "warn", "No UI to approve the plan")
|
|
525
541
|
: details.approved
|
|
526
|
-
? theme
|
|
542
|
+
? paint(theme, "ok", "✓ Plan approved")
|
|
527
543
|
: details.refined
|
|
528
|
-
? theme
|
|
529
|
-
: theme
|
|
544
|
+
? paint(theme, "accent", "Refining the plan")
|
|
545
|
+
: paint(theme, "warn", "Plan not approved");
|
|
530
546
|
const extra =
|
|
531
547
|
details.refined && details.refinement
|
|
532
|
-
? `\n${theme
|
|
548
|
+
? `\n${paint(theme, "meta", `↳ ${sanitizePlanText(details.refinement)}`)}`
|
|
533
549
|
: "";
|
|
534
550
|
const shown = details.relativePath ?? details.planPath;
|
|
535
|
-
const path = shown ? `\n${theme
|
|
551
|
+
const path = shown ? `\n${paint(theme, "meta", shown)}` : "";
|
|
536
552
|
const planText = sanitizePlanText(details.plan);
|
|
537
553
|
let out = `${heading}${extra}${path}`;
|
|
538
554
|
if (planText) {
|
|
539
555
|
const { body, more } = expanded ? { body: planText, more: 0 } : preview(planText);
|
|
540
556
|
// A blank line separates the header block from the plan body.
|
|
541
557
|
out += `\n\n${theme.fg("toolOutput", body)}`;
|
|
542
|
-
if (more > 0) out += `\n${theme
|
|
558
|
+
if (more > 0) out += `\n${paint(theme, "faint", `… ${more} more lines`)} ${expandHint(theme)}`;
|
|
543
559
|
}
|
|
544
560
|
text.setText(out);
|
|
545
561
|
return text;
|
|
@@ -609,7 +625,7 @@ export function registerTools(
|
|
|
609
625
|
: updates && updates.length > 1
|
|
610
626
|
? `${updates.length} steps`
|
|
611
627
|
: "steps";
|
|
612
|
-
text.setText(theme.fg("toolTitle", theme.bold(`${SET_PLAN_STEPS_TOOL} `)) + theme
|
|
628
|
+
text.setText(theme.fg("toolTitle", theme.bold(`${SET_PLAN_STEPS_TOOL} `)) + paint(theme, "meta", label));
|
|
613
629
|
return text;
|
|
614
630
|
},
|
|
615
631
|
|
|
@@ -625,10 +641,10 @@ export function registerTools(
|
|
|
625
641
|
const pending = details.updates.filter((update) => update.status === "pending").map((u) => u.step);
|
|
626
642
|
const heading =
|
|
627
643
|
completed.length > 0
|
|
628
|
-
? theme
|
|
629
|
-
(pending.length > 0 ? theme
|
|
630
|
-
: theme
|
|
631
|
-
text.setText(heading + theme
|
|
644
|
+
? paint(theme, "ok", `✓ ${stepLabel(completed)} done`) +
|
|
645
|
+
(pending.length > 0 ? paint(theme, "meta", ` · ${stepLabel(pending)} reopened`) : "")
|
|
646
|
+
: paint(theme, "meta", `${stepLabel(pending)} reopened`);
|
|
647
|
+
text.setText(heading + paint(theme, "faint", ` · ${details.done}/${details.total}`));
|
|
632
648
|
return text;
|
|
633
649
|
},
|
|
634
650
|
});
|
package/extensions/plan/tui.ts
CHANGED
|
@@ -33,13 +33,17 @@ import {
|
|
|
33
33
|
import { stripControlChars } from "../../lib/format.ts";
|
|
34
34
|
import {
|
|
35
35
|
editorTheme,
|
|
36
|
-
screenHeader,
|
|
37
36
|
screenHint,
|
|
38
37
|
viewportRows,
|
|
39
38
|
type ScreenHint,
|
|
40
39
|
type ViewportRowsSource,
|
|
41
|
-
} from "../../lib/tui.ts";
|
|
42
|
-
import {
|
|
40
|
+
} from "../../lib/tui/chrome.ts";
|
|
41
|
+
import { isCloseKey } from "../../lib/tui/cursor.ts";
|
|
42
|
+
import { gaugeBar } from "../../lib/tui/gauge.ts";
|
|
43
|
+
import { GLYPHS } from "../../lib/tui/glyphs.ts";
|
|
44
|
+
import { titleRule } from "../../lib/tui/rules.ts";
|
|
45
|
+
import { emptyState, checkbox } from "../../lib/tui/rows.ts";
|
|
46
|
+
import { paint } from "../../lib/tui/tokens.ts";
|
|
43
47
|
import { planTitle, type StoredPlan } from "./plans.ts";
|
|
44
48
|
import { extractPlanSections } from "./steps.ts";
|
|
45
49
|
|
|
@@ -110,14 +114,12 @@ export function sanitizePlanText(text: string): string {
|
|
|
110
114
|
|
|
111
115
|
/** A task checkbox in a list marker: `- [x] `, `1. [ ] `. */
|
|
112
116
|
const TASK_MARKER = /^([-*+]|\d+[.)])(\s+)(\[([ xX])\])(\s*)$/;
|
|
113
|
-
/** Task-checkbox markers; three columns wide so list wrapping stays aligned. */
|
|
114
|
-
const TASK_BOX = { done: `[${GLYPHS.check}]`, pending: "[ ]" } as const;
|
|
115
117
|
|
|
116
118
|
/**
|
|
117
|
-
* Wrap the Markdown theme so task checkboxes read at a glance
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* continuation
|
|
119
|
+
* Wrap the Markdown theme so task checkboxes read at a glance with the shared
|
|
120
|
+
* `◼`/`◻` vocabulary, the same box the bytefly screen and the question dialog
|
|
121
|
+
* draw. The replacement is one column wide instead of the bracket's three, so a
|
|
122
|
+
* wrapped list continuation shifts left by two.
|
|
121
123
|
*/
|
|
122
124
|
function planMarkdownTheme(theme: Theme, base: MarkdownTheme): MarkdownTheme {
|
|
123
125
|
return {
|
|
@@ -126,27 +128,19 @@ function planMarkdownTheme(theme: Theme, base: MarkdownTheme): MarkdownTheme {
|
|
|
126
128
|
const match = TASK_MARKER.exec(text);
|
|
127
129
|
if (!match) return base.listBullet(text);
|
|
128
130
|
const [, bullet, gap, , mark, trail] = match;
|
|
129
|
-
|
|
130
|
-
return `${base.listBullet(`${bullet}${gap}`)}${box}${trail}`;
|
|
131
|
+
return `${base.listBullet(`${bullet}${gap}`)}${checkbox(theme, mark.toLowerCase() === "x")}${trail}`;
|
|
131
132
|
},
|
|
132
133
|
};
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
/** A fixed-width gauge: `fillSegments` (rounded) in `fill`, the rest dim. */
|
|
136
|
-
function gauge(theme: Theme, fillSegments: number, fill: "accent" | "success"): string {
|
|
137
|
-
const count = Math.min(GAUGE_SEGMENTS, Math.max(0, Math.round(fillSegments)));
|
|
138
|
-
return (
|
|
139
|
-
theme.fg(fill, GLYPHS.gaugeFull.repeat(count)) + theme.fg("dim", GLYPHS.gaugeEmpty.repeat(GAUGE_SEGMENTS - count))
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
136
|
/** Scroll position below the body: a gauge, the percent, and the visible range. */
|
|
144
137
|
function scrollRow(theme: Theme, width: number, offset: number, end: number, total: number, section?: string): string {
|
|
145
138
|
const maxOffset = Math.max(0, total - (end - offset));
|
|
146
139
|
const percent = maxOffset > 0 ? Math.round((offset / maxOffset) * 100) : 100;
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
140
|
+
const bar = gaugeBar(theme, { percent, blocks: GAUGE_SEGMENTS, role: "accent", emptyRole: "faint" });
|
|
141
|
+
const head = `${bar} ${paint(theme, "accent", `${percent}%`)}`;
|
|
142
|
+
const range = paint(theme, "faint", `lines ${offset + 1}–${end} of ${total}`);
|
|
143
|
+
const label = section ? paint(theme, "meta", `${GLYPHS.plan} ${section}`) : "";
|
|
150
144
|
// Keep the gauge and percent; drop the range first, then the section label, on a
|
|
151
145
|
// narrow terminal.
|
|
152
146
|
const candidates = [
|
|
@@ -295,7 +289,7 @@ export class PlanViewComponent implements Component {
|
|
|
295
289
|
|
|
296
290
|
handleInput(data: string): void {
|
|
297
291
|
if (this.editing) {
|
|
298
|
-
if (
|
|
292
|
+
if (isCloseKey(data)) {
|
|
299
293
|
this.cancelRefine();
|
|
300
294
|
return;
|
|
301
295
|
}
|
|
@@ -303,7 +297,7 @@ export class PlanViewComponent implements Component {
|
|
|
303
297
|
this.options.requestRender();
|
|
304
298
|
return;
|
|
305
299
|
}
|
|
306
|
-
if (
|
|
300
|
+
if (isCloseKey(data)) {
|
|
307
301
|
this.options.onClose("keep");
|
|
308
302
|
return;
|
|
309
303
|
}
|
|
@@ -361,19 +355,19 @@ export class PlanViewComponent implements Component {
|
|
|
361
355
|
this.offset = Math.min(Math.max(0, this.offset), maxOffset);
|
|
362
356
|
const sectionLabel = this.currentSection(sections)?.title;
|
|
363
357
|
|
|
364
|
-
const lines: string[] = [
|
|
358
|
+
const lines: string[] = [titleRule(theme, w, truncateToWidth(`Plan Review · ${title}`, Math.max(1, w - 6)))];
|
|
365
359
|
lines.push("");
|
|
366
360
|
|
|
367
361
|
const end = Math.min(body.length, this.offset + visible);
|
|
368
362
|
for (let i = this.offset; i < end; i++) lines.push(body[i]);
|
|
369
|
-
if (body.length === 0) lines.push(
|
|
363
|
+
if (body.length === 0) lines.push(...emptyState(theme, w, { label: "This plan is empty." }));
|
|
370
364
|
|
|
371
365
|
if (this.editing) {
|
|
372
366
|
// Keep the scroll position visible while editing: the body above is still
|
|
373
367
|
// clipped, so the reader needs to know where they are.
|
|
374
368
|
if (this.maxOffset > 0) lines.push(scrollRow(theme, w, this.offset, end, body.length, sectionLabel));
|
|
375
369
|
else lines.push("");
|
|
376
|
-
lines.push(truncateToWidth(` ${theme
|
|
370
|
+
lines.push(truncateToWidth(` ${paint(theme, "meta", "Refine the plan:")}`, w));
|
|
377
371
|
for (const line of editorLines) lines.push(truncateToWidth(` ${line}`, w));
|
|
378
372
|
lines.push("");
|
|
379
373
|
lines.push(screenHint(theme, w, ["Esc back to plan", { text: "Enter submit", color: "success" }]));
|