kritya 0.8.2-beta
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/LICENSE +21 -0
- package/README.md +434 -0
- package/dist/agent/budget.js +25 -0
- package/dist/agent/compactor.js +78 -0
- package/dist/agent/contextWarning.js +6 -0
- package/dist/agent/killSwitch.js +111 -0
- package/dist/agent/loop.js +543 -0
- package/dist/agent/memory.js +145 -0
- package/dist/agent/plugins.js +408 -0
- package/dist/agent/skills.js +210 -0
- package/dist/agent/skillsCli.js +51 -0
- package/dist/agent/systemPrompt.js +122 -0
- package/dist/agent/tokens.js +55 -0
- package/dist/agent/toolExecutor.js +331 -0
- package/dist/agent/workflow.js +496 -0
- package/dist/agent/worktree.js +116 -0
- package/dist/atomicWrite.js +109 -0
- package/dist/audit/audit.js +230 -0
- package/dist/audit/cli.js +135 -0
- package/dist/commands/custom.js +69 -0
- package/dist/commands/mcpCommand.js +433 -0
- package/dist/commands/pluginsCommand.js +204 -0
- package/dist/commands/registry.js +739 -0
- package/dist/config/config.js +214 -0
- package/dist/config/debug.js +19 -0
- package/dist/config/models.js +82 -0
- package/dist/config/retention.js +24 -0
- package/dist/config/winAcl.js +38 -0
- package/dist/crash.js +102 -0
- package/dist/engine.js +113 -0
- package/dist/git/git.js +46 -0
- package/dist/headless.js +274 -0
- package/dist/hooks/hooks.js +122 -0
- package/dist/index.js +670 -0
- package/dist/lsp/client.js +413 -0
- package/dist/lsp/manager.js +0 -0
- package/dist/lsp/registry.js +62 -0
- package/dist/mcp/callback.js +141 -0
- package/dist/mcp/client.js +945 -0
- package/dist/mcp/login.js +117 -0
- package/dist/mcp/oauth.js +345 -0
- package/dist/mcp/servers.js +114 -0
- package/dist/mcp/spawnWin.js +86 -0
- package/dist/mcp/tokens.js +97 -0
- package/dist/mcp/transport.js +295 -0
- package/dist/net/urlSafety.js +158 -0
- package/dist/permissions/danger.js +96 -0
- package/dist/permissions/permissions.js +62 -0
- package/dist/permissions/rules.js +69 -0
- package/dist/plugins/discover.js +108 -0
- package/dist/plugins/mcp.js +80 -0
- package/dist/provider/client.js +460 -0
- package/dist/provider/switchyardClient.js +46 -0
- package/dist/provider/switchyardSidecar.js +245 -0
- package/dist/provider/textToolCalls.js +140 -0
- package/dist/repomap/repoMap.js +168 -0
- package/dist/repomap/symbols.js +190 -0
- package/dist/session/store.js +328 -0
- package/dist/shell/background.js +131 -0
- package/dist/shell/sandbox.js +396 -0
- package/dist/telemetry/metrics.js +172 -0
- package/dist/telemetry/otlp.js +128 -0
- package/dist/telemetry/tracer.js +240 -0
- package/dist/tools/askUser.js +64 -0
- package/dist/tools/bg.js +59 -0
- package/dist/tools/common.js +158 -0
- package/dist/tools/deepResearch.js +126 -0
- package/dist/tools/diff.js +70 -0
- package/dist/tools/document/docx.js +44 -0
- package/dist/tools/document/pdf.js +190 -0
- package/dist/tools/document/pptx.js +148 -0
- package/dist/tools/document/types.js +26 -0
- package/dist/tools/document/xlsx.js +87 -0
- package/dist/tools/document.js +389 -0
- package/dist/tools/edit.js +56 -0
- package/dist/tools/fetchUrl.js +218 -0
- package/dist/tools/fuzzyMatch.js +74 -0
- package/dist/tools/glob.js +34 -0
- package/dist/tools/grep.js +85 -0
- package/dist/tools/ignore.js +28 -0
- package/dist/tools/index.js +66 -0
- package/dist/tools/ls.js +27 -0
- package/dist/tools/lsp.js +239 -0
- package/dist/tools/notebook.js +215 -0
- package/dist/tools/read.js +32 -0
- package/dist/tools/repoMap.js +26 -0
- package/dist/tools/secretScan.js +143 -0
- package/dist/tools/shell.js +134 -0
- package/dist/tools/skills.js +69 -0
- package/dist/tools/subagent.js +57 -0
- package/dist/tools/tasks.js +43 -0
- package/dist/tools/webSearch.js +97 -0
- package/dist/tools/write.js +47 -0
- package/dist/tools/writeAgent.js +72 -0
- package/dist/trust/aiDisclosure.js +45 -0
- package/dist/trust/mcpTrust.js +111 -0
- package/dist/trust/trust.js +231 -0
- package/dist/types.js +1 -0
- package/dist/ui/AiDisclosurePrompt.js +23 -0
- package/dist/ui/App.js +449 -0
- package/dist/ui/Banner.js +71 -0
- package/dist/ui/ElicitationPrompt.js +46 -0
- package/dist/ui/Markdown.js +148 -0
- package/dist/ui/McpTrustPrompt.js +42 -0
- package/dist/ui/ModelPicker.js +19 -0
- package/dist/ui/PermissionPrompt.js +27 -0
- package/dist/ui/SelectList.js +24 -0
- package/dist/ui/Spinner.js +12 -0
- package/dist/ui/StatusLine.js +11 -0
- package/dist/ui/TranscriptItem.js +13 -0
- package/dist/ui/TrustPrompt.js +12 -0
- package/dist/ui/highlight.js +137 -0
- package/dist/ui/inline.js +241 -0
- package/dist/ui/mermaid.js +98 -0
- package/dist/ui/table.js +148 -0
- package/dist/ui/toolOutputPreview.js +34 -0
- package/dist/ui/useAgent.js +534 -0
- package/dist/ui/useKillSwitch.js +65 -0
- package/dist/ui/useSessionResume.js +39 -0
- package/dist/ui/useUsageBudget.js +149 -0
- package/dist/ui/viewport.js +71 -0
- package/dist/undo/undo.js +293 -0
- package/dist/version.js +4 -0
- package/package.json +122 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useStdout } from "ink";
|
|
4
|
+
import stringWidth from "string-width";
|
|
5
|
+
import { tokenizeLine } from "./highlight.js";
|
|
6
|
+
import { parseInline, tokensWidth, wrapInline } from "./inline.js";
|
|
7
|
+
import { parseMermaidTree, renderMermaidTree } from "./mermaid.js";
|
|
8
|
+
import { terminalColumns } from "./viewport.js";
|
|
9
|
+
import { COLUMN_GAP, columnWidths, detectTable, dropPartialTrailingTable, isTableRow, shouldStack, } from "./table.js";
|
|
10
|
+
const TOKEN_COLORS = {
|
|
11
|
+
keyword: "magenta",
|
|
12
|
+
string: "green",
|
|
13
|
+
comment: "gray",
|
|
14
|
+
number: "cyan",
|
|
15
|
+
plain: undefined,
|
|
16
|
+
};
|
|
17
|
+
function CodeLine({ line }) {
|
|
18
|
+
if (!line)
|
|
19
|
+
return _jsx(Text, { children: " " });
|
|
20
|
+
return (_jsx(Text, { children: tokenizeLine(line).map((t, i) => (_jsx(Text, { color: TOKEN_COLORS[t.kind], bold: t.kind === "keyword", children: t.text }, i))) }));
|
|
21
|
+
}
|
|
22
|
+
function Inline({ tokens }) {
|
|
23
|
+
if (!tokens.length)
|
|
24
|
+
return _jsx(_Fragment, { children: " " });
|
|
25
|
+
return (_jsx(_Fragment, { children: tokens.map((t, i) => (_jsx(Text, { bold: t.bold, italic: t.italic, dimColor: t.dim, color: t.code ? "yellow" : undefined, children: t.text }, i))) }));
|
|
26
|
+
}
|
|
27
|
+
function InlineText({ text, ...style }) {
|
|
28
|
+
return (_jsx(Text, { ...style, children: _jsx(Inline, { tokens: parseInline(text) }) }));
|
|
29
|
+
}
|
|
30
|
+
/** Wrapped text with a marker on the first line and a hanging indent under it. */
|
|
31
|
+
function Marked({ text, width, marker, indent, ...style }) {
|
|
32
|
+
const lines = wrapInline(text, width - stringWidth(marker));
|
|
33
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((tokens, i) => (_jsxs(Text, { ...style, children: [i === 0 ? marker : indent, _jsx(Inline, { tokens: tokens })] }, i))) }));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Minimal terminal markdown: fenced code blocks (syntax-highlighted), headers,
|
|
37
|
+
* bullets, quotes, rules, tables, and inline emphasis. Deliberately
|
|
38
|
+
* lightweight — not a full implementation.
|
|
39
|
+
*/
|
|
40
|
+
export function Markdown({ text, streaming = false }) {
|
|
41
|
+
const { stdout } = useStdout();
|
|
42
|
+
const width = Math.max(20, terminalColumns(stdout) - 2);
|
|
43
|
+
const blocks = React.useMemo(() => renderBlocks(text, width, streaming), [text, width, streaming]);
|
|
44
|
+
return _jsx(Box, { flexDirection: "column", children: blocks });
|
|
45
|
+
}
|
|
46
|
+
function renderBlocks(text, width, streaming) {
|
|
47
|
+
let lines = text.split("\n");
|
|
48
|
+
if (streaming && !hasOpenFence(lines))
|
|
49
|
+
lines = dropPartialTrailingTable(lines);
|
|
50
|
+
const blocks = [];
|
|
51
|
+
let inCode = false;
|
|
52
|
+
let codeLang = "";
|
|
53
|
+
let codeLines = [];
|
|
54
|
+
let key = 0;
|
|
55
|
+
const flushCode = () => {
|
|
56
|
+
if (!codeLines.length)
|
|
57
|
+
return;
|
|
58
|
+
if (codeLang === "mermaid") {
|
|
59
|
+
const tree = parseMermaidTree(codeLines);
|
|
60
|
+
if (tree) {
|
|
61
|
+
const treeLines = renderMermaidTree(tree);
|
|
62
|
+
blocks.push(_jsx(Box, { flexDirection: "column", children: treeLines.map((l, i) => (_jsx(Text, { children: l }, i))) }, key++));
|
|
63
|
+
codeLines = [];
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Size the frame to the code, not to the terminal — a two-line snippet
|
|
68
|
+
// in a wide terminal was drawing a box the full width of the screen.
|
|
69
|
+
const longest = Math.max(...codeLines.map((l) => stringWidth(l)));
|
|
70
|
+
blocks.push(_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, width: Math.min(width, longest + 4), children: codeLines.map((l, i) => (_jsx(CodeLine, { line: l }, i))) }, key++));
|
|
71
|
+
codeLines = [];
|
|
72
|
+
};
|
|
73
|
+
for (let i = 0; i < lines.length; i++) {
|
|
74
|
+
const line = lines[i];
|
|
75
|
+
if (line.trimStart().startsWith("```")) {
|
|
76
|
+
if (inCode) {
|
|
77
|
+
flushCode();
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
codeLang = line.trim().slice(3).trim().toLowerCase();
|
|
81
|
+
}
|
|
82
|
+
inCode = !inCode;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (inCode) {
|
|
86
|
+
codeLines.push(line);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (isTableRow(line)) {
|
|
90
|
+
const found = detectTable(lines, i);
|
|
91
|
+
if (found) {
|
|
92
|
+
blocks.push(_jsx(TableBlock, { table: found.table, width: width }, key++));
|
|
93
|
+
i = found.next - 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
|
|
98
|
+
blocks.push(_jsx(Text, { dimColor: true, children: "─".repeat(width) }, key++));
|
|
99
|
+
}
|
|
100
|
+
else if (/^#{1,6}\s/.test(line)) {
|
|
101
|
+
blocks.push(_jsx(InlineText, { text: line.replace(/^#{1,6}\s/, ""), bold: true, color: "magenta" }, key++));
|
|
102
|
+
}
|
|
103
|
+
else if (/^\s*>\s?/.test(line)) {
|
|
104
|
+
blocks.push(_jsx(Marked, { text: line.replace(/^\s*>\s?/, ""), width: width, marker: "\u2502 ", indent: "\u2502 ", dimColor: true }, key++));
|
|
105
|
+
}
|
|
106
|
+
else if (/^\s*[-*]\s/.test(line)) {
|
|
107
|
+
const [, pad, rest] = /^(\s*)[-*]\s+([\s\S]*)$/.exec(line);
|
|
108
|
+
blocks.push(_jsx(Marked, { text: rest, width: width, marker: `${pad}• `, indent: `${pad} ` }, key++));
|
|
109
|
+
}
|
|
110
|
+
else if (/^\s*\d{1,3}[.)]\s/.test(line)) {
|
|
111
|
+
const [, pad, number, rest] = /^(\s*)(\d{1,3}[.)])\s+([\s\S]*)$/.exec(line);
|
|
112
|
+
blocks.push(_jsx(Marked, { text: rest, width: width, marker: `${pad}${number} `, indent: `${pad}${" ".repeat(number.length + 1)}` }, key++));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Paragraphs wrap through the same path as everything else: Ink's own
|
|
116
|
+
// wrapping carries the break's space onto the next line, which shows up
|
|
117
|
+
// as a stray leading space at column zero.
|
|
118
|
+
const [, pad, rest] = /^(\s*)([\s\S]*)$/.exec(line);
|
|
119
|
+
blocks.push(_jsx(Marked, { text: rest || " ", width: width, marker: pad, indent: pad }, key++));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (inCode)
|
|
123
|
+
flushCode();
|
|
124
|
+
return blocks;
|
|
125
|
+
}
|
|
126
|
+
function hasOpenFence(lines) {
|
|
127
|
+
return lines.filter((l) => l.trimStart().startsWith("```")).length % 2 === 1;
|
|
128
|
+
}
|
|
129
|
+
function TableBlock({ table, width }) {
|
|
130
|
+
if (shouldStack(table, width))
|
|
131
|
+
return _jsx(StackedTable, { table: table, width: width });
|
|
132
|
+
const widths = columnWidths(table, width);
|
|
133
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Row, { cells: table.header, widths: widths, align: table.align, bold: true }), _jsx(Text, { dimColor: true, children: widths.map((w) => "─".repeat(w)).join(" ".repeat(COLUMN_GAP)) }), table.rows.map((cells, i) => (_jsx(Row, { cells: cells, widths: widths, align: table.align }, i)))] }));
|
|
134
|
+
}
|
|
135
|
+
function Row({ cells, widths, align, bold, }) {
|
|
136
|
+
const wrapped = widths.map((w, i) => wrapInline(cells[i] ?? "", w));
|
|
137
|
+
const height = Math.max(1, ...wrapped.map((lines) => lines.length));
|
|
138
|
+
return (_jsx(Box, { flexDirection: "row", children: wrapped.map((lines, col) => (_jsx(Box, { flexDirection: "column", width: widths[col], marginRight: col < widths.length - 1 ? COLUMN_GAP : 0, children: Array.from({ length: height }, (_, r) => {
|
|
139
|
+
const tokens = lines[r] ?? [];
|
|
140
|
+
const pad = Math.max(0, widths[col] - tokensWidth(tokens));
|
|
141
|
+
const lead = align[col] === "right" ? pad : align[col] === "center" ? Math.floor(pad / 2) : 0;
|
|
142
|
+
return (_jsxs(Text, { bold: bold, wrap: "truncate", children: [lead ? " ".repeat(lead) : "", tokens.length ? _jsx(Inline, { tokens: tokens }) : " "] }, r));
|
|
143
|
+
}) }, col))) }));
|
|
144
|
+
}
|
|
145
|
+
/** Too narrow (or too many columns) for a grid: one labelled block per row. */
|
|
146
|
+
function StackedTable({ table, width }) {
|
|
147
|
+
return (_jsx(Box, { flexDirection: "column", children: table.rows.map((cells, i) => (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: cells.map((cell, col) => cell ? (_jsxs(Box, { flexDirection: "column", children: [table.header[col] ? _jsx(InlineText, { text: table.header[col], bold: true }) : null, wrapInline(cell, Math.max(1, width - 2)).map((tokens, r) => (_jsxs(Text, { children: [" ", _jsx(Inline, { tokens: tokens })] }, r)))] }, col)) : null) }, i))) }));
|
|
148
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { SelectList } from "./SelectList.js";
|
|
5
|
+
/**
|
|
6
|
+
* First-use approval for servers declared by the workspace's .mcp.json.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately one server at a time. Approving the whole file as a batch means
|
|
9
|
+
* that a branch adding a useful server and a hostile one offers only "both" or
|
|
10
|
+
* "neither" — and someone who wants the useful one takes both. Per-server, the
|
|
11
|
+
* hostile one can be refused on its own, so the safe answer stops costing
|
|
12
|
+
* anything.
|
|
13
|
+
*/
|
|
14
|
+
function targetFor(cfg) {
|
|
15
|
+
return cfg.url ?? [cfg.command, ...(cfg.args ?? [])].filter(Boolean).join(" ");
|
|
16
|
+
}
|
|
17
|
+
export function McpTrustPrompt({ servers, onComplete, }) {
|
|
18
|
+
const names = Object.keys(servers);
|
|
19
|
+
const [index, setIndex] = useState(0);
|
|
20
|
+
const [approved, setApproved] = useState([]);
|
|
21
|
+
const name = names[index];
|
|
22
|
+
const cfg = servers[name];
|
|
23
|
+
const isRemote = Boolean(cfg.url);
|
|
24
|
+
const decide = (trust) => {
|
|
25
|
+
const next = trust ? [...approved, name] : approved;
|
|
26
|
+
if (index + 1 < names.length) {
|
|
27
|
+
setApproved(next);
|
|
28
|
+
setIndex(index + 1);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
onComplete(next);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["New MCP server", names.length > 1 ? ` (${index + 1} of ${names.length})` : ""] }), _jsx(Text, { children: "This workspace's .mcp.json declares a server not previously approved. It will run as a child process (or contact a remote endpoint) with your credentials, and its tools may be called automatically. Review before trusting." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: name }), _jsx(Text, { dimColor: true, children: targetFor(cfg) }), isRemote ? (_jsx(Text, { color: "yellow", children: "\u26A0 remote endpoint \u2014 requests, and any token you hold for it, leave this machine" })) : null, cfg.cwd ? _jsxs(Text, { dimColor: true, children: ["runs in: ", cfg.cwd] }) : null] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectList
|
|
35
|
+
// Keyed by name so the highlight resets to the top for each server:
|
|
36
|
+
// holding Enter through a list shouldn't carry a "trust" choice
|
|
37
|
+
// forward onto a server the user hasn't looked at.
|
|
38
|
+
, { items: [
|
|
39
|
+
{ label: `Trust and load "${name}"`, value: "trust" },
|
|
40
|
+
{ label: "Don't trust (skip it this session)", value: "no" },
|
|
41
|
+
], onSelect: (v) => decide(v === "trust"), onCancel: () => onComplete(approved) }, name) })] }));
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { CURATED_MODELS } from "../config/models.js";
|
|
4
|
+
import { SelectList } from "./SelectList.js";
|
|
5
|
+
export function ModelPicker({ current, customModels, onSelect, onCancel, }) {
|
|
6
|
+
const items = [
|
|
7
|
+
...CURATED_MODELS.map((m) => ({
|
|
8
|
+
label: m.label + (m.id === current ? " (current)" : ""),
|
|
9
|
+
value: m.id,
|
|
10
|
+
hint: m.note ? `${m.id} · ${m.note}` : m.id,
|
|
11
|
+
})),
|
|
12
|
+
...customModels.map((m) => ({
|
|
13
|
+
label: (m.label ?? m.id) + (m.id === current ? " (current)" : ""),
|
|
14
|
+
value: m.id,
|
|
15
|
+
hint: `${m.id} · custom`,
|
|
16
|
+
})),
|
|
17
|
+
];
|
|
18
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "magenta", children: ["Select model", " ", _jsx(Text, { dimColor: true, children: "(Esc to cancel, or /model <id> for any NVIDIA model)" })] }), _jsx(SelectList, { items: items, onSelect: onSelect, onCancel: onCancel })] }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { SelectList } from "./SelectList.js";
|
|
4
|
+
const MAX_DIFF_LINES = 30;
|
|
5
|
+
export function PermissionPrompt({ toolName, summary, diff, warning, onDecision, }) {
|
|
6
|
+
useInput((input) => {
|
|
7
|
+
const c = input.toLowerCase();
|
|
8
|
+
if (c === "y")
|
|
9
|
+
onDecision("yes");
|
|
10
|
+
else if (c === "a" && !warning)
|
|
11
|
+
onDecision("always");
|
|
12
|
+
else if (c === "n")
|
|
13
|
+
onDecision("no");
|
|
14
|
+
});
|
|
15
|
+
const diffLines = diff ? diff.split("\n") : [];
|
|
16
|
+
const shown = diffLines.slice(0, MAX_DIFF_LINES);
|
|
17
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: warning ? "red" : "yellow", paddingX: 1, children: [_jsxs(Text, { bold: true, color: warning ? "red" : "yellow", children: [warning ? "⚠ Dangerous command" : "Permission required", ": ", toolName] }), warning && _jsxs(Text, { color: "red", children: ["This looks like ", warning, ". Review carefully."] }), _jsx(Text, { children: summary }), shown.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [shown.map((line, i) => (_jsx(Text, { color: line.startsWith("+") ? "green" : line.startsWith("-") ? "red" : undefined, dimColor: !line.startsWith("+") && !line.startsWith("-"), children: line || " " }, i))), diffLines.length > MAX_DIFF_LINES && (_jsxs(Text, { dimColor: true, children: ["\u2026 (", diffLines.length - MAX_DIFF_LINES, " more lines)"] }))] })), _jsx(Box, { marginTop: 1, children: _jsx(SelectList, { items: warning
|
|
18
|
+
? [
|
|
19
|
+
{ label: "Yes, run it once (y)", value: "yes" },
|
|
20
|
+
{ label: "No (n)", value: "no" },
|
|
21
|
+
]
|
|
22
|
+
: [
|
|
23
|
+
{ label: "Yes (y)", value: "yes" },
|
|
24
|
+
{ label: `Yes, always allow ${toolName} this session (a)`, value: "always" },
|
|
25
|
+
{ label: "No (n)", value: "no" },
|
|
26
|
+
], onSelect: (v) => onDecision(v), onCancel: () => onDecision("no") }) })] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
export function SelectList({ items, onSelect, onCancel, }) {
|
|
5
|
+
const [rawIndex, setIndex] = useState(0);
|
|
6
|
+
// Items can shrink under us (e.g. a live filter above the list).
|
|
7
|
+
const index = items.length ? Math.min(rawIndex, items.length - 1) : 0;
|
|
8
|
+
useInput((_input, key) => {
|
|
9
|
+
if (!items.length) {
|
|
10
|
+
if (key.escape && onCancel)
|
|
11
|
+
onCancel();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (key.upArrow)
|
|
15
|
+
setIndex((index - 1 + items.length) % items.length);
|
|
16
|
+
else if (key.downArrow)
|
|
17
|
+
setIndex((index + 1) % items.length);
|
|
18
|
+
else if (key.return)
|
|
19
|
+
onSelect(items[index].value);
|
|
20
|
+
else if (key.escape && onCancel)
|
|
21
|
+
onCancel();
|
|
22
|
+
});
|
|
23
|
+
return (_jsx(Box, { flexDirection: "column", children: items.map((item, i) => (_jsxs(Text, { color: i === index ? "green" : undefined, children: [i === index ? "❯ " : " ", item.label, item.hint ? _jsxs(Text, { dimColor: true, children: [" \u2014 ", item.hint] }) : null] }, item.value))) }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Text } from "ink";
|
|
4
|
+
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
5
|
+
export function Spinner({ label }) {
|
|
6
|
+
const [frame, setFrame] = useState(0);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const timer = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), 80);
|
|
9
|
+
return () => clearInterval(timer);
|
|
10
|
+
}, []);
|
|
11
|
+
return (_jsxs(Text, { color: "yellow", children: [FRAMES[frame], " ", _jsx(Text, { dimColor: true, children: label })] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/** The single dim status bar pinned to the bottom of the screen. */
|
|
5
|
+
export function StatusLine({ killed, killReason, dryRunMode, planMode, acceptEdits, autoApprovedCount, model, workflow, branch, tasks, ctxPct, budgetPct, budgetStopped, phase, elapsed, totalUsage, totalCost, verbose, workspace, }) {
|
|
6
|
+
return (_jsxs(Text, { dimColor: true, children: [killed ? (_jsxs(Text, { bold: true, color: "red", children: ["\u26D4 KILLED", killReason ? ` (${killReason})` : "", " \u00B7", " "] })) : (""), dryRunMode ? (_jsx(Text, { color: "cyan", children: "dry-run \u00B7 " })) : planMode ? (_jsx(Text, { color: "cyan", children: "plan \u00B7 " })) : acceptEdits ? (_jsxs(Text, { color: "green", children: ["accept edits (", autoApprovedCount, " auto-approved) \u00B7 "] })) : (""), model, workflow ? (_jsxs(Text, { color: "magenta", children: [" ", "\u00B7 \u2691 ", workflow.name, ":", workflow.phase] })) : (""), branch ? ` · ⎇ ${branch}` : "", tasks.length > 0
|
|
7
|
+
? ` · tasks ${tasks.filter((t) => t.status === "done").length}/${tasks.length}`
|
|
8
|
+
: "", ctxPct > 0 ? ` · ctx ${ctxPct}%` : "", budgetPct > 0 ? (_jsxs(Text, { color: budgetStopped ? "red" : budgetPct >= 80 ? "yellow" : undefined, children: [" ", "\u00B7 budget ", budgetPct, "%"] })) : (""), phase === "working" && elapsed > 0 ? ` · ${elapsed}s` : "", " \u00B7", " ", totalUsage.estimated ? "~" : "", totalUsage.promptTokens.toLocaleString(), " in", (totalUsage.cachedPromptTokens ?? 0) > 0
|
|
9
|
+
? ` (${Math.round(((totalUsage.cachedPromptTokens ?? 0) / totalUsage.promptTokens) * 100)}% cached)`
|
|
10
|
+
: "", " ", "/ ", totalUsage.completionTokens.toLocaleString(), " out", totalCost > 0 ? ` · $${totalCost.toFixed(4)}` : "", verbose ? " · verbose" : "", " \u00B7 ", path.basename(workspace)] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { Banner } from "./Banner.js";
|
|
4
|
+
import { Markdown } from "./Markdown.js";
|
|
5
|
+
import { toolOutputPreview } from "./toolOutputPreview.js";
|
|
6
|
+
import { terminalColumns } from "./viewport.js";
|
|
7
|
+
/** Renders one line of the transcript — a user message, assistant reply, tool call, info line, or banner. */
|
|
8
|
+
export function TranscriptItem({ item, verbose, stdout }) {
|
|
9
|
+
return (_jsxs(Box, { marginBottom: item.kind === "tool" ? 0 : 1, flexDirection: "column", children: [item.kind === "user" && (_jsxs(Text, { children: [_jsxs(Text, { bold: true, color: "green", children: ["\u276F", " "] }), item.text] })), item.kind === "assistant" && _jsx(Markdown, { text: item.text }), item.kind === "tool" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [item.error ? _jsx(Text, { color: "red", children: "\u2717" }) : _jsx(Text, { color: "green", children: "\u2713" }), " ", item.summary, item.resultSummary && !verbose ? ` — ${item.resultSummary}` : ""] }), item.output && item.output.trim() && (item.resultSummary === undefined || verbose) && (_jsx(Text, { dimColor: true, children: toolOutputPreview(item.output, verbose, Math.max(20, terminalColumns(stdout) - 6), item.error)
|
|
10
|
+
.split("\n")
|
|
11
|
+
.map((l) => ` ${l}`)
|
|
12
|
+
.join("\n") }))] })), item.kind === "info" && _jsx(Text, { dimColor: true, children: item.text }), item.kind === "banner" && _jsx(Banner, { subtitle: item.subtitle })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { SelectList } from "./SelectList.js";
|
|
4
|
+
const MAX_PREVIEW_LINES = 40;
|
|
5
|
+
export function TrustPrompt({ workspace, preview, onDecision, }) {
|
|
6
|
+
const lines = preview.split("\n");
|
|
7
|
+
const shown = lines.slice(0, MAX_PREVIEW_LINES);
|
|
8
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Untrusted workspace settings" }), _jsxs(Text, { children: [workspace, " ships content that would take effect automatically: settings `allow` rules or `hooks`, a `.env` file, and/or custom slash commands. Everything you would be approving is shown below \u2014 review it before trusting this workspace."] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [shown.map((line, i) => (_jsx(Text, { dimColor: true, children: line || " " }, i))), lines.length > MAX_PREVIEW_LINES && (_jsxs(Text, { dimColor: true, children: ["\u2026 (", lines.length - MAX_PREVIEW_LINES, " more lines)"] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectList, { items: [
|
|
9
|
+
{ label: "Trust this workspace", value: "trust" },
|
|
10
|
+
{ label: "Don't trust (just this session)", value: "no" },
|
|
11
|
+
], onSelect: (v) => onDecision(v === "trust"), onCancel: () => onDecision(false) }) })] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Shared keyword set across the languages an agent CLI shows most:
|
|
2
|
+
// TS/JS, Python, Go, Rust, Java, shell.
|
|
3
|
+
const KEYWORDS = new Set([
|
|
4
|
+
"const",
|
|
5
|
+
"let",
|
|
6
|
+
"var",
|
|
7
|
+
"function",
|
|
8
|
+
"def",
|
|
9
|
+
"class",
|
|
10
|
+
"if",
|
|
11
|
+
"else",
|
|
12
|
+
"elif",
|
|
13
|
+
"for",
|
|
14
|
+
"while",
|
|
15
|
+
"return",
|
|
16
|
+
"import",
|
|
17
|
+
"from",
|
|
18
|
+
"export",
|
|
19
|
+
"async",
|
|
20
|
+
"await",
|
|
21
|
+
"try",
|
|
22
|
+
"catch",
|
|
23
|
+
"except",
|
|
24
|
+
"finally",
|
|
25
|
+
"new",
|
|
26
|
+
"type",
|
|
27
|
+
"interface",
|
|
28
|
+
"enum",
|
|
29
|
+
"struct",
|
|
30
|
+
"impl",
|
|
31
|
+
"fn",
|
|
32
|
+
"pub",
|
|
33
|
+
"match",
|
|
34
|
+
"switch",
|
|
35
|
+
"case",
|
|
36
|
+
"break",
|
|
37
|
+
"continue",
|
|
38
|
+
"true",
|
|
39
|
+
"false",
|
|
40
|
+
"null",
|
|
41
|
+
"None",
|
|
42
|
+
"nil",
|
|
43
|
+
"undefined",
|
|
44
|
+
"this",
|
|
45
|
+
"self",
|
|
46
|
+
"static",
|
|
47
|
+
"void",
|
|
48
|
+
"public",
|
|
49
|
+
"private",
|
|
50
|
+
"extends",
|
|
51
|
+
"implements",
|
|
52
|
+
"lambda",
|
|
53
|
+
"yield",
|
|
54
|
+
"in",
|
|
55
|
+
"of",
|
|
56
|
+
"not",
|
|
57
|
+
"and",
|
|
58
|
+
"or",
|
|
59
|
+
"package",
|
|
60
|
+
"func",
|
|
61
|
+
"go",
|
|
62
|
+
"chan",
|
|
63
|
+
"defer",
|
|
64
|
+
"then",
|
|
65
|
+
"fi",
|
|
66
|
+
"do",
|
|
67
|
+
"done",
|
|
68
|
+
"echo",
|
|
69
|
+
]);
|
|
70
|
+
const WORD_RE = /[A-Za-z_$][A-Za-z0-9_$]*/y;
|
|
71
|
+
const NUMBER_RE = /\d[\d_]*(\.\d+)?([eE][+-]?\d+)?[a-zA-Z]*/y;
|
|
72
|
+
/**
|
|
73
|
+
* Language-agnostic single-line tokenizer for terminal syntax highlighting.
|
|
74
|
+
* Strings win over comments (a "//" inside quotes is not a comment); block
|
|
75
|
+
* comments are approximated as line comments.
|
|
76
|
+
*/
|
|
77
|
+
export function tokenizeLine(line) {
|
|
78
|
+
const tokens = [];
|
|
79
|
+
let plain = "";
|
|
80
|
+
const flush = () => {
|
|
81
|
+
if (plain) {
|
|
82
|
+
tokens.push({ text: plain, kind: "plain" });
|
|
83
|
+
plain = "";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
let i = 0;
|
|
87
|
+
while (i < line.length) {
|
|
88
|
+
const ch = line[i];
|
|
89
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
90
|
+
flush();
|
|
91
|
+
let j = i + 1;
|
|
92
|
+
while (j < line.length && line[j] !== ch) {
|
|
93
|
+
if (line[j] === "\\")
|
|
94
|
+
j++;
|
|
95
|
+
j++;
|
|
96
|
+
}
|
|
97
|
+
j = Math.min(j + 1, line.length);
|
|
98
|
+
tokens.push({ text: line.slice(i, j), kind: "string" });
|
|
99
|
+
i = j;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if ((ch === "/" && (line[i + 1] === "/" || line[i + 1] === "*")) || ch === "#") {
|
|
103
|
+
flush();
|
|
104
|
+
tokens.push({ text: line.slice(i), kind: "comment" });
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (/[A-Za-z_$]/.test(ch)) {
|
|
108
|
+
WORD_RE.lastIndex = i;
|
|
109
|
+
const m = WORD_RE.exec(line);
|
|
110
|
+
flush();
|
|
111
|
+
tokens.push({ text: m[0], kind: KEYWORDS.has(m[0]) ? "keyword" : "plain" });
|
|
112
|
+
i += m[0].length;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (/\d/.test(ch)) {
|
|
116
|
+
NUMBER_RE.lastIndex = i;
|
|
117
|
+
const m = NUMBER_RE.exec(line);
|
|
118
|
+
flush();
|
|
119
|
+
tokens.push({ text: m[0], kind: "number" });
|
|
120
|
+
i += m[0].length;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
plain += ch;
|
|
124
|
+
i++;
|
|
125
|
+
}
|
|
126
|
+
flush();
|
|
127
|
+
// Merge adjacent plain tokens (word followed by punctuation, etc.).
|
|
128
|
+
const merged = [];
|
|
129
|
+
for (const t of tokens) {
|
|
130
|
+
const last = merged[merged.length - 1];
|
|
131
|
+
if (last && last.kind === "plain" && t.kind === "plain")
|
|
132
|
+
last.text += t.text;
|
|
133
|
+
else
|
|
134
|
+
merged.push({ ...t });
|
|
135
|
+
}
|
|
136
|
+
return merged;
|
|
137
|
+
}
|