reasonix 0.4.17 → 0.4.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/cli/{chunk-3YQRWFES.js → chunk-HNEWBEWZ.js} +22 -1
- package/dist/cli/chunk-HNEWBEWZ.js.map +1 -0
- package/dist/cli/index.js +651 -235
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-HK5XLH55.js → prompt-JNNNJLYF.js} +2 -2
- package/dist/index.d.ts +160 -3
- package/dist/index.js +216 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-3YQRWFES.js.map +0 -1
- /package/dist/cli/{prompt-HK5XLH55.js.map → prompt-JNNNJLYF.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
applyProjectMemory,
|
|
5
5
|
memoryEnabled,
|
|
6
6
|
readProjectMemory
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HNEWBEWZ.js";
|
|
8
8
|
|
|
9
9
|
// src/cli/index.ts
|
|
10
10
|
import { Command } from "commander";
|
|
@@ -575,9 +575,25 @@ function setByPath(target, path, value) {
|
|
|
575
575
|
var ToolRegistry = class {
|
|
576
576
|
_tools = /* @__PURE__ */ new Map();
|
|
577
577
|
_autoFlatten;
|
|
578
|
+
/**
|
|
579
|
+
* When true, `dispatch` refuses any tool whose `readOnly` flag isn't
|
|
580
|
+
* set (and whose `readOnlyCheck` doesn't pass on the specific args).
|
|
581
|
+
* Drives `reasonix code`'s Plan Mode — the model can still explore
|
|
582
|
+
* via read tools but its writes and non-allowlisted shell calls are
|
|
583
|
+
* bounced until the user approves a submitted plan.
|
|
584
|
+
*/
|
|
585
|
+
_planMode = false;
|
|
578
586
|
constructor(opts = {}) {
|
|
579
587
|
this._autoFlatten = opts.autoFlatten !== false;
|
|
580
588
|
}
|
|
589
|
+
/** Enable / disable plan-mode enforcement at dispatch. */
|
|
590
|
+
setPlanMode(on) {
|
|
591
|
+
this._planMode = Boolean(on);
|
|
592
|
+
}
|
|
593
|
+
/** True when the registry is currently refusing non-readonly calls. */
|
|
594
|
+
get planMode() {
|
|
595
|
+
return this._planMode;
|
|
596
|
+
}
|
|
581
597
|
register(def) {
|
|
582
598
|
if (!def.name) throw new Error("tool requires a name");
|
|
583
599
|
const internal = { ...def };
|
|
@@ -629,16 +645,38 @@ var ToolRegistry = class {
|
|
|
629
645
|
if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
|
|
630
646
|
args = nestArguments(args);
|
|
631
647
|
}
|
|
648
|
+
if (this._planMode && !isReadOnlyCall(tool, args)) {
|
|
649
|
+
return JSON.stringify({
|
|
650
|
+
error: `${name}: unavailable in plan mode \u2014 this is a read-only exploration phase. Use read_file / list_directory / search_files / directory_tree / web_search / allowlisted shell commands to investigate. Call submit_plan with your proposed plan when you're ready for the user's review.`
|
|
651
|
+
});
|
|
652
|
+
}
|
|
632
653
|
try {
|
|
633
654
|
const result = await tool.fn(args, { signal: opts.signal });
|
|
634
655
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
635
656
|
} catch (err) {
|
|
657
|
+
const e = err;
|
|
658
|
+
if (typeof e.toToolResult === "function") {
|
|
659
|
+
try {
|
|
660
|
+
return JSON.stringify(e.toToolResult());
|
|
661
|
+
} catch {
|
|
662
|
+
}
|
|
663
|
+
}
|
|
636
664
|
return JSON.stringify({
|
|
637
|
-
error: `${
|
|
665
|
+
error: `${e.name}: ${e.message}`
|
|
638
666
|
});
|
|
639
667
|
}
|
|
640
668
|
}
|
|
641
669
|
};
|
|
670
|
+
function isReadOnlyCall(tool, args) {
|
|
671
|
+
if (tool.readOnlyCheck) {
|
|
672
|
+
try {
|
|
673
|
+
return Boolean(tool.readOnlyCheck(args));
|
|
674
|
+
} catch {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return tool.readOnly === true;
|
|
679
|
+
}
|
|
642
680
|
function hasDotKey(obj) {
|
|
643
681
|
for (const k of Object.keys(obj)) {
|
|
644
682
|
if (k.includes(".")) return true;
|
|
@@ -1025,6 +1063,16 @@ var ToolCallRepair = class {
|
|
|
1025
1063
|
this.opts = opts;
|
|
1026
1064
|
this.storm = new StormBreaker(opts.stormWindow ?? 6, opts.stormThreshold ?? 3);
|
|
1027
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Drop the StormBreaker's sliding window of recent (name, args)
|
|
1068
|
+
* signatures. Called at the start of every user turn — a fresh user
|
|
1069
|
+
* message is a new intent, so carrying old repetition state into it
|
|
1070
|
+
* would turn a valid "try again with different input" flow into a
|
|
1071
|
+
* false-positive block.
|
|
1072
|
+
*/
|
|
1073
|
+
resetStorm() {
|
|
1074
|
+
this.storm.reset();
|
|
1075
|
+
}
|
|
1028
1076
|
process(declaredCalls, reasoningContent, content = null) {
|
|
1029
1077
|
const report = {
|
|
1030
1078
|
scavenged: 0,
|
|
@@ -1477,6 +1525,7 @@ var CacheFirstLoop = class {
|
|
|
1477
1525
|
async *step(userInput) {
|
|
1478
1526
|
this._turn++;
|
|
1479
1527
|
this.scratch.reset();
|
|
1528
|
+
this.repair.resetStorm();
|
|
1480
1529
|
this._turnAbort = new AbortController();
|
|
1481
1530
|
const signal = this._turnAbort.signal;
|
|
1482
1531
|
let pendingUser = userInput;
|
|
@@ -1700,6 +1749,16 @@ var CacheFirstLoop = class {
|
|
|
1700
1749
|
repair: report,
|
|
1701
1750
|
branch: branchSummary
|
|
1702
1751
|
};
|
|
1752
|
+
if (report.stormsBroken > 0) {
|
|
1753
|
+
const noteTail = report.notes.length ? ` \u2014 ${report.notes[report.notes.length - 1]}` : "";
|
|
1754
|
+
const allSuppressed = repairedCalls.length === 0 && toolCalls.length > 0;
|
|
1755
|
+
const phrase = allSuppressed ? `stopped the model from calling the same tool with identical args repeatedly (all ${toolCalls.length} call(s) this turn were already in the recent-repeat window). Likely a stuck retry \u2014 reword your instruction, rule out the underlying blocker, or try /retry after fixing it` : `suppressed ${report.stormsBroken} repeat tool call(s) that had fired 3+ times with identical args in a sliding window`;
|
|
1756
|
+
yield {
|
|
1757
|
+
turn: this._turn,
|
|
1758
|
+
role: "warning",
|
|
1759
|
+
content: `${phrase}${noteTail}`
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1703
1762
|
if (repairedCalls.length === 0) {
|
|
1704
1763
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
1705
1764
|
return;
|
|
@@ -1949,6 +2008,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
1949
2008
|
registry.register({
|
|
1950
2009
|
name: "read_file",
|
|
1951
2010
|
description: "Read a file under the sandbox root. Returns the full contents (truncated with a notice if larger than the per-call cap). Paths may be relative to the root or absolute-under-root.",
|
|
2011
|
+
readOnly: true,
|
|
1952
2012
|
parameters: {
|
|
1953
2013
|
type: "object",
|
|
1954
2014
|
properties: {
|
|
@@ -1986,6 +2046,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
1986
2046
|
registry.register({
|
|
1987
2047
|
name: "list_directory",
|
|
1988
2048
|
description: "List entries in a directory under the sandbox root. Returns one line per entry, marking directories with a trailing slash. Not recursive \u2014 use directory_tree for that.",
|
|
2049
|
+
readOnly: true,
|
|
1989
2050
|
parameters: {
|
|
1990
2051
|
type: "object",
|
|
1991
2052
|
properties: {
|
|
@@ -2005,6 +2066,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2005
2066
|
registry.register({
|
|
2006
2067
|
name: "directory_tree",
|
|
2007
2068
|
description: "Recursively list entries in a directory. Shows indented tree structure with directories marked '/'. Caps output so a huge tree doesn't drown the context.",
|
|
2069
|
+
readOnly: true,
|
|
2008
2070
|
parameters: {
|
|
2009
2071
|
type: "object",
|
|
2010
2072
|
properties: {
|
|
@@ -2051,6 +2113,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2051
2113
|
registry.register({
|
|
2052
2114
|
name: "search_files",
|
|
2053
2115
|
description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line.",
|
|
2116
|
+
readOnly: true,
|
|
2054
2117
|
parameters: {
|
|
2055
2118
|
type: "object",
|
|
2056
2119
|
properties: {
|
|
@@ -2103,6 +2166,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2103
2166
|
registry.register({
|
|
2104
2167
|
name: "get_file_info",
|
|
2105
2168
|
description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
|
|
2169
|
+
readOnly: true,
|
|
2106
2170
|
parameters: {
|
|
2107
2171
|
type: "object",
|
|
2108
2172
|
properties: {
|
|
@@ -2259,8 +2323,54 @@ function lineDiff(a, b) {
|
|
|
2259
2323
|
return out;
|
|
2260
2324
|
}
|
|
2261
2325
|
|
|
2326
|
+
// src/tools/plan.ts
|
|
2327
|
+
var PlanProposedError = class extends Error {
|
|
2328
|
+
plan;
|
|
2329
|
+
constructor(plan) {
|
|
2330
|
+
super(
|
|
2331
|
+
"PlanProposedError: plan submitted. STOP calling tools now \u2014 the TUI has shown the plan to the user. Wait for their next message; it will either approve (you'll then implement the plan), request a refinement (you should explore more and submit an updated plan), or cancel (drop the plan and ask what they want instead). Don't call any tools in the meantime."
|
|
2332
|
+
);
|
|
2333
|
+
this.name = "PlanProposedError";
|
|
2334
|
+
this.plan = plan;
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Structured tool-result shape. Consumed by the TUI to extract the
|
|
2338
|
+
* plan without regex-scraping the error message.
|
|
2339
|
+
*/
|
|
2340
|
+
toToolResult() {
|
|
2341
|
+
return { error: `${this.name}: ${this.message}`, plan: this.plan };
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
function registerPlanTool(registry, opts = {}) {
|
|
2345
|
+
registry.register({
|
|
2346
|
+
name: "submit_plan",
|
|
2347
|
+
description: "Submit a concrete plan to the user for review before executing. Use this for tasks that warrant a review gate \u2014 multi-file refactors, architecture changes, anything that would be expensive or confusing to undo. Skip it for small fixes (one-line typo, obvious bug with a clear fix) \u2014 just make the change. The user will either approve (you then implement it), ask for refinement, or cancel. If the user has already enabled /plan mode, writes are blocked at dispatch and you MUST use this. Write the plan as markdown with a one-line summary, a bulleted list of files to touch and what will change, and any risks or open questions.",
|
|
2348
|
+
readOnly: true,
|
|
2349
|
+
parameters: {
|
|
2350
|
+
type: "object",
|
|
2351
|
+
properties: {
|
|
2352
|
+
plan: {
|
|
2353
|
+
type: "string",
|
|
2354
|
+
description: "Markdown-formatted plan. Lead with a one-sentence summary. Then a file-by-file breakdown of what you'll change and why. Flag any risks or open questions at the end so the user can weigh in before you start."
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
required: ["plan"]
|
|
2358
|
+
},
|
|
2359
|
+
fn: async (args) => {
|
|
2360
|
+
const plan = (args?.plan ?? "").trim();
|
|
2361
|
+
if (!plan) {
|
|
2362
|
+
throw new Error("submit_plan: empty plan \u2014 write a markdown plan and try again.");
|
|
2363
|
+
}
|
|
2364
|
+
opts.onPlanSubmitted?.(plan);
|
|
2365
|
+
throw new PlanProposedError(plan);
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2368
|
+
return registry;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2262
2371
|
// src/tools/shell.ts
|
|
2263
2372
|
import { spawn } from "child_process";
|
|
2373
|
+
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
2264
2374
|
import * as pathMod2 from "path";
|
|
2265
2375
|
var DEFAULT_TIMEOUT_SEC = 60;
|
|
2266
2376
|
var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
|
|
@@ -2378,10 +2488,12 @@ async function runCommand(cmd, opts) {
|
|
|
2378
2488
|
windowsHide: true,
|
|
2379
2489
|
env: process.env
|
|
2380
2490
|
};
|
|
2491
|
+
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
2492
|
+
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
2381
2493
|
return await new Promise((resolve6, reject) => {
|
|
2382
2494
|
let child;
|
|
2383
2495
|
try {
|
|
2384
|
-
child = spawn(
|
|
2496
|
+
child = spawn(bin, args, effectiveSpawnOpts);
|
|
2385
2497
|
} catch (err) {
|
|
2386
2498
|
reject(err);
|
|
2387
2499
|
return;
|
|
@@ -2415,6 +2527,59 @@ async function runCommand(cmd, opts) {
|
|
|
2415
2527
|
});
|
|
2416
2528
|
});
|
|
2417
2529
|
}
|
|
2530
|
+
function resolveExecutable(cmd, opts = {}) {
|
|
2531
|
+
const platform = opts.platform ?? process.platform;
|
|
2532
|
+
if (platform !== "win32") return cmd;
|
|
2533
|
+
if (!cmd) return cmd;
|
|
2534
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod2.isAbsolute(cmd)) return cmd;
|
|
2535
|
+
if (pathMod2.extname(cmd)) return cmd;
|
|
2536
|
+
const env = opts.env ?? process.env;
|
|
2537
|
+
const pathExt = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
2538
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod2.delimiter);
|
|
2539
|
+
const pathDirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
2540
|
+
const isFile = opts.isFile ?? defaultIsFile;
|
|
2541
|
+
for (const dir of pathDirs) {
|
|
2542
|
+
for (const ext of pathExt) {
|
|
2543
|
+
const full = pathMod2.join(dir, cmd + ext);
|
|
2544
|
+
if (isFile(full)) return full;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
return cmd;
|
|
2548
|
+
}
|
|
2549
|
+
function defaultIsFile(full) {
|
|
2550
|
+
try {
|
|
2551
|
+
return existsSync2(full) && statSync2(full).isFile();
|
|
2552
|
+
} catch {
|
|
2553
|
+
return false;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
function prepareSpawn(argv, opts = {}) {
|
|
2557
|
+
const head = argv[0] ?? "";
|
|
2558
|
+
const tail = argv.slice(1);
|
|
2559
|
+
const platform = opts.platform ?? process.platform;
|
|
2560
|
+
const resolved = resolveExecutable(head, opts);
|
|
2561
|
+
if (platform !== "win32") {
|
|
2562
|
+
return { bin: resolved, args: [...tail], spawnOverrides: {} };
|
|
2563
|
+
}
|
|
2564
|
+
if (/\.(cmd|bat)$/i.test(resolved)) {
|
|
2565
|
+
const cmdline = [resolved, ...tail].map(quoteForCmdExe).join(" ");
|
|
2566
|
+
return {
|
|
2567
|
+
bin: "cmd.exe",
|
|
2568
|
+
args: ["/d", "/s", "/c", cmdline],
|
|
2569
|
+
// windowsVerbatimArguments prevents Node from re-quoting the /c
|
|
2570
|
+
// payload — we've already composed an exact cmd.exe command
|
|
2571
|
+
// line. Without this Node wraps our already-quoted string in
|
|
2572
|
+
// another round of quotes and cmd.exe can't parse it.
|
|
2573
|
+
spawnOverrides: { windowsVerbatimArguments: true }
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
return { bin: resolved, args: [...tail], spawnOverrides: {} };
|
|
2577
|
+
}
|
|
2578
|
+
function quoteForCmdExe(arg) {
|
|
2579
|
+
if (arg === "") return '""';
|
|
2580
|
+
if (!/[\s"&|<>^%(),;!]/.test(arg)) return arg;
|
|
2581
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
2582
|
+
}
|
|
2418
2583
|
var NeedsConfirmationError = class extends Error {
|
|
2419
2584
|
command;
|
|
2420
2585
|
constructor(command) {
|
|
@@ -2434,6 +2599,16 @@ function registerShellTools(registry, opts) {
|
|
|
2434
2599
|
registry.register({
|
|
2435
2600
|
name: "run_command",
|
|
2436
2601
|
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.",
|
|
2602
|
+
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
2603
|
+
// cargo check, ls, grep …) so the model can actually investigate
|
|
2604
|
+
// during planning. Anything that would otherwise trigger a
|
|
2605
|
+
// confirmation prompt is treated as "not read-only" and bounced.
|
|
2606
|
+
readOnlyCheck: (args) => {
|
|
2607
|
+
if (allowAll) return true;
|
|
2608
|
+
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
2609
|
+
if (!cmd) return false;
|
|
2610
|
+
return isAllowed(cmd, extraAllowed);
|
|
2611
|
+
},
|
|
2437
2612
|
parameters: {
|
|
2438
2613
|
type: "object",
|
|
2439
2614
|
properties: {
|
|
@@ -2600,6 +2775,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
2600
2775
|
registry.register({
|
|
2601
2776
|
name: "web_search",
|
|
2602
2777
|
description: "Search the public web. Returns ranked results with title, url, and snippet. Use this when the question needs information more current than your training data, when you're unsure of a factual detail, or when the user asks about a specific webpage/library/release you haven't seen.",
|
|
2778
|
+
readOnly: true,
|
|
2603
2779
|
parameters: {
|
|
2604
2780
|
type: "object",
|
|
2605
2781
|
properties: {
|
|
@@ -2622,6 +2798,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
2622
2798
|
registry.register({
|
|
2623
2799
|
name: "web_fetch",
|
|
2624
2800
|
description: "Download a URL and return its visible text content (HTML pages get scripts/styles/nav stripped). Truncated at the tool-result cap. Use after web_search when a snippet isn't enough.",
|
|
2801
|
+
readOnly: true,
|
|
2625
2802
|
parameters: {
|
|
2626
2803
|
type: "object",
|
|
2627
2804
|
properties: {
|
|
@@ -3817,7 +3994,7 @@ async function trySection(load) {
|
|
|
3817
3994
|
}
|
|
3818
3995
|
|
|
3819
3996
|
// src/code/edit-blocks.ts
|
|
3820
|
-
import { existsSync as
|
|
3997
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3821
3998
|
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
3822
3999
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
3823
4000
|
function parseEditBlocks(text) {
|
|
@@ -3846,7 +4023,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
3846
4023
|
};
|
|
3847
4024
|
}
|
|
3848
4025
|
const searchEmpty = block.search.length === 0;
|
|
3849
|
-
const exists =
|
|
4026
|
+
const exists = existsSync3(absTarget);
|
|
3850
4027
|
try {
|
|
3851
4028
|
if (!exists) {
|
|
3852
4029
|
if (!searchEmpty) {
|
|
@@ -3894,7 +4071,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
3894
4071
|
if (seen.has(b.path)) continue;
|
|
3895
4072
|
seen.add(b.path);
|
|
3896
4073
|
const abs = resolve4(absRoot, b.path);
|
|
3897
|
-
if (!
|
|
4074
|
+
if (!existsSync3(abs)) {
|
|
3898
4075
|
snapshots.push({ path: b.path, prevContent: null });
|
|
3899
4076
|
continue;
|
|
3900
4077
|
}
|
|
@@ -3919,7 +4096,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
3919
4096
|
}
|
|
3920
4097
|
try {
|
|
3921
4098
|
if (snap.prevContent === null) {
|
|
3922
|
-
if (
|
|
4099
|
+
if (existsSync3(abs)) unlinkSync2(abs);
|
|
3923
4100
|
return {
|
|
3924
4101
|
path: snap.path,
|
|
3925
4102
|
status: "applied",
|
|
@@ -3942,16 +4119,16 @@ function sep() {
|
|
|
3942
4119
|
}
|
|
3943
4120
|
|
|
3944
4121
|
// src/index.ts
|
|
3945
|
-
var VERSION = "0.4.
|
|
4122
|
+
var VERSION = "0.4.19";
|
|
3946
4123
|
|
|
3947
4124
|
// src/cli/commands/chat.tsx
|
|
3948
|
-
import { existsSync as
|
|
4125
|
+
import { existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
3949
4126
|
import { render } from "ink";
|
|
3950
|
-
import
|
|
4127
|
+
import React15, { useState as useState7 } from "react";
|
|
3951
4128
|
|
|
3952
4129
|
// src/cli/ui/App.tsx
|
|
3953
|
-
import { Box as
|
|
3954
|
-
import
|
|
4130
|
+
import { Box as Box11, Static, Text as Text11, useApp, useInput as useInput4 } from "ink";
|
|
4131
|
+
import React12, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
|
|
3955
4132
|
|
|
3956
4133
|
// src/cli/ui/EventLog.tsx
|
|
3957
4134
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -4425,9 +4602,177 @@ function truncate2(s, max) {
|
|
|
4425
4602
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
4426
4603
|
}
|
|
4427
4604
|
|
|
4428
|
-
// src/cli/ui/
|
|
4605
|
+
// src/cli/ui/PlanConfirm.tsx
|
|
4606
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
4607
|
+
import React6 from "react";
|
|
4608
|
+
|
|
4609
|
+
// src/cli/ui/Select.tsx
|
|
4429
4610
|
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
4430
|
-
import React5, {
|
|
4611
|
+
import React5, { useState as useState2 } from "react";
|
|
4612
|
+
function SingleSelect({
|
|
4613
|
+
items,
|
|
4614
|
+
initialValue,
|
|
4615
|
+
onSubmit,
|
|
4616
|
+
onCancel
|
|
4617
|
+
}) {
|
|
4618
|
+
const initialIndex = Math.max(
|
|
4619
|
+
0,
|
|
4620
|
+
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
4621
|
+
);
|
|
4622
|
+
const [index, setIndex] = useState2(initialIndex === -1 ? 0 : initialIndex);
|
|
4623
|
+
useInput((_input, key) => {
|
|
4624
|
+
if (key.upArrow) {
|
|
4625
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4626
|
+
} else if (key.downArrow) {
|
|
4627
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4628
|
+
} else if (key.return) {
|
|
4629
|
+
const chosen = items[index];
|
|
4630
|
+
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
4631
|
+
} else if (key.escape && onCancel) {
|
|
4632
|
+
onCancel();
|
|
4633
|
+
}
|
|
4634
|
+
});
|
|
4635
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React5.createElement(
|
|
4636
|
+
SelectRow,
|
|
4637
|
+
{
|
|
4638
|
+
key: item.value,
|
|
4639
|
+
item,
|
|
4640
|
+
active: i === index,
|
|
4641
|
+
marker: i === index ? "\u25B8" : " "
|
|
4642
|
+
}
|
|
4643
|
+
)));
|
|
4644
|
+
}
|
|
4645
|
+
function MultiSelect({
|
|
4646
|
+
items,
|
|
4647
|
+
initialSelected = [],
|
|
4648
|
+
onSubmit,
|
|
4649
|
+
onCancel,
|
|
4650
|
+
footer
|
|
4651
|
+
}) {
|
|
4652
|
+
const [index, setIndex] = useState2(() => {
|
|
4653
|
+
const first = items.findIndex((i) => !i.disabled);
|
|
4654
|
+
return first === -1 ? 0 : first;
|
|
4655
|
+
});
|
|
4656
|
+
const [selected, setSelected] = useState2(new Set(initialSelected));
|
|
4657
|
+
useInput((input, key) => {
|
|
4658
|
+
if (key.upArrow) {
|
|
4659
|
+
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4660
|
+
} else if (key.downArrow) {
|
|
4661
|
+
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4662
|
+
} else if (input === " ") {
|
|
4663
|
+
const item = items[index];
|
|
4664
|
+
if (!item || item.disabled) return;
|
|
4665
|
+
setSelected((prev) => {
|
|
4666
|
+
const next = new Set(prev);
|
|
4667
|
+
if (next.has(item.value)) next.delete(item.value);
|
|
4668
|
+
else next.add(item.value);
|
|
4669
|
+
return next;
|
|
4670
|
+
});
|
|
4671
|
+
} else if (key.return) {
|
|
4672
|
+
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
4673
|
+
onSubmit(ordered);
|
|
4674
|
+
} else if (key.escape && onCancel) {
|
|
4675
|
+
onCancel();
|
|
4676
|
+
}
|
|
4677
|
+
});
|
|
4678
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, items.map((item, i) => {
|
|
4679
|
+
const checked = selected.has(item.value);
|
|
4680
|
+
const marker = checked ? "[x]" : "[ ]";
|
|
4681
|
+
return /* @__PURE__ */ React5.createElement(
|
|
4682
|
+
SelectRow,
|
|
4683
|
+
{
|
|
4684
|
+
key: item.value,
|
|
4685
|
+
item,
|
|
4686
|
+
active: i === index,
|
|
4687
|
+
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
4688
|
+
}
|
|
4689
|
+
);
|
|
4690
|
+
}), footer ? /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, footer)) : null);
|
|
4691
|
+
}
|
|
4692
|
+
function SelectRow({
|
|
4693
|
+
item,
|
|
4694
|
+
active,
|
|
4695
|
+
marker
|
|
4696
|
+
}) {
|
|
4697
|
+
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
4698
|
+
return /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Text4, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React5.createElement(Box4, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, item.hint)) : null);
|
|
4699
|
+
}
|
|
4700
|
+
function findNextEnabled(items, from, step) {
|
|
4701
|
+
if (items.length === 0) return 0;
|
|
4702
|
+
let i = from;
|
|
4703
|
+
for (let tries = 0; tries < items.length; tries++) {
|
|
4704
|
+
i = (i + step + items.length) % items.length;
|
|
4705
|
+
if (!items[i]?.disabled) return i;
|
|
4706
|
+
}
|
|
4707
|
+
return from;
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
// src/cli/ui/PlanConfirm.tsx
|
|
4711
|
+
var DEFAULT_MAX_RENDERED = 2400;
|
|
4712
|
+
function PlanConfirm({ plan, onChoose, maxRenderedChars }) {
|
|
4713
|
+
const cap = maxRenderedChars ?? DEFAULT_MAX_RENDERED;
|
|
4714
|
+
const tooLong = plan.length > cap;
|
|
4715
|
+
const visible = tooLong ? `${plan.slice(0, cap)}
|
|
4716
|
+
|
|
4717
|
+
\u2026 (${plan.length - cap} chars truncated \u2014 use /tool to view the full proposal)` : plan;
|
|
4718
|
+
const hasOpenQuestions = /^#{1,6}\s*(open[-\s]?questions?|risks?|unknowns?|assumptions?|unclear)/im.test(plan) || /^#{1,6}\s*(待确认|开放问题|风险|未知|假设|不确定)/im.test(plan);
|
|
4719
|
+
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: "cyan" }, "\u25B8 plan submitted \u2014 awaiting your review")), /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Markdown, { text: visible })), hasOpenQuestions ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: "yellow" }, "\u25B2 the plan has open questions or flagged risks \u2014 pick", " ", /* @__PURE__ */ React6.createElement(Text5, { bold: true }, "Refine / answer questions"), " to write concrete answers before the model moves on.")) : null, /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(
|
|
4720
|
+
SingleSelect,
|
|
4721
|
+
{
|
|
4722
|
+
initialValue: hasOpenQuestions ? "refine" : "approve",
|
|
4723
|
+
items: [
|
|
4724
|
+
{
|
|
4725
|
+
value: "approve",
|
|
4726
|
+
label: "Approve and implement",
|
|
4727
|
+
hint: "Exit plan mode. The model starts executing. You'll get a text input to add any last instructions (or just press Enter to skip)."
|
|
4728
|
+
},
|
|
4729
|
+
{
|
|
4730
|
+
value: "refine",
|
|
4731
|
+
label: "Refine / answer questions",
|
|
4732
|
+
hint: "Stay in plan mode. Write answers, modifications, or critiques; the model revises and re-submits."
|
|
4733
|
+
},
|
|
4734
|
+
{
|
|
4735
|
+
value: "cancel",
|
|
4736
|
+
label: "Cancel",
|
|
4737
|
+
hint: "Exit plan mode. Drop the plan; the model won't implement it."
|
|
4738
|
+
}
|
|
4739
|
+
],
|
|
4740
|
+
onSubmit: (v) => onChoose(v)
|
|
4741
|
+
}
|
|
4742
|
+
)));
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4745
|
+
// src/cli/ui/PlanRefineInput.tsx
|
|
4746
|
+
import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
|
|
4747
|
+
import React7, { useState as useState3 } from "react";
|
|
4748
|
+
function PlanRefineInput({ mode, onSubmit, onCancel }) {
|
|
4749
|
+
const [value, setValue] = useState3("");
|
|
4750
|
+
useInput2((input, key) => {
|
|
4751
|
+
if (key.escape) {
|
|
4752
|
+
onCancel();
|
|
4753
|
+
return;
|
|
4754
|
+
}
|
|
4755
|
+
if (key.return) {
|
|
4756
|
+
onSubmit(value.trim());
|
|
4757
|
+
return;
|
|
4758
|
+
}
|
|
4759
|
+
if (key.backspace || key.delete) {
|
|
4760
|
+
setValue((v) => v.slice(0, -1));
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
if (input && !key.ctrl && !key.meta) {
|
|
4764
|
+
setValue((v) => v + input);
|
|
4765
|
+
}
|
|
4766
|
+
});
|
|
4767
|
+
const title = mode === "approve" ? "\u25B8 approving \u2014 any last instructions or answers to open questions?" : "\u25B8 refining \u2014 what should the model change?";
|
|
4768
|
+
const hint = mode === "approve" ? "Answer questions the plan raised, add constraints, or just press Enter to approve as-is." : "Describe what's wrong or missing, or answer questions the plan raised.";
|
|
4769
|
+
const blankHint = mode === "approve" ? " (Enter with blank = approve without extra instructions.)" : " (Enter with blank = ask the model to list concrete questions.)";
|
|
4770
|
+
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" }, title)), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, { dimColor: true }, hint, " Enter to send \xB7 Esc to return to the picker.", value === "" ? blankHint : "")), /* @__PURE__ */ React7.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text6, null, /* @__PURE__ */ React7.createElement(Text6, { color: "yellow" }, "\u203A "), /* @__PURE__ */ React7.createElement(Text6, null, value || " "), /* @__PURE__ */ React7.createElement(Text6, { color: "yellow" }, "\u258D"))));
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
// src/cli/ui/PromptInput.tsx
|
|
4774
|
+
import { Box as Box7, Text as Text7, useInput as useInput3 } from "ink";
|
|
4775
|
+
import React8, { useRef, useState as useState4 } from "react";
|
|
4431
4776
|
|
|
4432
4777
|
// src/cli/ui/multiline-keys.ts
|
|
4433
4778
|
var BACKSLASH_SUFFIX = /\\$/;
|
|
@@ -4542,7 +4887,7 @@ function PromptInput({
|
|
|
4542
4887
|
disabled,
|
|
4543
4888
|
placeholder
|
|
4544
4889
|
}) {
|
|
4545
|
-
const [cursor, setCursor] =
|
|
4890
|
+
const [cursor, setCursor] = useState4(value.length);
|
|
4546
4891
|
const lastLocalValueRef = useRef(value);
|
|
4547
4892
|
if (value !== lastLocalValueRef.current) {
|
|
4548
4893
|
lastLocalValueRef.current = value;
|
|
@@ -4552,7 +4897,7 @@ function PromptInput({
|
|
|
4552
4897
|
}
|
|
4553
4898
|
const tick = useTick();
|
|
4554
4899
|
const showCursor = disabled ? false : Math.floor(tick / 4) % 2 === 0;
|
|
4555
|
-
|
|
4900
|
+
useInput3(
|
|
4556
4901
|
(input, key) => {
|
|
4557
4902
|
const ke = {
|
|
4558
4903
|
input,
|
|
@@ -4587,13 +4932,13 @@ function PromptInput({
|
|
|
4587
4932
|
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
4588
4933
|
const borderColor = disabled ? "gray" : "cyan";
|
|
4589
4934
|
const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
|
|
4590
|
-
return /* @__PURE__ */
|
|
4935
|
+
return /* @__PURE__ */ React8.createElement(Box7, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
4591
4936
|
const isFirst = i === 0;
|
|
4592
4937
|
const showPlaceholder = isFirst && value.length === 0;
|
|
4593
4938
|
const isCursorLine = i === cursorLine;
|
|
4594
4939
|
return (
|
|
4595
4940
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
4596
|
-
/* @__PURE__ */
|
|
4941
|
+
/* @__PURE__ */ React8.createElement(Box7, { key: i }, isFirst ? /* @__PURE__ */ React8.createElement(Text7, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, " "), showPlaceholder ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, isCursorLine && !disabled ? /* @__PURE__ */ React8.createElement(Text7, { color: borderColor }, showCursor ? "\u258C" : " ") : null, /* @__PURE__ */ React8.createElement(Text7, { dimColor: true }, effectivePlaceholder)) : isCursorLine && !disabled ? /* @__PURE__ */ React8.createElement(
|
|
4597
4942
|
LineWithCursor,
|
|
4598
4943
|
{
|
|
4599
4944
|
line,
|
|
@@ -4601,7 +4946,7 @@ function PromptInput({
|
|
|
4601
4946
|
showCursor,
|
|
4602
4947
|
borderColor
|
|
4603
4948
|
}
|
|
4604
|
-
) : /* @__PURE__ */
|
|
4949
|
+
) : /* @__PURE__ */ React8.createElement(Text7, null, line))
|
|
4605
4950
|
);
|
|
4606
4951
|
}));
|
|
4607
4952
|
}
|
|
@@ -4615,119 +4960,16 @@ function LineWithCursor({
|
|
|
4615
4960
|
const atCursor = line.slice(col, col + 1);
|
|
4616
4961
|
const after = line.slice(col + 1);
|
|
4617
4962
|
if (atCursor.length === 0) {
|
|
4618
|
-
return /* @__PURE__ */
|
|
4619
|
-
}
|
|
4620
|
-
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));
|
|
4621
|
-
}
|
|
4622
|
-
|
|
4623
|
-
// src/cli/ui/ShellConfirm.tsx
|
|
4624
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
4625
|
-
import React7 from "react";
|
|
4626
|
-
|
|
4627
|
-
// src/cli/ui/Select.tsx
|
|
4628
|
-
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
4629
|
-
import React6, { useState as useState3 } from "react";
|
|
4630
|
-
function SingleSelect({
|
|
4631
|
-
items,
|
|
4632
|
-
initialValue,
|
|
4633
|
-
onSubmit,
|
|
4634
|
-
onCancel
|
|
4635
|
-
}) {
|
|
4636
|
-
const initialIndex = Math.max(
|
|
4637
|
-
0,
|
|
4638
|
-
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
4639
|
-
);
|
|
4640
|
-
const [index, setIndex] = useState3(initialIndex === -1 ? 0 : initialIndex);
|
|
4641
|
-
useInput2((_input, key) => {
|
|
4642
|
-
if (key.upArrow) {
|
|
4643
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4644
|
-
} else if (key.downArrow) {
|
|
4645
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4646
|
-
} else if (key.return) {
|
|
4647
|
-
const chosen = items[index];
|
|
4648
|
-
if (chosen && !chosen.disabled) onSubmit(chosen.value);
|
|
4649
|
-
} else if (key.escape && onCancel) {
|
|
4650
|
-
onCancel();
|
|
4651
|
-
}
|
|
4652
|
-
});
|
|
4653
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React6.createElement(
|
|
4654
|
-
SelectRow,
|
|
4655
|
-
{
|
|
4656
|
-
key: item.value,
|
|
4657
|
-
item,
|
|
4658
|
-
active: i === index,
|
|
4659
|
-
marker: i === index ? "\u25B8" : " "
|
|
4660
|
-
}
|
|
4661
|
-
)));
|
|
4662
|
-
}
|
|
4663
|
-
function MultiSelect({
|
|
4664
|
-
items,
|
|
4665
|
-
initialSelected = [],
|
|
4666
|
-
onSubmit,
|
|
4667
|
-
onCancel,
|
|
4668
|
-
footer
|
|
4669
|
-
}) {
|
|
4670
|
-
const [index, setIndex] = useState3(() => {
|
|
4671
|
-
const first = items.findIndex((i) => !i.disabled);
|
|
4672
|
-
return first === -1 ? 0 : first;
|
|
4673
|
-
});
|
|
4674
|
-
const [selected, setSelected] = useState3(new Set(initialSelected));
|
|
4675
|
-
useInput2((input, key) => {
|
|
4676
|
-
if (key.upArrow) {
|
|
4677
|
-
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4678
|
-
} else if (key.downArrow) {
|
|
4679
|
-
setIndex((i) => findNextEnabled(items, i, 1));
|
|
4680
|
-
} else if (input === " ") {
|
|
4681
|
-
const item = items[index];
|
|
4682
|
-
if (!item || item.disabled) return;
|
|
4683
|
-
setSelected((prev) => {
|
|
4684
|
-
const next = new Set(prev);
|
|
4685
|
-
if (next.has(item.value)) next.delete(item.value);
|
|
4686
|
-
else next.add(item.value);
|
|
4687
|
-
return next;
|
|
4688
|
-
});
|
|
4689
|
-
} else if (key.return) {
|
|
4690
|
-
const ordered = items.filter((i) => selected.has(i.value)).map((i) => i.value);
|
|
4691
|
-
onSubmit(ordered);
|
|
4692
|
-
} else if (key.escape && onCancel) {
|
|
4693
|
-
onCancel();
|
|
4694
|
-
}
|
|
4695
|
-
});
|
|
4696
|
-
return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, items.map((item, i) => {
|
|
4697
|
-
const checked = selected.has(item.value);
|
|
4698
|
-
const marker = checked ? "[x]" : "[ ]";
|
|
4699
|
-
return /* @__PURE__ */ React6.createElement(
|
|
4700
|
-
SelectRow,
|
|
4701
|
-
{
|
|
4702
|
-
key: item.value,
|
|
4703
|
-
item,
|
|
4704
|
-
active: i === index,
|
|
4705
|
-
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
4706
|
-
}
|
|
4707
|
-
);
|
|
4708
|
-
}), footer ? /* @__PURE__ */ React6.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, footer)) : null);
|
|
4709
|
-
}
|
|
4710
|
-
function SelectRow({
|
|
4711
|
-
item,
|
|
4712
|
-
active,
|
|
4713
|
-
marker
|
|
4714
|
-
}) {
|
|
4715
|
-
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
4716
|
-
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);
|
|
4717
|
-
}
|
|
4718
|
-
function findNextEnabled(items, from, step) {
|
|
4719
|
-
if (items.length === 0) return 0;
|
|
4720
|
-
let i = from;
|
|
4721
|
-
for (let tries = 0; tries < items.length; tries++) {
|
|
4722
|
-
i = (i + step + items.length) % items.length;
|
|
4723
|
-
if (!items[i]?.disabled) return i;
|
|
4963
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text7, null, before), /* @__PURE__ */ React8.createElement(Text7, { color: borderColor }, showCursor ? "\u258C" : " "));
|
|
4724
4964
|
}
|
|
4725
|
-
return
|
|
4965
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text7, null, before), /* @__PURE__ */ React8.createElement(Text7, { inverse: showCursor }, atCursor), /* @__PURE__ */ React8.createElement(Text7, null, after));
|
|
4726
4966
|
}
|
|
4727
4967
|
|
|
4728
4968
|
// src/cli/ui/ShellConfirm.tsx
|
|
4969
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
4970
|
+
import React9 from "react";
|
|
4729
4971
|
function ShellConfirm({ command, allowPrefix, onChoose }) {
|
|
4730
|
-
return /* @__PURE__ */
|
|
4972
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "yellow" }, "\u25B8 model wants to run a shell command")), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "$ "), /* @__PURE__ */ React9.createElement(Text8, { color: "cyan" }, command))), /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(
|
|
4731
4973
|
SingleSelect,
|
|
4732
4974
|
{
|
|
4733
4975
|
initialValue: "run_once",
|
|
@@ -4782,15 +5024,15 @@ function derivePrefix(command) {
|
|
|
4782
5024
|
}
|
|
4783
5025
|
|
|
4784
5026
|
// src/cli/ui/SlashSuggestions.tsx
|
|
4785
|
-
import { Box as
|
|
4786
|
-
import
|
|
5027
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
5028
|
+
import React10 from "react";
|
|
4787
5029
|
function SlashSuggestions({
|
|
4788
5030
|
matches,
|
|
4789
5031
|
selectedIndex
|
|
4790
5032
|
}) {
|
|
4791
5033
|
if (matches === null) return null;
|
|
4792
5034
|
if (matches.length === 0) {
|
|
4793
|
-
return /* @__PURE__ */
|
|
5035
|
+
return /* @__PURE__ */ React10.createElement(Box9, { paddingX: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
|
|
4794
5036
|
}
|
|
4795
5037
|
const MAX = 8;
|
|
4796
5038
|
const total = matches.length;
|
|
@@ -4798,27 +5040,28 @@ function SlashSuggestions({
|
|
|
4798
5040
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
4799
5041
|
const hiddenAbove = windowStart;
|
|
4800
5042
|
const hiddenBelow = total - windowStart - shown.length;
|
|
4801
|
-
return /* @__PURE__ */
|
|
5043
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React10.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2191/\u2193 navigate \xB7 Tab or Enter to pick"));
|
|
4802
5044
|
}
|
|
4803
5045
|
function SuggestionRow({ spec, isSelected }) {
|
|
4804
5046
|
const marker = isSelected ? "\u25B8" : " ";
|
|
4805
5047
|
const name = `/${spec.cmd}`;
|
|
4806
5048
|
const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
|
|
4807
5049
|
if (isSelected) {
|
|
4808
|
-
return /* @__PURE__ */
|
|
5050
|
+
return /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React10.createElement(Text9, { color: "cyan" }, " ", spec.summary));
|
|
4809
5051
|
}
|
|
4810
|
-
return /* @__PURE__ */
|
|
5052
|
+
return /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
|
|
4811
5053
|
}
|
|
4812
5054
|
|
|
4813
5055
|
// src/cli/ui/StatsPanel.tsx
|
|
4814
|
-
import { Box as
|
|
4815
|
-
import
|
|
5056
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
5057
|
+
import React11 from "react";
|
|
4816
5058
|
function StatsPanel({
|
|
4817
5059
|
summary,
|
|
4818
5060
|
model,
|
|
4819
5061
|
prefixHash,
|
|
4820
5062
|
harvestOn,
|
|
4821
5063
|
branchBudget,
|
|
5064
|
+
planMode,
|
|
4822
5065
|
balance
|
|
4823
5066
|
}) {
|
|
4824
5067
|
const hitPct = (summary.cacheHitRatio * 100).toFixed(1);
|
|
@@ -4827,7 +5070,7 @@ function StatsPanel({
|
|
|
4827
5070
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
4828
5071
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
4829
5072
|
const ctxColor = ctxRatio >= 0.8 ? "red" : ctxRatio >= 0.5 ? "yellow" : void 0;
|
|
4830
|
-
return /* @__PURE__ */
|
|
5073
|
+
return /* @__PURE__ */ React11.createElement(Box10, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Box10, { justifyContent: "space-between" }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, model), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "blue" }, " \xB7 branch", branchBudget) : null, planMode ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " ", "\xB7 PLAN") : null), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cache hit "), /* @__PURE__ */ React11.createElement(Text10, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cost "), /* @__PURE__ */ React11.createElement(Text10, { color: "green", bold: true }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")")), summary.lastPromptTokens > 0 ? /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "ctx "), /* @__PURE__ */ React11.createElement(Text10, { color: ctxColor, bold: ctxColor !== void 0 }, formatTokens(summary.lastPromptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (", (ctxRatio * 100).toFixed(0), "%)"), ctxRatio >= 0.8 ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " ", "\xB7 /compact") : null) : null, balance ? /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "balance "), /* @__PURE__ */ React11.createElement(Text10, { 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));
|
|
4831
5074
|
}
|
|
4832
5075
|
function formatTokens(n) {
|
|
4833
5076
|
if (n < 1e3) return String(n);
|
|
@@ -4869,6 +5112,17 @@ var SLASH_COMMANDS = [
|
|
|
4869
5112
|
argsHint: '"msg"',
|
|
4870
5113
|
summary: "git add -A && git commit -m ...",
|
|
4871
5114
|
contextual: "code"
|
|
5115
|
+
},
|
|
5116
|
+
{
|
|
5117
|
+
cmd: "plan",
|
|
5118
|
+
argsHint: "[on|off]",
|
|
5119
|
+
summary: "toggle read-only plan mode (writes bounced until submit_plan + approval)",
|
|
5120
|
+
contextual: "code"
|
|
5121
|
+
},
|
|
5122
|
+
{
|
|
5123
|
+
cmd: "apply-plan",
|
|
5124
|
+
summary: "force-approve a pending / in-text plan (fallback if picker was missed)",
|
|
5125
|
+
contextual: "code"
|
|
4872
5126
|
}
|
|
4873
5127
|
];
|
|
4874
5128
|
function suggestSlashCommands(prefix, codeMode = false) {
|
|
@@ -4925,6 +5179,8 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
4925
5179
|
" /discard (code mode) drop pending edits without writing",
|
|
4926
5180
|
" /undo (code mode) roll back the last applied edit batch",
|
|
4927
5181
|
' /commit "msg" (code mode) git add -A && git commit -m "msg"',
|
|
5182
|
+
" /plan [on|off] (code mode) toggle read-only plan mode; writes gated behind submit_plan + your approval",
|
|
5183
|
+
" /apply-plan (code mode) force-approve pending/in-text plan (fallback)",
|
|
4928
5184
|
" /sessions list saved sessions (current is marked with \u25B8)",
|
|
4929
5185
|
" /forget delete the current session from disk",
|
|
4930
5186
|
" /new start fresh: drop all context + clear scrollback",
|
|
@@ -5108,6 +5364,42 @@ ${entry.text}`
|
|
|
5108
5364
|
}
|
|
5109
5365
|
return { info: ctx.codeDiscard() };
|
|
5110
5366
|
}
|
|
5367
|
+
case "plan": {
|
|
5368
|
+
if (!ctx.setPlanMode) {
|
|
5369
|
+
return {
|
|
5370
|
+
info: "/plan is only available inside `reasonix code` \u2014 chat mode doesn't gate tool writes."
|
|
5371
|
+
};
|
|
5372
|
+
}
|
|
5373
|
+
const currentOn = Boolean(ctx.planMode);
|
|
5374
|
+
const raw = (args[0] ?? "").toLowerCase();
|
|
5375
|
+
let target;
|
|
5376
|
+
if (raw === "on" || raw === "true" || raw === "1") target = true;
|
|
5377
|
+
else if (raw === "off" || raw === "false" || raw === "0") target = false;
|
|
5378
|
+
else target = !currentOn;
|
|
5379
|
+
ctx.setPlanMode(target);
|
|
5380
|
+
if (target) {
|
|
5381
|
+
return {
|
|
5382
|
+
info: "\u25B8 plan mode ON \u2014 write tools are gated; the model MUST call `submit_plan` before anything executes. (The model can also call submit_plan on its own for big tasks even when plan mode is off \u2014 this toggle is the stronger, explicit constraint.) Type /plan off to leave."
|
|
5383
|
+
};
|
|
5384
|
+
}
|
|
5385
|
+
return {
|
|
5386
|
+
info: "\u25B8 plan mode OFF \u2014 write tools are live again. Model can still propose plans autonomously for large tasks."
|
|
5387
|
+
};
|
|
5388
|
+
}
|
|
5389
|
+
case "apply-plan":
|
|
5390
|
+
case "applyplan": {
|
|
5391
|
+
if (!ctx.setPlanMode) {
|
|
5392
|
+
return {
|
|
5393
|
+
info: "/apply-plan is only available inside `reasonix code`."
|
|
5394
|
+
};
|
|
5395
|
+
}
|
|
5396
|
+
ctx.setPlanMode(false);
|
|
5397
|
+
ctx.clearPendingPlan?.();
|
|
5398
|
+
return {
|
|
5399
|
+
info: "\u25B8 plan approved \u2014 implementing",
|
|
5400
|
+
resubmit: "The plan above has been approved. Implement it now. You are out of plan mode \u2014 use edit_file / write_file / run_command as needed. Stick to the plan unless you discover a concrete reason to deviate; if you do, tell me and wait for a response before making that deviation."
|
|
5401
|
+
};
|
|
5402
|
+
}
|
|
5111
5403
|
case "commit": {
|
|
5112
5404
|
if (!ctx.codeRoot) {
|
|
5113
5405
|
return {
|
|
@@ -5178,6 +5470,7 @@ ${entry.text}`
|
|
|
5178
5470
|
const toolCount = loop.prefix.toolSpecs.length;
|
|
5179
5471
|
const mcpLine = ` mcp ${mcpCount} server(s), ${toolCount} tool(s) in registry`;
|
|
5180
5472
|
const pendingLine = pending > 0 ? ` edits ${pending} pending (/apply to commit, /discard to drop)` : "";
|
|
5473
|
+
const planLine = ctx.planMode ? " plan ON \u2014 writes gated (submit_plan + approval)" : "";
|
|
5181
5474
|
const lines = [
|
|
5182
5475
|
` model ${loop.model}`,
|
|
5183
5476
|
` flags harvest=${loop.harvestEnabled ? "on" : "off"} \xB7 branch=${branchBudget > 1 ? branchBudget : "off"} \xB7 stream=${loop.stream ? "on" : "off"}`,
|
|
@@ -5186,6 +5479,7 @@ ${entry.text}`
|
|
|
5186
5479
|
sessionLine
|
|
5187
5480
|
];
|
|
5188
5481
|
if (pendingLine) lines.push(pendingLine);
|
|
5482
|
+
if (planLine) lines.push(planLine);
|
|
5189
5483
|
return { info: lines.join("\n") };
|
|
5190
5484
|
}
|
|
5191
5485
|
case "model": {
|
|
@@ -5332,25 +5626,28 @@ function App({
|
|
|
5332
5626
|
codeMode
|
|
5333
5627
|
}) {
|
|
5334
5628
|
const { exit } = useApp();
|
|
5335
|
-
const [historical, setHistorical] =
|
|
5336
|
-
const [streaming, setStreaming] =
|
|
5337
|
-
const [input, setInput] =
|
|
5338
|
-
const [busy, setBusy] =
|
|
5629
|
+
const [historical, setHistorical] = useState5([]);
|
|
5630
|
+
const [streaming, setStreaming] = useState5(null);
|
|
5631
|
+
const [input, setInput] = useState5("");
|
|
5632
|
+
const [busy, setBusy] = useState5(false);
|
|
5339
5633
|
const abortedThisTurn = useRef2(false);
|
|
5340
|
-
const [ongoingTool, setOngoingTool] =
|
|
5341
|
-
const [toolProgress, setToolProgress] =
|
|
5342
|
-
const [statusLine, setStatusLine] =
|
|
5343
|
-
const [balance, setBalance] =
|
|
5634
|
+
const [ongoingTool, setOngoingTool] = useState5(null);
|
|
5635
|
+
const [toolProgress, setToolProgress] = useState5(null);
|
|
5636
|
+
const [statusLine, setStatusLine] = useState5(null);
|
|
5637
|
+
const [balance, setBalance] = useState5(null);
|
|
5344
5638
|
const lastEditSnapshots = useRef2(null);
|
|
5345
5639
|
const pendingEdits = useRef2([]);
|
|
5346
|
-
const [pendingShell, setPendingShell] =
|
|
5347
|
-
const [
|
|
5640
|
+
const [pendingShell, setPendingShell] = useState5(null);
|
|
5641
|
+
const [pendingPlan, setPendingPlan] = useState5(null);
|
|
5642
|
+
const [stagedInput, setStagedInput] = useState5(null);
|
|
5643
|
+
const [planMode, setPlanMode] = useState5(false);
|
|
5644
|
+
const [queuedSubmit, setQueuedSubmit] = useState5(null);
|
|
5348
5645
|
const promptHistory = useRef2([]);
|
|
5349
5646
|
const historyCursor = useRef2(-1);
|
|
5350
5647
|
const assistantIterCounter = useRef2(0);
|
|
5351
5648
|
const toolHistoryRef = useRef2([]);
|
|
5352
|
-
const [slashSelected, setSlashSelected] =
|
|
5353
|
-
const [summary, setSummary] =
|
|
5649
|
+
const [slashSelected, setSlashSelected] = useState5(0);
|
|
5650
|
+
const [summary, setSummary] = useState5({
|
|
5354
5651
|
turns: 0,
|
|
5355
5652
|
totalCostUsd: 0,
|
|
5356
5653
|
totalInputCostUsd: 0,
|
|
@@ -5455,7 +5752,7 @@ function App({
|
|
|
5455
5752
|
]);
|
|
5456
5753
|
}
|
|
5457
5754
|
}, [session, loop]);
|
|
5458
|
-
|
|
5755
|
+
useInput4((_input, key) => {
|
|
5459
5756
|
if (key.escape && busy) {
|
|
5460
5757
|
if (abortedThisTurn.current) return;
|
|
5461
5758
|
abortedThisTurn.current = true;
|
|
@@ -5535,6 +5832,16 @@ function App({
|
|
|
5535
5832
|
},
|
|
5536
5833
|
[model, prefixHash]
|
|
5537
5834
|
);
|
|
5835
|
+
const togglePlanMode = useCallback(
|
|
5836
|
+
(on) => {
|
|
5837
|
+
setPlanMode(on);
|
|
5838
|
+
tools?.setPlanMode(on);
|
|
5839
|
+
},
|
|
5840
|
+
[tools]
|
|
5841
|
+
);
|
|
5842
|
+
const clearPendingPlan = useCallback(() => {
|
|
5843
|
+
setPendingPlan(null);
|
|
5844
|
+
}, []);
|
|
5538
5845
|
const handleSubmit = useCallback(
|
|
5539
5846
|
async (raw) => {
|
|
5540
5847
|
let text = raw.trim();
|
|
@@ -5567,7 +5874,10 @@ function App({
|
|
|
5567
5874
|
codeRoot: codeMode?.rootDir,
|
|
5568
5875
|
pendingEditCount: codeMode ? pendingEdits.current.length : void 0,
|
|
5569
5876
|
toolHistory: () => toolHistoryRef.current,
|
|
5570
|
-
memoryRoot: codeMode?.rootDir ?? process.cwd()
|
|
5877
|
+
memoryRoot: codeMode?.rootDir ?? process.cwd(),
|
|
5878
|
+
planMode,
|
|
5879
|
+
setPlanMode: codeMode ? togglePlanMode : void 0,
|
|
5880
|
+
clearPendingPlan: codeMode ? clearPendingPlan : void 0
|
|
5571
5881
|
});
|
|
5572
5882
|
if (result.exit) {
|
|
5573
5883
|
transcriptRef.current?.end();
|
|
@@ -5743,6 +6053,15 @@ function App({
|
|
|
5743
6053
|
} catch {
|
|
5744
6054
|
}
|
|
5745
6055
|
}
|
|
6056
|
+
if (codeMode && ev.toolName === "submit_plan" && ev.content.includes('"PlanProposedError:')) {
|
|
6057
|
+
try {
|
|
6058
|
+
const parsed = JSON.parse(ev.content);
|
|
6059
|
+
if (typeof parsed.plan === "string" && parsed.plan.trim()) {
|
|
6060
|
+
setPendingPlan(parsed.plan.trim());
|
|
6061
|
+
}
|
|
6062
|
+
} catch {
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
5746
6065
|
} else if (ev.role === "error") {
|
|
5747
6066
|
setHistorical((prev) => [
|
|
5748
6067
|
...prev,
|
|
@@ -5775,6 +6094,7 @@ function App({
|
|
|
5775
6094
|
},
|
|
5776
6095
|
[
|
|
5777
6096
|
busy,
|
|
6097
|
+
clearPendingPlan,
|
|
5778
6098
|
codeApply,
|
|
5779
6099
|
codeDiscard,
|
|
5780
6100
|
codeMode,
|
|
@@ -5783,7 +6103,9 @@ function App({
|
|
|
5783
6103
|
loop,
|
|
5784
6104
|
mcpSpecs,
|
|
5785
6105
|
mcpServers,
|
|
6106
|
+
planMode,
|
|
5786
6107
|
slashSelected,
|
|
6108
|
+
togglePlanMode,
|
|
5787
6109
|
writeTranscript
|
|
5788
6110
|
]
|
|
5789
6111
|
);
|
|
@@ -5848,7 +6170,92 @@ ${body}`;
|
|
|
5848
6170
|
void handleSubmit(text);
|
|
5849
6171
|
}
|
|
5850
6172
|
}, [busy, queuedSubmit, handleSubmit]);
|
|
5851
|
-
|
|
6173
|
+
const handlePlanConfirm = useCallback(
|
|
6174
|
+
async (choice) => {
|
|
6175
|
+
const hadPendingPlan = pendingPlan !== null;
|
|
6176
|
+
if (!hadPendingPlan && choice !== "approve") {
|
|
6177
|
+
return;
|
|
6178
|
+
}
|
|
6179
|
+
if (choice === "refine" || choice === "approve") {
|
|
6180
|
+
if (pendingPlan) {
|
|
6181
|
+
setStagedInput({ plan: pendingPlan, mode: choice });
|
|
6182
|
+
setPendingPlan(null);
|
|
6183
|
+
} else if (choice === "approve") {
|
|
6184
|
+
setStagedInput({ plan: "", mode: "approve" });
|
|
6185
|
+
}
|
|
6186
|
+
return;
|
|
6187
|
+
}
|
|
6188
|
+
setPendingPlan(null);
|
|
6189
|
+
togglePlanMode(false);
|
|
6190
|
+
const marker = "\u25B8 plan cancelled";
|
|
6191
|
+
const synthetic = "The plan was cancelled. Drop it entirely. Ask me what I actually want before proposing another plan or making any changes.";
|
|
6192
|
+
setHistorical((prev) => [
|
|
6193
|
+
...prev,
|
|
6194
|
+
{ id: `plan-${choice}-${Date.now()}`, role: "info", text: marker }
|
|
6195
|
+
]);
|
|
6196
|
+
if (busy) {
|
|
6197
|
+
loop.abort();
|
|
6198
|
+
setQueuedSubmit(synthetic);
|
|
6199
|
+
} else {
|
|
6200
|
+
await handleSubmit(synthetic);
|
|
6201
|
+
}
|
|
6202
|
+
},
|
|
6203
|
+
[pendingPlan, togglePlanMode, busy, loop, handleSubmit]
|
|
6204
|
+
);
|
|
6205
|
+
const handleStagedInputSubmit = useCallback(
|
|
6206
|
+
async (feedback) => {
|
|
6207
|
+
const staged = stagedInput;
|
|
6208
|
+
setStagedInput(null);
|
|
6209
|
+
if (!staged) return;
|
|
6210
|
+
const trimmed = feedback.trim();
|
|
6211
|
+
let synthetic;
|
|
6212
|
+
let marker;
|
|
6213
|
+
if (staged.mode === "approve") {
|
|
6214
|
+
togglePlanMode(false);
|
|
6215
|
+
if (trimmed) {
|
|
6216
|
+
synthetic = `The plan above has been approved. Implement it now. You are out of plan mode \u2014 use edit_file / write_file / run_command as needed.
|
|
6217
|
+
|
|
6218
|
+
User's additional instructions / answers to your open questions:
|
|
6219
|
+
|
|
6220
|
+
${trimmed}
|
|
6221
|
+
|
|
6222
|
+
Factor these in before the first edit. Stick to the plan unless you discover a concrete reason to deviate; if you do, tell me and wait for a response.`;
|
|
6223
|
+
marker = `\u25B8 plan approved + instructions \u2014 ${trimmed.length > 50 ? `${trimmed.slice(0, 50)}\u2026` : trimmed}`;
|
|
6224
|
+
} else {
|
|
6225
|
+
synthetic = "The plan above has been approved. Implement it now. You are out of plan mode \u2014 use edit_file / write_file / run_command as needed. If the plan listed open questions and I didn't answer them, default to the safest interpretation and call them out in your first reply. Don't fabricate preferences \u2014 if a question is truly unanswerable without me, stop and ask.";
|
|
6226
|
+
marker = "\u25B8 plan approved \u2014 implementing";
|
|
6227
|
+
}
|
|
6228
|
+
} else {
|
|
6229
|
+
if (trimmed) {
|
|
6230
|
+
synthetic = `The plan needs refinement. User feedback / answers:
|
|
6231
|
+
|
|
6232
|
+
${trimmed}
|
|
6233
|
+
|
|
6234
|
+
Stay in plan mode \u2014 address the feedback (explore more if needed), then submit an improved submit_plan call. Don't propose a near-identical plan unless you explain why the feedback doesn't apply.`;
|
|
6235
|
+
marker = `\u25B8 refining \u2014 ${trimmed.length > 50 ? `${trimmed.slice(0, 50)}\u2026` : trimmed}`;
|
|
6236
|
+
} else {
|
|
6237
|
+
synthetic = "The plan needs refinement, but the user didn't give specifics. Ask them one or two concrete questions \u2014 scope, approach, file boundaries, or the risks you flagged \u2014 then wait for their answer before submitting an updated plan.";
|
|
6238
|
+
marker = "\u25B8 refining \u2014 asking the model to clarify";
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
setHistorical((prev) => [
|
|
6242
|
+
...prev,
|
|
6243
|
+
{ id: `plan-${staged.mode}-${Date.now()}`, role: "info", text: marker }
|
|
6244
|
+
]);
|
|
6245
|
+
if (busy) {
|
|
6246
|
+
loop.abort();
|
|
6247
|
+
setQueuedSubmit(synthetic);
|
|
6248
|
+
} else {
|
|
6249
|
+
await handleSubmit(synthetic);
|
|
6250
|
+
}
|
|
6251
|
+
},
|
|
6252
|
+
[stagedInput, togglePlanMode, busy, loop, handleSubmit]
|
|
6253
|
+
);
|
|
6254
|
+
const handleStagedInputCancel = useCallback(() => {
|
|
6255
|
+
if (stagedInput?.plan) setPendingPlan(stagedInput.plan);
|
|
6256
|
+
setStagedInput(null);
|
|
6257
|
+
}, [stagedInput]);
|
|
6258
|
+
return /* @__PURE__ */ React12.createElement(TickerProvider, { disabled: PLAIN_UI }, /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(
|
|
5852
6259
|
StatsPanel,
|
|
5853
6260
|
{
|
|
5854
6261
|
summary,
|
|
@@ -5856,16 +6263,24 @@ ${body}`;
|
|
|
5856
6263
|
prefixHash,
|
|
5857
6264
|
harvestOn: loop.harvestEnabled,
|
|
5858
6265
|
branchBudget: loop.branchOptions.budget,
|
|
6266
|
+
planMode,
|
|
5859
6267
|
balance
|
|
5860
6268
|
}
|
|
5861
|
-
), /* @__PURE__ */
|
|
6269
|
+
), /* @__PURE__ */ React12.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React12.createElement(EventRow, { key: item.id, event: item })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && streaming ? /* @__PURE__ */ React12.createElement(Box11, { marginY: 1 }, /* @__PURE__ */ React12.createElement(EventRow, { event: streaming })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && ongoingTool ? /* @__PURE__ */ React12.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !ongoingTool && statusLine ? /* @__PURE__ */ React12.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React12.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React12.createElement(
|
|
6270
|
+
PlanRefineInput,
|
|
6271
|
+
{
|
|
6272
|
+
mode: stagedInput.mode,
|
|
6273
|
+
onSubmit: handleStagedInputSubmit,
|
|
6274
|
+
onCancel: handleStagedInputCancel
|
|
6275
|
+
}
|
|
6276
|
+
) : pendingPlan ? /* @__PURE__ */ React12.createElement(PlanConfirm, { plan: pendingPlan, onChoose: handlePlanConfirm }) : pendingShell ? /* @__PURE__ */ React12.createElement(
|
|
5862
6277
|
ShellConfirm,
|
|
5863
6278
|
{
|
|
5864
6279
|
command: pendingShell,
|
|
5865
6280
|
allowPrefix: derivePrefix(pendingShell),
|
|
5866
6281
|
onChoose: handleShellConfirm
|
|
5867
6282
|
}
|
|
5868
|
-
) : /* @__PURE__ */
|
|
6283
|
+
) : /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(
|
|
5869
6284
|
PromptInput,
|
|
5870
6285
|
{
|
|
5871
6286
|
value: input,
|
|
@@ -5873,13 +6288,13 @@ ${body}`;
|
|
|
5873
6288
|
onSubmit: handleSubmit,
|
|
5874
6289
|
disabled: busy
|
|
5875
6290
|
}
|
|
5876
|
-
), /* @__PURE__ */
|
|
6291
|
+
), /* @__PURE__ */ React12.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }))));
|
|
5877
6292
|
}
|
|
5878
6293
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
5879
6294
|
function StatusRow({ text }) {
|
|
5880
6295
|
const tick = useTick();
|
|
5881
6296
|
const elapsed = useElapsedSeconds();
|
|
5882
|
-
return /* @__PURE__ */
|
|
6297
|
+
return /* @__PURE__ */ React12.createElement(Box11, { marginY: 1 }, /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React12.createElement(Text11, { color: "magenta" }, ` ${text}`), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, ` ${elapsed}s`));
|
|
5883
6298
|
}
|
|
5884
6299
|
function OngoingToolRow({
|
|
5885
6300
|
tool,
|
|
@@ -5888,7 +6303,7 @@ function OngoingToolRow({
|
|
|
5888
6303
|
const tick = useTick();
|
|
5889
6304
|
const elapsed = useElapsedSeconds();
|
|
5890
6305
|
const summary = summarizeToolArgs(tool.name, tool.args);
|
|
5891
|
-
return /* @__PURE__ */
|
|
6306
|
+
return /* @__PURE__ */ React12.createElement(Box11, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, SPINNER_FRAMES[tick % SPINNER_FRAMES.length]), /* @__PURE__ */ React12.createElement(Text11, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React12.createElement(Box11, { paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React12.createElement(Box11, { paddingLeft: 2 }, /* @__PURE__ */ React12.createElement(Text11, { dimColor: true }, summary)) : null);
|
|
5892
6307
|
}
|
|
5893
6308
|
function renderProgressLine(p) {
|
|
5894
6309
|
const msg = p.message ? ` ${p.message}` : "";
|
|
@@ -5984,15 +6399,15 @@ function describeRepair(repair) {
|
|
|
5984
6399
|
}
|
|
5985
6400
|
|
|
5986
6401
|
// src/cli/ui/SessionPicker.tsx
|
|
5987
|
-
import { Box as
|
|
5988
|
-
import
|
|
6402
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
6403
|
+
import React13 from "react";
|
|
5989
6404
|
function SessionPicker({
|
|
5990
6405
|
sessionName,
|
|
5991
6406
|
messageCount,
|
|
5992
6407
|
lastActive,
|
|
5993
6408
|
onChoose
|
|
5994
6409
|
}) {
|
|
5995
|
-
return /* @__PURE__ */
|
|
6410
|
+
return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React13.createElement(Box12, { marginBottom: 1 }, /* @__PURE__ */ React13.createElement(Text12, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, ` \xB7 last active ${relativeTime(lastActive)}`)), /* @__PURE__ */ React13.createElement(
|
|
5996
6411
|
SingleSelect,
|
|
5997
6412
|
{
|
|
5998
6413
|
initialValue: "new",
|
|
@@ -6015,7 +6430,7 @@ function SessionPicker({
|
|
|
6015
6430
|
],
|
|
6016
6431
|
onSubmit: (v) => onChoose(v)
|
|
6017
6432
|
}
|
|
6018
|
-
), /* @__PURE__ */
|
|
6433
|
+
), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, "\u2191\u2193 to move \xB7 Enter to pick")));
|
|
6019
6434
|
}
|
|
6020
6435
|
function relativeTime(date) {
|
|
6021
6436
|
const ms = Date.now() - date.getTime();
|
|
@@ -6031,12 +6446,12 @@ function relativeTime(date) {
|
|
|
6031
6446
|
}
|
|
6032
6447
|
|
|
6033
6448
|
// src/cli/ui/Setup.tsx
|
|
6034
|
-
import { Box as
|
|
6449
|
+
import { Box as Box13, Text as Text13, useApp as useApp2 } from "ink";
|
|
6035
6450
|
import TextInput from "ink-text-input";
|
|
6036
|
-
import
|
|
6451
|
+
import React14, { useState as useState6 } from "react";
|
|
6037
6452
|
function Setup({ onReady }) {
|
|
6038
|
-
const [value, setValue] =
|
|
6039
|
-
const [error, setError] =
|
|
6453
|
+
const [value, setValue] = useState6("");
|
|
6454
|
+
const [error, setError] = useState6(null);
|
|
6040
6455
|
const { exit } = useApp2();
|
|
6041
6456
|
const handleSubmit = (raw) => {
|
|
6042
6457
|
const trimmed = raw.trim();
|
|
@@ -6057,7 +6472,7 @@ function Setup({ onReady }) {
|
|
|
6057
6472
|
}
|
|
6058
6473
|
onReady(trimmed);
|
|
6059
6474
|
};
|
|
6060
|
-
return /* @__PURE__ */
|
|
6475
|
+
return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React14.createElement(Text13, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React14.createElement(
|
|
6061
6476
|
TextInput,
|
|
6062
6477
|
{
|
|
6063
6478
|
value,
|
|
@@ -6066,7 +6481,7 @@ function Setup({ onReady }) {
|
|
|
6066
6481
|
mask: "\u2022",
|
|
6067
6482
|
placeholder: "sk-..."
|
|
6068
6483
|
}
|
|
6069
|
-
)), error ? /* @__PURE__ */
|
|
6484
|
+
)), error ? /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { color: "red" }, error)) : value ? /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React14.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React14.createElement(Text13, { dimColor: true }, "(Type /exit to abort.)")));
|
|
6070
6485
|
}
|
|
6071
6486
|
|
|
6072
6487
|
// src/cli/commands/chat.tsx
|
|
@@ -6079,10 +6494,10 @@ function Root({
|
|
|
6079
6494
|
sessionPreview,
|
|
6080
6495
|
...appProps
|
|
6081
6496
|
}) {
|
|
6082
|
-
const [key, setKey] =
|
|
6083
|
-
const [pending, setPending] =
|
|
6497
|
+
const [key, setKey] = useState7(initialKey);
|
|
6498
|
+
const [pending, setPending] = useState7(sessionPreview);
|
|
6084
6499
|
if (!key) {
|
|
6085
|
-
return /* @__PURE__ */
|
|
6500
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6086
6501
|
Setup,
|
|
6087
6502
|
{
|
|
6088
6503
|
onReady: (k) => {
|
|
@@ -6094,7 +6509,7 @@ function Root({
|
|
|
6094
6509
|
}
|
|
6095
6510
|
process.env.DEEPSEEK_API_KEY = key;
|
|
6096
6511
|
if (pending && appProps.session) {
|
|
6097
|
-
return /* @__PURE__ */
|
|
6512
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6098
6513
|
SessionPicker,
|
|
6099
6514
|
{
|
|
6100
6515
|
sessionName: appProps.session,
|
|
@@ -6109,7 +6524,7 @@ function Root({
|
|
|
6109
6524
|
}
|
|
6110
6525
|
);
|
|
6111
6526
|
}
|
|
6112
|
-
return /* @__PURE__ */
|
|
6527
|
+
return /* @__PURE__ */ React15.createElement(
|
|
6113
6528
|
App,
|
|
6114
6529
|
{
|
|
6115
6530
|
model: appProps.model,
|
|
@@ -6201,14 +6616,14 @@ async function chatCommand(opts) {
|
|
|
6201
6616
|
const prior = loadSessionMessages(opts.session);
|
|
6202
6617
|
if (prior.length > 0) {
|
|
6203
6618
|
const p = sessionPath(opts.session);
|
|
6204
|
-
const mtime =
|
|
6619
|
+
const mtime = existsSync4(p) ? statSync3(p).mtime : /* @__PURE__ */ new Date();
|
|
6205
6620
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
6206
6621
|
}
|
|
6207
6622
|
} else if (opts.session && opts.forceNew) {
|
|
6208
6623
|
rewriteSession(opts.session, []);
|
|
6209
6624
|
}
|
|
6210
6625
|
const { waitUntilExit } = render(
|
|
6211
|
-
/* @__PURE__ */
|
|
6626
|
+
/* @__PURE__ */ React15.createElement(
|
|
6212
6627
|
Root,
|
|
6213
6628
|
{
|
|
6214
6629
|
initialKey,
|
|
@@ -6234,7 +6649,7 @@ async function chatCommand(opts) {
|
|
|
6234
6649
|
// src/cli/commands/code.tsx
|
|
6235
6650
|
import { basename, resolve as resolve5 } from "path";
|
|
6236
6651
|
async function codeCommand(opts = {}) {
|
|
6237
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
6652
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-JNNNJLYF.js");
|
|
6238
6653
|
const rootDir = resolve5(opts.dir ?? process.cwd());
|
|
6239
6654
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
6240
6655
|
const tools = new ToolRegistry();
|
|
@@ -6245,6 +6660,7 @@ async function codeCommand(opts = {}) {
|
|
|
6245
6660
|
// choices; merged on top of the built-in allowlist in shell.ts.
|
|
6246
6661
|
extraAllowed: loadProjectShellAllowed(rootDir)
|
|
6247
6662
|
});
|
|
6663
|
+
registerPlanTool(tools);
|
|
6248
6664
|
process.stderr.write(
|
|
6249
6665
|
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
6250
6666
|
`
|
|
@@ -6267,34 +6683,34 @@ async function codeCommand(opts = {}) {
|
|
|
6267
6683
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
6268
6684
|
import { basename as basename2 } from "path";
|
|
6269
6685
|
import { render as render2 } from "ink";
|
|
6270
|
-
import
|
|
6686
|
+
import React18 from "react";
|
|
6271
6687
|
|
|
6272
6688
|
// src/cli/ui/DiffApp.tsx
|
|
6273
|
-
import { Box as
|
|
6274
|
-
import
|
|
6689
|
+
import { Box as Box15, Static as Static2, Text as Text15, useApp as useApp3, useInput as useInput5 } from "ink";
|
|
6690
|
+
import React17, { useState as useState8 } from "react";
|
|
6275
6691
|
|
|
6276
6692
|
// src/cli/ui/RecordView.tsx
|
|
6277
|
-
import { Box as
|
|
6278
|
-
import
|
|
6693
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
6694
|
+
import React16 from "react";
|
|
6279
6695
|
function RecordView({ rec, compact = false }) {
|
|
6280
6696
|
const toolArgsMax = compact ? 120 : 200;
|
|
6281
6697
|
const toolContentMax = compact ? 200 : 400;
|
|
6282
6698
|
if (rec.role === "user") {
|
|
6283
|
-
return /* @__PURE__ */
|
|
6699
|
+
return /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text14, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React16.createElement(Text14, null, rec.content));
|
|
6284
6700
|
}
|
|
6285
6701
|
if (rec.role === "assistant_final") {
|
|
6286
|
-
return /* @__PURE__ */
|
|
6702
|
+
return /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Text14, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React16.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React16.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React16.createElement(Text14, null, rec.content) : /* @__PURE__ */ React16.createElement(Text14, { dimColor: true, italic: true }, "(tool-call response only)"));
|
|
6287
6703
|
}
|
|
6288
6704
|
if (rec.role === "tool") {
|
|
6289
|
-
return /* @__PURE__ */
|
|
6705
|
+
return /* @__PURE__ */ React16.createElement(Box14, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text14, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
|
|
6290
6706
|
}
|
|
6291
6707
|
if (rec.role === "error") {
|
|
6292
|
-
return /* @__PURE__ */
|
|
6708
|
+
return /* @__PURE__ */ React16.createElement(Box14, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text14, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React16.createElement(Text14, { color: "red" }, rec.error ?? rec.content));
|
|
6293
6709
|
}
|
|
6294
6710
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
6295
6711
|
return null;
|
|
6296
6712
|
}
|
|
6297
|
-
return /* @__PURE__ */
|
|
6713
|
+
return /* @__PURE__ */ React16.createElement(Box14, null, /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
6298
6714
|
}
|
|
6299
6715
|
function CacheBadge({ usage }) {
|
|
6300
6716
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -6303,7 +6719,7 @@ function CacheBadge({ usage }) {
|
|
|
6303
6719
|
if (total === 0) return null;
|
|
6304
6720
|
const pct2 = hit / total * 100;
|
|
6305
6721
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
6306
|
-
return /* @__PURE__ */
|
|
6722
|
+
return /* @__PURE__ */ React16.createElement(Text14, null, /* @__PURE__ */ React16.createElement(Text14, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React16.createElement(Text14, { color }, pct2.toFixed(1), "%"));
|
|
6307
6723
|
}
|
|
6308
6724
|
function truncate3(s, max) {
|
|
6309
6725
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -6314,8 +6730,8 @@ function DiffApp({ report }) {
|
|
|
6314
6730
|
const { exit } = useApp3();
|
|
6315
6731
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
6316
6732
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
6317
|
-
const [idx, setIdx] =
|
|
6318
|
-
|
|
6733
|
+
const [idx, setIdx] = useState8(Math.max(0, initialIdx));
|
|
6734
|
+
useInput5((input, key) => {
|
|
6319
6735
|
if (input === "q" || key.ctrl && input === "c") {
|
|
6320
6736
|
exit();
|
|
6321
6737
|
return;
|
|
@@ -6337,7 +6753,7 @@ function DiffApp({ report }) {
|
|
|
6337
6753
|
}
|
|
6338
6754
|
});
|
|
6339
6755
|
const pair = report.pairs[idx];
|
|
6340
|
-
return /* @__PURE__ */
|
|
6756
|
+
return /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(DiffHeader, { report }), /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React17.createElement(Text15, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React17.createElement(Text15, null, pair ? /* @__PURE__ */ React17.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React17.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React17.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text15, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React17.createElement(Text15, null, pair.divergenceNote)) : null, /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "j"), "/", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "k"), "/", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "N"), "/", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "g"), "/", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React17.createElement(Text15, { bold: true }, "q"), " ", "quit")));
|
|
6341
6757
|
}
|
|
6342
6758
|
function DiffHeader({ report }) {
|
|
6343
6759
|
const a = report.a;
|
|
@@ -6355,15 +6771,15 @@ function DiffHeader({ report }) {
|
|
|
6355
6771
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
6356
6772
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
6357
6773
|
}
|
|
6358
|
-
return /* @__PURE__ */
|
|
6774
|
+
return /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Box15, { justifyContent: "space-between" }, /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React17.createElement(Text15, { color: "blue" }, a.label), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " vs B="), /* @__PURE__ */ React17.createElement(Text15, { color: "magenta" }, b.label)), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, "cache "), /* @__PURE__ */ React17.createElement(Text15, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React17.createElement(Text15, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React17.createElement(Text15, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, "cost "), /* @__PURE__ */ React17.createElement(Text15, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React17.createElement(Text15, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React17.createElement(Text15, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React17.createElement(Text15, null, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true }, "model calls "), /* @__PURE__ */ React17.createElement(Text15, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true, italic: true }, prefixLine)) : null);
|
|
6359
6775
|
}
|
|
6360
6776
|
function Pane({
|
|
6361
6777
|
label,
|
|
6362
6778
|
headerColor,
|
|
6363
6779
|
records
|
|
6364
6780
|
}) {
|
|
6365
|
-
return /* @__PURE__ */
|
|
6366
|
-
|
|
6781
|
+
return /* @__PURE__ */ React17.createElement(
|
|
6782
|
+
Box15,
|
|
6367
6783
|
{
|
|
6368
6784
|
flexDirection: "column",
|
|
6369
6785
|
flexGrow: 1,
|
|
@@ -6371,21 +6787,21 @@ function Pane({
|
|
|
6371
6787
|
borderStyle: "single",
|
|
6372
6788
|
borderColor: headerColor
|
|
6373
6789
|
},
|
|
6374
|
-
/* @__PURE__ */
|
|
6375
|
-
records.length === 0 ? /* @__PURE__ */
|
|
6790
|
+
/* @__PURE__ */ React17.createElement(Text15, { color: headerColor, bold: true }, label),
|
|
6791
|
+
records.length === 0 ? /* @__PURE__ */ React17.createElement(Box15, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text15, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React17.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React17.createElement(RecordView, { key, rec, compact: true }))
|
|
6376
6792
|
);
|
|
6377
6793
|
}
|
|
6378
6794
|
function KindBadge({ kind }) {
|
|
6379
6795
|
if (kind === "match") {
|
|
6380
|
-
return /* @__PURE__ */
|
|
6796
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "green" }, "\u2713 match");
|
|
6381
6797
|
}
|
|
6382
6798
|
if (kind === "diverge") {
|
|
6383
|
-
return /* @__PURE__ */
|
|
6799
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "yellow" }, "\u2605 diverge");
|
|
6384
6800
|
}
|
|
6385
6801
|
if (kind === "only_in_a") {
|
|
6386
|
-
return /* @__PURE__ */
|
|
6802
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "blue" }, "\u2190 only in A");
|
|
6387
6803
|
}
|
|
6388
|
-
return /* @__PURE__ */
|
|
6804
|
+
return /* @__PURE__ */ React17.createElement(Text15, { color: "magenta" }, "\u2192 only in B");
|
|
6389
6805
|
}
|
|
6390
6806
|
function paneRecords(pair, side) {
|
|
6391
6807
|
if (!pair) return [];
|
|
@@ -6416,7 +6832,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
6416
6832
|
return;
|
|
6417
6833
|
}
|
|
6418
6834
|
if (wantTui) {
|
|
6419
|
-
const { waitUntilExit } = render2(
|
|
6835
|
+
const { waitUntilExit } = render2(React18.createElement(DiffApp, { report }), {
|
|
6420
6836
|
exitOnCtrlC: true,
|
|
6421
6837
|
patchConsole: false
|
|
6422
6838
|
});
|
|
@@ -6557,16 +6973,16 @@ function pad(s, width) {
|
|
|
6557
6973
|
|
|
6558
6974
|
// src/cli/commands/replay.ts
|
|
6559
6975
|
import { render as render3 } from "ink";
|
|
6560
|
-
import
|
|
6976
|
+
import React20 from "react";
|
|
6561
6977
|
|
|
6562
6978
|
// src/cli/ui/ReplayApp.tsx
|
|
6563
|
-
import { Box as
|
|
6564
|
-
import
|
|
6979
|
+
import { Box as Box16, Static as Static3, Text as Text16, useApp as useApp4, useInput as useInput6 } from "ink";
|
|
6980
|
+
import React19, { useMemo as useMemo2, useState as useState9 } from "react";
|
|
6565
6981
|
function ReplayApp({ meta, pages }) {
|
|
6566
6982
|
const { exit } = useApp4();
|
|
6567
6983
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
6568
|
-
const [idx, setIdx] =
|
|
6569
|
-
|
|
6984
|
+
const [idx, setIdx] = useState9(maxIdx);
|
|
6985
|
+
useInput6((input, key) => {
|
|
6570
6986
|
if (input === "q" || key.ctrl && input === "c") {
|
|
6571
6987
|
exit();
|
|
6572
6988
|
return;
|
|
@@ -6600,14 +7016,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
6600
7016
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
6601
7017
|
const currentPage = pages[idx];
|
|
6602
7018
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
6603
|
-
return /* @__PURE__ */
|
|
7019
|
+
return /* @__PURE__ */ React19.createElement(Box16, { flexDirection: "column" }, /* @__PURE__ */ React19.createElement(
|
|
6604
7020
|
StatsPanel,
|
|
6605
7021
|
{
|
|
6606
7022
|
summary,
|
|
6607
7023
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
6608
7024
|
prefixHash
|
|
6609
7025
|
}
|
|
6610
|
-
), /* @__PURE__ */
|
|
7026
|
+
), /* @__PURE__ */ React19.createElement(Box16, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React19.createElement(Box16, { justifyContent: "space-between" }, /* @__PURE__ */ React19.createElement(Text16, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React19.createElement(Text16, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React19.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React19.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React19.createElement(Text16, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React19.createElement(Box16, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React19.createElement(Text16, { dimColor: true }, /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "j"), "/", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "k"), "/", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React19.createElement(Text16, { bold: true }, "q"), " quit")));
|
|
6611
7027
|
}
|
|
6612
7028
|
|
|
6613
7029
|
// src/cli/commands/replay.ts
|
|
@@ -6619,7 +7035,7 @@ async function replayCommand(opts) {
|
|
|
6619
7035
|
}
|
|
6620
7036
|
const { parsed } = replayFromFile(opts.path);
|
|
6621
7037
|
const pages = groupRecordsByTurn(parsed.records);
|
|
6622
|
-
const { waitUntilExit } = render3(
|
|
7038
|
+
const { waitUntilExit } = render3(React20.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
6623
7039
|
exitOnCtrlC: true,
|
|
6624
7040
|
patchConsole: false
|
|
6625
7041
|
});
|
|
@@ -6921,12 +7337,12 @@ function truncate4(s, max) {
|
|
|
6921
7337
|
|
|
6922
7338
|
// src/cli/commands/setup.tsx
|
|
6923
7339
|
import { render as render4 } from "ink";
|
|
6924
|
-
import
|
|
7340
|
+
import React22 from "react";
|
|
6925
7341
|
|
|
6926
7342
|
// src/cli/ui/Wizard.tsx
|
|
6927
|
-
import { Box as
|
|
7343
|
+
import { Box as Box17, Text as Text17, useApp as useApp5, useInput as useInput7 } from "ink";
|
|
6928
7344
|
import TextInput2 from "ink-text-input";
|
|
6929
|
-
import
|
|
7345
|
+
import React21, { useState as useState10 } from "react";
|
|
6930
7346
|
|
|
6931
7347
|
// src/cli/ui/presets.ts
|
|
6932
7348
|
var PRESETS = {
|
|
@@ -6953,19 +7369,19 @@ var PRESET_DESCRIPTIONS = {
|
|
|
6953
7369
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
6954
7370
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
6955
7371
|
const { exit } = useApp5();
|
|
6956
|
-
const [step, setStep] =
|
|
6957
|
-
const [data, setData] =
|
|
7372
|
+
const [step, setStep] = useState10(existingApiKey ? "preset" : "apiKey");
|
|
7373
|
+
const [data, setData] = useState10({
|
|
6958
7374
|
apiKey: existingApiKey ?? "",
|
|
6959
7375
|
preset: initial?.preset ?? "fast",
|
|
6960
7376
|
selectedCatalog: deriveInitialCatalog(initial?.mcp ?? []),
|
|
6961
7377
|
catalogArgs: {}
|
|
6962
7378
|
});
|
|
6963
|
-
const [error, setError] =
|
|
6964
|
-
|
|
7379
|
+
const [error, setError] = useState10(null);
|
|
7380
|
+
useInput7((_input, key) => {
|
|
6965
7381
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
6966
7382
|
});
|
|
6967
7383
|
if (step === "apiKey") {
|
|
6968
|
-
return /* @__PURE__ */
|
|
7384
|
+
return /* @__PURE__ */ React21.createElement(
|
|
6969
7385
|
ApiKeyStep,
|
|
6970
7386
|
{
|
|
6971
7387
|
onSubmit: (key) => {
|
|
@@ -6979,7 +7395,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6979
7395
|
);
|
|
6980
7396
|
}
|
|
6981
7397
|
if (step === "preset") {
|
|
6982
|
-
return /* @__PURE__ */
|
|
7398
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React21.createElement(
|
|
6983
7399
|
SingleSelect,
|
|
6984
7400
|
{
|
|
6985
7401
|
items: presetItems(),
|
|
@@ -6989,10 +7405,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
6989
7405
|
setStep("mcp");
|
|
6990
7406
|
}
|
|
6991
7407
|
}
|
|
6992
|
-
), /* @__PURE__ */
|
|
7408
|
+
), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
|
|
6993
7409
|
}
|
|
6994
7410
|
if (step === "mcp") {
|
|
6995
|
-
return /* @__PURE__ */
|
|
7411
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React21.createElement(
|
|
6996
7412
|
MultiSelect,
|
|
6997
7413
|
{
|
|
6998
7414
|
items: mcpItems(),
|
|
@@ -7017,7 +7433,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7017
7433
|
}
|
|
7018
7434
|
const currentName = pending[0];
|
|
7019
7435
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
7020
|
-
return /* @__PURE__ */
|
|
7436
|
+
return /* @__PURE__ */ React21.createElement(
|
|
7021
7437
|
McpArgsStep,
|
|
7022
7438
|
{
|
|
7023
7439
|
entry,
|
|
@@ -7035,7 +7451,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7035
7451
|
}
|
|
7036
7452
|
if (step === "review") {
|
|
7037
7453
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
7038
|
-
return /* @__PURE__ */
|
|
7454
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column" }, /* @__PURE__ */ React21.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React21.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React21.createElement(
|
|
7039
7455
|
SummaryLine,
|
|
7040
7456
|
{
|
|
7041
7457
|
label: "MCP",
|
|
@@ -7043,8 +7459,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7043
7459
|
}
|
|
7044
7460
|
), specs.map((spec, i) => (
|
|
7045
7461
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
7046
|
-
/* @__PURE__ */
|
|
7047
|
-
)), /* @__PURE__ */
|
|
7462
|
+
/* @__PURE__ */ React21.createElement(Box17, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\xB7 ", spec))
|
|
7463
|
+
)), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { color: "red" }, error)) : null, /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React21.createElement(
|
|
7048
7464
|
ReviewConfirm,
|
|
7049
7465
|
{
|
|
7050
7466
|
onConfirm: () => {
|
|
@@ -7070,15 +7486,15 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
7070
7486
|
}
|
|
7071
7487
|
));
|
|
7072
7488
|
}
|
|
7073
|
-
return /* @__PURE__ */
|
|
7489
|
+
return /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React21.createElement(ExitOnEnter, { onExit: exit }));
|
|
7074
7490
|
}
|
|
7075
7491
|
function ApiKeyStep({
|
|
7076
7492
|
onSubmit,
|
|
7077
7493
|
error,
|
|
7078
7494
|
onError
|
|
7079
7495
|
}) {
|
|
7080
|
-
const [value, setValue] =
|
|
7081
|
-
return /* @__PURE__ */
|
|
7496
|
+
const [value, setValue] = useState10("");
|
|
7497
|
+
return /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React21.createElement(
|
|
7082
7498
|
TextInput2,
|
|
7083
7499
|
{
|
|
7084
7500
|
value,
|
|
@@ -7095,7 +7511,7 @@ function ApiKeyStep({
|
|
|
7095
7511
|
mask: "\u2022",
|
|
7096
7512
|
placeholder: "sk-..."
|
|
7097
7513
|
}
|
|
7098
|
-
)), error ? /* @__PURE__ */
|
|
7514
|
+
)), error ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { color: "red" }, error)) : value ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "preview: ", redactKey(value))) : null);
|
|
7099
7515
|
}
|
|
7100
7516
|
function McpArgsStep({
|
|
7101
7517
|
entry,
|
|
@@ -7103,8 +7519,8 @@ function McpArgsStep({
|
|
|
7103
7519
|
onSubmit,
|
|
7104
7520
|
onError
|
|
7105
7521
|
}) {
|
|
7106
|
-
const [value, setValue] =
|
|
7107
|
-
return /* @__PURE__ */
|
|
7522
|
+
const [value, setValue] = useState10("");
|
|
7523
|
+
return /* @__PURE__ */ React21.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column" }, /* @__PURE__ */ React21.createElement(Text17, null, entry.summary), entry.note ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, null, "Required parameter: "), /* @__PURE__ */ React21.createElement(Text17, { bold: true }, entry.userArgs)), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React21.createElement(
|
|
7108
7524
|
TextInput2,
|
|
7109
7525
|
{
|
|
7110
7526
|
value,
|
|
@@ -7120,16 +7536,16 @@ function McpArgsStep({
|
|
|
7120
7536
|
},
|
|
7121
7537
|
placeholder: placeholderFor(entry)
|
|
7122
7538
|
}
|
|
7123
|
-
)), error ? /* @__PURE__ */
|
|
7539
|
+
)), error ? /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text17, { color: "red" }, error)) : null));
|
|
7124
7540
|
}
|
|
7125
7541
|
function ReviewConfirm({ onConfirm }) {
|
|
7126
|
-
|
|
7542
|
+
useInput7((_i, key) => {
|
|
7127
7543
|
if (key.return) onConfirm();
|
|
7128
7544
|
});
|
|
7129
7545
|
return null;
|
|
7130
7546
|
}
|
|
7131
7547
|
function ExitOnEnter({ onExit }) {
|
|
7132
|
-
|
|
7548
|
+
useInput7((_i, key) => {
|
|
7133
7549
|
if (key.return) onExit();
|
|
7134
7550
|
});
|
|
7135
7551
|
return null;
|
|
@@ -7140,10 +7556,10 @@ function StepFrame({
|
|
|
7140
7556
|
total,
|
|
7141
7557
|
children
|
|
7142
7558
|
}) {
|
|
7143
|
-
return /* @__PURE__ */
|
|
7559
|
+
return /* @__PURE__ */ React21.createElement(Box17, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React21.createElement(Box17, null, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React21.createElement(Text17, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React21.createElement(Box17, { marginTop: 1, flexDirection: "column" }, children));
|
|
7144
7560
|
}
|
|
7145
7561
|
function SummaryLine({ label, value }) {
|
|
7146
|
-
return /* @__PURE__ */
|
|
7562
|
+
return /* @__PURE__ */ React21.createElement(Box17, null, /* @__PURE__ */ React21.createElement(Text17, null, label.padEnd(12)), /* @__PURE__ */ React21.createElement(Text17, { bold: true }, value));
|
|
7147
7563
|
}
|
|
7148
7564
|
function presetItems() {
|
|
7149
7565
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -7199,7 +7615,7 @@ async function setupCommand(_opts = {}) {
|
|
|
7199
7615
|
const existingKey = loadApiKey();
|
|
7200
7616
|
const existing = readConfig();
|
|
7201
7617
|
const { waitUntilExit, unmount } = render4(
|
|
7202
|
-
/* @__PURE__ */
|
|
7618
|
+
/* @__PURE__ */ React22.createElement(
|
|
7203
7619
|
Wizard,
|
|
7204
7620
|
{
|
|
7205
7621
|
existingApiKey: existingKey,
|
|
@@ -7217,9 +7633,9 @@ async function setupCommand(_opts = {}) {
|
|
|
7217
7633
|
}
|
|
7218
7634
|
|
|
7219
7635
|
// src/cli/commands/stats.ts
|
|
7220
|
-
import { existsSync as
|
|
7636
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
7221
7637
|
function statsCommand(opts) {
|
|
7222
|
-
if (!
|
|
7638
|
+
if (!existsSync5(opts.transcript)) {
|
|
7223
7639
|
console.error(`no such transcript: ${opts.transcript}`);
|
|
7224
7640
|
process.exit(1);
|
|
7225
7641
|
}
|