@oh-my-pi/pi-coding-agent 1.337.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/CHANGELOG.md +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- package/src/utils/tools-manager.ts +274 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Armin says hi! A fun easter egg with animated XBM art.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Component, TUI } from "@oh-my-pi/pi-tui";
|
|
6
|
+
import { theme } from "../theme/theme.js";
|
|
7
|
+
|
|
8
|
+
// XBM image: 31x36 pixels, LSB first, 1=background, 0=foreground
|
|
9
|
+
const WIDTH = 31;
|
|
10
|
+
const HEIGHT = 36;
|
|
11
|
+
const BITS = [
|
|
12
|
+
0xff, 0xff, 0xff, 0x7f, 0xff, 0xf0, 0xff, 0x7f, 0xff, 0xed, 0xff, 0x7f, 0xff, 0xdb, 0xff, 0x7f, 0xff, 0xb7, 0xff,
|
|
13
|
+
0x7f, 0xff, 0x77, 0xfe, 0x7f, 0x3f, 0xf8, 0xfe, 0x7f, 0xdf, 0xff, 0xfe, 0x7f, 0xdf, 0x3f, 0xfc, 0x7f, 0x9f, 0xc3,
|
|
14
|
+
0xfb, 0x7f, 0x6f, 0xfc, 0xf4, 0x7f, 0xf7, 0x0f, 0xf7, 0x7f, 0xf7, 0xff, 0xf7, 0x7f, 0xf7, 0xff, 0xe3, 0x7f, 0xf7,
|
|
15
|
+
0x07, 0xe8, 0x7f, 0xef, 0xf8, 0x67, 0x70, 0x0f, 0xff, 0xbb, 0x6f, 0xf1, 0x00, 0xd0, 0x5b, 0xfd, 0x3f, 0xec, 0x53,
|
|
16
|
+
0xc1, 0xff, 0xef, 0x57, 0x9f, 0xfd, 0xee, 0x5f, 0x9f, 0xfc, 0xae, 0x5f, 0x1f, 0x78, 0xac, 0x5f, 0x3f, 0x00, 0x50,
|
|
17
|
+
0x6c, 0x7f, 0x00, 0xdc, 0x77, 0xff, 0xc0, 0x3f, 0x78, 0xff, 0x01, 0xf8, 0x7f, 0xff, 0x03, 0x9c, 0x78, 0xff, 0x07,
|
|
18
|
+
0x8c, 0x7c, 0xff, 0x0f, 0xce, 0x78, 0xff, 0xff, 0xcf, 0x7f, 0xff, 0xff, 0xcf, 0x78, 0xff, 0xff, 0xdf, 0x78, 0xff,
|
|
19
|
+
0xff, 0xdf, 0x7d, 0xff, 0xff, 0x3f, 0x7e, 0xff, 0xff, 0xff, 0x7f,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const BYTES_PER_ROW = Math.ceil(WIDTH / 8);
|
|
23
|
+
const DISPLAY_HEIGHT = Math.ceil(HEIGHT / 2); // Half-block rendering
|
|
24
|
+
|
|
25
|
+
type Effect = "typewriter" | "scanline" | "rain" | "fade" | "crt" | "glitch" | "dissolve";
|
|
26
|
+
|
|
27
|
+
const EFFECTS: Effect[] = ["typewriter", "scanline", "rain", "fade", "crt", "glitch", "dissolve"];
|
|
28
|
+
|
|
29
|
+
// Get pixel at (x, y): true = foreground, false = background
|
|
30
|
+
function getPixel(x: number, y: number): boolean {
|
|
31
|
+
if (y >= HEIGHT) return false;
|
|
32
|
+
const byteIndex = y * BYTES_PER_ROW + Math.floor(x / 8);
|
|
33
|
+
const bitIndex = x % 8;
|
|
34
|
+
return ((BITS[byteIndex] >> bitIndex) & 1) === 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Get the character for a cell (2 vertical pixels packed)
|
|
38
|
+
function getChar(x: number, row: number): string {
|
|
39
|
+
const upper = getPixel(x, row * 2);
|
|
40
|
+
const lower = getPixel(x, row * 2 + 1);
|
|
41
|
+
if (upper && lower) return "█";
|
|
42
|
+
if (upper) return "▀";
|
|
43
|
+
if (lower) return "▄";
|
|
44
|
+
return " ";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Build the final image grid
|
|
48
|
+
function buildFinalGrid(): string[][] {
|
|
49
|
+
const grid: string[][] = [];
|
|
50
|
+
for (let row = 0; row < DISPLAY_HEIGHT; row++) {
|
|
51
|
+
const line: string[] = [];
|
|
52
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
53
|
+
line.push(getChar(x, row));
|
|
54
|
+
}
|
|
55
|
+
grid.push(line);
|
|
56
|
+
}
|
|
57
|
+
return grid;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class ArminComponent implements Component {
|
|
61
|
+
private ui: TUI;
|
|
62
|
+
private interval: ReturnType<typeof setInterval> | null = null;
|
|
63
|
+
private effect: Effect;
|
|
64
|
+
private finalGrid: string[][];
|
|
65
|
+
private currentGrid: string[][];
|
|
66
|
+
private effectState: Record<string, unknown> = {};
|
|
67
|
+
private cachedLines: string[] = [];
|
|
68
|
+
private cachedWidth = 0;
|
|
69
|
+
private gridVersion = 0;
|
|
70
|
+
private cachedVersion = -1;
|
|
71
|
+
|
|
72
|
+
constructor(ui: TUI) {
|
|
73
|
+
this.ui = ui;
|
|
74
|
+
this.effect = EFFECTS[Math.floor(Math.random() * EFFECTS.length)];
|
|
75
|
+
this.finalGrid = buildFinalGrid();
|
|
76
|
+
this.currentGrid = this.createEmptyGrid();
|
|
77
|
+
|
|
78
|
+
this.initEffect();
|
|
79
|
+
this.startAnimation();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
invalidate(): void {
|
|
83
|
+
this.cachedWidth = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
render(width: number): string[] {
|
|
87
|
+
if (width === this.cachedWidth && this.cachedVersion === this.gridVersion) {
|
|
88
|
+
return this.cachedLines;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const padding = 1;
|
|
92
|
+
const availableWidth = width - padding;
|
|
93
|
+
|
|
94
|
+
this.cachedLines = this.currentGrid.map((row) => {
|
|
95
|
+
// Clip row to available width before applying color
|
|
96
|
+
const clipped = row.slice(0, availableWidth).join("");
|
|
97
|
+
const padRight = Math.max(0, width - padding - clipped.length);
|
|
98
|
+
return ` ${theme.fg("accent", clipped)}${" ".repeat(padRight)}`;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Add "ARMIN SAYS HI" at the end
|
|
102
|
+
const message = "ARMIN SAYS HI";
|
|
103
|
+
const msgPadRight = Math.max(0, width - padding - message.length);
|
|
104
|
+
this.cachedLines.push(` ${theme.fg("accent", message)}${" ".repeat(msgPadRight)}`);
|
|
105
|
+
|
|
106
|
+
this.cachedWidth = width;
|
|
107
|
+
this.cachedVersion = this.gridVersion;
|
|
108
|
+
|
|
109
|
+
return this.cachedLines;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private createEmptyGrid(): string[][] {
|
|
113
|
+
return Array.from({ length: DISPLAY_HEIGHT }, () => Array(WIDTH).fill(" "));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private initEffect(): void {
|
|
117
|
+
switch (this.effect) {
|
|
118
|
+
case "typewriter":
|
|
119
|
+
this.effectState = { pos: 0 };
|
|
120
|
+
break;
|
|
121
|
+
case "scanline":
|
|
122
|
+
this.effectState = { row: 0 };
|
|
123
|
+
break;
|
|
124
|
+
case "rain":
|
|
125
|
+
// Track falling position for each column
|
|
126
|
+
this.effectState = {
|
|
127
|
+
drops: Array.from({ length: WIDTH }, () => ({
|
|
128
|
+
y: -Math.floor(Math.random() * DISPLAY_HEIGHT * 2),
|
|
129
|
+
settled: 0,
|
|
130
|
+
})),
|
|
131
|
+
};
|
|
132
|
+
break;
|
|
133
|
+
case "fade": {
|
|
134
|
+
// Shuffle all pixel positions
|
|
135
|
+
const positions: [number, number][] = [];
|
|
136
|
+
for (let row = 0; row < DISPLAY_HEIGHT; row++) {
|
|
137
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
138
|
+
positions.push([row, x]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Fisher-Yates shuffle
|
|
142
|
+
for (let i = positions.length - 1; i > 0; i--) {
|
|
143
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
144
|
+
[positions[i], positions[j]] = [positions[j], positions[i]];
|
|
145
|
+
}
|
|
146
|
+
this.effectState = { positions, idx: 0 };
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "crt":
|
|
150
|
+
this.effectState = { expansion: 0 };
|
|
151
|
+
break;
|
|
152
|
+
case "glitch":
|
|
153
|
+
this.effectState = { phase: 0, glitchFrames: 8 };
|
|
154
|
+
break;
|
|
155
|
+
case "dissolve": {
|
|
156
|
+
// Start with random noise
|
|
157
|
+
this.currentGrid = Array.from({ length: DISPLAY_HEIGHT }, () =>
|
|
158
|
+
Array.from({ length: WIDTH }, () => {
|
|
159
|
+
const chars = [" ", "░", "▒", "▓", "█", "▀", "▄"];
|
|
160
|
+
return chars[Math.floor(Math.random() * chars.length)];
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
// Shuffle positions for gradual resolve
|
|
164
|
+
const dissolvePositions: [number, number][] = [];
|
|
165
|
+
for (let row = 0; row < DISPLAY_HEIGHT; row++) {
|
|
166
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
167
|
+
dissolvePositions.push([row, x]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (let i = dissolvePositions.length - 1; i > 0; i--) {
|
|
171
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
172
|
+
[dissolvePositions[i], dissolvePositions[j]] = [dissolvePositions[j], dissolvePositions[i]];
|
|
173
|
+
}
|
|
174
|
+
this.effectState = { positions: dissolvePositions, idx: 0 };
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private startAnimation(): void {
|
|
181
|
+
const fps = this.effect === "glitch" ? 60 : 30;
|
|
182
|
+
this.interval = setInterval(() => {
|
|
183
|
+
const done = this.tickEffect();
|
|
184
|
+
this.updateDisplay();
|
|
185
|
+
this.ui.requestRender();
|
|
186
|
+
if (done) {
|
|
187
|
+
this.stopAnimation();
|
|
188
|
+
}
|
|
189
|
+
}, 1000 / fps);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private stopAnimation(): void {
|
|
193
|
+
if (this.interval) {
|
|
194
|
+
clearInterval(this.interval);
|
|
195
|
+
this.interval = null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private tickEffect(): boolean {
|
|
200
|
+
switch (this.effect) {
|
|
201
|
+
case "typewriter":
|
|
202
|
+
return this.tickTypewriter();
|
|
203
|
+
case "scanline":
|
|
204
|
+
return this.tickScanline();
|
|
205
|
+
case "rain":
|
|
206
|
+
return this.tickRain();
|
|
207
|
+
case "fade":
|
|
208
|
+
return this.tickFade();
|
|
209
|
+
case "crt":
|
|
210
|
+
return this.tickCrt();
|
|
211
|
+
case "glitch":
|
|
212
|
+
return this.tickGlitch();
|
|
213
|
+
case "dissolve":
|
|
214
|
+
return this.tickDissolve();
|
|
215
|
+
default:
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private tickTypewriter(): boolean {
|
|
221
|
+
const state = this.effectState as { pos: number };
|
|
222
|
+
const pixelsPerFrame = 3;
|
|
223
|
+
|
|
224
|
+
for (let i = 0; i < pixelsPerFrame; i++) {
|
|
225
|
+
const row = Math.floor(state.pos / WIDTH);
|
|
226
|
+
const x = state.pos % WIDTH;
|
|
227
|
+
if (row >= DISPLAY_HEIGHT) return true;
|
|
228
|
+
this.currentGrid[row][x] = this.finalGrid[row][x];
|
|
229
|
+
state.pos++;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private tickScanline(): boolean {
|
|
235
|
+
const state = this.effectState as { row: number };
|
|
236
|
+
if (state.row >= DISPLAY_HEIGHT) return true;
|
|
237
|
+
|
|
238
|
+
// Copy row
|
|
239
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
240
|
+
this.currentGrid[state.row][x] = this.finalGrid[state.row][x];
|
|
241
|
+
}
|
|
242
|
+
state.row++;
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private tickRain(): boolean {
|
|
247
|
+
const state = this.effectState as {
|
|
248
|
+
drops: { y: number; settled: number }[];
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
let allSettled = true;
|
|
252
|
+
this.currentGrid = this.createEmptyGrid();
|
|
253
|
+
|
|
254
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
255
|
+
const drop = state.drops[x];
|
|
256
|
+
|
|
257
|
+
// Draw settled pixels
|
|
258
|
+
for (let row = DISPLAY_HEIGHT - 1; row >= DISPLAY_HEIGHT - drop.settled; row--) {
|
|
259
|
+
if (row >= 0) {
|
|
260
|
+
this.currentGrid[row][x] = this.finalGrid[row][x];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check if this column is done
|
|
265
|
+
if (drop.settled >= DISPLAY_HEIGHT) continue;
|
|
266
|
+
|
|
267
|
+
allSettled = false;
|
|
268
|
+
|
|
269
|
+
// Find the target row for this column (lowest non-space pixel)
|
|
270
|
+
let targetRow = -1;
|
|
271
|
+
for (let row = DISPLAY_HEIGHT - 1 - drop.settled; row >= 0; row--) {
|
|
272
|
+
if (this.finalGrid[row][x] !== " ") {
|
|
273
|
+
targetRow = row;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Move drop down
|
|
279
|
+
drop.y++;
|
|
280
|
+
|
|
281
|
+
// Draw falling drop
|
|
282
|
+
if (drop.y >= 0 && drop.y < DISPLAY_HEIGHT) {
|
|
283
|
+
if (targetRow >= 0 && drop.y >= targetRow) {
|
|
284
|
+
// Settle
|
|
285
|
+
drop.settled = DISPLAY_HEIGHT - targetRow;
|
|
286
|
+
drop.y = -Math.floor(Math.random() * 5) - 1;
|
|
287
|
+
} else {
|
|
288
|
+
// Still falling
|
|
289
|
+
this.currentGrid[drop.y][x] = "▓";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return allSettled;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private tickFade(): boolean {
|
|
298
|
+
const state = this.effectState as { positions: [number, number][]; idx: number };
|
|
299
|
+
const pixelsPerFrame = 15;
|
|
300
|
+
|
|
301
|
+
for (let i = 0; i < pixelsPerFrame; i++) {
|
|
302
|
+
if (state.idx >= state.positions.length) return true;
|
|
303
|
+
const [row, x] = state.positions[state.idx];
|
|
304
|
+
this.currentGrid[row][x] = this.finalGrid[row][x];
|
|
305
|
+
state.idx++;
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private tickCrt(): boolean {
|
|
311
|
+
const state = this.effectState as { expansion: number };
|
|
312
|
+
const midRow = Math.floor(DISPLAY_HEIGHT / 2);
|
|
313
|
+
|
|
314
|
+
this.currentGrid = this.createEmptyGrid();
|
|
315
|
+
|
|
316
|
+
// Draw from middle expanding outward
|
|
317
|
+
const top = midRow - state.expansion;
|
|
318
|
+
const bottom = midRow + state.expansion;
|
|
319
|
+
|
|
320
|
+
for (let row = Math.max(0, top); row <= Math.min(DISPLAY_HEIGHT - 1, bottom); row++) {
|
|
321
|
+
for (let x = 0; x < WIDTH; x++) {
|
|
322
|
+
this.currentGrid[row][x] = this.finalGrid[row][x];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
state.expansion++;
|
|
327
|
+
return state.expansion > DISPLAY_HEIGHT;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private tickGlitch(): boolean {
|
|
331
|
+
const state = this.effectState as { phase: number; glitchFrames: number };
|
|
332
|
+
|
|
333
|
+
if (state.phase < state.glitchFrames) {
|
|
334
|
+
// Glitch phase: show corrupted version
|
|
335
|
+
this.currentGrid = this.finalGrid.map((row) => {
|
|
336
|
+
const offset = Math.floor(Math.random() * 7) - 3;
|
|
337
|
+
const glitchRow = [...row];
|
|
338
|
+
|
|
339
|
+
// Random horizontal offset
|
|
340
|
+
if (Math.random() < 0.3) {
|
|
341
|
+
const shifted = glitchRow.slice(offset).concat(glitchRow.slice(0, offset));
|
|
342
|
+
return shifted.slice(0, WIDTH);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Random vertical swap
|
|
346
|
+
if (Math.random() < 0.2) {
|
|
347
|
+
const swapRow = Math.floor(Math.random() * DISPLAY_HEIGHT);
|
|
348
|
+
return [...this.finalGrid[swapRow]];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return glitchRow;
|
|
352
|
+
});
|
|
353
|
+
state.phase++;
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Final frame: show clean image
|
|
358
|
+
this.currentGrid = this.finalGrid.map((row) => [...row]);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private tickDissolve(): boolean {
|
|
363
|
+
const state = this.effectState as { positions: [number, number][]; idx: number };
|
|
364
|
+
const pixelsPerFrame = 20;
|
|
365
|
+
|
|
366
|
+
for (let i = 0; i < pixelsPerFrame; i++) {
|
|
367
|
+
if (state.idx >= state.positions.length) return true;
|
|
368
|
+
const [row, x] = state.positions[state.idx];
|
|
369
|
+
this.currentGrid[row][x] = this.finalGrid[row][x];
|
|
370
|
+
state.idx++;
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private updateDisplay(): void {
|
|
376
|
+
this.gridVersion++;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
dispose(): void {
|
|
380
|
+
this.stopAnimation();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
3
|
+
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Component that renders a complete assistant message
|
|
7
|
+
*/
|
|
8
|
+
export class AssistantMessageComponent extends Container {
|
|
9
|
+
private contentContainer: Container;
|
|
10
|
+
private hideThinkingBlock: boolean;
|
|
11
|
+
|
|
12
|
+
constructor(message?: AssistantMessage, hideThinkingBlock = false) {
|
|
13
|
+
super();
|
|
14
|
+
|
|
15
|
+
this.hideThinkingBlock = hideThinkingBlock;
|
|
16
|
+
|
|
17
|
+
// Container for text/thinking content
|
|
18
|
+
this.contentContainer = new Container();
|
|
19
|
+
this.addChild(this.contentContainer);
|
|
20
|
+
|
|
21
|
+
if (message) {
|
|
22
|
+
this.updateContent(message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setHideThinkingBlock(hide: boolean): void {
|
|
27
|
+
this.hideThinkingBlock = hide;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
updateContent(message: AssistantMessage): void {
|
|
31
|
+
// Clear content container
|
|
32
|
+
this.contentContainer.clear();
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
message.content.length > 0 &&
|
|
36
|
+
message.content.some(
|
|
37
|
+
(c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()),
|
|
38
|
+
)
|
|
39
|
+
) {
|
|
40
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Render content in order
|
|
44
|
+
for (let i = 0; i < message.content.length; i++) {
|
|
45
|
+
const content = message.content[i];
|
|
46
|
+
if (content.type === "text" && content.text.trim()) {
|
|
47
|
+
// Assistant text messages with no background - trim the text
|
|
48
|
+
// Set paddingY=0 to avoid extra spacing before tool executions
|
|
49
|
+
this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, getMarkdownTheme()));
|
|
50
|
+
} else if (content.type === "thinking" && content.thinking.trim()) {
|
|
51
|
+
// Check if there's text content after this thinking block
|
|
52
|
+
const hasTextAfter = message.content.slice(i + 1).some((c) => c.type === "text" && c.text.trim());
|
|
53
|
+
|
|
54
|
+
if (this.hideThinkingBlock) {
|
|
55
|
+
// Show static "Thinking..." label when hidden
|
|
56
|
+
this.contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", "Thinking...")), 1, 0));
|
|
57
|
+
if (hasTextAfter) {
|
|
58
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Thinking traces in thinkingText color, italic
|
|
62
|
+
this.contentContainer.addChild(
|
|
63
|
+
new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), {
|
|
64
|
+
color: (text: string) => theme.fg("thinkingText", text),
|
|
65
|
+
italic: true,
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if aborted - show after partial content
|
|
74
|
+
// But only if there are no tool calls (tool execution components will show the error)
|
|
75
|
+
const hasToolCalls = message.content.some((c) => c.type === "toolCall");
|
|
76
|
+
if (!hasToolCalls) {
|
|
77
|
+
if (message.stopReason === "aborted") {
|
|
78
|
+
this.contentContainer.addChild(new Text(theme.fg("error", "\nAborted"), 1, 0));
|
|
79
|
+
} else if (message.stopReason === "error") {
|
|
80
|
+
const errorMsg = message.errorMessage || "Unknown error";
|
|
81
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
82
|
+
this.contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component for displaying bash command execution with streaming output.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Container, Loader, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
6
|
+
import stripAnsi from "strip-ansi";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_MAX_BYTES,
|
|
9
|
+
DEFAULT_MAX_LINES,
|
|
10
|
+
type TruncationResult,
|
|
11
|
+
truncateTail,
|
|
12
|
+
} from "../../../core/tools/truncate.js";
|
|
13
|
+
import { theme } from "../theme/theme.js";
|
|
14
|
+
import { DynamicBorder } from "./dynamic-border.js";
|
|
15
|
+
import { truncateToVisualLines } from "./visual-truncate.js";
|
|
16
|
+
|
|
17
|
+
// Preview line limit when not expanded (matches tool execution behavior)
|
|
18
|
+
const PREVIEW_LINES = 20;
|
|
19
|
+
|
|
20
|
+
export class BashExecutionComponent extends Container {
|
|
21
|
+
private command: string;
|
|
22
|
+
private outputLines: string[] = [];
|
|
23
|
+
private status: "running" | "complete" | "cancelled" | "error" = "running";
|
|
24
|
+
private exitCode: number | undefined = undefined;
|
|
25
|
+
private loader: Loader;
|
|
26
|
+
private truncationResult?: TruncationResult;
|
|
27
|
+
private fullOutputPath?: string;
|
|
28
|
+
private expanded = false;
|
|
29
|
+
private contentContainer: Container;
|
|
30
|
+
private ui: TUI;
|
|
31
|
+
|
|
32
|
+
constructor(command: string, ui: TUI) {
|
|
33
|
+
super();
|
|
34
|
+
this.command = command;
|
|
35
|
+
this.ui = ui;
|
|
36
|
+
|
|
37
|
+
const borderColor = (str: string) => theme.fg("bashMode", str);
|
|
38
|
+
|
|
39
|
+
// Add spacer
|
|
40
|
+
this.addChild(new Spacer(1));
|
|
41
|
+
|
|
42
|
+
// Top border
|
|
43
|
+
this.addChild(new DynamicBorder(borderColor));
|
|
44
|
+
|
|
45
|
+
// Content container (holds dynamic content between borders)
|
|
46
|
+
this.contentContainer = new Container();
|
|
47
|
+
this.addChild(this.contentContainer);
|
|
48
|
+
|
|
49
|
+
// Command header
|
|
50
|
+
const header = new Text(theme.fg("bashMode", theme.bold(`$ ${command}`)), 1, 0);
|
|
51
|
+
this.contentContainer.addChild(header);
|
|
52
|
+
|
|
53
|
+
// Loader
|
|
54
|
+
this.loader = new Loader(
|
|
55
|
+
ui,
|
|
56
|
+
(spinner) => theme.fg("bashMode", spinner),
|
|
57
|
+
(text) => theme.fg("muted", text),
|
|
58
|
+
"Running... (esc to cancel)",
|
|
59
|
+
);
|
|
60
|
+
this.contentContainer.addChild(this.loader);
|
|
61
|
+
|
|
62
|
+
// Bottom border
|
|
63
|
+
this.addChild(new DynamicBorder(borderColor));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set whether the output is expanded (shows full output) or collapsed (preview only).
|
|
68
|
+
*/
|
|
69
|
+
setExpanded(expanded: boolean): void {
|
|
70
|
+
this.expanded = expanded;
|
|
71
|
+
this.updateDisplay();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
appendOutput(chunk: string): void {
|
|
75
|
+
// Strip ANSI codes and normalize line endings
|
|
76
|
+
// Note: binary data is already sanitized in tui-renderer.ts executeBashCommand
|
|
77
|
+
const clean = stripAnsi(chunk).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
78
|
+
|
|
79
|
+
// Append to output lines
|
|
80
|
+
const newLines = clean.split("\n");
|
|
81
|
+
if (this.outputLines.length > 0 && newLines.length > 0) {
|
|
82
|
+
// Append first chunk to last line (incomplete line continuation)
|
|
83
|
+
this.outputLines[this.outputLines.length - 1] += newLines[0];
|
|
84
|
+
this.outputLines.push(...newLines.slice(1));
|
|
85
|
+
} else {
|
|
86
|
+
this.outputLines.push(...newLines);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.updateDisplay();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setComplete(
|
|
93
|
+
exitCode: number | undefined,
|
|
94
|
+
cancelled: boolean,
|
|
95
|
+
truncationResult?: TruncationResult,
|
|
96
|
+
fullOutputPath?: string,
|
|
97
|
+
): void {
|
|
98
|
+
this.exitCode = exitCode;
|
|
99
|
+
this.status = cancelled
|
|
100
|
+
? "cancelled"
|
|
101
|
+
: exitCode !== 0 && exitCode !== undefined && exitCode !== null
|
|
102
|
+
? "error"
|
|
103
|
+
: "complete";
|
|
104
|
+
this.truncationResult = truncationResult;
|
|
105
|
+
this.fullOutputPath = fullOutputPath;
|
|
106
|
+
|
|
107
|
+
// Stop loader
|
|
108
|
+
this.loader.stop();
|
|
109
|
+
|
|
110
|
+
this.updateDisplay();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private updateDisplay(): void {
|
|
114
|
+
// Apply truncation for LLM context limits (same limits as bash tool)
|
|
115
|
+
const fullOutput = this.outputLines.join("\n");
|
|
116
|
+
const contextTruncation = truncateTail(fullOutput, {
|
|
117
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
118
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Get the lines to potentially display (after context truncation)
|
|
122
|
+
const availableLines = contextTruncation.content ? contextTruncation.content.split("\n") : [];
|
|
123
|
+
|
|
124
|
+
// Apply preview truncation based on expanded state
|
|
125
|
+
const previewLogicalLines = availableLines.slice(-PREVIEW_LINES);
|
|
126
|
+
const hiddenLineCount = availableLines.length - previewLogicalLines.length;
|
|
127
|
+
|
|
128
|
+
// Rebuild content container
|
|
129
|
+
this.contentContainer.clear();
|
|
130
|
+
|
|
131
|
+
// Command header
|
|
132
|
+
const header = new Text(theme.fg("bashMode", theme.bold(`$ ${this.command}`)), 1, 0);
|
|
133
|
+
this.contentContainer.addChild(header);
|
|
134
|
+
|
|
135
|
+
// Output
|
|
136
|
+
if (availableLines.length > 0) {
|
|
137
|
+
if (this.expanded) {
|
|
138
|
+
// Show all lines
|
|
139
|
+
const displayText = availableLines.map((line) => theme.fg("muted", line)).join("\n");
|
|
140
|
+
this.contentContainer.addChild(new Text(`\n${displayText}`, 1, 0));
|
|
141
|
+
} else {
|
|
142
|
+
// Use shared visual truncation utility
|
|
143
|
+
const styledOutput = previewLogicalLines.map((line) => theme.fg("muted", line)).join("\n");
|
|
144
|
+
const { visualLines } = truncateToVisualLines(
|
|
145
|
+
`\n${styledOutput}`,
|
|
146
|
+
PREVIEW_LINES,
|
|
147
|
+
this.ui.terminal.columns,
|
|
148
|
+
1, // padding
|
|
149
|
+
);
|
|
150
|
+
this.contentContainer.addChild({ render: () => visualLines, invalidate: () => {} });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Loader or status
|
|
155
|
+
if (this.status === "running") {
|
|
156
|
+
this.contentContainer.addChild(this.loader);
|
|
157
|
+
} else {
|
|
158
|
+
const statusParts: string[] = [];
|
|
159
|
+
|
|
160
|
+
// Show how many lines are hidden (collapsed preview)
|
|
161
|
+
if (hiddenLineCount > 0) {
|
|
162
|
+
statusParts.push(theme.fg("dim", `... ${hiddenLineCount} more lines (ctrl+o to expand)`));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (this.status === "cancelled") {
|
|
166
|
+
statusParts.push(theme.fg("warning", "(cancelled)"));
|
|
167
|
+
} else if (this.status === "error") {
|
|
168
|
+
statusParts.push(theme.fg("error", `(exit ${this.exitCode})`));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Add truncation warning (context truncation, not preview truncation)
|
|
172
|
+
const wasTruncated = this.truncationResult?.truncated || contextTruncation.truncated;
|
|
173
|
+
if (wasTruncated && this.fullOutputPath) {
|
|
174
|
+
statusParts.push(theme.fg("warning", `Output truncated. Full output: ${this.fullOutputPath}`));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (statusParts.length > 0) {
|
|
178
|
+
this.contentContainer.addChild(new Text(`\n${statusParts.join("\n")}`, 1, 0));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get the raw output for creating BashExecutionMessage.
|
|
185
|
+
*/
|
|
186
|
+
getOutput(): string {
|
|
187
|
+
return this.outputLines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get the command that was executed.
|
|
192
|
+
*/
|
|
193
|
+
getCommand(): string {
|
|
194
|
+
return this.command;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CancellableLoader, Container, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import type { Theme } from "../theme/theme.js";
|
|
3
|
+
import { DynamicBorder } from "./dynamic-border.js";
|
|
4
|
+
|
|
5
|
+
/** Loader wrapped with borders for hook UI */
|
|
6
|
+
export class BorderedLoader extends Container {
|
|
7
|
+
private loader: CancellableLoader;
|
|
8
|
+
|
|
9
|
+
constructor(tui: TUI, theme: Theme, message: string) {
|
|
10
|
+
super();
|
|
11
|
+
const borderColor = (s: string) => theme.fg("border", s);
|
|
12
|
+
this.addChild(new DynamicBorder(borderColor));
|
|
13
|
+
this.loader = new CancellableLoader(
|
|
14
|
+
tui,
|
|
15
|
+
(s) => theme.fg("accent", s),
|
|
16
|
+
(s) => theme.fg("muted", s),
|
|
17
|
+
message,
|
|
18
|
+
);
|
|
19
|
+
this.addChild(this.loader);
|
|
20
|
+
this.addChild(new Spacer(1));
|
|
21
|
+
this.addChild(new Text(theme.fg("muted", "esc cancel"), 1, 0));
|
|
22
|
+
this.addChild(new Spacer(1));
|
|
23
|
+
this.addChild(new DynamicBorder(borderColor));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get signal(): AbortSignal {
|
|
27
|
+
return this.loader.signal;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set onAbort(fn: (() => void) | undefined) {
|
|
31
|
+
this.loader.onAbort = fn;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
handleInput(data: string): void {
|
|
35
|
+
this.loader.handleInput(data);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
dispose(): void {
|
|
39
|
+
this.loader.dispose();
|
|
40
|
+
}
|
|
41
|
+
}
|