zoe-agent 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/LICENSE +96 -0
  3. package/README.md +568 -0
  4. package/dist/adapters/cli/agent.d.ts +59 -0
  5. package/dist/adapters/cli/agent.js +232 -0
  6. package/dist/adapters/cli/bootstrap.d.ts +25 -0
  7. package/dist/adapters/cli/bootstrap.js +204 -0
  8. package/dist/adapters/cli/commands/build-registry.d.ts +14 -0
  9. package/dist/adapters/cli/commands/build-registry.js +88 -0
  10. package/dist/adapters/cli/commands/clear.d.ts +7 -0
  11. package/dist/adapters/cli/commands/clear.js +10 -0
  12. package/dist/adapters/cli/commands/compact.d.ts +13 -0
  13. package/dist/adapters/cli/commands/compact.js +96 -0
  14. package/dist/adapters/cli/commands/exit.d.ts +7 -0
  15. package/dist/adapters/cli/commands/exit.js +9 -0
  16. package/dist/adapters/cli/commands/gateway.d.ts +7 -0
  17. package/dist/adapters/cli/commands/gateway.js +152 -0
  18. package/dist/adapters/cli/commands/help.d.ts +9 -0
  19. package/dist/adapters/cli/commands/help.js +12 -0
  20. package/dist/adapters/cli/commands/models.d.ts +10 -0
  21. package/dist/adapters/cli/commands/models.js +32 -0
  22. package/dist/adapters/cli/commands/registry.d.ts +70 -0
  23. package/dist/adapters/cli/commands/registry.js +111 -0
  24. package/dist/adapters/cli/commands/settings-utils.d.ts +38 -0
  25. package/dist/adapters/cli/commands/settings-utils.js +182 -0
  26. package/dist/adapters/cli/commands/settings.d.ts +9 -0
  27. package/dist/adapters/cli/commands/settings.js +395 -0
  28. package/dist/adapters/cli/commands/skills.d.ts +7 -0
  29. package/dist/adapters/cli/commands/skills.js +21 -0
  30. package/dist/adapters/cli/config-loader.d.ts +27 -0
  31. package/dist/adapters/cli/config-loader.js +48 -0
  32. package/dist/adapters/cli/docker-utils.d.ts +37 -0
  33. package/dist/adapters/cli/docker-utils.js +90 -0
  34. package/dist/adapters/cli/index.d.ts +2 -0
  35. package/dist/adapters/cli/index.js +88 -0
  36. package/dist/adapters/cli/repl.d.ts +22 -0
  37. package/dist/adapters/cli/repl.js +256 -0
  38. package/dist/adapters/cli/setup.d.ts +19 -0
  39. package/dist/adapters/cli/setup.js +613 -0
  40. package/dist/adapters/cli/system-prompts.d.ts +56 -0
  41. package/dist/adapters/cli/system-prompts.js +131 -0
  42. package/dist/adapters/cli/tui/app.d.ts +58 -0
  43. package/dist/adapters/cli/tui/app.js +314 -0
  44. package/dist/adapters/cli/tui/components/assistant-message.d.ts +5 -0
  45. package/dist/adapters/cli/tui/components/assistant-message.js +9 -0
  46. package/dist/adapters/cli/tui/components/autocomplete.d.ts +19 -0
  47. package/dist/adapters/cli/tui/components/autocomplete.js +75 -0
  48. package/dist/adapters/cli/tui/components/command-palette.d.ts +15 -0
  49. package/dist/adapters/cli/tui/components/command-palette.js +50 -0
  50. package/dist/adapters/cli/tui/components/diff-viewer.d.ts +5 -0
  51. package/dist/adapters/cli/tui/components/diff-viewer.js +109 -0
  52. package/dist/adapters/cli/tui/components/error-message.d.ts +5 -0
  53. package/dist/adapters/cli/tui/components/error-message.js +8 -0
  54. package/dist/adapters/cli/tui/components/footer.d.ts +20 -0
  55. package/dist/adapters/cli/tui/components/footer.js +19 -0
  56. package/dist/adapters/cli/tui/components/goal-status.d.ts +12 -0
  57. package/dist/adapters/cli/tui/components/goal-status.js +22 -0
  58. package/dist/adapters/cli/tui/components/info-message.d.ts +5 -0
  59. package/dist/adapters/cli/tui/components/info-message.js +8 -0
  60. package/dist/adapters/cli/tui/components/logo-banner.d.ts +7 -0
  61. package/dist/adapters/cli/tui/components/logo-banner.js +33 -0
  62. package/dist/adapters/cli/tui/components/markdown.d.ts +9 -0
  63. package/dist/adapters/cli/tui/components/markdown.js +92 -0
  64. package/dist/adapters/cli/tui/components/message-area.d.ts +19 -0
  65. package/dist/adapters/cli/tui/components/message-area.js +55 -0
  66. package/dist/adapters/cli/tui/components/permission-prompt.d.ts +13 -0
  67. package/dist/adapters/cli/tui/components/permission-prompt.js +32 -0
  68. package/dist/adapters/cli/tui/components/prompt-area.d.ts +22 -0
  69. package/dist/adapters/cli/tui/components/prompt-area.js +68 -0
  70. package/dist/adapters/cli/tui/components/text-input.d.ts +27 -0
  71. package/dist/adapters/cli/tui/components/text-input.js +142 -0
  72. package/dist/adapters/cli/tui/components/tool-call-block.d.ts +11 -0
  73. package/dist/adapters/cli/tui/components/tool-call-block.js +68 -0
  74. package/dist/adapters/cli/tui/components/user-message.d.ts +5 -0
  75. package/dist/adapters/cli/tui/components/user-message.js +8 -0
  76. package/dist/adapters/cli/tui/diff/file-write-meta.d.ts +11 -0
  77. package/dist/adapters/cli/tui/diff/file-write-meta.js +11 -0
  78. package/dist/adapters/cli/tui/diff/line-diff.d.ts +17 -0
  79. package/dist/adapters/cli/tui/diff/line-diff.js +44 -0
  80. package/dist/adapters/cli/tui/feed-serializer.d.ts +29 -0
  81. package/dist/adapters/cli/tui/feed-serializer.js +70 -0
  82. package/dist/adapters/cli/tui/file-index.d.ts +8 -0
  83. package/dist/adapters/cli/tui/file-index.js +41 -0
  84. package/dist/adapters/cli/tui/hooks/use-agent.d.ts +54 -0
  85. package/dist/adapters/cli/tui/hooks/use-agent.js +177 -0
  86. package/dist/adapters/cli/tui/hooks/use-feed.d.ts +16 -0
  87. package/dist/adapters/cli/tui/hooks/use-feed.js +25 -0
  88. package/dist/adapters/cli/tui/hooks/use-file-watcher.d.ts +10 -0
  89. package/dist/adapters/cli/tui/hooks/use-file-watcher.js +43 -0
  90. package/dist/adapters/cli/tui/hooks/use-keybindings.d.ts +16 -0
  91. package/dist/adapters/cli/tui/hooks/use-keybindings.js +25 -0
  92. package/dist/adapters/cli/tui/hooks/use-theme.d.ts +8 -0
  93. package/dist/adapters/cli/tui/hooks/use-theme.js +12 -0
  94. package/dist/adapters/cli/tui/index.d.ts +19 -0
  95. package/dist/adapters/cli/tui/index.js +206 -0
  96. package/dist/adapters/cli/tui/ink-reset.d.ts +29 -0
  97. package/dist/adapters/cli/tui/ink-reset.js +57 -0
  98. package/dist/adapters/cli/tui/layout.d.ts +15 -0
  99. package/dist/adapters/cli/tui/layout.js +15 -0
  100. package/dist/adapters/cli/tui/logo/gradient.d.ts +11 -0
  101. package/dist/adapters/cli/tui/logo/gradient.js +31 -0
  102. package/dist/adapters/cli/tui/overlays/help-dialog.d.ts +4 -0
  103. package/dist/adapters/cli/tui/overlays/help-dialog.js +26 -0
  104. package/dist/adapters/cli/tui/overlays/model-selector.d.ts +14 -0
  105. package/dist/adapters/cli/tui/overlays/model-selector.js +43 -0
  106. package/dist/adapters/cli/tui/overlays/session-selector.d.ts +35 -0
  107. package/dist/adapters/cli/tui/overlays/session-selector.js +162 -0
  108. package/dist/adapters/cli/tui/overlays/settings-overlay.d.ts +24 -0
  109. package/dist/adapters/cli/tui/overlays/settings-overlay.js +126 -0
  110. package/dist/adapters/cli/tui/session-export.d.ts +21 -0
  111. package/dist/adapters/cli/tui/session-export.js +63 -0
  112. package/dist/adapters/cli/tui/theme.d.ts +23 -0
  113. package/dist/adapters/cli/tui/theme.js +22 -0
  114. package/dist/adapters/cli/tui/types.d.ts +52 -0
  115. package/dist/adapters/cli/tui/types.js +12 -0
  116. package/dist/adapters/sdk/agent.d.ts +20 -0
  117. package/dist/adapters/sdk/agent.js +356 -0
  118. package/dist/adapters/sdk/http.d.ts +43 -0
  119. package/dist/adapters/sdk/http.js +61 -0
  120. package/dist/adapters/sdk/index.d.ts +58 -0
  121. package/dist/adapters/sdk/index.js +209 -0
  122. package/dist/adapters/sdk/settings.d.ts +18 -0
  123. package/dist/adapters/sdk/settings.js +57 -0
  124. package/dist/adapters/sdk/tools.d.ts +7 -0
  125. package/dist/adapters/sdk/tools.js +13 -0
  126. package/dist/adapters/server/auth.d.ts +53 -0
  127. package/dist/adapters/server/auth.js +168 -0
  128. package/dist/adapters/server/index.d.ts +40 -0
  129. package/dist/adapters/server/index.js +255 -0
  130. package/dist/adapters/server/rest-gateway.d.ts +13 -0
  131. package/dist/adapters/server/rest-gateway.js +218 -0
  132. package/dist/adapters/server/rest.d.ts +37 -0
  133. package/dist/adapters/server/rest.js +341 -0
  134. package/dist/adapters/server/server-core.d.ts +55 -0
  135. package/dist/adapters/server/server-core.js +121 -0
  136. package/dist/adapters/server/session-store.d.ts +81 -0
  137. package/dist/adapters/server/session-store.js +272 -0
  138. package/dist/adapters/server/settings-handlers.d.ts +24 -0
  139. package/dist/adapters/server/settings-handlers.js +360 -0
  140. package/dist/adapters/server/standalone.d.ts +19 -0
  141. package/dist/adapters/server/standalone.js +113 -0
  142. package/dist/adapters/server/websocket.d.ts +26 -0
  143. package/dist/adapters/server/websocket.js +68 -0
  144. package/dist/adapters/server/ws-handlers.d.ts +32 -0
  145. package/dist/adapters/server/ws-handlers.js +523 -0
  146. package/dist/adapters/server/ws-types.d.ts +304 -0
  147. package/dist/adapters/server/ws-types.js +7 -0
  148. package/dist/core/agent-loop.d.ts +68 -0
  149. package/dist/core/agent-loop.js +423 -0
  150. package/dist/core/config.d.ts +115 -0
  151. package/dist/core/config.js +189 -0
  152. package/dist/core/errors.d.ts +58 -0
  153. package/dist/core/errors.js +88 -0
  154. package/dist/core/hooks.d.ts +35 -0
  155. package/dist/core/hooks.js +49 -0
  156. package/dist/core/index.d.ts +23 -0
  157. package/dist/core/index.js +29 -0
  158. package/dist/core/message-convert.d.ts +41 -0
  159. package/dist/core/message-convert.js +94 -0
  160. package/dist/core/middleware/auth.d.ts +24 -0
  161. package/dist/core/middleware/auth.js +28 -0
  162. package/dist/core/middleware/logging.d.ts +23 -0
  163. package/dist/core/middleware/logging.js +28 -0
  164. package/dist/core/middleware/rate-limit.d.ts +27 -0
  165. package/dist/core/middleware/rate-limit.js +38 -0
  166. package/dist/core/middleware/semantic-tools.d.ts +10 -0
  167. package/dist/core/middleware/semantic-tools.js +43 -0
  168. package/dist/core/middleware.d.ts +48 -0
  169. package/dist/core/middleware.js +38 -0
  170. package/dist/core/permission.d.ts +25 -0
  171. package/dist/core/permission.js +50 -0
  172. package/dist/core/provider-config.d.ts +129 -0
  173. package/dist/core/provider-config.js +273 -0
  174. package/dist/core/provider-env.d.ts +39 -0
  175. package/dist/core/provider-env.js +142 -0
  176. package/dist/core/provider-resolver.d.ts +12 -0
  177. package/dist/core/provider-resolver.js +12 -0
  178. package/dist/core/session-store.d.ts +75 -0
  179. package/dist/core/session-store.js +245 -0
  180. package/dist/core/settings-manager.d.ts +57 -0
  181. package/dist/core/settings-manager.js +359 -0
  182. package/dist/core/settings-schema.d.ts +38 -0
  183. package/dist/core/settings-schema.js +171 -0
  184. package/dist/core/skill-catalog.d.ts +6 -0
  185. package/dist/core/skill-catalog.js +17 -0
  186. package/dist/core/skill-invoker.d.ts +127 -0
  187. package/dist/core/skill-invoker.js +182 -0
  188. package/dist/core/stream-accumulator.d.ts +21 -0
  189. package/dist/core/stream-accumulator.js +51 -0
  190. package/dist/core/stream-manager.d.ts +58 -0
  191. package/dist/core/stream-manager.js +212 -0
  192. package/dist/core/tool-executor.d.ts +84 -0
  193. package/dist/core/tool-executor.js +256 -0
  194. package/dist/core/types.d.ts +259 -0
  195. package/dist/core/types.js +11 -0
  196. package/dist/gateway/gateway.d.ts +52 -0
  197. package/dist/gateway/gateway.js +537 -0
  198. package/dist/gateway/index.d.ts +21 -0
  199. package/dist/gateway/index.js +31 -0
  200. package/dist/gateway/openapi-importer.d.ts +15 -0
  201. package/dist/gateway/openapi-importer.js +66 -0
  202. package/dist/gateway/semantic-scorer.d.ts +7 -0
  203. package/dist/gateway/semantic-scorer.js +24 -0
  204. package/dist/gateway/settings-adapter.d.ts +49 -0
  205. package/dist/gateway/settings-adapter.js +137 -0
  206. package/dist/gateway/tool-factory.d.ts +9 -0
  207. package/dist/gateway/tool-factory.js +414 -0
  208. package/dist/gateway/types.d.ts +68 -0
  209. package/dist/gateway/types.js +7 -0
  210. package/dist/models-catalog.js +46 -0
  211. package/dist/providers/anthropic.d.ts +22 -0
  212. package/dist/providers/anthropic.js +148 -0
  213. package/dist/providers/factory.d.ts +10 -0
  214. package/dist/providers/factory.js +25 -0
  215. package/dist/providers/openai.d.ts +15 -0
  216. package/dist/providers/openai.js +71 -0
  217. package/dist/providers/types.d.ts +48 -0
  218. package/dist/providers/types.js +1 -0
  219. package/dist/skills/args.d.ts +37 -0
  220. package/dist/skills/args.js +99 -0
  221. package/dist/skills/index.d.ts +11 -0
  222. package/dist/skills/index.js +23 -0
  223. package/dist/skills/loader.d.ts +3 -0
  224. package/dist/skills/loader.js +59 -0
  225. package/dist/skills/parser.d.ts +7 -0
  226. package/dist/skills/parser.js +152 -0
  227. package/dist/skills/registry.d.ts +13 -0
  228. package/dist/skills/registry.js +74 -0
  229. package/dist/skills/resolver.d.ts +19 -0
  230. package/dist/skills/resolver.js +116 -0
  231. package/dist/skills/types.d.ts +74 -0
  232. package/dist/skills/types.js +50 -0
  233. package/dist/tools/browser.d.ts +2 -0
  234. package/dist/tools/browser.js +68 -0
  235. package/dist/tools/core.d.ts +20 -0
  236. package/dist/tools/core.js +244 -0
  237. package/dist/tools/email.d.ts +2 -0
  238. package/dist/tools/email.js +61 -0
  239. package/dist/tools/image.d.ts +2 -0
  240. package/dist/tools/image.js +257 -0
  241. package/dist/tools/index.d.ts +2 -0
  242. package/dist/tools/index.js +88 -0
  243. package/dist/tools/interface.d.ts +22 -0
  244. package/dist/tools/interface.js +1 -0
  245. package/dist/tools/notify.d.ts +2 -0
  246. package/dist/tools/notify.js +100 -0
  247. package/dist/tools/prompt-optimizer.d.ts +2 -0
  248. package/dist/tools/prompt-optimizer.js +65 -0
  249. package/dist/tools/screenshot.d.ts +2 -0
  250. package/dist/tools/screenshot.js +184 -0
  251. package/dist/tools/search.d.ts +2 -0
  252. package/dist/tools/search.js +78 -0
  253. package/dist/tools/todos.d.ts +10 -0
  254. package/dist/tools/todos.js +50 -0
  255. package/package.json +119 -0
  256. package/skills/docker-ops/SKILL.md +329 -0
  257. package/skills/k8s-deploy/SKILL.md +397 -0
  258. package/skills/log-analyzer/SKILL.md +331 -0
  259. package/skills/speckit-analyze/SKILL.md +260 -0
  260. package/skills/speckit-checklist/SKILL.md +374 -0
  261. package/skills/speckit-clarify/SKILL.md +286 -0
  262. package/skills/speckit-constitution/SKILL.md +157 -0
  263. package/skills/speckit-implement/SKILL.md +224 -0
  264. package/skills/speckit-plan/SKILL.md +171 -0
  265. package/skills/speckit-specify/SKILL.md +346 -0
  266. package/skills/speckit-tasks/SKILL.md +215 -0
  267. package/skills/speckit-taskstoissues/SKILL.md +107 -0
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { useTheme } from '../hooks/use-theme.js';
5
+ import { fuzzyFilter } from './autocomplete.js';
6
+ const MAX_VISIBLE = 8;
7
+ /**
8
+ * Ctrl+P command palette: type to fuzzy-filter commands + skills, ↑/↓ to
9
+ * navigate, Enter to run, Esc to close. Owns its input (the prompt is hidden
10
+ * while the palette is open).
11
+ */
12
+ export function CommandPalette({ commands, skills, onRun, onClose }) {
13
+ const theme = useTheme();
14
+ const [query, setQuery] = useState('');
15
+ const [selected, setSelected] = useState(0);
16
+ const matches = fuzzyFilter([...commands, ...skills], query);
17
+ useInput((input, key) => {
18
+ if (key.escape) {
19
+ onClose();
20
+ return;
21
+ }
22
+ if (key.return) {
23
+ const m = matches[Math.min(selected, matches.length - 1)] ?? matches[0];
24
+ if (m)
25
+ onRun(m.name);
26
+ return;
27
+ }
28
+ if (key.upArrow) {
29
+ setSelected((i) => Math.max(0, i - 1));
30
+ }
31
+ else if (key.downArrow) {
32
+ setSelected((i) => Math.min(matches.length - 1, i + 1));
33
+ }
34
+ else if (key.backspace || key.delete) {
35
+ setQuery((q) => q.slice(0, -1));
36
+ setSelected(0);
37
+ }
38
+ else if (input && !key.ctrl && !key.meta && input.length >= 1 && input >= ' ') {
39
+ setQuery((q) => q + input);
40
+ setSelected(0);
41
+ }
42
+ });
43
+ const half = Math.floor(MAX_VISIBLE / 2);
44
+ const start = Math.max(0, Math.min(selected - half, matches.length - MAX_VISIBLE));
45
+ const visible = matches.slice(start, start + MAX_VISIBLE);
46
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.purple, paddingLeft: 1, paddingRight: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.purple, bold: true, children: "\u276F " }), _jsx(Text, { color: theme.fg, children: query }), _jsxs(Text, { color: theme.fgDim, children: [" ", matches.length > 0 ? `(↑↓ select, Enter run, Esc close)` : '— no matches'] })] }), visible.map((s, i) => {
47
+ const sel = start + i === selected;
48
+ return (_jsxs(Box, { children: [_jsxs(Text, { backgroundColor: sel ? theme.blue : undefined, color: sel ? theme.bg : theme.green, children: [sel ? '▶ ' : ' ', s.name] }), s.description ? _jsxs(Text, { color: theme.fgDim, children: [" ", s.description.split('\n')[0].slice(0, 40)] }) : null] }, s.name));
49
+ }), matches.length > MAX_VISIBLE ? (_jsxs(Text, { color: theme.fgDim, children: [" ", matches.length - MAX_VISIBLE, " more"] })) : null] }));
50
+ }
@@ -0,0 +1,5 @@
1
+ export declare function DiffViewer({ oldContent, newContent, expanded }: {
2
+ oldContent: string | null;
3
+ newContent: string;
4
+ expanded: boolean;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,109 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Inline unified-diff renderer for `write_file` tool calls.
4
+ *
5
+ * Renders the DiffViewLine[] from `computeDiffLines` as green `+` / red `-` /
6
+ * dim ` ` lines with padded line numbers. Unchanged runs not adjacent to a
7
+ * change are collapsed with a `… N unchanged lines skipped` marker (Pi-style,
8
+ * research.md R4). Collapsed-by-default with a `… N more lines` expand hint
9
+ * (Ctrl+O toggles `expanded` globally in app.tsx).
10
+ *
11
+ * Rendered as ONE `<Text>` with nested colored spans separated by `\n` — the
12
+ * canonical ink pattern for multi-line colored text (a `<Box>` of separate
13
+ * `<Text>` rows mis-measures on some line widths).
14
+ */
15
+ import { Text } from 'ink';
16
+ import { useMemo } from 'react';
17
+ import { useTheme } from '../hooks/use-theme.js';
18
+ import { computeDiffLines } from '../diff/line-diff.js';
19
+ const CONTEXT = 3; // context lines retained around each changed run
20
+ const COLLAPSED_BUDGET = 50; // max diff lines shown when collapsed
21
+ /** Collapse runs of unchanged context not within `context` lines of a change. */
22
+ function collapseContext(lines, context) {
23
+ const keep = new Array(lines.length).fill(false);
24
+ for (let i = 0; i < lines.length; i++) {
25
+ if (lines[i].kind !== 'context') {
26
+ const from = Math.max(0, i - context);
27
+ const to = Math.min(lines.length - 1, i + context);
28
+ for (let j = from; j <= to; j++)
29
+ keep[j] = true;
30
+ }
31
+ }
32
+ const items = [];
33
+ let run = 0;
34
+ for (let i = 0; i < lines.length; i++) {
35
+ if (keep[i]) {
36
+ if (run > 0) {
37
+ items.push({ type: 'skip', count: run });
38
+ run = 0;
39
+ }
40
+ items.push({ type: 'line', line: lines[i] });
41
+ }
42
+ else {
43
+ run++;
44
+ }
45
+ }
46
+ if (run > 0)
47
+ items.push({ type: 'skip', count: run });
48
+ return items;
49
+ }
50
+ function linePrefix(line, width) {
51
+ if (line.kind === 'added')
52
+ return `+${String(line.newLineNo).padStart(width)} ${line.text}`;
53
+ if (line.kind === 'removed')
54
+ return `-${String(line.oldLineNo).padStart(width)} ${line.text}`;
55
+ return ` ${String(line.oldLineNo).padStart(width)} ${line.text}`;
56
+ }
57
+ function lineColor(line, theme) {
58
+ return line.kind === 'added' ? theme.green : line.kind === 'removed' ? theme.red : theme.fgDim;
59
+ }
60
+ export function DiffViewer({ oldContent, newContent, expanded }) {
61
+ const theme = useTheme();
62
+ const lines = useMemo(() => computeDiffLines(oldContent, newContent), [oldContent, newContent]);
63
+ const items = useMemo(() => collapseContext(lines, CONTEXT), [lines]);
64
+ // No diffable lines ⇒ either a brand-new empty file or an empty→empty rewrite.
65
+ if (lines.length === 0) {
66
+ return _jsxs(Text, { color: theme.fgDim, children: [" ", oldContent === null ? '(new empty file)' : '(no changes)'] });
67
+ }
68
+ if (!lines.some((l) => l.kind !== 'context')) {
69
+ return _jsx(Text, { color: theme.fgDim, children: " (no changes)" });
70
+ }
71
+ const maxNo = lines.reduce((m, l) => Math.max(m, l.kind === 'added' ? l.newLineNo : l.kind === 'removed' ? l.oldLineNo : l.newLineNo), 1);
72
+ const width = String(maxNo).length;
73
+ let shown = items;
74
+ let dropped = 0;
75
+ if (!expanded) {
76
+ const kept = [];
77
+ let count = 0;
78
+ for (const it of items) {
79
+ if (it.type === 'line' && count >= COLLAPSED_BUDGET) {
80
+ dropped++;
81
+ continue;
82
+ }
83
+ kept.push(it);
84
+ if (it.type === 'line')
85
+ count++;
86
+ }
87
+ shown = kept;
88
+ }
89
+ // Trim a trailing skip marker (nothing follows it).
90
+ if (shown.length > 0 && shown[shown.length - 1].type === 'skip') {
91
+ shown = shown.slice(0, -1);
92
+ }
93
+ const nodes = [];
94
+ shown.forEach((it, i) => {
95
+ if (i > 0)
96
+ nodes.push('\n');
97
+ if (it.type === 'skip') {
98
+ nodes.push(_jsx(Text, { color: theme.fgDim, children: ` … ${it.count} unchanged line${it.count === 1 ? '' : 's'} skipped` }, `s${i}`));
99
+ }
100
+ else {
101
+ nodes.push(_jsx(Text, { color: lineColor(it.line, theme), children: linePrefix(it.line, width) }, `l${i}`));
102
+ }
103
+ });
104
+ if (dropped > 0) {
105
+ nodes.push('\n');
106
+ nodes.push(_jsx(Text, { color: theme.fgDim, children: ` … ${dropped} more line${dropped === 1 ? '' : 's'} (Ctrl+O to expand)` }, "more"));
107
+ }
108
+ return _jsx(Text, { children: nodes });
109
+ }
@@ -0,0 +1,5 @@
1
+ import type { ErrorEntry } from '../types.js';
2
+ /** An error entry — red glyph + message. */
3
+ export declare function ErrorMessage({ entry }: {
4
+ entry: ErrorEntry;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../hooks/use-theme.js';
4
+ /** An error entry — red glyph + message. */
5
+ export function ErrorMessage({ entry }) {
6
+ const theme = useTheme();
7
+ return (_jsxs(Box, { children: [_jsx(Text, { color: theme.red, bold: true, children: "\u2717 " }), _jsx(Text, { color: theme.red, children: entry.message })] }));
8
+ }
@@ -0,0 +1,20 @@
1
+ import type { CumulativeUsage, PermissionLevel } from '../../../../core/types.js';
2
+ interface FooterProps {
3
+ providerType: string;
4
+ model: string;
5
+ usage: CumulativeUsage;
6
+ permissionLevel?: PermissionLevel;
7
+ skillCount: number;
8
+ gatewayOn: boolean;
9
+ mcpCount: number;
10
+ /** Last turn's input size in tokens (the current context-window usage). */
11
+ contextTokens: number;
12
+ /** The active model's max context in tokens (undefined if unknown). */
13
+ contextWindow?: number;
14
+ }
15
+ /**
16
+ * Fixed bottom status bar: provider | model | context-window | cost | permission
17
+ * | skills | gw. Context-window + cost update live from the agent's usage.
18
+ */
19
+ export declare function Footer({ providerType, model, usage, permissionLevel, skillCount, gatewayOn, mcpCount, contextTokens, contextWindow, }: FooterProps): import("react").JSX.Element;
20
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../hooks/use-theme.js';
4
+ /** "12k/200k (6%)" when the limit is known, else just the used amount. */
5
+ function fmtContext(used, limit) {
6
+ if (!limit)
7
+ return `${Math.round(used / 1000)}k`;
8
+ const pct = Math.round((used / limit) * 100);
9
+ return `${Math.round(used / 1000)}k/${Math.round(limit / 1000)}k (${pct}%)`;
10
+ }
11
+ /**
12
+ * Fixed bottom status bar: provider | model | context-window | cost | permission
13
+ * | skills | gw. Context-window + cost update live from the agent's usage.
14
+ */
15
+ export function Footer({ providerType, model, usage, permissionLevel, skillCount, gatewayOn, mcpCount, contextTokens, contextWindow, }) {
16
+ const theme = useTheme();
17
+ const sep = _jsx(Text, { color: theme.fgGutter, children: " \u2502 " });
18
+ return (_jsxs(Box, { children: [_jsx(Text, { color: theme.purple, children: providerType }), sep, _jsx(Text, { color: theme.cyan, children: model }), sep, _jsx(Text, { color: theme.fgDim, children: fmtContext(contextTokens, contextWindow) }), sep, _jsxs(Text, { color: theme.fgDim, children: ["$", usage.totalCost.toFixed(2)] }), sep, _jsx(Text, { color: theme.fgDim, children: permissionLevel ?? 'moderate' }), sep, _jsxs(Text, { color: theme.fgDim, children: [skillCount, " skills"] }), sep, _jsxs(Text, { color: gatewayOn ? theme.green : theme.fgDim, children: ["gw: ", gatewayOn ? `on (${mcpCount})` : 'off'] })] }));
19
+ }
@@ -0,0 +1,12 @@
1
+ export interface Todo {
2
+ description: string;
3
+ status: string;
4
+ }
5
+ /**
6
+ * GoalStatus — renders the agent's task list as bordered feed entries with
7
+ * status glyphs. Completed tasks are dimmed. Triggered when the agent calls
8
+ * the `manage_todos` tool (detected in tool-call-block).
9
+ */
10
+ export declare function GoalStatus({ todos }: {
11
+ todos: Todo[];
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../hooks/use-theme.js';
4
+ const GLYPHS = {
5
+ pending: '[ ]',
6
+ in_progress: '[~]',
7
+ completed: '[✓]',
8
+ blocked: '[!]',
9
+ };
10
+ /**
11
+ * GoalStatus — renders the agent's task list as bordered feed entries with
12
+ * status glyphs. Completed tasks are dimmed. Triggered when the agent calls
13
+ * the `manage_todos` tool (detected in tool-call-block).
14
+ */
15
+ export function GoalStatus({ todos }) {
16
+ const theme = useTheme();
17
+ const colorFor = (s) => s === 'completed' ? theme.green
18
+ : s === 'in_progress' ? theme.yellow
19
+ : s === 'blocked' ? theme.red
20
+ : theme.fgDim;
21
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.purple, paddingLeft: 1, paddingRight: 1, children: [_jsx(Text, { color: theme.purple, bold: true, children: "Tasks" }), todos.map((t, i) => (_jsxs(Box, { children: [_jsxs(Text, { children: [GLYPHS[t.status] ?? '[ ]', " "] }), _jsx(Text, { color: colorFor(t.status), dimColor: t.status === 'completed', children: t.description })] }, i)))] }));
22
+ }
@@ -0,0 +1,5 @@
1
+ import type { InfoEntry } from '../types.js';
2
+ /** An info/warning/status line entry — dim, no speaker token. */
3
+ export declare function InfoMessage({ entry }: {
4
+ entry: InfoEntry;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../hooks/use-theme.js';
4
+ /** An info/warning/status line entry — dim, no speaker token. */
5
+ export function InfoMessage({ entry }) {
6
+ const theme = useTheme();
7
+ return (_jsx(Box, { children: _jsx(Text, { color: theme.fgDim, children: entry.content }) }));
8
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Zoe Agent logo — figlet wordmark with a Tokyo Night 45° rainbow gradient
3
+ * (lolcat-style sweep, our palette) + a dim descriptor. Rendered as the first
4
+ * feed entry (kind: 'logo') on a fresh session, so it scrolls away as the user
5
+ * chats.
6
+ */
7
+ export declare function LogoBanner(): import("react").JSX.Element;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import figlet from 'figlet';
4
+ import { useTheme } from '../hooks/use-theme.js';
5
+ import { rainbowCellColor } from '../logo/gradient.js';
6
+ // figlet is lazy-loaded: this module only imports under the interactive TUI
7
+ // (app.tsx → message-area → here), so headless/SDK/Server never pull it in.
8
+ const WORDMARK = 'Zoe Agent';
9
+ // "ANSI Compact" = solid-block figlet font (~54 cols) — the block vibe of Delta
10
+ // Corps Priest 1 at a size that fits an 80-col terminal. Swap here for another
11
+ // figlet font (e.g. 'Small Block' = 25 cols pixelated, 'Delta Corps Priest 1' =
12
+ // ~102 cols wide). figlet has no scale option, so the font IS the size.
13
+ const FONT = 'ANSI Compact';
14
+ const VERSION = '0.3.0'; // keep in sync with package.json
15
+ // Render once at module load; rstrip each line to drop invisible trailing spaces.
16
+ const ART_LINES = figlet
17
+ .textSync(WORDMARK, { font: FONT, horizontalLayout: 'default' })
18
+ .replace(/\s+$/, '')
19
+ .split('\n')
20
+ .map((l) => l.replace(/\s+$/, ''));
21
+ const ROWS = ART_LINES.length;
22
+ const COLS = Math.max(...ART_LINES.map((l) => [...l].length));
23
+ /**
24
+ * Zoe Agent logo — figlet wordmark with a Tokyo Night 45° rainbow gradient
25
+ * (lolcat-style sweep, our palette) + a dim descriptor. Rendered as the first
26
+ * feed entry (kind: 'logo') on a fresh session, so it scrolls away as the user
27
+ * chats.
28
+ */
29
+ export function LogoBanner() {
30
+ const theme = useTheme();
31
+ const stops = [theme.red, theme.orange, theme.yellow, theme.green, theme.cyan, theme.blue, theme.purple];
32
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [ART_LINES.map((line, r) => (_jsx(Box, { children: [...line].map((ch, c) => ch === ' ' ? (_jsx(Text, { children: " " }, c)) : (_jsx(Text, { color: rainbowCellColor(r, c, ROWS, COLS, stops), children: ch }, c))) }, r))), _jsxs(Text, { color: theme.fgDim, children: [" by hashangit \u00B7 v", VERSION] })] }));
33
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ /**
3
+ * Minimal CommonMark-subset renderer for assistant messages: code fences,
4
+ * inline code, **bold**, [links](url), bullet lists, and headings. Custom
5
+ * (no `marked` dependency — its marked-terminal peer conflicts on v18).
6
+ */
7
+ export declare function Markdown({ content }: {
8
+ content: string;
9
+ }): React.ReactElement;
@@ -0,0 +1,92 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../hooks/use-theme.js';
4
+ const INLINE_RE = /\*\*(.+?)\*\*|`(.+?)`|\[(.+?)\]\(([^)]+)\)/g;
5
+ function parseInline(text) {
6
+ const segments = [];
7
+ let last = 0;
8
+ let m;
9
+ INLINE_RE.lastIndex = 0;
10
+ while ((m = INLINE_RE.exec(text)) !== null) {
11
+ if (m.index > last)
12
+ segments.push({ kind: 'text', text: text.slice(last, m.index) });
13
+ if (m[1] !== undefined)
14
+ segments.push({ kind: 'bold', text: m[1] });
15
+ else if (m[2] !== undefined)
16
+ segments.push({ kind: 'code', text: m[2] });
17
+ else if (m[3] !== undefined)
18
+ segments.push({ kind: 'link', text: m[3], url: m[4] });
19
+ last = INLINE_RE.lastIndex;
20
+ }
21
+ if (last < text.length)
22
+ segments.push({ kind: 'text', text: text.slice(last) });
23
+ return segments;
24
+ }
25
+ function InlineText({ text }) {
26
+ const theme = useTheme();
27
+ const segments = parseInline(text);
28
+ return (_jsx(Text, { children: segments.map((s, i) => {
29
+ if (s.kind === 'bold')
30
+ return _jsx(Text, { bold: true, children: s.text }, i);
31
+ if (s.kind === 'code')
32
+ return _jsx(Text, { backgroundColor: theme.bgHighlight, color: theme.orange, children: s.text }, i);
33
+ if (s.kind === 'link')
34
+ return _jsx(Text, { color: theme.cyan, children: s.text }, i);
35
+ return _jsx(Text, { children: s.text }, i);
36
+ }) }));
37
+ }
38
+ function parseBlocks(content) {
39
+ const lines = content.split('\n');
40
+ const blocks = [];
41
+ let i = 0;
42
+ while (i < lines.length) {
43
+ const line = lines[i];
44
+ if (line.startsWith('```')) {
45
+ const code = [];
46
+ i++;
47
+ while (i < lines.length && !lines[i].startsWith('```')) {
48
+ code.push(lines[i]);
49
+ i++;
50
+ }
51
+ i++; // closing fence
52
+ blocks.push({ type: 'code', lines: code });
53
+ }
54
+ else if (/^#{1,6}\s/.test(line)) {
55
+ blocks.push({ type: 'heading', text: line.replace(/^#{1,6}\s/, '') });
56
+ i++;
57
+ }
58
+ else if (/^\s*[-*]\s+/.test(line)) {
59
+ blocks.push({ type: 'list', text: line.replace(/^\s*[-*]\s+/, '') });
60
+ i++;
61
+ }
62
+ else if (line.trim() === '') {
63
+ i++; // collapse blanks
64
+ }
65
+ else {
66
+ blocks.push({ type: 'paragraph', text: line });
67
+ i++;
68
+ }
69
+ }
70
+ return blocks;
71
+ }
72
+ /**
73
+ * Minimal CommonMark-subset renderer for assistant messages: code fences,
74
+ * inline code, **bold**, [links](url), bullet lists, and headings. Custom
75
+ * (no `marked` dependency — its marked-terminal peer conflicts on v18).
76
+ */
77
+ export function Markdown({ content }) {
78
+ const theme = useTheme();
79
+ const blocks = parseBlocks(content);
80
+ return (_jsx(Box, { flexDirection: "column", children: blocks.map((b, i) => {
81
+ if (b.type === 'code') {
82
+ return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.fgGutter, paddingLeft: 1, paddingRight: 1, children: b.lines.map((l, j) => (_jsx(Text, { color: theme.fgDim, children: l || ' ' }, j))) }, i));
83
+ }
84
+ if (b.type === 'heading') {
85
+ return (_jsx(Text, { bold: true, color: theme.purple, children: _jsx(InlineText, { text: b.text }) }, i));
86
+ }
87
+ if (b.type === 'list') {
88
+ return (_jsxs(Box, { children: [_jsx(Text, { color: theme.green, children: "\u2022 " }), _jsx(InlineText, { text: b.text })] }, i));
89
+ }
90
+ return _jsx(Text, { children: _jsx(InlineText, { text: b.text }) }, i);
91
+ }) }));
92
+ }
@@ -0,0 +1,19 @@
1
+ import type { FeedEntry } from '../types.js';
2
+ /**
3
+ * Scrollable feed. Completed entries render via Ink's `<Static>` (each item is
4
+ * painted once and scrolls into the terminal's native scrollback); the
5
+ * pending-permission prompt and "working…" indicator are live components in
6
+ * `app.tsx`.
7
+ *
8
+ * Width handling: `<Static>` writes each item at the full terminal width and
9
+ * ignores parent padding, so an item that fills `columns` triggers the
10
+ * terminal's auto-wrap (a phantom row below). Each item is therefore capped at
11
+ * `columns - HORIZONTAL_PADDING` with a matching left pad, giving a symmetric
12
+ * gutter and keeping every line `< columns`. `useStdout` reads the live column
13
+ * count so resize reflows correctly.
14
+ */
15
+ export declare function MessageArea({ entries, staticKey, expanded }: {
16
+ entries: FeedEntry[];
17
+ staticKey: number;
18
+ expanded: boolean;
19
+ }): import("react").JSX.Element;
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Static, Text, useStdout } from 'ink';
3
+ import { useTheme } from '../hooks/use-theme.js';
4
+ import { HORIZONTAL_PADDING } from '../layout.js';
5
+ import { UserMessage } from './user-message.js';
6
+ import { AssistantMessage } from './assistant-message.js';
7
+ import { ToolCallBlock } from './tool-call-block.js';
8
+ import { ErrorMessage } from './error-message.js';
9
+ import { InfoMessage } from './info-message.js';
10
+ import { LogoBanner } from './logo-banner.js';
11
+ /** Renders one feed entry by kind. */
12
+ function FeedItem({ entry, expanded }) {
13
+ switch (entry.kind) {
14
+ case 'user':
15
+ return _jsx(UserMessage, { entry: entry });
16
+ case 'assistant':
17
+ return _jsx(AssistantMessage, { entry: entry });
18
+ case 'tool':
19
+ return _jsx(ToolCallBlock, { entry: entry, expanded: expanded });
20
+ case 'error':
21
+ return _jsx(ErrorMessage, { entry: entry });
22
+ case 'info':
23
+ return _jsx(InfoMessage, { entry: entry });
24
+ case 'logo':
25
+ return _jsx(LogoBanner, {});
26
+ }
27
+ }
28
+ /**
29
+ * Scrollable feed. Completed entries render via Ink's `<Static>` (each item is
30
+ * painted once and scrolls into the terminal's native scrollback); the
31
+ * pending-permission prompt and "working…" indicator are live components in
32
+ * `app.tsx`.
33
+ *
34
+ * Width handling: `<Static>` writes each item at the full terminal width and
35
+ * ignores parent padding, so an item that fills `columns` triggers the
36
+ * terminal's auto-wrap (a phantom row below). Each item is therefore capped at
37
+ * `columns - HORIZONTAL_PADDING` with a matching left pad, giving a symmetric
38
+ * gutter and keeping every line `< columns`. `useStdout` reads the live column
39
+ * count so resize reflows correctly.
40
+ */
41
+ export function MessageArea({ entries, staticKey, expanded }) {
42
+ const theme = useTheme();
43
+ const { stdout } = useStdout();
44
+ const columns = stdout?.columns ?? 80;
45
+ const itemWidth = Math.max(20, columns - HORIZONTAL_PADDING);
46
+ if (entries.length === 0) {
47
+ return (_jsx(Box, { paddingLeft: HORIZONTAL_PADDING, children: _jsx(Text, { color: theme.fgDim, children: "No messages yet \u2014 type a prompt and press Enter." }) }));
48
+ }
49
+ return (
50
+ // `key={staticKey}`: bumping it (on resize / expand-toggle) remounts
51
+ // <Static> for a full repaint. The caller resets Ink's accumulated
52
+ // `fullStaticOutput` first (see ink-reset.ts) so the remount doesn't
53
+ // duplicate history. Normal appends still render incrementally.
54
+ _jsx(Static, { items: entries, children: (entry) => (_jsx(Box, { width: itemWidth, paddingLeft: HORIZONTAL_PADDING, children: _jsx(FeedItem, { entry: entry, expanded: expanded }) }, entry.id)) }, staticKey));
55
+ }
@@ -0,0 +1,13 @@
1
+ interface PermissionPromptProps {
2
+ toolName: string;
3
+ args: Record<string, unknown>;
4
+ /** Called once with the user's decision. Double-fires are no-ops (use-agent clears the resolver). */
5
+ onResolve: (approve: boolean) => void;
6
+ }
7
+ /**
8
+ * Inline tool-approval prompt rendered in the feed while the agent is paused
9
+ * on `approveTool`. Stays within Ink's input handling — no stdin mode switch
10
+ * (unlike the readline path's inquirer suspend/resume).
11
+ */
12
+ export declare function PermissionPrompt({ toolName, args, onResolve }: PermissionPromptProps): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { useTheme } from '../hooks/use-theme.js';
4
+ /** One-line preview of the tool args. */
5
+ function formatArgs(args) {
6
+ if (typeof args.command === 'string' && args.command.length > 0)
7
+ return args.command;
8
+ const json = JSON.stringify(args);
9
+ return json === '{}' ? '' : json;
10
+ }
11
+ /**
12
+ * Inline tool-approval prompt rendered in the feed while the agent is paused
13
+ * on `approveTool`. Stays within Ink's input handling — no stdin mode switch
14
+ * (unlike the readline path's inquirer suspend/resume).
15
+ */
16
+ export function PermissionPrompt({ toolName, args, onResolve }) {
17
+ const theme = useTheme();
18
+ useInput((input) => {
19
+ const key = input.toLowerCase();
20
+ if (key === 'y')
21
+ onResolve(true);
22
+ else if (key === 'n')
23
+ onResolve(false);
24
+ });
25
+ const argsPreview = formatArgs(args);
26
+ return (_jsxs(Box, { borderStyle: "round", borderColor: theme.yellow, paddingLeft: 1, paddingRight: 1, children: [_jsx(Text, { color: theme.yellow, bold: true, children: "? " }), _jsx(Text, { color: theme.fg, children: "Run " }), _jsx(Text, { color: theme.purple, bold: true, children: toolName }), argsPreview ? _jsxs(Text, { color: theme.fgDim, children: [" ", truncate(argsPreview, 100)] }) : null, _jsx(Text, { color: theme.fg, children: "? [" }), _jsx(Text, { color: theme.green, bold: true, children: "y" }), _jsx(Text, { color: theme.fg, children: "/" }), _jsx(Text, { color: theme.red, bold: true, children: "n" }), _jsx(Text, { color: theme.fg, children: "]" })] }));
27
+ }
28
+ function truncate(text, max) {
29
+ if (text.length <= max)
30
+ return text;
31
+ return `${text.slice(0, max)}…`;
32
+ }
@@ -0,0 +1,22 @@
1
+ import { type Suggestion } from './autocomplete.js';
2
+ interface PromptAreaProps {
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ onSubmit: (value: string) => void;
6
+ /** Recall previous/next prompt (↑ on the top line / ↓ on the bottom line). */
7
+ onHistoryUp?: () => void;
8
+ onHistoryDown?: () => void;
9
+ /** `/command` source (built-in registry). */
10
+ commands: Suggestion[];
11
+ /** `/<skill-name>` source. */
12
+ skills: Suggestion[];
13
+ }
14
+ /**
15
+ * Single-line input (custom TextInput) with a fuzzy autocomplete dropdown +
16
+ * input history. Typing `/` suggests slash commands + skills; typing `@`
17
+ * suggests project files (recursive index, fuzzy-matched). When the dropdown is
18
+ * open, ↑/↓ navigate it; when closed, ↑/↓ recall previous prompts. Tab/Enter
19
+ * accepts; a second Enter submits. Multi-line is P2 (PRD 19).
20
+ */
21
+ export declare function PromptArea({ value, onChange, onSubmit, onHistoryUp, onHistoryDown, commands, skills }: PromptAreaProps): import("react").JSX.Element;
22
+ export {};
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { useTheme } from '../hooks/use-theme.js';
5
+ import { TextInput } from './text-input.js';
6
+ import { Autocomplete, fuzzyFilter } from './autocomplete.js';
7
+ import { getFileIndex } from '../file-index.js';
8
+ /** Inspect the input's last token; return completion context if it's `/` or `@`. */
9
+ function parseCompletion(value) {
10
+ const tokenStart = value.lastIndexOf(' ') + 1;
11
+ const token = value.slice(tokenStart);
12
+ if (token.startsWith('/'))
13
+ return { kind: '/', tokenStart, query: token.slice(1) };
14
+ if (token.startsWith('@'))
15
+ return { kind: '@', tokenStart, query: token.slice(1) };
16
+ return null;
17
+ }
18
+ /**
19
+ * Single-line input (custom TextInput) with a fuzzy autocomplete dropdown +
20
+ * input history. Typing `/` suggests slash commands + skills; typing `@`
21
+ * suggests project files (recursive index, fuzzy-matched). When the dropdown is
22
+ * open, ↑/↓ navigate it; when closed, ↑/↓ recall previous prompts. Tab/Enter
23
+ * accepts; a second Enter submits. Multi-line is P2 (PRD 19).
24
+ */
25
+ export function PromptArea({ value, onChange, onSubmit, onHistoryUp, onHistoryDown, commands, skills }) {
26
+ const theme = useTheme();
27
+ const [selectedIndex, setSelectedIndex] = useState(0);
28
+ const [dismissed, setDismissed] = useState(false);
29
+ const [files, setFiles] = useState(() => getFileIndex());
30
+ const active = parseCompletion(value);
31
+ useEffect(() => {
32
+ setSelectedIndex(0);
33
+ setDismissed(false);
34
+ }, [active?.kind, active?.query]);
35
+ useEffect(() => {
36
+ if (active?.kind === '@')
37
+ setFiles(getFileIndex());
38
+ }, [active?.kind]);
39
+ const matches = active?.kind === '/'
40
+ ? fuzzyFilter([...commands, ...skills], active.query)
41
+ : active?.kind === '@'
42
+ ? fuzzyFilter(files.map((f) => ({ name: f })), active.query)
43
+ : [];
44
+ const showDropdown = !!active && !dismissed && matches.length > 0;
45
+ // ↑/↓ navigate the autocomplete dropdown when it's open; otherwise the
46
+ // TextInput owns ↑/↓ (line navigation + history at the top/bottom edge).
47
+ useInput((inputChar, key) => {
48
+ if (!showDropdown || !active)
49
+ return;
50
+ if (key.return || key.tab || inputChar === '\t') {
51
+ const sel = matches[Math.min(selectedIndex, matches.length - 1)] ?? matches[0];
52
+ if (sel) {
53
+ const completed = (active.kind === '/' ? '/' : '@') + sel.name;
54
+ onChange(value.slice(0, active.tokenStart) + completed + ' ');
55
+ }
56
+ }
57
+ else if (key.upArrow) {
58
+ setSelectedIndex((i) => Math.max(0, i - 1));
59
+ }
60
+ else if (key.downArrow) {
61
+ setSelectedIndex((i) => Math.min(matches.length - 1, i + 1));
62
+ }
63
+ else if (key.escape) {
64
+ setDismissed(true);
65
+ }
66
+ });
67
+ return (_jsxs(Box, { flexDirection: "column", children: [showDropdown && active ? (_jsx(Autocomplete, { suggestions: matches, selectedIndex: selectedIndex, prefix: active.kind })) : null, _jsxs(Box, { borderStyle: "round", borderColor: theme.fgGutter, paddingLeft: 1, paddingRight: 1, children: [_jsx(Text, { color: theme.green, bold: true, children: "\u203A " }), _jsx(TextInput, { value: value, onChange: onChange, onSubmit: onSubmit, ignoreReturn: showDropdown, ignoreArrows: showDropdown, onHistoryUp: onHistoryUp, onHistoryDown: onHistoryDown, placeholder: "Ask Zoe Agent \u2014 type / for commands, @ for files (Shift+Enter newline)" })] })] }));
68
+ }