@geohar/unbien-cli 0.1.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.
Files changed (50) hide show
  1. package/README.md +72 -0
  2. package/dist/client.d.ts +89 -0
  3. package/dist/client.js +210 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/commands.d.ts +59 -0
  6. package/dist/commands.js +273 -0
  7. package/dist/commands.js.map +1 -0
  8. package/dist/connect.d.ts +2 -0
  9. package/dist/connect.js +475 -0
  10. package/dist/connect.js.map +1 -0
  11. package/dist/identity.d.ts +2 -0
  12. package/dist/identity.js +30 -0
  13. package/dist/identity.js.map +1 -0
  14. package/dist/invite.d.ts +10 -0
  15. package/dist/invite.js +28 -0
  16. package/dist/invite.js.map +1 -0
  17. package/dist/main.d.ts +3 -0
  18. package/dist/main.js +20 -0
  19. package/dist/main.js.map +1 -0
  20. package/dist/panels.d.ts +40 -0
  21. package/dist/panels.js +155 -0
  22. package/dist/panels.js.map +1 -0
  23. package/dist/picker.d.ts +6 -0
  24. package/dist/picker.js +95 -0
  25. package/dist/picker.js.map +1 -0
  26. package/dist/plan.d.ts +30 -0
  27. package/dist/plan.js +132 -0
  28. package/dist/plan.js.map +1 -0
  29. package/dist/reduce.d.ts +45 -0
  30. package/dist/reduce.js +123 -0
  31. package/dist/reduce.js.map +1 -0
  32. package/dist/render.d.ts +13 -0
  33. package/dist/render.js +91 -0
  34. package/dist/render.js.map +1 -0
  35. package/dist/replay.d.ts +2 -0
  36. package/dist/replay.js +57 -0
  37. package/dist/replay.js.map +1 -0
  38. package/dist/settings.d.ts +27 -0
  39. package/dist/settings.js +31 -0
  40. package/dist/settings.js.map +1 -0
  41. package/dist/store.d.ts +13 -0
  42. package/dist/store.js +20 -0
  43. package/dist/store.js.map +1 -0
  44. package/dist/theme.d.ts +7 -0
  45. package/dist/theme.js +110 -0
  46. package/dist/theme.js.map +1 -0
  47. package/dist/tui.d.ts +78 -0
  48. package/dist/tui.js +265 -0
  49. package/dist/tui.js.map +1 -0
  50. package/package.json +58 -0
