@hortus/cli 0.0.0-stage → 1.0.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/dist/api.d.ts +53 -0
  2. package/dist/api.d.ts.map +1 -0
  3. package/dist/api.js +15 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/app.d.ts +9 -0
  6. package/dist/app.d.ts.map +1 -0
  7. package/dist/app.js +93 -0
  8. package/dist/app.js.map +1 -0
  9. package/dist/cli.d.ts +66 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +235 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/command-menu.d.ts +33 -0
  14. package/dist/command-menu.d.ts.map +1 -0
  15. package/dist/command-menu.js +135 -0
  16. package/dist/command-menu.js.map +1 -0
  17. package/dist/commands.d.ts +36 -0
  18. package/dist/commands.d.ts.map +1 -0
  19. package/dist/commands.js +73 -0
  20. package/dist/commands.js.map +1 -0
  21. package/dist/entry.d.ts +35 -0
  22. package/dist/entry.d.ts.map +1 -0
  23. package/dist/entry.js +96 -0
  24. package/dist/entry.js.map +1 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +58 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/markdown.d.ts +20 -0
  30. package/dist/markdown.d.ts.map +1 -0
  31. package/dist/markdown.js +233 -0
  32. package/dist/markdown.js.map +1 -0
  33. package/dist/prompt-input.d.ts +14 -0
  34. package/dist/prompt-input.d.ts.map +1 -0
  35. package/dist/prompt-input.js +139 -0
  36. package/dist/prompt-input.js.map +1 -0
  37. package/dist/session-picker.d.ts +9 -0
  38. package/dist/session-picker.d.ts.map +1 -0
  39. package/dist/session-picker.js +47 -0
  40. package/dist/session-picker.js.map +1 -0
  41. package/dist/status-bar.d.ts +3 -0
  42. package/dist/status-bar.d.ts.map +1 -0
  43. package/dist/status-bar.js +12 -0
  44. package/dist/status-bar.js.map +1 -0
  45. package/dist/transcript.d.ts +30 -0
  46. package/dist/transcript.d.ts.map +1 -0
  47. package/dist/transcript.js +164 -0
  48. package/dist/transcript.js.map +1 -0
  49. package/package.json +37 -3
  50. package/README.md +0 -3
