gsd-pi 2.28.0-dev.e19bf89 → 2.29.0-dev.2ccf3fb
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 +24 -17
- package/dist/cli.js +15 -9
- package/dist/resource-loader.js +80 -8
- package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +186 -65
- package/dist/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/dist/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/dist/resources/extensions/gsd/auto-start.ts +25 -10
- package/dist/resources/extensions/gsd/auto-verification.ts +41 -7
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/dist/resources/extensions/gsd/auto.ts +67 -22
- package/dist/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/dist/resources/extensions/gsd/commands-logs.ts +536 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/dist/resources/extensions/gsd/commands.ts +75 -29
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/dist/resources/extensions/gsd/doctor-types.ts +13 -0
- package/dist/resources/extensions/gsd/doctor.ts +2 -6
- package/dist/resources/extensions/gsd/export.ts +28 -2
- package/dist/resources/extensions/gsd/gsd-db.ts +19 -0
- package/dist/resources/extensions/gsd/index.ts +2 -1
- package/dist/resources/extensions/gsd/json-persistence.ts +67 -0
- package/dist/resources/extensions/gsd/metrics.ts +17 -31
- package/dist/resources/extensions/gsd/paths.ts +0 -8
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/dist/resources/extensions/gsd/queue-order.ts +10 -11
- package/dist/resources/extensions/gsd/routing-history.ts +13 -17
- package/dist/resources/extensions/gsd/session-lock.ts +284 -0
- package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/dist/resources/extensions/gsd/types.ts +1 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/dist/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/dist/resources/extensions/gsd/verification-gate.ts +13 -2
- package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/dist/resources/extensions/mcp-client/index.ts +459 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
- package/dist/resources/extensions/remote-questions/notify.ts +1 -2
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/dist/resources/extensions/remote-questions/types.ts +3 -0
- package/dist/resources/extensions/shared/mod.ts +3 -0
- package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/dist/resources/skills/create-skill/SKILL.md +184 -0
- package/dist/resources/skills/create-skill/references/api-security.md +226 -0
- package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
- package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/package.json +6 -3
- package/packages/native/dist/native.d.ts +2 -0
- package/packages/native/dist/native.js +19 -5
- package/packages/native/src/native.ts +23 -9
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +10 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/scripts/copy-assets.cjs +39 -8
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +11 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -1
- package/packages/pi-tui/dist/autocomplete.d.ts +3 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +14 -0
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/src/autocomplete.ts +19 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +186 -65
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/src/resources/extensions/gsd/auto-start.ts +25 -10
- package/src/resources/extensions/gsd/auto-verification.ts +41 -7
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/src/resources/extensions/gsd/auto.ts +67 -22
- package/src/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/src/resources/extensions/gsd/commands-logs.ts +536 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/src/resources/extensions/gsd/commands.ts +75 -29
- package/src/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/src/resources/extensions/gsd/doctor-types.ts +13 -0
- package/src/resources/extensions/gsd/doctor.ts +2 -6
- package/src/resources/extensions/gsd/export.ts +28 -2
- package/src/resources/extensions/gsd/gsd-db.ts +19 -0
- package/src/resources/extensions/gsd/index.ts +2 -1
- package/src/resources/extensions/gsd/json-persistence.ts +67 -0
- package/src/resources/extensions/gsd/metrics.ts +17 -31
- package/src/resources/extensions/gsd/paths.ts +0 -8
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/src/resources/extensions/gsd/queue-order.ts +10 -11
- package/src/resources/extensions/gsd/routing-history.ts +13 -17
- package/src/resources/extensions/gsd/session-lock.ts +284 -0
- package/src/resources/extensions/gsd/session-status-io.ts +23 -41
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/src/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/src/resources/extensions/gsd/verification-gate.ts +13 -2
- package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/src/resources/extensions/mcp-client/index.ts +459 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/src/resources/extensions/remote-questions/http-client.ts +76 -0
- package/src/resources/extensions/remote-questions/notify.ts +1 -2
- package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/src/resources/extensions/remote-questions/types.ts +3 -0
- package/src/resources/extensions/shared/mod.ts +3 -0
- package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/src/resources/skills/create-skill/SKILL.md +184 -0
- package/src/resources/skills/create-skill/references/api-security.md +226 -0
- package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/src/resources/skills/create-skill/references/core-principles.md +437 -0
- package/src/resources/skills/create-skill/references/executable-code.md +175 -0
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/src/resources/skills/create-skill/references/using-templates.md +112 -0
- package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/dist/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/dist/resources/extensions/mcporter/index.ts +0 -525
- package/dist/resources/extensions/shared/progress-widget.ts +0 -282
- package/dist/resources/extensions/shared/thinking-widget.ts +0 -107
- package/src/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/src/resources/extensions/mcporter/index.ts +0 -525
- package/src/resources/extensions/shared/progress-widget.ts +0 -282
- package/src/resources/extensions/shared/thinking-widget.ts +0 -107
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<overview>
|
|
2
|
+
Complete custom tools reference — registration, parameters, execution, output truncation, overrides, rendering, and dynamic registration.
|
|
3
|
+
</overview>
|
|
4
|
+
|
|
5
|
+
<registration>
|
|
6
|
+
```typescript
|
|
7
|
+
import { Type } from "@sinclair/typebox";
|
|
8
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
9
|
+
|
|
10
|
+
pi.registerTool({
|
|
11
|
+
name: "my_tool", // Unique identifier (snake_case)
|
|
12
|
+
label: "My Tool", // Display name in TUI
|
|
13
|
+
description: "What this does", // Full description shown to LLM
|
|
14
|
+
|
|
15
|
+
// Optional: one-liner for system prompt "Available tools" section
|
|
16
|
+
promptSnippet: "Manage project todo items",
|
|
17
|
+
|
|
18
|
+
// Optional: bullets added to system prompt "Guidelines" when tool is active
|
|
19
|
+
promptGuidelines: [
|
|
20
|
+
"Use my_tool for task management instead of file edits."
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
// Parameter schema (MUST use TypeBox)
|
|
24
|
+
parameters: Type.Object({
|
|
25
|
+
action: StringEnum(["list", "add", "remove"] as const),
|
|
26
|
+
text: Type.Optional(Type.String({ description: "Item text" })),
|
|
27
|
+
id: Type.Optional(Type.Number({ description: "Item ID" })),
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
31
|
+
// 1. Check cancellation
|
|
32
|
+
if (signal?.aborted) {
|
|
33
|
+
return { content: [{ type: "text", text: "Cancelled" }] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Stream progress (optional)
|
|
37
|
+
onUpdate?.({
|
|
38
|
+
content: [{ type: "text", text: "Working..." }],
|
|
39
|
+
details: { progress: 50 },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 3. Do the work
|
|
43
|
+
const result = await doWork(params);
|
|
44
|
+
|
|
45
|
+
// 4. Return result
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: "Result text for LLM" }], // Sent to LLM context
|
|
48
|
+
details: { data: result }, // For rendering & state
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Optional: custom TUI rendering
|
|
53
|
+
renderCall(args, theme) { ... },
|
|
54
|
+
renderResult(result, { expanded, isPartial }, theme) { ... },
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
</registration>
|
|
58
|
+
|
|
59
|
+
<critical_stringenum>
|
|
60
|
+
**⚠️ MUST use `StringEnum` for string enum parameters:**
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
64
|
+
|
|
65
|
+
// ✅ Correct — works with all providers including Google
|
|
66
|
+
action: StringEnum(["list", "add", "remove"] as const)
|
|
67
|
+
|
|
68
|
+
// ❌ BROKEN with Google's API
|
|
69
|
+
action: Type.Union([Type.Literal("list"), Type.Literal("add")])
|
|
70
|
+
```
|
|
71
|
+
</critical_stringenum>
|
|
72
|
+
|
|
73
|
+
<output_truncation>
|
|
74
|
+
Tools MUST truncate output to avoid context overflow. Built-in limit: 50KB / 2000 lines.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import {
|
|
78
|
+
truncateHead, truncateTail, formatSize,
|
|
79
|
+
DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES,
|
|
80
|
+
} from "@mariozechner/pi-coding-agent";
|
|
81
|
+
|
|
82
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
83
|
+
const output = await runCommand();
|
|
84
|
+
const truncation = truncateHead(output, {
|
|
85
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
86
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
let result = truncation.content;
|
|
90
|
+
if (truncation.truncated) {
|
|
91
|
+
const tempFile = writeTempFile(output);
|
|
92
|
+
result += `\n\n[Output truncated: ${truncation.outputLines}/${truncation.totalLines} lines`;
|
|
93
|
+
result += ` (${formatSize(truncation.outputBytes)}/${formatSize(truncation.totalBytes)}).`;
|
|
94
|
+
result += ` Full output: ${tempFile}]`;
|
|
95
|
+
}
|
|
96
|
+
return { content: [{ type: "text", text: result }] };
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use `truncateHead` when beginning matters (search results, file reads). Use `truncateTail` when end matters (logs, command output).
|
|
101
|
+
</output_truncation>
|
|
102
|
+
|
|
103
|
+
<signaling_errors>
|
|
104
|
+
Throw to signal an error (sets `isError: true`). Returning a value never sets error flag.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
async execute(toolCallId, params) {
|
|
108
|
+
if (!isValid(params.input)) {
|
|
109
|
+
throw new Error(`Invalid input: ${params.input}`);
|
|
110
|
+
}
|
|
111
|
+
return { content: [{ type: "text", text: "OK" }], details: {} };
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
</signaling_errors>
|
|
115
|
+
|
|
116
|
+
<dynamic_registration>
|
|
117
|
+
Tools can be registered at any time — during load, in `session_start`, in command handlers. Available immediately without `/reload`.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
121
|
+
pi.registerTool({ name: "dynamic_tool", ... });
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Use `pi.setActiveTools(names)` to enable/disable tools at runtime.
|
|
126
|
+
</dynamic_registration>
|
|
127
|
+
|
|
128
|
+
<overriding_builtins>
|
|
129
|
+
Register a tool with the same name as a built-in (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) to override it. **Must match exact result shape including `details` type.**
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { createReadTool } from "@mariozechner/pi-coding-agent";
|
|
133
|
+
|
|
134
|
+
pi.registerTool({
|
|
135
|
+
name: "read",
|
|
136
|
+
label: "Read (Logged)",
|
|
137
|
+
description: "Read file contents with logging",
|
|
138
|
+
parameters: Type.Object({
|
|
139
|
+
path: Type.String(),
|
|
140
|
+
offset: Type.Optional(Type.Number()),
|
|
141
|
+
limit: Type.Optional(Type.Number()),
|
|
142
|
+
}),
|
|
143
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
144
|
+
console.log(`[AUDIT] Reading: ${params.path}`);
|
|
145
|
+
const builtIn = createReadTool(ctx.cwd);
|
|
146
|
+
return builtIn.execute(toolCallId, params, signal, onUpdate);
|
|
147
|
+
},
|
|
148
|
+
// Omit renderCall/renderResult to use built-in renderer
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Start with no built-in tools: `gsd --no-tools -e ./my-extension.ts`
|
|
153
|
+
</overriding_builtins>
|
|
154
|
+
|
|
155
|
+
<multiple_tools>
|
|
156
|
+
One extension can register multiple tools with shared state:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
export default function (pi: ExtensionAPI) {
|
|
160
|
+
let connection = null;
|
|
161
|
+
|
|
162
|
+
pi.registerTool({ name: "db_connect", ... });
|
|
163
|
+
pi.registerTool({ name: "db_query", ... });
|
|
164
|
+
pi.registerTool({ name: "db_close", ... });
|
|
165
|
+
|
|
166
|
+
pi.on("session_shutdown", async () => {
|
|
167
|
+
connection?.close();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
</multiple_tools>
|
|
172
|
+
|
|
173
|
+
<path_normalization>
|
|
174
|
+
Some models add `@` prefix to path arguments. Strip it:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
178
|
+
let path = params.path;
|
|
179
|
+
if (path.startsWith("@")) path = path.slice(1);
|
|
180
|
+
// ...
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
</path_normalization>
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
<overview>
|
|
2
|
+
Complete custom UI reference — dialogs, persistent elements, custom components, overlays, custom editors, built-in components, keyboard input, performance, theming, and common mistakes.
|
|
3
|
+
</overview>
|
|
4
|
+
|
|
5
|
+
<ui_architecture>
|
|
6
|
+
```
|
|
7
|
+
┌─────────────────────────────────────────────────┐
|
|
8
|
+
│ Custom Header (ctx.ui.setHeader) │
|
|
9
|
+
├─────────────────────────────────────────────────┤
|
|
10
|
+
│ Message Area │
|
|
11
|
+
│ - User/assistant messages │
|
|
12
|
+
│ - Tool calls ◄── renderCall/renderResult │
|
|
13
|
+
│ - Custom messages ◄── registerMessageRenderer │
|
|
14
|
+
├─────────────────────────────────────────────────┤
|
|
15
|
+
│ Widgets (above editor) ◄── ctx.ui.setWidget │
|
|
16
|
+
├─────────────────────────────────────────────────┤
|
|
17
|
+
│ Editor ◄── ctx.ui.custom() / setEditorComponent│
|
|
18
|
+
├─────────────────────────────────────────────────┤
|
|
19
|
+
│ Widgets (below editor) ◄── ctx.ui.setWidget │
|
|
20
|
+
├─────────────────────────────────────────────────┤
|
|
21
|
+
│ Footer ◄── ctx.ui.setFooter / setStatus │
|
|
22
|
+
└─────────────────────────────────────────────────┘
|
|
23
|
+
┌─────────────────────┐
|
|
24
|
+
│ Overlay (floating) │ ◄── ctx.ui.custom({ overlay })
|
|
25
|
+
└─────────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**11 ways to get UI on screen:**
|
|
29
|
+
|
|
30
|
+
| Method | Blocks? | Replaces editor? |
|
|
31
|
+
|--------|---------|-------------------|
|
|
32
|
+
| `ctx.ui.select/confirm/input/editor` | Yes | Temporarily |
|
|
33
|
+
| `ctx.ui.notify` | No | No |
|
|
34
|
+
| `ctx.ui.setStatus` | No | No (footer) |
|
|
35
|
+
| `ctx.ui.setWidget` | No | No |
|
|
36
|
+
| `ctx.ui.setFooter` | No | No (replaces footer) |
|
|
37
|
+
| `ctx.ui.setHeader` | No | No (replaces header) |
|
|
38
|
+
| `ctx.ui.custom()` | Yes | Temporarily |
|
|
39
|
+
| `ctx.ui.custom({overlay})` | Yes | No (renders on top) |
|
|
40
|
+
| `ctx.ui.setEditorComponent` | No | Yes (permanently) |
|
|
41
|
+
| `renderCall/renderResult` | No | No (inline in messages) |
|
|
42
|
+
| `registerMessageRenderer` | No | No (inline in messages) |
|
|
43
|
+
</ui_architecture>
|
|
44
|
+
|
|
45
|
+
<component_interface>
|
|
46
|
+
Every visual element implements:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
interface Component {
|
|
50
|
+
render(width: number): string[]; // Required — each line ≤ width visible chars
|
|
51
|
+
handleInput?(data: string): void; // Optional — receive keyboard input
|
|
52
|
+
wantsKeyRelease?: boolean; // Optional — receive key release events (Kitty protocol)
|
|
53
|
+
invalidate(): void; // Required — clear cached render state
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Render contract:**
|
|
58
|
+
- Return array of strings, one per line
|
|
59
|
+
- Each string MUST NOT exceed `width` in visible characters
|
|
60
|
+
- ANSI escape codes don't count toward visible width
|
|
61
|
+
- **Styles are reset at end of each line** — reapply per line
|
|
62
|
+
- Return `[]` for zero-height component
|
|
63
|
+
|
|
64
|
+
**Invalidation contract:**
|
|
65
|
+
- Clear ALL cached render output
|
|
66
|
+
- Clear any pre-baked themed strings
|
|
67
|
+
- Call `super.invalidate()` if extending a built-in component
|
|
68
|
+
</component_interface>
|
|
69
|
+
|
|
70
|
+
<dialogs>
|
|
71
|
+
Blocking dialog methods on `ctx.ui`:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const choice = await ctx.ui.select("Pick one:", ["A", "B", "C"]); // string | undefined
|
|
75
|
+
const ok = await ctx.ui.confirm("Delete?", "This cannot be undone"); // boolean
|
|
76
|
+
const name = await ctx.ui.input("Name:", "placeholder"); // string | undefined
|
|
77
|
+
const text = await ctx.ui.editor("Edit:", "prefilled text"); // string | undefined
|
|
78
|
+
|
|
79
|
+
// Timed auto-dismiss with countdown
|
|
80
|
+
const ok = await ctx.ui.confirm("Proceed?", "Auto-continues in 5s", { timeout: 5000 });
|
|
81
|
+
// Returns false on timeout, undefined for select/input
|
|
82
|
+
|
|
83
|
+
// Manual dismissal with AbortSignal (distinguish timeout from cancel)
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
86
|
+
const ok = await ctx.ui.confirm("Timed", "Auto-cancels in 5s", { signal: controller.signal });
|
|
87
|
+
clearTimeout(timeoutId);
|
|
88
|
+
if (controller.signal.aborted) { /* timed out */ }
|
|
89
|
+
```
|
|
90
|
+
</dialogs>
|
|
91
|
+
|
|
92
|
+
<persistent_ui>
|
|
93
|
+
```typescript
|
|
94
|
+
// Footer status (multiple extensions can set independent entries)
|
|
95
|
+
ctx.ui.setStatus("my-ext", "● Active");
|
|
96
|
+
ctx.ui.setStatus("my-ext", undefined); // Clear
|
|
97
|
+
|
|
98
|
+
// Widgets
|
|
99
|
+
ctx.ui.setWidget("my-id", ["Line 1", "Line 2"]); // Above editor
|
|
100
|
+
ctx.ui.setWidget("my-id", ["Below"], { placement: "belowEditor" }); // Below editor
|
|
101
|
+
ctx.ui.setWidget("my-id", (_tui, theme) => ({ // Themed
|
|
102
|
+
render: () => [theme.fg("accent", "Styled")],
|
|
103
|
+
invalidate: () => {},
|
|
104
|
+
}));
|
|
105
|
+
ctx.ui.setWidget("my-id", undefined); // Clear
|
|
106
|
+
|
|
107
|
+
// Working message during streaming
|
|
108
|
+
ctx.ui.setWorkingMessage("Analyzing code...");
|
|
109
|
+
ctx.ui.setWorkingMessage(); // Restore default
|
|
110
|
+
|
|
111
|
+
// Custom footer (full replacement)
|
|
112
|
+
ctx.ui.setFooter((tui, theme, footerData) => ({
|
|
113
|
+
render(width) {
|
|
114
|
+
const branch = footerData.getGitBranch(); // Only available here
|
|
115
|
+
const statuses = footerData.getExtensionStatuses(); // All setStatus values
|
|
116
|
+
return [truncateToWidth(`${branch} | model`, width)];
|
|
117
|
+
},
|
|
118
|
+
invalidate() {},
|
|
119
|
+
dispose: footerData.onBranchChange(() => tui.requestRender()), // Reactive
|
|
120
|
+
}));
|
|
121
|
+
ctx.ui.setFooter(undefined); // Restore default
|
|
122
|
+
|
|
123
|
+
// Custom header
|
|
124
|
+
ctx.ui.setHeader((tui, theme) => ({
|
|
125
|
+
render(width) { return [theme.fg("accent", theme.bold("My Header"))]; },
|
|
126
|
+
invalidate() {},
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
// Editor control
|
|
130
|
+
ctx.ui.setEditorText("Prefill");
|
|
131
|
+
const current = ctx.ui.getEditorText();
|
|
132
|
+
ctx.ui.pasteToEditor("pasted content"); // Triggers paste handling
|
|
133
|
+
|
|
134
|
+
// Tool expansion
|
|
135
|
+
ctx.ui.setToolsExpanded(true);
|
|
136
|
+
const expanded = ctx.ui.getToolsExpanded();
|
|
137
|
+
|
|
138
|
+
// Theme management
|
|
139
|
+
const themes = ctx.ui.getAllThemes();
|
|
140
|
+
ctx.ui.setTheme("light");
|
|
141
|
+
ctx.ui.theme.fg("accent", "text"); // Access current theme
|
|
142
|
+
```
|
|
143
|
+
</persistent_ui>
|
|
144
|
+
|
|
145
|
+
<custom_components>
|
|
146
|
+
`ctx.ui.custom()` temporarily replaces the editor. Returns a value when `done()` is called.
|
|
147
|
+
|
|
148
|
+
**Factory callback args:**
|
|
149
|
+
|
|
150
|
+
| Argument | Type | Purpose |
|
|
151
|
+
|----------|------|---------|
|
|
152
|
+
| `tui` | `TUI` | `tui.requestRender()` triggers re-render after state changes |
|
|
153
|
+
| `theme` | `Theme` | Current theme for styling |
|
|
154
|
+
| `keybindings` | `KeybindingsManager` | App keybinding config |
|
|
155
|
+
| `done` | `(value: T) => void` | Close component and return value |
|
|
156
|
+
|
|
157
|
+
**Inline pattern:**
|
|
158
|
+
```typescript
|
|
159
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, keybindings, done) => ({
|
|
160
|
+
render(width: number): string[] {
|
|
161
|
+
return [truncateToWidth("Press Enter to confirm, Escape to cancel", width)];
|
|
162
|
+
},
|
|
163
|
+
handleInput(data: string) {
|
|
164
|
+
if (matchesKey(data, Key.enter)) done("confirmed");
|
|
165
|
+
if (matchesKey(data, Key.escape)) done(null);
|
|
166
|
+
},
|
|
167
|
+
invalidate() {},
|
|
168
|
+
}));
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Class-based pattern (recommended for complex UI):**
|
|
172
|
+
```typescript
|
|
173
|
+
class MyComponent {
|
|
174
|
+
private selected = 0;
|
|
175
|
+
private cachedWidth?: number;
|
|
176
|
+
private cachedLines?: string[];
|
|
177
|
+
|
|
178
|
+
constructor(
|
|
179
|
+
private tui: { requestRender: () => void },
|
|
180
|
+
private theme: Theme,
|
|
181
|
+
private items: string[],
|
|
182
|
+
private done: (value: string | null) => void,
|
|
183
|
+
) {}
|
|
184
|
+
|
|
185
|
+
handleInput(data: string) {
|
|
186
|
+
if (matchesKey(data, Key.up) && this.selected > 0) this.selected--;
|
|
187
|
+
else if (matchesKey(data, Key.down) && this.selected < this.items.length - 1) this.selected++;
|
|
188
|
+
else if (matchesKey(data, Key.enter)) { this.done(this.items[this.selected]); return; }
|
|
189
|
+
else if (matchesKey(data, Key.escape)) { this.done(null); return; }
|
|
190
|
+
else return;
|
|
191
|
+
this.invalidate();
|
|
192
|
+
this.tui.requestRender();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
render(width: number): string[] {
|
|
196
|
+
if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
|
|
197
|
+
this.cachedLines = this.items.map((item, i) =>
|
|
198
|
+
truncateToWidth((i === this.selected ? "> " : " ") + item, width)
|
|
199
|
+
);
|
|
200
|
+
this.cachedWidth = width;
|
|
201
|
+
return this.cachedLines;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
invalidate() { this.cachedWidth = undefined; this.cachedLines = undefined; }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) =>
|
|
208
|
+
new MyComponent(tui, theme, ["A", "B", "C"], done)
|
|
209
|
+
);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Composing with built-in components:**
|
|
213
|
+
```typescript
|
|
214
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
215
|
+
const container = new Container();
|
|
216
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
217
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Title")), 1, 0));
|
|
218
|
+
|
|
219
|
+
const selectList = new SelectList(items, 10, {
|
|
220
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
221
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
222
|
+
description: (t) => theme.fg("muted", t),
|
|
223
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
224
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
225
|
+
});
|
|
226
|
+
selectList.onSelect = (item) => done(item.value);
|
|
227
|
+
selectList.onCancel = () => done(null);
|
|
228
|
+
container.addChild(selectList);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
render: (w) => container.render(w),
|
|
232
|
+
invalidate: () => container.invalidate(),
|
|
233
|
+
handleInput: (data) => { selectList.handleInput(data); tui.requestRender(); },
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
</custom_components>
|
|
238
|
+
|
|
239
|
+
<overlays>
|
|
240
|
+
Floating modals rendered on top of everything:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const result = await ctx.ui.custom<string | null>(
|
|
244
|
+
(tui, theme, _kb, done) => new MyDialog({ onClose: done }),
|
|
245
|
+
{
|
|
246
|
+
overlay: true,
|
|
247
|
+
overlayOptions: {
|
|
248
|
+
anchor: "center", // 9 positions (see below)
|
|
249
|
+
width: "50%", // number = columns, string = percentage
|
|
250
|
+
minWidth: 40,
|
|
251
|
+
maxHeight: "80%",
|
|
252
|
+
margin: 2, // All sides, or { top, right, bottom, left }
|
|
253
|
+
offsetX: 0, offsetY: 0, // Fine-tune position
|
|
254
|
+
visible: (w, h) => w >= 80, // Hide on narrow terminals
|
|
255
|
+
},
|
|
256
|
+
onHandle: (handle) => {
|
|
257
|
+
// handle.setHidden(true/false) — temporarily hide
|
|
258
|
+
// handle.hide() — permanently remove
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Anchor positions:**
|
|
265
|
+
```
|
|
266
|
+
top-left top-center top-right
|
|
267
|
+
left-center center right-center
|
|
268
|
+
bottom-left bottom-center bottom-right
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Stacked overlays:** Multiple overlays stack (newest on top). Closing one gives focus to the one below.
|
|
272
|
+
|
|
273
|
+
**⚠️ Overlay lifecycle:** Components are disposed when closed. Never reuse references — create fresh instances each time.
|
|
274
|
+
</overlays>
|
|
275
|
+
|
|
276
|
+
<custom_editor>
|
|
277
|
+
Replace the main input editor permanently:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { CustomEditor } from "@mariozechner/pi-coding-agent";
|
|
281
|
+
|
|
282
|
+
class VimEditor extends CustomEditor {
|
|
283
|
+
private mode: "normal" | "insert" = "insert";
|
|
284
|
+
|
|
285
|
+
handleInput(data: string): void {
|
|
286
|
+
if (matchesKey(data, "escape") && this.mode === "insert") {
|
|
287
|
+
this.mode = "normal"; return;
|
|
288
|
+
}
|
|
289
|
+
if (this.mode === "insert") { super.handleInput(data); return; }
|
|
290
|
+
switch (data) {
|
|
291
|
+
case "i": this.mode = "insert"; return;
|
|
292
|
+
case "h": super.handleInput("\x1b[D"); return; // Left
|
|
293
|
+
case "j": super.handleInput("\x1b[B"); return; // Down
|
|
294
|
+
case "k": super.handleInput("\x1b[A"); return; // Up
|
|
295
|
+
case "l": super.handleInput("\x1b[C"); return; // Right
|
|
296
|
+
}
|
|
297
|
+
if (data.length === 1 && data.charCodeAt(0) >= 32) return; // Block printable in normal
|
|
298
|
+
super.handleInput(data);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
ctx.ui.setEditorComponent((_tui, theme, keybindings) => new VimEditor(theme, keybindings));
|
|
303
|
+
ctx.ui.setEditorComponent(undefined); // Restore default
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Critical:** Extend `CustomEditor` (NOT `Editor`) to get app keybindings (escape to abort, ctrl+d, model switching).
|
|
307
|
+
</custom_editor>
|
|
308
|
+
|
|
309
|
+
<built_in_components>
|
|
310
|
+
**From `@mariozechner/pi-tui`:**
|
|
311
|
+
|
|
312
|
+
| Component | Constructor | Purpose |
|
|
313
|
+
|-----------|-------------|---------|
|
|
314
|
+
| `Text` | `new Text(content, paddingX, paddingY, bgFn?)` | Multi-line text with word wrap |
|
|
315
|
+
| `Box` | `new Box(paddingX, paddingY, bgFn)` | Container with padding+background, `.addChild()` |
|
|
316
|
+
| `Container` | `new Container()` | Vertical stack, `.addChild()`, `.removeChild()`, `.clear()` |
|
|
317
|
+
| `Spacer` | `new Spacer(lines)` | Empty vertical space |
|
|
318
|
+
| `Markdown` | `new Markdown(content, padX, padY, getMarkdownTheme())` | Rendered markdown with syntax highlighting |
|
|
319
|
+
| `Image` | `new Image(base64, mimeType, theme, opts?)` | Image rendering (Kitty, iTerm2) |
|
|
320
|
+
| `SelectList` | `new SelectList(items, maxVisible, themeOpts)` | Interactive selection with search and scrolling |
|
|
321
|
+
| `SettingsList` | `new SettingsList(items, maxVisible, theme, onChange, onClose, opts?)` | Toggle settings with left/right arrows |
|
|
322
|
+
| `Input` | `new Input()` | Text input field |
|
|
323
|
+
| `Editor` | `new Editor(tui, editorTheme)` | Multi-line editor with undo |
|
|
324
|
+
|
|
325
|
+
**SelectList usage:**
|
|
326
|
+
```typescript
|
|
327
|
+
const items: SelectItem[] = [
|
|
328
|
+
{ value: "opt1", label: "Option 1", description: "First option" },
|
|
329
|
+
{ value: "opt2", label: "Option 2" },
|
|
330
|
+
];
|
|
331
|
+
const selectList = new SelectList(items, 10, {
|
|
332
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
333
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
334
|
+
description: (t) => theme.fg("muted", t),
|
|
335
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
336
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
337
|
+
});
|
|
338
|
+
selectList.onSelect = (item) => { /* item.value */ };
|
|
339
|
+
selectList.onCancel = () => { /* escape pressed */ };
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**SettingsList usage:**
|
|
343
|
+
```typescript
|
|
344
|
+
const items: SettingItem[] = [
|
|
345
|
+
{ id: "verbose", label: "Verbose mode", currentValue: "off", values: ["on", "off"] },
|
|
346
|
+
{ id: "theme", label: "Theme", currentValue: "dark", values: ["dark", "light", "auto"] },
|
|
347
|
+
];
|
|
348
|
+
const settings = new SettingsList(items, 15, getSettingsListTheme(),
|
|
349
|
+
(id, newValue) => { /* setting changed */ },
|
|
350
|
+
() => { /* close requested */ },
|
|
351
|
+
{ enableSearch: true },
|
|
352
|
+
);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**From `@mariozechner/pi-coding-agent`:**
|
|
356
|
+
|
|
357
|
+
| Component | Constructor | Purpose |
|
|
358
|
+
|-----------|-------------|---------|
|
|
359
|
+
| `DynamicBorder` | `new DynamicBorder((s: string) => theme.fg("accent", s))` | Border line |
|
|
360
|
+
| `BorderedLoader` | — | Spinner with cancel support |
|
|
361
|
+
| `CustomEditor` | `new CustomEditor(theme, keybindings)` | Base class for custom editors |
|
|
362
|
+
</built_in_components>
|
|
363
|
+
|
|
364
|
+
<keyboard_input>
|
|
365
|
+
```typescript
|
|
366
|
+
import { matchesKey, Key } from "@mariozechner/pi-tui";
|
|
367
|
+
|
|
368
|
+
handleInput(data: string) {
|
|
369
|
+
// Basic keys
|
|
370
|
+
if (matchesKey(data, Key.up)) {}
|
|
371
|
+
if (matchesKey(data, Key.down)) {}
|
|
372
|
+
if (matchesKey(data, Key.enter)) {}
|
|
373
|
+
if (matchesKey(data, Key.escape)) {}
|
|
374
|
+
if (matchesKey(data, Key.tab)) {}
|
|
375
|
+
if (matchesKey(data, Key.space)) {}
|
|
376
|
+
if (matchesKey(data, Key.backspace)) {}
|
|
377
|
+
if (matchesKey(data, Key.home)) {}
|
|
378
|
+
if (matchesKey(data, Key.end)) {}
|
|
379
|
+
|
|
380
|
+
// With modifiers
|
|
381
|
+
if (matchesKey(data, Key.ctrl("c"))) {}
|
|
382
|
+
if (matchesKey(data, Key.shift("tab"))) {}
|
|
383
|
+
if (matchesKey(data, Key.alt("left"))) {}
|
|
384
|
+
if (matchesKey(data, Key.ctrlShift("p"))) {}
|
|
385
|
+
|
|
386
|
+
// String format also works: "enter", "ctrl+c", "shift+tab"
|
|
387
|
+
|
|
388
|
+
// Printable character detection
|
|
389
|
+
if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
|
390
|
+
// Letter, number, symbol
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**handleInput contract:**
|
|
396
|
+
1. Check for your keys
|
|
397
|
+
2. Update state
|
|
398
|
+
3. Call `this.invalidate()` if render output changes
|
|
399
|
+
4. Call `tui.requestRender()` to trigger re-render
|
|
400
|
+
</keyboard_input>
|
|
401
|
+
|
|
402
|
+
<line_width_rule>
|
|
403
|
+
**Cardinal rule: each line from render() must not exceed `width` visible characters.**
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
import { visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
407
|
+
|
|
408
|
+
visibleWidth("\x1b[32mHello\x1b[0m"); // Returns 5 (ignores ANSI codes)
|
|
409
|
+
truncateToWidth("Very long text here", 10); // "Very lo..."
|
|
410
|
+
truncateToWidth("Very long text here", 10, ""); // "Very long " (no ellipsis)
|
|
411
|
+
wrapTextWithAnsi("\x1b[32mLong green text\x1b[0m", 10); // Word wrap preserving ANSI
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
If lines exceed `width`, terminal wraps cause visual corruption.
|
|
415
|
+
</line_width_rule>
|
|
416
|
+
|
|
417
|
+
<performance_caching>
|
|
418
|
+
Always cache render output:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
class CachedComponent {
|
|
422
|
+
private cachedWidth?: number;
|
|
423
|
+
private cachedLines?: string[];
|
|
424
|
+
|
|
425
|
+
render(width: number): string[] {
|
|
426
|
+
if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
|
|
427
|
+
const lines = this.computeLines(width);
|
|
428
|
+
this.cachedWidth = width;
|
|
429
|
+
this.cachedLines = lines;
|
|
430
|
+
return lines;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
invalidate() { this.cachedWidth = undefined; this.cachedLines = undefined; }
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Update cycle:** State changes → `invalidate()` → `tui.requestRender()` → `render(width)` called
|
|
438
|
+
|
|
439
|
+
**Game loop pattern** (real-time updates):
|
|
440
|
+
```typescript
|
|
441
|
+
this.interval = setInterval(() => {
|
|
442
|
+
this.tick();
|
|
443
|
+
this.version++;
|
|
444
|
+
this.tui.requestRender();
|
|
445
|
+
}, 100); // 10 FPS
|
|
446
|
+
|
|
447
|
+
// Clean up in dispose()
|
|
448
|
+
clearInterval(this.interval);
|
|
449
|
+
```
|
|
450
|
+
</performance_caching>
|
|
451
|
+
|
|
452
|
+
<theme_colors>
|
|
453
|
+
Always use theme from callback params, never import directly.
|
|
454
|
+
|
|
455
|
+
**All foreground colors:**
|
|
456
|
+
|
|
457
|
+
| Category | Colors |
|
|
458
|
+
|----------|--------|
|
|
459
|
+
| General | `text`, `accent`, `muted`, `dim` |
|
|
460
|
+
| Status | `success`, `error`, `warning` |
|
|
461
|
+
| Borders | `border`, `borderAccent`, `borderMuted` |
|
|
462
|
+
| Messages | `userMessageText`, `customMessageText`, `customMessageLabel` |
|
|
463
|
+
| Tools | `toolTitle`, `toolOutput` |
|
|
464
|
+
| Diffs | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |
|
|
465
|
+
| Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |
|
|
466
|
+
| Syntax | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |
|
|
467
|
+
| Thinking | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh` |
|
|
468
|
+
|
|
469
|
+
**All background colors:** `selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`
|
|
470
|
+
|
|
471
|
+
**Syntax highlighting:**
|
|
472
|
+
```typescript
|
|
473
|
+
import { highlightCode, getLanguageFromPath } from "@mariozechner/pi-coding-agent";
|
|
474
|
+
const lang = getLanguageFromPath("/file.rs"); // "rust"
|
|
475
|
+
const highlighted = highlightCode(code, lang, theme);
|
|
476
|
+
```
|
|
477
|
+
</theme_colors>
|
|
478
|
+
|
|
479
|
+
<common_mistakes>
|
|
480
|
+
1. **Lines exceed width** → Visual corruption. Use `truncateToWidth()` on every line.
|
|
481
|
+
2. **Forgetting `tui.requestRender()`** → UI doesn't update. Call after invalidate().
|
|
482
|
+
3. **Importing theme directly** → Wrong colors after theme switch. Use theme from callback.
|
|
483
|
+
4. **Not typing DynamicBorder param** → `new DynamicBorder((s: string) => theme.fg("accent", s))`.
|
|
484
|
+
5. **Reusing disposed overlay components** → Create fresh instances each time.
|
|
485
|
+
6. **Styles bleeding across lines** → TUI resets per line. Reapply styles, or use `wrapTextWithAnsi()`.
|
|
486
|
+
7. **Not implementing invalidate()** → Theme changes don't take effect.
|
|
487
|
+
8. **Forgetting super.invalidate()** → `override invalidate() { super.invalidate(); /* cleanup */ }`
|
|
488
|
+
9. **Timer not cleaned up** → Call `clearInterval` before `done()`.
|
|
489
|
+
10. **Using ctx.ui in non-interactive mode** → Check `ctx.hasUI` first.
|
|
490
|
+
</common_mistakes>
|