reasonix 0.12.20 → 0.12.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +698 -106
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -181,8 +181,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
181
181
|
}
|
|
182
182
|
function sleep(ms, signal) {
|
|
183
183
|
if (ms <= 0) return Promise.resolve();
|
|
184
|
-
return new Promise((
|
|
185
|
-
const timer = setTimeout(
|
|
184
|
+
return new Promise((resolve14, reject) => {
|
|
185
|
+
const timer = setTimeout(resolve14, ms);
|
|
186
186
|
if (signal) {
|
|
187
187
|
const onAbort = () => {
|
|
188
188
|
clearTimeout(timer);
|
|
@@ -668,7 +668,7 @@ function matchesTool(hook, toolName) {
|
|
|
668
668
|
}
|
|
669
669
|
var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
|
|
670
670
|
function defaultSpawner(input) {
|
|
671
|
-
return new Promise((
|
|
671
|
+
return new Promise((resolve14) => {
|
|
672
672
|
const child = spawn(input.command, {
|
|
673
673
|
cwd: input.cwd,
|
|
674
674
|
shell: true,
|
|
@@ -713,7 +713,7 @@ function defaultSpawner(input) {
|
|
|
713
713
|
child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
|
|
714
714
|
child.once("error", (err) => {
|
|
715
715
|
clearTimeout(timer);
|
|
716
|
-
|
|
716
|
+
resolve14({
|
|
717
717
|
exitCode: null,
|
|
718
718
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
719
719
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
@@ -724,7 +724,7 @@ function defaultSpawner(input) {
|
|
|
724
724
|
});
|
|
725
725
|
child.once("close", (code) => {
|
|
726
726
|
clearTimeout(timer);
|
|
727
|
-
|
|
727
|
+
resolve14({
|
|
728
728
|
exitCode: code,
|
|
729
729
|
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
730
730
|
stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
|
|
@@ -762,13 +762,13 @@ async function runHooks(opts) {
|
|
|
762
762
|
const matching = opts.hooks.filter((h) => h.event === event && matchesTool(h, toolName));
|
|
763
763
|
const outcomes = [];
|
|
764
764
|
let blocked = false;
|
|
765
|
-
const
|
|
765
|
+
const stdin5 = `${JSON.stringify(opts.payload)}
|
|
766
766
|
`;
|
|
767
767
|
for (const hook of matching) {
|
|
768
768
|
const start = Date.now();
|
|
769
769
|
const timeoutMs = hook.timeout ?? DEFAULT_TIMEOUTS_MS[event];
|
|
770
770
|
const cwd2 = hook.cwd ?? opts.payload.cwd;
|
|
771
|
-
const raw = await spawner({ command: hook.command, cwd: cwd2, stdin:
|
|
771
|
+
const raw = await spawner({ command: hook.command, cwd: cwd2, stdin: stdin5, timeoutMs });
|
|
772
772
|
const decision = decideOutcome(event, raw);
|
|
773
773
|
outcomes.push({
|
|
774
774
|
hook,
|
|
@@ -2646,8 +2646,8 @@ var CacheFirstLoop = class {
|
|
|
2646
2646
|
}
|
|
2647
2647
|
);
|
|
2648
2648
|
for (let k = 0; k < budget2; k++) {
|
|
2649
|
-
const sample = queue.shift() ?? await new Promise((
|
|
2650
|
-
waiter =
|
|
2649
|
+
const sample = queue.shift() ?? await new Promise((resolve14) => {
|
|
2650
|
+
waiter = resolve14;
|
|
2651
2651
|
});
|
|
2652
2652
|
yield {
|
|
2653
2653
|
turn: this._turn,
|
|
@@ -5444,7 +5444,7 @@ async function runCommand(cmd, opts) {
|
|
|
5444
5444
|
};
|
|
5445
5445
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
5446
5446
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
5447
|
-
return await new Promise((
|
|
5447
|
+
return await new Promise((resolve14, reject) => {
|
|
5448
5448
|
let child;
|
|
5449
5449
|
try {
|
|
5450
5450
|
child = spawn3(bin, args, effectiveSpawnOpts);
|
|
@@ -5489,7 +5489,7 @@ async function runCommand(cmd, opts) {
|
|
|
5489
5489
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
5490
5490
|
|
|
5491
5491
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
5492
|
-
|
|
5492
|
+
resolve14({ exitCode: code, output, timedOut });
|
|
5493
5493
|
});
|
|
5494
5494
|
});
|
|
5495
5495
|
}
|
|
@@ -6832,7 +6832,7 @@ var McpClient = class {
|
|
|
6832
6832
|
const id = this.nextId++;
|
|
6833
6833
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
6834
6834
|
let abortHandler = null;
|
|
6835
|
-
const promise = new Promise((
|
|
6835
|
+
const promise = new Promise((resolve14, reject) => {
|
|
6836
6836
|
const timeout = setTimeout(() => {
|
|
6837
6837
|
this.pending.delete(id);
|
|
6838
6838
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -6841,7 +6841,7 @@ var McpClient = class {
|
|
|
6841
6841
|
);
|
|
6842
6842
|
}, this.requestTimeoutMs);
|
|
6843
6843
|
this.pending.set(id, {
|
|
6844
|
-
resolve:
|
|
6844
|
+
resolve: resolve14,
|
|
6845
6845
|
reject,
|
|
6846
6846
|
timeout
|
|
6847
6847
|
});
|
|
@@ -6964,12 +6964,12 @@ var StdioTransport = class {
|
|
|
6964
6964
|
}
|
|
6965
6965
|
async send(message) {
|
|
6966
6966
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
6967
|
-
return new Promise((
|
|
6967
|
+
return new Promise((resolve14, reject) => {
|
|
6968
6968
|
const line = `${JSON.stringify(message)}
|
|
6969
6969
|
`;
|
|
6970
6970
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
6971
6971
|
if (err) reject(err);
|
|
6972
|
-
else
|
|
6972
|
+
else resolve14();
|
|
6973
6973
|
});
|
|
6974
6974
|
});
|
|
6975
6975
|
}
|
|
@@ -6980,8 +6980,8 @@ var StdioTransport = class {
|
|
|
6980
6980
|
continue;
|
|
6981
6981
|
}
|
|
6982
6982
|
if (this.closed) return;
|
|
6983
|
-
const next = await new Promise((
|
|
6984
|
-
this.waiters.push(
|
|
6983
|
+
const next = await new Promise((resolve14) => {
|
|
6984
|
+
this.waiters.push(resolve14);
|
|
6985
6985
|
});
|
|
6986
6986
|
if (next === null) return;
|
|
6987
6987
|
yield next;
|
|
@@ -7047,8 +7047,8 @@ var SseTransport = class {
|
|
|
7047
7047
|
constructor(opts) {
|
|
7048
7048
|
this.url = opts.url;
|
|
7049
7049
|
this.headers = opts.headers ?? {};
|
|
7050
|
-
this.endpointReady = new Promise((
|
|
7051
|
-
this.resolveEndpoint =
|
|
7050
|
+
this.endpointReady = new Promise((resolve14, reject) => {
|
|
7051
|
+
this.resolveEndpoint = resolve14;
|
|
7052
7052
|
this.rejectEndpoint = reject;
|
|
7053
7053
|
});
|
|
7054
7054
|
this.endpointReady.catch(() => void 0);
|
|
@@ -7075,8 +7075,8 @@ var SseTransport = class {
|
|
|
7075
7075
|
continue;
|
|
7076
7076
|
}
|
|
7077
7077
|
if (this.closed) return;
|
|
7078
|
-
const next = await new Promise((
|
|
7079
|
-
this.waiters.push(
|
|
7078
|
+
const next = await new Promise((resolve14) => {
|
|
7079
|
+
this.waiters.push(resolve14);
|
|
7080
7080
|
});
|
|
7081
7081
|
if (next === null) return;
|
|
7082
7082
|
yield next;
|
|
@@ -7263,8 +7263,8 @@ var StreamableHttpTransport = class {
|
|
|
7263
7263
|
continue;
|
|
7264
7264
|
}
|
|
7265
7265
|
if (this.closed) return;
|
|
7266
|
-
const next = await new Promise((
|
|
7267
|
-
this.waiters.push(
|
|
7266
|
+
const next = await new Promise((resolve14) => {
|
|
7267
|
+
this.waiters.push(resolve14);
|
|
7268
7268
|
});
|
|
7269
7269
|
if (next === null) return;
|
|
7270
7270
|
yield next;
|
|
@@ -9732,7 +9732,7 @@ async function startOllamaDaemon(opts = {}) {
|
|
|
9732
9732
|
return { ready: false, pid };
|
|
9733
9733
|
}
|
|
9734
9734
|
async function pullOllamaModel(modelName, opts = {}) {
|
|
9735
|
-
return new Promise((
|
|
9735
|
+
return new Promise((resolve14) => {
|
|
9736
9736
|
const child = spawn5("ollama", ["pull", modelName], {
|
|
9737
9737
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9738
9738
|
windowsHide: true
|
|
@@ -9744,8 +9744,8 @@ async function pullOllamaModel(modelName, opts = {}) {
|
|
|
9744
9744
|
}
|
|
9745
9745
|
streamLines(child.stdout, (l) => opts.onLine?.(l, "stdout"));
|
|
9746
9746
|
streamLines(child.stderr, (l) => opts.onLine?.(l, "stderr"));
|
|
9747
|
-
child.once("exit", (code) =>
|
|
9748
|
-
child.once("error", () =>
|
|
9747
|
+
child.once("exit", (code) => resolve14(code ?? -1));
|
|
9748
|
+
child.once("error", () => resolve14(-1));
|
|
9749
9749
|
});
|
|
9750
9750
|
}
|
|
9751
9751
|
function streamLines(stream, cb) {
|
|
@@ -10590,7 +10590,7 @@ var MAX_BODY_BYTES = 256 * 1024;
|
|
|
10590
10590
|
async function readBody(req) {
|
|
10591
10591
|
let total = 0;
|
|
10592
10592
|
const chunks = [];
|
|
10593
|
-
return new Promise((
|
|
10593
|
+
return new Promise((resolve14, reject) => {
|
|
10594
10594
|
req.on("data", (chunk) => {
|
|
10595
10595
|
total += chunk.length;
|
|
10596
10596
|
if (total > MAX_BODY_BYTES) {
|
|
@@ -10600,7 +10600,7 @@ async function readBody(req) {
|
|
|
10600
10600
|
}
|
|
10601
10601
|
chunks.push(chunk);
|
|
10602
10602
|
});
|
|
10603
|
-
req.on("end", () =>
|
|
10603
|
+
req.on("end", () => resolve14(Buffer.concat(chunks).toString("utf8")));
|
|
10604
10604
|
req.on("error", reject);
|
|
10605
10605
|
});
|
|
10606
10606
|
}
|
|
@@ -10677,7 +10677,7 @@ function startDashboardServer(ctx, opts = {}) {
|
|
|
10677
10677
|
const token = opts.token ?? mintToken();
|
|
10678
10678
|
const host = opts.host ?? "127.0.0.1";
|
|
10679
10679
|
const port = opts.port ?? 0;
|
|
10680
|
-
return new Promise((
|
|
10680
|
+
return new Promise((resolve14, reject) => {
|
|
10681
10681
|
const server = createServer((req, res) => {
|
|
10682
10682
|
dispatch(req, res, ctx, token).catch((err) => {
|
|
10683
10683
|
if (!res.headersSent) {
|
|
@@ -10698,7 +10698,7 @@ function startDashboardServer(ctx, opts = {}) {
|
|
|
10698
10698
|
server.close(() => doneResolve());
|
|
10699
10699
|
setTimeout(() => server.closeAllConnections?.(), 1e3).unref();
|
|
10700
10700
|
});
|
|
10701
|
-
|
|
10701
|
+
resolve14({ url, token, port: finalPort, close });
|
|
10702
10702
|
});
|
|
10703
10703
|
});
|
|
10704
10704
|
}
|
|
@@ -10879,8 +10879,8 @@ function ModalCard({
|
|
|
10879
10879
|
icon,
|
|
10880
10880
|
children
|
|
10881
10881
|
}) {
|
|
10882
|
-
const { stdout:
|
|
10883
|
-
const cols =
|
|
10882
|
+
const { stdout: stdout4 } = useStdout();
|
|
10883
|
+
const cols = stdout4?.columns ?? 80;
|
|
10884
10884
|
const ruleWidth = Math.min(80, Math.max(28, cols - 4));
|
|
10885
10885
|
const titleText = icon ? ` ${icon} ${title} ` : ` ${title} `;
|
|
10886
10886
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: accent }, "\u2594".repeat(ruleWidth))), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { backgroundColor: accent, color: "black", bold: true }, titleText), subtitle ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, ` ${subtitle}`) : null), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, flexDirection: "column" }, children), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: accent, dimColor: true }, "\u2581".repeat(ruleWidth))));
|
|
@@ -11314,8 +11314,8 @@ function SelectRow({
|
|
|
11314
11314
|
active,
|
|
11315
11315
|
marker
|
|
11316
11316
|
}) {
|
|
11317
|
-
const
|
|
11318
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React4.createElement(Box3, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, item.hint)) : null);
|
|
11317
|
+
const color2 = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
11318
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { color: color2 }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React4.createElement(Box3, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, item.hint)) : null);
|
|
11319
11319
|
}
|
|
11320
11320
|
function findNextEnabled(items, from, step) {
|
|
11321
11321
|
if (items.length === 0) return 0;
|
|
@@ -11443,8 +11443,8 @@ function capLines(lines, maxLines, indent) {
|
|
|
11443
11443
|
var MODAL_OVERHEAD_ROWS = 18;
|
|
11444
11444
|
var MIN_DIFF_ROWS = 8;
|
|
11445
11445
|
function EditConfirm({ block, onChoose }) {
|
|
11446
|
-
const { stdout:
|
|
11447
|
-
const rows =
|
|
11446
|
+
const { stdout: stdout4 } = useStdout2();
|
|
11447
|
+
const rows = stdout4?.rows ?? 40;
|
|
11448
11448
|
const budget2 = Math.max(MIN_DIFF_ROWS, rows - MODAL_OVERHEAD_ROWS);
|
|
11449
11449
|
const allLines = useMemo(
|
|
11450
11450
|
() => formatEditBlockDiff(block, { contextLines: 2, maxLines: 1e5, indent: " " }),
|
|
@@ -11530,13 +11530,13 @@ function EditConfirm({ block, onChoose }) {
|
|
|
11530
11530
|
) : null,
|
|
11531
11531
|
/* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column" }, visibleLines.map((line, i) => {
|
|
11532
11532
|
const trimmed = line.trimStart();
|
|
11533
|
-
const
|
|
11534
|
-
const dim = !
|
|
11533
|
+
const color2 = trimmed.startsWith("+") ? "#4ade80" : trimmed.startsWith("-") ? "#f87171" : void 0;
|
|
11534
|
+
const dim = !color2;
|
|
11535
11535
|
return /* @__PURE__ */ React6.createElement(
|
|
11536
11536
|
Text4,
|
|
11537
11537
|
{
|
|
11538
11538
|
key: `diff-${effectiveScroll}-${i}`,
|
|
11539
|
-
color,
|
|
11539
|
+
color: color2,
|
|
11540
11540
|
dimColor: dim
|
|
11541
11541
|
},
|
|
11542
11542
|
line
|
|
@@ -11570,7 +11570,7 @@ function PlanStateBlock({ planState }) {
|
|
|
11570
11570
|
if (planState.rejectedPaths.length)
|
|
11571
11571
|
fields.push(["rejected", planState.rejectedPaths, "#94a3b8", true]);
|
|
11572
11572
|
if (fields.length === 0) return null;
|
|
11573
|
-
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items,
|
|
11573
|
+
return /* @__PURE__ */ React7.createElement(Box6, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items, color2, dim]) => /* @__PURE__ */ React7.createElement(Box6, { key: label }, /* @__PURE__ */ React7.createElement(Text5, { backgroundColor: color2, color: "black", bold: true, dimColor: dim }, ` ${label} ${items.length} `), /* @__PURE__ */ React7.createElement(Text5, null, " "), /* @__PURE__ */ React7.createElement(Text5, { dimColor: dim }, items.join(" \xB7 ")))));
|
|
11574
11574
|
}
|
|
11575
11575
|
|
|
11576
11576
|
// src/cli/ui/PlanStepList.tsx
|
|
@@ -12481,8 +12481,8 @@ function gradientCells(width, glyph = GLYPH.block) {
|
|
|
12481
12481
|
const t2 = width === 1 ? 0 : i * last / (width - 1);
|
|
12482
12482
|
const lo = Math.floor(t2);
|
|
12483
12483
|
const hi = Math.min(last, lo + 1);
|
|
12484
|
-
const
|
|
12485
|
-
cells.push({ ch: glyph, color });
|
|
12484
|
+
const color2 = t2 - lo < 0.5 ? GRADIENT[lo] : GRADIENT[hi];
|
|
12485
|
+
cells.push({ ch: glyph, color: color2 });
|
|
12486
12486
|
}
|
|
12487
12487
|
return cells;
|
|
12488
12488
|
}
|
|
@@ -12659,9 +12659,9 @@ var ROLE_GLYPH = {
|
|
|
12659
12659
|
};
|
|
12660
12660
|
function RoleGlyph({
|
|
12661
12661
|
glyph,
|
|
12662
|
-
color
|
|
12662
|
+
color: color2
|
|
12663
12663
|
}) {
|
|
12664
|
-
return /* @__PURE__ */ React11.createElement(Text8, { color, bold: true }, glyph);
|
|
12664
|
+
return /* @__PURE__ */ React11.createElement(Text8, { color: color2, bold: true }, glyph);
|
|
12665
12665
|
}
|
|
12666
12666
|
function ToolPill({ label, status: status2 }) {
|
|
12667
12667
|
const bg = status2 === "err" ? "red" : "yellow";
|
|
@@ -12794,8 +12794,8 @@ var EventRow = React11.memo(function EventRow2({
|
|
|
12794
12794
|
return /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text8, null, event.text));
|
|
12795
12795
|
});
|
|
12796
12796
|
function TurnSeparator() {
|
|
12797
|
-
const { stdout:
|
|
12798
|
-
const cols =
|
|
12797
|
+
const { stdout: stdout4 } = useStdout3();
|
|
12798
|
+
const cols = stdout4?.columns ?? 80;
|
|
12799
12799
|
const width = Math.max(16, cols - 2);
|
|
12800
12800
|
const sideWidth = Math.max(2, Math.floor((width - 5) / 2));
|
|
12801
12801
|
const leftCells = gradientCells(sideWidth, "\u2500");
|
|
@@ -12954,8 +12954,8 @@ function ModeStatusBar({
|
|
|
12954
12954
|
return /* @__PURE__ */ React12.createElement(ModeBarFrame, null, /* @__PURE__ */ React12.createElement(ModePill, { label, bg, flash }), /* @__PURE__ */ React12.createElement(Text9, { dimColor: true }, ` ${mid} \xB7 Shift+Tab to flip`), jobsTag);
|
|
12955
12955
|
}
|
|
12956
12956
|
function ModeBarFrame({ children }) {
|
|
12957
|
-
const { stdout:
|
|
12958
|
-
const cols =
|
|
12957
|
+
const { stdout: stdout4 } = useStdout4();
|
|
12958
|
+
const cols = stdout4?.columns ?? 80;
|
|
12959
12959
|
const ruleWidth = Math.max(20, cols - 2);
|
|
12960
12960
|
return /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text9, { color: "#475569", dimColor: true }, "\u254C".repeat(ruleWidth))), /* @__PURE__ */ React12.createElement(Box10, { paddingX: 1 }, children));
|
|
12961
12961
|
}
|
|
@@ -13756,8 +13756,8 @@ function PromptInput({
|
|
|
13756
13756
|
if (action.historyHandoff === "prev") onHistoryPrev?.();
|
|
13757
13757
|
if (action.historyHandoff === "next") onHistoryNext?.();
|
|
13758
13758
|
}, !disabled);
|
|
13759
|
-
const { stdout:
|
|
13760
|
-
const cols =
|
|
13759
|
+
const { stdout: stdout4 } = useStdout5();
|
|
13760
|
+
const cols = stdout4?.columns ?? 80;
|
|
13761
13761
|
const narrow = cols <= 90;
|
|
13762
13762
|
const promptBody = narrow ? "\u203A " : "you \u203A ";
|
|
13763
13763
|
const promptPrefix = BAR + promptBody;
|
|
@@ -14130,8 +14130,8 @@ function StatsPanel({
|
|
|
14130
14130
|
const branchOn = (branchBudget ?? 1) > 1;
|
|
14131
14131
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model2] ?? DEFAULT_CONTEXT_TOKENS;
|
|
14132
14132
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
14133
|
-
const { stdout:
|
|
14134
|
-
const columns =
|
|
14133
|
+
const { stdout: stdout4 } = useStdout6();
|
|
14134
|
+
const columns = stdout4?.columns ?? 80;
|
|
14135
14135
|
const narrow = columns < NARROW_BREAKPOINT;
|
|
14136
14136
|
const coldStart = summary.turns <= COLD_START_TURNS;
|
|
14137
14137
|
return /* @__PURE__ */ React21.createElement(Box19, { flexDirection: "column", paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React21.createElement(
|
|
@@ -14175,8 +14175,8 @@ function StatsPanel({
|
|
|
14175
14175
|
}
|
|
14176
14176
|
function BudgetRow({ spent, cap }) {
|
|
14177
14177
|
const pct2 = Math.max(0, spent / cap * 100);
|
|
14178
|
-
const
|
|
14179
|
-
return /* @__PURE__ */ React21.createElement(Box19, null, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " budget "), /* @__PURE__ */ React21.createElement(Text17, { color }, `$${spent.toFixed(4)} / $${cap.toFixed(2)}`, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, ` (${pct2.toFixed(0)}%)`)));
|
|
14178
|
+
const color2 = pct2 >= 100 ? "#f87171" : pct2 >= 80 ? "#fbbf24" : "#94a3b8";
|
|
14179
|
+
return /* @__PURE__ */ React21.createElement(Box19, null, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " budget "), /* @__PURE__ */ React21.createElement(Text17, { color: color2 }, `$${spent.toFixed(4)} / $${cap.toFixed(2)}`, /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, ` (${pct2.toFixed(0)}%)`)));
|
|
14180
14180
|
}
|
|
14181
14181
|
function Header({
|
|
14182
14182
|
model: model2,
|
|
@@ -14246,9 +14246,9 @@ function ContextCell({
|
|
|
14246
14246
|
if (promptTokens === 0) {
|
|
14247
14247
|
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info, dimColor: true }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u2014 (no turns yet)"));
|
|
14248
14248
|
}
|
|
14249
|
-
const
|
|
14249
|
+
const color2 = ratio >= 0.8 ? COLOR.err : ratio >= 0.6 ? COLOR.warn : COLOR.ok;
|
|
14250
14250
|
const pct2 = Math.round(ratio * 100);
|
|
14251
|
-
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Bar, { ratio, color, cells: showBar ? 14 : 10 }), /* @__PURE__ */ React21.createElement(Text17, null, " "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.err, bold: true }, " \xB7 /compact") : null);
|
|
14251
|
+
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25A3 ctx "), /* @__PURE__ */ React21.createElement(Bar, { ratio, color: color2, cells: showBar ? 14 : 10 }), /* @__PURE__ */ React21.createElement(Text17, null, " "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, formatTokens(promptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " (", pct2, "%)"), ratio >= 0.8 ? /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.err, bold: true }, " \xB7 /compact") : null);
|
|
14252
14252
|
}
|
|
14253
14253
|
function CacheCell({
|
|
14254
14254
|
hitRatio,
|
|
@@ -14262,8 +14262,8 @@ function CacheCell({
|
|
|
14262
14262
|
if (coldStart) {
|
|
14263
14263
|
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info, dimColor: true }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, pct2, "% "), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true, italic: true }, "(cold start)"));
|
|
14264
14264
|
}
|
|
14265
|
-
const
|
|
14266
|
-
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, pct2, "%"));
|
|
14265
|
+
const color2 = hitRatio >= 0.7 ? COLOR.ok : hitRatio >= 0.4 ? COLOR.warn : COLOR.err;
|
|
14266
|
+
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u232C cache "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, pct2, "%"));
|
|
14267
14267
|
}
|
|
14268
14268
|
function turnCostColor(cost) {
|
|
14269
14269
|
if (cost <= 0) return void 0;
|
|
@@ -14289,16 +14289,16 @@ function CostCell({
|
|
|
14289
14289
|
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25F4 turn "), /* @__PURE__ */ React21.createElement(Text17, { color: turnColor, bold: !coldStart, dimColor: coldStart }, "$", summary.lastTurnCostUsd.toFixed(4)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, " \xB7 session "), /* @__PURE__ */ React21.createElement(Text17, { color: sessionColor, bold: !coldStart, dimColor: coldStart }, "$", summary.totalCostUsd.toFixed(4)));
|
|
14290
14290
|
}
|
|
14291
14291
|
function BalanceCell({ balance }) {
|
|
14292
|
-
const
|
|
14293
|
-
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25D0 balance "), /* @__PURE__ */ React21.createElement(Text17, { color, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
|
|
14292
|
+
const color2 = balance.total < 1 ? COLOR.err : balance.total < 5 ? COLOR.warn : COLOR.ok;
|
|
14293
|
+
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: COLOR.info }, "\u25D0 balance "), /* @__PURE__ */ React21.createElement(Text17, { color: color2, bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : ""));
|
|
14294
14294
|
}
|
|
14295
14295
|
function Bar({
|
|
14296
14296
|
ratio,
|
|
14297
|
-
color,
|
|
14297
|
+
color: color2,
|
|
14298
14298
|
cells = 14
|
|
14299
14299
|
}) {
|
|
14300
14300
|
const filled = Math.max(0, Math.min(cells, Math.round(ratio * cells)));
|
|
14301
|
-
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color }, "\u25B0".repeat(filled)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u25B1".repeat(cells - filled)));
|
|
14301
|
+
return /* @__PURE__ */ React21.createElement(Text17, null, /* @__PURE__ */ React21.createElement(Text17, { color: color2 }, "\u25B0".repeat(filled)), /* @__PURE__ */ React21.createElement(Text17, { dimColor: true }, "\u25B1".repeat(cells - filled)));
|
|
14302
14302
|
}
|
|
14303
14303
|
function formatTokens(n) {
|
|
14304
14304
|
if (n < 1024) return String(n);
|
|
@@ -14310,8 +14310,8 @@ function formatTokens(n) {
|
|
|
14310
14310
|
import { Box as Box20, Text as Text18, useStdout as useStdout7 } from "ink";
|
|
14311
14311
|
import React22 from "react";
|
|
14312
14312
|
function WelcomeBanner({ inCodeMode }) {
|
|
14313
|
-
const { stdout:
|
|
14314
|
-
const cols =
|
|
14313
|
+
const { stdout: stdout4 } = useStdout7();
|
|
14314
|
+
const cols = stdout4?.columns ?? 80;
|
|
14315
14315
|
const ruleWidth = Math.min(60, Math.max(28, cols - 4));
|
|
14316
14316
|
return /* @__PURE__ */ React22.createElement(Box20, { flexDirection: "column", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React22.createElement(GradientRule, { width: ruleWidth }), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.brand }, "\u25C8 welcome"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, " \xB7 type a message to start")), /* @__PURE__ */ React22.createElement(BarRow, null), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.primary }, "quick start")), /* @__PURE__ */ React22.createElement(Hint, { cmd: "/help", desc: "every command + keyboard shortcut" }), /* @__PURE__ */ React22.createElement(Hint, { cmd: "/skill", desc: "invoke a stored playbook" }), inCodeMode ? /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement(Hint, { cmd: "@path", desc: "inline a file in your message" }), /* @__PURE__ */ React22.createElement(Hint, { cmd: "!cmd", desc: "run a shell command, output goes to context" })) : null, /* @__PURE__ */ React22.createElement(Hint, { cmd: "/exit", desc: "quit (Ctrl+C also works)" }), /* @__PURE__ */ React22.createElement(BarRow, null), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true, italic: true }, "tip:"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, " Ctrl+J inserts a newline \xB7 trailing \\ also continues")), /* @__PURE__ */ React22.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(GradientRule, { width: ruleWidth, thin: true })));
|
|
14317
14317
|
}
|
|
@@ -15718,8 +15718,8 @@ ${gitTail(commit2)}` };
|
|
|
15718
15718
|
}
|
|
15719
15719
|
function gitTail(res) {
|
|
15720
15720
|
const stderr = res.stderr ?? "";
|
|
15721
|
-
const
|
|
15722
|
-
const body = stderr.trim() ||
|
|
15721
|
+
const stdout4 = res.stdout ?? "";
|
|
15722
|
+
const body = stderr.trim() || stdout4.trim();
|
|
15723
15723
|
if (body) return body;
|
|
15724
15724
|
if (res.error) return res.error.message;
|
|
15725
15725
|
return "(no output from git)";
|
|
@@ -17689,19 +17689,19 @@ function App({
|
|
|
17689
17689
|
}, [busy]);
|
|
17690
17690
|
const [ongoingTool, setOngoingTool] = useState10(null);
|
|
17691
17691
|
const [toolProgress, setToolProgress] = useState10(null);
|
|
17692
|
-
const { stdout:
|
|
17692
|
+
const { stdout: stdout4 } = useStdout8();
|
|
17693
17693
|
useEffect6(() => {
|
|
17694
|
-
if (!
|
|
17695
|
-
|
|
17696
|
-
|
|
17694
|
+
if (!stdout4 || !stdout4.isTTY) return;
|
|
17695
|
+
stdout4.write("\x1B[?2004h");
|
|
17696
|
+
stdout4.write("\x1B[>4;2m");
|
|
17697
17697
|
return () => {
|
|
17698
|
-
|
|
17699
|
-
|
|
17698
|
+
stdout4.write("\x1B[?2004l");
|
|
17699
|
+
stdout4.write("\x1B[>4m");
|
|
17700
17700
|
};
|
|
17701
|
-
}, [
|
|
17701
|
+
}, [stdout4]);
|
|
17702
17702
|
const [isResizing, setIsResizing] = useState10(false);
|
|
17703
17703
|
useEffect6(() => {
|
|
17704
|
-
if (!
|
|
17704
|
+
if (!stdout4 || !stdout4.isTTY) return;
|
|
17705
17705
|
let timer = null;
|
|
17706
17706
|
const onResize = () => {
|
|
17707
17707
|
setIsResizing(true);
|
|
@@ -17711,12 +17711,12 @@ function App({
|
|
|
17711
17711
|
timer = null;
|
|
17712
17712
|
}, 400);
|
|
17713
17713
|
};
|
|
17714
|
-
|
|
17714
|
+
stdout4.on("resize", onResize);
|
|
17715
17715
|
return () => {
|
|
17716
|
-
|
|
17716
|
+
stdout4.off("resize", onResize);
|
|
17717
17717
|
if (timer) clearTimeout(timer);
|
|
17718
17718
|
};
|
|
17719
|
-
}, [
|
|
17719
|
+
}, [stdout4]);
|
|
17720
17720
|
const { activity: subagentActivity, sinkRef: subagentSinkRef } = useSubagent({
|
|
17721
17721
|
session,
|
|
17722
17722
|
setHistorical
|
|
@@ -18224,11 +18224,11 @@ function App({
|
|
|
18224
18224
|
if (key.escape && busy) {
|
|
18225
18225
|
if (abortedThisTurn.current) return;
|
|
18226
18226
|
abortedThisTurn.current = true;
|
|
18227
|
-
const
|
|
18228
|
-
if (
|
|
18227
|
+
const resolve14 = editReviewResolveRef.current;
|
|
18228
|
+
if (resolve14) {
|
|
18229
18229
|
editReviewResolveRef.current = null;
|
|
18230
18230
|
setPendingEditReview(null);
|
|
18231
|
-
|
|
18231
|
+
resolve14("reject");
|
|
18232
18232
|
}
|
|
18233
18233
|
if (activeLoopRef.current) stopLoop();
|
|
18234
18234
|
loop2.abort();
|
|
@@ -18694,11 +18694,11 @@ function App({
|
|
|
18694
18694
|
handleStagedInputSubmitRef.current(text ?? "", { plan: plan2, mode: choice }).catch(() => void 0);
|
|
18695
18695
|
},
|
|
18696
18696
|
resolveEditReview: (choice) => {
|
|
18697
|
-
const
|
|
18698
|
-
if (
|
|
18697
|
+
const resolve14 = editReviewResolveRef.current;
|
|
18698
|
+
if (resolve14) {
|
|
18699
18699
|
editReviewResolveRef.current = null;
|
|
18700
18700
|
setPendingEditReview(null);
|
|
18701
|
-
|
|
18701
|
+
resolve14(choice);
|
|
18702
18702
|
}
|
|
18703
18703
|
},
|
|
18704
18704
|
resolveWorkspaceConfirm: (choice) => {
|
|
@@ -19001,7 +19001,7 @@ function App({
|
|
|
19001
19001
|
return;
|
|
19002
19002
|
}
|
|
19003
19003
|
if (result.clear && result.info) {
|
|
19004
|
-
|
|
19004
|
+
stdout4?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
19005
19005
|
setHistorical([
|
|
19006
19006
|
{
|
|
19007
19007
|
id: `sys-${Date.now()}`,
|
|
@@ -19018,7 +19018,7 @@ function App({
|
|
|
19018
19018
|
return;
|
|
19019
19019
|
}
|
|
19020
19020
|
if (result.clear) {
|
|
19021
|
-
|
|
19021
|
+
stdout4?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
19022
19022
|
setHistorical([]);
|
|
19023
19023
|
if (codeMode) {
|
|
19024
19024
|
pendingEdits.current = [];
|
|
@@ -19601,7 +19601,7 @@ function App({
|
|
|
19601
19601
|
refreshModels,
|
|
19602
19602
|
proArmed,
|
|
19603
19603
|
persistPlanState,
|
|
19604
|
-
|
|
19604
|
+
stdout4,
|
|
19605
19605
|
stopLoop,
|
|
19606
19606
|
startLoop,
|
|
19607
19607
|
getLoopStatus,
|
|
@@ -20224,10 +20224,10 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
20224
20224
|
{
|
|
20225
20225
|
block: pendingEditReview,
|
|
20226
20226
|
onChoose: (choice) => {
|
|
20227
|
-
const
|
|
20228
|
-
if (
|
|
20227
|
+
const resolve14 = editReviewResolveRef.current;
|
|
20228
|
+
if (resolve14) {
|
|
20229
20229
|
editReviewResolveRef.current = null;
|
|
20230
|
-
|
|
20230
|
+
resolve14(choice);
|
|
20231
20231
|
}
|
|
20232
20232
|
}
|
|
20233
20233
|
}
|
|
@@ -20586,8 +20586,282 @@ async function codeCommand(opts = {}) {
|
|
|
20586
20586
|
});
|
|
20587
20587
|
}
|
|
20588
20588
|
|
|
20589
|
+
// src/cli/commands/commit.ts
|
|
20590
|
+
import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
|
|
20591
|
+
import { mkdtempSync, readFileSync as readFileSync22, unlinkSync as unlinkSync6, writeFileSync as writeFileSync13 } from "fs";
|
|
20592
|
+
import { tmpdir } from "os";
|
|
20593
|
+
import { join as join21 } from "path";
|
|
20594
|
+
import { stdin as stdin2, stdout } from "process";
|
|
20595
|
+
import { createInterface } from "readline/promises";
|
|
20596
|
+
var DEFAULT_MODEL = "deepseek-v4-flash";
|
|
20597
|
+
var DIFF_BYTE_CAP = 80 * 1024;
|
|
20598
|
+
var LOG_COUNT = 10;
|
|
20599
|
+
var SYSTEM_PROMPT2 = `You draft git commit messages.
|
|
20600
|
+
|
|
20601
|
+
Output ONLY the commit message \u2014 no preamble, no \`\`\` fences, no "Here's a commit message:" lead-in. The first line of your output IS the commit subject.
|
|
20602
|
+
|
|
20603
|
+
Match the project's existing style:
|
|
20604
|
+
- Look at the recent commits provided. Mirror their voice, conventional-commit prefix usage (or absence), tense, length, body structure.
|
|
20605
|
+
- If recent commits use a "type(scope): summary" prefix, use it. If they don't, don't invent one.
|
|
20606
|
+
- Subject line: one line, \u226472 chars, imperative mood, no trailing period.
|
|
20607
|
+
- Body (optional): explain WHY when the diff isn't self-evident. Wrap at ~72 chars. Skip the body for trivial changes \u2014 repeating the subject in the body is noise.
|
|
20608
|
+
|
|
20609
|
+
The diff is the source of truth for what changed; describe THAT, not your guesses about the broader project. If the diff includes a deletion you can't explain from the surrounding context, name it but don't speculate about why.
|
|
20610
|
+
|
|
20611
|
+
No emojis unless the recent commits use them.
|
|
20612
|
+
No co-author trailers, no "Generated with X" footers.`;
|
|
20613
|
+
function runGit(args, opts = {}) {
|
|
20614
|
+
const result = spawnSync3("git", args, {
|
|
20615
|
+
encoding: "utf8",
|
|
20616
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
20617
|
+
input: opts.input,
|
|
20618
|
+
maxBuffer: 32 * 1024 * 1024
|
|
20619
|
+
});
|
|
20620
|
+
return {
|
|
20621
|
+
stdout: result.stdout ?? "",
|
|
20622
|
+
stderr: result.stderr ?? "",
|
|
20623
|
+
status: result.status
|
|
20624
|
+
};
|
|
20625
|
+
}
|
|
20626
|
+
function dieIfNotGitRepo() {
|
|
20627
|
+
const r = runGit(["rev-parse", "--is-inside-work-tree"]);
|
|
20628
|
+
if (r.status !== 0) {
|
|
20629
|
+
process.stderr.write("reasonix commit: not inside a git repository.\n");
|
|
20630
|
+
process.exit(1);
|
|
20631
|
+
}
|
|
20632
|
+
}
|
|
20633
|
+
function readDiff() {
|
|
20634
|
+
const staged = runGit(["diff", "--staged", "--no-color"]);
|
|
20635
|
+
if (staged.status !== 0) {
|
|
20636
|
+
process.stderr.write(`reasonix commit: git diff --staged failed: ${staged.stderr.trim()}
|
|
20637
|
+
`);
|
|
20638
|
+
process.exit(1);
|
|
20639
|
+
}
|
|
20640
|
+
if (staged.stdout.trim().length > 0) {
|
|
20641
|
+
return capDiff(staged.stdout, "staged");
|
|
20642
|
+
}
|
|
20643
|
+
const wt = runGit(["diff", "--no-color"]);
|
|
20644
|
+
if (wt.stdout.trim().length === 0) {
|
|
20645
|
+
return null;
|
|
20646
|
+
}
|
|
20647
|
+
return capDiff(wt.stdout, "working-tree");
|
|
20648
|
+
}
|
|
20649
|
+
function capDiff(raw, source) {
|
|
20650
|
+
if (raw.length <= DIFF_BYTE_CAP) {
|
|
20651
|
+
return { diff: raw, source, truncated: false };
|
|
20652
|
+
}
|
|
20653
|
+
const head = raw.slice(0, Math.floor(DIFF_BYTE_CAP * 0.7));
|
|
20654
|
+
const tail = raw.slice(-Math.floor(DIFF_BYTE_CAP * 0.3));
|
|
20655
|
+
return {
|
|
20656
|
+
diff: `${head}
|
|
20657
|
+
|
|
20658
|
+
[\u2026 ${raw.length - DIFF_BYTE_CAP} bytes of diff truncated \u2026]
|
|
20659
|
+
|
|
20660
|
+
${tail}`,
|
|
20661
|
+
source,
|
|
20662
|
+
truncated: true
|
|
20663
|
+
};
|
|
20664
|
+
}
|
|
20665
|
+
function readRecentCommits() {
|
|
20666
|
+
const r = runGit(["log", `-${LOG_COUNT}`, "--no-merges", "--format=%s%n%b%n---END---"]);
|
|
20667
|
+
if (r.status !== 0) {
|
|
20668
|
+
return "";
|
|
20669
|
+
}
|
|
20670
|
+
return r.stdout.trim();
|
|
20671
|
+
}
|
|
20672
|
+
async function draftMessage(client, model2, diff, recentCommits) {
|
|
20673
|
+
const userParts = [];
|
|
20674
|
+
if (recentCommits) {
|
|
20675
|
+
userParts.push(`Recent commits (style reference):
|
|
20676
|
+
|
|
20677
|
+
${recentCommits}`);
|
|
20678
|
+
}
|
|
20679
|
+
if (diff.source === "working-tree") {
|
|
20680
|
+
userParts.push(
|
|
20681
|
+
"(NOTE: diff is from the working tree, not the staging area \u2014 nothing is staged yet. The user will stage selectively after seeing the draft.)"
|
|
20682
|
+
);
|
|
20683
|
+
}
|
|
20684
|
+
userParts.push(`Diff to summarize:
|
|
20685
|
+
|
|
20686
|
+
${diff.diff}`);
|
|
20687
|
+
const resp = await client.chat({
|
|
20688
|
+
model: model2,
|
|
20689
|
+
messages: [
|
|
20690
|
+
{ role: "system", content: SYSTEM_PROMPT2 },
|
|
20691
|
+
{ role: "user", content: userParts.join("\n\n") }
|
|
20692
|
+
],
|
|
20693
|
+
temperature: 0.2
|
|
20694
|
+
});
|
|
20695
|
+
return stripCodeFences(resp.content.trim());
|
|
20696
|
+
}
|
|
20697
|
+
function stripCodeFences(s) {
|
|
20698
|
+
const trimmed = s.trim();
|
|
20699
|
+
const fenceOpen = /^```[a-zA-Z]*\n/;
|
|
20700
|
+
const fenceClose = /\n?```$/;
|
|
20701
|
+
if (fenceOpen.test(trimmed) && fenceClose.test(trimmed)) {
|
|
20702
|
+
return trimmed.replace(fenceOpen, "").replace(fenceClose, "").trim();
|
|
20703
|
+
}
|
|
20704
|
+
return trimmed;
|
|
20705
|
+
}
|
|
20706
|
+
function printDraft(message) {
|
|
20707
|
+
const sep3 = "\u2500".repeat(60);
|
|
20708
|
+
process.stdout.write(`
|
|
20709
|
+
${sep3}
|
|
20710
|
+
${message}
|
|
20711
|
+
${sep3}
|
|
20712
|
+
|
|
20713
|
+
`);
|
|
20714
|
+
}
|
|
20715
|
+
async function promptChoice() {
|
|
20716
|
+
const rl = createInterface({ input: stdin2, output: stdout });
|
|
20717
|
+
try {
|
|
20718
|
+
const answer = await rl.question("[a]ccept / [r]egenerate / [e]dit / [c]ancel: ");
|
|
20719
|
+
const k = answer.trim().toLowerCase();
|
|
20720
|
+
if (k === "" || k === "a" || k === "y" || k === "yes") return "accept";
|
|
20721
|
+
if (k === "r" || k === "regen" || k === "regenerate") return "regen";
|
|
20722
|
+
if (k === "e" || k === "edit") return "edit";
|
|
20723
|
+
return "cancel";
|
|
20724
|
+
} finally {
|
|
20725
|
+
rl.close();
|
|
20726
|
+
}
|
|
20727
|
+
}
|
|
20728
|
+
function editInExternal(initial) {
|
|
20729
|
+
const editor = process.env.GIT_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR;
|
|
20730
|
+
if (!editor) {
|
|
20731
|
+
process.stderr.write(
|
|
20732
|
+
"reasonix commit: no $EDITOR / $VISUAL / $GIT_EDITOR set \u2014 can't open editor. Pick [a]ccept and `git commit --amend` afterwards.\n"
|
|
20733
|
+
);
|
|
20734
|
+
return null;
|
|
20735
|
+
}
|
|
20736
|
+
const dir = mkdtempSync(join21(tmpdir(), "reasonix-commit-"));
|
|
20737
|
+
const path5 = join21(dir, "COMMIT_EDITMSG");
|
|
20738
|
+
writeFileSync13(path5, initial, "utf8");
|
|
20739
|
+
const result = spawnSync3(`${editor} "${path5}"`, {
|
|
20740
|
+
stdio: "inherit",
|
|
20741
|
+
shell: true
|
|
20742
|
+
});
|
|
20743
|
+
if (result.status !== 0) {
|
|
20744
|
+
try {
|
|
20745
|
+
unlinkSync6(path5);
|
|
20746
|
+
} catch {
|
|
20747
|
+
}
|
|
20748
|
+
process.stderr.write(
|
|
20749
|
+
`reasonix commit: editor exited ${result.status} \u2014 keeping prior draft.
|
|
20750
|
+
`
|
|
20751
|
+
);
|
|
20752
|
+
return null;
|
|
20753
|
+
}
|
|
20754
|
+
let edited;
|
|
20755
|
+
try {
|
|
20756
|
+
edited = readFileSync22(path5, "utf8");
|
|
20757
|
+
} catch {
|
|
20758
|
+
return null;
|
|
20759
|
+
} finally {
|
|
20760
|
+
try {
|
|
20761
|
+
unlinkSync6(path5);
|
|
20762
|
+
} catch {
|
|
20763
|
+
}
|
|
20764
|
+
}
|
|
20765
|
+
const cleaned = edited.split(/\r?\n/).filter((line) => !/^\s*#/.test(line)).join("\n").trim();
|
|
20766
|
+
return cleaned || null;
|
|
20767
|
+
}
|
|
20768
|
+
function commitWithMessage(message) {
|
|
20769
|
+
const child = spawn6("git", ["commit", "-F", "-"], {
|
|
20770
|
+
stdio: ["pipe", "inherit", "inherit"]
|
|
20771
|
+
});
|
|
20772
|
+
child.stdin.write(message);
|
|
20773
|
+
child.stdin.end();
|
|
20774
|
+
child.on("close", (code) => {
|
|
20775
|
+
if (code !== 0) {
|
|
20776
|
+
process.stderr.write(`reasonix commit: git commit exited ${code}.
|
|
20777
|
+
`);
|
|
20778
|
+
process.exit(code ?? 1);
|
|
20779
|
+
}
|
|
20780
|
+
});
|
|
20781
|
+
}
|
|
20782
|
+
async function commitCommand(opts = {}) {
|
|
20783
|
+
loadDotenv();
|
|
20784
|
+
dieIfNotGitRepo();
|
|
20785
|
+
const apiKey = loadApiKey() ?? process.env.DEEPSEEK_API_KEY;
|
|
20786
|
+
if (!apiKey) {
|
|
20787
|
+
process.stderr.write(
|
|
20788
|
+
"reasonix commit: DEEPSEEK_API_KEY not set. Run `reasonix setup` to save one, or export it.\n"
|
|
20789
|
+
);
|
|
20790
|
+
process.exit(1);
|
|
20791
|
+
}
|
|
20792
|
+
const diff = readDiff();
|
|
20793
|
+
if (!diff) {
|
|
20794
|
+
process.stderr.write(
|
|
20795
|
+
"reasonix commit: no staged changes and working tree is clean \u2014 nothing to commit.\n"
|
|
20796
|
+
);
|
|
20797
|
+
process.exit(1);
|
|
20798
|
+
}
|
|
20799
|
+
if (diff.source === "working-tree") {
|
|
20800
|
+
process.stderr.write(
|
|
20801
|
+
"reasonix commit: nothing staged \u2014 drafting from working-tree diff. Stage your changes and re-run, or use the draft as a starting point.\n"
|
|
20802
|
+
);
|
|
20803
|
+
}
|
|
20804
|
+
if (diff.truncated) {
|
|
20805
|
+
process.stderr.write(
|
|
20806
|
+
"reasonix commit: diff exceeded 80KB; head + tail sent to the model. Large diffs often produce vague drafts \u2014 consider committing in smaller chunks.\n"
|
|
20807
|
+
);
|
|
20808
|
+
}
|
|
20809
|
+
const client = new DeepSeekClient({ apiKey });
|
|
20810
|
+
const model2 = opts.model ?? DEFAULT_MODEL;
|
|
20811
|
+
const recentCommits = readRecentCommits();
|
|
20812
|
+
let message = "";
|
|
20813
|
+
let firstPass = true;
|
|
20814
|
+
while (true) {
|
|
20815
|
+
if (firstPass) {
|
|
20816
|
+
process.stdout.write("Drafting commit message\u2026\n");
|
|
20817
|
+
} else {
|
|
20818
|
+
process.stdout.write("Regenerating\u2026\n");
|
|
20819
|
+
}
|
|
20820
|
+
firstPass = false;
|
|
20821
|
+
try {
|
|
20822
|
+
message = await draftMessage(client, model2, diff, recentCommits);
|
|
20823
|
+
} catch (err) {
|
|
20824
|
+
process.stderr.write(`reasonix commit: model call failed \u2014 ${err.message}
|
|
20825
|
+
`);
|
|
20826
|
+
process.exit(1);
|
|
20827
|
+
}
|
|
20828
|
+
if (!message) {
|
|
20829
|
+
process.stderr.write("reasonix commit: model returned an empty draft. Try again.\n");
|
|
20830
|
+
process.exit(1);
|
|
20831
|
+
}
|
|
20832
|
+
printDraft(message);
|
|
20833
|
+
if (opts.yes) break;
|
|
20834
|
+
if (diff.source === "working-tree") {
|
|
20835
|
+
process.stdout.write(
|
|
20836
|
+
"(no staged changes \u2014 draft printed above for you to copy. Stage with `git add` and re-run to commit.)\n"
|
|
20837
|
+
);
|
|
20838
|
+
return;
|
|
20839
|
+
}
|
|
20840
|
+
const choice = await promptChoice();
|
|
20841
|
+
if (choice === "accept") break;
|
|
20842
|
+
if (choice === "cancel") {
|
|
20843
|
+
process.stderr.write("commit cancelled.\n");
|
|
20844
|
+
return;
|
|
20845
|
+
}
|
|
20846
|
+
if (choice === "edit") {
|
|
20847
|
+
const edited = editInExternal(message);
|
|
20848
|
+
if (edited) {
|
|
20849
|
+
message = edited;
|
|
20850
|
+
printDraft(message);
|
|
20851
|
+
const next = await promptChoice();
|
|
20852
|
+
if (next === "accept") break;
|
|
20853
|
+
if (next === "cancel") {
|
|
20854
|
+
process.stderr.write("commit cancelled.\n");
|
|
20855
|
+
return;
|
|
20856
|
+
}
|
|
20857
|
+
}
|
|
20858
|
+
}
|
|
20859
|
+
}
|
|
20860
|
+
commitWithMessage(message);
|
|
20861
|
+
}
|
|
20862
|
+
|
|
20589
20863
|
// src/cli/commands/diff.ts
|
|
20590
|
-
import { writeFileSync as
|
|
20864
|
+
import { writeFileSync as writeFileSync14 } from "fs";
|
|
20591
20865
|
import { basename as basename3 } from "path";
|
|
20592
20866
|
import { render as render2 } from "ink";
|
|
20593
20867
|
import React30 from "react";
|
|
@@ -20626,8 +20900,8 @@ function CacheBadge({ usage }) {
|
|
|
20626
20900
|
const total = hit + miss;
|
|
20627
20901
|
if (total === 0) return null;
|
|
20628
20902
|
const pct2 = hit / total * 100;
|
|
20629
|
-
const
|
|
20630
|
-
return /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React28.createElement(Text23, { color }, pct2.toFixed(1), "%"));
|
|
20903
|
+
const color2 = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
20904
|
+
return /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React28.createElement(Text23, { color: color2 }, pct2.toFixed(1), "%"));
|
|
20631
20905
|
}
|
|
20632
20906
|
function truncate2(s, max) {
|
|
20633
20907
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -20734,7 +21008,7 @@ async function diffCommand(opts) {
|
|
|
20734
21008
|
if (wantMarkdown) {
|
|
20735
21009
|
console.log(renderSummaryTable(report));
|
|
20736
21010
|
const md = renderMarkdown(report);
|
|
20737
|
-
|
|
21011
|
+
writeFileSync14(opts.mdPath, md, "utf8");
|
|
20738
21012
|
console.log(`
|
|
20739
21013
|
markdown report written to ${opts.mdPath}`);
|
|
20740
21014
|
return;
|
|
@@ -20750,12 +21024,317 @@ markdown report written to ${opts.mdPath}`);
|
|
|
20750
21024
|
console.log(renderSummaryTable(report));
|
|
20751
21025
|
}
|
|
20752
21026
|
|
|
21027
|
+
// src/cli/commands/doctor.ts
|
|
21028
|
+
import { existsSync as existsSync23, statSync as statSync14 } from "fs";
|
|
21029
|
+
import { homedir as homedir10 } from "os";
|
|
21030
|
+
import { dirname as dirname16, join as join22, resolve as resolve12 } from "path";
|
|
21031
|
+
var TTY = process.stdout.isTTY && process.env.TERM !== "dumb";
|
|
21032
|
+
function color(text, code) {
|
|
21033
|
+
if (!TTY) return text;
|
|
21034
|
+
return `\x1B[${code}m${text}\x1B[0m`;
|
|
21035
|
+
}
|
|
21036
|
+
function badge(level) {
|
|
21037
|
+
if (level === "ok") return color("\u2713", "32");
|
|
21038
|
+
if (level === "warn") return color("\u26A0", "33");
|
|
21039
|
+
return color("\u2717", "31");
|
|
21040
|
+
}
|
|
21041
|
+
function tail4(s) {
|
|
21042
|
+
return s.length <= 4 ? s : `\u2026${s.slice(-4)}`;
|
|
21043
|
+
}
|
|
21044
|
+
function fmtBytes(n) {
|
|
21045
|
+
if (n < 1024) return `${n} B`;
|
|
21046
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
21047
|
+
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
21048
|
+
}
|
|
21049
|
+
async function checkApiKey() {
|
|
21050
|
+
const fromEnv = process.env.DEEPSEEK_API_KEY;
|
|
21051
|
+
if (fromEnv) {
|
|
21052
|
+
return {
|
|
21053
|
+
label: "api key ",
|
|
21054
|
+
level: "ok",
|
|
21055
|
+
detail: `set via env DEEPSEEK_API_KEY (${tail4(fromEnv)})`
|
|
21056
|
+
};
|
|
21057
|
+
}
|
|
21058
|
+
try {
|
|
21059
|
+
const cfg = readConfig();
|
|
21060
|
+
if (cfg.apiKey) {
|
|
21061
|
+
return {
|
|
21062
|
+
label: "api key ",
|
|
21063
|
+
level: "ok",
|
|
21064
|
+
detail: `from ${defaultConfigPath()} (${tail4(cfg.apiKey)})`
|
|
21065
|
+
};
|
|
21066
|
+
}
|
|
21067
|
+
} catch {
|
|
21068
|
+
}
|
|
21069
|
+
return {
|
|
21070
|
+
label: "api key ",
|
|
21071
|
+
level: "fail",
|
|
21072
|
+
detail: "not set \u2014 `reasonix setup` to save one, or export DEEPSEEK_API_KEY. Get a key at https://platform.deepseek.com/api_keys"
|
|
21073
|
+
};
|
|
21074
|
+
}
|
|
21075
|
+
async function checkConfig() {
|
|
21076
|
+
const path5 = defaultConfigPath();
|
|
21077
|
+
if (!existsSync23(path5)) {
|
|
21078
|
+
return {
|
|
21079
|
+
label: "config ",
|
|
21080
|
+
level: "warn",
|
|
21081
|
+
detail: "missing \u2014 running with library defaults. `reasonix setup` writes one."
|
|
21082
|
+
};
|
|
21083
|
+
}
|
|
21084
|
+
try {
|
|
21085
|
+
const cfg = readConfig(path5);
|
|
21086
|
+
const parts = [];
|
|
21087
|
+
if (cfg.preset) parts.push(`preset=${cfg.preset}`);
|
|
21088
|
+
if (cfg.editMode) parts.push(`editMode=${cfg.editMode}`);
|
|
21089
|
+
if (cfg.mcp && cfg.mcp.length > 0) parts.push(`mcp=${cfg.mcp.length}`);
|
|
21090
|
+
return {
|
|
21091
|
+
label: "config ",
|
|
21092
|
+
level: "ok",
|
|
21093
|
+
detail: `${path5}${parts.length ? ` (${parts.join(", ")})` : ""}`
|
|
21094
|
+
};
|
|
21095
|
+
} catch (err) {
|
|
21096
|
+
return {
|
|
21097
|
+
label: "config ",
|
|
21098
|
+
level: "fail",
|
|
21099
|
+
detail: `${path5} unreadable \u2014 ${err.message}`
|
|
21100
|
+
};
|
|
21101
|
+
}
|
|
21102
|
+
}
|
|
21103
|
+
async function checkApiReach() {
|
|
21104
|
+
const key = process.env.DEEPSEEK_API_KEY ?? readConfig().apiKey;
|
|
21105
|
+
if (!key) {
|
|
21106
|
+
return {
|
|
21107
|
+
label: "api reach ",
|
|
21108
|
+
level: "warn",
|
|
21109
|
+
detail: "skipped \u2014 no api key to test with"
|
|
21110
|
+
};
|
|
21111
|
+
}
|
|
21112
|
+
try {
|
|
21113
|
+
const client = new DeepSeekClient({ apiKey: key });
|
|
21114
|
+
const ctl = new AbortController();
|
|
21115
|
+
const timer = setTimeout(() => ctl.abort(), 8e3);
|
|
21116
|
+
let balance;
|
|
21117
|
+
try {
|
|
21118
|
+
balance = await client.getBalance({ signal: ctl.signal });
|
|
21119
|
+
} finally {
|
|
21120
|
+
clearTimeout(timer);
|
|
21121
|
+
}
|
|
21122
|
+
if (!balance) {
|
|
21123
|
+
return {
|
|
21124
|
+
label: "api reach ",
|
|
21125
|
+
level: "fail",
|
|
21126
|
+
detail: "/user/balance returned null \u2014 auth failed or network blocked"
|
|
21127
|
+
};
|
|
21128
|
+
}
|
|
21129
|
+
if (!balance.is_available) {
|
|
21130
|
+
const info2 = balance.balance_infos[0];
|
|
21131
|
+
return {
|
|
21132
|
+
label: "api reach ",
|
|
21133
|
+
level: "warn",
|
|
21134
|
+
detail: `account flagged not-available${info2 ? ` (${info2.total_balance} ${info2.currency})` : ""} \u2014 top up or check your dashboard`
|
|
21135
|
+
};
|
|
21136
|
+
}
|
|
21137
|
+
const info = balance.balance_infos[0];
|
|
21138
|
+
return {
|
|
21139
|
+
label: "api reach ",
|
|
21140
|
+
level: "ok",
|
|
21141
|
+
detail: info ? `/user/balance ok \u2014 ${info.total_balance} ${info.currency}` : "/user/balance ok"
|
|
21142
|
+
};
|
|
21143
|
+
} catch (err) {
|
|
21144
|
+
return {
|
|
21145
|
+
label: "api reach ",
|
|
21146
|
+
level: "fail",
|
|
21147
|
+
detail: `${err.message}`
|
|
21148
|
+
};
|
|
21149
|
+
}
|
|
21150
|
+
}
|
|
21151
|
+
async function checkTokenizer() {
|
|
21152
|
+
const candidates = [
|
|
21153
|
+
join22(
|
|
21154
|
+
dirname16(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")),
|
|
21155
|
+
"..",
|
|
21156
|
+
"..",
|
|
21157
|
+
"..",
|
|
21158
|
+
"data",
|
|
21159
|
+
"deepseek-tokenizer.json.gz"
|
|
21160
|
+
),
|
|
21161
|
+
join22(process.cwd(), "data", "deepseek-tokenizer.json.gz")
|
|
21162
|
+
];
|
|
21163
|
+
for (const p of candidates) {
|
|
21164
|
+
if (existsSync23(p)) {
|
|
21165
|
+
try {
|
|
21166
|
+
const stat2 = statSync14(p);
|
|
21167
|
+
return {
|
|
21168
|
+
label: "tokenizer ",
|
|
21169
|
+
level: "ok",
|
|
21170
|
+
detail: `${p} (${fmtBytes(stat2.size)})`
|
|
21171
|
+
};
|
|
21172
|
+
} catch {
|
|
21173
|
+
}
|
|
21174
|
+
}
|
|
21175
|
+
}
|
|
21176
|
+
return {
|
|
21177
|
+
label: "tokenizer ",
|
|
21178
|
+
level: "warn",
|
|
21179
|
+
detail: "data/deepseek-tokenizer.json.gz not found \u2014 token counts will fall back to char heuristics"
|
|
21180
|
+
};
|
|
21181
|
+
}
|
|
21182
|
+
async function checkSessions() {
|
|
21183
|
+
try {
|
|
21184
|
+
const list = listSessions();
|
|
21185
|
+
if (list.length === 0) {
|
|
21186
|
+
return {
|
|
21187
|
+
label: "sessions ",
|
|
21188
|
+
level: "ok",
|
|
21189
|
+
detail: "0 saved"
|
|
21190
|
+
};
|
|
21191
|
+
}
|
|
21192
|
+
const totalBytes = list.reduce((s, e) => s + e.size, 0);
|
|
21193
|
+
const oldest = list[list.length - 1];
|
|
21194
|
+
const ageDays = Math.floor((Date.now() - oldest.mtime.getTime()) / (24 * 60 * 60 * 1e3));
|
|
21195
|
+
const stale = list.filter(
|
|
21196
|
+
(e) => Date.now() - e.mtime.getTime() >= 90 * 24 * 60 * 60 * 1e3
|
|
21197
|
+
).length;
|
|
21198
|
+
const detail = `${list.length} saved \xB7 ${fmtBytes(totalBytes)} \xB7 oldest ${ageDays}d`;
|
|
21199
|
+
if (stale > 0) {
|
|
21200
|
+
return {
|
|
21201
|
+
label: "sessions ",
|
|
21202
|
+
level: "warn",
|
|
21203
|
+
detail: `${detail} \xB7 ${stale} idle \u226590d (run /prune-sessions)`
|
|
21204
|
+
};
|
|
21205
|
+
}
|
|
21206
|
+
return { label: "sessions ", level: "ok", detail };
|
|
21207
|
+
} catch (err) {
|
|
21208
|
+
return {
|
|
21209
|
+
label: "sessions ",
|
|
21210
|
+
level: "warn",
|
|
21211
|
+
detail: `cannot list \u2014 ${err.message}`
|
|
21212
|
+
};
|
|
21213
|
+
}
|
|
21214
|
+
}
|
|
21215
|
+
async function checkHooks(projectRoot) {
|
|
21216
|
+
try {
|
|
21217
|
+
const all = loadHooks({ projectRoot });
|
|
21218
|
+
const global = all.filter((h) => h.scope === "global").length;
|
|
21219
|
+
const project = all.filter((h) => h.scope === "project").length;
|
|
21220
|
+
return {
|
|
21221
|
+
label: "hooks ",
|
|
21222
|
+
level: "ok",
|
|
21223
|
+
detail: `${global} global, ${project} project`
|
|
21224
|
+
};
|
|
21225
|
+
} catch (err) {
|
|
21226
|
+
return {
|
|
21227
|
+
label: "hooks ",
|
|
21228
|
+
level: "warn",
|
|
21229
|
+
detail: `couldn't parse settings.json \u2014 ${err.message}`
|
|
21230
|
+
};
|
|
21231
|
+
}
|
|
21232
|
+
}
|
|
21233
|
+
async function checkOllama(projectRoot) {
|
|
21234
|
+
let exists = false;
|
|
21235
|
+
try {
|
|
21236
|
+
exists = await indexExists(projectRoot);
|
|
21237
|
+
} catch {
|
|
21238
|
+
}
|
|
21239
|
+
if (!exists) {
|
|
21240
|
+
return {
|
|
21241
|
+
label: "ollama ",
|
|
21242
|
+
level: "ok",
|
|
21243
|
+
detail: "not in use (no semantic index built; `reasonix index` to enable)"
|
|
21244
|
+
};
|
|
21245
|
+
}
|
|
21246
|
+
try {
|
|
21247
|
+
const status2 = await checkOllamaStatus(process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text");
|
|
21248
|
+
if (!status2.binaryFound) {
|
|
21249
|
+
return {
|
|
21250
|
+
label: "ollama ",
|
|
21251
|
+
level: "warn",
|
|
21252
|
+
detail: "binary not on PATH \u2014 semantic_search will fail; install from https://ollama.com"
|
|
21253
|
+
};
|
|
21254
|
+
}
|
|
21255
|
+
if (!status2.daemonRunning) {
|
|
21256
|
+
return {
|
|
21257
|
+
label: "ollama ",
|
|
21258
|
+
level: "warn",
|
|
21259
|
+
detail: "daemon not running \u2014 `ollama serve` (or just call /semantic in TUI to auto-start)"
|
|
21260
|
+
};
|
|
21261
|
+
}
|
|
21262
|
+
if (!status2.modelPulled) {
|
|
21263
|
+
return {
|
|
21264
|
+
label: "ollama ",
|
|
21265
|
+
level: "warn",
|
|
21266
|
+
detail: `model ${status2.modelName} not pulled \u2014 \`ollama pull ${status2.modelName}\``
|
|
21267
|
+
};
|
|
21268
|
+
}
|
|
21269
|
+
return {
|
|
21270
|
+
label: "ollama ",
|
|
21271
|
+
level: "ok",
|
|
21272
|
+
detail: `daemon up \xB7 model ${status2.modelName} ready`
|
|
21273
|
+
};
|
|
21274
|
+
} catch (err) {
|
|
21275
|
+
return {
|
|
21276
|
+
label: "ollama ",
|
|
21277
|
+
level: "warn",
|
|
21278
|
+
detail: `probe failed \u2014 ${err.message}`
|
|
21279
|
+
};
|
|
21280
|
+
}
|
|
21281
|
+
}
|
|
21282
|
+
async function checkProject(projectRoot) {
|
|
21283
|
+
const markers = [".git", "REASONIX.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
|
|
21284
|
+
const found = markers.filter((m) => existsSync23(join22(projectRoot, m)));
|
|
21285
|
+
if (found.length === 0) {
|
|
21286
|
+
return {
|
|
21287
|
+
label: "project ",
|
|
21288
|
+
level: "warn",
|
|
21289
|
+
detail: `${projectRoot} has none of: ${markers.slice(0, 3).join(", ")} \u2026 \u2014 \`reasonix code\` will still run, but @-mentions and project memory have nothing to anchor`
|
|
21290
|
+
};
|
|
21291
|
+
}
|
|
21292
|
+
return {
|
|
21293
|
+
label: "project ",
|
|
21294
|
+
level: "ok",
|
|
21295
|
+
detail: `${projectRoot} (${found.join(", ")})`
|
|
21296
|
+
};
|
|
21297
|
+
}
|
|
21298
|
+
async function doctorCommand() {
|
|
21299
|
+
loadDotenv();
|
|
21300
|
+
const projectRoot = resolve12(process.cwd());
|
|
21301
|
+
console.log(`${color(`reasonix ${VERSION} \xB7 doctor`, "1")} (cwd: ${projectRoot})`);
|
|
21302
|
+
console.log(` home: ${homedir10()}`);
|
|
21303
|
+
console.log("");
|
|
21304
|
+
const checks = await Promise.all([
|
|
21305
|
+
checkApiKey(),
|
|
21306
|
+
checkConfig(),
|
|
21307
|
+
checkApiReach(),
|
|
21308
|
+
checkTokenizer(),
|
|
21309
|
+
checkSessions(),
|
|
21310
|
+
checkHooks(projectRoot),
|
|
21311
|
+
checkOllama(projectRoot),
|
|
21312
|
+
checkProject(projectRoot)
|
|
21313
|
+
]);
|
|
21314
|
+
for (const c of checks) {
|
|
21315
|
+
console.log(` ${badge(c.level)} ${c.label} ${c.detail}`);
|
|
21316
|
+
}
|
|
21317
|
+
const ok = checks.filter((c) => c.level === "ok").length;
|
|
21318
|
+
const warn = checks.filter((c) => c.level === "warn").length;
|
|
21319
|
+
const fail = checks.filter((c) => c.level === "fail").length;
|
|
21320
|
+
console.log("");
|
|
21321
|
+
const summary = `${ok} ok \xB7 ${warn} warn \xB7 ${fail} fail`;
|
|
21322
|
+
if (fail > 0) {
|
|
21323
|
+
console.log(color(summary, "31"));
|
|
21324
|
+
process.exit(1);
|
|
21325
|
+
} else if (warn > 0) {
|
|
21326
|
+
console.log(color(summary, "33"));
|
|
21327
|
+
} else {
|
|
21328
|
+
console.log(color(summary, "32"));
|
|
21329
|
+
}
|
|
21330
|
+
}
|
|
21331
|
+
|
|
20753
21332
|
// src/cli/commands/index.ts
|
|
20754
|
-
import { resolve as
|
|
21333
|
+
import { resolve as resolve13 } from "path";
|
|
20755
21334
|
|
|
20756
21335
|
// src/index/semantic/preflight.ts
|
|
20757
|
-
import { stdin as
|
|
20758
|
-
import { createInterface } from "readline/promises";
|
|
21336
|
+
import { stdin as stdin3, stdout as stdout2 } from "process";
|
|
21337
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
20759
21338
|
async function ollamaPreflight(opts) {
|
|
20760
21339
|
const log = opts.log ?? ((line) => process.stderr.write(line));
|
|
20761
21340
|
const status2 = await checkOllamaStatus(opts.model, opts.baseUrl);
|
|
@@ -20813,7 +21392,7 @@ async function ollamaPreflight(opts) {
|
|
|
20813
21392
|
}
|
|
20814
21393
|
async function confirm(question, defaultYes) {
|
|
20815
21394
|
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
20816
|
-
const rl =
|
|
21395
|
+
const rl = createInterface2({ input: stdin3, output: stdout2 });
|
|
20817
21396
|
try {
|
|
20818
21397
|
const raw = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
20819
21398
|
if (raw === "") return defaultYes;
|
|
@@ -20825,7 +21404,7 @@ async function confirm(question, defaultYes) {
|
|
|
20825
21404
|
|
|
20826
21405
|
// src/cli/commands/index.ts
|
|
20827
21406
|
async function indexCommand(opts = {}) {
|
|
20828
|
-
const root =
|
|
21407
|
+
const root = resolve13(opts.dir ?? process.cwd());
|
|
20829
21408
|
const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;
|
|
20830
21409
|
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
20831
21410
|
const preflightOk = await ollamaPreflight({
|
|
@@ -21232,12 +21811,12 @@ function oneLine2(s, max = 200) {
|
|
|
21232
21811
|
}
|
|
21233
21812
|
|
|
21234
21813
|
// src/cli/commands/run.ts
|
|
21235
|
-
import { stdin as
|
|
21236
|
-
import { createInterface as
|
|
21814
|
+
import { stdin as stdin4, stdout as stdout3 } from "process";
|
|
21815
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
21237
21816
|
async function ensureApiKey() {
|
|
21238
21817
|
const existing = loadApiKey();
|
|
21239
21818
|
if (existing) return existing;
|
|
21240
|
-
if (!
|
|
21819
|
+
if (!stdin4.isTTY) {
|
|
21241
21820
|
process.stderr.write(
|
|
21242
21821
|
"DEEPSEEK_API_KEY is not set and stdin is not a TTY (cannot prompt).\nSet the env var, or run `reasonix chat` once interactively to save a key.\n"
|
|
21243
21822
|
);
|
|
@@ -21246,7 +21825,7 @@ async function ensureApiKey() {
|
|
|
21246
21825
|
process.stdout.write(
|
|
21247
21826
|
"DeepSeek API key not configured.\nGet one at https://platform.deepseek.com/api_keys\n"
|
|
21248
21827
|
);
|
|
21249
|
-
const rl =
|
|
21828
|
+
const rl = createInterface3({ input: stdin4, output: stdout3 });
|
|
21250
21829
|
try {
|
|
21251
21830
|
while (true) {
|
|
21252
21831
|
const answer = (await rl.question("API key \u203A ")).trim();
|
|
@@ -21717,7 +22296,7 @@ async function setupCommand(_opts = {}) {
|
|
|
21717
22296
|
}
|
|
21718
22297
|
|
|
21719
22298
|
// src/cli/commands/update.ts
|
|
21720
|
-
import { spawn as
|
|
22299
|
+
import { spawn as spawn7 } from "child_process";
|
|
21721
22300
|
function planUpdate(input) {
|
|
21722
22301
|
const diff = compareVersions(input.current, input.latest);
|
|
21723
22302
|
if (diff > 0) {
|
|
@@ -21747,13 +22326,13 @@ function planUpdate(input) {
|
|
|
21747
22326
|
};
|
|
21748
22327
|
}
|
|
21749
22328
|
function defaultSpawn(argv) {
|
|
21750
|
-
return new Promise((
|
|
21751
|
-
const child =
|
|
22329
|
+
return new Promise((resolve14, reject) => {
|
|
22330
|
+
const child = spawn7(argv[0], argv.slice(1), {
|
|
21752
22331
|
stdio: "inherit",
|
|
21753
22332
|
shell: process.platform === "win32"
|
|
21754
22333
|
});
|
|
21755
22334
|
child.once("error", reject);
|
|
21756
|
-
child.once("exit", (code) =>
|
|
22335
|
+
child.once("exit", (code) => resolve14(code ?? 1));
|
|
21757
22336
|
});
|
|
21758
22337
|
}
|
|
21759
22338
|
async function updateCommand(opts = {}) {
|
|
@@ -22038,6 +22617,19 @@ program.command("stats [transcript]").description(
|
|
|
22038
22617
|
).action((transcript) => {
|
|
22039
22618
|
statsCommand({ transcript });
|
|
22040
22619
|
});
|
|
22620
|
+
program.command("doctor").description(
|
|
22621
|
+
"One-command health check \u2014 API key, config, /user/balance reachability, tokenizer, sessions, hooks, Ollama (if used), project markers. Exit 1 on any fail; 0 on warn / clean."
|
|
22622
|
+
).action(async () => {
|
|
22623
|
+
await doctorCommand();
|
|
22624
|
+
});
|
|
22625
|
+
program.command("commit").description(
|
|
22626
|
+
"Draft a commit message from the staged diff (or working tree, if nothing staged), matching your repo's recent commit style. Review interactively before it lands."
|
|
22627
|
+
).option("-m, --model <id>", "Override the default model (deepseek-v4-flash)").option(
|
|
22628
|
+
"-y, --yes",
|
|
22629
|
+
"Skip the [a]ccept / [r]egenerate prompt and commit the first draft. Useful in scripts."
|
|
22630
|
+
).action(async (opts) => {
|
|
22631
|
+
await commitCommand({ model: opts.model, yes: !!opts.yes });
|
|
22632
|
+
});
|
|
22041
22633
|
program.command("sessions [name]").description("List saved chat sessions, or inspect one by name.").option("-v, --verbose", "Include system prompts + tool-call metadata when inspecting").action((name, opts) => {
|
|
22042
22634
|
sessionsCommand({ name, verbose: !!opts.verbose });
|
|
22043
22635
|
});
|