package/dist/render.js ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Draws transcript items with pi's OWN interactive components, so a remote
3
+ * session looks identical to a local one. Built-in tool definitions are
4
+ * constructed for their renderers only — `execute` is never called, so nothing
5
+ * here touches the filesystem.
6
+ */
7
+ import { AssistantMessageComponent, UserMessageComponent, ToolExecutionComponent, createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, getMarkdownTheme, } from "@earendil-works/pi-coding-agent";
8
+ // SAFETY: these components touch exactly one TUI member — `requestRender` — to
9
+ // schedule a repaint. A one-shot render has nothing to schedule, so the stub is
10
+ // complete for this use; a component reaching further would throw loudly here
11
+ // rather than silently mis-render.
12
+ const offlineTui = { requestRender: () => { } };
13
+ function builtinRenderers(cwd) {
14
+ return new Map([
15
+ ["bash", createBashToolDefinition(cwd)],
16
+ ["edit", createEditToolDefinition(cwd)],
17
+ ["find", createFindToolDefinition(cwd)],
18
+ ["grep", createGrepToolDefinition(cwd)],
19
+ ["ls", createLsToolDefinition(cwd)],
20
+ ["read", createReadToolDefinition(cwd)],
21
+ ["write", createWriteToolDefinition(cwd)],
22
+ ]);
23
+ }
24
+ /**
25
+ * Draw an in-flight assistant turn with the SAME component that draws the
26
+ * settled one, so the text doesn't visibly reflow when the message lands — it
27
+ * is simply replaced by its finished self.
28
+ */
29
+ /**
30
+ * In-flight reasoning, drawn as a thinking block by the same component that
31
+ * renders it once settled.
32
+ */
33
+ export function renderThinking(text, width) {
34
+ if (!text)
35
+ return [];
36
+ const component = new AssistantMessageComponent(undefined, false, getMarkdownTheme());
37
+ // A thinking block carries its prose in `thinking`, not `text`.
38
+ component.updateContent({
39
+ role: "assistant",
40
+ content: [{ type: "thinking", thinking: text }],
41
+ }, true);
42
+ return component.render(width);
43
+ }
44
+ export function renderStreaming(text, width) {
45
+ if (!text)
46
+ return [];
47
+ const component = new AssistantMessageComponent({ role: "assistant", content: [{ type: "text", text }] }, false, getMarkdownTheme());
48
+ component.updateContent({ role: "assistant", content: [{ type: "text", text }] }, true);
49
+ return component.render(width);
50
+ }
51
+ export function renderTranscript(items, width, cwd, hideThinking = false) {
52
+ const markdownTheme = getMarkdownTheme();
53
+ const renderers = builtinRenderers(cwd);
54
+ const lines = [];
55
+ for (const item of items) {
56
+ if (item.kind === "notice") {
57
+ lines.push(` ${item.text}`, "");
58
+ continue;
59
+ }
60
+ if (item.kind === "message") {
61
+ const { message } = item;
62
+ if (message.role === "user") {
63
+ const text = message.content
64
+ .filter((b) => b.type === "text")
65
+ .map((b) => b.text ?? "")
66
+ .join("\n");
67
+ lines.push(...new UserMessageComponent(text, markdownTheme).render(width), "");
68
+ continue;
69
+ }
70
+ const component = new AssistantMessageComponent(
71
+ // The wire message IS pi's own serialization of an AssistantMessage.
72
+ message, hideThinking, markdownTheme);
73
+ lines.push(...component.render(width), "");
74
+ continue;
75
+ }
76
+ const card = new ToolExecutionComponent(item.toolName, item.toolCallId, item.args, {}, renderers.get(item.toolName.toLowerCase()), offlineTui, cwd);
77
+ card.setArgsComplete();
78
+ card.markExecutionStarted();
79
+ if (item.result) {
80
+ card.updateResult({
81
+ content: item.result.content,
82
+ details: item.result.details,
83
+ isError: item.result.isError,
84
+ });
85
+ }
86
+ card.setExpanded(true);
87
+ lines.push(...card.render(width), "");
88
+ }
89
+ return lines;
90
+ }
91
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/replay.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Replays a captured rpc-envelope fixture through the reducer and pi's
4
+ * components — the look-and-feel prototype, with no transport and no session.
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { reduce, toEnvelope } from "./reduce.js";
8
+ import { renderTranscript } from "./render.js";
9
+ import { applyTheme, availableThemes } from "./theme.js";
10
+ function parseArgs(argv) {
11
+ const positional = [];
12
+ let theme;
13
+ let width;
14
+ let cwd;
15
+ for (let i = 0; i < argv.length; i++) {
16
+ if (argv[i] === "--theme")
17
+ theme = argv[++i];
18
+ else if (argv[i] === "--width")
19
+ width = Number(argv[++i]);
20
+ else if (argv[i] === "--cwd")
21
+ cwd = argv[++i];
22
+ else
23
+ positional.push(argv[i]);
24
+ }
25
+ return { file: positional[0], theme, width, cwd };
26
+ }
27
+ const { file, theme, width, cwd } = parseArgs(process.argv.slice(2));
28
+ // Listing themes must not require a fixture.
29
+ if (theme === "list") {
30
+ console.log((await availableThemes()).join("\n"));
31
+ process.exit(0);
32
+ }
33
+ if (!file) {
34
+ console.error("usage: unbien replay <fixture.jsonl> [--theme <name>|list] [--width <cols>] [--cwd <path>]");
35
+ process.exit(1);
36
+ }
37
+ await applyTheme(theme);
38
+ const envelopes = [];
39
+ for (const line of readFileSync(file, "utf8").split("\n")) {
40
+ if (!line.trim())
41
+ continue;
42
+ let parsed;
43
+ try {
44
+ parsed = JSON.parse(line);
45
+ }
46
+ catch {
47
+ continue;
48
+ }
49
+ const env = toEnvelope(parsed);
50
+ if (env)
51
+ envelopes.push(env);
52
+ }
53
+ const cols = width ?? process.stdout.columns ?? 100;
54
+ // Tool cards resolve paths against the SESSION's cwd, which is remote and has
55
+ // no relation to wherever this client happens to be running.
56
+ console.log(renderTranscript(reduce(envelopes), cols, cwd ?? process.cwd()).join("\n"));
57
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":";AACA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAwB,MAAM,aAAa,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAExD,SAAS,SAAS,CAAC,IAAuB;IACxC,MAAM,UAAU,GAAa,EAAE,CAAA;IAC/B,IAAI,KAAyB,CAAA;IAC7B,IAAI,KAAyB,CAAA;IAC7B,IAAI,GAAuB,CAAA;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;aACvC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;aACpD,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO;YAAE,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;;YACxC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,CAAA;IAChC,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAA;AACnD,CAAC;AAED,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAEpE,6CAA6C;AAC7C,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;AAED,IAAI,CAAC,IAAI,EAAE,CAAC;IACV,OAAO,CAAC,KAAK,CACX,4FAA4F,CAC7F,CAAA;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,CAAC,KAAK,CAAC,CAAA;AAEvB,MAAM,SAAS,GAAsB,EAAE,CAAA;AACvC,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IAC1D,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,SAAQ;IAC1B,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,SAAQ;IACV,CAAC;IACD,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;IAC9B,IAAI,GAAG;QAAE,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,IAAI,GAAG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,GAAG,CAAA;AACnD,8EAA8E;AAC9E,6DAA6D;AAC7D,OAAO,CAAC,GAAG,CACT,gBAAgB,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAC3E,CAAA"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The CLI's own settings, kept separate from pi's: these are client display
3
+ * preferences and have no meaning to the host session.
4
+ */
5
+ /** pi-plan's widget states: hidden, a one-line summary, or the full panel. */
6
+ export type PanelMode = "hidden" | "collapsed" | "expanded";
7
+ export interface CliSettings {
8
+ /** Theme name; falls back to pi's configured theme when unset. */
9
+ theme?: string;
10
+ /** Render reasoning deltas live, not just in the settled message. */
11
+ streamThinking: boolean;
12
+ /** Show the settled message's thinking block at all. */
13
+ showThinking: boolean;
14
+ /** Persistent plan widget above the composer. */
15
+ planMode: PanelMode;
16
+ /** Persistent subagents widget above the composer. */
17
+ subagentsMode: PanelMode;
18
+ /** Include finished plan items (pi-plan's `filter done`). */
19
+ planShowDone: boolean;
20
+ /** Include non-plan kinds — design/note context (pi-plan's `filter context`). */
21
+ planShowContext: boolean;
22
+ /** Row cap before the panel trails off with "…N more". */
23
+ planMaxRows: number;
24
+ }
25
+ export declare function settingsPath(): string;
26
+ export declare function loadSettings(path?: string): CliSettings;
27
+ export declare function saveSettings(settings: CliSettings, path?: string): void;
@@ -0,0 +1,31 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ const DEFAULTS = {
5
+ streamThinking: false,
6
+ showThinking: true,
7
+ planMode: "hidden",
8
+ subagentsMode: "hidden",
9
+ // pi-plan's own defaults: a plan should show what's actionable, not history.
10
+ planShowDone: false,
11
+ planShowContext: false,
12
+ planMaxRows: 18,
13
+ };
14
+ const SETTINGS_PATH = join(homedir(), ".config", "unbien-cli", "settings.json");
15
+ export function settingsPath() {
16
+ return SETTINGS_PATH;
17
+ }
18
+ export function loadSettings(path = SETTINGS_PATH) {
19
+ try {
20
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
21
+ return { ...DEFAULTS, ...parsed };
22
+ }
23
+ catch {
24
+ return { ...DEFAULTS };
25
+ }
26
+ }
27
+ export function saveSettings(settings, path = SETTINGS_PATH) {
28
+ mkdirSync(dirname(path), { recursive: true });
29
+ writeFileSync(path, `${JSON.stringify(settings, null, 2)}\n`);
30
+ }
31
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AA4BzC,MAAM,QAAQ,GAAgB;IAC5B,cAAc,EAAE,KAAK;IACrB,YAAY,EAAE,IAAI;IAClB,QAAQ,EAAE,QAAQ;IAClB,aAAa,EAAE,QAAQ;IACvB,6EAA6E;IAC7E,YAAY,EAAE,KAAK;IACnB,eAAe,EAAE,KAAK;IACtB,WAAW,EAAE,EAAE;CAChB,CAAA;AAED,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,CAAC,CAAA;AAE/E,MAAM,UAAU,YAAY;IAC1B,OAAO,aAAa,CAAA;AACtB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAI,GAAG,aAAa;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CACvB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CACH,CAAA;QACzB,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAA;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAA;IACxB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,QAAqB,EACrB,IAAI,GAAG,aAAa;IAEpB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7C,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;AAC/D,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The pi machines this client has paired with. Pairing is machine-level, so one
3
+ * successful pair reaches every session that pi hosts — the invite's room is
4
+ * just where the token was issued, not a limit on what we can attach to.
5
+ */
6
+ export interface PairedPeer {
7
+ epk: string;
8
+ relayUrl: string;
9
+ name?: string;
10
+ pairedAt: string;
11
+ }
12
+ export declare function loadPeers(path?: string): PairedPeer[];
13
+ export declare function rememberPeer(peer: PairedPeer, path?: string): void;
package/dist/store.js ADDED
@@ -0,0 +1,20 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ const STORE_PATH = join(homedir(), ".local", "state", "un-bien", "proxy-peers.json");
5
+ export function loadPeers(path = STORE_PATH) {
6
+ try {
7
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
8
+ return parsed.peers ?? [];
9
+ }
10
+ catch {
11
+ return [];
12
+ }
13
+ }
14
+ export function rememberPeer(peer, path = STORE_PATH) {
15
+ const peers = loadPeers(path).filter((p) => p.epk !== peer.epk);
16
+ peers.push(peer);
17
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
18
+ writeFileSync(path, JSON.stringify({ peers }, null, 2), { mode: 0o600 });
19
+ }
20
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAczC,MAAM,UAAU,GAAG,IAAI,CACrB,OAAO,EAAE,EACT,QAAQ,EACR,OAAO,EACP,SAAS,EACT,kBAAkB,CACnB,CAAA;AAED,MAAM,UAAU,SAAS,CAAC,IAAI,GAAG,UAAU;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAEnD,CAAA;QACD,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAA;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAgB,EAAE,IAAI,GAAG,UAAU;IAC9D,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC,CAAA;IAC/D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAChB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1D,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AAC1E,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare function availableThemes(): Promise<string[]>;
2
+ /**
3
+ * Explicit flag wins, then the theme configured in pi's settings, then pi's own
4
+ * terminal-background default. Returns the theme actually applied, which may
5
+ * differ from the request when a name cannot be resolved.
6
+ */
7
+ export declare function applyTheme(requested?: string): Promise<string>;
package/dist/theme.js ADDED
@@ -0,0 +1,110 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { DefaultResourceLoader, SettingsManager, getAgentDir, initTheme, } from "@earendil-works/pi-coding-agent";
4
+ const BUILTIN = ["dark", "light"];
5
+ /** pi's active theme is shared through this key so separate module copies agree. */
6
+ const THEME_KEY = Symbol.for("@earendil-works/pi-coding-agent:theme");
7
+ function customThemesDir() {
8
+ return join(getAgentDir(), "themes");
9
+ }
10
+ /**
11
+ * Themes can be contributed by pi PACKAGES, declared as a `pi.themes` array in
12
+ * the package manifest (e.g. `@wierdbytes/pi-tokyo-night`). Resolving those by
13
+ * hand would mean reimplementing pi's rules, so ask pi's own resource loader —
14
+ * with every other resource disabled, so no extension code is loaded or run.
15
+ */
16
+ async function contributedThemes() {
17
+ const agentDir = getAgentDir();
18
+ const loader = new DefaultResourceLoader({
19
+ cwd: process.cwd(),
20
+ agentDir,
21
+ settingsManager: SettingsManager.create(process.cwd(), agentDir),
22
+ noExtensions: true,
23
+ noSkills: true,
24
+ noPromptTemplates: true,
25
+ noContextFiles: true,
26
+ });
27
+ await loader.reload();
28
+ const found = new Map();
29
+ for (const theme of loader.getThemes().themes) {
30
+ if (theme.name && !found.has(theme.name))
31
+ found.set(theme.name, theme);
32
+ }
33
+ return found;
34
+ }
35
+ export async function availableThemes() {
36
+ const names = new Set(BUILTIN);
37
+ const dir = customThemesDir();
38
+ if (existsSync(dir)) {
39
+ for (const file of readdirSync(dir)) {
40
+ if (file.endsWith(".json"))
41
+ names.add(file.slice(0, -".json".length));
42
+ }
43
+ }
44
+ try {
45
+ for (const name of (await contributedThemes()).keys())
46
+ names.add(name);
47
+ }
48
+ catch {
49
+ // Package themes are a bonus; never block startup on resolving them.
50
+ }
51
+ return [...names].sort((a, b) => a.localeCompare(b));
52
+ }
53
+ /**
54
+ * pi's `theme` setting may be a single name or an auto pair `"<light>/<dark>"`.
55
+ * pi resolves the pair by querying the terminal's real background; that
56
+ * detector isn't exported, so fall back to the COLORFGBG hint.
57
+ */
58
+ function configuredTheme() {
59
+ let setting;
60
+ try {
61
+ setting = SettingsManager.create(process.cwd()).getThemeSetting();
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ if (!setting)
67
+ return undefined;
68
+ const slash = setting.indexOf("/");
69
+ if (slash === -1)
70
+ return setting;
71
+ const light = setting.slice(0, slash).trim();
72
+ const dark = setting.slice(slash + 1).trim();
73
+ if (!light || !dark)
74
+ return undefined;
75
+ const bg = process.env["COLORFGBG"]?.split(";").pop();
76
+ return bg !== undefined && Number(bg) >= 9 ? light : dark;
77
+ }
78
+ /**
79
+ * Explicit flag wins, then the theme configured in pi's settings, then pi's own
80
+ * terminal-background default. Returns the theme actually applied, which may
81
+ * differ from the request when a name cannot be resolved.
82
+ */
83
+ export async function applyTheme(requested) {
84
+ const name = requested ?? configuredTheme();
85
+ if (!name) {
86
+ initTheme(undefined, false);
87
+ return "auto";
88
+ }
89
+ // Anything pi can resolve on its own goes through the public API.
90
+ if (BUILTIN.includes(name) ||
91
+ existsSync(join(customThemesDir(), `${name}.json`))) {
92
+ initTheme(name, false);
93
+ return name;
94
+ }
95
+ try {
96
+ const contributed = (await contributedThemes()).get(name);
97
+ if (contributed) {
98
+ ;
99
+ globalThis[THEME_KEY] = contributed;
100
+ return name;
101
+ }
102
+ }
103
+ catch {
104
+ // Fall through to the built-in fallback below.
105
+ }
106
+ // initTheme falls back to dark silently; be explicit that we did not comply.
107
+ initTheme(name, false);
108
+ return `dark (could not resolve "${name}")`;
109
+ }
110
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"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"}
package/dist/tui.d.ts ADDED
@@ -0,0 +1,78 @@
1
+ import { TuiMainScreen, type SelectItem } from "@earendil-works/pi-tui";
2
+ export interface ShellStatus {
3
+ session: string;
4
+ cwd: string;
5
+ model?: string;
6
+ working?: boolean;
7
+ /** What the agent is doing right now, e.g. a running tool's name. */
8
+ activity?: string;
9
+ }
10
+ export declare class Shell {
11
+ private readonly onQuit;
12
+ readonly tui: TuiMainScreen;
13
+ private readonly editor;
14
+ private readonly footer;
15
+ private readonly working;
16
+ private isWorking;
17
+ private quitting;
18
+ /** In-flight assistant turn, shown above the editor until it settles. */
19
+ private readonly live;
20
+ private readonly transcript;
21
+ /** Persistent panel widgets, pinned above the composer like pi's own. */
22
+ private readonly widgets;
23
+ /**
24
+ * Command output (/tree, /plan, …) lives BELOW the transcript: `draw()`
25
+ * replaces the transcript wholesale on every frame, which would otherwise
26
+ * erase anything a command had printed.
27
+ */
28
+ private readonly notes;
29
+ private status;
30
+ /** A modal chooser owns input while open, so the editor must not also see it. */
31
+ private overlay;
32
+ constructor(status: ShellStatus, onSubmit: (text: string) => void, onQuit?: () => void);
33
+ start(): void;
34
+ /**
35
+ * Terminal reset is pi's job, not ours: `tui.stop()` → `ProcessTerminal.stop()`
36
+ * disables bracketed paste and the Kitty keyboard protocol, drops its stdin
37
+ * handlers, and restores raw mode to what it WAS — pausing stdin first, which
38
+ * its own comment marks as the fix for a Ctrl-D race that can close the parent
39
+ * shell over SSH. Doing any of that by hand here re-broke that ordering.
40
+ *
41
+ * We add only what pi can't know about: our socket, and a watchdog so a throw
42
+ * in teardown can't leave the process stranded.
43
+ */
44
+ quit(): void;
45
+ /**
46
+ * The in-flight turn is REWRITTEN on every delta, so it can't be appended to
47
+ * the transcript. It renders in its own region and is cleared the moment the
48
+ * settled message takes its place — same component, so no visible reflow.
49
+ */
50
+ setLive(lines: readonly string[]): void;
51
+ stop(): void;
52
+ setStatus(patch: Partial<ShellStatus>): void;
53
+ /**
54
+ * Transcript must live INSIDE the TUI: a full repaint emits `\x1b[3J`, which
55
+ * clears the scrollback buffer, so anything written straight to stdout is
56
+ * erased the moment the shell next redraws.
57
+ */
58
+ print(lines: readonly string[]): void;
59
+ /** Drop command output (it is scratch, not conversation). */
60
+ clearNotes(): void;
61
+ /** Replace the pinned panel widgets (plan / subagents). */
62
+ setWidgets(lines: readonly string[]): void;
63
+ /** Replace the whole transcript (history replay re-reduces from scratch). */
64
+ setTranscript(lines: readonly string[]): void;
65
+ /**
66
+ * A chooser whose rows carry ACTIONS: a single keypress both picks the row
67
+ * and says what to do with it. Filtering is off here on purpose — the letter
68
+ * keys are the shortcuts, and a filter would swallow them.
69
+ */
70
+ chooseAction(title: string, items: SelectItem[], hint: string, keys: readonly string[]): Promise<{
71
+ item: SelectItem;
72
+ action: string;
73
+ } | null>;
74
+ /** A highlighted chooser layered over the editor; resolves to the pick. */
75
+ choose(title: string, items: SelectItem[]): Promise<SelectItem | null>;
76
+ get hasOverlay(): boolean;
77
+ private renderFooter;
78
+ }