@sigrun/tui 0.1.0 → 0.2.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 (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +3 -3
  3. package/dist/actions.d.ts +0 -78
  4. package/dist/actions.js +0 -12
  5. package/dist/api.d.ts +0 -147
  6. package/dist/api.js +0 -185
  7. package/dist/clipboard.d.ts +0 -20
  8. package/dist/clipboard.js +0 -19
  9. package/dist/editor-launch.d.ts +0 -34
  10. package/dist/editor-launch.js +0 -70
  11. package/dist/keymaps/emacs.d.ts +0 -18
  12. package/dist/keymaps/emacs.js +0 -133
  13. package/dist/keymaps/standard.d.ts +0 -17
  14. package/dist/keymaps/standard.js +0 -131
  15. package/dist/keymaps/types.d.ts +0 -43
  16. package/dist/keymaps/types.js +0 -28
  17. package/dist/keymaps/vim.d.ts +0 -48
  18. package/dist/keymaps/vim.js +0 -254
  19. package/dist/launch.d.ts +0 -149
  20. package/dist/launch.js +0 -433
  21. package/dist/sse.d.ts +0 -101
  22. package/dist/sse.js +0 -374
  23. package/dist/theme.d.ts +0 -51
  24. package/dist/theme.js +0 -105
  25. package/dist/ui/ambient.d.ts +0 -81
  26. package/dist/ui/ambient.js +0 -301
  27. package/dist/ui/app.d.ts +0 -209
  28. package/dist/ui/app.js +0 -1241
  29. package/dist/ui/composer.d.ts +0 -66
  30. package/dist/ui/composer.js +0 -125
  31. package/dist/ui/conversation-pane.d.ts +0 -123
  32. package/dist/ui/conversation-pane.js +0 -463
  33. package/dist/ui/help.d.ts +0 -15
  34. package/dist/ui/help.js +0 -80
  35. package/dist/ui/interactions.d.ts +0 -90
  36. package/dist/ui/interactions.js +0 -335
  37. package/dist/ui/picker.d.ts +0 -26
  38. package/dist/ui/picker.js +0 -130
  39. package/dist/ui/sidebar.d.ts +0 -76
  40. package/dist/ui/sidebar.js +0 -205
  41. package/dist/ui/status-bar.d.ts +0 -77
  42. package/dist/ui/status-bar.js +0 -146
@@ -1,66 +0,0 @@
1
- /**
2
- * The composer's pure pieces (Task 7): slash-input parsing, the fetch-once
3
- * command catalog, and error-line formatting. The composer itself is NOT
4
- * its own component — it is the pi-tui `Editor` the shell owns plus the
5
- * wiring in `app.ts` (busy tracking, the dispatch); these are the parts
6
- * that carry no app state.
7
- */
8
- import type { SlashCommand, SigrunClient } from "../api.js";
9
- /** A parsed `/name args…` line. */
10
- export interface SlashInput {
11
- /** The bare command name (no slash), lowercased. */
12
- readonly name: string;
13
- /** Everything after the first space, trimmed ("" when absent). */
14
- readonly args: string;
15
- }
16
- /** Whether a command name is the skills channel (`/skill:name`, Task 2c):
17
- * expansion is server-side at admission, and an UNKNOWN skill passes
18
- * through unchanged — so a `skill:`-prefixed line is ALWAYS a submission,
19
- * never an endpoint command or a local error. (Checked on the LOWERCASED
20
- * parse; the line itself is submitted verbatim either way.) */
21
- export declare function isSkillCommandName(name: string): boolean;
22
- /** The text a command-picker selection inserts into the composer: an
23
- * EXPAND entry (Task 2c — a prompt template or a skill) inserts
24
- * `/name ` ALWAYS (args are its whole point; a skill carries no argsHint
25
- * but takes them), an arg-taking endpoint command inserts `/name ` for
26
- * its args, and an argless endpoint command inserts bare `/name` for
27
- * immediate execution. */
28
- export declare function commandInsertText(command: SlashCommand): string;
29
- /**
30
- * Parse a slash-prefixed composer line; `undefined` when the input is not
31
- * a command (only a LEADING slash routes — "look at /etc" stays a message).
32
- * A bare "/" parses to an empty name (the caller renders the empty-command
33
- * error).
34
- */
35
- export declare function parseSlashInput(input: string): SlashInput | undefined;
36
- /**
37
- * The server's slash-command catalog, cached per session. The server
38
- * re-scans its skill/template roots on every `GET /v1/commands` request —
39
- * the cache is a SESSION SNAPSHOT, not a boot constant — so a lookup
40
- * that misses ({@link SlashCatalog.get}) can re-check the live catalog
41
- * via {@link SlashCatalog.getFresh} before declaring a name unknown. A
42
- * failed fetch is NOT cached: the rejected promise is dropped so the
43
- * next `/` retries.
44
- */
45
- export declare class SlashCatalog {
46
- #private;
47
- constructor(client: SigrunClient);
48
- /** The catalog entry for `name` (bare, lowercased) from the CACHED
49
- * snapshot, or undefined. */
50
- get(name: string): Promise<SlashCommand | undefined>;
51
- /** The live-catalog re-check: refetch NOW (replacing the cache with the
52
- * fresh response) and look `name` up again — the one retry a dispatch
53
- * makes when the cached snapshot lacks a name (the server re-scans
54
- * per request, so a template added mid-session becomes submittable
55
- * without a restart). Throws through on a failed fetch, like `get`. */
56
- getFresh(name: string): Promise<SlashCommand | undefined>;
57
- /** Every catalog name (the vim `:` Tab completion's source, C4). */
58
- names(): Promise<string[]>;
59
- }
60
- /**
61
- * Format a failed composer action for the pane's error line. A
62
- * {@link SigrunApiError} renders the status plus the server's own
63
- * `{error: …}` body (Review Focus 2 — the server's words, never a generic
64
- * "something failed"); anything else renders its message.
65
- */
66
- export declare function describeCommandError(action: string, error: unknown): string;
@@ -1,125 +0,0 @@
1
- /**
2
- * The composer's pure pieces (Task 7): slash-input parsing, the fetch-once
3
- * command catalog, and error-line formatting. The composer itself is NOT
4
- * its own component — it is the pi-tui `Editor` the shell owns plus the
5
- * wiring in `app.ts` (busy tracking, the dispatch); these are the parts
6
- * that carry no app state.
7
- */
8
- import { SigrunApiError } from "../api.js";
9
- /** Whether a command name is the skills channel (`/skill:name`, Task 2c):
10
- * expansion is server-side at admission, and an UNKNOWN skill passes
11
- * through unchanged — so a `skill:`-prefixed line is ALWAYS a submission,
12
- * never an endpoint command or a local error. (Checked on the LOWERCASED
13
- * parse; the line itself is submitted verbatim either way.) */
14
- export function isSkillCommandName(name) {
15
- return name.startsWith("skill:");
16
- }
17
- /** The text a command-picker selection inserts into the composer: an
18
- * EXPAND entry (Task 2c — a prompt template or a skill) inserts
19
- * `/name ` ALWAYS (args are its whole point; a skill carries no argsHint
20
- * but takes them), an arg-taking endpoint command inserts `/name ` for
21
- * its args, and an argless endpoint command inserts bare `/name` for
22
- * immediate execution. */
23
- export function commandInsertText(command) {
24
- return command.kind === "expand" || command.argsHint !== undefined
25
- ? `/${command.name} `
26
- : `/${command.name}`;
27
- }
28
- /**
29
- * Parse a slash-prefixed composer line; `undefined` when the input is not
30
- * a command (only a LEADING slash routes — "look at /etc" stays a message).
31
- * A bare "/" parses to an empty name (the caller renders the empty-command
32
- * error).
33
- */
34
- export function parseSlashInput(input) {
35
- if (!input.startsWith("/"))
36
- return undefined;
37
- const space = input.indexOf(" ");
38
- if (space === -1)
39
- return { name: input.slice(1).toLowerCase(), args: "" };
40
- return {
41
- name: input.slice(1, space).toLowerCase(),
42
- args: input.slice(space + 1).trim(),
43
- };
44
- }
45
- /**
46
- * The server's slash-command catalog, cached per session. The server
47
- * re-scans its skill/template roots on every `GET /v1/commands` request —
48
- * the cache is a SESSION SNAPSHOT, not a boot constant — so a lookup
49
- * that misses ({@link SlashCatalog.get}) can re-check the live catalog
50
- * via {@link SlashCatalog.getFresh} before declaring a name unknown. A
51
- * failed fetch is NOT cached: the rejected promise is dropped so the
52
- * next `/` retries.
53
- */
54
- export class SlashCatalog {
55
- #client;
56
- #cache;
57
- constructor(client) {
58
- this.#client = client;
59
- }
60
- /** The catalog entry for `name` (bare, lowercased) from the CACHED
61
- * snapshot, or undefined. */
62
- async get(name) {
63
- try {
64
- this.#cache ??= this.#client.getCommands();
65
- const response = await this.#cache;
66
- return response.commands.find((command) => command.name === name);
67
- }
68
- catch (error) {
69
- this.#cache = undefined;
70
- throw error;
71
- }
72
- }
73
- /** The live-catalog re-check: refetch NOW (replacing the cache with the
74
- * fresh response) and look `name` up again — the one retry a dispatch
75
- * makes when the cached snapshot lacks a name (the server re-scans
76
- * per request, so a template added mid-session becomes submittable
77
- * without a restart). Throws through on a failed fetch, like `get`. */
78
- async getFresh(name) {
79
- this.#cache = undefined;
80
- return await this.get(name);
81
- }
82
- /** Every catalog name (the vim `:` Tab completion's source, C4). */
83
- async names() {
84
- try {
85
- this.#cache ??= this.#client.getCommands();
86
- return (await this.#cache).commands.map((command) => command.name);
87
- }
88
- catch (error) {
89
- this.#cache = undefined;
90
- throw error;
91
- }
92
- }
93
- }
94
- /**
95
- * Format a failed composer action for the pane's error line. A
96
- * {@link SigrunApiError} renders the status plus the server's own
97
- * `{error: …}` body (Review Focus 2 — the server's words, never a generic
98
- * "something failed"); anything else renders its message.
99
- */
100
- export function describeCommandError(action, error) {
101
- if (error instanceof SigrunApiError) {
102
- const body = error.body;
103
- const detail = typeof body === "object" &&
104
- body !== null &&
105
- "error" in body &&
106
- typeof body.error === "string"
107
- ? body.error
108
- : undefined;
109
- // A failed extension command/run names its contributor (W5 Task 3's
110
- // `command_failed` + `extension` body) — the slug is what the operator
111
- // needs next, so it rides the same line.
112
- const extension = typeof body === "object" &&
113
- body !== null &&
114
- "extension" in body &&
115
- typeof body.extension === "string"
116
- ? body.extension
117
- : undefined;
118
- const suffix = extension === undefined ? "" : ` (${extension})`;
119
- return detail === undefined
120
- ? `${action} failed (${error.status})`
121
- : `${action} failed (${error.status}): ${detail}${suffix}`;
122
- }
123
- const message = error instanceof Error ? error.message : String(error);
124
- return `${action} failed: ${message}`;
125
- }
@@ -1,123 +0,0 @@
1
- /**
2
- * The conversation pane (Task 6; restyled in C2, UI refresh): renders a
3
- * `ConversationViewWire`'s entries as TURNS (B1's `groupTurns`) instead
4
- * of raw entries — a user turn is the accent `▎` bar on every line, an
5
- * assistant turn is prose plus ONE collapsible activity line
6
- * (`▸ Thinking · 2 tools`; `▾` when open) that unfolds into the thinking
7
- * text and `✓ tool preview` rows (`✗` error, `·` no result yet — the
8
- * wire carries no per-tool running marker, so `·` covers pending too).
9
- * `pi.compaction` renders the centred `── context compacted ──` divider. The `[tool]`/`[thinking]` tags of
10
- * the entry-era pane are gone.
11
- *
12
- * Task 2c: an EXPANDED input (`/skill:name`, a prompt template) is one
13
- * entry (`sigrun.user.expanded`, `data {typed, command}`, the expanded
14
- * text on `model`) that groups as a USER turn — the body is the TYPED
15
- * command with a dim `▸ expanded from /name` chip line that folds the
16
- * full admitted text open/closed (closed by default).
17
- *
18
- * Focus (the C3 action layer's seam): `focusIndex` names the focused
19
- * turn (`undefined` = none — the ScrollView follows the end), the
20
- * focused turn's FIRST line carries the `surface2` background and a `›`
21
- * gutter marker; `lineRangeOf` reports the turn's line offsets from the
22
- * last render for `ScrollView.scrollTo`; `textOf`/`entryIdOf` are the
23
- * copy payload and wire identity of a turn.
24
- *
25
- * Mouse (additive — the pane is fully keyboard-usable): each activity
26
- * line is a `MouseRegion` whose left click toggles the fold; a click on
27
- * a turn's body sets the focus to that turn.
28
- *
29
- * The SSE contract (Task 4): every snapshot/frame is a FULL revision, so
30
- * {@link showView} REPLACES the rendered state wholesale. Folds and
31
- * focus are keyed by turn INDEX within the latest view — a replacement
32
- * re-derives the turns and keeps the index-based state (the action
33
- * layer owns re-targeting when the structure shifts).
34
- *
35
- * Unknown entry kinds render a fail-visible fallback naming the kind
36
- * (the webui's rule — a new server-side kind must never vanish silently).
37
- *
38
- * Error/notice lines (Task 7): the composer surfaces its immediate
39
- * failures as `[error]`/`[notice]` rows. They are NOT stream state, so
40
- * {@link showView}'s wholesale replacement preserves them (fix wave F4:
41
- * the shell discards them on every conversation swap via
42
- * {@link ConversationPane.clearNotices}).
43
- */
44
- import { type Component } from "@earendil-works/pi-tui";
45
- import { type Turn } from "@sigrun/shared/turns";
46
- import { type TuiTheme } from "../theme.js";
47
- import type { ConversationViewWire } from "../api.js";
48
- /** One §5 message's structural shape (B1's `TurnMessage` + content parts). */
49
- interface MessageShape {
50
- readonly role: string;
51
- readonly content: string | readonly {
52
- readonly type: string;
53
- readonly text?: string;
54
- readonly thinking?: string;
55
- readonly path?: string;
56
- readonly id?: string;
57
- readonly name?: string;
58
- readonly arguments?: unknown;
59
- }[];
60
- readonly toolCallId?: string;
61
- readonly toolName?: string;
62
- readonly isError?: boolean;
63
- }
64
- /** One §5 entry's structural shape (kind + optional model messages). */
65
- export interface EntryShape {
66
- readonly id: number;
67
- readonly kind: string;
68
- readonly model?: readonly MessageShape[];
69
- /** The entry's kind-specific JSON payload — an expanded input's
70
- * `{typed, command}` (see `expandedUserEntry`). */
71
- readonly data?: unknown;
72
- }
73
- export interface ConversationPaneOptions {
74
- /** The token theme; defaults to the plain (identity) fallback. */
75
- readonly theme?: TuiTheme;
76
- }
77
- export declare class ConversationPane implements Component {
78
- #private;
79
- constructor(options?: ConversationPaneOptions);
80
- /** The current view's turns (B1 grouping), in render order. */
81
- turns(): readonly Turn<EntryShape>[];
82
- /** The focused turn index; `undefined` = none (follow the end). */
83
- get focusIndex(): number | undefined;
84
- /** Focus a turn (or clear with `undefined`); repaints the marker. */
85
- setFocus(index: number | undefined): void;
86
- /** Open/close an assistant turn's activity fold — or an expanded user
87
- * turn's chip disclosure (Task 2c: the full admitted text hides behind
88
- * the `expanded from /name` line, same fold state, same seam). */
89
- toggleFold(index: number): void;
90
- /**
91
- * The turn's line offsets from the last render (for
92
- * `ScrollView.scrollTo`): `start` is the turn's first line, `end` the
93
- * first line after it. Undefined before a render or out of range.
94
- */
95
- lineRangeOf(index: number): {
96
- start: number;
97
- end: number;
98
- } | undefined;
99
- /** The turn's markdown source (the copy payload). */
100
- textOf(index: number): string;
101
- /** The turn's wire identity: the user entry id / assistant lastEntryId. */
102
- entryIdOf(index: number): number;
103
- /**
104
- * Replace the pane with the projection of `wire` (the SSE rule: a full
105
- * revision replaces; a reconnect's snapshot subsumes missed frames).
106
- * Composer notices are not stream state: they re-render after the
107
- * turns.
108
- */
109
- showView(wire: ConversationViewWire): void;
110
- /** Append an `[error]` line (a failed composer action's surface). */
111
- showError(text: string): void;
112
- /** Append a `[notice]` line (an outcome, e.g. an abort that settled). */
113
- showNotice(text: string): void;
114
- /**
115
- * Discard all composer-surfaced lines (fix wave F4): the shell calls
116
- * this on every conversation swap so a stale error never renders in
117
- * the new conversation.
118
- */
119
- clearNotices(): void;
120
- render(width: number): string[];
121
- invalidate(): void;
122
- }
123
- export {};