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.
- package/CHANGELOG.md +154 -0
- package/LICENSE +96 -0
- package/README.md +568 -0
- package/dist/adapters/cli/agent.d.ts +59 -0
- package/dist/adapters/cli/agent.js +232 -0
- package/dist/adapters/cli/bootstrap.d.ts +25 -0
- package/dist/adapters/cli/bootstrap.js +204 -0
- package/dist/adapters/cli/commands/build-registry.d.ts +14 -0
- package/dist/adapters/cli/commands/build-registry.js +88 -0
- package/dist/adapters/cli/commands/clear.d.ts +7 -0
- package/dist/adapters/cli/commands/clear.js +10 -0
- package/dist/adapters/cli/commands/compact.d.ts +13 -0
- package/dist/adapters/cli/commands/compact.js +96 -0
- package/dist/adapters/cli/commands/exit.d.ts +7 -0
- package/dist/adapters/cli/commands/exit.js +9 -0
- package/dist/adapters/cli/commands/gateway.d.ts +7 -0
- package/dist/adapters/cli/commands/gateway.js +152 -0
- package/dist/adapters/cli/commands/help.d.ts +9 -0
- package/dist/adapters/cli/commands/help.js +12 -0
- package/dist/adapters/cli/commands/models.d.ts +10 -0
- package/dist/adapters/cli/commands/models.js +32 -0
- package/dist/adapters/cli/commands/registry.d.ts +70 -0
- package/dist/adapters/cli/commands/registry.js +111 -0
- package/dist/adapters/cli/commands/settings-utils.d.ts +38 -0
- package/dist/adapters/cli/commands/settings-utils.js +182 -0
- package/dist/adapters/cli/commands/settings.d.ts +9 -0
- package/dist/adapters/cli/commands/settings.js +395 -0
- package/dist/adapters/cli/commands/skills.d.ts +7 -0
- package/dist/adapters/cli/commands/skills.js +21 -0
- package/dist/adapters/cli/config-loader.d.ts +27 -0
- package/dist/adapters/cli/config-loader.js +48 -0
- package/dist/adapters/cli/docker-utils.d.ts +37 -0
- package/dist/adapters/cli/docker-utils.js +90 -0
- package/dist/adapters/cli/index.d.ts +2 -0
- package/dist/adapters/cli/index.js +88 -0
- package/dist/adapters/cli/repl.d.ts +22 -0
- package/dist/adapters/cli/repl.js +256 -0
- package/dist/adapters/cli/setup.d.ts +19 -0
- package/dist/adapters/cli/setup.js +613 -0
- package/dist/adapters/cli/system-prompts.d.ts +56 -0
- package/dist/adapters/cli/system-prompts.js +131 -0
- package/dist/adapters/cli/tui/app.d.ts +58 -0
- package/dist/adapters/cli/tui/app.js +314 -0
- package/dist/adapters/cli/tui/components/assistant-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/assistant-message.js +9 -0
- package/dist/adapters/cli/tui/components/autocomplete.d.ts +19 -0
- package/dist/adapters/cli/tui/components/autocomplete.js +75 -0
- package/dist/adapters/cli/tui/components/command-palette.d.ts +15 -0
- package/dist/adapters/cli/tui/components/command-palette.js +50 -0
- package/dist/adapters/cli/tui/components/diff-viewer.d.ts +5 -0
- package/dist/adapters/cli/tui/components/diff-viewer.js +109 -0
- package/dist/adapters/cli/tui/components/error-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/error-message.js +8 -0
- package/dist/adapters/cli/tui/components/footer.d.ts +20 -0
- package/dist/adapters/cli/tui/components/footer.js +19 -0
- package/dist/adapters/cli/tui/components/goal-status.d.ts +12 -0
- package/dist/adapters/cli/tui/components/goal-status.js +22 -0
- package/dist/adapters/cli/tui/components/info-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/info-message.js +8 -0
- package/dist/adapters/cli/tui/components/logo-banner.d.ts +7 -0
- package/dist/adapters/cli/tui/components/logo-banner.js +33 -0
- package/dist/adapters/cli/tui/components/markdown.d.ts +9 -0
- package/dist/adapters/cli/tui/components/markdown.js +92 -0
- package/dist/adapters/cli/tui/components/message-area.d.ts +19 -0
- package/dist/adapters/cli/tui/components/message-area.js +55 -0
- package/dist/adapters/cli/tui/components/permission-prompt.d.ts +13 -0
- package/dist/adapters/cli/tui/components/permission-prompt.js +32 -0
- package/dist/adapters/cli/tui/components/prompt-area.d.ts +22 -0
- package/dist/adapters/cli/tui/components/prompt-area.js +68 -0
- package/dist/adapters/cli/tui/components/text-input.d.ts +27 -0
- package/dist/adapters/cli/tui/components/text-input.js +142 -0
- package/dist/adapters/cli/tui/components/tool-call-block.d.ts +11 -0
- package/dist/adapters/cli/tui/components/tool-call-block.js +68 -0
- package/dist/adapters/cli/tui/components/user-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/user-message.js +8 -0
- package/dist/adapters/cli/tui/diff/file-write-meta.d.ts +11 -0
- package/dist/adapters/cli/tui/diff/file-write-meta.js +11 -0
- package/dist/adapters/cli/tui/diff/line-diff.d.ts +17 -0
- package/dist/adapters/cli/tui/diff/line-diff.js +44 -0
- package/dist/adapters/cli/tui/feed-serializer.d.ts +29 -0
- package/dist/adapters/cli/tui/feed-serializer.js +70 -0
- package/dist/adapters/cli/tui/file-index.d.ts +8 -0
- package/dist/adapters/cli/tui/file-index.js +41 -0
- package/dist/adapters/cli/tui/hooks/use-agent.d.ts +54 -0
- package/dist/adapters/cli/tui/hooks/use-agent.js +177 -0
- package/dist/adapters/cli/tui/hooks/use-feed.d.ts +16 -0
- package/dist/adapters/cli/tui/hooks/use-feed.js +25 -0
- package/dist/adapters/cli/tui/hooks/use-file-watcher.d.ts +10 -0
- package/dist/adapters/cli/tui/hooks/use-file-watcher.js +43 -0
- package/dist/adapters/cli/tui/hooks/use-keybindings.d.ts +16 -0
- package/dist/adapters/cli/tui/hooks/use-keybindings.js +25 -0
- package/dist/adapters/cli/tui/hooks/use-theme.d.ts +8 -0
- package/dist/adapters/cli/tui/hooks/use-theme.js +12 -0
- package/dist/adapters/cli/tui/index.d.ts +19 -0
- package/dist/adapters/cli/tui/index.js +206 -0
- package/dist/adapters/cli/tui/ink-reset.d.ts +29 -0
- package/dist/adapters/cli/tui/ink-reset.js +57 -0
- package/dist/adapters/cli/tui/layout.d.ts +15 -0
- package/dist/adapters/cli/tui/layout.js +15 -0
- package/dist/adapters/cli/tui/logo/gradient.d.ts +11 -0
- package/dist/adapters/cli/tui/logo/gradient.js +31 -0
- package/dist/adapters/cli/tui/overlays/help-dialog.d.ts +4 -0
- package/dist/adapters/cli/tui/overlays/help-dialog.js +26 -0
- package/dist/adapters/cli/tui/overlays/model-selector.d.ts +14 -0
- package/dist/adapters/cli/tui/overlays/model-selector.js +43 -0
- package/dist/adapters/cli/tui/overlays/session-selector.d.ts +35 -0
- package/dist/adapters/cli/tui/overlays/session-selector.js +162 -0
- package/dist/adapters/cli/tui/overlays/settings-overlay.d.ts +24 -0
- package/dist/adapters/cli/tui/overlays/settings-overlay.js +126 -0
- package/dist/adapters/cli/tui/session-export.d.ts +21 -0
- package/dist/adapters/cli/tui/session-export.js +63 -0
- package/dist/adapters/cli/tui/theme.d.ts +23 -0
- package/dist/adapters/cli/tui/theme.js +22 -0
- package/dist/adapters/cli/tui/types.d.ts +52 -0
- package/dist/adapters/cli/tui/types.js +12 -0
- package/dist/adapters/sdk/agent.d.ts +20 -0
- package/dist/adapters/sdk/agent.js +356 -0
- package/dist/adapters/sdk/http.d.ts +43 -0
- package/dist/adapters/sdk/http.js +61 -0
- package/dist/adapters/sdk/index.d.ts +58 -0
- package/dist/adapters/sdk/index.js +209 -0
- package/dist/adapters/sdk/settings.d.ts +18 -0
- package/dist/adapters/sdk/settings.js +57 -0
- package/dist/adapters/sdk/tools.d.ts +7 -0
- package/dist/adapters/sdk/tools.js +13 -0
- package/dist/adapters/server/auth.d.ts +53 -0
- package/dist/adapters/server/auth.js +168 -0
- package/dist/adapters/server/index.d.ts +40 -0
- package/dist/adapters/server/index.js +255 -0
- package/dist/adapters/server/rest-gateway.d.ts +13 -0
- package/dist/adapters/server/rest-gateway.js +218 -0
- package/dist/adapters/server/rest.d.ts +37 -0
- package/dist/adapters/server/rest.js +341 -0
- package/dist/adapters/server/server-core.d.ts +55 -0
- package/dist/adapters/server/server-core.js +121 -0
- package/dist/adapters/server/session-store.d.ts +81 -0
- package/dist/adapters/server/session-store.js +272 -0
- package/dist/adapters/server/settings-handlers.d.ts +24 -0
- package/dist/adapters/server/settings-handlers.js +360 -0
- package/dist/adapters/server/standalone.d.ts +19 -0
- package/dist/adapters/server/standalone.js +113 -0
- package/dist/adapters/server/websocket.d.ts +26 -0
- package/dist/adapters/server/websocket.js +68 -0
- package/dist/adapters/server/ws-handlers.d.ts +32 -0
- package/dist/adapters/server/ws-handlers.js +523 -0
- package/dist/adapters/server/ws-types.d.ts +304 -0
- package/dist/adapters/server/ws-types.js +7 -0
- package/dist/core/agent-loop.d.ts +68 -0
- package/dist/core/agent-loop.js +423 -0
- package/dist/core/config.d.ts +115 -0
- package/dist/core/config.js +189 -0
- package/dist/core/errors.d.ts +58 -0
- package/dist/core/errors.js +88 -0
- package/dist/core/hooks.d.ts +35 -0
- package/dist/core/hooks.js +49 -0
- package/dist/core/index.d.ts +23 -0
- package/dist/core/index.js +29 -0
- package/dist/core/message-convert.d.ts +41 -0
- package/dist/core/message-convert.js +94 -0
- package/dist/core/middleware/auth.d.ts +24 -0
- package/dist/core/middleware/auth.js +28 -0
- package/dist/core/middleware/logging.d.ts +23 -0
- package/dist/core/middleware/logging.js +28 -0
- package/dist/core/middleware/rate-limit.d.ts +27 -0
- package/dist/core/middleware/rate-limit.js +38 -0
- package/dist/core/middleware/semantic-tools.d.ts +10 -0
- package/dist/core/middleware/semantic-tools.js +43 -0
- package/dist/core/middleware.d.ts +48 -0
- package/dist/core/middleware.js +38 -0
- package/dist/core/permission.d.ts +25 -0
- package/dist/core/permission.js +50 -0
- package/dist/core/provider-config.d.ts +129 -0
- package/dist/core/provider-config.js +273 -0
- package/dist/core/provider-env.d.ts +39 -0
- package/dist/core/provider-env.js +142 -0
- package/dist/core/provider-resolver.d.ts +12 -0
- package/dist/core/provider-resolver.js +12 -0
- package/dist/core/session-store.d.ts +75 -0
- package/dist/core/session-store.js +245 -0
- package/dist/core/settings-manager.d.ts +57 -0
- package/dist/core/settings-manager.js +359 -0
- package/dist/core/settings-schema.d.ts +38 -0
- package/dist/core/settings-schema.js +171 -0
- package/dist/core/skill-catalog.d.ts +6 -0
- package/dist/core/skill-catalog.js +17 -0
- package/dist/core/skill-invoker.d.ts +127 -0
- package/dist/core/skill-invoker.js +182 -0
- package/dist/core/stream-accumulator.d.ts +21 -0
- package/dist/core/stream-accumulator.js +51 -0
- package/dist/core/stream-manager.d.ts +58 -0
- package/dist/core/stream-manager.js +212 -0
- package/dist/core/tool-executor.d.ts +84 -0
- package/dist/core/tool-executor.js +256 -0
- package/dist/core/types.d.ts +259 -0
- package/dist/core/types.js +11 -0
- package/dist/gateway/gateway.d.ts +52 -0
- package/dist/gateway/gateway.js +537 -0
- package/dist/gateway/index.d.ts +21 -0
- package/dist/gateway/index.js +31 -0
- package/dist/gateway/openapi-importer.d.ts +15 -0
- package/dist/gateway/openapi-importer.js +66 -0
- package/dist/gateway/semantic-scorer.d.ts +7 -0
- package/dist/gateway/semantic-scorer.js +24 -0
- package/dist/gateway/settings-adapter.d.ts +49 -0
- package/dist/gateway/settings-adapter.js +137 -0
- package/dist/gateway/tool-factory.d.ts +9 -0
- package/dist/gateway/tool-factory.js +414 -0
- package/dist/gateway/types.d.ts +68 -0
- package/dist/gateway/types.js +7 -0
- package/dist/models-catalog.js +46 -0
- package/dist/providers/anthropic.d.ts +22 -0
- package/dist/providers/anthropic.js +148 -0
- package/dist/providers/factory.d.ts +10 -0
- package/dist/providers/factory.js +25 -0
- package/dist/providers/openai.d.ts +15 -0
- package/dist/providers/openai.js +71 -0
- package/dist/providers/types.d.ts +48 -0
- package/dist/providers/types.js +1 -0
- package/dist/skills/args.d.ts +37 -0
- package/dist/skills/args.js +99 -0
- package/dist/skills/index.d.ts +11 -0
- package/dist/skills/index.js +23 -0
- package/dist/skills/loader.d.ts +3 -0
- package/dist/skills/loader.js +59 -0
- package/dist/skills/parser.d.ts +7 -0
- package/dist/skills/parser.js +152 -0
- package/dist/skills/registry.d.ts +13 -0
- package/dist/skills/registry.js +74 -0
- package/dist/skills/resolver.d.ts +19 -0
- package/dist/skills/resolver.js +116 -0
- package/dist/skills/types.d.ts +74 -0
- package/dist/skills/types.js +50 -0
- package/dist/tools/browser.d.ts +2 -0
- package/dist/tools/browser.js +68 -0
- package/dist/tools/core.d.ts +20 -0
- package/dist/tools/core.js +244 -0
- package/dist/tools/email.d.ts +2 -0
- package/dist/tools/email.js +61 -0
- package/dist/tools/image.d.ts +2 -0
- package/dist/tools/image.js +257 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +88 -0
- package/dist/tools/interface.d.ts +22 -0
- package/dist/tools/interface.js +1 -0
- package/dist/tools/notify.d.ts +2 -0
- package/dist/tools/notify.js +100 -0
- package/dist/tools/prompt-optimizer.d.ts +2 -0
- package/dist/tools/prompt-optimizer.js +65 -0
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +184 -0
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +78 -0
- package/dist/tools/todos.d.ts +10 -0
- package/dist/tools/todos.js +50 -0
- package/package.json +119 -0
- package/skills/docker-ops/SKILL.md +329 -0
- package/skills/k8s-deploy/SKILL.md +397 -0
- package/skills/log-analyzer/SKILL.md +331 -0
- package/skills/speckit-analyze/SKILL.md +260 -0
- package/skills/speckit-checklist/SKILL.md +374 -0
- package/skills/speckit-clarify/SKILL.md +286 -0
- package/skills/speckit-constitution/SKILL.md +157 -0
- package/skills/speckit-implement/SKILL.md +224 -0
- package/skills/speckit-plan/SKILL.md +171 -0
- package/skills/speckit-specify/SKILL.md +346 -0
- package/skills/speckit-tasks/SKILL.md +215 -0
- 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,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,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,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
|
+
}
|