reasonix 0.4.14 → 0.4.16
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/README.md +66 -0
- package/dist/cli/index.js +1098 -300
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +197 -2
- package/dist/index.js +435 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -32,11 +32,33 @@ function loadApiKey(path = defaultConfigPath()) {
|
|
|
32
32
|
if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
|
|
33
33
|
return readConfig(path).apiKey;
|
|
34
34
|
}
|
|
35
|
+
function searchEnabled(path = defaultConfigPath()) {
|
|
36
|
+
const env = process.env.REASONIX_SEARCH;
|
|
37
|
+
if (env === "off" || env === "false" || env === "0") return false;
|
|
38
|
+
const cfg = readConfig(path).search;
|
|
39
|
+
if (cfg === false) return false;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
35
42
|
function saveApiKey(key, path = defaultConfigPath()) {
|
|
36
43
|
const cfg = readConfig(path);
|
|
37
44
|
cfg.apiKey = key.trim();
|
|
38
45
|
writeConfig(cfg, path);
|
|
39
46
|
}
|
|
47
|
+
function loadProjectShellAllowed(rootDir, path = defaultConfigPath()) {
|
|
48
|
+
const cfg = readConfig(path);
|
|
49
|
+
return cfg.projects?.[rootDir]?.shellAllowed ?? [];
|
|
50
|
+
}
|
|
51
|
+
function addProjectShellAllowed(rootDir, prefix, path = defaultConfigPath()) {
|
|
52
|
+
const trimmed = prefix.trim();
|
|
53
|
+
if (!trimmed) return;
|
|
54
|
+
const cfg = readConfig(path);
|
|
55
|
+
if (!cfg.projects) cfg.projects = {};
|
|
56
|
+
if (!cfg.projects[rootDir]) cfg.projects[rootDir] = {};
|
|
57
|
+
const existing = cfg.projects[rootDir].shellAllowed ?? [];
|
|
58
|
+
if (existing.includes(trimmed)) return;
|
|
59
|
+
cfg.projects[rootDir].shellAllowed = [...existing, trimmed];
|
|
60
|
+
writeConfig(cfg, path);
|
|
61
|
+
}
|
|
40
62
|
function isPlausibleKey(key) {
|
|
41
63
|
const trimmed = key.trim();
|
|
42
64
|
return /^sk-[A-Za-z0-9_-]{16,}$/.test(trimmed);
|
|
@@ -96,8 +118,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
96
118
|
}
|
|
97
119
|
function sleep(ms, signal) {
|
|
98
120
|
if (ms <= 0) return Promise.resolve();
|
|
99
|
-
return new Promise((
|
|
100
|
-
const timer = setTimeout(
|
|
121
|
+
return new Promise((resolve6, reject) => {
|
|
122
|
+
const timer = setTimeout(resolve6, ms);
|
|
101
123
|
if (signal) {
|
|
102
124
|
const onAbort = () => {
|
|
103
125
|
clearTimeout(timer);
|
|
@@ -1537,8 +1559,8 @@ var CacheFirstLoop = class {
|
|
|
1537
1559
|
}
|
|
1538
1560
|
);
|
|
1539
1561
|
for (let k = 0; k < budget; k++) {
|
|
1540
|
-
const sample = queue.shift() ?? await new Promise((
|
|
1541
|
-
waiter =
|
|
1562
|
+
const sample = queue.shift() ?? await new Promise((resolve6) => {
|
|
1563
|
+
waiter = resolve6;
|
|
1542
1564
|
});
|
|
1543
1565
|
yield {
|
|
1544
1566
|
turn: this._turn,
|
|
@@ -2232,13 +2254,409 @@ function lineDiff(a, b) {
|
|
|
2232
2254
|
return out;
|
|
2233
2255
|
}
|
|
2234
2256
|
|
|
2257
|
+
// src/tools/shell.ts
|
|
2258
|
+
import { spawn } from "child_process";
|
|
2259
|
+
import * as pathMod2 from "path";
|
|
2260
|
+
var DEFAULT_TIMEOUT_SEC = 60;
|
|
2261
|
+
var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
|
|
2262
|
+
var BUILTIN_ALLOWLIST = [
|
|
2263
|
+
// Repo inspection
|
|
2264
|
+
"git status",
|
|
2265
|
+
"git diff",
|
|
2266
|
+
"git log",
|
|
2267
|
+
"git show",
|
|
2268
|
+
"git blame",
|
|
2269
|
+
"git branch",
|
|
2270
|
+
"git remote",
|
|
2271
|
+
"git rev-parse",
|
|
2272
|
+
"git config --get",
|
|
2273
|
+
// Filesystem inspection
|
|
2274
|
+
"ls",
|
|
2275
|
+
"pwd",
|
|
2276
|
+
"cat",
|
|
2277
|
+
"head",
|
|
2278
|
+
"tail",
|
|
2279
|
+
"wc",
|
|
2280
|
+
"file",
|
|
2281
|
+
"tree",
|
|
2282
|
+
"find",
|
|
2283
|
+
"grep",
|
|
2284
|
+
"rg",
|
|
2285
|
+
// Language version probes
|
|
2286
|
+
"node --version",
|
|
2287
|
+
"node -v",
|
|
2288
|
+
"npm --version",
|
|
2289
|
+
"npx --version",
|
|
2290
|
+
"python --version",
|
|
2291
|
+
"python3 --version",
|
|
2292
|
+
"cargo --version",
|
|
2293
|
+
"go version",
|
|
2294
|
+
"rustc --version",
|
|
2295
|
+
"deno --version",
|
|
2296
|
+
"bun --version",
|
|
2297
|
+
// Test runners (non-destructive by convention)
|
|
2298
|
+
"npm test",
|
|
2299
|
+
"npm run test",
|
|
2300
|
+
"npx vitest run",
|
|
2301
|
+
"npx vitest",
|
|
2302
|
+
"npx jest",
|
|
2303
|
+
"pytest",
|
|
2304
|
+
"python -m pytest",
|
|
2305
|
+
"cargo test",
|
|
2306
|
+
"cargo check",
|
|
2307
|
+
"cargo clippy",
|
|
2308
|
+
"go test",
|
|
2309
|
+
"go vet",
|
|
2310
|
+
"deno test",
|
|
2311
|
+
"bun test",
|
|
2312
|
+
// Linters / typecheckers (read-only by convention)
|
|
2313
|
+
"npm run lint",
|
|
2314
|
+
"npm run typecheck",
|
|
2315
|
+
"npx tsc --noEmit",
|
|
2316
|
+
"npx biome check",
|
|
2317
|
+
"npx eslint",
|
|
2318
|
+
"npx prettier --check",
|
|
2319
|
+
"ruff",
|
|
2320
|
+
"mypy"
|
|
2321
|
+
];
|
|
2322
|
+
function tokenizeCommand(cmd) {
|
|
2323
|
+
const out = [];
|
|
2324
|
+
let cur = "";
|
|
2325
|
+
let quote = null;
|
|
2326
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
2327
|
+
const ch = cmd[i];
|
|
2328
|
+
if (quote) {
|
|
2329
|
+
if (ch === quote) {
|
|
2330
|
+
quote = null;
|
|
2331
|
+
} else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
|
|
2332
|
+
cur += cmd[++i];
|
|
2333
|
+
} else {
|
|
2334
|
+
cur += ch;
|
|
2335
|
+
}
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
if (ch === '"' || ch === "'") {
|
|
2339
|
+
quote = ch;
|
|
2340
|
+
continue;
|
|
2341
|
+
}
|
|
2342
|
+
if (ch === " " || ch === " ") {
|
|
2343
|
+
if (cur.length > 0) {
|
|
2344
|
+
out.push(cur);
|
|
2345
|
+
cur = "";
|
|
2346
|
+
}
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
cur += ch;
|
|
2350
|
+
}
|
|
2351
|
+
if (quote) throw new Error(`unclosed ${quote} in command`);
|
|
2352
|
+
if (cur.length > 0) out.push(cur);
|
|
2353
|
+
return out;
|
|
2354
|
+
}
|
|
2355
|
+
function isAllowed(cmd, extra = []) {
|
|
2356
|
+
const normalized = cmd.trim().replace(/\s+/g, " ");
|
|
2357
|
+
const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
|
|
2358
|
+
for (const prefix of allowlist) {
|
|
2359
|
+
if (normalized === prefix) return true;
|
|
2360
|
+
if (normalized.startsWith(`${prefix} `)) return true;
|
|
2361
|
+
}
|
|
2362
|
+
return false;
|
|
2363
|
+
}
|
|
2364
|
+
async function runCommand(cmd, opts) {
|
|
2365
|
+
const argv = tokenizeCommand(cmd);
|
|
2366
|
+
if (argv.length === 0) throw new Error("run_command: empty command");
|
|
2367
|
+
const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
|
|
2368
|
+
const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
2369
|
+
const spawnOpts = {
|
|
2370
|
+
cwd: opts.cwd,
|
|
2371
|
+
shell: false,
|
|
2372
|
+
// no shell-expansion — see header comment
|
|
2373
|
+
windowsHide: true,
|
|
2374
|
+
env: process.env
|
|
2375
|
+
};
|
|
2376
|
+
return await new Promise((resolve6, reject) => {
|
|
2377
|
+
let child;
|
|
2378
|
+
try {
|
|
2379
|
+
child = spawn(argv[0], argv.slice(1), spawnOpts);
|
|
2380
|
+
} catch (err) {
|
|
2381
|
+
reject(err);
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
let buf = "";
|
|
2385
|
+
let timedOut = false;
|
|
2386
|
+
const killTimer = setTimeout(() => {
|
|
2387
|
+
timedOut = true;
|
|
2388
|
+
child.kill("SIGKILL");
|
|
2389
|
+
}, timeoutMs);
|
|
2390
|
+
const onAbort = () => child.kill("SIGKILL");
|
|
2391
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2392
|
+
const onData = (chunk) => {
|
|
2393
|
+
buf += chunk.toString();
|
|
2394
|
+
if (buf.length > maxChars * 2) buf = `${buf.slice(0, maxChars * 2)}`;
|
|
2395
|
+
};
|
|
2396
|
+
child.stdout?.on("data", onData);
|
|
2397
|
+
child.stderr?.on("data", onData);
|
|
2398
|
+
child.on("error", (err) => {
|
|
2399
|
+
clearTimeout(killTimer);
|
|
2400
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
2401
|
+
reject(err);
|
|
2402
|
+
});
|
|
2403
|
+
child.on("close", (code) => {
|
|
2404
|
+
clearTimeout(killTimer);
|
|
2405
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
2406
|
+
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
2407
|
+
|
|
2408
|
+
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
2409
|
+
resolve6({ exitCode: code, output, timedOut });
|
|
2410
|
+
});
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
var NeedsConfirmationError = class extends Error {
|
|
2414
|
+
command;
|
|
2415
|
+
constructor(command) {
|
|
2416
|
+
super(
|
|
2417
|
+
`run_command: "${command}" needs the user's approval before it runs. STOP calling tools now \u2014 the TUI has already prompted the user to press y (run) or n (deny). Wait for their next message; it will either be the command's output (if they approved) or an instruction to continue without it (if they denied). Don't retry the command or call other shell commands in the meantime.`
|
|
2418
|
+
);
|
|
2419
|
+
this.name = "NeedsConfirmationError";
|
|
2420
|
+
this.command = command;
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
function registerShellTools(registry, opts) {
|
|
2424
|
+
const rootDir = pathMod2.resolve(opts.rootDir);
|
|
2425
|
+
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
2426
|
+
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
2427
|
+
const extraAllowed = opts.extraAllowed ?? [];
|
|
2428
|
+
const allowAll = opts.allowAll ?? false;
|
|
2429
|
+
registry.register({
|
|
2430
|
+
name: "run_command",
|
|
2431
|
+
description: "Run a shell command in the project root and return its combined stdout+stderr. Read-only and test commands (git status, ls, npm test, pytest, cargo test, grep, etc.) run immediately. Anything that could mutate state (npm install, git commit, rm, chmod) is refused and the user has to confirm in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
2432
|
+
parameters: {
|
|
2433
|
+
type: "object",
|
|
2434
|
+
properties: {
|
|
2435
|
+
command: {
|
|
2436
|
+
type: "string",
|
|
2437
|
+
description: "Full command line, e.g. 'npm test' or 'git diff src/foo.ts'. Tokenized with POSIX-ish quoting; no shell expansion, no pipes, no redirects."
|
|
2438
|
+
},
|
|
2439
|
+
timeoutSec: {
|
|
2440
|
+
type: "integer",
|
|
2441
|
+
description: `Override the default ${timeoutSec}s timeout for a single command.`
|
|
2442
|
+
}
|
|
2443
|
+
},
|
|
2444
|
+
required: ["command"]
|
|
2445
|
+
},
|
|
2446
|
+
fn: async (args, ctx) => {
|
|
2447
|
+
const cmd = args.command.trim();
|
|
2448
|
+
if (!cmd) throw new Error("run_command: empty command");
|
|
2449
|
+
if (!allowAll && !isAllowed(cmd, extraAllowed)) {
|
|
2450
|
+
throw new NeedsConfirmationError(cmd);
|
|
2451
|
+
}
|
|
2452
|
+
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
2453
|
+
const result = await runCommand(cmd, {
|
|
2454
|
+
cwd: rootDir,
|
|
2455
|
+
timeoutSec: effectiveTimeout,
|
|
2456
|
+
maxOutputChars,
|
|
2457
|
+
signal: ctx?.signal
|
|
2458
|
+
});
|
|
2459
|
+
return formatCommandResult(cmd, result);
|
|
2460
|
+
}
|
|
2461
|
+
});
|
|
2462
|
+
return registry;
|
|
2463
|
+
}
|
|
2464
|
+
function formatCommandResult(cmd, r) {
|
|
2465
|
+
const header = r.timedOut ? `$ ${cmd}
|
|
2466
|
+
[killed after timeout]` : `$ ${cmd}
|
|
2467
|
+
[exit ${r.exitCode ?? "?"}]`;
|
|
2468
|
+
return r.output ? `${header}
|
|
2469
|
+
${r.output}` : header;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/tools/web.ts
|
|
2473
|
+
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
2474
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
2475
|
+
var DEFAULT_TOPK = 5;
|
|
2476
|
+
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
2477
|
+
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
2478
|
+
async function webSearch(query, opts = {}) {
|
|
2479
|
+
const topK = Math.max(1, Math.min(10, opts.topK ?? DEFAULT_TOPK));
|
|
2480
|
+
const resp = await fetch(`${MOJEEK_ENDPOINT}?q=${encodeURIComponent(query)}`, {
|
|
2481
|
+
headers: {
|
|
2482
|
+
"User-Agent": USER_AGENT,
|
|
2483
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9",
|
|
2484
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
2485
|
+
},
|
|
2486
|
+
signal: opts.signal,
|
|
2487
|
+
redirect: "follow"
|
|
2488
|
+
});
|
|
2489
|
+
if (!resp.ok) throw new Error(`web_search ${resp.status}`);
|
|
2490
|
+
const html = await resp.text();
|
|
2491
|
+
const results = parseMojeekResults(html).slice(0, topK);
|
|
2492
|
+
if (results.length === 0) {
|
|
2493
|
+
if (/no results found|did not match any documents/i.test(html)) return [];
|
|
2494
|
+
if (/captcha|verify you are human|access denied|forbidden/i.test(html)) {
|
|
2495
|
+
throw new Error("web_search: Mojeek anti-bot page \u2014 rate-limited or blocked");
|
|
2496
|
+
}
|
|
2497
|
+
throw new Error(
|
|
2498
|
+
`web_search: 0 results but response doesn't look like a real empty page (${html.length} chars, first 120: ${html.slice(0, 120).replace(/\s+/g, " ")})`
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
return results;
|
|
2502
|
+
}
|
|
2503
|
+
function parseMojeekResults(html) {
|
|
2504
|
+
const titles = [];
|
|
2505
|
+
const titleAnchorRe = /<a\b[^>]*\bclass="title"[^>]*>[\s\S]*?<\/a>/g;
|
|
2506
|
+
let m;
|
|
2507
|
+
while (true) {
|
|
2508
|
+
m = titleAnchorRe.exec(html);
|
|
2509
|
+
if (m === null) break;
|
|
2510
|
+
titles.push(m[0]);
|
|
2511
|
+
}
|
|
2512
|
+
const snippets = [];
|
|
2513
|
+
const snippetRe = /<p\b[^>]*\bclass="s"[^>]*>([\s\S]*?)<\/p>/g;
|
|
2514
|
+
while (true) {
|
|
2515
|
+
m = snippetRe.exec(html);
|
|
2516
|
+
if (m === null) break;
|
|
2517
|
+
snippets.push(m[1] ?? "");
|
|
2518
|
+
}
|
|
2519
|
+
const hrefRe = /href="([^"]+)"/;
|
|
2520
|
+
const innerRe = /<a\b[^>]*>([\s\S]*?)<\/a>/;
|
|
2521
|
+
const results = [];
|
|
2522
|
+
for (let i = 0; i < titles.length; i++) {
|
|
2523
|
+
const anchor = titles[i];
|
|
2524
|
+
const hrefMatch = anchor.match(hrefRe);
|
|
2525
|
+
const innerMatch = anchor.match(innerRe);
|
|
2526
|
+
if (!hrefMatch?.[1]) continue;
|
|
2527
|
+
results.push({
|
|
2528
|
+
title: decodeHtmlEntities(stripHtml(innerMatch?.[1] ?? "")).trim(),
|
|
2529
|
+
url: hrefMatch[1],
|
|
2530
|
+
snippet: decodeHtmlEntities(stripHtml(snippets[i] ?? "")).replace(/\s+/g, " ").trim()
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
return results;
|
|
2534
|
+
}
|
|
2535
|
+
async function webFetch(url, opts = {}) {
|
|
2536
|
+
const maxChars = opts.maxChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
2537
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
2538
|
+
const ctl = new AbortController();
|
|
2539
|
+
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
2540
|
+
const cancel = () => ctl.abort();
|
|
2541
|
+
opts.signal?.addEventListener("abort", cancel, { once: true });
|
|
2542
|
+
let resp;
|
|
2543
|
+
try {
|
|
2544
|
+
resp = await fetch(url, {
|
|
2545
|
+
headers: { "User-Agent": USER_AGENT, Accept: "text/html,text/plain,*/*" },
|
|
2546
|
+
signal: ctl.signal,
|
|
2547
|
+
redirect: "follow"
|
|
2548
|
+
});
|
|
2549
|
+
} finally {
|
|
2550
|
+
clearTimeout(timer);
|
|
2551
|
+
opts.signal?.removeEventListener("abort", cancel);
|
|
2552
|
+
}
|
|
2553
|
+
if (!resp.ok) throw new Error(`web_fetch ${resp.status} for ${url}`);
|
|
2554
|
+
const contentType = resp.headers.get("content-type") ?? "";
|
|
2555
|
+
const raw = await resp.text();
|
|
2556
|
+
const title = extractTitle(raw);
|
|
2557
|
+
const text = contentType.includes("text/html") ? htmlToText(raw) : raw;
|
|
2558
|
+
const truncated = text.length > maxChars;
|
|
2559
|
+
const finalText = truncated ? `${text.slice(0, maxChars)}
|
|
2560
|
+
|
|
2561
|
+
[\u2026 truncated ${text.length - maxChars} chars \u2026]` : text;
|
|
2562
|
+
return { url, title, text: finalText, truncated };
|
|
2563
|
+
}
|
|
2564
|
+
function htmlToText(html) {
|
|
2565
|
+
let s = html;
|
|
2566
|
+
s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
|
|
2567
|
+
s = s.replace(/<style[\s\S]*?<\/style>/gi, "");
|
|
2568
|
+
s = s.replace(/<noscript[\s\S]*?<\/noscript>/gi, "");
|
|
2569
|
+
s = s.replace(/<nav[\s\S]*?<\/nav>/gi, "");
|
|
2570
|
+
s = s.replace(/<footer[\s\S]*?<\/footer>/gi, "");
|
|
2571
|
+
s = s.replace(/<aside[\s\S]*?<\/aside>/gi, "");
|
|
2572
|
+
s = s.replace(/<svg[\s\S]*?<\/svg>/gi, "");
|
|
2573
|
+
s = s.replace(/<\/?(p|div|br|h[1-6]|li|tr|section|article)\b[^>]*>/gi, "\n");
|
|
2574
|
+
s = s.replace(/<[^>]+>/g, "");
|
|
2575
|
+
s = decodeHtmlEntities(s);
|
|
2576
|
+
s = s.replace(/[ \t]+/g, " ");
|
|
2577
|
+
s = s.replace(/\n[ \t]+/g, "\n");
|
|
2578
|
+
s = s.replace(/\n{3,}/g, "\n\n");
|
|
2579
|
+
return s.trim();
|
|
2580
|
+
}
|
|
2581
|
+
function stripHtml(s) {
|
|
2582
|
+
return s.replace(/<[^>]+>/g, "");
|
|
2583
|
+
}
|
|
2584
|
+
function decodeHtmlEntities(s) {
|
|
2585
|
+
return s.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
2586
|
+
}
|
|
2587
|
+
function extractTitle(html) {
|
|
2588
|
+
const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
2589
|
+
if (!m?.[1]) return void 0;
|
|
2590
|
+
return m[1].replace(/\s+/g, " ").trim() || void 0;
|
|
2591
|
+
}
|
|
2592
|
+
function registerWebTools(registry, opts = {}) {
|
|
2593
|
+
const defaultTopK = opts.defaultTopK ?? DEFAULT_TOPK;
|
|
2594
|
+
const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
2595
|
+
registry.register({
|
|
2596
|
+
name: "web_search",
|
|
2597
|
+
description: "Search the public web. Returns ranked results with title, url, and snippet. Use this when the question needs information more current than your training data, when you're unsure of a factual detail, or when the user asks about a specific webpage/library/release you haven't seen.",
|
|
2598
|
+
parameters: {
|
|
2599
|
+
type: "object",
|
|
2600
|
+
properties: {
|
|
2601
|
+
query: { type: "string", description: "Natural-language search query." },
|
|
2602
|
+
topK: {
|
|
2603
|
+
type: "integer",
|
|
2604
|
+
description: `Number of results to return (1..10). Default ${defaultTopK}.`
|
|
2605
|
+
}
|
|
2606
|
+
},
|
|
2607
|
+
required: ["query"]
|
|
2608
|
+
},
|
|
2609
|
+
fn: async (args, ctx) => {
|
|
2610
|
+
const results = await webSearch(args.query, {
|
|
2611
|
+
topK: args.topK ?? defaultTopK,
|
|
2612
|
+
signal: ctx?.signal
|
|
2613
|
+
});
|
|
2614
|
+
return formatSearchResults(args.query, results);
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
registry.register({
|
|
2618
|
+
name: "web_fetch",
|
|
2619
|
+
description: "Download a URL and return its visible text content (HTML pages get scripts/styles/nav stripped). Truncated at the tool-result cap. Use after web_search when a snippet isn't enough.",
|
|
2620
|
+
parameters: {
|
|
2621
|
+
type: "object",
|
|
2622
|
+
properties: {
|
|
2623
|
+
url: { type: "string", description: "Absolute http:// or https:// URL." }
|
|
2624
|
+
},
|
|
2625
|
+
required: ["url"]
|
|
2626
|
+
},
|
|
2627
|
+
fn: async (args, ctx) => {
|
|
2628
|
+
if (!/^https?:\/\//i.test(args.url)) {
|
|
2629
|
+
throw new Error("web_fetch: url must start with http:// or https://");
|
|
2630
|
+
}
|
|
2631
|
+
const page = await webFetch(args.url, { maxChars: maxFetchChars, signal: ctx?.signal });
|
|
2632
|
+
const header = page.title ? `${page.title}
|
|
2633
|
+
${page.url}` : page.url;
|
|
2634
|
+
return `${header}
|
|
2635
|
+
|
|
2636
|
+
${page.text}`;
|
|
2637
|
+
}
|
|
2638
|
+
});
|
|
2639
|
+
return registry;
|
|
2640
|
+
}
|
|
2641
|
+
function formatSearchResults(query, results) {
|
|
2642
|
+
const lines = [`query: ${query}`, `
|
|
2643
|
+
results (${results.length}):`];
|
|
2644
|
+
results.forEach((r, i) => {
|
|
2645
|
+
lines.push(`
|
|
2646
|
+
${i + 1}. ${r.title}`);
|
|
2647
|
+
lines.push(` ${r.url}`);
|
|
2648
|
+
if (r.snippet) lines.push(` ${r.snippet}`);
|
|
2649
|
+
});
|
|
2650
|
+
return lines.join("\n");
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2235
2653
|
// src/env.ts
|
|
2236
2654
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2237
|
-
import { resolve as
|
|
2655
|
+
import { resolve as resolve3 } from "path";
|
|
2238
2656
|
function loadDotenv(path = ".env") {
|
|
2239
2657
|
let raw;
|
|
2240
2658
|
try {
|
|
2241
|
-
raw = readFileSync3(
|
|
2659
|
+
raw = readFileSync3(resolve3(process.cwd(), path), "utf8");
|
|
2242
2660
|
} catch {
|
|
2243
2661
|
return;
|
|
2244
2662
|
}
|
|
@@ -2951,7 +3369,7 @@ var McpClient = class {
|
|
|
2951
3369
|
const id = this.nextId++;
|
|
2952
3370
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
2953
3371
|
let abortHandler = null;
|
|
2954
|
-
const promise = new Promise((
|
|
3372
|
+
const promise = new Promise((resolve6, reject) => {
|
|
2955
3373
|
const timeout = setTimeout(() => {
|
|
2956
3374
|
this.pending.delete(id);
|
|
2957
3375
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -2960,7 +3378,7 @@ var McpClient = class {
|
|
|
2960
3378
|
);
|
|
2961
3379
|
}, this.requestTimeoutMs);
|
|
2962
3380
|
this.pending.set(id, {
|
|
2963
|
-
resolve:
|
|
3381
|
+
resolve: resolve6,
|
|
2964
3382
|
reject,
|
|
2965
3383
|
timeout
|
|
2966
3384
|
});
|
|
@@ -3042,7 +3460,7 @@ var McpClient = class {
|
|
|
3042
3460
|
};
|
|
3043
3461
|
|
|
3044
3462
|
// src/mcp/stdio.ts
|
|
3045
|
-
import { spawn } from "child_process";
|
|
3463
|
+
import { spawn as spawn2 } from "child_process";
|
|
3046
3464
|
var StdioTransport = class {
|
|
3047
3465
|
child;
|
|
3048
3466
|
queue = [];
|
|
@@ -3057,14 +3475,14 @@ var StdioTransport = class {
|
|
|
3057
3475
|
opts.command,
|
|
3058
3476
|
...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
|
|
3059
3477
|
].join(" ");
|
|
3060
|
-
this.child =
|
|
3478
|
+
this.child = spawn2(line, [], {
|
|
3061
3479
|
env,
|
|
3062
3480
|
cwd: opts.cwd,
|
|
3063
3481
|
stdio: ["pipe", "pipe", "inherit"],
|
|
3064
3482
|
shell: true
|
|
3065
3483
|
});
|
|
3066
3484
|
} else {
|
|
3067
|
-
this.child =
|
|
3485
|
+
this.child = spawn2(opts.command, opts.args ?? [], {
|
|
3068
3486
|
env,
|
|
3069
3487
|
cwd: opts.cwd,
|
|
3070
3488
|
stdio: ["pipe", "pipe", "inherit"]
|
|
@@ -3083,12 +3501,12 @@ var StdioTransport = class {
|
|
|
3083
3501
|
}
|
|
3084
3502
|
async send(message) {
|
|
3085
3503
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
3086
|
-
return new Promise((
|
|
3504
|
+
return new Promise((resolve6, reject) => {
|
|
3087
3505
|
const line = `${JSON.stringify(message)}
|
|
3088
3506
|
`;
|
|
3089
3507
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
3090
3508
|
if (err) reject(err);
|
|
3091
|
-
else
|
|
3509
|
+
else resolve6();
|
|
3092
3510
|
});
|
|
3093
3511
|
});
|
|
3094
3512
|
}
|
|
@@ -3099,8 +3517,8 @@ var StdioTransport = class {
|
|
|
3099
3517
|
continue;
|
|
3100
3518
|
}
|
|
3101
3519
|
if (this.closed) return;
|
|
3102
|
-
const next = await new Promise((
|
|
3103
|
-
this.waiters.push(
|
|
3520
|
+
const next = await new Promise((resolve6) => {
|
|
3521
|
+
this.waiters.push(resolve6);
|
|
3104
3522
|
});
|
|
3105
3523
|
if (next === null) return;
|
|
3106
3524
|
yield next;
|
|
@@ -3166,8 +3584,8 @@ var SseTransport = class {
|
|
|
3166
3584
|
constructor(opts) {
|
|
3167
3585
|
this.url = opts.url;
|
|
3168
3586
|
this.headers = opts.headers ?? {};
|
|
3169
|
-
this.endpointReady = new Promise((
|
|
3170
|
-
this.resolveEndpoint =
|
|
3587
|
+
this.endpointReady = new Promise((resolve6, reject) => {
|
|
3588
|
+
this.resolveEndpoint = resolve6;
|
|
3171
3589
|
this.rejectEndpoint = reject;
|
|
3172
3590
|
});
|
|
3173
3591
|
this.endpointReady.catch(() => void 0);
|
|
@@ -3194,8 +3612,8 @@ var SseTransport = class {
|
|
|
3194
3612
|
continue;
|
|
3195
3613
|
}
|
|
3196
3614
|
if (this.closed) return;
|
|
3197
|
-
const next = await new Promise((
|
|
3198
|
-
this.waiters.push(
|
|
3615
|
+
const next = await new Promise((resolve6) => {
|
|
3616
|
+
this.waiters.push(resolve6);
|
|
3199
3617
|
});
|
|
3200
3618
|
if (next === null) return;
|
|
3201
3619
|
yield next;
|
|
@@ -3395,7 +3813,7 @@ async function trySection(load) {
|
|
|
3395
3813
|
|
|
3396
3814
|
// src/code/edit-blocks.ts
|
|
3397
3815
|
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3398
|
-
import { dirname as dirname4, resolve as
|
|
3816
|
+
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
3399
3817
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
3400
3818
|
function parseEditBlocks(text) {
|
|
3401
3819
|
const out = [];
|
|
@@ -3413,8 +3831,8 @@ function parseEditBlocks(text) {
|
|
|
3413
3831
|
return out;
|
|
3414
3832
|
}
|
|
3415
3833
|
function applyEditBlock(block, rootDir) {
|
|
3416
|
-
const absRoot =
|
|
3417
|
-
const absTarget =
|
|
3834
|
+
const absRoot = resolve4(rootDir);
|
|
3835
|
+
const absTarget = resolve4(absRoot, block.path);
|
|
3418
3836
|
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
|
|
3419
3837
|
return {
|
|
3420
3838
|
path: block.path,
|
|
@@ -3464,13 +3882,13 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
3464
3882
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
3465
3883
|
}
|
|
3466
3884
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
3467
|
-
const absRoot =
|
|
3885
|
+
const absRoot = resolve4(rootDir);
|
|
3468
3886
|
const seen = /* @__PURE__ */ new Set();
|
|
3469
3887
|
const snapshots = [];
|
|
3470
3888
|
for (const b of blocks) {
|
|
3471
3889
|
if (seen.has(b.path)) continue;
|
|
3472
3890
|
seen.add(b.path);
|
|
3473
|
-
const abs =
|
|
3891
|
+
const abs = resolve4(absRoot, b.path);
|
|
3474
3892
|
if (!existsSync2(abs)) {
|
|
3475
3893
|
snapshots.push({ path: b.path, prevContent: null });
|
|
3476
3894
|
continue;
|
|
@@ -3484,9 +3902,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
3484
3902
|
return snapshots;
|
|
3485
3903
|
}
|
|
3486
3904
|
function restoreSnapshots(snapshots, rootDir) {
|
|
3487
|
-
const absRoot =
|
|
3905
|
+
const absRoot = resolve4(rootDir);
|
|
3488
3906
|
return snapshots.map((snap) => {
|
|
3489
|
-
const abs =
|
|
3907
|
+
const abs = resolve4(absRoot, snap.path);
|
|
3490
3908
|
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
|
|
3491
3909
|
return {
|
|
3492
3910
|
path: snap.path,
|
|
@@ -3519,15 +3937,16 @@ function sep() {
|
|
|
3519
3937
|
}
|
|
3520
3938
|
|
|
3521
3939
|
// src/index.ts
|
|
3522
|
-
var VERSION = "0.4.
|
|
3940
|
+
var VERSION = "0.4.16";
|
|
3523
3941
|
|
|
3524
3942
|
// src/cli/commands/chat.tsx
|
|
3943
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
3525
3944
|
import { render } from "ink";
|
|
3526
|
-
import
|
|
3945
|
+
import React13, { useState as useState6 } from "react";
|
|
3527
3946
|
|
|
3528
3947
|
// src/cli/ui/App.tsx
|
|
3529
|
-
import { Box as
|
|
3530
|
-
import
|
|
3948
|
+
import { Box as Box9, Static, Text as Text9, useApp, useInput as useInput3 } from "ink";
|
|
3949
|
+
import React10, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState4 } from "react";
|
|
3531
3950
|
|
|
3532
3951
|
// src/cli/ui/EventLog.tsx
|
|
3533
3952
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -3600,7 +4019,7 @@ function stripMath(s) {
|
|
|
3600
4019
|
(_m, n, k) => `C(${n.trim()},${k.trim()})`
|
|
3601
4020
|
).replace(/\\sqrt\s*\{([^{}]+)\}/g, (_m, g) => `\u221A(${g.trim()})`).replace(/\\boxed\s*\{([^{}]+)\}/g, (_m, g) => `\u3010${g.trim()}\u3011`).replace(/\\text\s*\{([^{}]+)\}/g, (_m, g) => g.trim()).replace(/\\overline\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0304`).replace(/\\hat\s*\{([^{}]+)\}/g, (_m, g) => `${g.trim()}\u0302`).replace(/\\vec\s*\{([^{}]+)\}/g, (_m, g) => `\u2192${g.trim()}`).replace(/\\cdot/g, "\xB7").replace(/\\times/g, "\xD7").replace(/\\div/g, "\xF7").replace(/\\pm/g, "\xB1").replace(/\\mp/g, "\u2213").replace(/\\leq/g, "\u2264").replace(/\\geq/g, "\u2265").replace(/\\neq/g, "\u2260").replace(/\\approx/g, "\u2248").replace(/\\in\b/g, "\u2208").replace(/\\notin\b/g, "\u2209").replace(/\\infty/g, "\u221E").replace(/\\sum\b/g, "\u03A3").replace(/\\prod\b/g, "\u03A0").replace(/\\int\b/g, "\u222B").replace(/\\alpha/g, "\u03B1").replace(/\\beta/g, "\u03B2").replace(/\\gamma/g, "\u03B3").replace(/\\delta/g, "\u03B4").replace(/\\theta/g, "\u03B8").replace(/\\lambda/g, "\u03BB").replace(/\\mu/g, "\u03BC").replace(/\\pi/g, "\u03C0").replace(/\\sigma/g, "\u03C3").replace(/\\phi/g, "\u03C6").replace(/\\omega/g, "\u03C9").replace(/\\implies\b/g, "\u21D2").replace(/\\iff\b/g, "\u21D4").replace(/\\to\b/g, "\u2192").replace(/\\rightarrow/g, "\u2192").replace(/\\Rightarrow/g, "\u21D2").replace(/\\leftarrow/g, "\u2190").replace(/\\Leftarrow/g, "\u21D0").replace(/\\ldots/g, "\u2026").replace(/\\cdots/g, "\u22EF").replace(/\\quad/g, " ").replace(/\\qquad/g, " ").replace(/\\,/g, " ").replace(/\\;/g, " ").replace(/\\!/g, "").replace(/\\\\/g, "\n").replace(/\^\{([\w+-]+)\}/g, (_m, g) => toSuperscript(g)).replace(/\^([0-9+\-n])/g, (_m, g) => toSuperscript(g)).replace(/_\{([\w+-]+)\}/g, (_m, g) => toSubscript(g)).replace(/_([0-9+\-])/g, (_m, g) => toSubscript(g)).replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}\s*\{([^{}]+)\}/g, "($1)/($2)").replace(/\\[a-zA-Z]+\s*\{([^{}]+)\}/g, "$1").replace(/\\[a-zA-Z]+/g, "").replace(/[ \t]{2,}/g, " ");
|
|
3602
4021
|
}
|
|
3603
|
-
var INLINE_RE = /(\*\*([^*\n]+?)
|
|
4022
|
+
var INLINE_RE = /(\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
|
|
3604
4023
|
function InlineMd({ text }) {
|
|
3605
4024
|
const parts = [];
|
|
3606
4025
|
let last = 0;
|
|
@@ -3615,12 +4034,17 @@ function InlineMd({ text }) {
|
|
|
3615
4034
|
/* @__PURE__ */ React2.createElement(Text2, { key: `b${idx++}`, bold: true }, m[2])
|
|
3616
4035
|
);
|
|
3617
4036
|
} else if (m[3] !== void 0) {
|
|
4037
|
+
const stripped = m[3].replace(/^(\w+)\s+/, "");
|
|
3618
4038
|
parts.push(
|
|
3619
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" },
|
|
4039
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, stripped)
|
|
3620
4040
|
);
|
|
3621
4041
|
} else if (m[4] !== void 0) {
|
|
3622
4042
|
parts.push(
|
|
3623
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `
|
|
4043
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, m[4])
|
|
4044
|
+
);
|
|
4045
|
+
} else if (m[5] !== void 0) {
|
|
4046
|
+
parts.push(
|
|
4047
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `i${idx++}`, italic: true }, m[5])
|
|
3624
4048
|
);
|
|
3625
4049
|
}
|
|
3626
4050
|
last = start + m[0].length;
|
|
@@ -3638,6 +4062,7 @@ function parseBlocks(raw) {
|
|
|
3638
4062
|
let codeLang = "";
|
|
3639
4063
|
let codeBuf = [];
|
|
3640
4064
|
let listBuf = null;
|
|
4065
|
+
let codeFence = "";
|
|
3641
4066
|
const flushPara = () => {
|
|
3642
4067
|
if (para.length) {
|
|
3643
4068
|
out.push({ kind: "paragraph", text: para.join(" ") });
|
|
@@ -3683,22 +4108,37 @@ function parseBlocks(raw) {
|
|
|
3683
4108
|
para.push(filename);
|
|
3684
4109
|
}
|
|
3685
4110
|
}
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
if (
|
|
4111
|
+
if (!inCode) {
|
|
4112
|
+
const open = line.match(/^ {0,3}(`{3,})(\w*)\s*(.*)$/);
|
|
4113
|
+
if (open) {
|
|
4114
|
+
const fence = open[1];
|
|
4115
|
+
const lang = open[2] ?? "";
|
|
4116
|
+
const rest = open[3] ?? "";
|
|
4117
|
+
const closeOnSame = rest.match(new RegExp(`^(.*?)${fence}\\s*$`));
|
|
4118
|
+
if (closeOnSame) {
|
|
4119
|
+
flushPara();
|
|
4120
|
+
flushList();
|
|
4121
|
+
out.push({ kind: "code", lang, text: (closeOnSame[1] ?? "").trim() });
|
|
4122
|
+
continue;
|
|
4123
|
+
}
|
|
4124
|
+
flushPara();
|
|
4125
|
+
flushList();
|
|
4126
|
+
inCode = true;
|
|
4127
|
+
codeLang = lang;
|
|
4128
|
+
codeFence = fence;
|
|
4129
|
+
if (rest.length > 0) codeBuf.push(rest);
|
|
4130
|
+
continue;
|
|
4131
|
+
}
|
|
4132
|
+
} else {
|
|
4133
|
+
const close = line.match(/^ {0,3}(`{3,})\s*$/);
|
|
4134
|
+
if (close && close[1].length >= codeFence.length) {
|
|
3689
4135
|
out.push({ kind: "code", lang: codeLang, text: codeBuf.join("\n") });
|
|
3690
4136
|
codeBuf = [];
|
|
3691
4137
|
codeLang = "";
|
|
4138
|
+
codeFence = "";
|
|
3692
4139
|
inCode = false;
|
|
3693
|
-
|
|
3694
|
-
flushPara();
|
|
3695
|
-
flushList();
|
|
3696
|
-
inCode = true;
|
|
3697
|
-
codeLang = fence[1] ?? "";
|
|
4140
|
+
continue;
|
|
3698
4141
|
}
|
|
3699
|
-
continue;
|
|
3700
|
-
}
|
|
3701
|
-
if (inCode) {
|
|
3702
4142
|
codeBuf.push(rawLine);
|
|
3703
4143
|
continue;
|
|
3704
4144
|
}
|
|
@@ -3960,7 +4400,7 @@ function StreamingAssistant({ event }) {
|
|
|
3960
4400
|
label = parts.join(" \xB7 ");
|
|
3961
4401
|
labelColor = "green";
|
|
3962
4402
|
}
|
|
3963
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React4.createElement(Pulse, null), /* @__PURE__ */ React4.createElement(Text3, { color: labelColor }, ` ${label} `), /* @__PURE__ */ React4.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "\u25B8 ", tail) : reasoningOnly ? /* @__PURE__ */ React4.createElement(Text3, { color: "yellow", dimColor: true }, " R1 is thinking before it speaks \u2014 body text starts when reasoning completes (typically 20-90s).") : /* @__PURE__ */ React4.createElement(Text3, { dimColor: true,
|
|
4403
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React4.createElement(Pulse, null), /* @__PURE__ */ React4.createElement(Text3, { color: labelColor }, ` ${label} `), /* @__PURE__ */ React4.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "\u25B8 ", tail) : preFirstByte ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, " connection open, first byte typically in 5-60s depending on model + load") : reasoningOnly ? /* @__PURE__ */ React4.createElement(Text3, { color: "yellow", dimColor: true }, " R1 is thinking before it speaks \u2014 body text starts when reasoning completes (typically 20-90s).") : toolCallOnly ? /* @__PURE__ */ React4.createElement(Text3, { color: "magenta", dimColor: true }, " tool-call arguments streaming \u2014 the model is about to dispatch a tool") : event.reasoning ? /* @__PURE__ */ React4.createElement(Text3, { color: "yellow", dimColor: true }, " R1 still reasoning \u2014 body text or tool call arrives when thinking completes") : null);
|
|
3964
4404
|
}
|
|
3965
4405
|
function Pulse() {
|
|
3966
4406
|
const tick = useTick();
|
|
@@ -3982,40 +4422,111 @@ function truncate2(s, max) {
|
|
|
3982
4422
|
|
|
3983
4423
|
// src/cli/ui/PromptInput.tsx
|
|
3984
4424
|
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
3985
|
-
import React5 from "react";
|
|
4425
|
+
import React5, { useRef, useState as useState2 } from "react";
|
|
3986
4426
|
|
|
3987
4427
|
// src/cli/ui/multiline-keys.ts
|
|
3988
4428
|
var BACKSLASH_SUFFIX = /\\$/;
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4429
|
+
var NOOP = { next: null, cursor: null, submit: false };
|
|
4430
|
+
function processMultilineKey(value, cursor, key) {
|
|
4431
|
+
if (key.tab || key.escape || key.pageUp || key.pageDown) {
|
|
4432
|
+
return NOOP;
|
|
4433
|
+
}
|
|
4434
|
+
if (value.length === 0 && (key.upArrow || key.downArrow)) {
|
|
4435
|
+
return NOOP;
|
|
4436
|
+
}
|
|
4437
|
+
if (key.leftArrow) {
|
|
4438
|
+
return { next: null, cursor: Math.max(0, cursor - 1), submit: false };
|
|
4439
|
+
}
|
|
4440
|
+
if (key.rightArrow) {
|
|
4441
|
+
return { next: null, cursor: Math.min(value.length, cursor + 1), submit: false };
|
|
4442
|
+
}
|
|
4443
|
+
if (key.upArrow) {
|
|
4444
|
+
const moved = moveCursorUp(value, cursor);
|
|
4445
|
+
return moved === cursor ? NOOP : { next: null, cursor: moved, submit: false };
|
|
4446
|
+
}
|
|
4447
|
+
if (key.downArrow) {
|
|
4448
|
+
const moved = moveCursorDown(value, cursor);
|
|
4449
|
+
return moved === cursor ? NOOP : { next: null, cursor: moved, submit: false };
|
|
4450
|
+
}
|
|
4451
|
+
if (key.ctrl && key.input === "a") {
|
|
4452
|
+
return { next: null, cursor: startOfLine(value, cursor), submit: false };
|
|
4453
|
+
}
|
|
4454
|
+
if (key.ctrl && key.input === "e") {
|
|
4455
|
+
return { next: null, cursor: endOfLine(value, cursor), submit: false };
|
|
3992
4456
|
}
|
|
3993
4457
|
if (key.input === "\n" || key.ctrl && key.input === "j") {
|
|
3994
|
-
return
|
|
3995
|
-
`, submit: false };
|
|
4458
|
+
return insertAt(value, cursor, "\n");
|
|
3996
4459
|
}
|
|
3997
4460
|
if (key.return) {
|
|
3998
|
-
if (key.shift)
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
return { next: `${value.slice(0, -1)}
|
|
4004
|
-
`, submit: false };
|
|
4461
|
+
if (key.shift) return insertAt(value, cursor, "\n");
|
|
4462
|
+
if (cursor === value.length && BACKSLASH_SUFFIX.test(value)) {
|
|
4463
|
+
const replaced = `${value.slice(0, -1)}
|
|
4464
|
+
`;
|
|
4465
|
+
return { next: replaced, cursor: replaced.length, submit: false };
|
|
4005
4466
|
}
|
|
4006
|
-
return { next: null, submit: true, submitValue: value };
|
|
4467
|
+
return { next: null, cursor: null, submit: true, submitValue: value };
|
|
4007
4468
|
}
|
|
4008
|
-
if (key.backspace || key.delete) {
|
|
4009
|
-
if (
|
|
4010
|
-
return {
|
|
4469
|
+
if (key.backspace || key.delete || key.input === "\x7F" || key.input === "\b") {
|
|
4470
|
+
if (cursor === 0) return NOOP;
|
|
4471
|
+
return {
|
|
4472
|
+
next: value.slice(0, cursor - 1) + value.slice(cursor),
|
|
4473
|
+
cursor: cursor - 1,
|
|
4474
|
+
submit: false
|
|
4475
|
+
};
|
|
4011
4476
|
}
|
|
4012
|
-
if ((key.ctrl || key.meta) && key.input.length === 0)
|
|
4013
|
-
|
|
4477
|
+
if ((key.ctrl || key.meta) && key.input.length === 0) return NOOP;
|
|
4478
|
+
if (key.ctrl || key.meta) return NOOP;
|
|
4479
|
+
if (key.input.length > 0) {
|
|
4480
|
+
return insertAt(value, cursor, key.input);
|
|
4014
4481
|
}
|
|
4015
|
-
|
|
4016
|
-
|
|
4482
|
+
return NOOP;
|
|
4483
|
+
}
|
|
4484
|
+
function insertAt(value, cursor, insert) {
|
|
4485
|
+
return {
|
|
4486
|
+
next: value.slice(0, cursor) + insert + value.slice(cursor),
|
|
4487
|
+
cursor: cursor + insert.length,
|
|
4488
|
+
submit: false
|
|
4489
|
+
};
|
|
4490
|
+
}
|
|
4491
|
+
function lineAndColumn(value, cursor) {
|
|
4492
|
+
let line = 0;
|
|
4493
|
+
let col = 0;
|
|
4494
|
+
const n = Math.min(cursor, value.length);
|
|
4495
|
+
for (let i = 0; i < n; i++) {
|
|
4496
|
+
if (value[i] === "\n") {
|
|
4497
|
+
line++;
|
|
4498
|
+
col = 0;
|
|
4499
|
+
} else {
|
|
4500
|
+
col++;
|
|
4501
|
+
}
|
|
4017
4502
|
}
|
|
4018
|
-
return {
|
|
4503
|
+
return { line, col };
|
|
4504
|
+
}
|
|
4505
|
+
function startOfLine(value, cursor) {
|
|
4506
|
+
return value.lastIndexOf("\n", cursor - 1) + 1;
|
|
4507
|
+
}
|
|
4508
|
+
function endOfLine(value, cursor) {
|
|
4509
|
+
const nl = value.indexOf("\n", cursor);
|
|
4510
|
+
return nl === -1 ? value.length : nl;
|
|
4511
|
+
}
|
|
4512
|
+
function moveCursorUp(value, cursor) {
|
|
4513
|
+
const curStart = startOfLine(value, cursor);
|
|
4514
|
+
if (curStart === 0) return cursor;
|
|
4515
|
+
const col = cursor - curStart;
|
|
4516
|
+
const prevEnd = curStart - 1;
|
|
4517
|
+
const prevStart = value.lastIndexOf("\n", prevEnd - 1) + 1;
|
|
4518
|
+
const prevLen = prevEnd - prevStart;
|
|
4519
|
+
return prevStart + Math.min(col, prevLen);
|
|
4520
|
+
}
|
|
4521
|
+
function moveCursorDown(value, cursor) {
|
|
4522
|
+
const nextNl = value.indexOf("\n", cursor);
|
|
4523
|
+
if (nextNl === -1) return cursor;
|
|
4524
|
+
const curStart = startOfLine(value, cursor);
|
|
4525
|
+
const col = cursor - curStart;
|
|
4526
|
+
const nextStart = nextNl + 1;
|
|
4527
|
+
const followingNl = value.indexOf("\n", nextStart);
|
|
4528
|
+
const nextLen = (followingNl === -1 ? value.length : followingNl) - nextStart;
|
|
4529
|
+
return nextStart + Math.min(col, nextLen);
|
|
4019
4530
|
}
|
|
4020
4531
|
|
|
4021
4532
|
// src/cli/ui/PromptInput.tsx
|
|
@@ -4026,11 +4537,19 @@ function PromptInput({
|
|
|
4026
4537
|
disabled,
|
|
4027
4538
|
placeholder
|
|
4028
4539
|
}) {
|
|
4540
|
+
const [cursor, setCursor] = useState2(value.length);
|
|
4541
|
+
const lastLocalValueRef = useRef(value);
|
|
4542
|
+
if (value !== lastLocalValueRef.current) {
|
|
4543
|
+
lastLocalValueRef.current = value;
|
|
4544
|
+
if (cursor !== value.length) {
|
|
4545
|
+
setCursor(value.length);
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4029
4548
|
const tick = useTick();
|
|
4030
4549
|
const showCursor = disabled ? false : Math.floor(tick / 4) % 2 === 0;
|
|
4031
4550
|
useInput(
|
|
4032
4551
|
(input, key) => {
|
|
4033
|
-
const
|
|
4552
|
+
const ke = {
|
|
4034
4553
|
input,
|
|
4035
4554
|
return: key.return,
|
|
4036
4555
|
shift: key.shift,
|
|
@@ -4047,8 +4566,14 @@ function PromptInput({
|
|
|
4047
4566
|
pageUp: key.pageUp,
|
|
4048
4567
|
pageDown: key.pageDown
|
|
4049
4568
|
};
|
|
4050
|
-
const action = processMultilineKey(value,
|
|
4051
|
-
if (action.next !== null)
|
|
4569
|
+
const action = processMultilineKey(value, cursor, ke);
|
|
4570
|
+
if (action.next !== null) {
|
|
4571
|
+
lastLocalValueRef.current = action.next;
|
|
4572
|
+
onChange(action.next);
|
|
4573
|
+
}
|
|
4574
|
+
if (action.cursor !== null) {
|
|
4575
|
+
setCursor(action.cursor);
|
|
4576
|
+
}
|
|
4052
4577
|
if (action.submit) onSubmit(action.submitValue ?? value);
|
|
4053
4578
|
},
|
|
4054
4579
|
{ isActive: !disabled }
|
|
@@ -4056,27 +4581,211 @@ function PromptInput({
|
|
|
4056
4581
|
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? "type a message, or /command \xB7 Ctrl+J for newline";
|
|
4057
4582
|
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
4058
4583
|
const borderColor = disabled ? "gray" : "cyan";
|
|
4584
|
+
const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
|
|
4059
4585
|
return /* @__PURE__ */ React5.createElement(Box4, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
4060
|
-
const isLast = i === lines.length - 1;
|
|
4061
4586
|
const isFirst = i === 0;
|
|
4062
4587
|
const showPlaceholder = isFirst && value.length === 0;
|
|
4588
|
+
const isCursorLine = i === cursorLine;
|
|
4063
4589
|
return (
|
|
4064
4590
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
4065
|
-
/* @__PURE__ */ React5.createElement(Box4, { key: i }, isFirst ? /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, " "), showPlaceholder
|
|
4591
|
+
/* @__PURE__ */ React5.createElement(Box4, { key: i }, isFirst ? /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, " "), showPlaceholder ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, isCursorLine && !disabled ? /* @__PURE__ */ React5.createElement(Text4, { color: borderColor }, showCursor ? "\u258C" : " ") : null, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, effectivePlaceholder)) : isCursorLine && !disabled ? /* @__PURE__ */ React5.createElement(
|
|
4592
|
+
LineWithCursor,
|
|
4593
|
+
{
|
|
4594
|
+
line,
|
|
4595
|
+
col: cursorCol,
|
|
4596
|
+
showCursor,
|
|
4597
|
+
borderColor
|
|
4598
|
+
}
|
|
4599
|
+
) : /* @__PURE__ */ React5.createElement(Text4, null, line))
|
|
4066
4600
|
);
|
|
4067
4601
|
}));
|
|
4068
4602
|
}
|
|
4603
|
+
function LineWithCursor({
|
|
4604
|
+
line,
|
|
4605
|
+
col,
|
|
4606
|
+
showCursor,
|
|
4607
|
+
borderColor
|
|
4608
|
+
}) {
|
|
4609
|
+
const before = line.slice(0, col);
|
|
4610
|
+
const atCursor = line.slice(col, col + 1);
|
|
4611
|
+
const after = line.slice(col + 1);
|
|
4612
|
+
if (atCursor.length === 0) {
|
|
4613
|
+
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text4, null, before), /* @__PURE__ */ React5.createElement(Text4, { color: borderColor }, showCursor ? "\u258C" : " "));
|
|
4614
|
+
}
|
|
4615
|
+
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text4, null, before), /* @__PURE__ */ React5.createElement(Text4, { inverse: showCursor }, atCursor), /* @__PURE__ */ React5.createElement(Text4, null, after));
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
// src/cli/ui/ShellConfirm.tsx
|
|
4619
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
4620
|
+
import React7 from "react";
|
|
4621
|
+
|
|
4622
|
+
// src/cli/ui/Select.tsx
|
|
4623
|
+
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
4624
|
+
import React6, { useState as useState3 } from "react";
|
|
4625
|
+
function SingleSelect({
|
|
4626
|
+
items,
|
|
4627
|
+
initialValue,
|
|
4628
|
+
onSubmit,
|
|
4629
|
+
onCancel
|
|
4630
|
+
}) {
|
|
4631
|
+
const initialIndex = Math.max(
|
|
4632
|
+
0,
|
|
4633
|
+
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
4634
|
+
);
|
|
4635
|
+
const [index, setIndex] = useState3(initialIndex === -1 ? 0 : initialIndex);
|
|
4636
|
+
useInput2((_input, key) => {
|
|
4637
|
+
if (key.upArrow) {
|
|
4638
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4639
|
+
} else if (key.downArrow) {
|
|
4640
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4641
|
+
} else if (key.return) {
|
|
4642
|
+
const chosen = items[index];
|
|
4643
|
+
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
4644
|
+
} else if (key.escape && onCancel) {
|
|
4645
|
+
onCancel();
|
|
4646
|
+
}
|
|
4647
|
+
});
|
|
4648
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React6.createElement(
|
|
4649
|
+
SelectRow,
|
|
4650
|
+
{
|
|
4651
|
+
key: item.value,
|
|
4652
|
+
item,
|
|
4653
|
+
active: i === index,
|
|
4654
|
+
marker: i === index ? "\u25B8" : " "
|
|
4655
|
+
}
|
|
4656
|
+
)));
|
|
4657
|
+
}
|
|
4658
|
+
function MultiSelect({
|
|
4659
|
+
items,
|
|
4660
|
+
initialSelected = [],
|
|
4661
|
+
onSubmit,
|
|
4662
|
+
onCancel,
|
|
4663
|
+
footer
|
|
4664
|
+
}) {
|
|
4665
|
+
const [index, setIndex] = useState3(() => {
|
|
4666
|
+
const first = items.findIndex((i) => !i.disabled);
|
|
4667
|
+
return first === -1 ? 0 : first;
|
|
4668
|
+
});
|
|
4669
|
+
const [selected, setSelected] = useState3(new Set(initialSelected));
|
|
4670
|
+
useInput2((input, key) => {
|
|
4671
|
+
if (key.upArrow) {
|
|
4672
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4673
|
+
} else if (key.downArrow) {
|
|
4674
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4675
|
+
} else if (input === " ") {
|
|
4676
|
+
const item = items[index];
|
|
4677
|
+
if (!item || item.disabled) return;
|
|
4678
|
+
setSelected((prev) => {
|
|
4679
|
+
const next = new Set(prev);
|
|
4680
|
+
if (next.has(item.value)) next.delete(item.value);
|
|
4681
|
+
else next.add(item.value);
|
|
4682
|
+
return next;
|
|
4683
|
+
});
|
|
4684
|
+
} else if (key.return) {
|
|
4685
|
+
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
4686
|
+
onSubmit(ordered);
|
|
4687
|
+
} else if (key.escape && onCancel) {
|
|
4688
|
+
onCancel();
|
|
4689
|
+
}
|
|
4690
|
+
});
|
|
4691
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => {
|
|
4692
|
+
const checked = selected.has(item.value);
|
|
4693
|
+
const marker = checked ? "[x]" : "[ ]";
|
|
4694
|
+
return /* @__PURE__ */ React6.createElement(
|
|
4695
|
+
SelectRow,
|
|
4696
|
+
{
|
|
4697
|
+
key: item.value,
|
|
4698
|
+
item,
|
|
4699
|
+
active: i === index,
|
|
4700
|
+
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
4701
|
+
}
|
|
4702
|
+
);
|
|
4703
|
+
}), footer ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, footer)) : null);
|
|
4704
|
+
}
|
|
4705
|
+
function SelectRow({
|
|
4706
|
+
item,
|
|
4707
|
+
active,
|
|
4708
|
+
marker
|
|
4709
|
+
}) {
|
|
4710
|
+
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
4711
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React6.createElement(Box5, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, item.hint)) : null);
|
|
4712
|
+
}
|
|
4713
|
+
function findNextEnabled(items, from, step) {
|
|
4714
|
+
if (items.length === 0) return 0;
|
|
4715
|
+
let i = from;
|
|
4716
|
+
for (let tries = 0; tries < items.length; tries++) {
|
|
4717
|
+
i = (i + step + items.length) % items.length;
|
|
4718
|
+
if (!items[i]?.disabled) return i;
|
|
4719
|
+
}
|
|
4720
|
+
return from;
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
// src/cli/ui/ShellConfirm.tsx
|
|
4724
|
+
function ShellConfirm({ command, allowPrefix, onChoose }) {
|
|
4725
|
+
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React7.createElement(Box6, null, /* @__PURE__ */ React7.createElement(Text6, { bold: true, color: "yellow" }, "\u25B8 model wants to run a shell command")), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, "$ "), /* @__PURE__ */ React7.createElement(Text6, { color: "cyan" }, command))), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(
|
|
4726
|
+
SingleSelect,
|
|
4727
|
+
{
|
|
4728
|
+
initialValue: "run_once",
|
|
4729
|
+
items: [
|
|
4730
|
+
{
|
|
4731
|
+
value: "run_once",
|
|
4732
|
+
label: "Run once",
|
|
4733
|
+
hint: "Execute this command, don't remember it."
|
|
4734
|
+
},
|
|
4735
|
+
{
|
|
4736
|
+
value: "always_allow",
|
|
4737
|
+
label: `Always allow "${allowPrefix}" in this project`,
|
|
4738
|
+
hint: "Save the prefix to ~/.reasonix/config.json; future matches auto-run."
|
|
4739
|
+
},
|
|
4740
|
+
{
|
|
4741
|
+
value: "deny",
|
|
4742
|
+
label: "Deny",
|
|
4743
|
+
hint: "Tell the model the user refused; it will continue without this command."
|
|
4744
|
+
}
|
|
4745
|
+
],
|
|
4746
|
+
onSubmit: (v) => onChoose(v)
|
|
4747
|
+
}
|
|
4748
|
+
)));
|
|
4749
|
+
}
|
|
4750
|
+
function derivePrefix(command) {
|
|
4751
|
+
const tokens = command.trim().split(/\s+/).filter(Boolean);
|
|
4752
|
+
if (tokens.length === 0) return "";
|
|
4753
|
+
if (tokens.length === 1) return tokens[0];
|
|
4754
|
+
const first = tokens[0];
|
|
4755
|
+
const TWO_TOKEN_WRAPPERS = /* @__PURE__ */ new Set([
|
|
4756
|
+
"npm",
|
|
4757
|
+
"npx",
|
|
4758
|
+
"pnpm",
|
|
4759
|
+
"yarn",
|
|
4760
|
+
"bun",
|
|
4761
|
+
"git",
|
|
4762
|
+
"cargo",
|
|
4763
|
+
"go",
|
|
4764
|
+
"docker",
|
|
4765
|
+
"kubectl",
|
|
4766
|
+
"python",
|
|
4767
|
+
"python3",
|
|
4768
|
+
"deno",
|
|
4769
|
+
"pip",
|
|
4770
|
+
"pip3",
|
|
4771
|
+
"make",
|
|
4772
|
+
"rake",
|
|
4773
|
+
"bundle",
|
|
4774
|
+
"gem"
|
|
4775
|
+
]);
|
|
4776
|
+
return TWO_TOKEN_WRAPPERS.has(first) ? `${first} ${tokens[1]}` : first;
|
|
4777
|
+
}
|
|
4069
4778
|
|
|
4070
4779
|
// src/cli/ui/SlashSuggestions.tsx
|
|
4071
|
-
import { Box as
|
|
4072
|
-
import
|
|
4780
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
4781
|
+
import React8 from "react";
|
|
4073
4782
|
function SlashSuggestions({
|
|
4074
4783
|
matches,
|
|
4075
4784
|
selectedIndex
|
|
4076
4785
|
}) {
|
|
4077
4786
|
if (matches === null) return null;
|
|
4078
4787
|
if (matches.length === 0) {
|
|
4079
|
-
return /* @__PURE__ */
|
|
4788
|
+
return /* @__PURE__ */ React8.createElement(Box7, { paddingX: 1 }, /* @__PURE__ */ React8.createElement(Text7, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
|
|
4080
4789
|
}
|
|
4081
4790
|
const MAX = 8;
|
|
4082
4791
|
const total = matches.length;
|
|
@@ -4084,21 +4793,21 @@ function SlashSuggestions({
|
|
|
4084
4793
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
4085
4794
|
const hiddenAbove = windowStart;
|
|
4086
4795
|
const hiddenBelow = total - windowStart - shown.length;
|
|
4087
|
-
return /* @__PURE__ */
|
|
4796
|
+
return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React8.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " \u2191/\u2193 navigate \xB7 Tab or Enter to pick"));
|
|
4088
4797
|
}
|
|
4089
4798
|
function SuggestionRow({ spec, isSelected }) {
|
|
4090
4799
|
const marker = isSelected ? "\u25B8" : " ";
|
|
4091
4800
|
const name = `/${spec.cmd}`;
|
|
4092
4801
|
const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
|
|
4093
4802
|
if (isSelected) {
|
|
4094
|
-
return /* @__PURE__ */
|
|
4803
|
+
return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React8.createElement(Text7, { color: "cyan" }, " ", spec.summary));
|
|
4095
4804
|
}
|
|
4096
|
-
return /* @__PURE__ */
|
|
4805
|
+
return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
|
|
4097
4806
|
}
|
|
4098
4807
|
|
|
4099
4808
|
// src/cli/ui/StatsPanel.tsx
|
|
4100
|
-
import { Box as
|
|
4101
|
-
import
|
|
4809
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
4810
|
+
import React9 from "react";
|
|
4102
4811
|
function StatsPanel({
|
|
4103
4812
|
summary,
|
|
4104
4813
|
model,
|
|
@@ -4113,7 +4822,7 @@ function StatsPanel({
|
|
|
4113
4822
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
4114
4823
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
4115
4824
|
const ctxColor = ctxRatio >= 0.8 ? "red" : ctxRatio >= 0.5 ? "yellow" : void 0;
|
|
4116
|
-
return /* @__PURE__ */
|
|
4825
|
+
return /* @__PURE__ */ React9.createElement(Box8, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(Box8, { justifyContent: "space-between" }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, model), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React9.createElement(Text8, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React9.createElement(Text8, { color: "blue" }, " \xB7 branch", branchBudget) : null), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "cache hit "), /* @__PURE__ */ React9.createElement(Text8, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "cost "), /* @__PURE__ */ React9.createElement(Text8, { color: "green", bold: true }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")")), summary.lastPromptTokens > 0 ? /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "ctx "), /* @__PURE__ */ React9.createElement(Text8, { color: ctxColor, bold: ctxColor !== void 0 }, formatTokens(summary.lastPromptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " (", (ctxRatio * 100).toFixed(0), "%)"), ctxRatio >= 0.8 ? /* @__PURE__ */ React9.createElement(Text8, { color: "red", bold: true }, " ", "\xB7 /compact") : null) : null, balance ? /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "balance "), /* @__PURE__ */ React9.createElement(Text8, { color: balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green", bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : "")) : null));
|
|
4117
4826
|
}
|
|
4118
4827
|
function formatTokens(n) {
|
|
4119
4828
|
if (n < 1e3) return String(n);
|
|
@@ -4577,23 +5286,25 @@ function App({
|
|
|
4577
5286
|
codeMode
|
|
4578
5287
|
}) {
|
|
4579
5288
|
const { exit } = useApp();
|
|
4580
|
-
const [historical, setHistorical] =
|
|
4581
|
-
const [streaming, setStreaming] =
|
|
4582
|
-
const [input, setInput] =
|
|
4583
|
-
const [busy, setBusy] =
|
|
4584
|
-
const abortedThisTurn =
|
|
4585
|
-
const [ongoingTool, setOngoingTool] =
|
|
4586
|
-
const [toolProgress, setToolProgress] =
|
|
4587
|
-
const [statusLine, setStatusLine] =
|
|
4588
|
-
const [balance, setBalance] =
|
|
4589
|
-
const lastEditSnapshots =
|
|
4590
|
-
const pendingEdits =
|
|
4591
|
-
const
|
|
4592
|
-
const
|
|
4593
|
-
const
|
|
4594
|
-
const
|
|
4595
|
-
const
|
|
4596
|
-
const
|
|
5289
|
+
const [historical, setHistorical] = useState4([]);
|
|
5290
|
+
const [streaming, setStreaming] = useState4(null);
|
|
5291
|
+
const [input, setInput] = useState4("");
|
|
5292
|
+
const [busy, setBusy] = useState4(false);
|
|
5293
|
+
const abortedThisTurn = useRef2(false);
|
|
5294
|
+
const [ongoingTool, setOngoingTool] = useState4(null);
|
|
5295
|
+
const [toolProgress, setToolProgress] = useState4(null);
|
|
5296
|
+
const [statusLine, setStatusLine] = useState4(null);
|
|
5297
|
+
const [balance, setBalance] = useState4(null);
|
|
5298
|
+
const lastEditSnapshots = useRef2(null);
|
|
5299
|
+
const pendingEdits = useRef2([]);
|
|
5300
|
+
const [pendingShell, setPendingShell] = useState4(null);
|
|
5301
|
+
const [queuedSubmit, setQueuedSubmit] = useState4(null);
|
|
5302
|
+
const promptHistory = useRef2([]);
|
|
5303
|
+
const historyCursor = useRef2(-1);
|
|
5304
|
+
const assistantIterCounter = useRef2(0);
|
|
5305
|
+
const toolHistoryRef = useRef2([]);
|
|
5306
|
+
const [slashSelected, setSlashSelected] = useState4(0);
|
|
5307
|
+
const [summary, setSummary] = useState4({
|
|
4597
5308
|
turns: 0,
|
|
4598
5309
|
totalCostUsd: 0,
|
|
4599
5310
|
totalInputCostUsd: 0,
|
|
@@ -4603,7 +5314,7 @@ function App({
|
|
|
4603
5314
|
cacheHitRatio: 0,
|
|
4604
5315
|
lastPromptTokens: 0
|
|
4605
5316
|
});
|
|
4606
|
-
const transcriptRef =
|
|
5317
|
+
const transcriptRef = useRef2(null);
|
|
4607
5318
|
if (transcript && !transcriptRef.current) {
|
|
4608
5319
|
transcriptRef.current = openTranscriptFile(transcript, {
|
|
4609
5320
|
version: 1,
|
|
@@ -4628,7 +5339,7 @@ function App({
|
|
|
4628
5339
|
return prev;
|
|
4629
5340
|
});
|
|
4630
5341
|
}, [slashMatches]);
|
|
4631
|
-
const loopRef =
|
|
5342
|
+
const loopRef = useRef2(null);
|
|
4632
5343
|
const loop = useMemo(() => {
|
|
4633
5344
|
if (loopRef.current) return loopRef.current;
|
|
4634
5345
|
const client = new DeepSeekClient();
|
|
@@ -4665,7 +5376,7 @@ function App({
|
|
|
4665
5376
|
if (progressSink.current) progressSink.current = null;
|
|
4666
5377
|
};
|
|
4667
5378
|
}, [progressSink]);
|
|
4668
|
-
const sessionBannerShown =
|
|
5379
|
+
const sessionBannerShown = useRef2(false);
|
|
4669
5380
|
useEffect2(() => {
|
|
4670
5381
|
if (sessionBannerShown.current) return;
|
|
4671
5382
|
sessionBannerShown.current = true;
|
|
@@ -4698,7 +5409,7 @@ function App({
|
|
|
4698
5409
|
]);
|
|
4699
5410
|
}
|
|
4700
5411
|
}, [session, loop]);
|
|
4701
|
-
|
|
5412
|
+
useInput3((_input, key) => {
|
|
4702
5413
|
if (key.escape && busy) {
|
|
4703
5414
|
if (abortedThisTurn.current) return;
|
|
4704
5415
|
abortedThisTurn.current = true;
|
|
@@ -4706,6 +5417,7 @@ function App({
|
|
|
4706
5417
|
return;
|
|
4707
5418
|
}
|
|
4708
5419
|
if (busy) return;
|
|
5420
|
+
if (pendingShell) return;
|
|
4709
5421
|
if (slashMatches && slashMatches.length > 0) {
|
|
4710
5422
|
if (key.upArrow) {
|
|
4711
5423
|
setSlashSelected((i) => Math.max(0, i - 1));
|
|
@@ -4721,20 +5433,22 @@ function App({
|
|
|
4721
5433
|
return;
|
|
4722
5434
|
}
|
|
4723
5435
|
}
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
if (
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
if (
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
5436
|
+
if (input.length === 0) {
|
|
5437
|
+
const hist = promptHistory.current;
|
|
5438
|
+
if (key.upArrow) {
|
|
5439
|
+
if (hist.length === 0) return;
|
|
5440
|
+
const nextCursor = Math.min(historyCursor.current + 1, hist.length - 1);
|
|
5441
|
+
historyCursor.current = nextCursor;
|
|
5442
|
+
setInput(hist[hist.length - 1 - nextCursor] ?? "");
|
|
5443
|
+
return;
|
|
5444
|
+
}
|
|
5445
|
+
if (key.downArrow) {
|
|
5446
|
+
if (historyCursor.current < 0) return;
|
|
5447
|
+
const nextCursor = historyCursor.current - 1;
|
|
5448
|
+
historyCursor.current = nextCursor;
|
|
5449
|
+
setInput(nextCursor < 0 ? "" : hist[hist.length - 1 - nextCursor] ?? "");
|
|
5450
|
+
return;
|
|
5451
|
+
}
|
|
4738
5452
|
}
|
|
4739
5453
|
});
|
|
4740
5454
|
const codeUndo = useCallback(() => {
|
|
@@ -4973,6 +5687,15 @@ function App({
|
|
|
4973
5687
|
toolName: ev.toolName
|
|
4974
5688
|
}
|
|
4975
5689
|
]);
|
|
5690
|
+
if (codeMode && ev.toolName === "run_command" && ev.content.includes('"NeedsConfirmationError:') && ev.toolArgs) {
|
|
5691
|
+
try {
|
|
5692
|
+
const parsed = JSON.parse(ev.toolArgs);
|
|
5693
|
+
if (typeof parsed.command === "string" && parsed.command.trim()) {
|
|
5694
|
+
setPendingShell(parsed.command.trim());
|
|
5695
|
+
}
|
|
5696
|
+
} catch {
|
|
5697
|
+
}
|
|
5698
|
+
}
|
|
4976
5699
|
} else if (ev.role === "error") {
|
|
4977
5700
|
setHistorical((prev) => [
|
|
4978
5701
|
...prev,
|
|
@@ -5017,7 +5740,68 @@ function App({
|
|
|
5017
5740
|
writeTranscript
|
|
5018
5741
|
]
|
|
5019
5742
|
);
|
|
5020
|
-
|
|
5743
|
+
const handleShellConfirm = useCallback(
|
|
5744
|
+
async (choice) => {
|
|
5745
|
+
const cmd = pendingShell;
|
|
5746
|
+
if (!cmd || !codeMode) return;
|
|
5747
|
+
setPendingShell(null);
|
|
5748
|
+
let synthetic;
|
|
5749
|
+
if (choice === "deny") {
|
|
5750
|
+
setHistorical((prev) => [
|
|
5751
|
+
...prev,
|
|
5752
|
+
{ id: `sh-deny-${Date.now()}`, role: "info", text: `\u25B8 denied: ${cmd}` }
|
|
5753
|
+
]);
|
|
5754
|
+
synthetic = `I denied running \`${cmd}\`. Please continue without running it.`;
|
|
5755
|
+
} else {
|
|
5756
|
+
if (choice === "always_allow") {
|
|
5757
|
+
const prefix = derivePrefix(cmd);
|
|
5758
|
+
addProjectShellAllowed(codeMode.rootDir, prefix);
|
|
5759
|
+
setHistorical((prev) => [
|
|
5760
|
+
...prev,
|
|
5761
|
+
{
|
|
5762
|
+
id: `sh-allow-${Date.now()}`,
|
|
5763
|
+
role: "info",
|
|
5764
|
+
text: `\u25B8 always allowed "${prefix}" for ${codeMode.rootDir}`
|
|
5765
|
+
}
|
|
5766
|
+
]);
|
|
5767
|
+
}
|
|
5768
|
+
setHistorical((prev) => [
|
|
5769
|
+
...prev,
|
|
5770
|
+
{ id: `sh-run-${Date.now()}`, role: "info", text: `\u25B8 running: ${cmd}` }
|
|
5771
|
+
]);
|
|
5772
|
+
let body;
|
|
5773
|
+
try {
|
|
5774
|
+
const res = await runCommand(cmd, { cwd: codeMode.rootDir });
|
|
5775
|
+
body = formatCommandResult(cmd, res);
|
|
5776
|
+
} catch (err) {
|
|
5777
|
+
body = `$ ${cmd}
|
|
5778
|
+
[failed to spawn] ${err.message}`;
|
|
5779
|
+
}
|
|
5780
|
+
setHistorical((prev) => [
|
|
5781
|
+
...prev,
|
|
5782
|
+
{ id: `sh-out-${Date.now()}`, role: "info", text: body }
|
|
5783
|
+
]);
|
|
5784
|
+
synthetic = `I ran the command you requested. Output:
|
|
5785
|
+
|
|
5786
|
+
${body}`;
|
|
5787
|
+
}
|
|
5788
|
+
if (busy) {
|
|
5789
|
+
loop.abort();
|
|
5790
|
+
setQueuedSubmit(synthetic);
|
|
5791
|
+
} else {
|
|
5792
|
+
await handleSubmit(synthetic);
|
|
5793
|
+
}
|
|
5794
|
+
},
|
|
5795
|
+
[pendingShell, codeMode, handleSubmit, busy, loop]
|
|
5796
|
+
);
|
|
5797
|
+
useEffect2(() => {
|
|
5798
|
+
if (!busy && queuedSubmit !== null) {
|
|
5799
|
+
const text = queuedSubmit;
|
|
5800
|
+
setQueuedSubmit(null);
|
|
5801
|
+
void handleSubmit(text);
|
|
5802
|
+
}
|
|
5803
|
+
}, [busy, queuedSubmit, handleSubmit]);
|
|
5804
|
+
return /* @__PURE__ */ React10.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(
|
|
5021
5805
|
StatsPanel,
|
|
5022
5806
|
{
|
|
5023
5807
|
summary,
|
|
@@ -5027,13 +5811,28 @@ function App({
|
|
|
5027
5811
|
branchBudget: loop.branchOptions.budget,
|
|
5028
5812
|
balance
|
|
5029
5813
|
}
|
|
5030
|
-
), /* @__PURE__ */
|
|
5814
|
+
), /* @__PURE__ */ React10.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React10.createElement(EventRow, { key: item.id, event: item })), !PLAIN_UI && !pendingShell && streaming ? /* @__PURE__ */ React10.createElement(Box9, { marginY: 1 }, /* @__PURE__ */ React10.createElement(EventRow, { event: streaming })) : null, !PLAIN_UI && !pendingShell && ongoingTool ? /* @__PURE__ */ React10.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !ongoingTool && statusLine ? /* @__PURE__ */ React10.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && !pendingShell && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React10.createElement(StatusRow, { text: "processing\u2026" }) : null, pendingShell ? /* @__PURE__ */ React10.createElement(
|
|
5815
|
+
ShellConfirm,
|
|
5816
|
+
{
|
|
5817
|
+
command: pendingShell,
|
|
5818
|
+
allowPrefix: derivePrefix(pendingShell),
|
|
5819
|
+
onChoose: handleShellConfirm
|
|
5820
|
+
}
|
|
5821
|
+
) : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
|
|
5822
|
+
PromptInput,
|
|
5823
|
+
{
|
|
5824
|
+
value: input,
|
|
5825
|
+
onChange: setInput,
|
|
5826
|
+
onSubmit: handleSubmit,
|
|
5827
|
+
disabled: busy
|
|
5828
|
+
}
|
|
5829
|
+
), /* @__PURE__ */ React10.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }))));
|
|
5031
5830
|
}
|
|
5032
5831
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
5033
5832
|
function StatusRow({ text }) {
|
|
5034
5833
|
const tick = useTick();
|
|
5035
5834
|
const elapsed = useElapsedSeconds();
|
|
5036
|
-
return /* @__PURE__ */
|
|
5835
|
+
return /* @__PURE__ */ React10.createElement(Box9, { marginY: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${elapsed}s`));
|
|
5037
5836
|
}
|
|
5038
5837
|
function OngoingToolRow({
|
|
5039
5838
|
tool,
|
|
@@ -5042,7 +5841,7 @@ function OngoingToolRow({
|
|
|
5042
5841
|
const tick = useTick();
|
|
5043
5842
|
const elapsed = useElapsedSeconds();
|
|
5044
5843
|
const summary = summarizeToolArgs(tool.name, tool.args);
|
|
5045
|
-
return /* @__PURE__ */
|
|
5844
|
+
return /* @__PURE__ */ React10.createElement(Box9, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React10.createElement(Box9, { paddingLeft: 2 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, summary)) : null);
|
|
5046
5845
|
}
|
|
5047
5846
|
function renderProgressLine(p) {
|
|
5048
5847
|
const msg = p.message ? ` ${p.message}` : "";
|
|
@@ -5137,13 +5936,60 @@ function describeRepair(repair) {
|
|
|
5137
5936
|
return parts.length ? `[repair] ${parts.join(", ")}` : "";
|
|
5138
5937
|
}
|
|
5139
5938
|
|
|
5939
|
+
// src/cli/ui/SessionPicker.tsx
|
|
5940
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
5941
|
+
import React11 from "react";
|
|
5942
|
+
function SessionPicker({
|
|
5943
|
+
sessionName,
|
|
5944
|
+
messageCount,
|
|
5945
|
+
lastActive,
|
|
5946
|
+
onChoose
|
|
5947
|
+
}) {
|
|
5948
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React11.createElement(Box10, { marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Text10, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, ` \xB7 last active ${relativeTime(lastActive)}`)), /* @__PURE__ */ React11.createElement(
|
|
5949
|
+
SingleSelect,
|
|
5950
|
+
{
|
|
5951
|
+
initialValue: "new",
|
|
5952
|
+
items: [
|
|
5953
|
+
{
|
|
5954
|
+
value: "new",
|
|
5955
|
+
label: "Start new conversation",
|
|
5956
|
+
hint: "Previous messages kept on disk; your turn starts fresh."
|
|
5957
|
+
},
|
|
5958
|
+
{
|
|
5959
|
+
value: "resume",
|
|
5960
|
+
label: "Resume",
|
|
5961
|
+
hint: `Continue where you left off (${messageCount} messages in context).`
|
|
5962
|
+
},
|
|
5963
|
+
{
|
|
5964
|
+
value: "delete",
|
|
5965
|
+
label: "Delete and start new",
|
|
5966
|
+
hint: "Wipes the session file irreversibly. Other sessions untouched."
|
|
5967
|
+
}
|
|
5968
|
+
],
|
|
5969
|
+
onSubmit: (v) => onChoose(v)
|
|
5970
|
+
}
|
|
5971
|
+
), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
|
|
5972
|
+
}
|
|
5973
|
+
function relativeTime(date) {
|
|
5974
|
+
const ms = Date.now() - date.getTime();
|
|
5975
|
+
const mins = Math.floor(ms / 6e4);
|
|
5976
|
+
if (mins < 1) return "just now";
|
|
5977
|
+
if (mins < 60) return `${mins}m ago`;
|
|
5978
|
+
const hours = Math.floor(mins / 60);
|
|
5979
|
+
if (hours < 24) return `${hours}h ago`;
|
|
5980
|
+
const days = Math.floor(hours / 24);
|
|
5981
|
+
if (days === 1) return "yesterday";
|
|
5982
|
+
if (days < 7) return `${days}d ago`;
|
|
5983
|
+
return date.toISOString().slice(0, 10);
|
|
5984
|
+
}
|
|
5985
|
+
|
|
5140
5986
|
// src/cli/ui/Setup.tsx
|
|
5141
|
-
import { Box as
|
|
5987
|
+
import { Box as Box11, Text as Text11, useApp as useApp2 } from "ink";
|
|
5142
5988
|
import TextInput from "ink-text-input";
|
|
5143
|
-
import
|
|
5989
|
+
import React12, { useState as useState5 } from "react";
|
|
5144
5990
|
function Setup({ onReady }) {
|
|
5145
|
-
const [value, setValue] =
|
|
5146
|
-
const [error, setError] =
|
|
5991
|
+
const [value, setValue] = useState5("");
|
|
5992
|
+
const [error, setError] = useState5(null);
|
|
5147
5993
|
const { exit } = useApp2();
|
|
5148
5994
|
const handleSubmit = (raw) => {
|
|
5149
5995
|
const trimmed = raw.trim();
|
|
@@ -5164,7 +6010,7 @@ function Setup({ onReady }) {
|
|
|
5164
6010
|
}
|
|
5165
6011
|
onReady(trimmed);
|
|
5166
6012
|
};
|
|
5167
|
-
return /* @__PURE__ */
|
|
6013
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React12.createElement(
|
|
5168
6014
|
TextInput,
|
|
5169
6015
|
{
|
|
5170
6016
|
value,
|
|
@@ -5173,14 +6019,23 @@ function Setup({ onReady }) {
|
|
|
5173
6019
|
mask: "\u2022",
|
|
5174
6020
|
placeholder: "sk-..."
|
|
5175
6021
|
}
|
|
5176
|
-
)), error ? /* @__PURE__ */
|
|
6022
|
+
)), error ? /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "red" }, error)) : value ? /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, "(Type /exit to abort.)")));
|
|
5177
6023
|
}
|
|
5178
6024
|
|
|
5179
6025
|
// src/cli/commands/chat.tsx
|
|
5180
|
-
function Root({
|
|
5181
|
-
|
|
6026
|
+
function Root({
|
|
6027
|
+
initialKey,
|
|
6028
|
+
tools,
|
|
6029
|
+
mcpSpecs,
|
|
6030
|
+
mcpServers,
|
|
6031
|
+
progressSink,
|
|
6032
|
+
sessionPreview,
|
|
6033
|
+
...appProps
|
|
6034
|
+
}) {
|
|
6035
|
+
const [key, setKey] = useState6(initialKey);
|
|
6036
|
+
const [pending, setPending] = useState6(sessionPreview);
|
|
5182
6037
|
if (!key) {
|
|
5183
|
-
return /* @__PURE__ */
|
|
6038
|
+
return /* @__PURE__ */ React13.createElement(
|
|
5184
6039
|
Setup,
|
|
5185
6040
|
{
|
|
5186
6041
|
onReady: (k) => {
|
|
@@ -5191,7 +6046,23 @@ function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appPro
|
|
|
5191
6046
|
);
|
|
5192
6047
|
}
|
|
5193
6048
|
process.env.DEEPSEEK_API_KEY = key;
|
|
5194
|
-
|
|
6049
|
+
if (pending && appProps.session) {
|
|
6050
|
+
return /* @__PURE__ */ React13.createElement(
|
|
6051
|
+
SessionPicker,
|
|
6052
|
+
{
|
|
6053
|
+
sessionName: appProps.session,
|
|
6054
|
+
messageCount: pending.messageCount,
|
|
6055
|
+
lastActive: pending.lastActive,
|
|
6056
|
+
onChoose: (choice) => {
|
|
6057
|
+
if (choice === "new" || choice === "delete") {
|
|
6058
|
+
rewriteSession(appProps.session, []);
|
|
6059
|
+
}
|
|
6060
|
+
setPending(void 0);
|
|
6061
|
+
}
|
|
6062
|
+
}
|
|
6063
|
+
);
|
|
6064
|
+
}
|
|
6065
|
+
return /* @__PURE__ */ React13.createElement(
|
|
5195
6066
|
App,
|
|
5196
6067
|
{
|
|
5197
6068
|
model: appProps.model,
|
|
@@ -5274,8 +6145,23 @@ async function chatCommand(opts) {
|
|
|
5274
6145
|
}
|
|
5275
6146
|
}
|
|
5276
6147
|
const mcpSpecs = successfulSpecs;
|
|
6148
|
+
if (searchEnabled()) {
|
|
6149
|
+
if (!tools) tools = new ToolRegistry();
|
|
6150
|
+
registerWebTools(tools);
|
|
6151
|
+
}
|
|
6152
|
+
let sessionPreview;
|
|
6153
|
+
if (opts.session && !opts.forceResume && !opts.forceNew) {
|
|
6154
|
+
const prior = loadSessionMessages(opts.session);
|
|
6155
|
+
if (prior.length > 0) {
|
|
6156
|
+
const p = sessionPath(opts.session);
|
|
6157
|
+
const mtime = existsSync3(p) ? statSync2(p).mtime : /* @__PURE__ */ new Date();
|
|
6158
|
+
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
6159
|
+
}
|
|
6160
|
+
} else if (opts.session && opts.forceNew) {
|
|
6161
|
+
rewriteSession(opts.session, []);
|
|
6162
|
+
}
|
|
5277
6163
|
const { waitUntilExit } = render(
|
|
5278
|
-
/* @__PURE__ */
|
|
6164
|
+
/* @__PURE__ */ React13.createElement(
|
|
5279
6165
|
Root,
|
|
5280
6166
|
{
|
|
5281
6167
|
initialKey,
|
|
@@ -5283,6 +6169,7 @@ async function chatCommand(opts) {
|
|
|
5283
6169
|
mcpSpecs,
|
|
5284
6170
|
mcpServers,
|
|
5285
6171
|
progressSink,
|
|
6172
|
+
sessionPreview,
|
|
5286
6173
|
...opts
|
|
5287
6174
|
}
|
|
5288
6175
|
),
|
|
@@ -5298,15 +6185,21 @@ async function chatCommand(opts) {
|
|
|
5298
6185
|
}
|
|
5299
6186
|
|
|
5300
6187
|
// src/cli/commands/code.tsx
|
|
5301
|
-
import { basename, resolve as
|
|
6188
|
+
import { basename, resolve as resolve5 } from "path";
|
|
5302
6189
|
async function codeCommand(opts = {}) {
|
|
5303
6190
|
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-MMANQ36Z.js");
|
|
5304
|
-
const rootDir =
|
|
6191
|
+
const rootDir = resolve5(opts.dir ?? process.cwd());
|
|
5305
6192
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
5306
6193
|
const tools = new ToolRegistry();
|
|
5307
6194
|
registerFilesystemTools(tools, { rootDir });
|
|
6195
|
+
registerShellTools(tools, {
|
|
6196
|
+
rootDir,
|
|
6197
|
+
// Per-project "always allow" list persisted from prior ShellConfirm
|
|
6198
|
+
// choices; merged on top of the built-in allowlist in shell.ts.
|
|
6199
|
+
extraAllowed: loadProjectShellAllowed(rootDir)
|
|
6200
|
+
});
|
|
5308
6201
|
process.stderr.write(
|
|
5309
|
-
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native
|
|
6202
|
+
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
5310
6203
|
`
|
|
5311
6204
|
);
|
|
5312
6205
|
await chatCommand({
|
|
@@ -5317,7 +6210,9 @@ async function codeCommand(opts = {}) {
|
|
|
5317
6210
|
transcript: opts.transcript,
|
|
5318
6211
|
session,
|
|
5319
6212
|
seedTools: tools,
|
|
5320
|
-
codeMode: { rootDir }
|
|
6213
|
+
codeMode: { rootDir },
|
|
6214
|
+
forceResume: opts.forceResume,
|
|
6215
|
+
forceNew: opts.forceNew
|
|
5321
6216
|
});
|
|
5322
6217
|
}
|
|
5323
6218
|
|
|
@@ -5325,34 +6220,34 @@ async function codeCommand(opts = {}) {
|
|
|
5325
6220
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
5326
6221
|
import { basename as basename2 } from "path";
|
|
5327
6222
|
import { render as render2 } from "ink";
|
|
5328
|
-
import
|
|
6223
|
+
import React16 from "react";
|
|
5329
6224
|
|
|
5330
6225
|
// src/cli/ui/DiffApp.tsx
|
|
5331
|
-
import { Box as
|
|
5332
|
-
import
|
|
6226
|
+
import { Box as Box13, Static as Static2, Text as Text13, useApp as useApp3, useInput as useInput4 } from "ink";
|
|
6227
|
+
import React15, { useState as useState7 } from "react";
|
|
5333
6228
|
|
|
5334
6229
|
// src/cli/ui/RecordView.tsx
|
|
5335
|
-
import { Box as
|
|
5336
|
-
import
|
|
6230
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
6231
|
+
import React14 from "react";
|
|
5337
6232
|
function RecordView({ rec, compact = false }) {
|
|
5338
6233
|
const toolArgsMax = compact ? 120 : 200;
|
|
5339
6234
|
const toolContentMax = compact ? 200 : 400;
|
|
5340
6235
|
if (rec.role === "user") {
|
|
5341
|
-
return /* @__PURE__ */
|
|
6236
|
+
return /* @__PURE__ */ React14.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text12, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React14.createElement(Text12, null, rec.content));
|
|
5342
6237
|
}
|
|
5343
6238
|
if (rec.role === "assistant_final") {
|
|
5344
|
-
return /* @__PURE__ */
|
|
6239
|
+
return /* @__PURE__ */ React14.createElement(Box12, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React14.createElement(Box12, null, /* @__PURE__ */ React14.createElement(Text12, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React14.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React14.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React14.createElement(Text12, null, rec.content) : /* @__PURE__ */ React14.createElement(Text12, { dimColor: true, italic: true }, "(tool-call response only)"));
|
|
5345
6240
|
}
|
|
5346
6241
|
if (rec.role === "tool") {
|
|
5347
|
-
return /* @__PURE__ */
|
|
6242
|
+
return /* @__PURE__ */ React14.createElement(Box12, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text12, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
|
|
5348
6243
|
}
|
|
5349
6244
|
if (rec.role === "error") {
|
|
5350
|
-
return /* @__PURE__ */
|
|
6245
|
+
return /* @__PURE__ */ React14.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text12, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React14.createElement(Text12, { color: "red" }, rec.error ?? rec.content));
|
|
5351
6246
|
}
|
|
5352
6247
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
5353
6248
|
return null;
|
|
5354
6249
|
}
|
|
5355
|
-
return /* @__PURE__ */
|
|
6250
|
+
return /* @__PURE__ */ React14.createElement(Box12, null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
5356
6251
|
}
|
|
5357
6252
|
function CacheBadge({ usage }) {
|
|
5358
6253
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -5361,7 +6256,7 @@ function CacheBadge({ usage }) {
|
|
|
5361
6256
|
if (total === 0) return null;
|
|
5362
6257
|
const pct2 = hit / total * 100;
|
|
5363
6258
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
5364
|
-
return /* @__PURE__ */
|
|
6259
|
+
return /* @__PURE__ */ React14.createElement(Text12, null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React14.createElement(Text12, { color }, pct2.toFixed(1), "%"));
|
|
5365
6260
|
}
|
|
5366
6261
|
function truncate3(s, max) {
|
|
5367
6262
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -5372,8 +6267,8 @@ function DiffApp({ report }) {
|
|
|
5372
6267
|
const { exit } = useApp3();
|
|
5373
6268
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
5374
6269
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
5375
|
-
const [idx, setIdx] =
|
|
5376
|
-
|
|
6270
|
+
const [idx, setIdx] = useState7(Math.max(0, initialIdx));
|
|
6271
|
+
useInput4((input, key) => {
|
|
5377
6272
|
if (input === "q" || key.ctrl && input === "c") {
|
|
5378
6273
|
exit();
|
|
5379
6274
|
return;
|
|
@@ -5395,7 +6290,7 @@ function DiffApp({ report }) {
|
|
|
5395
6290
|
}
|
|
5396
6291
|
});
|
|
5397
6292
|
const pair = report.pairs[idx];
|
|
5398
|
-
return /* @__PURE__ */
|
|
6293
|
+
return /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React15.createElement(DiffHeader, { report }), /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React15.createElement(Text13, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React15.createElement(Text13, null, pair ? /* @__PURE__ */ React15.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React15.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React15.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React15.createElement(Text13, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React15.createElement(Text13, null, pair.divergenceNote)) : null, /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "j"), "/", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "k"), "/", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "N"), "/", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "g"), "/", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React15.createElement(Text13, { bold: true }, "q"), " ", "quit")));
|
|
5399
6294
|
}
|
|
5400
6295
|
function DiffHeader({ report }) {
|
|
5401
6296
|
const a = report.a;
|
|
@@ -5413,15 +6308,15 @@ function DiffHeader({ report }) {
|
|
|
5413
6308
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
5414
6309
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
5415
6310
|
}
|
|
5416
|
-
return /* @__PURE__ */
|
|
6311
|
+
return /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React15.createElement(Box13, { justifyContent: "space-between" }, /* @__PURE__ */ React15.createElement(Text13, null, /* @__PURE__ */ React15.createElement(Text13, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React15.createElement(Text13, { color: "blue" }, a.label), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, " vs B="), /* @__PURE__ */ React15.createElement(Text13, { color: "magenta" }, b.label)), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React15.createElement(Text13, null, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, "cache "), /* @__PURE__ */ React15.createElement(Text13, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React15.createElement(Text13, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React15.createElement(Text13, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React15.createElement(Text13, null, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, "cost "), /* @__PURE__ */ React15.createElement(Text13, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React15.createElement(Text13, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React15.createElement(Text13, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React15.createElement(Text13, null, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true }, "model calls "), /* @__PURE__ */ React15.createElement(Text13, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true, italic: true }, prefixLine)) : null);
|
|
5417
6312
|
}
|
|
5418
6313
|
function Pane({
|
|
5419
6314
|
label,
|
|
5420
6315
|
headerColor,
|
|
5421
6316
|
records
|
|
5422
6317
|
}) {
|
|
5423
|
-
return /* @__PURE__ */
|
|
5424
|
-
|
|
6318
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6319
|
+
Box13,
|
|
5425
6320
|
{
|
|
5426
6321
|
flexDirection: "column",
|
|
5427
6322
|
flexGrow: 1,
|
|
@@ -5429,21 +6324,21 @@ function Pane({
|
|
|
5429
6324
|
borderStyle: "single",
|
|
5430
6325
|
borderColor: headerColor
|
|
5431
6326
|
},
|
|
5432
|
-
/* @__PURE__ */
|
|
5433
|
-
records.length === 0 ? /* @__PURE__ */
|
|
6327
|
+
/* @__PURE__ */ React15.createElement(Text13, { color: headerColor, bold: true }, label),
|
|
6328
|
+
records.length === 0 ? /* @__PURE__ */ React15.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text13, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React15.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React15.createElement(RecordView, { key, rec, compact: true }))
|
|
5434
6329
|
);
|
|
5435
6330
|
}
|
|
5436
6331
|
function KindBadge({ kind }) {
|
|
5437
6332
|
if (kind === "match") {
|
|
5438
|
-
return /* @__PURE__ */
|
|
6333
|
+
return /* @__PURE__ */ React15.createElement(Text13, { color: "green" }, "\u2713 match");
|
|
5439
6334
|
}
|
|
5440
6335
|
if (kind === "diverge") {
|
|
5441
|
-
return /* @__PURE__ */
|
|
6336
|
+
return /* @__PURE__ */ React15.createElement(Text13, { color: "yellow" }, "\u2605 diverge");
|
|
5442
6337
|
}
|
|
5443
6338
|
if (kind === "only_in_a") {
|
|
5444
|
-
return /* @__PURE__ */
|
|
6339
|
+
return /* @__PURE__ */ React15.createElement(Text13, { color: "blue" }, "\u2190 only in A");
|
|
5445
6340
|
}
|
|
5446
|
-
return /* @__PURE__ */
|
|
6341
|
+
return /* @__PURE__ */ React15.createElement(Text13, { color: "magenta" }, "\u2192 only in B");
|
|
5447
6342
|
}
|
|
5448
6343
|
function paneRecords(pair, side) {
|
|
5449
6344
|
if (!pair) return [];
|
|
@@ -5474,7 +6369,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
5474
6369
|
return;
|
|
5475
6370
|
}
|
|
5476
6371
|
if (wantTui) {
|
|
5477
|
-
const { waitUntilExit } = render2(
|
|
6372
|
+
const { waitUntilExit } = render2(React16.createElement(DiffApp, { report }), {
|
|
5478
6373
|
exitOnCtrlC: true,
|
|
5479
6374
|
patchConsole: false
|
|
5480
6375
|
});
|
|
@@ -5615,16 +6510,16 @@ function pad(s, width) {
|
|
|
5615
6510
|
|
|
5616
6511
|
// src/cli/commands/replay.ts
|
|
5617
6512
|
import { render as render3 } from "ink";
|
|
5618
|
-
import
|
|
6513
|
+
import React18 from "react";
|
|
5619
6514
|
|
|
5620
6515
|
// src/cli/ui/ReplayApp.tsx
|
|
5621
|
-
import { Box as
|
|
5622
|
-
import
|
|
6516
|
+
import { Box as Box14, Static as Static3, Text as Text14, useApp as useApp4, useInput as useInput5 } from "ink";
|
|
6517
|
+
import React17, { useMemo as useMemo2, useState as useState8 } from "react";
|
|
5623
6518
|
function ReplayApp({ meta, pages }) {
|
|
5624
6519
|
const { exit } = useApp4();
|
|
5625
6520
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
5626
|
-
const [idx, setIdx] =
|
|
5627
|
-
|
|
6521
|
+
const [idx, setIdx] = useState8(maxIdx);
|
|
6522
|
+
useInput5((input, key) => {
|
|
5628
6523
|
if (input === "q" || key.ctrl && input === "c") {
|
|
5629
6524
|
exit();
|
|
5630
6525
|
return;
|
|
@@ -5658,14 +6553,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
5658
6553
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
5659
6554
|
const currentPage = pages[idx];
|
|
5660
6555
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
5661
|
-
return /* @__PURE__ */
|
|
6556
|
+
return /* @__PURE__ */ React17.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(
|
|
5662
6557
|
StatsPanel,
|
|
5663
6558
|
{
|
|
5664
6559
|
summary,
|
|
5665
6560
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
5666
6561
|
prefixHash
|
|
5667
6562
|
}
|
|
5668
|
-
), /* @__PURE__ */
|
|
6563
|
+
), /* @__PURE__ */ React17.createElement(Box14, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React17.createElement(Box14, { justifyContent: "space-between" }, /* @__PURE__ */ React17.createElement(Text14, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React17.createElement(Text14, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React17.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React17.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React17.createElement(Text14, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React17.createElement(Box14, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React17.createElement(Text14, { dimColor: true }, /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "j"), "/", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "k"), "/", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React17.createElement(Text14, { bold: true }, "q"), " quit")));
|
|
5669
6564
|
}
|
|
5670
6565
|
|
|
5671
6566
|
// src/cli/commands/replay.ts
|
|
@@ -5677,7 +6572,7 @@ async function replayCommand(opts) {
|
|
|
5677
6572
|
}
|
|
5678
6573
|
const { parsed } = replayFromFile(opts.path);
|
|
5679
6574
|
const pages = groupRecordsByTurn(parsed.records);
|
|
5680
|
-
const { waitUntilExit } = render3(
|
|
6575
|
+
const { waitUntilExit } = render3(React18.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
5681
6576
|
exitOnCtrlC: true,
|
|
5682
6577
|
patchConsole: false
|
|
5683
6578
|
});
|
|
@@ -5804,7 +6699,7 @@ async function ensureApiKey() {
|
|
|
5804
6699
|
rl.close();
|
|
5805
6700
|
}
|
|
5806
6701
|
}
|
|
5807
|
-
async function
|
|
6702
|
+
async function runCommand2(opts) {
|
|
5808
6703
|
loadDotenv();
|
|
5809
6704
|
const apiKey = await ensureApiKey();
|
|
5810
6705
|
process.env.DEEPSEEK_API_KEY = apiKey;
|
|
@@ -5979,113 +6874,12 @@ function truncate4(s, max) {
|
|
|
5979
6874
|
|
|
5980
6875
|
// src/cli/commands/setup.tsx
|
|
5981
6876
|
import { render as render4 } from "ink";
|
|
5982
|
-
import
|
|
6877
|
+
import React20 from "react";
|
|
5983
6878
|
|
|
5984
6879
|
// src/cli/ui/Wizard.tsx
|
|
5985
|
-
import { Box as
|
|
6880
|
+
import { Box as Box15, Text as Text15, useApp as useApp5, useInput as useInput6 } from "ink";
|
|
5986
6881
|
import TextInput2 from "ink-text-input";
|
|
5987
|
-
import
|
|
5988
|
-
|
|
5989
|
-
// src/cli/ui/Select.tsx
|
|
5990
|
-
import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
|
|
5991
|
-
import React16, { useState as useState7 } from "react";
|
|
5992
|
-
function SingleSelect({
|
|
5993
|
-
items,
|
|
5994
|
-
initialValue,
|
|
5995
|
-
onSubmit,
|
|
5996
|
-
onCancel
|
|
5997
|
-
}) {
|
|
5998
|
-
const initialIndex = Math.max(
|
|
5999
|
-
0,
|
|
6000
|
-
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
6001
|
-
);
|
|
6002
|
-
const [index, setIndex] = useState7(initialIndex === -1 ? 0 : initialIndex);
|
|
6003
|
-
useInput5((_input, key) => {
|
|
6004
|
-
if (key.upArrow) {
|
|
6005
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
6006
|
-
} else if (key.downArrow) {
|
|
6007
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
6008
|
-
} else if (key.return) {
|
|
6009
|
-
const chosen = items[index];
|
|
6010
|
-
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
6011
|
-
} else if (key.escape && onCancel) {
|
|
6012
|
-
onCancel();
|
|
6013
|
-
}
|
|
6014
|
-
});
|
|
6015
|
-
return /* @__PURE__ */ React16.createElement(Box12, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React16.createElement(
|
|
6016
|
-
SelectRow,
|
|
6017
|
-
{
|
|
6018
|
-
key: item.value,
|
|
6019
|
-
item,
|
|
6020
|
-
active: i === index,
|
|
6021
|
-
marker: i === index ? "\u25B8" : " "
|
|
6022
|
-
}
|
|
6023
|
-
)));
|
|
6024
|
-
}
|
|
6025
|
-
function MultiSelect({
|
|
6026
|
-
items,
|
|
6027
|
-
initialSelected = [],
|
|
6028
|
-
onSubmit,
|
|
6029
|
-
onCancel,
|
|
6030
|
-
footer
|
|
6031
|
-
}) {
|
|
6032
|
-
const [index, setIndex] = useState7(() => {
|
|
6033
|
-
const first = items.findIndex((i) => !i.disabled);
|
|
6034
|
-
return first === -1 ? 0 : first;
|
|
6035
|
-
});
|
|
6036
|
-
const [selected, setSelected] = useState7(new Set(initialSelected));
|
|
6037
|
-
useInput5((input, key) => {
|
|
6038
|
-
if (key.upArrow) {
|
|
6039
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
6040
|
-
} else if (key.downArrow) {
|
|
6041
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
6042
|
-
} else if (input === " ") {
|
|
6043
|
-
const item = items[index];
|
|
6044
|
-
if (!item || item.disabled) return;
|
|
6045
|
-
setSelected((prev) => {
|
|
6046
|
-
const next = new Set(prev);
|
|
6047
|
-
if (next.has(item.value)) next.delete(item.value);
|
|
6048
|
-
else next.add(item.value);
|
|
6049
|
-
return next;
|
|
6050
|
-
});
|
|
6051
|
-
} else if (key.return) {
|
|
6052
|
-
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
6053
|
-
onSubmit(ordered);
|
|
6054
|
-
} else if (key.escape && onCancel) {
|
|
6055
|
-
onCancel();
|
|
6056
|
-
}
|
|
6057
|
-
});
|
|
6058
|
-
return /* @__PURE__ */ React16.createElement(Box12, { flexDirection: "column" }, items.map((item, i) => {
|
|
6059
|
-
const checked = selected.has(item.value);
|
|
6060
|
-
const marker = checked ? "[x]" : "[ ]";
|
|
6061
|
-
return /* @__PURE__ */ React16.createElement(
|
|
6062
|
-
SelectRow,
|
|
6063
|
-
{
|
|
6064
|
-
key: item.value,
|
|
6065
|
-
item,
|
|
6066
|
-
active: i === index,
|
|
6067
|
-
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
6068
|
-
}
|
|
6069
|
-
);
|
|
6070
|
-
}), footer ? /* @__PURE__ */ React16.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text12, { dimColor: true }, footer)) : null);
|
|
6071
|
-
}
|
|
6072
|
-
function SelectRow({
|
|
6073
|
-
item,
|
|
6074
|
-
active,
|
|
6075
|
-
marker
|
|
6076
|
-
}) {
|
|
6077
|
-
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
6078
|
-
return /* @__PURE__ */ React16.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(Box12, null, /* @__PURE__ */ React16.createElement(Text12, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React16.createElement(Box12, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React16.createElement(Text12, { dimColor: true }, item.hint)) : null);
|
|
6079
|
-
}
|
|
6080
|
-
function findNextEnabled(items, from, step) {
|
|
6081
|
-
if (items.length === 0) return 0;
|
|
6082
|
-
let i = from;
|
|
6083
|
-
for (let tries = 0; tries < items.length; tries++) {
|
|
6084
|
-
i = (i + step + items.length) % items.length;
|
|
6085
|
-
if (!items[i]?.disabled) return i;
|
|
6086
|
-
}
|
|
6087
|
-
return from;
|
|
6088
|
-
}
|
|
6882
|
+
import React19, { useState as useState9 } from "react";
|
|
6089
6883
|
|
|
6090
6884
|
// src/cli/ui/presets.ts
|
|
6091
6885
|
var PRESETS = {
|
|
@@ -6112,19 +6906,19 @@ var PRESET_DESCRIPTIONS = {
|
|
|
6112
6906
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
6113
6907
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
6114
6908
|
const { exit } = useApp5();
|
|
6115
|
-
const [step, setStep] =
|
|
6116
|
-
const [data, setData] =
|
|
6909
|
+
const [step, setStep] = useState9(existingApiKey ? "preset" : "apiKey");
|
|
6910
|
+
const [data, setData] = useState9({
|
|
6117
6911
|
apiKey: existingApiKey ?? "",
|
|
6118
6912
|
preset: initial?.preset ?? "fast",
|
|
6119
6913
|
selectedCatalog: deriveInitialCatalog(initial?.mcp ?? []),
|
|
6120
6914
|
catalogArgs: {}
|
|
6121
6915
|
});
|
|
6122
|
-
const [error, setError] =
|
|
6916
|
+
const [error, setError] = useState9(null);
|
|
6123
6917
|
useInput6((_input, key) => {
|
|
6124
6918
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
6125
6919
|
});
|
|
6126
6920
|
if (step === "apiKey") {
|
|
6127
|
-
return /* @__PURE__ */
|
|
6921
|
+
return /* @__PURE__ */ React19.createElement(
|
|
6128
6922
|
ApiKeyStep,
|
|
6129
6923
|
{
|
|
6130
6924
|
onSubmit: (key) => {
|
|
@@ -6138,7 +6932,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6138
6932
|
);
|
|
6139
6933
|
}
|
|
6140
6934
|
if (step === "preset") {
|
|
6141
|
-
return /* @__PURE__ */
|
|
6935
|
+
return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React19.createElement(
|
|
6142
6936
|
SingleSelect,
|
|
6143
6937
|
{
|
|
6144
6938
|
items: presetItems(),
|
|
@@ -6148,10 +6942,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6148
6942
|
setStep("mcp");
|
|
6149
6943
|
}
|
|
6150
6944
|
}
|
|
6151
|
-
), /* @__PURE__ */
|
|
6945
|
+
), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
|
|
6152
6946
|
}
|
|
6153
6947
|
if (step === "mcp") {
|
|
6154
|
-
return /* @__PURE__ */
|
|
6948
|
+
return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React19.createElement(
|
|
6155
6949
|
MultiSelect,
|
|
6156
6950
|
{
|
|
6157
6951
|
items: mcpItems(),
|
|
@@ -6176,7 +6970,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6176
6970
|
}
|
|
6177
6971
|
const currentName = pending[0];
|
|
6178
6972
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
6179
|
-
return /* @__PURE__ */
|
|
6973
|
+
return /* @__PURE__ */ React19.createElement(
|
|
6180
6974
|
McpArgsStep,
|
|
6181
6975
|
{
|
|
6182
6976
|
entry,
|
|
@@ -6194,7 +6988,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6194
6988
|
}
|
|
6195
6989
|
if (step === "review") {
|
|
6196
6990
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
6197
|
-
return /* @__PURE__ */
|
|
6991
|
+
return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column" }, /* @__PURE__ */ React19.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React19.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React19.createElement(
|
|
6198
6992
|
SummaryLine,
|
|
6199
6993
|
{
|
|
6200
6994
|
label: "MCP",
|
|
@@ -6202,8 +6996,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6202
6996
|
}
|
|
6203
6997
|
), specs.map((spec, i) => (
|
|
6204
6998
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
6205
|
-
/* @__PURE__ */
|
|
6206
|
-
)), /* @__PURE__ */
|
|
6999
|
+
/* @__PURE__ */ React19.createElement(Box15, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "\xB7 ", spec))
|
|
7000
|
+
)), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { color: "red" }, error)) : null, /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React19.createElement(
|
|
6207
7001
|
ReviewConfirm,
|
|
6208
7002
|
{
|
|
6209
7003
|
onConfirm: () => {
|
|
@@ -6229,15 +7023,15 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6229
7023
|
}
|
|
6230
7024
|
));
|
|
6231
7025
|
}
|
|
6232
|
-
return /* @__PURE__ */
|
|
7026
|
+
return /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React19.createElement(ExitOnEnter, { onExit: exit }));
|
|
6233
7027
|
}
|
|
6234
7028
|
function ApiKeyStep({
|
|
6235
7029
|
onSubmit,
|
|
6236
7030
|
error,
|
|
6237
7031
|
onError
|
|
6238
7032
|
}) {
|
|
6239
|
-
const [value, setValue] =
|
|
6240
|
-
return /* @__PURE__ */
|
|
7033
|
+
const [value, setValue] = useState9("");
|
|
7034
|
+
return /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React19.createElement(
|
|
6241
7035
|
TextInput2,
|
|
6242
7036
|
{
|
|
6243
7037
|
value,
|
|
@@ -6254,7 +7048,7 @@ function ApiKeyStep({
|
|
|
6254
7048
|
mask: "\u2022",
|
|
6255
7049
|
placeholder: "sk-..."
|
|
6256
7050
|
}
|
|
6257
|
-
)), error ? /* @__PURE__ */
|
|
7051
|
+
)), error ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { color: "red" }, error)) : value ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "preview: ", redactKey(value))) : null);
|
|
6258
7052
|
}
|
|
6259
7053
|
function McpArgsStep({
|
|
6260
7054
|
entry,
|
|
@@ -6262,8 +7056,8 @@ function McpArgsStep({
|
|
|
6262
7056
|
onSubmit,
|
|
6263
7057
|
onError
|
|
6264
7058
|
}) {
|
|
6265
|
-
const [value, setValue] =
|
|
6266
|
-
return /* @__PURE__ */
|
|
7059
|
+
const [value, setValue] = useState9("");
|
|
7060
|
+
return /* @__PURE__ */ React19.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column" }, /* @__PURE__ */ React19.createElement(Text15, null, entry.summary), entry.note ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, null, "Required parameter: "), /* @__PURE__ */ React19.createElement(Text15, { bold: true }, entry.userArgs)), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React19.createElement(
|
|
6267
7061
|
TextInput2,
|
|
6268
7062
|
{
|
|
6269
7063
|
value,
|
|
@@ -6279,7 +7073,7 @@ function McpArgsStep({
|
|
|
6279
7073
|
},
|
|
6280
7074
|
placeholder: placeholderFor(entry)
|
|
6281
7075
|
}
|
|
6282
|
-
)), error ? /* @__PURE__ */
|
|
7076
|
+
)), error ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { color: "red" }, error)) : null));
|
|
6283
7077
|
}
|
|
6284
7078
|
function ReviewConfirm({ onConfirm }) {
|
|
6285
7079
|
useInput6((_i, key) => {
|
|
@@ -6299,10 +7093,10 @@ function StepFrame({
|
|
|
6299
7093
|
total,
|
|
6300
7094
|
children
|
|
6301
7095
|
}) {
|
|
6302
|
-
return /* @__PURE__ */
|
|
7096
|
+
return /* @__PURE__ */ React19.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React19.createElement(Box15, null, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React19.createElement(Text15, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1, flexDirection: "column" }, children));
|
|
6303
7097
|
}
|
|
6304
7098
|
function SummaryLine({ label, value }) {
|
|
6305
|
-
return /* @__PURE__ */
|
|
7099
|
+
return /* @__PURE__ */ React19.createElement(Box15, null, /* @__PURE__ */ React19.createElement(Text15, null, label.padEnd(12)), /* @__PURE__ */ React19.createElement(Text15, { bold: true }, value));
|
|
6306
7100
|
}
|
|
6307
7101
|
function presetItems() {
|
|
6308
7102
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -6358,7 +7152,7 @@ async function setupCommand(_opts = {}) {
|
|
|
6358
7152
|
const existingKey = loadApiKey();
|
|
6359
7153
|
const existing = readConfig();
|
|
6360
7154
|
const { waitUntilExit, unmount } = render4(
|
|
6361
|
-
/* @__PURE__ */
|
|
7155
|
+
/* @__PURE__ */ React20.createElement(
|
|
6362
7156
|
Wizard,
|
|
6363
7157
|
{
|
|
6364
7158
|
existingApiKey: existingKey,
|
|
@@ -6376,9 +7170,9 @@ async function setupCommand(_opts = {}) {
|
|
|
6376
7170
|
}
|
|
6377
7171
|
|
|
6378
7172
|
// src/cli/commands/stats.ts
|
|
6379
|
-
import { existsSync as
|
|
7173
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
6380
7174
|
function statsCommand(opts) {
|
|
6381
|
-
if (!
|
|
7175
|
+
if (!existsSync4(opts.transcript)) {
|
|
6382
7176
|
console.error(`no such transcript: ${opts.transcript}`);
|
|
6383
7177
|
process.exit(1);
|
|
6384
7178
|
}
|
|
@@ -6465,12 +7259,14 @@ program.command("setup").description("Interactive wizard \u2014 API key, preset,
|
|
|
6465
7259
|
});
|
|
6466
7260
|
program.command("code [dir]").description(
|
|
6467
7261
|
"Code-editing chat \u2014 filesystem MCP auto-bridged at <dir> (default: cwd), coding system prompt, smart preset. Model proposes SEARCH/REPLACE blocks; Reasonix applies them to disk."
|
|
6468
|
-
).option("-m, --model <id>", "Override default reasoner model").option("--no-session", "Disable session persistence for this run").option("--transcript <path>", "Write a JSONL transcript to this path").action(async (dir, opts) => {
|
|
7262
|
+
).option("-m, --model <id>", "Override default reasoner model").option("--no-session", "Disable session persistence for this run").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option("-n, --new", "Skip the session picker \u2014 always wipe prior messages and start fresh").option("--transcript <path>", "Write a JSONL transcript to this path").action(async (dir, opts) => {
|
|
6469
7263
|
await codeCommand({
|
|
6470
7264
|
dir,
|
|
6471
7265
|
model: opts.model,
|
|
6472
7266
|
noSession: opts.session === false,
|
|
6473
|
-
transcript: opts.transcript
|
|
7267
|
+
transcript: opts.transcript,
|
|
7268
|
+
forceResume: !!opts.resume,
|
|
7269
|
+
forceNew: !!opts.new
|
|
6474
7270
|
});
|
|
6475
7271
|
});
|
|
6476
7272
|
program.command("chat").description("Interactive Ink TUI with live cache/cost panel.").option("-m, --model <id>", "DeepSeek model id (overrides preset)").option("-s, --system <prompt>", "System prompt (pinned in the immutable prefix)", DEFAULT_SYSTEM).option("--transcript <path>", "Write a JSONL transcript to this path").option(
|
|
@@ -6483,7 +7279,7 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
6483
7279
|
"--branch <n>",
|
|
6484
7280
|
"Self-consistency: run N parallel samples per turn and pick the most confident (disables streaming; enables harvest)",
|
|
6485
7281
|
(v) => Number.parseInt(v, 10)
|
|
6486
|
-
).option("--session <name>", "Use a named session (default: from config, usually 'default').").option("--no-session", "Disable session persistence for this run (ephemeral chat)").option(
|
|
7282
|
+
).option("--session <name>", "Use a named session (default: from config, usually 'default').").option("--no-session", "Disable session persistence for this run (ephemeral chat)").option("-r, --resume", "Skip the session picker \u2014 always continue prior messages").option("-n, --new", "Skip the session picker \u2014 always wipe prior messages and start fresh").option(
|
|
6487
7283
|
"--mcp <spec>",
|
|
6488
7284
|
'MCP server spec; repeatable. "name=cmd args...", "cmd args...", or a URL (http/https \u2192 SSE transport). Overrides config.mcp when provided.',
|
|
6489
7285
|
(value, previous = []) => [...previous, value],
|
|
@@ -6509,7 +7305,9 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
6509
7305
|
branch: defaults.branch,
|
|
6510
7306
|
session: defaults.session,
|
|
6511
7307
|
mcp: defaults.mcp,
|
|
6512
|
-
mcpPrefix: opts.mcpPrefix
|
|
7308
|
+
mcpPrefix: opts.mcpPrefix,
|
|
7309
|
+
forceResume: !!opts.resume,
|
|
7310
|
+
forceNew: !!opts.new
|
|
6513
7311
|
});
|
|
6514
7312
|
});
|
|
6515
7313
|
program.command("run <task>").description("Run a single task non-interactively, streaming output.").option("-m, --model <id>", "DeepSeek model id (overrides preset)").option("-s, --system <prompt>", "System prompt", DEFAULT_SYSTEM).option("--preset <name>", "Bundle of model + harvest + branch: fast | smart | max").option("--harvest", "Extract typed plan state from R1 reasoning (Pillar 2)").option(
|
|
@@ -6533,7 +7331,7 @@ program.command("run <task>").description("Run a single task non-interactively,
|
|
|
6533
7331
|
preset: opts.preset,
|
|
6534
7332
|
noConfig: opts.config === false
|
|
6535
7333
|
});
|
|
6536
|
-
await
|
|
7334
|
+
await runCommand2({
|
|
6537
7335
|
task,
|
|
6538
7336
|
model: defaults.model,
|
|
6539
7337
|
system: opts.system,
|