reasonix 0.4.15 → 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/dist/cli/index.js +740 -252
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +98 -2
- package/dist/index.js +248 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -44,6 +44,21 @@ function saveApiKey(key, path = defaultConfigPath()) {
|
|
|
44
44
|
cfg.apiKey = key.trim();
|
|
45
45
|
writeConfig(cfg, path);
|
|
46
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
|
+
}
|
|
47
62
|
function isPlausibleKey(key) {
|
|
48
63
|
const trimmed = key.trim();
|
|
49
64
|
return /^sk-[A-Za-z0-9_-]{16,}$/.test(trimmed);
|
|
@@ -103,8 +118,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
103
118
|
}
|
|
104
119
|
function sleep(ms, signal) {
|
|
105
120
|
if (ms <= 0) return Promise.resolve();
|
|
106
|
-
return new Promise((
|
|
107
|
-
const timer = setTimeout(
|
|
121
|
+
return new Promise((resolve6, reject) => {
|
|
122
|
+
const timer = setTimeout(resolve6, ms);
|
|
108
123
|
if (signal) {
|
|
109
124
|
const onAbort = () => {
|
|
110
125
|
clearTimeout(timer);
|
|
@@ -1544,8 +1559,8 @@ var CacheFirstLoop = class {
|
|
|
1544
1559
|
}
|
|
1545
1560
|
);
|
|
1546
1561
|
for (let k = 0; k < budget; k++) {
|
|
1547
|
-
const sample = queue.shift() ?? await new Promise((
|
|
1548
|
-
waiter =
|
|
1562
|
+
const sample = queue.shift() ?? await new Promise((resolve6) => {
|
|
1563
|
+
waiter = resolve6;
|
|
1549
1564
|
});
|
|
1550
1565
|
yield {
|
|
1551
1566
|
turn: this._turn,
|
|
@@ -2239,6 +2254,221 @@ function lineDiff(a, b) {
|
|
|
2239
2254
|
return out;
|
|
2240
2255
|
}
|
|
2241
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
|
+
|
|
2242
2472
|
// src/tools/web.ts
|
|
2243
2473
|
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
2244
2474
|
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
@@ -2422,11 +2652,11 @@ ${i + 1}. ${r.title}`);
|
|
|
2422
2652
|
|
|
2423
2653
|
// src/env.ts
|
|
2424
2654
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2425
|
-
import { resolve as
|
|
2655
|
+
import { resolve as resolve3 } from "path";
|
|
2426
2656
|
function loadDotenv(path = ".env") {
|
|
2427
2657
|
let raw;
|
|
2428
2658
|
try {
|
|
2429
|
-
raw = readFileSync3(
|
|
2659
|
+
raw = readFileSync3(resolve3(process.cwd(), path), "utf8");
|
|
2430
2660
|
} catch {
|
|
2431
2661
|
return;
|
|
2432
2662
|
}
|
|
@@ -3139,7 +3369,7 @@ var McpClient = class {
|
|
|
3139
3369
|
const id = this.nextId++;
|
|
3140
3370
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
3141
3371
|
let abortHandler = null;
|
|
3142
|
-
const promise = new Promise((
|
|
3372
|
+
const promise = new Promise((resolve6, reject) => {
|
|
3143
3373
|
const timeout = setTimeout(() => {
|
|
3144
3374
|
this.pending.delete(id);
|
|
3145
3375
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -3148,7 +3378,7 @@ var McpClient = class {
|
|
|
3148
3378
|
);
|
|
3149
3379
|
}, this.requestTimeoutMs);
|
|
3150
3380
|
this.pending.set(id, {
|
|
3151
|
-
resolve:
|
|
3381
|
+
resolve: resolve6,
|
|
3152
3382
|
reject,
|
|
3153
3383
|
timeout
|
|
3154
3384
|
});
|
|
@@ -3230,7 +3460,7 @@ var McpClient = class {
|
|
|
3230
3460
|
};
|
|
3231
3461
|
|
|
3232
3462
|
// src/mcp/stdio.ts
|
|
3233
|
-
import { spawn } from "child_process";
|
|
3463
|
+
import { spawn as spawn2 } from "child_process";
|
|
3234
3464
|
var StdioTransport = class {
|
|
3235
3465
|
child;
|
|
3236
3466
|
queue = [];
|
|
@@ -3245,14 +3475,14 @@ var StdioTransport = class {
|
|
|
3245
3475
|
opts.command,
|
|
3246
3476
|
...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
|
|
3247
3477
|
].join(" ");
|
|
3248
|
-
this.child =
|
|
3478
|
+
this.child = spawn2(line, [], {
|
|
3249
3479
|
env,
|
|
3250
3480
|
cwd: opts.cwd,
|
|
3251
3481
|
stdio: ["pipe", "pipe", "inherit"],
|
|
3252
3482
|
shell: true
|
|
3253
3483
|
});
|
|
3254
3484
|
} else {
|
|
3255
|
-
this.child =
|
|
3485
|
+
this.child = spawn2(opts.command, opts.args ?? [], {
|
|
3256
3486
|
env,
|
|
3257
3487
|
cwd: opts.cwd,
|
|
3258
3488
|
stdio: ["pipe", "pipe", "inherit"]
|
|
@@ -3271,12 +3501,12 @@ var StdioTransport = class {
|
|
|
3271
3501
|
}
|
|
3272
3502
|
async send(message) {
|
|
3273
3503
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
3274
|
-
return new Promise((
|
|
3504
|
+
return new Promise((resolve6, reject) => {
|
|
3275
3505
|
const line = `${JSON.stringify(message)}
|
|
3276
3506
|
`;
|
|
3277
3507
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
3278
3508
|
if (err) reject(err);
|
|
3279
|
-
else
|
|
3509
|
+
else resolve6();
|
|
3280
3510
|
});
|
|
3281
3511
|
});
|
|
3282
3512
|
}
|
|
@@ -3287,8 +3517,8 @@ var StdioTransport = class {
|
|
|
3287
3517
|
continue;
|
|
3288
3518
|
}
|
|
3289
3519
|
if (this.closed) return;
|
|
3290
|
-
const next = await new Promise((
|
|
3291
|
-
this.waiters.push(
|
|
3520
|
+
const next = await new Promise((resolve6) => {
|
|
3521
|
+
this.waiters.push(resolve6);
|
|
3292
3522
|
});
|
|
3293
3523
|
if (next === null) return;
|
|
3294
3524
|
yield next;
|
|
@@ -3354,8 +3584,8 @@ var SseTransport = class {
|
|
|
3354
3584
|
constructor(opts) {
|
|
3355
3585
|
this.url = opts.url;
|
|
3356
3586
|
this.headers = opts.headers ?? {};
|
|
3357
|
-
this.endpointReady = new Promise((
|
|
3358
|
-
this.resolveEndpoint =
|
|
3587
|
+
this.endpointReady = new Promise((resolve6, reject) => {
|
|
3588
|
+
this.resolveEndpoint = resolve6;
|
|
3359
3589
|
this.rejectEndpoint = reject;
|
|
3360
3590
|
});
|
|
3361
3591
|
this.endpointReady.catch(() => void 0);
|
|
@@ -3382,8 +3612,8 @@ var SseTransport = class {
|
|
|
3382
3612
|
continue;
|
|
3383
3613
|
}
|
|
3384
3614
|
if (this.closed) return;
|
|
3385
|
-
const next = await new Promise((
|
|
3386
|
-
this.waiters.push(
|
|
3615
|
+
const next = await new Promise((resolve6) => {
|
|
3616
|
+
this.waiters.push(resolve6);
|
|
3387
3617
|
});
|
|
3388
3618
|
if (next === null) return;
|
|
3389
3619
|
yield next;
|
|
@@ -3583,7 +3813,7 @@ async function trySection(load) {
|
|
|
3583
3813
|
|
|
3584
3814
|
// src/code/edit-blocks.ts
|
|
3585
3815
|
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3586
|
-
import { dirname as dirname4, resolve as
|
|
3816
|
+
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
3587
3817
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
3588
3818
|
function parseEditBlocks(text) {
|
|
3589
3819
|
const out = [];
|
|
@@ -3601,8 +3831,8 @@ function parseEditBlocks(text) {
|
|
|
3601
3831
|
return out;
|
|
3602
3832
|
}
|
|
3603
3833
|
function applyEditBlock(block, rootDir) {
|
|
3604
|
-
const absRoot =
|
|
3605
|
-
const absTarget =
|
|
3834
|
+
const absRoot = resolve4(rootDir);
|
|
3835
|
+
const absTarget = resolve4(absRoot, block.path);
|
|
3606
3836
|
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
|
|
3607
3837
|
return {
|
|
3608
3838
|
path: block.path,
|
|
@@ -3652,13 +3882,13 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
3652
3882
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
3653
3883
|
}
|
|
3654
3884
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
3655
|
-
const absRoot =
|
|
3885
|
+
const absRoot = resolve4(rootDir);
|
|
3656
3886
|
const seen = /* @__PURE__ */ new Set();
|
|
3657
3887
|
const snapshots = [];
|
|
3658
3888
|
for (const b of blocks) {
|
|
3659
3889
|
if (seen.has(b.path)) continue;
|
|
3660
3890
|
seen.add(b.path);
|
|
3661
|
-
const abs =
|
|
3891
|
+
const abs = resolve4(absRoot, b.path);
|
|
3662
3892
|
if (!existsSync2(abs)) {
|
|
3663
3893
|
snapshots.push({ path: b.path, prevContent: null });
|
|
3664
3894
|
continue;
|
|
@@ -3672,9 +3902,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
3672
3902
|
return snapshots;
|
|
3673
3903
|
}
|
|
3674
3904
|
function restoreSnapshots(snapshots, rootDir) {
|
|
3675
|
-
const absRoot =
|
|
3905
|
+
const absRoot = resolve4(rootDir);
|
|
3676
3906
|
return snapshots.map((snap) => {
|
|
3677
|
-
const abs =
|
|
3907
|
+
const abs = resolve4(absRoot, snap.path);
|
|
3678
3908
|
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
|
|
3679
3909
|
return {
|
|
3680
3910
|
path: snap.path,
|
|
@@ -3707,15 +3937,16 @@ function sep() {
|
|
|
3707
3937
|
}
|
|
3708
3938
|
|
|
3709
3939
|
// src/index.ts
|
|
3710
|
-
var VERSION = "0.4.
|
|
3940
|
+
var VERSION = "0.4.16";
|
|
3711
3941
|
|
|
3712
3942
|
// src/cli/commands/chat.tsx
|
|
3943
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
3713
3944
|
import { render } from "ink";
|
|
3714
|
-
import
|
|
3945
|
+
import React13, { useState as useState6 } from "react";
|
|
3715
3946
|
|
|
3716
3947
|
// src/cli/ui/App.tsx
|
|
3717
|
-
import { Box as
|
|
3718
|
-
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";
|
|
3719
3950
|
|
|
3720
3951
|
// src/cli/ui/EventLog.tsx
|
|
3721
3952
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -3788,7 +4019,7 @@ function stripMath(s) {
|
|
|
3788
4019
|
(_m, n, k) => `C(${n.trim()},${k.trim()})`
|
|
3789
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, " ");
|
|
3790
4021
|
}
|
|
3791
|
-
var INLINE_RE = /(\*\*([^*\n]+?)
|
|
4022
|
+
var INLINE_RE = /(\*\*([^*\n]+?)\*\*|```([^\n]+?)```|`([^`\n]+?)`|(?<![*\w])\*([^*\n]+?)\*(?!\w))/g;
|
|
3792
4023
|
function InlineMd({ text }) {
|
|
3793
4024
|
const parts = [];
|
|
3794
4025
|
let last = 0;
|
|
@@ -3803,12 +4034,17 @@ function InlineMd({ text }) {
|
|
|
3803
4034
|
/* @__PURE__ */ React2.createElement(Text2, { key: `b${idx++}`, bold: true }, m[2])
|
|
3804
4035
|
);
|
|
3805
4036
|
} else if (m[3] !== void 0) {
|
|
4037
|
+
const stripped = m[3].replace(/^(\w+)\s+/, "");
|
|
3806
4038
|
parts.push(
|
|
3807
|
-
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" },
|
|
4039
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, stripped)
|
|
3808
4040
|
);
|
|
3809
4041
|
} else if (m[4] !== void 0) {
|
|
3810
4042
|
parts.push(
|
|
3811
|
-
/* @__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])
|
|
3812
4048
|
);
|
|
3813
4049
|
}
|
|
3814
4050
|
last = start + m[0].length;
|
|
@@ -3826,6 +4062,7 @@ function parseBlocks(raw) {
|
|
|
3826
4062
|
let codeLang = "";
|
|
3827
4063
|
let codeBuf = [];
|
|
3828
4064
|
let listBuf = null;
|
|
4065
|
+
let codeFence = "";
|
|
3829
4066
|
const flushPara = () => {
|
|
3830
4067
|
if (para.length) {
|
|
3831
4068
|
out.push({ kind: "paragraph", text: para.join(" ") });
|
|
@@ -3871,22 +4108,37 @@ function parseBlocks(raw) {
|
|
|
3871
4108
|
para.push(filename);
|
|
3872
4109
|
}
|
|
3873
4110
|
}
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
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) {
|
|
3877
4135
|
out.push({ kind: "code", lang: codeLang, text: codeBuf.join("\n") });
|
|
3878
4136
|
codeBuf = [];
|
|
3879
4137
|
codeLang = "";
|
|
4138
|
+
codeFence = "";
|
|
3880
4139
|
inCode = false;
|
|
3881
|
-
|
|
3882
|
-
flushPara();
|
|
3883
|
-
flushList();
|
|
3884
|
-
inCode = true;
|
|
3885
|
-
codeLang = fence[1] ?? "";
|
|
4140
|
+
continue;
|
|
3886
4141
|
}
|
|
3887
|
-
continue;
|
|
3888
|
-
}
|
|
3889
|
-
if (inCode) {
|
|
3890
4142
|
codeBuf.push(rawLine);
|
|
3891
4143
|
continue;
|
|
3892
4144
|
}
|
|
@@ -4214,7 +4466,7 @@ function processMultilineKey(value, cursor, key) {
|
|
|
4214
4466
|
}
|
|
4215
4467
|
return { next: null, cursor: null, submit: true, submitValue: value };
|
|
4216
4468
|
}
|
|
4217
|
-
if (key.backspace) {
|
|
4469
|
+
if (key.backspace || key.delete || key.input === "\x7F" || key.input === "\b") {
|
|
4218
4470
|
if (cursor === 0) return NOOP;
|
|
4219
4471
|
return {
|
|
4220
4472
|
next: value.slice(0, cursor - 1) + value.slice(cursor),
|
|
@@ -4222,14 +4474,6 @@ function processMultilineKey(value, cursor, key) {
|
|
|
4222
4474
|
submit: false
|
|
4223
4475
|
};
|
|
4224
4476
|
}
|
|
4225
|
-
if (key.delete) {
|
|
4226
|
-
if (cursor === value.length) return NOOP;
|
|
4227
|
-
return {
|
|
4228
|
-
next: value.slice(0, cursor) + value.slice(cursor + 1),
|
|
4229
|
-
cursor,
|
|
4230
|
-
submit: false
|
|
4231
|
-
};
|
|
4232
|
-
}
|
|
4233
4477
|
if ((key.ctrl || key.meta) && key.input.length === 0) return NOOP;
|
|
4234
4478
|
if (key.ctrl || key.meta) return NOOP;
|
|
4235
4479
|
if (key.input.length > 0) {
|
|
@@ -4371,16 +4615,177 @@ function LineWithCursor({
|
|
|
4371
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));
|
|
4372
4616
|
}
|
|
4373
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
|
+
}
|
|
4778
|
+
|
|
4374
4779
|
// src/cli/ui/SlashSuggestions.tsx
|
|
4375
|
-
import { Box as
|
|
4376
|
-
import
|
|
4780
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
4781
|
+
import React8 from "react";
|
|
4377
4782
|
function SlashSuggestions({
|
|
4378
4783
|
matches,
|
|
4379
4784
|
selectedIndex
|
|
4380
4785
|
}) {
|
|
4381
4786
|
if (matches === null) return null;
|
|
4382
4787
|
if (matches.length === 0) {
|
|
4383
|
-
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"));
|
|
4384
4789
|
}
|
|
4385
4790
|
const MAX = 8;
|
|
4386
4791
|
const total = matches.length;
|
|
@@ -4388,21 +4793,21 @@ function SlashSuggestions({
|
|
|
4388
4793
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
4389
4794
|
const hiddenAbove = windowStart;
|
|
4390
4795
|
const hiddenBelow = total - windowStart - shown.length;
|
|
4391
|
-
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"));
|
|
4392
4797
|
}
|
|
4393
4798
|
function SuggestionRow({ spec, isSelected }) {
|
|
4394
4799
|
const marker = isSelected ? "\u25B8" : " ";
|
|
4395
4800
|
const name = `/${spec.cmd}`;
|
|
4396
4801
|
const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
|
|
4397
4802
|
if (isSelected) {
|
|
4398
|
-
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));
|
|
4399
4804
|
}
|
|
4400
|
-
return /* @__PURE__ */
|
|
4805
|
+
return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
|
|
4401
4806
|
}
|
|
4402
4807
|
|
|
4403
4808
|
// src/cli/ui/StatsPanel.tsx
|
|
4404
|
-
import { Box as
|
|
4405
|
-
import
|
|
4809
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
4810
|
+
import React9 from "react";
|
|
4406
4811
|
function StatsPanel({
|
|
4407
4812
|
summary,
|
|
4408
4813
|
model,
|
|
@@ -4417,7 +4822,7 @@ function StatsPanel({
|
|
|
4417
4822
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
4418
4823
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
4419
4824
|
const ctxColor = ctxRatio >= 0.8 ? "red" : ctxRatio >= 0.5 ? "yellow" : void 0;
|
|
4420
|
-
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));
|
|
4421
4826
|
}
|
|
4422
4827
|
function formatTokens(n) {
|
|
4423
4828
|
if (n < 1e3) return String(n);
|
|
@@ -4881,23 +5286,25 @@ function App({
|
|
|
4881
5286
|
codeMode
|
|
4882
5287
|
}) {
|
|
4883
5288
|
const { exit } = useApp();
|
|
4884
|
-
const [historical, setHistorical] =
|
|
4885
|
-
const [streaming, setStreaming] =
|
|
4886
|
-
const [input, setInput] =
|
|
4887
|
-
const [busy, setBusy] =
|
|
5289
|
+
const [historical, setHistorical] = useState4([]);
|
|
5290
|
+
const [streaming, setStreaming] = useState4(null);
|
|
5291
|
+
const [input, setInput] = useState4("");
|
|
5292
|
+
const [busy, setBusy] = useState4(false);
|
|
4888
5293
|
const abortedThisTurn = useRef2(false);
|
|
4889
|
-
const [ongoingTool, setOngoingTool] =
|
|
4890
|
-
const [toolProgress, setToolProgress] =
|
|
4891
|
-
const [statusLine, setStatusLine] =
|
|
4892
|
-
const [balance, setBalance] =
|
|
5294
|
+
const [ongoingTool, setOngoingTool] = useState4(null);
|
|
5295
|
+
const [toolProgress, setToolProgress] = useState4(null);
|
|
5296
|
+
const [statusLine, setStatusLine] = useState4(null);
|
|
5297
|
+
const [balance, setBalance] = useState4(null);
|
|
4893
5298
|
const lastEditSnapshots = useRef2(null);
|
|
4894
5299
|
const pendingEdits = useRef2([]);
|
|
5300
|
+
const [pendingShell, setPendingShell] = useState4(null);
|
|
5301
|
+
const [queuedSubmit, setQueuedSubmit] = useState4(null);
|
|
4895
5302
|
const promptHistory = useRef2([]);
|
|
4896
5303
|
const historyCursor = useRef2(-1);
|
|
4897
5304
|
const assistantIterCounter = useRef2(0);
|
|
4898
5305
|
const toolHistoryRef = useRef2([]);
|
|
4899
|
-
const [slashSelected, setSlashSelected] =
|
|
4900
|
-
const [summary, setSummary] =
|
|
5306
|
+
const [slashSelected, setSlashSelected] = useState4(0);
|
|
5307
|
+
const [summary, setSummary] = useState4({
|
|
4901
5308
|
turns: 0,
|
|
4902
5309
|
totalCostUsd: 0,
|
|
4903
5310
|
totalInputCostUsd: 0,
|
|
@@ -5002,7 +5409,7 @@ function App({
|
|
|
5002
5409
|
]);
|
|
5003
5410
|
}
|
|
5004
5411
|
}, [session, loop]);
|
|
5005
|
-
|
|
5412
|
+
useInput3((_input, key) => {
|
|
5006
5413
|
if (key.escape && busy) {
|
|
5007
5414
|
if (abortedThisTurn.current) return;
|
|
5008
5415
|
abortedThisTurn.current = true;
|
|
@@ -5010,6 +5417,7 @@ function App({
|
|
|
5010
5417
|
return;
|
|
5011
5418
|
}
|
|
5012
5419
|
if (busy) return;
|
|
5420
|
+
if (pendingShell) return;
|
|
5013
5421
|
if (slashMatches && slashMatches.length > 0) {
|
|
5014
5422
|
if (key.upArrow) {
|
|
5015
5423
|
setSlashSelected((i) => Math.max(0, i - 1));
|
|
@@ -5279,6 +5687,15 @@ function App({
|
|
|
5279
5687
|
toolName: ev.toolName
|
|
5280
5688
|
}
|
|
5281
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
|
+
}
|
|
5282
5699
|
} else if (ev.role === "error") {
|
|
5283
5700
|
setHistorical((prev) => [
|
|
5284
5701
|
...prev,
|
|
@@ -5323,7 +5740,68 @@ function App({
|
|
|
5323
5740
|
writeTranscript
|
|
5324
5741
|
]
|
|
5325
5742
|
);
|
|
5326
|
-
|
|
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(
|
|
5327
5805
|
StatsPanel,
|
|
5328
5806
|
{
|
|
5329
5807
|
summary,
|
|
@@ -5333,13 +5811,28 @@ function App({
|
|
|
5333
5811
|
branchBudget: loop.branchOptions.budget,
|
|
5334
5812
|
balance
|
|
5335
5813
|
}
|
|
5336
|
-
), /* @__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 }))));
|
|
5337
5830
|
}
|
|
5338
5831
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
5339
5832
|
function StatusRow({ text }) {
|
|
5340
5833
|
const tick = useTick();
|
|
5341
5834
|
const elapsed = useElapsedSeconds();
|
|
5342
|
-
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`));
|
|
5343
5836
|
}
|
|
5344
5837
|
function OngoingToolRow({
|
|
5345
5838
|
tool,
|
|
@@ -5348,7 +5841,7 @@ function OngoingToolRow({
|
|
|
5348
5841
|
const tick = useTick();
|
|
5349
5842
|
const elapsed = useElapsedSeconds();
|
|
5350
5843
|
const summary = summarizeToolArgs(tool.name, tool.args);
|
|
5351
|
-
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);
|
|
5352
5845
|
}
|
|
5353
5846
|
function renderProgressLine(p) {
|
|
5354
5847
|
const msg = p.message ? ` ${p.message}` : "";
|
|
@@ -5443,13 +5936,60 @@ function describeRepair(repair) {
|
|
|
5443
5936
|
return parts.length ? `[repair] ${parts.join(", ")}` : "";
|
|
5444
5937
|
}
|
|
5445
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
|
+
|
|
5446
5986
|
// src/cli/ui/Setup.tsx
|
|
5447
|
-
import { Box as
|
|
5987
|
+
import { Box as Box11, Text as Text11, useApp as useApp2 } from "ink";
|
|
5448
5988
|
import TextInput from "ink-text-input";
|
|
5449
|
-
import
|
|
5989
|
+
import React12, { useState as useState5 } from "react";
|
|
5450
5990
|
function Setup({ onReady }) {
|
|
5451
|
-
const [value, setValue] =
|
|
5452
|
-
const [error, setError] =
|
|
5991
|
+
const [value, setValue] = useState5("");
|
|
5992
|
+
const [error, setError] = useState5(null);
|
|
5453
5993
|
const { exit } = useApp2();
|
|
5454
5994
|
const handleSubmit = (raw) => {
|
|
5455
5995
|
const trimmed = raw.trim();
|
|
@@ -5470,7 +6010,7 @@ function Setup({ onReady }) {
|
|
|
5470
6010
|
}
|
|
5471
6011
|
onReady(trimmed);
|
|
5472
6012
|
};
|
|
5473
|
-
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(
|
|
5474
6014
|
TextInput,
|
|
5475
6015
|
{
|
|
5476
6016
|
value,
|
|
@@ -5479,14 +6019,23 @@ function Setup({ onReady }) {
|
|
|
5479
6019
|
mask: "\u2022",
|
|
5480
6020
|
placeholder: "sk-..."
|
|
5481
6021
|
}
|
|
5482
|
-
)), 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.)")));
|
|
5483
6023
|
}
|
|
5484
6024
|
|
|
5485
6025
|
// src/cli/commands/chat.tsx
|
|
5486
|
-
function Root({
|
|
5487
|
-
|
|
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);
|
|
5488
6037
|
if (!key) {
|
|
5489
|
-
return /* @__PURE__ */
|
|
6038
|
+
return /* @__PURE__ */ React13.createElement(
|
|
5490
6039
|
Setup,
|
|
5491
6040
|
{
|
|
5492
6041
|
onReady: (k) => {
|
|
@@ -5497,7 +6046,23 @@ function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appPro
|
|
|
5497
6046
|
);
|
|
5498
6047
|
}
|
|
5499
6048
|
process.env.DEEPSEEK_API_KEY = key;
|
|
5500
|
-
|
|
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(
|
|
5501
6066
|
App,
|
|
5502
6067
|
{
|
|
5503
6068
|
model: appProps.model,
|
|
@@ -5584,8 +6149,19 @@ async function chatCommand(opts) {
|
|
|
5584
6149
|
if (!tools) tools = new ToolRegistry();
|
|
5585
6150
|
registerWebTools(tools);
|
|
5586
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
|
+
}
|
|
5587
6163
|
const { waitUntilExit } = render(
|
|
5588
|
-
/* @__PURE__ */
|
|
6164
|
+
/* @__PURE__ */ React13.createElement(
|
|
5589
6165
|
Root,
|
|
5590
6166
|
{
|
|
5591
6167
|
initialKey,
|
|
@@ -5593,6 +6169,7 @@ async function chatCommand(opts) {
|
|
|
5593
6169
|
mcpSpecs,
|
|
5594
6170
|
mcpServers,
|
|
5595
6171
|
progressSink,
|
|
6172
|
+
sessionPreview,
|
|
5596
6173
|
...opts
|
|
5597
6174
|
}
|
|
5598
6175
|
),
|
|
@@ -5608,15 +6185,21 @@ async function chatCommand(opts) {
|
|
|
5608
6185
|
}
|
|
5609
6186
|
|
|
5610
6187
|
// src/cli/commands/code.tsx
|
|
5611
|
-
import { basename, resolve as
|
|
6188
|
+
import { basename, resolve as resolve5 } from "path";
|
|
5612
6189
|
async function codeCommand(opts = {}) {
|
|
5613
6190
|
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-MMANQ36Z.js");
|
|
5614
|
-
const rootDir =
|
|
6191
|
+
const rootDir = resolve5(opts.dir ?? process.cwd());
|
|
5615
6192
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
5616
6193
|
const tools = new ToolRegistry();
|
|
5617
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
|
+
});
|
|
5618
6201
|
process.stderr.write(
|
|
5619
|
-
`\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)
|
|
5620
6203
|
`
|
|
5621
6204
|
);
|
|
5622
6205
|
await chatCommand({
|
|
@@ -5627,7 +6210,9 @@ async function codeCommand(opts = {}) {
|
|
|
5627
6210
|
transcript: opts.transcript,
|
|
5628
6211
|
session,
|
|
5629
6212
|
seedTools: tools,
|
|
5630
|
-
codeMode: { rootDir }
|
|
6213
|
+
codeMode: { rootDir },
|
|
6214
|
+
forceResume: opts.forceResume,
|
|
6215
|
+
forceNew: opts.forceNew
|
|
5631
6216
|
});
|
|
5632
6217
|
}
|
|
5633
6218
|
|
|
@@ -5635,34 +6220,34 @@ async function codeCommand(opts = {}) {
|
|
|
5635
6220
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
5636
6221
|
import { basename as basename2 } from "path";
|
|
5637
6222
|
import { render as render2 } from "ink";
|
|
5638
|
-
import
|
|
6223
|
+
import React16 from "react";
|
|
5639
6224
|
|
|
5640
6225
|
// src/cli/ui/DiffApp.tsx
|
|
5641
|
-
import { Box as
|
|
5642
|
-
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";
|
|
5643
6228
|
|
|
5644
6229
|
// src/cli/ui/RecordView.tsx
|
|
5645
|
-
import { Box as
|
|
5646
|
-
import
|
|
6230
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
6231
|
+
import React14 from "react";
|
|
5647
6232
|
function RecordView({ rec, compact = false }) {
|
|
5648
6233
|
const toolArgsMax = compact ? 120 : 200;
|
|
5649
6234
|
const toolContentMax = compact ? 200 : 400;
|
|
5650
6235
|
if (rec.role === "user") {
|
|
5651
|
-
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));
|
|
5652
6237
|
}
|
|
5653
6238
|
if (rec.role === "assistant_final") {
|
|
5654
|
-
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)"));
|
|
5655
6240
|
}
|
|
5656
6241
|
if (rec.role === "tool") {
|
|
5657
|
-
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)));
|
|
5658
6243
|
}
|
|
5659
6244
|
if (rec.role === "error") {
|
|
5660
|
-
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));
|
|
5661
6246
|
}
|
|
5662
6247
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
5663
6248
|
return null;
|
|
5664
6249
|
}
|
|
5665
|
-
return /* @__PURE__ */
|
|
6250
|
+
return /* @__PURE__ */ React14.createElement(Box12, null, /* @__PURE__ */ React14.createElement(Text12, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
5666
6251
|
}
|
|
5667
6252
|
function CacheBadge({ usage }) {
|
|
5668
6253
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -5671,7 +6256,7 @@ function CacheBadge({ usage }) {
|
|
|
5671
6256
|
if (total === 0) return null;
|
|
5672
6257
|
const pct2 = hit / total * 100;
|
|
5673
6258
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
5674
|
-
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), "%"));
|
|
5675
6260
|
}
|
|
5676
6261
|
function truncate3(s, max) {
|
|
5677
6262
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -5682,8 +6267,8 @@ function DiffApp({ report }) {
|
|
|
5682
6267
|
const { exit } = useApp3();
|
|
5683
6268
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
5684
6269
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
5685
|
-
const [idx, setIdx] =
|
|
5686
|
-
|
|
6270
|
+
const [idx, setIdx] = useState7(Math.max(0, initialIdx));
|
|
6271
|
+
useInput4((input, key) => {
|
|
5687
6272
|
if (input === "q" || key.ctrl && input === "c") {
|
|
5688
6273
|
exit();
|
|
5689
6274
|
return;
|
|
@@ -5705,7 +6290,7 @@ function DiffApp({ report }) {
|
|
|
5705
6290
|
}
|
|
5706
6291
|
});
|
|
5707
6292
|
const pair = report.pairs[idx];
|
|
5708
|
-
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")));
|
|
5709
6294
|
}
|
|
5710
6295
|
function DiffHeader({ report }) {
|
|
5711
6296
|
const a = report.a;
|
|
@@ -5723,15 +6308,15 @@ function DiffHeader({ report }) {
|
|
|
5723
6308
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
5724
6309
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
5725
6310
|
}
|
|
5726
|
-
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);
|
|
5727
6312
|
}
|
|
5728
6313
|
function Pane({
|
|
5729
6314
|
label,
|
|
5730
6315
|
headerColor,
|
|
5731
6316
|
records
|
|
5732
6317
|
}) {
|
|
5733
|
-
return /* @__PURE__ */
|
|
5734
|
-
|
|
6318
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6319
|
+
Box13,
|
|
5735
6320
|
{
|
|
5736
6321
|
flexDirection: "column",
|
|
5737
6322
|
flexGrow: 1,
|
|
@@ -5739,21 +6324,21 @@ function Pane({
|
|
|
5739
6324
|
borderStyle: "single",
|
|
5740
6325
|
borderColor: headerColor
|
|
5741
6326
|
},
|
|
5742
|
-
/* @__PURE__ */
|
|
5743
|
-
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 }))
|
|
5744
6329
|
);
|
|
5745
6330
|
}
|
|
5746
6331
|
function KindBadge({ kind }) {
|
|
5747
6332
|
if (kind === "match") {
|
|
5748
|
-
return /* @__PURE__ */
|
|
6333
|
+
return /* @__PURE__ */ React15.createElement(Text13, { color: "green" }, "\u2713 match");
|
|
5749
6334
|
}
|
|
5750
6335
|
if (kind === "diverge") {
|
|
5751
|
-
return /* @__PURE__ */
|
|
6336
|
+
return /* @__PURE__ */ React15.createElement(Text13, { color: "yellow" }, "\u2605 diverge");
|
|
5752
6337
|
}
|
|
5753
6338
|
if (kind === "only_in_a") {
|
|
5754
|
-
return /* @__PURE__ */
|
|
6339
|
+
return /* @__PURE__ */ React15.createElement(Text13, { color: "blue" }, "\u2190 only in A");
|
|
5755
6340
|
}
|
|
5756
|
-
return /* @__PURE__ */
|
|
6341
|
+
return /* @__PURE__ */ React15.createElement(Text13, { color: "magenta" }, "\u2192 only in B");
|
|
5757
6342
|
}
|
|
5758
6343
|
function paneRecords(pair, side) {
|
|
5759
6344
|
if (!pair) return [];
|
|
@@ -5784,7 +6369,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
5784
6369
|
return;
|
|
5785
6370
|
}
|
|
5786
6371
|
if (wantTui) {
|
|
5787
|
-
const { waitUntilExit } = render2(
|
|
6372
|
+
const { waitUntilExit } = render2(React16.createElement(DiffApp, { report }), {
|
|
5788
6373
|
exitOnCtrlC: true,
|
|
5789
6374
|
patchConsole: false
|
|
5790
6375
|
});
|
|
@@ -5925,16 +6510,16 @@ function pad(s, width) {
|
|
|
5925
6510
|
|
|
5926
6511
|
// src/cli/commands/replay.ts
|
|
5927
6512
|
import { render as render3 } from "ink";
|
|
5928
|
-
import
|
|
6513
|
+
import React18 from "react";
|
|
5929
6514
|
|
|
5930
6515
|
// src/cli/ui/ReplayApp.tsx
|
|
5931
|
-
import { Box as
|
|
5932
|
-
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";
|
|
5933
6518
|
function ReplayApp({ meta, pages }) {
|
|
5934
6519
|
const { exit } = useApp4();
|
|
5935
6520
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
5936
|
-
const [idx, setIdx] =
|
|
5937
|
-
|
|
6521
|
+
const [idx, setIdx] = useState8(maxIdx);
|
|
6522
|
+
useInput5((input, key) => {
|
|
5938
6523
|
if (input === "q" || key.ctrl && input === "c") {
|
|
5939
6524
|
exit();
|
|
5940
6525
|
return;
|
|
@@ -5968,14 +6553,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
5968
6553
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
5969
6554
|
const currentPage = pages[idx];
|
|
5970
6555
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
5971
|
-
return /* @__PURE__ */
|
|
6556
|
+
return /* @__PURE__ */ React17.createElement(Box14, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(
|
|
5972
6557
|
StatsPanel,
|
|
5973
6558
|
{
|
|
5974
6559
|
summary,
|
|
5975
6560
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
5976
6561
|
prefixHash
|
|
5977
6562
|
}
|
|
5978
|
-
), /* @__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")));
|
|
5979
6564
|
}
|
|
5980
6565
|
|
|
5981
6566
|
// src/cli/commands/replay.ts
|
|
@@ -5987,7 +6572,7 @@ async function replayCommand(opts) {
|
|
|
5987
6572
|
}
|
|
5988
6573
|
const { parsed } = replayFromFile(opts.path);
|
|
5989
6574
|
const pages = groupRecordsByTurn(parsed.records);
|
|
5990
|
-
const { waitUntilExit } = render3(
|
|
6575
|
+
const { waitUntilExit } = render3(React18.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
5991
6576
|
exitOnCtrlC: true,
|
|
5992
6577
|
patchConsole: false
|
|
5993
6578
|
});
|
|
@@ -6114,7 +6699,7 @@ async function ensureApiKey() {
|
|
|
6114
6699
|
rl.close();
|
|
6115
6700
|
}
|
|
6116
6701
|
}
|
|
6117
|
-
async function
|
|
6702
|
+
async function runCommand2(opts) {
|
|
6118
6703
|
loadDotenv();
|
|
6119
6704
|
const apiKey = await ensureApiKey();
|
|
6120
6705
|
process.env.DEEPSEEK_API_KEY = apiKey;
|
|
@@ -6289,113 +6874,12 @@ function truncate4(s, max) {
|
|
|
6289
6874
|
|
|
6290
6875
|
// src/cli/commands/setup.tsx
|
|
6291
6876
|
import { render as render4 } from "ink";
|
|
6292
|
-
import
|
|
6877
|
+
import React20 from "react";
|
|
6293
6878
|
|
|
6294
6879
|
// src/cli/ui/Wizard.tsx
|
|
6295
|
-
import { Box as
|
|
6880
|
+
import { Box as Box15, Text as Text15, useApp as useApp5, useInput as useInput6 } from "ink";
|
|
6296
6881
|
import TextInput2 from "ink-text-input";
|
|
6297
|
-
import
|
|
6298
|
-
|
|
6299
|
-
// src/cli/ui/Select.tsx
|
|
6300
|
-
import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
|
|
6301
|
-
import React16, { useState as useState8 } from "react";
|
|
6302
|
-
function SingleSelect({
|
|
6303
|
-
items,
|
|
6304
|
-
initialValue,
|
|
6305
|
-
onSubmit,
|
|
6306
|
-
onCancel
|
|
6307
|
-
}) {
|
|
6308
|
-
const initialIndex = Math.max(
|
|
6309
|
-
0,
|
|
6310
|
-
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
6311
|
-
);
|
|
6312
|
-
const [index, setIndex] = useState8(initialIndex === -1 ? 0 : initialIndex);
|
|
6313
|
-
useInput5((_input, key) => {
|
|
6314
|
-
if (key.upArrow) {
|
|
6315
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
6316
|
-
} else if (key.downArrow) {
|
|
6317
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
6318
|
-
} else if (key.return) {
|
|
6319
|
-
const chosen = items[index];
|
|
6320
|
-
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
6321
|
-
} else if (key.escape && onCancel) {
|
|
6322
|
-
onCancel();
|
|
6323
|
-
}
|
|
6324
|
-
});
|
|
6325
|
-
return /* @__PURE__ */ React16.createElement(Box12, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React16.createElement(
|
|
6326
|
-
SelectRow,
|
|
6327
|
-
{
|
|
6328
|
-
key: item.value,
|
|
6329
|
-
item,
|
|
6330
|
-
active: i === index,
|
|
6331
|
-
marker: i === index ? "\u25B8" : " "
|
|
6332
|
-
}
|
|
6333
|
-
)));
|
|
6334
|
-
}
|
|
6335
|
-
function MultiSelect({
|
|
6336
|
-
items,
|
|
6337
|
-
initialSelected = [],
|
|
6338
|
-
onSubmit,
|
|
6339
|
-
onCancel,
|
|
6340
|
-
footer
|
|
6341
|
-
}) {
|
|
6342
|
-
const [index, setIndex] = useState8(() => {
|
|
6343
|
-
const first = items.findIndex((i) => !i.disabled);
|
|
6344
|
-
return first === -1 ? 0 : first;
|
|
6345
|
-
});
|
|
6346
|
-
const [selected, setSelected] = useState8(new Set(initialSelected));
|
|
6347
|
-
useInput5((input, key) => {
|
|
6348
|
-
if (key.upArrow) {
|
|
6349
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
6350
|
-
} else if (key.downArrow) {
|
|
6351
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
6352
|
-
} else if (input === " ") {
|
|
6353
|
-
const item = items[index];
|
|
6354
|
-
if (!item || item.disabled) return;
|
|
6355
|
-
setSelected((prev) => {
|
|
6356
|
-
const next = new Set(prev);
|
|
6357
|
-
if (next.has(item.value)) next.delete(item.value);
|
|
6358
|
-
else next.add(item.value);
|
|
6359
|
-
return next;
|
|
6360
|
-
});
|
|
6361
|
-
} else if (key.return) {
|
|
6362
|
-
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
6363
|
-
onSubmit(ordered);
|
|
6364
|
-
} else if (key.escape && onCancel) {
|
|
6365
|
-
onCancel();
|
|
6366
|
-
}
|
|
6367
|
-
});
|
|
6368
|
-
return /* @__PURE__ */ React16.createElement(Box12, { flexDirection: "column" }, items.map((item, i) => {
|
|
6369
|
-
const checked = selected.has(item.value);
|
|
6370
|
-
const marker = checked ? "[x]" : "[ ]";
|
|
6371
|
-
return /* @__PURE__ */ React16.createElement(
|
|
6372
|
-
SelectRow,
|
|
6373
|
-
{
|
|
6374
|
-
key: item.value,
|
|
6375
|
-
item,
|
|
6376
|
-
active: i === index,
|
|
6377
|
-
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
6378
|
-
}
|
|
6379
|
-
);
|
|
6380
|
-
}), footer ? /* @__PURE__ */ React16.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text12, { dimColor: true }, footer)) : null);
|
|
6381
|
-
}
|
|
6382
|
-
function SelectRow({
|
|
6383
|
-
item,
|
|
6384
|
-
active,
|
|
6385
|
-
marker
|
|
6386
|
-
}) {
|
|
6387
|
-
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
6388
|
-
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);
|
|
6389
|
-
}
|
|
6390
|
-
function findNextEnabled(items, from, step) {
|
|
6391
|
-
if (items.length === 0) return 0;
|
|
6392
|
-
let i = from;
|
|
6393
|
-
for (let tries = 0; tries < items.length; tries++) {
|
|
6394
|
-
i = (i + step + items.length) % items.length;
|
|
6395
|
-
if (!items[i]?.disabled) return i;
|
|
6396
|
-
}
|
|
6397
|
-
return from;
|
|
6398
|
-
}
|
|
6882
|
+
import React19, { useState as useState9 } from "react";
|
|
6399
6883
|
|
|
6400
6884
|
// src/cli/ui/presets.ts
|
|
6401
6885
|
var PRESETS = {
|
|
@@ -6434,7 +6918,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6434
6918
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
6435
6919
|
});
|
|
6436
6920
|
if (step === "apiKey") {
|
|
6437
|
-
return /* @__PURE__ */
|
|
6921
|
+
return /* @__PURE__ */ React19.createElement(
|
|
6438
6922
|
ApiKeyStep,
|
|
6439
6923
|
{
|
|
6440
6924
|
onSubmit: (key) => {
|
|
@@ -6448,7 +6932,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6448
6932
|
);
|
|
6449
6933
|
}
|
|
6450
6934
|
if (step === "preset") {
|
|
6451
|
-
return /* @__PURE__ */
|
|
6935
|
+
return /* @__PURE__ */ React19.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React19.createElement(
|
|
6452
6936
|
SingleSelect,
|
|
6453
6937
|
{
|
|
6454
6938
|
items: presetItems(),
|
|
@@ -6458,10 +6942,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6458
6942
|
setStep("mcp");
|
|
6459
6943
|
}
|
|
6460
6944
|
}
|
|
6461
|
-
), /* @__PURE__ */
|
|
6945
|
+
), /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
|
|
6462
6946
|
}
|
|
6463
6947
|
if (step === "mcp") {
|
|
6464
|
-
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(
|
|
6465
6949
|
MultiSelect,
|
|
6466
6950
|
{
|
|
6467
6951
|
items: mcpItems(),
|
|
@@ -6486,7 +6970,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6486
6970
|
}
|
|
6487
6971
|
const currentName = pending[0];
|
|
6488
6972
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
6489
|
-
return /* @__PURE__ */
|
|
6973
|
+
return /* @__PURE__ */ React19.createElement(
|
|
6490
6974
|
McpArgsStep,
|
|
6491
6975
|
{
|
|
6492
6976
|
entry,
|
|
@@ -6504,7 +6988,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6504
6988
|
}
|
|
6505
6989
|
if (step === "review") {
|
|
6506
6990
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
6507
|
-
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(
|
|
6508
6992
|
SummaryLine,
|
|
6509
6993
|
{
|
|
6510
6994
|
label: "MCP",
|
|
@@ -6512,8 +6996,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6512
6996
|
}
|
|
6513
6997
|
), specs.map((spec, i) => (
|
|
6514
6998
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
6515
|
-
/* @__PURE__ */
|
|
6516
|
-
)), /* @__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(
|
|
6517
7001
|
ReviewConfirm,
|
|
6518
7002
|
{
|
|
6519
7003
|
onConfirm: () => {
|
|
@@ -6539,7 +7023,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6539
7023
|
}
|
|
6540
7024
|
));
|
|
6541
7025
|
}
|
|
6542
|
-
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 }));
|
|
6543
7027
|
}
|
|
6544
7028
|
function ApiKeyStep({
|
|
6545
7029
|
onSubmit,
|
|
@@ -6547,7 +7031,7 @@ function ApiKeyStep({
|
|
|
6547
7031
|
onError
|
|
6548
7032
|
}) {
|
|
6549
7033
|
const [value, setValue] = useState9("");
|
|
6550
|
-
return /* @__PURE__ */
|
|
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(
|
|
6551
7035
|
TextInput2,
|
|
6552
7036
|
{
|
|
6553
7037
|
value,
|
|
@@ -6564,7 +7048,7 @@ function ApiKeyStep({
|
|
|
6564
7048
|
mask: "\u2022",
|
|
6565
7049
|
placeholder: "sk-..."
|
|
6566
7050
|
}
|
|
6567
|
-
)), 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);
|
|
6568
7052
|
}
|
|
6569
7053
|
function McpArgsStep({
|
|
6570
7054
|
entry,
|
|
@@ -6573,7 +7057,7 @@ function McpArgsStep({
|
|
|
6573
7057
|
onError
|
|
6574
7058
|
}) {
|
|
6575
7059
|
const [value, setValue] = useState9("");
|
|
6576
|
-
return /* @__PURE__ */
|
|
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(
|
|
6577
7061
|
TextInput2,
|
|
6578
7062
|
{
|
|
6579
7063
|
value,
|
|
@@ -6589,7 +7073,7 @@ function McpArgsStep({
|
|
|
6589
7073
|
},
|
|
6590
7074
|
placeholder: placeholderFor(entry)
|
|
6591
7075
|
}
|
|
6592
|
-
)), error ? /* @__PURE__ */
|
|
7076
|
+
)), error ? /* @__PURE__ */ React19.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React19.createElement(Text15, { color: "red" }, error)) : null));
|
|
6593
7077
|
}
|
|
6594
7078
|
function ReviewConfirm({ onConfirm }) {
|
|
6595
7079
|
useInput6((_i, key) => {
|
|
@@ -6609,10 +7093,10 @@ function StepFrame({
|
|
|
6609
7093
|
total,
|
|
6610
7094
|
children
|
|
6611
7095
|
}) {
|
|
6612
|
-
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));
|
|
6613
7097
|
}
|
|
6614
7098
|
function SummaryLine({ label, value }) {
|
|
6615
|
-
return /* @__PURE__ */
|
|
7099
|
+
return /* @__PURE__ */ React19.createElement(Box15, null, /* @__PURE__ */ React19.createElement(Text15, null, label.padEnd(12)), /* @__PURE__ */ React19.createElement(Text15, { bold: true }, value));
|
|
6616
7100
|
}
|
|
6617
7101
|
function presetItems() {
|
|
6618
7102
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -6668,7 +7152,7 @@ async function setupCommand(_opts = {}) {
|
|
|
6668
7152
|
const existingKey = loadApiKey();
|
|
6669
7153
|
const existing = readConfig();
|
|
6670
7154
|
const { waitUntilExit, unmount } = render4(
|
|
6671
|
-
/* @__PURE__ */
|
|
7155
|
+
/* @__PURE__ */ React20.createElement(
|
|
6672
7156
|
Wizard,
|
|
6673
7157
|
{
|
|
6674
7158
|
existingApiKey: existingKey,
|
|
@@ -6686,9 +7170,9 @@ async function setupCommand(_opts = {}) {
|
|
|
6686
7170
|
}
|
|
6687
7171
|
|
|
6688
7172
|
// src/cli/commands/stats.ts
|
|
6689
|
-
import { existsSync as
|
|
7173
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
6690
7174
|
function statsCommand(opts) {
|
|
6691
|
-
if (!
|
|
7175
|
+
if (!existsSync4(opts.transcript)) {
|
|
6692
7176
|
console.error(`no such transcript: ${opts.transcript}`);
|
|
6693
7177
|
process.exit(1);
|
|
6694
7178
|
}
|
|
@@ -6775,12 +7259,14 @@ program.command("setup").description("Interactive wizard \u2014 API key, preset,
|
|
|
6775
7259
|
});
|
|
6776
7260
|
program.command("code [dir]").description(
|
|
6777
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."
|
|
6778
|
-
).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) => {
|
|
6779
7263
|
await codeCommand({
|
|
6780
7264
|
dir,
|
|
6781
7265
|
model: opts.model,
|
|
6782
7266
|
noSession: opts.session === false,
|
|
6783
|
-
transcript: opts.transcript
|
|
7267
|
+
transcript: opts.transcript,
|
|
7268
|
+
forceResume: !!opts.resume,
|
|
7269
|
+
forceNew: !!opts.new
|
|
6784
7270
|
});
|
|
6785
7271
|
});
|
|
6786
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(
|
|
@@ -6793,7 +7279,7 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
6793
7279
|
"--branch <n>",
|
|
6794
7280
|
"Self-consistency: run N parallel samples per turn and pick the most confident (disables streaming; enables harvest)",
|
|
6795
7281
|
(v) => Number.parseInt(v, 10)
|
|
6796
|
-
).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(
|
|
6797
7283
|
"--mcp <spec>",
|
|
6798
7284
|
'MCP server spec; repeatable. "name=cmd args...", "cmd args...", or a URL (http/https \u2192 SSE transport). Overrides config.mcp when provided.',
|
|
6799
7285
|
(value, previous = []) => [...previous, value],
|
|
@@ -6819,7 +7305,9 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
6819
7305
|
branch: defaults.branch,
|
|
6820
7306
|
session: defaults.session,
|
|
6821
7307
|
mcp: defaults.mcp,
|
|
6822
|
-
mcpPrefix: opts.mcpPrefix
|
|
7308
|
+
mcpPrefix: opts.mcpPrefix,
|
|
7309
|
+
forceResume: !!opts.resume,
|
|
7310
|
+
forceNew: !!opts.new
|
|
6823
7311
|
});
|
|
6824
7312
|
});
|
|
6825
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(
|
|
@@ -6843,7 +7331,7 @@ program.command("run <task>").description("Run a single task non-interactively,
|
|
|
6843
7331
|
preset: opts.preset,
|
|
6844
7332
|
noConfig: opts.config === false
|
|
6845
7333
|
});
|
|
6846
|
-
await
|
|
7334
|
+
await runCommand2({
|
|
6847
7335
|
task,
|
|
6848
7336
|
model: defaults.model,
|
|
6849
7337
|
system: opts.system,
|