pi-chassis 0.0.0-stage → 0.7.1
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 +118 -0
- package/LICENSE +21 -0
- package/README.md +51 -2
- package/THIRD_PARTY_NOTICES +30 -0
- package/dist/chassis/alert.d.ts +50 -0
- package/dist/chassis/alert.js +107 -0
- package/dist/chassis/api.d.ts +411 -0
- package/dist/chassis/api.js +676 -0
- package/dist/chassis/bash-output.d.ts +89 -0
- package/dist/chassis/bash-output.js +157 -0
- package/dist/chassis/brand.d.ts +99 -0
- package/dist/chassis/brand.js +79 -0
- package/dist/chassis/client.d.ts +87 -0
- package/dist/chassis/client.js +271 -0
- package/dist/chassis/commands.d.ts +60 -0
- package/dist/chassis/commands.js +211 -0
- package/dist/chassis/editor.d.ts +70 -0
- package/dist/chassis/editor.js +213 -0
- package/dist/chassis/footer.d.ts +14 -0
- package/dist/chassis/footer.js +136 -0
- package/dist/chassis/header.d.ts +12 -0
- package/dist/chassis/header.js +38 -0
- package/dist/chassis/home.d.ts +48 -0
- package/dist/chassis/home.js +184 -0
- package/dist/chassis/icons.d.ts +203 -0
- package/dist/chassis/icons.js +190 -0
- package/dist/chassis/index.d.ts +21 -0
- package/dist/chassis/index.js +582 -0
- package/dist/chassis/layout.d.ts +40 -0
- package/dist/chassis/layout.js +264 -0
- package/dist/chassis/message-modes.d.ts +21 -0
- package/dist/chassis/message-modes.js +49 -0
- package/dist/chassis/messages.d.ts +75 -0
- package/dist/chassis/messages.js +392 -0
- package/dist/chassis/modes.d.ts +21 -0
- package/dist/chassis/modes.js +99 -0
- package/dist/chassis/palette.d.ts +84 -0
- package/dist/chassis/palette.js +235 -0
- package/dist/chassis/peek.d.ts +61 -0
- package/dist/chassis/peek.js +156 -0
- package/dist/chassis/prompt-row.d.ts +42 -0
- package/dist/chassis/prompt-row.js +68 -0
- package/dist/chassis/provider-view.d.ts +34 -0
- package/dist/chassis/provider-view.js +107 -0
- package/dist/chassis/provider.d.ts +69 -0
- package/dist/chassis/provider.js +73 -0
- package/dist/chassis/readonly-command.d.ts +19 -0
- package/dist/chassis/readonly-command.js +454 -0
- package/dist/chassis/sanitize.d.ts +42 -0
- package/dist/chassis/sanitize.js +103 -0
- package/dist/chassis/session-info.d.ts +39 -0
- package/dist/chassis/session-info.js +107 -0
- package/dist/chassis/sidebar.d.ts +48 -0
- package/dist/chassis/sidebar.js +273 -0
- package/dist/chassis/state.d.ts +38 -0
- package/dist/chassis/state.js +19 -0
- package/dist/chassis/telemetry.d.ts +60 -0
- package/dist/chassis/telemetry.js +191 -0
- package/dist/chassis/tools.d.ts +112 -0
- package/dist/chassis/tools.js +588 -0
- package/dist/chassis/version.d.ts +2 -0
- package/dist/chassis/version.js +20 -0
- package/dist/diff/git-diff.d.ts +71 -0
- package/dist/diff/git-diff.js +325 -0
- package/dist/diff/host.d.ts +34 -0
- package/dist/diff/host.js +50 -0
- package/dist/diff/index.d.ts +13 -0
- package/dist/diff/index.js +86 -0
- package/dist/diff/render.d.ts +67 -0
- package/dist/diff/render.js +196 -0
- package/dist/diff/types.d.ts +60 -0
- package/dist/diff/types.js +4 -0
- package/dist/diff/view.d.ts +118 -0
- package/dist/diff/view.js +528 -0
- package/dist/git/flows.d.ts +92 -0
- package/dist/git/flows.js +415 -0
- package/dist/git/forge.d.ts +26 -0
- package/dist/git/forge.js +41 -0
- package/dist/git/index.d.ts +20 -0
- package/dist/git/index.js +121 -0
- package/dist/git/repo.d.ts +64 -0
- package/dist/git/repo.js +186 -0
- package/dist/git/run.d.ts +38 -0
- package/dist/git/run.js +127 -0
- package/dist/git/settings.d.ts +8 -0
- package/dist/git/settings.js +38 -0
- package/dist/git/text.d.ts +51 -0
- package/dist/git/text.js +109 -0
- package/dist/widgets/container/detect.d.ts +23 -0
- package/dist/widgets/container/detect.js +67 -0
- package/dist/widgets/container/index.d.ts +9 -0
- package/dist/widgets/container/index.js +26 -0
- package/dist/widgets/mcp/index.d.ts +13 -0
- package/dist/widgets/mcp/index.js +50 -0
- package/dist/widgets/mcp/status.d.ts +50 -0
- package/dist/widgets/mcp/status.js +70 -0
- package/dist/widgets/session-todos/index.d.ts +12 -0
- package/dist/widgets/session-todos/index.js +52 -0
- package/dist/widgets/subagents/index.d.ts +16 -0
- package/dist/widgets/subagents/index.js +173 -0
- package/dist/widgets/subagents/status.d.ts +61 -0
- package/dist/widgets/subagents/status.js +198 -0
- package/dist/widgets/todo/index.d.ts +13 -0
- package/dist/widgets/todo/index.js +65 -0
- package/dist/widgets/todo/parse.d.ts +29 -0
- package/dist/widgets/todo/parse.js +122 -0
- package/docs/branding.md +25 -0
- package/docs/development.md +22 -0
- package/docs/diff-api.md +18 -0
- package/docs/git.md +23 -0
- package/docs/internals.md +13 -0
- package/docs/provider-status.md +23 -0
- package/docs/status-widgets.md +157 -0
- package/docs/tool-rows.md +50 -0
- package/docs/usage.md +62 -0
- package/docs/widgets.md +113 -0
- package/extensions/chassis/alert.ts +127 -0
- package/extensions/chassis/api.ts +967 -0
- package/extensions/chassis/bash-output.ts +186 -0
- package/extensions/chassis/brand.ts +151 -0
- package/extensions/chassis/client.ts +284 -0
- package/extensions/chassis/commands.ts +244 -0
- package/extensions/chassis/editor.ts +253 -0
- package/extensions/chassis/footer.ts +131 -0
- package/extensions/chassis/header.ts +43 -0
- package/extensions/chassis/home.ts +186 -0
- package/extensions/chassis/icons.ts +220 -0
- package/extensions/chassis/index.ts +605 -0
- package/extensions/chassis/layout.ts +298 -0
- package/extensions/chassis/message-modes.ts +48 -0
- package/extensions/chassis/messages.ts +403 -0
- package/extensions/chassis/modes.ts +108 -0
- package/extensions/chassis/palette.ts +258 -0
- package/extensions/chassis/peek.ts +167 -0
- package/extensions/chassis/prompt-row.ts +86 -0
- package/extensions/chassis/provider-view.ts +118 -0
- package/extensions/chassis/provider.ts +115 -0
- package/extensions/chassis/readonly-command.ts +440 -0
- package/extensions/chassis/sanitize.ts +108 -0
- package/extensions/chassis/session-info.ts +127 -0
- package/extensions/chassis/sidebar.ts +285 -0
- package/extensions/chassis/state.ts +50 -0
- package/extensions/chassis/telemetry.ts +222 -0
- package/extensions/chassis/tools.ts +618 -0
- package/extensions/chassis/version.ts +20 -0
- package/extensions/diff/git-diff.ts +365 -0
- package/extensions/diff/host.ts +58 -0
- package/extensions/diff/index.ts +101 -0
- package/extensions/diff/render.ts +217 -0
- package/extensions/diff/types.ts +67 -0
- package/extensions/diff/view.ts +523 -0
- package/extensions/git/flows.ts +485 -0
- package/extensions/git/forge.ts +54 -0
- package/extensions/git/index.ts +117 -0
- package/extensions/git/repo.ts +200 -0
- package/extensions/git/run.ts +143 -0
- package/extensions/git/settings.ts +38 -0
- package/extensions/git/text.ts +133 -0
- package/extensions/widgets/container/detect.ts +81 -0
- package/extensions/widgets/container/index.ts +29 -0
- package/extensions/widgets/mcp/index.ts +53 -0
- package/extensions/widgets/mcp/status.ts +94 -0
- package/extensions/widgets/session-todos/index.ts +55 -0
- package/extensions/widgets/subagents/index.ts +170 -0
- package/extensions/widgets/subagents/status.ts +237 -0
- package/extensions/widgets/todo/index.ts +66 -0
- package/extensions/widgets/todo/parse.ts +119 -0
- package/package.json +91 -4
- package/scripts/link-pi.mjs +76 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash tool output for the one-line tool rows: separate what pi appended to the text (exit status,
|
|
3
|
+
* truncation notice) from what the command printed, clean the printed lines for the screen, and
|
|
4
|
+
* fold them. Pure functions with no pi imports, so tests run standalone.
|
|
5
|
+
*
|
|
6
|
+
* pi adds its notices at the END of the text, after a blank line:
|
|
7
|
+
*
|
|
8
|
+
* <output>
|
|
9
|
+
*
|
|
10
|
+
* [Showing lines 1001-3000 of 3000. Full output: /tmp/pi-bash-x.log] (only when truncated)
|
|
11
|
+
*
|
|
12
|
+
* Command exited with code 3 (only on failure)
|
|
13
|
+
*
|
|
14
|
+
* Only that trailing position is read, so output that merely contains those words is never
|
|
15
|
+
* mistaken for a notice.
|
|
16
|
+
*/
|
|
17
|
+
export type BashStatus = "ok" | "failed" | "aborted" | "timeout" | "no-exit";
|
|
18
|
+
/** How long a command runs before its live output appears under the row. */
|
|
19
|
+
export declare const LIVE_DELAY_MS = 500;
|
|
20
|
+
/** Lines of live output shown under a running row. */
|
|
21
|
+
export declare const LIVE_TAIL_LINES = 5;
|
|
22
|
+
/** Lines of a finished command's output left under its row until the row is expanded. */
|
|
23
|
+
export declare const PEEK_LINES = 3;
|
|
24
|
+
export interface BashOutput {
|
|
25
|
+
/** What the command printed, cleaned for display, without pi's notices. */
|
|
26
|
+
lines: string[];
|
|
27
|
+
status: BashStatus;
|
|
28
|
+
/** The exit code, when pi reported one. */
|
|
29
|
+
exitCode?: number;
|
|
30
|
+
timeoutSecs?: number;
|
|
31
|
+
/** pi cut the output; `lines` holds only the part it kept (the end). */
|
|
32
|
+
truncated: boolean;
|
|
33
|
+
/** Lines the command printed, counting those pi cut. Equals `lines.length` when not truncated. */
|
|
34
|
+
totalLines: number;
|
|
35
|
+
fullOutputPath?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface BashOutputHints {
|
|
38
|
+
/** Whether pi reports this result as an error. */
|
|
39
|
+
isError: boolean;
|
|
40
|
+
/** `result.details.truncation` */
|
|
41
|
+
truncation?: {
|
|
42
|
+
truncated?: boolean;
|
|
43
|
+
totalLines?: number;
|
|
44
|
+
};
|
|
45
|
+
/** `result.details.fullOutputPath` */
|
|
46
|
+
fullOutputPath?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One output line as plain text: every escape sequence removed (colour, cursor movement, titles,
|
|
50
|
+
* hyperlinks), a progress line that was redrawn with carriage returns reduced to its last state,
|
|
51
|
+
* tabs widened, other control characters dropped.
|
|
52
|
+
*/
|
|
53
|
+
export declare function cleanLine(line: string): string;
|
|
54
|
+
/** A line longer than this is cut, like OpenCode's character cap: no terminal shows more. */
|
|
55
|
+
export declare const MAX_LINE_CHARS = 1000;
|
|
56
|
+
/** Split pi's bash result text into the command's output and the status pi attached. */
|
|
57
|
+
export declare function parseBashOutput(raw: string, hints: BashOutputHints): BashOutput;
|
|
58
|
+
/** The first or last `keep` lines, and how many of the given lines were left out. */
|
|
59
|
+
export declare function foldLines(lines: string[], keep: number, from: "head" | "tail"): {
|
|
60
|
+
shown: string[];
|
|
61
|
+
hidden: number;
|
|
62
|
+
};
|
|
63
|
+
/** "2.1s" for a run of a second or more, else undefined (a 0.0s tag is noise). */
|
|
64
|
+
export declare function formatWallTime(seconds: unknown): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* The live output of a command that is still running, from the partial result pi sends while it
|
|
67
|
+
* runs (the text so far, cut to its last lines by pi). Returns the last `keep` lines, cleaned for
|
|
68
|
+
* the screen, and how many earlier lines are not shown. There is no status to read yet, so nothing
|
|
69
|
+
* is removed from the end: a line that looks like a notice is just a line.
|
|
70
|
+
*/
|
|
71
|
+
export declare function liveTail(raw: string, keep?: number): {
|
|
72
|
+
lines: string[];
|
|
73
|
+
earlier: number;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Whether a running row should show its live output yet: it has waited `LIVE_DELAY_MS` since it
|
|
77
|
+
* first appeared, and there is something to show.
|
|
78
|
+
*/
|
|
79
|
+
export declare function liveVisible(firstSeenAt: number | undefined, now: number, hasOutput: boolean): boolean;
|
|
80
|
+
/** Milliseconds until a row that is not yet visible should look again, or undefined if it never will. */
|
|
81
|
+
export declare function liveWaitMs(firstSeenAt: number | undefined, now: number): number | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* What stays visible under a finished row: the last `keep` lines (an error is usually at the end),
|
|
84
|
+
* and how many lines are not shown, counting those pi cut from a very long output.
|
|
85
|
+
*/
|
|
86
|
+
export declare function bashPeek(out: BashOutput, keep?: number): {
|
|
87
|
+
lines: string[];
|
|
88
|
+
hidden: number;
|
|
89
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash tool output for the one-line tool rows: separate what pi appended to the text (exit status,
|
|
3
|
+
* truncation notice) from what the command printed, clean the printed lines for the screen, and
|
|
4
|
+
* fold them. Pure functions with no pi imports, so tests run standalone.
|
|
5
|
+
*
|
|
6
|
+
* pi adds its notices at the END of the text, after a blank line:
|
|
7
|
+
*
|
|
8
|
+
* <output>
|
|
9
|
+
*
|
|
10
|
+
* [Showing lines 1001-3000 of 3000. Full output: /tmp/pi-bash-x.log] (only when truncated)
|
|
11
|
+
*
|
|
12
|
+
* Command exited with code 3 (only on failure)
|
|
13
|
+
*
|
|
14
|
+
* Only that trailing position is read, so output that merely contains those words is never
|
|
15
|
+
* mistaken for a notice.
|
|
16
|
+
*/
|
|
17
|
+
/** How long a command runs before its live output appears under the row. */
|
|
18
|
+
export const LIVE_DELAY_MS = 500;
|
|
19
|
+
/** Lines of live output shown under a running row. */
|
|
20
|
+
export const LIVE_TAIL_LINES = 5;
|
|
21
|
+
/** Lines of a finished command's output left under its row until the row is expanded. */
|
|
22
|
+
export const PEEK_LINES = 3;
|
|
23
|
+
const EXIT = /^Command exited with code (-?\d+)$/;
|
|
24
|
+
const TIMEOUT = /^Command timed out after (\d+(?:\.\d+)?) seconds?$/;
|
|
25
|
+
const SHOWING = /^\[Showing .*Full output: (.+)\]$/;
|
|
26
|
+
const CSI = /\x1b\[[0-9;:?<=>]*[ -/]*[@-~]/g;
|
|
27
|
+
const OSC = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g;
|
|
28
|
+
const OTHER_ESC = /\x1b[\s\S]?/g;
|
|
29
|
+
const CONTROL = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
|
|
30
|
+
/**
|
|
31
|
+
* One output line as plain text: every escape sequence removed (colour, cursor movement, titles,
|
|
32
|
+
* hyperlinks), a progress line that was redrawn with carriage returns reduced to its last state,
|
|
33
|
+
* tabs widened, other control characters dropped.
|
|
34
|
+
*/
|
|
35
|
+
export function cleanLine(line) {
|
|
36
|
+
const plain = line.replace(CSI, "").replace(OSC, "").replace(OTHER_ESC, "");
|
|
37
|
+
const last = plain.split("\r").reverse().find((p) => p.trim() !== "") ?? "";
|
|
38
|
+
const clean = last.replace(/\t/g, " ").replace(CONTROL, "").replace(/\s+$/, "");
|
|
39
|
+
return capLine(clean);
|
|
40
|
+
}
|
|
41
|
+
/** A line longer than this is cut, like OpenCode's character cap: no terminal shows more. */
|
|
42
|
+
export const MAX_LINE_CHARS = 1000;
|
|
43
|
+
function capLine(line) {
|
|
44
|
+
if (line.length <= MAX_LINE_CHARS)
|
|
45
|
+
return line;
|
|
46
|
+
// Cut by code points, so an emoji is never split in half.
|
|
47
|
+
return `${[...line].slice(0, MAX_LINE_CHARS - 1).join("")}…`;
|
|
48
|
+
}
|
|
49
|
+
/** Split pi's bash result text into the command's output and the status pi attached. */
|
|
50
|
+
export function parseBashOutput(raw, hints) {
|
|
51
|
+
const all = raw.replace(/\r\n/g, "\n").split("\n");
|
|
52
|
+
const dropBlankTail = () => {
|
|
53
|
+
while (all.length && all[all.length - 1].trim() === "")
|
|
54
|
+
all.pop();
|
|
55
|
+
};
|
|
56
|
+
dropBlankTail();
|
|
57
|
+
let status = "ok";
|
|
58
|
+
let exitCode;
|
|
59
|
+
let timeoutSecs;
|
|
60
|
+
if (hints.isError) {
|
|
61
|
+
status = "failed";
|
|
62
|
+
const last = all[all.length - 1] ?? "";
|
|
63
|
+
let m;
|
|
64
|
+
if ((m = last.match(EXIT))) {
|
|
65
|
+
exitCode = Number(m[1]);
|
|
66
|
+
all.pop();
|
|
67
|
+
}
|
|
68
|
+
else if (last === "Command aborted") {
|
|
69
|
+
status = "aborted";
|
|
70
|
+
all.pop();
|
|
71
|
+
}
|
|
72
|
+
else if ((m = last.match(TIMEOUT))) {
|
|
73
|
+
status = "timeout";
|
|
74
|
+
timeoutSecs = Number(m[1]);
|
|
75
|
+
all.pop();
|
|
76
|
+
}
|
|
77
|
+
else if (last === "Command terminated without an exit code") {
|
|
78
|
+
status = "no-exit";
|
|
79
|
+
all.pop();
|
|
80
|
+
}
|
|
81
|
+
else if (/operation was aborted/i.test(raw)) {
|
|
82
|
+
status = "aborted";
|
|
83
|
+
}
|
|
84
|
+
dropBlankTail();
|
|
85
|
+
}
|
|
86
|
+
let truncated = false;
|
|
87
|
+
let fullOutputPath = hints.fullOutputPath;
|
|
88
|
+
if (hints.truncation?.truncated) {
|
|
89
|
+
truncated = true;
|
|
90
|
+
const m = (all[all.length - 1] ?? "").match(SHOWING);
|
|
91
|
+
if (m) {
|
|
92
|
+
fullOutputPath = m[1];
|
|
93
|
+
all.pop();
|
|
94
|
+
dropBlankTail();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// pi's placeholder for a command that printed nothing.
|
|
98
|
+
if (all.length === 1 && all[0] === "(no output)")
|
|
99
|
+
all.length = 0;
|
|
100
|
+
const lines = all.map(cleanLine);
|
|
101
|
+
const reported = hints.truncation?.totalLines;
|
|
102
|
+
const totalLines = truncated && typeof reported === "number" && reported >= lines.length ? reported : lines.length;
|
|
103
|
+
return { lines, status, exitCode, timeoutSecs, truncated, totalLines, fullOutputPath };
|
|
104
|
+
}
|
|
105
|
+
/** The first or last `keep` lines, and how many of the given lines were left out. */
|
|
106
|
+
export function foldLines(lines, keep, from) {
|
|
107
|
+
if (lines.length <= keep)
|
|
108
|
+
return { shown: lines, hidden: 0 };
|
|
109
|
+
return { shown: from === "head" ? lines.slice(0, keep) : lines.slice(lines.length - keep), hidden: lines.length - keep };
|
|
110
|
+
}
|
|
111
|
+
/** "2.1s" for a run of a second or more, else undefined (a 0.0s tag is noise). */
|
|
112
|
+
export function formatWallTime(seconds) {
|
|
113
|
+
if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds < 1)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (seconds < 60)
|
|
116
|
+
return `${seconds.toFixed(1)}s`;
|
|
117
|
+
const m = Math.floor(seconds / 60);
|
|
118
|
+
return `${m}m${String(Math.round(seconds % 60)).padStart(2, "0")}s`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The live output of a command that is still running, from the partial result pi sends while it
|
|
122
|
+
* runs (the text so far, cut to its last lines by pi). Returns the last `keep` lines, cleaned for
|
|
123
|
+
* the screen, and how many earlier lines are not shown. There is no status to read yet, so nothing
|
|
124
|
+
* is removed from the end: a line that looks like a notice is just a line.
|
|
125
|
+
*/
|
|
126
|
+
export function liveTail(raw, keep = LIVE_TAIL_LINES) {
|
|
127
|
+
const all = raw.replace(/\r\n/g, "\n").split("\n");
|
|
128
|
+
while (all.length && all[all.length - 1].trim() === "")
|
|
129
|
+
all.pop();
|
|
130
|
+
const lines = all.map(cleanLine);
|
|
131
|
+
// A last line that cleans to nothing (a bare redraw escape) would show as an empty row.
|
|
132
|
+
while (lines.length && lines[lines.length - 1] === "")
|
|
133
|
+
lines.pop();
|
|
134
|
+
const { shown, hidden } = foldLines(lines, keep, "tail");
|
|
135
|
+
return { lines: shown, earlier: hidden };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Whether a running row should show its live output yet: it has waited `LIVE_DELAY_MS` since it
|
|
139
|
+
* first appeared, and there is something to show.
|
|
140
|
+
*/
|
|
141
|
+
export function liveVisible(firstSeenAt, now, hasOutput) {
|
|
142
|
+
return hasOutput && firstSeenAt !== undefined && now - firstSeenAt >= LIVE_DELAY_MS;
|
|
143
|
+
}
|
|
144
|
+
/** Milliseconds until a row that is not yet visible should look again, or undefined if it never will. */
|
|
145
|
+
export function liveWaitMs(firstSeenAt, now) {
|
|
146
|
+
if (firstSeenAt === undefined)
|
|
147
|
+
return undefined;
|
|
148
|
+
return Math.max(0, LIVE_DELAY_MS - (now - firstSeenAt)) + 10;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* What stays visible under a finished row: the last `keep` lines (an error is usually at the end),
|
|
152
|
+
* and how many lines are not shown, counting those pi cut from a very long output.
|
|
153
|
+
*/
|
|
154
|
+
export function bashPeek(out, keep = PEEK_LINES) {
|
|
155
|
+
const { shown } = foldLines(out.lines, keep, "tail");
|
|
156
|
+
return { lines: shown, hidden: Math.max(0, out.totalLines - shown.length) };
|
|
157
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand: the few things that make the TUI look like a particular product: the mark shown next to
|
|
3
|
+
* the version, the startup art and tagline, and the accent colours of the chrome.
|
|
4
|
+
*
|
|
5
|
+
* chassis ships a neutral default. A separate package layers its own identity on top by calling
|
|
6
|
+
* `registerBrand()` from client.ts:
|
|
7
|
+
*
|
|
8
|
+
* import { registerBrand } from "pi-chassis/client";
|
|
9
|
+
* export default function () {
|
|
10
|
+
* registerBrand({ name: "Acme", glyph: "A", colors: { primary: "#E4572E" } });
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* The brand is stored on globalThis (the same reason the widget registry is: pi loads every
|
|
14
|
+
* extension in its own module scope) and read while rendering, so load order does not matter and
|
|
15
|
+
* a later registration simply wins. Fields left out keep the defaults.
|
|
16
|
+
*
|
|
17
|
+
* This file has no imports, so client.ts can re-export it without loading anything else.
|
|
18
|
+
*/
|
|
19
|
+
/** `#RRGGBB` (or any colour string pi-tui's parseColor accepts). */
|
|
20
|
+
export type ColorValue = string;
|
|
21
|
+
export interface BrandColors {
|
|
22
|
+
/** Build-mode rail and badge, focus marks, the mark next to the version. */
|
|
23
|
+
primary: ColorValue;
|
|
24
|
+
/** Darker companion of `primary`. */
|
|
25
|
+
primaryDark: ColorValue;
|
|
26
|
+
/** Plan-mode rail and badge, and the provider glyph in the sidebar. */
|
|
27
|
+
plan: ColorValue;
|
|
28
|
+
/** Quiet brand neutral used for taglines. */
|
|
29
|
+
neutral: ColorValue;
|
|
30
|
+
}
|
|
31
|
+
export interface BrandSurfaces {
|
|
32
|
+
/** Sidebar, user messages, tool blocks. Keep in step with the theme's panel colour. */
|
|
33
|
+
panel: ColorValue;
|
|
34
|
+
/** The prompt box. Keep in step with the theme's raised panel colour. */
|
|
35
|
+
element: ColorValue;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `surfaces` are for dark themes, and `lightSurfaces` for light ones (a theme says which it is with
|
|
39
|
+
* its `appearance`). Text that chassis draws in a brand colour is darkened on a light surface until
|
|
40
|
+
* it can be read; rails, bars and the mode pill keep the brand colour exactly.
|
|
41
|
+
*/
|
|
42
|
+
export interface BrandSpec {
|
|
43
|
+
/** Shown as "<glyph> <name> pi <version>". Leave out for "<glyph> pi <version>". */
|
|
44
|
+
name?: string;
|
|
45
|
+
/** A single-cell mark. Pick one your users' fonts have, or ship a font that does. */
|
|
46
|
+
glyph?: string;
|
|
47
|
+
/** Line under the startup art. */
|
|
48
|
+
tagline?: string;
|
|
49
|
+
/** Startup header art, one string per row, painted in `primary`. Default: the pi logo. */
|
|
50
|
+
art?: string[];
|
|
51
|
+
colors?: Partial<BrandColors>;
|
|
52
|
+
/** For dark themes. */
|
|
53
|
+
surfaces?: Partial<BrandSurfaces>;
|
|
54
|
+
/** For light themes. Default: a light neutral pair. */
|
|
55
|
+
lightSurfaces?: Partial<BrandSurfaces>;
|
|
56
|
+
/**
|
|
57
|
+
* The icon set to use when the user has not chosen one: "nerd" for a brand whose users have a
|
|
58
|
+
* Nerd Font (or that ships one), "plain" for icons that need no special font. Default: "plain".
|
|
59
|
+
*/
|
|
60
|
+
icons?: "nerd" | "plain";
|
|
61
|
+
}
|
|
62
|
+
export declare const DEFAULT_BRAND: {
|
|
63
|
+
readonly glyph: "●";
|
|
64
|
+
readonly colors: {
|
|
65
|
+
readonly primary: "#4F8EB3";
|
|
66
|
+
readonly primaryDark: "#3B6B87";
|
|
67
|
+
readonly plan: "#EAB65D";
|
|
68
|
+
readonly neutral: "#C9CCC9";
|
|
69
|
+
};
|
|
70
|
+
readonly surfaces: {
|
|
71
|
+
readonly panel: "#121212";
|
|
72
|
+
readonly element: "#1C1C1C";
|
|
73
|
+
};
|
|
74
|
+
readonly lightSurfaces: {
|
|
75
|
+
readonly panel: "#F0F0F0";
|
|
76
|
+
readonly element: "#E6E6E6";
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
export interface ResolvedBrand {
|
|
80
|
+
name?: string;
|
|
81
|
+
glyph: string;
|
|
82
|
+
tagline?: string;
|
|
83
|
+
art?: string[];
|
|
84
|
+
colors: BrandColors;
|
|
85
|
+
surfaces: BrandSurfaces;
|
|
86
|
+
lightSurfaces: BrandSurfaces;
|
|
87
|
+
icons?: "nerd" | "plain";
|
|
88
|
+
/** Changes whenever the brand does; part of render cache keys. */
|
|
89
|
+
version: number;
|
|
90
|
+
}
|
|
91
|
+
/** The brand in effect, with defaults filled in. Cheap: cached per change. */
|
|
92
|
+
export declare function getBrand(): ResolvedBrand;
|
|
93
|
+
/**
|
|
94
|
+
* Merge a brand onto the current one. Nested `colors`, `surfaces` and `lightSurfaces` merge key by key. Returns a
|
|
95
|
+
* function that restores the previous brand (used by tests and by packages that unload).
|
|
96
|
+
*/
|
|
97
|
+
export declare function setBrand(spec: BrandSpec): () => void;
|
|
98
|
+
/** Forget any registered brand and go back to the defaults. */
|
|
99
|
+
export declare function resetBrand(): void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand: the few things that make the TUI look like a particular product: the mark shown next to
|
|
3
|
+
* the version, the startup art and tagline, and the accent colours of the chrome.
|
|
4
|
+
*
|
|
5
|
+
* chassis ships a neutral default. A separate package layers its own identity on top by calling
|
|
6
|
+
* `registerBrand()` from client.ts:
|
|
7
|
+
*
|
|
8
|
+
* import { registerBrand } from "pi-chassis/client";
|
|
9
|
+
* export default function () {
|
|
10
|
+
* registerBrand({ name: "Acme", glyph: "A", colors: { primary: "#E4572E" } });
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* The brand is stored on globalThis (the same reason the widget registry is: pi loads every
|
|
14
|
+
* extension in its own module scope) and read while rendering, so load order does not matter and
|
|
15
|
+
* a later registration simply wins. Fields left out keep the defaults.
|
|
16
|
+
*
|
|
17
|
+
* This file has no imports, so client.ts can re-export it without loading anything else.
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_BRAND = {
|
|
20
|
+
glyph: "●",
|
|
21
|
+
colors: {
|
|
22
|
+
primary: "#4F8EB3",
|
|
23
|
+
primaryDark: "#3B6B87",
|
|
24
|
+
plan: "#EAB65D",
|
|
25
|
+
neutral: "#C9CCC9",
|
|
26
|
+
},
|
|
27
|
+
surfaces: {
|
|
28
|
+
panel: "#121212",
|
|
29
|
+
element: "#1C1C1C",
|
|
30
|
+
},
|
|
31
|
+
lightSurfaces: {
|
|
32
|
+
panel: "#F0F0F0",
|
|
33
|
+
element: "#E6E6E6",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const KEY = Symbol.for("pi-chassis/brand");
|
|
37
|
+
function slot() {
|
|
38
|
+
const g = globalThis;
|
|
39
|
+
g[KEY] ??= { spec: {}, version: 0 };
|
|
40
|
+
return g[KEY];
|
|
41
|
+
}
|
|
42
|
+
/** The brand in effect, with defaults filled in. Cheap: cached per change. */
|
|
43
|
+
export function getBrand() {
|
|
44
|
+
const s = slot();
|
|
45
|
+
if (s.resolved?.version === s.version)
|
|
46
|
+
return s.resolved;
|
|
47
|
+
s.resolved = {
|
|
48
|
+
name: s.spec.name,
|
|
49
|
+
glyph: s.spec.glyph || DEFAULT_BRAND.glyph,
|
|
50
|
+
tagline: s.spec.tagline,
|
|
51
|
+
art: s.spec.art?.length ? s.spec.art : undefined,
|
|
52
|
+
colors: { ...DEFAULT_BRAND.colors, ...s.spec.colors },
|
|
53
|
+
surfaces: { ...DEFAULT_BRAND.surfaces, ...s.spec.surfaces },
|
|
54
|
+
lightSurfaces: { ...DEFAULT_BRAND.lightSurfaces, ...s.spec.lightSurfaces },
|
|
55
|
+
icons: s.spec.icons,
|
|
56
|
+
version: s.version,
|
|
57
|
+
};
|
|
58
|
+
return s.resolved;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Merge a brand onto the current one. Nested `colors`, `surfaces` and `lightSurfaces` merge key by key. Returns a
|
|
62
|
+
* function that restores the previous brand (used by tests and by packages that unload).
|
|
63
|
+
*/
|
|
64
|
+
export function setBrand(spec) {
|
|
65
|
+
const s = slot();
|
|
66
|
+
const before = s.spec;
|
|
67
|
+
s.spec = { ...before, ...spec, colors: { ...before.colors, ...spec.colors }, surfaces: { ...before.surfaces, ...spec.surfaces }, lightSurfaces: { ...before.lightSurfaces, ...spec.lightSurfaces } };
|
|
68
|
+
s.version++;
|
|
69
|
+
return () => {
|
|
70
|
+
s.spec = before;
|
|
71
|
+
s.version++;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Forget any registered brand and go back to the defaults. */
|
|
75
|
+
export function resetBrand() {
|
|
76
|
+
const s = slot();
|
|
77
|
+
s.spec = {};
|
|
78
|
+
s.version++;
|
|
79
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widget client: the entry point for widgets that live in other extensions or packages.
|
|
3
|
+
*
|
|
4
|
+
* import { registerWidget } from "pi-chassis/client";
|
|
5
|
+
*
|
|
6
|
+
* registerWidget(pi, { id: "git", render: () => ({ text: "main ●2" }) });
|
|
7
|
+
*
|
|
8
|
+
* Same spec and handle as api.ts, but it never loads the registry code: it only looks for the
|
|
9
|
+
* host on globalThis (the Symbol.for keys below), so it works whichever extension loads first
|
|
10
|
+
* and it works without chassis installed:
|
|
11
|
+
*
|
|
12
|
+
* - chassis already loaded: the widget is registered with it straight away.
|
|
13
|
+
* - chassis loads later: the widget is queued and chassis registers it when it loads.
|
|
14
|
+
* - chassis is not installed (checked at session_start, when every extension has loaded): the
|
|
15
|
+
* widget falls back to ctx.ui.setStatus(id, "<label> <text>"), updated on `refreshMs`. Only
|
|
16
|
+
* the one-line view is shown there: no detail lines, colours or caching.
|
|
17
|
+
*
|
|
18
|
+
* Import it as "pi-chassis/client" (the package's `exports` map), which resolves to compiled
|
|
19
|
+
* JavaScript with type declarations (dist/), so a consumer's plain Node tests and tsc need no
|
|
20
|
+
* TypeScript loader. pi itself loads this package's extensions from the .ts sources. It loads
|
|
21
|
+
* two value modules, `./brand.ts` and `./provider.ts`, and `../diff/host.ts` for the diff view API,
|
|
22
|
+
* and the rest of what it imports is types. To vendor it, copy those four files and keep the
|
|
23
|
+
* `chassis/` and `diff/` folder layout, or import it from the package instead. Any one of the
|
|
24
|
+
* three missing fails the load. Those modules import only types themselves, so they add no
|
|
25
|
+
* further files.
|
|
26
|
+
*/
|
|
27
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
28
|
+
import type { ToolRowsOptions, ToolRowSpecs, WidgetHandle, WidgetSpec } from "./api.ts";
|
|
29
|
+
import { type BrandSpec } from "./brand.ts";
|
|
30
|
+
import { type ProviderStatusSpec } from "./provider.ts";
|
|
31
|
+
/** The registry API version this client was written for. */
|
|
32
|
+
export declare const CLIENT_API_VERSION = 1;
|
|
33
|
+
/** The tool row API version this client was written for (`toolRowsVersion` on the registry). */
|
|
34
|
+
export declare const CLIENT_TOOL_ROWS_VERSION = 1;
|
|
35
|
+
/**
|
|
36
|
+
* Register (or replace) a widget with chassis if present, else show it as a plain status.
|
|
37
|
+
*
|
|
38
|
+
* Call it from the extension's default export. If you call it from inside a `session_start`
|
|
39
|
+
* handler instead (to be sure every extension has loaded), pass that handler's `ctx`: the widget
|
|
40
|
+
* missed that session's `session_start` event, and `ctx` lets chassis run its first `update` and
|
|
41
|
+
* start its timer now. A call made there when chassis is not installed shows nothing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function registerWidget(pi: ExtensionAPI, spec: WidgetSpec, ctx?: ExtensionContext): WidgetHandle;
|
|
44
|
+
/**
|
|
45
|
+
* Draw the rows of tools that belong to your extension the way chassis draws its built-in tool rows:
|
|
46
|
+
* one line per call, an icon, a title, a short description and a right-aligned outcome, with an
|
|
47
|
+
* expanded view on ctrl+o. Keyed by tool name. Returns a function that removes the rows again.
|
|
48
|
+
*
|
|
49
|
+
* registerToolRows({
|
|
50
|
+
* deploy_status: {
|
|
51
|
+
* icon: { nerd: "\u{F0AD}", plain: "#" },
|
|
52
|
+
* title: "Deploy",
|
|
53
|
+
* describe: (args, _cwd, theme) => theme.fg("muted", String(args.env)),
|
|
54
|
+
* summarize: (result) => String(result.details?.state ?? "done"),
|
|
55
|
+
* },
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* It works whichever package loads first (rows registered before chassis loads are queued and taken
|
|
59
|
+
* over when it does) and does nothing when chassis is not installed: the tools keep their own rows.
|
|
60
|
+
* chassis draws the built-in tools (read, write, edit, bash, grep, find, ls) itself and ignores a spec
|
|
61
|
+
* for those names. See ToolRowSpec in api.ts for what a spec may return.
|
|
62
|
+
*
|
|
63
|
+
* Pass `{ id }` if this can run more than once in a process (on every session_start, or after
|
|
64
|
+
* /reload): registering again with the same id replaces the earlier rows instead of adding more.
|
|
65
|
+
* Without an id, call the returned function (on session_shutdown, say) before registering again.
|
|
66
|
+
*/
|
|
67
|
+
export declare function registerToolRows(specs: ToolRowSpecs, options?: ToolRowsOptions): () => void;
|
|
68
|
+
/**
|
|
69
|
+
* Layer a brand (name, glyph, tagline, startup art, colours) on top of chassis. Call it from an
|
|
70
|
+
* extension's default export; it works whichever package loads first, and chassis repaints. The
|
|
71
|
+
* returned function restores the previous brand. See brand.ts for the fields.
|
|
72
|
+
*/
|
|
73
|
+
export declare function registerBrand(spec: BrandSpec): () => void;
|
|
74
|
+
/**
|
|
75
|
+
* Add a provider's own facts (a plan or quota bar, a display name, a glyph colour) to the sidebar's
|
|
76
|
+
* provider section and the footer. See provider.ts. The returned function removes it.
|
|
77
|
+
*/
|
|
78
|
+
export declare function registerProviderStatus(spec: ProviderStatusSpec): () => void;
|
|
79
|
+
/** Ask chassis to repaint now (no-op when it isn't loaded). For state a widget changes outside `update`. */
|
|
80
|
+
export declare function requestRender(): void;
|
|
81
|
+
export { addDiffAction, openChanges, openDiff } from "../diff/host.ts";
|
|
82
|
+
export type { DiffAction, DiffActionContext, DiffFileInfo, DiffHunkInfo, OpenDiffRequest } from "../diff/types.ts";
|
|
83
|
+
export type { ProviderMeter, ProviderStatusSpec } from "./provider.ts";
|
|
84
|
+
export type { BrandColors, BrandSpec, BrandSurfaces } from "./brand.ts";
|
|
85
|
+
export type { IconLike } from "./icons.ts";
|
|
86
|
+
export type { ToolRowResult, ToolRowsOptions, ToolRowSpec, ToolRowSpecs } from "./api.ts";
|
|
87
|
+
export type { DetailLine, DetailLink, WidgetAction, WidgetHandle, WidgetSpec, WidgetView, WidgetRenderContext, WidgetSlot, WidgetColor, WidgetLevel } from "./api.ts";
|