package/dist/api.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { Plugin, SessionState } from "@hortus/core";
2
+ import { type ComponentType } from "react";
3
+ import type { UiState } from "./cli.ts";
4
+ import type { Command } from "./commands.ts";
5
+ export type { SessionState } from "@hortus/core";
6
+ export { useShallow } from "zustand/react/shallow";
7
+ export type { Cli, UiState } from "./cli.ts";
8
+ export type { Candidate, Command } from "./commands.ts";
9
+ export { theme, usageLine } from "./entry.ts";
10
+ /** UI parts a plugin can replace. */
11
+ export interface Slots {
12
+ statusBar: ComponentType;
13
+ }
14
+ /** Default export of a plugin whose hortus.json depends on "@hortus/cli". */
15
+ export interface CliPlugin extends Plugin {
16
+ /** each slot override gets the implementation it replaces as `Default`: the built-in one or an earlier plugin's */
17
+ cli?: {
18
+ [K in keyof Slots]?: ComponentType<{
19
+ Default: Slots[K];
20
+ }>;
21
+ } & {
22
+ /** added to the `/` menu; one named like an earlier command (built-in or plugin) replaces it */
23
+ commands?: Command[];
24
+ };
25
+ }
26
+ export declare const CliContext: import("react").Context<{
27
+ ui: import("zustand").StoreApi<UiState>;
28
+ agent: {
29
+ providers: Record<string, import("@hortus/core").ProviderDef>;
30
+ plugins: Plugin[];
31
+ resolve: (opts?: Pick<import("@hortus/core").RunOptions, "model" | "provider">) => Promise<{
32
+ name: string;
33
+ model: string;
34
+ provider: import("@hortus/core").Provider;
35
+ }>;
36
+ listModels: () => Promise<{
37
+ provider: string;
38
+ id: string;
39
+ }[]>;
40
+ run: (history: import("@hortus/core").Message[], opts?: import("@hortus/core").RunOptions) => Promise<void>;
41
+ };
42
+ readonly session: import("@hortus/core").Session;
43
+ commands: Command[];
44
+ submit: (value: string) => boolean;
45
+ abort: () => void | undefined;
46
+ setCopied: (chars: number) => void;
47
+ report: (text: string) => void;
48
+ setSession: (session: import("@hortus/core").Session) => void;
49
+ } | undefined>;
50
+ /** Selectors must return primitives or existing references, or go through useShallow (zustand loops on a fresh object). */
51
+ export declare const useSession: <T>(selector: (s: SessionState) => T) => T;
52
+ export declare const useUi: <T>(selector: (s: UiState) => T) => T;
53
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,KAAK,aAAa,EAA6B,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAO,OAAO,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9C,qCAAqC;AACrC,MAAM,WAAW,KAAK;IACpB,SAAS,EAAE,aAAa,CAAC;CAC1B;AAED,6EAA6E;AAC7E,MAAM,WAAW,SAAU,SAAQ,MAAM;IACvC,mHAAmH;IACnH,GAAG,CAAC,EAAE;SAAG,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC;YAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;SAAE,CAAC;KAAE,GAAG;QACpE,gGAAgG;QAChG,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;KACtB,CAAC;CACH;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;cAA4C,CAAC;AAQpE,2HAA2H;AAC3H,eAAO,MAAM,UAAU,GAAI,CAAC,YAAY,CAAC,CAAC,EAAE,YAAY,KAAK,CAAC,KAAG,CAI9D,CAAC;AACJ,eAAO,MAAM,KAAK,GAAI,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,KAAG,CAAoC,CAAC"}
package/dist/api.js ADDED
@@ -0,0 +1,15 @@
1
+ import { createContext, useContext } from "react";
2
+ import { useStore } from "zustand";
3
+ export { useShallow } from "zustand/react/shallow";
4
+ export { theme, usageLine } from "./entry.js";
5
+ export const CliContext = createContext(undefined);
6
+ function useCli() {
7
+ const cli = useContext(CliContext);
8
+ if (!cli)
9
+ throw new Error("hortus's hooks only work inside its TUI");
10
+ return cli;
11
+ }
12
+ /** Selectors must return primitives or existing references, or go through useShallow (zustand loops on a fresh object). */
13
+ export const useSession = (selector) => useStore(useUi((s) => s.session), selector);
14
+ export const useUi = (selector) => useStore(useCli().ui, selector);
15
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKnC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGnD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAgB9C,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAkB,SAAS,CAAC,CAAC;AAEpE,SAAS,MAAM;IACb,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IACrE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,2HAA2H;AAC3H,MAAM,CAAC,MAAM,UAAU,GAAG,CAAI,QAAgC,EAAK,EAAE,CACnE,QAAQ,CACN,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EACvB,QAAQ,CACT,CAAC;AACJ,MAAM,CAAC,MAAM,KAAK,GAAG,CAAI,QAA2B,EAAK,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC"}
package/dist/app.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { type CliPlugin, type Slots } from "./api.ts";
2
+ import type { Cli } from "./cli.ts";
3
+ /** Built-in slots, each wrapped by every plugin's override in load order (later plugins end up outermost). */
4
+ export declare function composeSlots(plugins: CliPlugin[], onError: (text: string) => void): Slots;
5
+ /** The TUI; plugins' `cli` overrides are composed once into the slots it renders. */
6
+ export declare function App({ cli }: {
7
+ cli: Cli;
8
+ }): import("react").JSX.Element;
9
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,KAAK,EAAS,MAAM,UAAU,CAAC;AACzE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAgDpC,8GAA8G;AAC9G,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,KAAK,CAgBzF;AAED,qFAAqF;AACrF,wBAAgB,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE;IAAE,GAAG,EAAE,GAAG,CAAA;CAAE,+BAOxC"}
package/dist/app.js ADDED
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useApp, useBoxMetrics, useInput, useWindowSize } from "ink";
3
+ import { Component, useMemo, useRef, useState } from "react";
4
+ import { CliContext, useUi } from "./api.js";
5
+ import { CommandMenu, useCommandMenu } from "./command-menu.js";
6
+ import { theme } from "./entry.js";
7
+ import { PromptInput } from "./prompt-input.js";
8
+ import { StatusBar } from "./status-bar.js";
9
+ import { Transcript } from "./transcript.js";
10
+ /** Clips children to its own height. `fromBottom` = rows scrolled up from the end, so 0 follows new content. */
11
+ function ScrollBox({ fromBottom, children, ...props }) {
12
+ const view = useRef(null);
13
+ const content = useRef(null);
14
+ const max = Math.max(0, useBoxMetrics(content).height - useBoxMetrics(view).height);
15
+ return (_jsx(Box, { ref: view, overflow: "hidden", flexDirection: "column",
16
+ // size from the parent, not from the (taller/wider) content
17
+ flexBasis: 0, contentOffsetY: max - Math.min(fromBottom, max), ...props, children: _jsx(Box, { ref: content, flexShrink: 0, flexDirection: "column", width: "100%", children: children }) }));
18
+ }
19
+ const clamp = (n) => Math.max(0, Math.min(n, 10_000));
20
+ const INPUT_ROWS = 8;
21
+ /** A throwing plugin component falls back to the one it replaced instead of taking the whole TUI down. */
22
+ class Guard extends Component {
23
+ state = { failed: false };
24
+ static getDerivedStateFromError = () => ({ failed: true });
25
+ componentDidCatch(e) {
26
+ this.props.onError(e);
27
+ }
28
+ render() {
29
+ const Fallback = this.props.fallback;
30
+ return this.state.failed ? _jsx(Fallback, {}) : this.props.children;
31
+ }
32
+ }
33
+ /** Built-in slots, each wrapped by every plugin's override in load order (later plugins end up outermost). */
34
+ export function composeSlots(plugins, onError) {
35
+ const slots = { statusBar: StatusBar };
36
+ for (const { cli } of plugins)
37
+ for (const k of Object.keys(slots)) {
38
+ const Override = cli?.[k];
39
+ if (!Override)
40
+ continue;
41
+ const Default = slots[k];
42
+ const fail = (e) => onError(`${k} plugin failed, showing the one it replaced: ${e instanceof Error ? e.message : String(e)}`);
43
+ slots[k] = () => (_jsx(Guard, { fallback: Default, onError: fail, children: _jsx(Override, { Default: Default }) }));
44
+ }
45
+ return slots;
46
+ }
47
+ /** The TUI; plugins' `cli` overrides are composed once into the slots it renders. */
48
+ export function App({ cli }) {
49
+ const slots = useMemo(() => composeSlots(cli.agent.plugins, cli.report), [cli]);
50
+ return (_jsx(CliContext, { value: cli, children: _jsx(Layout, { cli: cli, slots: slots }) }));
51
+ }
52
+ function Layout({ cli, slots }) {
53
+ const entries = useUi((s) => s.entries);
54
+ const live = useUi((s) => s.live);
55
+ const busy = useUi((s) => s.busy);
56
+ const path = useUi((s) => s.session.path);
57
+ const [input, setInput] = useState("");
58
+ const menu = useCommandMenu(cli, input, setInput);
59
+ // rows the input is scrolled up from its end; clamped on read by ScrollBox, so overshoot just sticks at the top
60
+ const [inScroll, setInScroll] = useState(0);
61
+ const { rows } = useWindowSize();
62
+ const { exit } = useApp();
63
+ useInput((input, key) => {
64
+ if (key.ctrl && input === "d")
65
+ exit();
66
+ else if (key.ctrl && input === "c") {
67
+ setInput("");
68
+ setInScroll(0);
69
+ }
70
+ else if (key.escape && menu.shown)
71
+ menu.hide();
72
+ else if (key.escape)
73
+ cli.abort();
74
+ });
75
+ const submit = (value) => {
76
+ if (!cli.submit(value))
77
+ return;
78
+ setInput("");
79
+ setInScroll(0);
80
+ };
81
+ // stable across keystrokes, so Transcript doesn't re-wrap the whole history on every key
82
+ const shown = useMemo(() => [
83
+ ...entries,
84
+ ...(live.thinking ? [{ id: -2, kind: "thinking", text: live.thinking }] : []),
85
+ ...(live.text ? [{ id: -1, kind: "assistant", text: live.text }] : []),
86
+ ], [entries, live]);
87
+ return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Transcript
88
+ // another session (/new) starts unscrolled, with nothing selected
89
+ , { entries: shown, onWheelBelow: (delta) => setInScroll((n) => clamp(n + delta)), onCopy: cli.setCopied }, path), _jsxs(Box, { flexShrink: 0, marginTop: 1, borderStyle: "round", borderColor: busy ? theme.borderIdle : theme.border, paddingX: 1, children: [_jsx(Text, { color: theme.accent, children: "> " }), _jsx(ScrollBox, { flexGrow: 1, maxHeight: INPUT_ROWS, fromBottom: inScroll, children: _jsx(PromptInput, { value: input, onChange: setInput, onSubmit: submit, onKey: menu.onKey,
90
+ // scroll just enough to keep the caret's row in view
91
+ onCaretRow: (row) => setInScroll((n) => Math.min(row, Math.max(n, row - INPUT_ROWS + 1))) }) })] }), menu.shown && _jsx(CommandMenu, { rows: menu.rows, at: menu.at }), _jsx(slots.statusBar, {})] }));
92
+ }
93
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import type { Agent, Message, Plugin, Session } from "@hortus/core";
2
+ import { type Command } from "./commands.ts";
3
+ import { type Entry } from "./entry.ts";
4
+ /** What the TUI shows; written only by `createCli`, read through `useUi`. */
5
+ export interface UiState {
6
+ /** the session shown and run on; swapped by `setSession` (e.g. /new) */
7
+ session: Session;
8
+ entries: Entry[];
9
+ /** streamed thinking and text not yet turned into entries */
10
+ live: {
11
+ thinking: string;
12
+ text: string;
13
+ };
14
+ /** messages of the running turn; appended to the session when the run ends */
15
+ turn: Message[];
16
+ busy: boolean;
17
+ /** provider name and model the next run would use, plus the context window when the API reports it */
18
+ status?: {
19
+ name: string;
20
+ model: string;
21
+ window?: number;
22
+ };
23
+ /** chars of the last copy, shown until the next submit */
24
+ copied?: number;
25
+ }
26
+ /** Transcript entries for messages already in the session, so a resumed one shows as it was left. */
27
+ export declare function replay(messages: Message[]): Entry[];
28
+ /**
29
+ * The TUI's logic, without React: runs the agent on `session` or a `/name arg` command, turns its events into
30
+ * transcript entries and keeps `status` in step with the session's model and provider. `makeAgent` creates the agent
31
+ * with the run observer it's given.
32
+ */
33
+ export declare function createCli(session: Session, makeAgent: (observer: Plugin) => Promise<Agent>): Promise<{
34
+ ui: import("zustand/vanilla").StoreApi<UiState>;
35
+ agent: {
36
+ providers: Record<string, import("@hortus/core").ProviderDef>;
37
+ plugins: Plugin[];
38
+ resolve: (opts?: Pick<import("@hortus/core").RunOptions, "model" | "provider">) => Promise<{
39
+ name: string;
40
+ model: string;
41
+ provider: import("@hortus/core").Provider;
42
+ }>;
43
+ listModels: () => Promise<{
44
+ provider: string;
45
+ id: string;
46
+ }[]>;
47
+ run: (history: Message[], opts?: import("@hortus/core").RunOptions) => Promise<void>;
48
+ };
49
+ /** the session shown, `ui`'s `session` */
50
+ readonly session: Session;
51
+ /** the built-ins and every plugin's `cli.commands`, one per name */
52
+ commands: Command[];
53
+ /**
54
+ * starts `value` as a command or the next turn; false (the prompt keeps its input) when blank, still running or an
55
+ * unknown command
56
+ */
57
+ submit: (value: string) => boolean;
58
+ abort: () => void | undefined;
59
+ setCopied: (chars: number) => void;
60
+ /** adds an error entry to the transcript */
61
+ report: (text: string) => void;
62
+ /** shows `session` in place of the current one: the transcript restarts from its messages, and runs go to it */
63
+ setSession: (session: Session) => void;
64
+ }>;
65
+ export type Cli = Awaited<ReturnType<typeof createCli>>;
66
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAY,MAAM,cAAc,CAAC;AAG9E,OAAO,EAAmB,KAAK,OAAO,EAA6B,MAAM,eAAe,CAAC;AACzF,OAAO,EAAE,KAAK,KAAK,EAAqB,MAAM,YAAY,CAAC;AAE3D,6EAA6E;AAC7E,MAAM,WAAW,OAAO;IACtB,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,KAAK,EAAE,CAAC;IACjB,6DAA6D;IAC7D,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,8EAA8E;IAC9E,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,sGAAsG;IACtG,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAenD;AAsBD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;IAsJ7F,0CAA0C;;IAI1C,oEAAoE;;IAEpE;;;OAGG;oBACa,MAAM;;uBAaH,MAAM;IACzB,4CAA4C;mBAC7B,MAAM;IACrB,gHAAgH;0BAC1F,OAAO;GAOhC;AAED,MAAM,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,235 @@
1
+ import { createStore } from "zustand/vanilla";
2
+ import { builtinCommands, findCommand, parseCommand } from "./commands.js";
3
+ import { entry, formatTool } from "./entry.js";
4
+ /** Transcript entries for messages already in the session, so a resumed one shows as it was left. */
5
+ export function replay(messages) {
6
+ const out = [];
7
+ const calls = new Map();
8
+ for (const m of messages) {
9
+ if (m.role === "user")
10
+ out.push(entry("user", `\n${m.content}\n`));
11
+ else if (m.role === "assistant") {
12
+ if (m.text)
13
+ out.push(entry("assistant", m.text));
14
+ for (const c of m.toolCalls)
15
+ calls.set(c.id, c);
16
+ }
17
+ else
18
+ for (const r of m.results) {
19
+ const c = calls.get(r.id);
20
+ if (c)
21
+ out.push(entry(r.isError ? "toolError" : "toolOk", formatTool(c, r)));
22
+ }
23
+ }
24
+ return out;
25
+ }
26
+ /** UI state for showing `session` from its start: its file and settings, then its messages so far. */
27
+ function show(session) {
28
+ const { messages, model, provider } = session.getState();
29
+ return {
30
+ session,
31
+ entries: [
32
+ entry("info", `Session: ${session.path} (${messages.length} messages)${provider ? ` · provider: ${provider}` : ""}${model ? ` · model: ${model}` : ""}`),
33
+ ...replay(messages),
34
+ ],
35
+ live: { thinking: "", text: "" },
36
+ turn: [],
37
+ copied: undefined,
38
+ };
39
+ }
40
+ const errorText = (e) => (e instanceof Error ? e.message : String(e));
41
+ /**
42
+ * The TUI's logic, without React: runs the agent on `session` or a `/name arg` command, turns its events into
43
+ * transcript entries and keeps `status` in step with the session's model and provider. `makeAgent` creates the agent
44
+ * with the run observer it's given.
45
+ */
46
+ export async function createCli(session, makeAgent) {
47
+ const ui = createStore()(() => ({ ...show(session), busy: false }));
48
+ const push = (...e) => ui.setState((s) => ({ entries: [...s.entries, ...e] }));
49
+ // Deltas reach `live` at most once a frame: every store change renders synchronously (useSyncExternalStore), and a
50
+ // fast stream of back-to-back sync renders trips React's nested-update limit while Ink's useBoxMetrics has updates
51
+ // pending.
52
+ let pending = { thinking: "", text: "" };
53
+ let timer;
54
+ const take = () => {
55
+ clearTimeout(timer);
56
+ timer = undefined;
57
+ const p = pending;
58
+ pending = { thinking: "", text: "" };
59
+ return p;
60
+ };
61
+ const stream = (key, delta) => {
62
+ pending[key] += delta;
63
+ timer ??= setTimeout(() => {
64
+ const p = take();
65
+ ui.setState(({ live }) => ({ live: { thinking: live.thinking + p.thinking, text: live.text + p.text } }));
66
+ }, 1000 / 30);
67
+ };
68
+ // streamed-but-unfinished thinking and text become entries, then the live view resets
69
+ const flush = () => {
70
+ const p = take();
71
+ ui.setState(({ entries, live }) => {
72
+ const thinking = live.thinking + p.thinking;
73
+ const text = live.text + p.text;
74
+ return {
75
+ entries: [
76
+ ...entries,
77
+ ...(thinking ? [entry("thinking", thinking)] : []),
78
+ ...(text ? [entry("assistant", text)] : []),
79
+ ],
80
+ live: { thinking: "", text: "" },
81
+ };
82
+ });
83
+ };
84
+ // pending tool blocks by call id, replaced by their result block when it arrives
85
+ const calls = new Map();
86
+ const agent = await makeAgent({
87
+ on: {
88
+ thinking: (delta) => stream("thinking", delta),
89
+ text: (delta) => stream("text", delta),
90
+ tool_call: (call) => {
91
+ const e = entry("tool", formatTool(call));
92
+ calls.set(call.id, { call, entry: e });
93
+ push(e);
94
+ },
95
+ message: (message) => {
96
+ if (message.role === "tool") {
97
+ const done = new Map();
98
+ for (const r of message.results) {
99
+ const c = calls.get(r.id);
100
+ if (!c)
101
+ continue;
102
+ done.set(c.entry, { ...c.entry, kind: r.isError ? "toolError" : "toolOk", text: formatTool(c.call, r) });
103
+ calls.delete(r.id);
104
+ }
105
+ ui.setState((s) => ({ entries: s.entries.map((e) => done.get(e) ?? e) }));
106
+ }
107
+ // an assistant message closes the streamed thinking and text
108
+ flush();
109
+ ui.setState((s) => ({ turn: [...s.turn, message] }));
110
+ },
111
+ },
112
+ });
113
+ // later plugins win, so a command can be replaced by name (like tools)
114
+ const commands = [
115
+ ...new Map([...builtinCommands, ...agent.plugins.flatMap((p) => p.cli?.commands ?? [])].map((c) => [
116
+ c.name,
117
+ c,
118
+ ])).values(),
119
+ ];
120
+ // re-resolved whenever the session, or its model or provider, changes
121
+ let seq = 0;
122
+ async function refreshStatus() {
123
+ const n = ++seq;
124
+ const { model, provider } = ui.getState().session.getState();
125
+ try {
126
+ const r = await agent.resolve({ model, provider });
127
+ // a newer change won; don't overwrite it with a stale result
128
+ if (n !== seq)
129
+ return;
130
+ ui.setState({ status: { name: r.name, model: r.model } });
131
+ const window = await r.provider.contextWindow?.(r.model).catch(() => undefined);
132
+ if (n === seq)
133
+ ui.setState({ status: { name: r.name, model: r.model, window } });
134
+ }
135
+ catch {
136
+ if (n === seq)
137
+ ui.setState({ status: undefined });
138
+ }
139
+ }
140
+ const watch = (session) => session.subscribe((s, prev) => {
141
+ if (s.model !== prev.model || s.provider !== prev.provider)
142
+ void refreshStatus();
143
+ });
144
+ let unwatch = watch(session);
145
+ ui.subscribe((s, prev) => {
146
+ if (s.session === prev.session)
147
+ return;
148
+ unwatch();
149
+ unwatch = watch(s.session);
150
+ void refreshStatus();
151
+ });
152
+ void refreshStatus();
153
+ let aborter;
154
+ async function run(value) {
155
+ const { session } = ui.getState();
156
+ const turn = [{ role: "user", content: value }];
157
+ // blank rows pad the message's background, as pi does
158
+ ui.setState((s) => ({ busy: true, turn, entries: [...s.entries, entry("user", `\n${value}\n`)] }));
159
+ const abort = new AbortController();
160
+ aborter = abort;
161
+ try {
162
+ const { messages, model, provider } = session.getState();
163
+ await agent.run([...messages, ...turn], { model, provider, signal: abort.signal }).catch((e) => {
164
+ if (!abort.signal.aborted)
165
+ throw e;
166
+ });
167
+ if (abort.signal.aborted) {
168
+ // the partial reply stays on screen but not in history; completed messages are kept
169
+ flush();
170
+ // error entries get no separator row, so the leading newline adds one; the next turn adds the one below
171
+ push(entry("error", "\nInterrupted"));
172
+ }
173
+ // the turn moves into session.messages in one tick, so the usage line never counts it twice
174
+ const saved = session.append(ui.getState().turn);
175
+ ui.setState({ turn: [] });
176
+ await saved;
177
+ }
178
+ catch (e) {
179
+ flush();
180
+ push(entry("error", errorText(e)));
181
+ }
182
+ ui.setState({ busy: false });
183
+ }
184
+ // not busy meanwhile, so a command can start a turn with `submit`
185
+ async function command(c, arg) {
186
+ try {
187
+ const text = await c.run(arg, cli);
188
+ if (text)
189
+ push(entry("info", text));
190
+ }
191
+ catch (e) {
192
+ push(entry("error", errorText(e)));
193
+ }
194
+ }
195
+ const cli = {
196
+ ui,
197
+ agent,
198
+ /** the session shown, `ui`'s `session` */
199
+ get session() {
200
+ return ui.getState().session;
201
+ },
202
+ /** the built-ins and every plugin's `cli.commands`, one per name */
203
+ commands,
204
+ /**
205
+ * starts `value` as a command or the next turn; false (the prompt keeps its input) when blank, still running or an
206
+ * unknown command
207
+ */
208
+ submit: (value) => {
209
+ if (!value.trim() || ui.getState().busy)
210
+ return false;
211
+ const cmd = parseCommand(value.trim());
212
+ const c = cmd && findCommand(commands, cmd.name);
213
+ if (cmd && !c) {
214
+ push(entry("error", `Unknown command: /${cmd.name}`));
215
+ return false;
216
+ }
217
+ ui.setState({ copied: undefined });
218
+ (c ? command(c, cmd?.arg ?? "") : run(value)).catch((e) => push(entry("error", errorText(e))));
219
+ return true;
220
+ },
221
+ abort: () => aborter?.abort(),
222
+ setCopied: (chars) => ui.setState({ copied: chars }),
223
+ /** adds an error entry to the transcript */
224
+ report: (text) => push(entry("error", text)),
225
+ /** shows `session` in place of the current one: the transcript restarts from its messages, and runs go to it */
226
+ setSession: (session) => {
227
+ // the running turn would end up split between the two
228
+ if (ui.getState().busy)
229
+ throw new Error("Can't switch sessions while a turn is running");
230
+ ui.setState(show(session));
231
+ },
232
+ };
233
+ return cli;
234
+ }
235
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type Key } from "ink";
2
+ import type { Cli } from "./cli.ts";
3
+ import { type Candidate, type Command } from "./commands.ts";
4
+ /** One row of the `/` menu, and the input once it's chosen. */
5
+ export interface Suggestion {
6
+ label: string;
7
+ /** dim text after the label */
8
+ description?: string;
9
+ value: string;
10
+ }
11
+ /**
12
+ * The `/` menu's rows for `input`: commands while the name is typed, then the command's candidates from `loaded` for
13
+ * its argument; undefined while those are still loading.
14
+ */
15
+ export declare function suggest(commands: Command[], input: string, loaded: Map<Command, Candidate[]>): Suggestion[] | undefined;
16
+ /**
17
+ * The `/` menu for the prompt's `input`. `onKey` takes the prompt's ↑/↓ (select), Tab (put the selected row into the
18
+ * input) and Enter (the same, unless the input already is it or nothing has loaded yet: then it submits) while the menu
19
+ * shows; `hide` is Esc.
20
+ */
21
+ export declare function useCommandMenu(cli: Cli, input: string, setInput: (value: string) => void): {
22
+ rows: Suggestion[] | undefined;
23
+ at: number;
24
+ shown: boolean;
25
+ hide: () => void;
26
+ onKey: (_: string, key: Key) => boolean;
27
+ };
28
+ /** `rows` under the prompt, at most five, scrolled to keep the selected (accent) one in view; undefined = loading. */
29
+ export declare function CommandMenu({ rows, at }: {
30
+ rows: Suggestion[] | undefined;
31
+ at: number;
32
+ }): import("react").JSX.Element;
33
+ //# sourceMappingURL=command-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-menu.d.ts","sourceRoot":"","sources":["../src/command-menu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,GAAG,EAAQ,MAAM,KAAK,CAAC;AAG1C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAA6B,MAAM,eAAe,CAAC;AAGxF,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAgBD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,4BAmC5F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;;;;;eAkDrE,MAAM,OAAO,GAAG;EAYnC;AAID,sHAAsH;AACtH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE;IAAE,IAAI,EAAE,UAAU,EAAE,GAAG,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,+BA2BvF"}