@aragon-agent/cli 0.4.3

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 (156) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +558 -0
  3. package/dist/agent/coalesce.d.ts +14 -0
  4. package/dist/agent/coalesce.js +41 -0
  5. package/dist/agent/controller.d.ts +132 -0
  6. package/dist/agent/controller.js +332 -0
  7. package/dist/agent/headless.d.ts +22 -0
  8. package/dist/agent/headless.js +74 -0
  9. package/dist/agent/reducer.d.ts +181 -0
  10. package/dist/agent/reducer.js +420 -0
  11. package/dist/agent/system-prompt.d.ts +30 -0
  12. package/dist/agent/system-prompt.js +58 -0
  13. package/dist/agent/usage.d.ts +13 -0
  14. package/dist/agent/usage.js +41 -0
  15. package/dist/cli.d.ts +11 -0
  16. package/dist/cli.js +482 -0
  17. package/dist/commands/builtins.d.ts +16 -0
  18. package/dist/commands/builtins.js +223 -0
  19. package/dist/commands/registry.d.ts +48 -0
  20. package/dist/commands/registry.js +45 -0
  21. package/dist/commands/skills.d.ts +22 -0
  22. package/dist/commands/skills.js +625 -0
  23. package/dist/config/app-paths.d.ts +11 -0
  24. package/dist/config/app-paths.js +16 -0
  25. package/dist/config/env.d.ts +28 -0
  26. package/dist/config/env.js +92 -0
  27. package/dist/config/load.d.ts +48 -0
  28. package/dist/config/load.js +158 -0
  29. package/dist/config/migrate-legacy-state.d.ts +78 -0
  30. package/dist/config/migrate-legacy-state.js +181 -0
  31. package/dist/config/schema.d.ts +249 -0
  32. package/dist/config/schema.js +247 -0
  33. package/dist/config/store.d.ts +44 -0
  34. package/dist/config/store.js +126 -0
  35. package/dist/input/keymap.d.ts +20 -0
  36. package/dist/input/keymap.js +85 -0
  37. package/dist/session/persist.d.ts +24 -0
  38. package/dist/session/persist.js +51 -0
  39. package/dist/skills/archive.d.ts +41 -0
  40. package/dist/skills/archive.js +152 -0
  41. package/dist/skills/cli-commands.d.ts +52 -0
  42. package/dist/skills/cli-commands.js +502 -0
  43. package/dist/skills/fetch-source.d.ts +104 -0
  44. package/dist/skills/fetch-source.js +448 -0
  45. package/dist/skills/installer.d.ts +117 -0
  46. package/dist/skills/installer.js +414 -0
  47. package/dist/skills/lock.d.ts +75 -0
  48. package/dist/skills/lock.js +223 -0
  49. package/dist/skills/manifest.d.ts +47 -0
  50. package/dist/skills/manifest.js +95 -0
  51. package/dist/skills/node-host.d.ts +23 -0
  52. package/dist/skills/node-host.js +59 -0
  53. package/dist/skills/paths.d.ts +61 -0
  54. package/dist/skills/paths.js +107 -0
  55. package/dist/skills/service.d.ts +256 -0
  56. package/dist/skills/service.js +730 -0
  57. package/dist/skills/tools.d.ts +35 -0
  58. package/dist/skills/tools.js +164 -0
  59. package/dist/skills/updater.d.ts +101 -0
  60. package/dist/skills/updater.js +330 -0
  61. package/dist/skills/usage.d.ts +72 -0
  62. package/dist/skills/usage.js +216 -0
  63. package/dist/tools/bash-tool.d.ts +12 -0
  64. package/dist/tools/bash-tool.js +131 -0
  65. package/dist/tools/diff.d.ts +15 -0
  66. package/dist/tools/diff.js +50 -0
  67. package/dist/tools/fs-tools.d.ts +16 -0
  68. package/dist/tools/fs-tools.js +215 -0
  69. package/dist/tools/index.d.ts +93 -0
  70. package/dist/tools/index.js +157 -0
  71. package/dist/tools/search-tools.d.ts +11 -0
  72. package/dist/tools/search-tools.js +225 -0
  73. package/dist/ui/App.d.ts +24 -0
  74. package/dist/ui/App.js +522 -0
  75. package/dist/ui/AutocompletePopup.d.ts +21 -0
  76. package/dist/ui/AutocompletePopup.js +19 -0
  77. package/dist/ui/Composer.d.ts +40 -0
  78. package/dist/ui/Composer.js +61 -0
  79. package/dist/ui/Header.d.ts +30 -0
  80. package/dist/ui/Header.js +25 -0
  81. package/dist/ui/Logo.d.ts +50 -0
  82. package/dist/ui/Logo.js +85 -0
  83. package/dist/ui/Markdown.d.ts +23 -0
  84. package/dist/ui/Markdown.js +122 -0
  85. package/dist/ui/PromptInput.d.ts +81 -0
  86. package/dist/ui/PromptInput.js +383 -0
  87. package/dist/ui/SessionOpener.d.ts +30 -0
  88. package/dist/ui/SessionOpener.js +12 -0
  89. package/dist/ui/StatusBar.d.ts +39 -0
  90. package/dist/ui/StatusBar.js +43 -0
  91. package/dist/ui/ToastStack.d.ts +24 -0
  92. package/dist/ui/ToastStack.js +38 -0
  93. package/dist/ui/Transcript.d.ts +62 -0
  94. package/dist/ui/Transcript.js +128 -0
  95. package/dist/ui/capabilities.d.ts +25 -0
  96. package/dist/ui/capabilities.js +59 -0
  97. package/dist/ui/console-bridge.d.ts +19 -0
  98. package/dist/ui/console-bridge.js +52 -0
  99. package/dist/ui/density.d.ts +26 -0
  100. package/dist/ui/density.js +34 -0
  101. package/dist/ui/entries/AssistantEntry.d.ts +23 -0
  102. package/dist/ui/entries/AssistantEntry.js +13 -0
  103. package/dist/ui/entries/EntryFrame.d.ts +23 -0
  104. package/dist/ui/entries/EntryFrame.js +6 -0
  105. package/dist/ui/entries/ToolCard.d.ts +38 -0
  106. package/dist/ui/entries/ToolCard.js +72 -0
  107. package/dist/ui/entries/ToolPreview.d.ts +57 -0
  108. package/dist/ui/entries/ToolPreview.js +144 -0
  109. package/dist/ui/entries/UserEntry.d.ts +14 -0
  110. package/dist/ui/entries/UserEntry.js +5 -0
  111. package/dist/ui/exit-snapshot.d.ts +27 -0
  112. package/dist/ui/exit-snapshot.js +26 -0
  113. package/dist/ui/gauge.d.ts +24 -0
  114. package/dist/ui/gauge.js +36 -0
  115. package/dist/ui/glyphs.d.ts +87 -0
  116. package/dist/ui/glyphs.js +127 -0
  117. package/dist/ui/gradient.d.ts +11 -0
  118. package/dist/ui/gradient.js +50 -0
  119. package/dist/ui/layout/AppShell.d.ts +24 -0
  120. package/dist/ui/layout/AppShell.js +9 -0
  121. package/dist/ui/layout/Gutter.d.ts +67 -0
  122. package/dist/ui/layout/Gutter.js +42 -0
  123. package/dist/ui/layout/OverlayFrame.d.ts +62 -0
  124. package/dist/ui/layout/OverlayFrame.js +92 -0
  125. package/dist/ui/layout/ScrollViewport.d.ts +33 -0
  126. package/dist/ui/layout/ScrollViewport.js +71 -0
  127. package/dist/ui/layout/budget.d.ts +42 -0
  128. package/dist/ui/layout/budget.js +47 -0
  129. package/dist/ui/layout/frame.d.ts +58 -0
  130. package/dist/ui/layout/frame.js +75 -0
  131. package/dist/ui/layout/overlay-window.d.ts +43 -0
  132. package/dist/ui/layout/overlay-window.js +51 -0
  133. package/dist/ui/layout/scroll.d.ts +28 -0
  134. package/dist/ui/layout/scroll.js +76 -0
  135. package/dist/ui/layout/useTerminalSize.d.ts +13 -0
  136. package/dist/ui/layout/useTerminalSize.js +50 -0
  137. package/dist/ui/markdown-blocks.d.ts +62 -0
  138. package/dist/ui/markdown-blocks.js +120 -0
  139. package/dist/ui/overlays/ConfirmDialog.d.ts +30 -0
  140. package/dist/ui/overlays/ConfirmDialog.js +25 -0
  141. package/dist/ui/overlays/HelpOverlay.d.ts +17 -0
  142. package/dist/ui/overlays/HelpOverlay.js +60 -0
  143. package/dist/ui/overlays/ModelPicker.d.ts +29 -0
  144. package/dist/ui/overlays/ModelPicker.js +29 -0
  145. package/dist/ui/overlays/SettingsScreen.d.ts +37 -0
  146. package/dist/ui/overlays/SettingsScreen.js +106 -0
  147. package/dist/ui/palettes.d.ts +79 -0
  148. package/dist/ui/palettes.js +115 -0
  149. package/dist/ui/screen.d.ts +27 -0
  150. package/dist/ui/screen.js +57 -0
  151. package/dist/ui/theme.d.ts +78 -0
  152. package/dist/ui/theme.js +127 -0
  153. package/dist/ui/transcript-text.d.ts +27 -0
  154. package/dist/ui/transcript-text.js +110 -0
  155. package/package.json +73 -0
  156. package/skills/skill-creator/SKILL.md +96 -0
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Header (spec §4.2) — the always-present brand bar.
3
+ *
4
+ * In full-screen this is EXACTLY ONE ROW at every size, which is what makes the
5
+ * viewport height monotonic in `rows` and the first submit jump-free. The 6-row
6
+ * wordmark it used to host now lives in `SessionOpener`, inside the viewport.
7
+ *
8
+ * `banner` survives for the inline path only (`App.tsx` picks it with a local
9
+ * ternary and never calls `pickHeaderVariant`), so inline keeps its v0.3.0
10
+ * geometry. `art` is no longer reachable from here.
11
+ *
12
+ * Every line is `wrap="truncate"`: a long cwd silently costing a second row
13
+ * would push the bottom chrome off-frame and break R2.
14
+ */
15
+ import React from 'react';
16
+ import type { Theme } from './theme.js';
17
+ import type { TermCapabilities } from './capabilities.js';
18
+ import { type HeaderVariant } from './Logo.js';
19
+ interface HeaderProps {
20
+ version: string;
21
+ cwd: string;
22
+ provider: string;
23
+ model: string;
24
+ hasKey: boolean;
25
+ variant: HeaderVariant;
26
+ theme: Theme;
27
+ caps: TermCapabilities;
28
+ }
29
+ export declare function Header({ version, cwd, provider, model, hasKey, variant, theme, caps, }: HeaderProps): React.ReactElement;
30
+ export {};
@@ -0,0 +1,25 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { homedir } from 'node:os';
3
+ import { basename } from 'node:path';
4
+ import { Box, Text } from 'ink';
5
+ import { Logo } from './Logo.js';
6
+ /** Collapse the home directory to `~` for a shorter path display. */
7
+ function tildeCwd(cwd) {
8
+ const home = homedir();
9
+ if (home && cwd.startsWith(home))
10
+ return `~${cwd.slice(home.length)}`;
11
+ return cwd;
12
+ }
13
+ function KeyDot({ hasKey, theme }) {
14
+ return (_jsxs(Text, { color: hasKey ? theme.toolDone : theme.toolRunning, children: [hasKey ? theme.symbols.keyOn : theme.symbols.keyOff, " ", hasKey ? 'key set' : 'no key'] }));
15
+ }
16
+ export function Header({ version, cwd, provider, model, hasKey, variant, theme, caps, }) {
17
+ if (variant === 'mini') {
18
+ return (_jsx(Box, { flexDirection: "column", flexShrink: 0, children: _jsx(Text, { wrap: "truncate", children: _jsx(Logo, { variant: "mini", theme: theme, caps: caps }) }) }));
19
+ }
20
+ if (variant === 'bar') {
21
+ return (_jsx(Box, { flexDirection: "column", flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Logo, { variant: "bar", theme: theme, caps: caps }), _jsxs(Text, { color: theme.accent, children: [' ', provider, ":", model] }), _jsxs(Text, { color: theme.muted, children: [' ', basename(cwd) || cwd] }), _jsxs(Text, { color: hasKey ? theme.toolDone : theme.toolRunning, children: [' ', hasKey ? theme.symbols.keyOn : theme.symbols.keyOff] })] }) }));
22
+ }
23
+ // `banner` (inline only): wordmark row + meta row.
24
+ return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(Logo, { variant: variant, theme: theme, caps: caps }), _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: theme.muted, children: ["v", version] }), _jsxs(Text, { color: theme.accent, children: [' ', provider, ":", model] }), _jsx(Text, { children: ' ' }), _jsx(KeyDot, { hasKey: hasKey, theme: theme }), _jsxs(Text, { color: theme.muted, children: [' ', tildeCwd(cwd)] })] })] }));
25
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Brand wordmark (spec §5.1 / R1). The first row of the frame always starts
3
+ * with a brand glyph, at every terminal size and every capability tier.
4
+ */
5
+ import React from 'react';
6
+ import type { Theme } from './theme.js';
7
+ import type { TermCapabilities } from './capabilities.js';
8
+ /** 6 rows × 52 columns, every row equal width (pinned by logo.test.ts). */
9
+ export declare const LOGO_ART: readonly string[];
10
+ export declare const LOGO_ART_WIDTH = 52;
11
+ export type HeaderVariant = 'art' | 'banner' | 'bar' | 'mini';
12
+ /** How the one-off opening wordmark renders inside the viewport (§4.2). */
13
+ export type OpenerVariant = 'art' | 'banner' | 'none';
14
+ /**
15
+ * Pick the header tier (spec §4.2).
16
+ *
17
+ * `cols` is the only input, and that is the point. The old signature also took
18
+ * `empty` and `rows` and could return `art`, so the brand region was 8 rows tall
19
+ * on an empty session and 1 row afterwards: sending the first message made the
20
+ * viewport jump by 7 rows, and widening a 27-row terminal to 28 rows made the
21
+ * visible content SHRINK. A constant-height brand bar removes both, which is
22
+ * what lets `budget.ts::viewportRows` be monotonic (A-3/A-4/A-9).
23
+ *
24
+ * The wordmark did not disappear — it moved into the viewport as
25
+ * `SessionOpener`, where it scrolls away like any other content.
26
+ *
27
+ * NOTE: `Header` still renders `banner`; the inline path picks it directly at
28
+ * `App.tsx` and does not call this function.
29
+ */
30
+ export declare function pickHeaderVariant(cols: number): HeaderVariant;
31
+ /**
32
+ * Pick the opening wordmark tier.
33
+ *
34
+ * Judged against VIEWPORT rows rather than terminal rows because the wordmark
35
+ * now spends the viewport's budget, not the frame's: the test has to be against
36
+ * the account being charged.
37
+ *
38
+ * No hand-drawn ASCII fallback for `art`: figlet-style art is hard to keep
39
+ * column-aligned, and a terminal without Unicode should not be paying six rows
40
+ * for a wordmark in the first place. `banner` still carries the brand there, and
41
+ * the always-present header bar carries it even at `none` (R1).
42
+ */
43
+ export declare function pickOpenerVariant(viewportRows: number, cols: number, caps: TermCapabilities): OpenerVariant;
44
+ export interface LogoProps {
45
+ /** `art` / `banner` come from `SessionOpener`; `bar` / `mini` from `Header`. */
46
+ variant: HeaderVariant | OpenerVariant;
47
+ theme: Theme;
48
+ caps: TermCapabilities;
49
+ }
50
+ export declare function Logo({ variant, theme, caps }: LogoProps): React.ReactElement | null;
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import { gradientLine } from './gradient.js';
4
+ /** 6 rows × 52 columns, every row equal width (pinned by logo.test.ts). */
5
+ export const LOGO_ART = [
6
+ ' █████╗ ██████╗ █████╗ ██████╗ ██████╗ ███╗ ██╗',
7
+ '██╔══██╗██╔══██╗██╔══██╗██╔════╝ ██╔═══██╗████╗ ██║',
8
+ '███████║██████╔╝███████║██║ ███╗██║ ██║██╔██╗ ██║',
9
+ '██╔══██║██╔══██╗██╔══██║██║ ██║██║ ██║██║╚██╗██║',
10
+ '██║ ██║██║ ██║██║ ██║╚██████╔╝╚██████╔╝██║ ╚████║',
11
+ '╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝',
12
+ ];
13
+ export const LOGO_ART_WIDTH = 52;
14
+ /** Minimum viewport rows / cols each tier needs before it earns its budget. */
15
+ const ART_MIN_VIEWPORT_ROWS = 14;
16
+ /**
17
+ * 4 columns of headroom on purpose: the art renders with `wrap="truncate"`, so
18
+ * at zero headroom anything upstream claiming a single column silently shears
19
+ * the right edge off the `N`. Between `LOGO_ART_WIDTH` and this threshold the
20
+ * opener degrades to `banner`, which is the intended fallback (rename C2).
21
+ */
22
+ const ART_MIN_COLS = LOGO_ART_WIDTH + 4;
23
+ const BANNER_MIN_COLS = 48;
24
+ const BAR_MIN_COLS = 40;
25
+ /**
26
+ * Pick the header tier (spec §4.2).
27
+ *
28
+ * `cols` is the only input, and that is the point. The old signature also took
29
+ * `empty` and `rows` and could return `art`, so the brand region was 8 rows tall
30
+ * on an empty session and 1 row afterwards: sending the first message made the
31
+ * viewport jump by 7 rows, and widening a 27-row terminal to 28 rows made the
32
+ * visible content SHRINK. A constant-height brand bar removes both, which is
33
+ * what lets `budget.ts::viewportRows` be monotonic (A-3/A-4/A-9).
34
+ *
35
+ * The wordmark did not disappear — it moved into the viewport as
36
+ * `SessionOpener`, where it scrolls away like any other content.
37
+ *
38
+ * NOTE: `Header` still renders `banner`; the inline path picks it directly at
39
+ * `App.tsx` and does not call this function.
40
+ */
41
+ export function pickHeaderVariant(cols) {
42
+ return cols >= BAR_MIN_COLS ? 'bar' : 'mini';
43
+ }
44
+ /**
45
+ * Pick the opening wordmark tier.
46
+ *
47
+ * Judged against VIEWPORT rows rather than terminal rows because the wordmark
48
+ * now spends the viewport's budget, not the frame's: the test has to be against
49
+ * the account being charged.
50
+ *
51
+ * No hand-drawn ASCII fallback for `art`: figlet-style art is hard to keep
52
+ * column-aligned, and a terminal without Unicode should not be paying six rows
53
+ * for a wordmark in the first place. `banner` still carries the brand there, and
54
+ * the always-present header bar carries it even at `none` (R1).
55
+ */
56
+ export function pickOpenerVariant(viewportRows, cols, caps) {
57
+ if (viewportRows >= ART_MIN_VIEWPORT_ROWS &&
58
+ cols >= ART_MIN_COLS &&
59
+ caps.unicode &&
60
+ caps.colorLevel >= 2) {
61
+ return 'art';
62
+ }
63
+ if (cols >= BANNER_MIN_COLS)
64
+ return 'banner';
65
+ return 'none';
66
+ }
67
+ /** Rotate the gradient stops by one per row so the block reads as a diagonal sheen. */
68
+ function shiftStops(stops, row) {
69
+ if (stops.length === 0)
70
+ return stops;
71
+ const at = row % stops.length;
72
+ return [...stops.slice(at), ...stops.slice(0, at)];
73
+ }
74
+ export function Logo({ variant, theme, caps }) {
75
+ const wordmark = `${theme.symbols.wordmark} AragonAgent`;
76
+ if (variant === 'none')
77
+ return null;
78
+ if (variant === 'art') {
79
+ return (_jsx(_Fragment, { children: LOGO_ART.map((line, i) => (_jsx(Text, { wrap: "truncate", color: theme.gradient.length > 0 ? undefined : theme.logoShadow, children: gradientLine(line, shiftStops(theme.gradient, i), caps.colorLevel) }, i))) }));
80
+ }
81
+ if (variant === 'banner') {
82
+ return (_jsx(Text, { wrap: "truncate", bold: true, color: theme.gradient.length > 0 ? undefined : theme.primary, children: gradientLine(wordmark, theme.gradient, caps.colorLevel) }));
83
+ }
84
+ return (_jsx(Text, { bold: true, color: theme.primary, children: wordmark }));
85
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Lightweight markdown renderer for Ink (spec §4 / §4.7).
3
+ *
4
+ * Supports: fenced code blocks (syntax-highlighted via cli-highlight, on a left
5
+ * rail with a language tag), six DISTINCT heading weights, horizontal rules,
6
+ * unordered/ordered lists, blockquotes, pipe tables, and inline **bold** /
7
+ * *italic* / `code` / ~~strike~~. Anything else renders as plain text. Still
8
+ * intentionally small — not a CommonMark implementation.
9
+ *
10
+ * The grammar lives in `markdown-blocks.ts`; this file only renders.
11
+ */
12
+ import React from 'react';
13
+ import type { Theme } from './theme.js';
14
+ import type { TermCapabilities } from './capabilities.js';
15
+ interface MarkdownProps {
16
+ text: string;
17
+ theme: Theme;
18
+ caps: TermCapabilities;
19
+ }
20
+ export declare function Markdown({ text, theme, caps }: MarkdownProps): React.ReactElement;
21
+ /** Turn parsed inline spans into Ink `<Text>` nodes. */
22
+ export declare function renderInline(text: string, theme: Theme): React.ReactNode;
23
+ export {};
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { highlight } from 'cli-highlight';
4
+ import { pickGlyphs } from './glyphs.js';
5
+ import { railBorderProps } from './layout/Gutter.js';
6
+ import { formatTable, isRule, isTableDivider, parseHeading, parseInline, parseTableRow, tableAlignments, } from './markdown-blocks.js';
7
+ const FENCE = /^```(\w*)\s*$/;
8
+ /** Width of the `---` rule. Fixed so it cannot force a wrap on narrow frames. */
9
+ const RULE_WIDTH = 24;
10
+ export function Markdown({ text, theme, caps }) {
11
+ const glyphs = pickGlyphs(caps);
12
+ const lines = text.split('\n');
13
+ const blocks = [];
14
+ let i = 0;
15
+ let key = 0;
16
+ while (i < lines.length) {
17
+ const line = lines[i] ?? '';
18
+ const fence = FENCE.exec(line.trim());
19
+ if (fence) {
20
+ const lang = fence[1] || '';
21
+ const codeLines = [];
22
+ i += 1;
23
+ while (i < lines.length && !FENCE.test((lines[i] ?? '').trim())) {
24
+ codeLines.push(lines[i] ?? '');
25
+ i += 1;
26
+ }
27
+ i += 1; // skip closing fence
28
+ blocks.push(_jsx(CodeBlock, { code: codeLines.join('\n'), lang: lang, theme: theme, glyphs: glyphs }, `b${key++}`));
29
+ continue;
30
+ }
31
+ // A pipe row followed by a divider row starts a table; consume the run.
32
+ const headerCells = parseTableRow(line);
33
+ if (headerCells && i + 1 < lines.length && isTableDivider(lines[i + 1] ?? '')) {
34
+ const align = tableAlignments(lines[i + 1] ?? '');
35
+ const rows = [headerCells];
36
+ i += 2;
37
+ while (i < lines.length) {
38
+ const cells = parseTableRow(lines[i] ?? '');
39
+ if (!cells)
40
+ break;
41
+ rows.push(cells);
42
+ i += 1;
43
+ }
44
+ const formatted = formatTable(rows, align);
45
+ blocks.push(_jsx(Box, { flexDirection: "column", children: formatted.map((row, r) => (_jsx(Text, { wrap: "truncate", bold: r === 0, color: r === 0 ? theme.accent : undefined, children: row }, r))) }, `b${key++}`));
46
+ continue;
47
+ }
48
+ blocks.push(_jsx(InlineLine, { line: line, theme: theme, glyphs: glyphs }, `b${key++}`));
49
+ i += 1;
50
+ }
51
+ return _jsx(Box, { flexDirection: "column", children: blocks });
52
+ }
53
+ /**
54
+ * Fenced code. The round border is gone (§4.3): nested inside a tool card it
55
+ * produced a box inside a box and squeezed an 80-column terminal down to 74
56
+ * usable columns. A left rail plus the language tag conveys the same "this is
57
+ * code" boundary for 1 column and 0 rows.
58
+ */
59
+ function CodeBlock({ code, lang, theme, glyphs, }) {
60
+ let rendered = code;
61
+ try {
62
+ rendered = highlight(code, { language: lang || undefined, ignoreIllegals: true });
63
+ }
64
+ catch {
65
+ rendered = code;
66
+ }
67
+ return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingLeft: 1, ...railBorderProps(glyphs.railVertical, theme.border), children: [lang.length > 0 && (_jsx(Text, { color: theme.chip.fg, backgroundColor: theme.chip.bg, children: ` ${lang} ` })), _jsx(Text, { children: rendered })] }));
68
+ }
69
+ /** Six heading levels used to render identically; now they carry three weights. */
70
+ function headingStyle(level, theme) {
71
+ if (level <= 2)
72
+ return { color: theme.primary };
73
+ if (level === 3)
74
+ return { color: theme.accent };
75
+ return { color: theme.assistant };
76
+ }
77
+ function InlineLine({ line, theme, glyphs, }) {
78
+ const heading = parseHeading(line);
79
+ if (heading) {
80
+ const style = headingStyle(heading.level, theme);
81
+ if (heading.level === 1) {
82
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: style.color, children: heading.text }), _jsx(Text, { color: theme.border, children: glyphs.hrule.repeat(RULE_WIDTH) })] }));
83
+ }
84
+ return (_jsx(Text, { bold: true, color: style.color, children: heading.text }));
85
+ }
86
+ if (isRule(line)) {
87
+ return _jsx(Text, { color: theme.border, children: glyphs.hrule.repeat(RULE_WIDTH) });
88
+ }
89
+ const quote = /^>\s?(.*)$/.exec(line);
90
+ if (quote) {
91
+ return (_jsxs(Text, { color: theme.muted, italic: true, children: [glyphs.railVertical, " ", renderInline(quote[1] ?? '', theme)] }));
92
+ }
93
+ const bullet = /^(\s*)([-*+])\s+(.*)$/.exec(line);
94
+ if (bullet) {
95
+ return (_jsxs(Text, { children: [bullet[1], _jsxs(Text, { color: theme.accent, children: [glyphs.bullet, " "] }), renderInline(bullet[3] ?? '', theme)] }));
96
+ }
97
+ const ordered = /^(\s*)(\d+)\.\s+(.*)$/.exec(line);
98
+ if (ordered) {
99
+ return (_jsxs(Text, { children: [ordered[1], _jsxs(Text, { color: theme.accent, children: [ordered[2], ". "] }), renderInline(ordered[3] ?? '', theme)] }));
100
+ }
101
+ return _jsx(Text, { children: renderInline(line, theme) });
102
+ }
103
+ /** Turn parsed inline spans into Ink `<Text>` nodes. */
104
+ export function renderInline(text, theme) {
105
+ const spans = parseInline(text);
106
+ if (spans.length === 0)
107
+ return text;
108
+ return spans.map((span, i) => {
109
+ switch (span.kind) {
110
+ case 'code':
111
+ return (_jsx(Text, { color: theme.code, children: span.text }, `i${i}`));
112
+ case 'bold':
113
+ return (_jsx(Text, { bold: true, children: span.text }, `i${i}`));
114
+ case 'italic':
115
+ return (_jsx(Text, { italic: true, children: span.text }, `i${i}`));
116
+ case 'strike':
117
+ return (_jsx(Text, { strikethrough: true, children: span.text }, `i${i}`));
118
+ default:
119
+ return span.text;
120
+ }
121
+ });
122
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * PromptInput (spec §3.6 / §3.10) — a controlled multiline input on Ink's
3
+ * `useInput` with: full line editing (Home/End, word jump/delete, kill-line via
4
+ * `input/keymap`), multi-line vertical cursor navigation, prompt-history recall
5
+ * at the edges, a slash-command palette, and `@file` path completion.
6
+ *
7
+ * The buffer/cursor transforms (`applyEdit`, `moveVertical`) and the suggestion
8
+ * computations (`slashSuggestions`, `fileTokenAt`) are pure module functions,
9
+ * exported for `input.test.ts`, so the component itself stays thin.
10
+ */
11
+ import React from 'react';
12
+ import type { Theme } from './theme.js';
13
+ import type { TermCapabilities } from './capabilities.js';
14
+ import { type EditIntent } from '../input/keymap.js';
15
+ import { type Suggestion } from './AutocompletePopup.js';
16
+ export interface CommandOption {
17
+ name: string;
18
+ description: string;
19
+ }
20
+ interface PromptInputProps {
21
+ isActive: boolean;
22
+ running: boolean;
23
+ history: string[];
24
+ commands: CommandOption[];
25
+ cwd: string;
26
+ theme: Theme;
27
+ caps: TermCapabilities;
28
+ /**
29
+ * Row budget for the completion popup (§5.2 / R-14).
30
+ *
31
+ * The popup renders INSIDE this component, inside the shell's `flexShrink={0}`
32
+ * bottom chrome, and can add up to 9 rows. On a short terminal that pushes the
33
+ * status bar past the frame height, where the root box's `overflow: hidden`
34
+ * clips it away — the one piece of chrome R2 promises is always visible.
35
+ * `App` derives this from `budget.ts` so the popup shrinks instead.
36
+ */
37
+ popupMaxRows?: number;
38
+ onSubmit: (text: string) => void;
39
+ onHelp?: () => void;
40
+ /**
41
+ * Set by `Composer` in full-screen mode: draw the rounded frame around the
42
+ * input row only, so the autocomplete popup lands OUTSIDE it (spec §4.7)
43
+ * without lifting the editor's buffer/cursor state out of this component.
44
+ * Unset ⇒ the v0.2.0 borderless inline rendering.
45
+ */
46
+ bordered?: {
47
+ color?: string;
48
+ };
49
+ /** Fires only when the draft flips empty ⇄ non-empty, for the border color. */
50
+ onDraftChange?: (hasDraft: boolean) => void;
51
+ }
52
+ /** Apply an edit intent as a pure buffer/cursor transform. */
53
+ export declare function applyEdit(buffer: string, cursor: number, intent: EditIntent): {
54
+ buffer: string;
55
+ cursor: number;
56
+ };
57
+ /**
58
+ * Move the cursor up/down one line preserving the column. Returns `null` at the
59
+ * top/bottom edge (or for a single-line buffer) so the caller can fall back to
60
+ * prompt-history recall.
61
+ */
62
+ export declare function moveVertical(buffer: string, cursor: number, dir: 'up' | 'down'): {
63
+ cursor: number;
64
+ } | null;
65
+ /**
66
+ * Slash-command suggestions when the buffer is a bare `/word` (no space yet).
67
+ *
68
+ * The character class covers `-` and `:` as well as `\w` (D16). Skill names are
69
+ * kebab-case and the namespaced fallback is `/skill:<name>`; with the original
70
+ * `^\/(\w*)$` a user typing `/my-sk` got NO popup at all — not an error message,
71
+ * just silence, which reads as "that command does not exist".
72
+ */
73
+ export declare function slashSuggestions(buffer: string, commands: CommandOption[]): Suggestion[] | null;
74
+ /** The `@file` token under the cursor (`@` + non-space run), if any. */
75
+ export declare function fileTokenAt(buffer: string, cursor: number): {
76
+ start: number;
77
+ end: number;
78
+ query: string;
79
+ } | null;
80
+ export declare function PromptInput({ isActive, running, history, commands, cwd, theme, caps, popupMaxRows, onSubmit, onHelp, bordered, onDraftChange, }: PromptInputProps): React.ReactElement;
81
+ export {};