reasonix 0.4.14 → 0.4.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/cli/index.js +393 -83
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +101 -2
- package/dist/index.js +188 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -32,6 +32,13 @@ function loadApiKey(path = defaultConfigPath()) {
|
|
|
32
32
|
if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
|
|
33
33
|
return readConfig(path).apiKey;
|
|
34
34
|
}
|
|
35
|
+
function searchEnabled(path = defaultConfigPath()) {
|
|
36
|
+
const env = process.env.REASONIX_SEARCH;
|
|
37
|
+
if (env === "off" || env === "false" || env === "0") return false;
|
|
38
|
+
const cfg = readConfig(path).search;
|
|
39
|
+
if (cfg === false) return false;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
35
42
|
function saveApiKey(key, path = defaultConfigPath()) {
|
|
36
43
|
const cfg = readConfig(path);
|
|
37
44
|
cfg.apiKey = key.trim();
|
|
@@ -2232,6 +2239,187 @@ function lineDiff(a, b) {
|
|
|
2232
2239
|
return out;
|
|
2233
2240
|
}
|
|
2234
2241
|
|
|
2242
|
+
// src/tools/web.ts
|
|
2243
|
+
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
2244
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
2245
|
+
var DEFAULT_TOPK = 5;
|
|
2246
|
+
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
2247
|
+
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
2248
|
+
async function webSearch(query, opts = {}) {
|
|
2249
|
+
const topK = Math.max(1, Math.min(10, opts.topK ?? DEFAULT_TOPK));
|
|
2250
|
+
const resp = await fetch(`${MOJEEK_ENDPOINT}?q=${encodeURIComponent(query)}`, {
|
|
2251
|
+
headers: {
|
|
2252
|
+
"User-Agent": USER_AGENT,
|
|
2253
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9",
|
|
2254
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
2255
|
+
},
|
|
2256
|
+
signal: opts.signal,
|
|
2257
|
+
redirect: "follow"
|
|
2258
|
+
});
|
|
2259
|
+
if (!resp.ok) throw new Error(`web_search ${resp.status}`);
|
|
2260
|
+
const html = await resp.text();
|
|
2261
|
+
const results = parseMojeekResults(html).slice(0, topK);
|
|
2262
|
+
if (results.length === 0) {
|
|
2263
|
+
if (/no results found|did not match any documents/i.test(html)) return [];
|
|
2264
|
+
if (/captcha|verify you are human|access denied|forbidden/i.test(html)) {
|
|
2265
|
+
throw new Error("web_search: Mojeek anti-bot page \u2014 rate-limited or blocked");
|
|
2266
|
+
}
|
|
2267
|
+
throw new Error(
|
|
2268
|
+
`web_search: 0 results but response doesn't look like a real empty page (${html.length} chars, first 120: ${html.slice(0, 120).replace(/\s+/g, " ")})`
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
return results;
|
|
2272
|
+
}
|
|
2273
|
+
function parseMojeekResults(html) {
|
|
2274
|
+
const titles = [];
|
|
2275
|
+
const titleAnchorRe = /<a\b[^>]*\bclass="title"[^>]*>[\s\S]*?<\/a>/g;
|
|
2276
|
+
let m;
|
|
2277
|
+
while (true) {
|
|
2278
|
+
m = titleAnchorRe.exec(html);
|
|
2279
|
+
if (m === null) break;
|
|
2280
|
+
titles.push(m[0]);
|
|
2281
|
+
}
|
|
2282
|
+
const snippets = [];
|
|
2283
|
+
const snippetRe = /<p\b[^>]*\bclass="s"[^>]*>([\s\S]*?)<\/p>/g;
|
|
2284
|
+
while (true) {
|
|
2285
|
+
m = snippetRe.exec(html);
|
|
2286
|
+
if (m === null) break;
|
|
2287
|
+
snippets.push(m[1] ?? "");
|
|
2288
|
+
}
|
|
2289
|
+
const hrefRe = /href="([^"]+)"/;
|
|
2290
|
+
const innerRe = /<a\b[^>]*>([\s\S]*?)<\/a>/;
|
|
2291
|
+
const results = [];
|
|
2292
|
+
for (let i = 0; i < titles.length; i++) {
|
|
2293
|
+
const anchor = titles[i];
|
|
2294
|
+
const hrefMatch = anchor.match(hrefRe);
|
|
2295
|
+
const innerMatch = anchor.match(innerRe);
|
|
2296
|
+
if (!hrefMatch?.[1]) continue;
|
|
2297
|
+
results.push({
|
|
2298
|
+
title: decodeHtmlEntities(stripHtml(innerMatch?.[1] ?? "")).trim(),
|
|
2299
|
+
url: hrefMatch[1],
|
|
2300
|
+
snippet: decodeHtmlEntities(stripHtml(snippets[i] ?? "")).replace(/\s+/g, " ").trim()
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
return results;
|
|
2304
|
+
}
|
|
2305
|
+
async function webFetch(url, opts = {}) {
|
|
2306
|
+
const maxChars = opts.maxChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
2307
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
2308
|
+
const ctl = new AbortController();
|
|
2309
|
+
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
2310
|
+
const cancel = () => ctl.abort();
|
|
2311
|
+
opts.signal?.addEventListener("abort", cancel, { once: true });
|
|
2312
|
+
let resp;
|
|
2313
|
+
try {
|
|
2314
|
+
resp = await fetch(url, {
|
|
2315
|
+
headers: { "User-Agent": USER_AGENT, Accept: "text/html,text/plain,*/*" },
|
|
2316
|
+
signal: ctl.signal,
|
|
2317
|
+
redirect: "follow"
|
|
2318
|
+
});
|
|
2319
|
+
} finally {
|
|
2320
|
+
clearTimeout(timer);
|
|
2321
|
+
opts.signal?.removeEventListener("abort", cancel);
|
|
2322
|
+
}
|
|
2323
|
+
if (!resp.ok) throw new Error(`web_fetch ${resp.status} for ${url}`);
|
|
2324
|
+
const contentType = resp.headers.get("content-type") ?? "";
|
|
2325
|
+
const raw = await resp.text();
|
|
2326
|
+
const title = extractTitle(raw);
|
|
2327
|
+
const text = contentType.includes("text/html") ? htmlToText(raw) : raw;
|
|
2328
|
+
const truncated = text.length > maxChars;
|
|
2329
|
+
const finalText = truncated ? `${text.slice(0, maxChars)}
|
|
2330
|
+
|
|
2331
|
+
[\u2026 truncated ${text.length - maxChars} chars \u2026]` : text;
|
|
2332
|
+
return { url, title, text: finalText, truncated };
|
|
2333
|
+
}
|
|
2334
|
+
function htmlToText(html) {
|
|
2335
|
+
let s = html;
|
|
2336
|
+
s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
|
|
2337
|
+
s = s.replace(/<style[\s\S]*?<\/style>/gi, "");
|
|
2338
|
+
s = s.replace(/<noscript[\s\S]*?<\/noscript>/gi, "");
|
|
2339
|
+
s = s.replace(/<nav[\s\S]*?<\/nav>/gi, "");
|
|
2340
|
+
s = s.replace(/<footer[\s\S]*?<\/footer>/gi, "");
|
|
2341
|
+
s = s.replace(/<aside[\s\S]*?<\/aside>/gi, "");
|
|
2342
|
+
s = s.replace(/<svg[\s\S]*?<\/svg>/gi, "");
|
|
2343
|
+
s = s.replace(/<\/?(p|div|br|h[1-6]|li|tr|section|article)\b[^>]*>/gi, "\n");
|
|
2344
|
+
s = s.replace(/<[^>]+>/g, "");
|
|
2345
|
+
s = decodeHtmlEntities(s);
|
|
2346
|
+
s = s.replace(/[ \t]+/g, " ");
|
|
2347
|
+
s = s.replace(/\n[ \t]+/g, "\n");
|
|
2348
|
+
s = s.replace(/\n{3,}/g, "\n\n");
|
|
2349
|
+
return s.trim();
|
|
2350
|
+
}
|
|
2351
|
+
function stripHtml(s) {
|
|
2352
|
+
return s.replace(/<[^>]+>/g, "");
|
|
2353
|
+
}
|
|
2354
|
+
function decodeHtmlEntities(s) {
|
|
2355
|
+
return s.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
2356
|
+
}
|
|
2357
|
+
function extractTitle(html) {
|
|
2358
|
+
const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
2359
|
+
if (!m?.[1]) return void 0;
|
|
2360
|
+
return m[1].replace(/\s+/g, " ").trim() || void 0;
|
|
2361
|
+
}
|
|
2362
|
+
function registerWebTools(registry, opts = {}) {
|
|
2363
|
+
const defaultTopK = opts.defaultTopK ?? DEFAULT_TOPK;
|
|
2364
|
+
const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
2365
|
+
registry.register({
|
|
2366
|
+
name: "web_search",
|
|
2367
|
+
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.",
|
|
2368
|
+
parameters: {
|
|
2369
|
+
type: "object",
|
|
2370
|
+
properties: {
|
|
2371
|
+
query: { type: "string", description: "Natural-language search query." },
|
|
2372
|
+
topK: {
|
|
2373
|
+
type: "integer",
|
|
2374
|
+
description: `Number of results to return (1..10). Default ${defaultTopK}.`
|
|
2375
|
+
}
|
|
2376
|
+
},
|
|
2377
|
+
required: ["query"]
|
|
2378
|
+
},
|
|
2379
|
+
fn: async (args, ctx) => {
|
|
2380
|
+
const results = await webSearch(args.query, {
|
|
2381
|
+
topK: args.topK ?? defaultTopK,
|
|
2382
|
+
signal: ctx?.signal
|
|
2383
|
+
});
|
|
2384
|
+
return formatSearchResults(args.query, results);
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
registry.register({
|
|
2388
|
+
name: "web_fetch",
|
|
2389
|
+
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.",
|
|
2390
|
+
parameters: {
|
|
2391
|
+
type: "object",
|
|
2392
|
+
properties: {
|
|
2393
|
+
url: { type: "string", description: "Absolute http:// or https:// URL." }
|
|
2394
|
+
},
|
|
2395
|
+
required: ["url"]
|
|
2396
|
+
},
|
|
2397
|
+
fn: async (args, ctx) => {
|
|
2398
|
+
if (!/^https?:\/\//i.test(args.url)) {
|
|
2399
|
+
throw new Error("web_fetch: url must start with http:// or https://");
|
|
2400
|
+
}
|
|
2401
|
+
const page = await webFetch(args.url, { maxChars: maxFetchChars, signal: ctx?.signal });
|
|
2402
|
+
const header = page.title ? `${page.title}
|
|
2403
|
+
${page.url}` : page.url;
|
|
2404
|
+
return `${header}
|
|
2405
|
+
|
|
2406
|
+
${page.text}`;
|
|
2407
|
+
}
|
|
2408
|
+
});
|
|
2409
|
+
return registry;
|
|
2410
|
+
}
|
|
2411
|
+
function formatSearchResults(query, results) {
|
|
2412
|
+
const lines = [`query: ${query}`, `
|
|
2413
|
+
results (${results.length}):`];
|
|
2414
|
+
results.forEach((r, i) => {
|
|
2415
|
+
lines.push(`
|
|
2416
|
+
${i + 1}. ${r.title}`);
|
|
2417
|
+
lines.push(` ${r.url}`);
|
|
2418
|
+
if (r.snippet) lines.push(` ${r.snippet}`);
|
|
2419
|
+
});
|
|
2420
|
+
return lines.join("\n");
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2235
2423
|
// src/env.ts
|
|
2236
2424
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2237
2425
|
import { resolve as resolve2 } from "path";
|
|
@@ -3519,15 +3707,15 @@ function sep() {
|
|
|
3519
3707
|
}
|
|
3520
3708
|
|
|
3521
3709
|
// src/index.ts
|
|
3522
|
-
var VERSION = "0.4.
|
|
3710
|
+
var VERSION = "0.4.15";
|
|
3523
3711
|
|
|
3524
3712
|
// src/cli/commands/chat.tsx
|
|
3525
3713
|
import { render } from "ink";
|
|
3526
|
-
import React10, { useState as
|
|
3714
|
+
import React10, { useState as useState5 } from "react";
|
|
3527
3715
|
|
|
3528
3716
|
// src/cli/ui/App.tsx
|
|
3529
3717
|
import { Box as Box7, Static, Text as Text7, useApp, useInput as useInput2 } from "ink";
|
|
3530
|
-
import React8, { useCallback, useEffect as useEffect2, useMemo, useRef, useState as
|
|
3718
|
+
import React8, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState3 } from "react";
|
|
3531
3719
|
|
|
3532
3720
|
// src/cli/ui/EventLog.tsx
|
|
3533
3721
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -3960,7 +4148,7 @@ function StreamingAssistant({ event }) {
|
|
|
3960
4148
|
label = parts.join(" \xB7 ");
|
|
3961
4149
|
labelColor = "green";
|
|
3962
4150
|
}
|
|
3963
|
-
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React4.createElement(Pulse, null), /* @__PURE__ */ React4.createElement(Text3, { color: labelColor }, ` ${label} `), /* @__PURE__ */ React4.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "\u25B8 ", tail) : reasoningOnly ? /* @__PURE__ */ React4.createElement(Text3, { color: "yellow", dimColor: true }, " R1 is thinking before it speaks \u2014 body text starts when reasoning completes (typically 20-90s).") : /* @__PURE__ */ React4.createElement(Text3, { dimColor: true,
|
|
4151
|
+
return /* @__PURE__ */ React4.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Box3, null, /* @__PURE__ */ React4.createElement(Text3, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React4.createElement(Pulse, null), /* @__PURE__ */ React4.createElement(Text3, { color: labelColor }, ` ${label} `), /* @__PURE__ */ React4.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true }, "\u25B8 ", tail) : preFirstByte ? /* @__PURE__ */ React4.createElement(Text3, { dimColor: true, italic: true }, " connection open, first byte typically in 5-60s depending on model + load") : reasoningOnly ? /* @__PURE__ */ React4.createElement(Text3, { color: "yellow", dimColor: true }, " R1 is thinking before it speaks \u2014 body text starts when reasoning completes (typically 20-90s).") : toolCallOnly ? /* @__PURE__ */ React4.createElement(Text3, { color: "magenta", dimColor: true }, " tool-call arguments streaming \u2014 the model is about to dispatch a tool") : event.reasoning ? /* @__PURE__ */ React4.createElement(Text3, { color: "yellow", dimColor: true }, " R1 still reasoning \u2014 body text or tool call arrives when thinking completes") : null);
|
|
3964
4152
|
}
|
|
3965
4153
|
function Pulse() {
|
|
3966
4154
|
const tick = useTick();
|
|
@@ -3982,40 +4170,119 @@ function truncate2(s, max) {
|
|
|
3982
4170
|
|
|
3983
4171
|
// src/cli/ui/PromptInput.tsx
|
|
3984
4172
|
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
3985
|
-
import React5 from "react";
|
|
4173
|
+
import React5, { useRef, useState as useState2 } from "react";
|
|
3986
4174
|
|
|
3987
4175
|
// src/cli/ui/multiline-keys.ts
|
|
3988
4176
|
var BACKSLASH_SUFFIX = /\\$/;
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4177
|
+
var NOOP = { next: null, cursor: null, submit: false };
|
|
4178
|
+
function processMultilineKey(value, cursor, key) {
|
|
4179
|
+
if (key.tab || key.escape || key.pageUp || key.pageDown) {
|
|
4180
|
+
return NOOP;
|
|
4181
|
+
}
|
|
4182
|
+
if (value.length === 0 && (key.upArrow || key.downArrow)) {
|
|
4183
|
+
return NOOP;
|
|
4184
|
+
}
|
|
4185
|
+
if (key.leftArrow) {
|
|
4186
|
+
return { next: null, cursor: Math.max(0, cursor - 1), submit: false };
|
|
4187
|
+
}
|
|
4188
|
+
if (key.rightArrow) {
|
|
4189
|
+
return { next: null, cursor: Math.min(value.length, cursor + 1), submit: false };
|
|
4190
|
+
}
|
|
4191
|
+
if (key.upArrow) {
|
|
4192
|
+
const moved = moveCursorUp(value, cursor);
|
|
4193
|
+
return moved === cursor ? NOOP : { next: null, cursor: moved, submit: false };
|
|
4194
|
+
}
|
|
4195
|
+
if (key.downArrow) {
|
|
4196
|
+
const moved = moveCursorDown(value, cursor);
|
|
4197
|
+
return moved === cursor ? NOOP : { next: null, cursor: moved, submit: false };
|
|
4198
|
+
}
|
|
4199
|
+
if (key.ctrl && key.input === "a") {
|
|
4200
|
+
return { next: null, cursor: startOfLine(value, cursor), submit: false };
|
|
4201
|
+
}
|
|
4202
|
+
if (key.ctrl && key.input === "e") {
|
|
4203
|
+
return { next: null, cursor: endOfLine(value, cursor), submit: false };
|
|
3992
4204
|
}
|
|
3993
4205
|
if (key.input === "\n" || key.ctrl && key.input === "j") {
|
|
3994
|
-
return
|
|
3995
|
-
`, submit: false };
|
|
4206
|
+
return insertAt(value, cursor, "\n");
|
|
3996
4207
|
}
|
|
3997
4208
|
if (key.return) {
|
|
3998
|
-
if (key.shift)
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
return { next: `${value.slice(0, -1)}
|
|
4004
|
-
`, submit: false };
|
|
4209
|
+
if (key.shift) return insertAt(value, cursor, "\n");
|
|
4210
|
+
if (cursor === value.length && BACKSLASH_SUFFIX.test(value)) {
|
|
4211
|
+
const replaced = `${value.slice(0, -1)}
|
|
4212
|
+
`;
|
|
4213
|
+
return { next: replaced, cursor: replaced.length, submit: false };
|
|
4005
4214
|
}
|
|
4006
|
-
return { next: null, submit: true, submitValue: value };
|
|
4215
|
+
return { next: null, cursor: null, submit: true, submitValue: value };
|
|
4007
4216
|
}
|
|
4008
|
-
if (key.backspace
|
|
4009
|
-
if (
|
|
4010
|
-
return {
|
|
4217
|
+
if (key.backspace) {
|
|
4218
|
+
if (cursor === 0) return NOOP;
|
|
4219
|
+
return {
|
|
4220
|
+
next: value.slice(0, cursor - 1) + value.slice(cursor),
|
|
4221
|
+
cursor: cursor - 1,
|
|
4222
|
+
submit: false
|
|
4223
|
+
};
|
|
4224
|
+
}
|
|
4225
|
+
if (key.delete) {
|
|
4226
|
+
if (cursor === value.length) return NOOP;
|
|
4227
|
+
return {
|
|
4228
|
+
next: value.slice(0, cursor) + value.slice(cursor + 1),
|
|
4229
|
+
cursor,
|
|
4230
|
+
submit: false
|
|
4231
|
+
};
|
|
4011
4232
|
}
|
|
4012
|
-
if ((key.ctrl || key.meta) && key.input.length === 0)
|
|
4013
|
-
|
|
4233
|
+
if ((key.ctrl || key.meta) && key.input.length === 0) return NOOP;
|
|
4234
|
+
if (key.ctrl || key.meta) return NOOP;
|
|
4235
|
+
if (key.input.length > 0) {
|
|
4236
|
+
return insertAt(value, cursor, key.input);
|
|
4014
4237
|
}
|
|
4015
|
-
|
|
4016
|
-
|
|
4238
|
+
return NOOP;
|
|
4239
|
+
}
|
|
4240
|
+
function insertAt(value, cursor, insert) {
|
|
4241
|
+
return {
|
|
4242
|
+
next: value.slice(0, cursor) + insert + value.slice(cursor),
|
|
4243
|
+
cursor: cursor + insert.length,
|
|
4244
|
+
submit: false
|
|
4245
|
+
};
|
|
4246
|
+
}
|
|
4247
|
+
function lineAndColumn(value, cursor) {
|
|
4248
|
+
let line = 0;
|
|
4249
|
+
let col = 0;
|
|
4250
|
+
const n = Math.min(cursor, value.length);
|
|
4251
|
+
for (let i = 0; i < n; i++) {
|
|
4252
|
+
if (value[i] === "\n") {
|
|
4253
|
+
line++;
|
|
4254
|
+
col = 0;
|
|
4255
|
+
} else {
|
|
4256
|
+
col++;
|
|
4257
|
+
}
|
|
4017
4258
|
}
|
|
4018
|
-
return {
|
|
4259
|
+
return { line, col };
|
|
4260
|
+
}
|
|
4261
|
+
function startOfLine(value, cursor) {
|
|
4262
|
+
return value.lastIndexOf("\n", cursor - 1) + 1;
|
|
4263
|
+
}
|
|
4264
|
+
function endOfLine(value, cursor) {
|
|
4265
|
+
const nl = value.indexOf("\n", cursor);
|
|
4266
|
+
return nl === -1 ? value.length : nl;
|
|
4267
|
+
}
|
|
4268
|
+
function moveCursorUp(value, cursor) {
|
|
4269
|
+
const curStart = startOfLine(value, cursor);
|
|
4270
|
+
if (curStart === 0) return cursor;
|
|
4271
|
+
const col = cursor - curStart;
|
|
4272
|
+
const prevEnd = curStart - 1;
|
|
4273
|
+
const prevStart = value.lastIndexOf("\n", prevEnd - 1) + 1;
|
|
4274
|
+
const prevLen = prevEnd - prevStart;
|
|
4275
|
+
return prevStart + Math.min(col, prevLen);
|
|
4276
|
+
}
|
|
4277
|
+
function moveCursorDown(value, cursor) {
|
|
4278
|
+
const nextNl = value.indexOf("\n", cursor);
|
|
4279
|
+
if (nextNl === -1) return cursor;
|
|
4280
|
+
const curStart = startOfLine(value, cursor);
|
|
4281
|
+
const col = cursor - curStart;
|
|
4282
|
+
const nextStart = nextNl + 1;
|
|
4283
|
+
const followingNl = value.indexOf("\n", nextStart);
|
|
4284
|
+
const nextLen = (followingNl === -1 ? value.length : followingNl) - nextStart;
|
|
4285
|
+
return nextStart + Math.min(col, nextLen);
|
|
4019
4286
|
}
|
|
4020
4287
|
|
|
4021
4288
|
// src/cli/ui/PromptInput.tsx
|
|
@@ -4026,11 +4293,19 @@ function PromptInput({
|
|
|
4026
4293
|
disabled,
|
|
4027
4294
|
placeholder
|
|
4028
4295
|
}) {
|
|
4296
|
+
const [cursor, setCursor] = useState2(value.length);
|
|
4297
|
+
const lastLocalValueRef = useRef(value);
|
|
4298
|
+
if (value !== lastLocalValueRef.current) {
|
|
4299
|
+
lastLocalValueRef.current = value;
|
|
4300
|
+
if (cursor !== value.length) {
|
|
4301
|
+
setCursor(value.length);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4029
4304
|
const tick = useTick();
|
|
4030
4305
|
const showCursor = disabled ? false : Math.floor(tick / 4) % 2 === 0;
|
|
4031
4306
|
useInput(
|
|
4032
4307
|
(input, key) => {
|
|
4033
|
-
const
|
|
4308
|
+
const ke = {
|
|
4034
4309
|
input,
|
|
4035
4310
|
return: key.return,
|
|
4036
4311
|
shift: key.shift,
|
|
@@ -4047,8 +4322,14 @@ function PromptInput({
|
|
|
4047
4322
|
pageUp: key.pageUp,
|
|
4048
4323
|
pageDown: key.pageDown
|
|
4049
4324
|
};
|
|
4050
|
-
const action = processMultilineKey(value,
|
|
4051
|
-
if (action.next !== null)
|
|
4325
|
+
const action = processMultilineKey(value, cursor, ke);
|
|
4326
|
+
if (action.next !== null) {
|
|
4327
|
+
lastLocalValueRef.current = action.next;
|
|
4328
|
+
onChange(action.next);
|
|
4329
|
+
}
|
|
4330
|
+
if (action.cursor !== null) {
|
|
4331
|
+
setCursor(action.cursor);
|
|
4332
|
+
}
|
|
4052
4333
|
if (action.submit) onSubmit(action.submitValue ?? value);
|
|
4053
4334
|
},
|
|
4054
4335
|
{ isActive: !disabled }
|
|
@@ -4056,16 +4337,39 @@ function PromptInput({
|
|
|
4056
4337
|
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? "type a message, or /command \xB7 Ctrl+J for newline";
|
|
4057
4338
|
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
4058
4339
|
const borderColor = disabled ? "gray" : "cyan";
|
|
4340
|
+
const { line: cursorLine, col: cursorCol } = lineAndColumn(value, cursor);
|
|
4059
4341
|
return /* @__PURE__ */ React5.createElement(Box4, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
4060
|
-
const isLast = i === lines.length - 1;
|
|
4061
4342
|
const isFirst = i === 0;
|
|
4062
4343
|
const showPlaceholder = isFirst && value.length === 0;
|
|
4344
|
+
const isCursorLine = i === cursorLine;
|
|
4063
4345
|
return (
|
|
4064
4346
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
4065
|
-
/* @__PURE__ */ React5.createElement(Box4, { key: i }, isFirst ? /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, " "), showPlaceholder
|
|
4347
|
+
/* @__PURE__ */ React5.createElement(Box4, { key: i }, isFirst ? /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, " "), showPlaceholder ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, isCursorLine && !disabled ? /* @__PURE__ */ React5.createElement(Text4, { color: borderColor }, showCursor ? "\u258C" : " ") : null, /* @__PURE__ */ React5.createElement(Text4, { dimColor: true }, effectivePlaceholder)) : isCursorLine && !disabled ? /* @__PURE__ */ React5.createElement(
|
|
4348
|
+
LineWithCursor,
|
|
4349
|
+
{
|
|
4350
|
+
line,
|
|
4351
|
+
col: cursorCol,
|
|
4352
|
+
showCursor,
|
|
4353
|
+
borderColor
|
|
4354
|
+
}
|
|
4355
|
+
) : /* @__PURE__ */ React5.createElement(Text4, null, line))
|
|
4066
4356
|
);
|
|
4067
4357
|
}));
|
|
4068
4358
|
}
|
|
4359
|
+
function LineWithCursor({
|
|
4360
|
+
line,
|
|
4361
|
+
col,
|
|
4362
|
+
showCursor,
|
|
4363
|
+
borderColor
|
|
4364
|
+
}) {
|
|
4365
|
+
const before = line.slice(0, col);
|
|
4366
|
+
const atCursor = line.slice(col, col + 1);
|
|
4367
|
+
const after = line.slice(col + 1);
|
|
4368
|
+
if (atCursor.length === 0) {
|
|
4369
|
+
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text4, null, before), /* @__PURE__ */ React5.createElement(Text4, { color: borderColor }, showCursor ? "\u258C" : " "));
|
|
4370
|
+
}
|
|
4371
|
+
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text4, null, before), /* @__PURE__ */ React5.createElement(Text4, { inverse: showCursor }, atCursor), /* @__PURE__ */ React5.createElement(Text4, null, after));
|
|
4372
|
+
}
|
|
4069
4373
|
|
|
4070
4374
|
// src/cli/ui/SlashSuggestions.tsx
|
|
4071
4375
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
@@ -4577,23 +4881,23 @@ function App({
|
|
|
4577
4881
|
codeMode
|
|
4578
4882
|
}) {
|
|
4579
4883
|
const { exit } = useApp();
|
|
4580
|
-
const [historical, setHistorical] =
|
|
4581
|
-
const [streaming, setStreaming] =
|
|
4582
|
-
const [input, setInput] =
|
|
4583
|
-
const [busy, setBusy] =
|
|
4584
|
-
const abortedThisTurn =
|
|
4585
|
-
const [ongoingTool, setOngoingTool] =
|
|
4586
|
-
const [toolProgress, setToolProgress] =
|
|
4587
|
-
const [statusLine, setStatusLine] =
|
|
4588
|
-
const [balance, setBalance] =
|
|
4589
|
-
const lastEditSnapshots =
|
|
4590
|
-
const pendingEdits =
|
|
4591
|
-
const promptHistory =
|
|
4592
|
-
const historyCursor =
|
|
4593
|
-
const assistantIterCounter =
|
|
4594
|
-
const toolHistoryRef =
|
|
4595
|
-
const [slashSelected, setSlashSelected] =
|
|
4596
|
-
const [summary, setSummary] =
|
|
4884
|
+
const [historical, setHistorical] = useState3([]);
|
|
4885
|
+
const [streaming, setStreaming] = useState3(null);
|
|
4886
|
+
const [input, setInput] = useState3("");
|
|
4887
|
+
const [busy, setBusy] = useState3(false);
|
|
4888
|
+
const abortedThisTurn = useRef2(false);
|
|
4889
|
+
const [ongoingTool, setOngoingTool] = useState3(null);
|
|
4890
|
+
const [toolProgress, setToolProgress] = useState3(null);
|
|
4891
|
+
const [statusLine, setStatusLine] = useState3(null);
|
|
4892
|
+
const [balance, setBalance] = useState3(null);
|
|
4893
|
+
const lastEditSnapshots = useRef2(null);
|
|
4894
|
+
const pendingEdits = useRef2([]);
|
|
4895
|
+
const promptHistory = useRef2([]);
|
|
4896
|
+
const historyCursor = useRef2(-1);
|
|
4897
|
+
const assistantIterCounter = useRef2(0);
|
|
4898
|
+
const toolHistoryRef = useRef2([]);
|
|
4899
|
+
const [slashSelected, setSlashSelected] = useState3(0);
|
|
4900
|
+
const [summary, setSummary] = useState3({
|
|
4597
4901
|
turns: 0,
|
|
4598
4902
|
totalCostUsd: 0,
|
|
4599
4903
|
totalInputCostUsd: 0,
|
|
@@ -4603,7 +4907,7 @@ function App({
|
|
|
4603
4907
|
cacheHitRatio: 0,
|
|
4604
4908
|
lastPromptTokens: 0
|
|
4605
4909
|
});
|
|
4606
|
-
const transcriptRef =
|
|
4910
|
+
const transcriptRef = useRef2(null);
|
|
4607
4911
|
if (transcript && !transcriptRef.current) {
|
|
4608
4912
|
transcriptRef.current = openTranscriptFile(transcript, {
|
|
4609
4913
|
version: 1,
|
|
@@ -4628,7 +4932,7 @@ function App({
|
|
|
4628
4932
|
return prev;
|
|
4629
4933
|
});
|
|
4630
4934
|
}, [slashMatches]);
|
|
4631
|
-
const loopRef =
|
|
4935
|
+
const loopRef = useRef2(null);
|
|
4632
4936
|
const loop = useMemo(() => {
|
|
4633
4937
|
if (loopRef.current) return loopRef.current;
|
|
4634
4938
|
const client = new DeepSeekClient();
|
|
@@ -4665,7 +4969,7 @@ function App({
|
|
|
4665
4969
|
if (progressSink.current) progressSink.current = null;
|
|
4666
4970
|
};
|
|
4667
4971
|
}, [progressSink]);
|
|
4668
|
-
const sessionBannerShown =
|
|
4972
|
+
const sessionBannerShown = useRef2(false);
|
|
4669
4973
|
useEffect2(() => {
|
|
4670
4974
|
if (sessionBannerShown.current) return;
|
|
4671
4975
|
sessionBannerShown.current = true;
|
|
@@ -4721,20 +5025,22 @@ function App({
|
|
|
4721
5025
|
return;
|
|
4722
5026
|
}
|
|
4723
5027
|
}
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
if (
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
if (
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
5028
|
+
if (input.length === 0) {
|
|
5029
|
+
const hist = promptHistory.current;
|
|
5030
|
+
if (key.upArrow) {
|
|
5031
|
+
if (hist.length === 0) return;
|
|
5032
|
+
const nextCursor = Math.min(historyCursor.current + 1, hist.length - 1);
|
|
5033
|
+
historyCursor.current = nextCursor;
|
|
5034
|
+
setInput(hist[hist.length - 1 - nextCursor] ?? "");
|
|
5035
|
+
return;
|
|
5036
|
+
}
|
|
5037
|
+
if (key.downArrow) {
|
|
5038
|
+
if (historyCursor.current < 0) return;
|
|
5039
|
+
const nextCursor = historyCursor.current - 1;
|
|
5040
|
+
historyCursor.current = nextCursor;
|
|
5041
|
+
setInput(nextCursor < 0 ? "" : hist[hist.length - 1 - nextCursor] ?? "");
|
|
5042
|
+
return;
|
|
5043
|
+
}
|
|
4738
5044
|
}
|
|
4739
5045
|
});
|
|
4740
5046
|
const codeUndo = useCallback(() => {
|
|
@@ -5140,10 +5446,10 @@ function describeRepair(repair) {
|
|
|
5140
5446
|
// src/cli/ui/Setup.tsx
|
|
5141
5447
|
import { Box as Box8, Text as Text8, useApp as useApp2 } from "ink";
|
|
5142
5448
|
import TextInput from "ink-text-input";
|
|
5143
|
-
import React9, { useState as
|
|
5449
|
+
import React9, { useState as useState4 } from "react";
|
|
5144
5450
|
function Setup({ onReady }) {
|
|
5145
|
-
const [value, setValue] =
|
|
5146
|
-
const [error, setError] =
|
|
5451
|
+
const [value, setValue] = useState4("");
|
|
5452
|
+
const [error, setError] = useState4(null);
|
|
5147
5453
|
const { exit } = useApp2();
|
|
5148
5454
|
const handleSubmit = (raw) => {
|
|
5149
5455
|
const trimmed = raw.trim();
|
|
@@ -5178,7 +5484,7 @@ function Setup({ onReady }) {
|
|
|
5178
5484
|
|
|
5179
5485
|
// src/cli/commands/chat.tsx
|
|
5180
5486
|
function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appProps }) {
|
|
5181
|
-
const [key, setKey] =
|
|
5487
|
+
const [key, setKey] = useState5(initialKey);
|
|
5182
5488
|
if (!key) {
|
|
5183
5489
|
return /* @__PURE__ */ React10.createElement(
|
|
5184
5490
|
Setup,
|
|
@@ -5274,6 +5580,10 @@ async function chatCommand(opts) {
|
|
|
5274
5580
|
}
|
|
5275
5581
|
}
|
|
5276
5582
|
const mcpSpecs = successfulSpecs;
|
|
5583
|
+
if (searchEnabled()) {
|
|
5584
|
+
if (!tools) tools = new ToolRegistry();
|
|
5585
|
+
registerWebTools(tools);
|
|
5586
|
+
}
|
|
5277
5587
|
const { waitUntilExit } = render(
|
|
5278
5588
|
/* @__PURE__ */ React10.createElement(
|
|
5279
5589
|
Root,
|
|
@@ -5329,7 +5639,7 @@ import React13 from "react";
|
|
|
5329
5639
|
|
|
5330
5640
|
// src/cli/ui/DiffApp.tsx
|
|
5331
5641
|
import { Box as Box10, Static as Static2, Text as Text10, useApp as useApp3, useInput as useInput3 } from "ink";
|
|
5332
|
-
import React12, { useState as
|
|
5642
|
+
import React12, { useState as useState6 } from "react";
|
|
5333
5643
|
|
|
5334
5644
|
// src/cli/ui/RecordView.tsx
|
|
5335
5645
|
import { Box as Box9, Text as Text9 } from "ink";
|
|
@@ -5372,7 +5682,7 @@ function DiffApp({ report }) {
|
|
|
5372
5682
|
const { exit } = useApp3();
|
|
5373
5683
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
5374
5684
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
5375
|
-
const [idx, setIdx] =
|
|
5685
|
+
const [idx, setIdx] = useState6(Math.max(0, initialIdx));
|
|
5376
5686
|
useInput3((input, key) => {
|
|
5377
5687
|
if (input === "q" || key.ctrl && input === "c") {
|
|
5378
5688
|
exit();
|
|
@@ -5619,11 +5929,11 @@ import React15 from "react";
|
|
|
5619
5929
|
|
|
5620
5930
|
// src/cli/ui/ReplayApp.tsx
|
|
5621
5931
|
import { Box as Box11, Static as Static3, Text as Text11, useApp as useApp4, useInput as useInput4 } from "ink";
|
|
5622
|
-
import React14, { useMemo as useMemo2, useState as
|
|
5932
|
+
import React14, { useMemo as useMemo2, useState as useState7 } from "react";
|
|
5623
5933
|
function ReplayApp({ meta, pages }) {
|
|
5624
5934
|
const { exit } = useApp4();
|
|
5625
5935
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
5626
|
-
const [idx, setIdx] =
|
|
5936
|
+
const [idx, setIdx] = useState7(maxIdx);
|
|
5627
5937
|
useInput4((input, key) => {
|
|
5628
5938
|
if (input === "q" || key.ctrl && input === "c") {
|
|
5629
5939
|
exit();
|
|
@@ -5984,11 +6294,11 @@ import React18 from "react";
|
|
|
5984
6294
|
// src/cli/ui/Wizard.tsx
|
|
5985
6295
|
import { Box as Box13, Text as Text13, useApp as useApp5, useInput as useInput6 } from "ink";
|
|
5986
6296
|
import TextInput2 from "ink-text-input";
|
|
5987
|
-
import React17, { useState as
|
|
6297
|
+
import React17, { useState as useState9 } from "react";
|
|
5988
6298
|
|
|
5989
6299
|
// src/cli/ui/Select.tsx
|
|
5990
6300
|
import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
|
|
5991
|
-
import React16, { useState as
|
|
6301
|
+
import React16, { useState as useState8 } from "react";
|
|
5992
6302
|
function SingleSelect({
|
|
5993
6303
|
items,
|
|
5994
6304
|
initialValue,
|
|
@@ -5999,7 +6309,7 @@ function SingleSelect({
|
|
|
5999
6309
|
0,
|
|
6000
6310
|
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
6001
6311
|
);
|
|
6002
|
-
const [index, setIndex] =
|
|
6312
|
+
const [index, setIndex] = useState8(initialIndex === -1 ? 0 : initialIndex);
|
|
6003
6313
|
useInput5((_input, key) => {
|
|
6004
6314
|
if (key.upArrow) {
|
|
6005
6315
|
setIndex((i) => findNextEnabled(items, i, -1));
|
|
@@ -6029,11 +6339,11 @@ function MultiSelect({
|
|
|
6029
6339
|
onCancel,
|
|
6030
6340
|
footer
|
|
6031
6341
|
}) {
|
|
6032
|
-
const [index, setIndex] =
|
|
6342
|
+
const [index, setIndex] = useState8(() => {
|
|
6033
6343
|
const first = items.findIndex((i) => !i.disabled);
|
|
6034
6344
|
return first === -1 ? 0 : first;
|
|
6035
6345
|
});
|
|
6036
|
-
const [selected, setSelected] =
|
|
6346
|
+
const [selected, setSelected] = useState8(new Set(initialSelected));
|
|
6037
6347
|
useInput5((input, key) => {
|
|
6038
6348
|
if (key.upArrow) {
|
|
6039
6349
|
setIndex((i) => findNextEnabled(items, i, -1));
|
|
@@ -6112,14 +6422,14 @@ var PRESET_DESCRIPTIONS = {
|
|
|
6112
6422
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
6113
6423
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
6114
6424
|
const { exit } = useApp5();
|
|
6115
|
-
const [step, setStep] =
|
|
6116
|
-
const [data, setData] =
|
|
6425
|
+
const [step, setStep] = useState9(existingApiKey ? "preset" : "apiKey");
|
|
6426
|
+
const [data, setData] = useState9({
|
|
6117
6427
|
apiKey: existingApiKey ?? "",
|
|
6118
6428
|
preset: initial?.preset ?? "fast",
|
|
6119
6429
|
selectedCatalog: deriveInitialCatalog(initial?.mcp ?? []),
|
|
6120
6430
|
catalogArgs: {}
|
|
6121
6431
|
});
|
|
6122
|
-
const [error, setError] =
|
|
6432
|
+
const [error, setError] = useState9(null);
|
|
6123
6433
|
useInput6((_input, key) => {
|
|
6124
6434
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
6125
6435
|
});
|
|
@@ -6236,7 +6546,7 @@ function ApiKeyStep({
|
|
|
6236
6546
|
error,
|
|
6237
6547
|
onError
|
|
6238
6548
|
}) {
|
|
6239
|
-
const [value, setValue] =
|
|
6549
|
+
const [value, setValue] = useState9("");
|
|
6240
6550
|
return /* @__PURE__ */ React17.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React17.createElement(Text13, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React17.createElement(
|
|
6241
6551
|
TextInput2,
|
|
6242
6552
|
{
|
|
@@ -6262,7 +6572,7 @@ function McpArgsStep({
|
|
|
6262
6572
|
onSubmit,
|
|
6263
6573
|
onError
|
|
6264
6574
|
}) {
|
|
6265
|
-
const [value, setValue] =
|
|
6575
|
+
const [value, setValue] = useState9("");
|
|
6266
6576
|
return /* @__PURE__ */ React17.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React17.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React17.createElement(Text13, null, entry.summary), entry.note ? /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, null, "Required parameter: "), /* @__PURE__ */ React17.createElement(Text13, { bold: true }, entry.userArgs)), /* @__PURE__ */ React17.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text13, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React17.createElement(
|
|
6267
6577
|
TextInput2,
|
|
6268
6578
|
{
|