reasonix 0.4.5 → 0.4.9
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 +993 -203
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +174 -14
- package/dist/index.js +486 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -96,8 +96,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
96
96
|
}
|
|
97
97
|
function sleep(ms, signal) {
|
|
98
98
|
if (ms <= 0) return Promise.resolve();
|
|
99
|
-
return new Promise((
|
|
100
|
-
const timer = setTimeout(
|
|
99
|
+
return new Promise((resolve5, reject) => {
|
|
100
|
+
const timer = setTimeout(resolve5, ms);
|
|
101
101
|
if (signal) {
|
|
102
102
|
const onAbort = () => {
|
|
103
103
|
clearTimeout(timer);
|
|
@@ -563,7 +563,7 @@ var ToolRegistry = class {
|
|
|
563
563
|
}
|
|
564
564
|
}));
|
|
565
565
|
}
|
|
566
|
-
async dispatch(name, argumentsRaw) {
|
|
566
|
+
async dispatch(name, argumentsRaw, opts = {}) {
|
|
567
567
|
const tool = this._tools.get(name);
|
|
568
568
|
if (!tool) {
|
|
569
569
|
return JSON.stringify({ error: `unknown tool: ${name}` });
|
|
@@ -580,7 +580,7 @@ var ToolRegistry = class {
|
|
|
580
580
|
args = nestArguments(args);
|
|
581
581
|
}
|
|
582
582
|
try {
|
|
583
|
-
const result = await tool.fn(args);
|
|
583
|
+
const result = await tool.fn(args, { signal: opts.signal });
|
|
584
584
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
585
585
|
} catch (err) {
|
|
586
586
|
return JSON.stringify({
|
|
@@ -614,8 +614,20 @@ async function bridgeMcpTools(client, opts = {}) {
|
|
|
614
614
|
name: registeredName,
|
|
615
615
|
description: mcpTool.description ?? "",
|
|
616
616
|
parameters: mcpTool.inputSchema,
|
|
617
|
-
fn: async (args) => {
|
|
618
|
-
const toolResult = await client.callTool(mcpTool.name, args
|
|
617
|
+
fn: async (args, ctx) => {
|
|
618
|
+
const toolResult = await client.callTool(mcpTool.name, args, {
|
|
619
|
+
// Forward server-side progress frames to the bridge caller,
|
|
620
|
+
// tagged with the registered name so multi-server UIs can
|
|
621
|
+
// disambiguate. No-op when `onProgress` isn't configured —
|
|
622
|
+
// the client then also omits the _meta.progressToken and
|
|
623
|
+
// the server won't emit progress.
|
|
624
|
+
onProgress: opts.onProgress ? (info) => opts.onProgress({ toolName: registeredName, ...info }) : void 0,
|
|
625
|
+
// Thread the tool-dispatch AbortSignal all the way down to
|
|
626
|
+
// the MCP request so Esc truly cancels in flight — the
|
|
627
|
+
// client will emit notifications/cancelled AND reject the
|
|
628
|
+
// pending promise immediately, no "wait for subprocess".
|
|
629
|
+
signal: ctx?.signal
|
|
630
|
+
});
|
|
619
631
|
return flattenMcpResult(toolResult, { maxChars: maxResultChars });
|
|
620
632
|
}
|
|
621
633
|
});
|
|
@@ -1204,11 +1216,13 @@ var CacheFirstLoop = class {
|
|
|
1204
1216
|
_turn = 0;
|
|
1205
1217
|
_streamPreference;
|
|
1206
1218
|
/**
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1219
|
+
* AbortController per active turn. Threaded through the DeepSeek
|
|
1220
|
+
* HTTP calls AND every tool dispatch so Esc actually cancels the
|
|
1221
|
+
* in-flight network/subprocess work — not "we'll get to it after
|
|
1222
|
+
* the current call finishes." Re-created at the start of each
|
|
1223
|
+
* `step()` (the prior turn's signal has already fired).
|
|
1210
1224
|
*/
|
|
1211
|
-
|
|
1225
|
+
_turnAbort = new AbortController();
|
|
1212
1226
|
constructor(opts) {
|
|
1213
1227
|
this.client = opts.client;
|
|
1214
1228
|
this.prefix = opts.prefix;
|
|
@@ -1321,14 +1335,15 @@ var CacheFirstLoop = class {
|
|
|
1321
1335
|
return msgs;
|
|
1322
1336
|
}
|
|
1323
1337
|
/**
|
|
1324
|
-
* Signal the currently-running {@link step}
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1338
|
+
* Signal the currently-running {@link step} to stop **now**. Cancels
|
|
1339
|
+
* the in-flight network request (DeepSeek HTTP/SSE) AND any tool call
|
|
1340
|
+
* currently dispatching (MCP `notifications/cancelled` + promise
|
|
1341
|
+
* reject). The loop itself also sees `signal.aborted` at each
|
|
1342
|
+
* iteration boundary and exits quickly instead of looping again.
|
|
1343
|
+
* Called by the TUI on Esc.
|
|
1329
1344
|
*/
|
|
1330
1345
|
abort() {
|
|
1331
|
-
this.
|
|
1346
|
+
this._turnAbort.abort();
|
|
1332
1347
|
}
|
|
1333
1348
|
/**
|
|
1334
1349
|
* Drop everything in the log after (and including) the most recent
|
|
@@ -1366,13 +1381,14 @@ var CacheFirstLoop = class {
|
|
|
1366
1381
|
async *step(userInput) {
|
|
1367
1382
|
this._turn++;
|
|
1368
1383
|
this.scratch.reset();
|
|
1369
|
-
this.
|
|
1384
|
+
this._turnAbort = new AbortController();
|
|
1385
|
+
const signal = this._turnAbort.signal;
|
|
1370
1386
|
let pendingUser = userInput;
|
|
1371
1387
|
const toolSpecs = this.prefix.tools();
|
|
1372
1388
|
const warnAt = Math.max(1, Math.floor(this.maxToolIters * 0.7));
|
|
1373
1389
|
let warnedForIterBudget = false;
|
|
1374
1390
|
for (let iter = 0; iter < this.maxToolIters; iter++) {
|
|
1375
|
-
if (
|
|
1391
|
+
if (signal.aborted) {
|
|
1376
1392
|
yield {
|
|
1377
1393
|
turn: this._turn,
|
|
1378
1394
|
role: "warning",
|
|
@@ -1435,7 +1451,8 @@ var CacheFirstLoop = class {
|
|
|
1435
1451
|
{
|
|
1436
1452
|
model: this.model,
|
|
1437
1453
|
messages,
|
|
1438
|
-
tools: toolSpecs.length ? toolSpecs : void 0
|
|
1454
|
+
tools: toolSpecs.length ? toolSpecs : void 0,
|
|
1455
|
+
signal
|
|
1439
1456
|
},
|
|
1440
1457
|
{
|
|
1441
1458
|
...this.branchOptions,
|
|
@@ -1444,8 +1461,8 @@ var CacheFirstLoop = class {
|
|
|
1444
1461
|
}
|
|
1445
1462
|
);
|
|
1446
1463
|
for (let k = 0; k < budget; k++) {
|
|
1447
|
-
const sample = queue.shift() ?? await new Promise((
|
|
1448
|
-
waiter =
|
|
1464
|
+
const sample = queue.shift() ?? await new Promise((resolve5) => {
|
|
1465
|
+
waiter = resolve5;
|
|
1449
1466
|
});
|
|
1450
1467
|
yield {
|
|
1451
1468
|
turn: this._turn,
|
|
@@ -1485,7 +1502,8 @@ var CacheFirstLoop = class {
|
|
|
1485
1502
|
for await (const chunk of this.client.stream({
|
|
1486
1503
|
model: this.model,
|
|
1487
1504
|
messages,
|
|
1488
|
-
tools: toolSpecs.length ? toolSpecs : void 0
|
|
1505
|
+
tools: toolSpecs.length ? toolSpecs : void 0,
|
|
1506
|
+
signal
|
|
1489
1507
|
})) {
|
|
1490
1508
|
if (chunk.contentDelta) {
|
|
1491
1509
|
assistantContent += chunk.contentDelta;
|
|
@@ -1524,7 +1542,8 @@ var CacheFirstLoop = class {
|
|
|
1524
1542
|
const resp = await this.client.chat({
|
|
1525
1543
|
model: this.model,
|
|
1526
1544
|
messages,
|
|
1527
|
-
tools: toolSpecs.length ? toolSpecs : void 0
|
|
1545
|
+
tools: toolSpecs.length ? toolSpecs : void 0,
|
|
1546
|
+
signal
|
|
1528
1547
|
});
|
|
1529
1548
|
assistantContent = resp.content;
|
|
1530
1549
|
reasoningContent = resp.reasoningContent ?? "";
|
|
@@ -1588,7 +1607,7 @@ var CacheFirstLoop = class {
|
|
|
1588
1607
|
toolName: name,
|
|
1589
1608
|
toolArgs: args
|
|
1590
1609
|
};
|
|
1591
|
-
const result = await this.tools.dispatch(name, args);
|
|
1610
|
+
const result = await this.tools.dispatch(name, args, { signal });
|
|
1592
1611
|
this.appendAndPersist({
|
|
1593
1612
|
role: "tool",
|
|
1594
1613
|
tool_call_id: call.id ?? "",
|
|
@@ -1615,8 +1634,9 @@ var CacheFirstLoop = class {
|
|
|
1615
1634
|
});
|
|
1616
1635
|
const resp = await this.client.chat({
|
|
1617
1636
|
model: this.model,
|
|
1618
|
-
messages
|
|
1637
|
+
messages,
|
|
1619
1638
|
// no tools → model is forced to answer in text
|
|
1639
|
+
signal: this._turnAbort.signal
|
|
1620
1640
|
});
|
|
1621
1641
|
const rawContent = resp.content?.trim() ?? "";
|
|
1622
1642
|
const cleaned = stripHallucinatedToolMarkup(rawContent);
|
|
@@ -1712,13 +1732,298 @@ function formatLoopError(err) {
|
|
|
1712
1732
|
return msg;
|
|
1713
1733
|
}
|
|
1714
1734
|
|
|
1735
|
+
// src/tools/filesystem.ts
|
|
1736
|
+
import { promises as fs } from "fs";
|
|
1737
|
+
import * as pathMod from "path";
|
|
1738
|
+
var DEFAULT_MAX_READ_BYTES = 2 * 1024 * 1024;
|
|
1739
|
+
var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
1740
|
+
function registerFilesystemTools(registry, opts) {
|
|
1741
|
+
const rootDir = pathMod.resolve(opts.rootDir);
|
|
1742
|
+
const allowWriting = opts.allowWriting !== false;
|
|
1743
|
+
const maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
|
|
1744
|
+
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
1745
|
+
const safePath = (raw) => {
|
|
1746
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
1747
|
+
throw new Error("path must be a non-empty string");
|
|
1748
|
+
}
|
|
1749
|
+
const resolved = pathMod.resolve(rootDir, raw);
|
|
1750
|
+
const normRoot = pathMod.resolve(rootDir);
|
|
1751
|
+
const rel = pathMod.relative(normRoot, resolved);
|
|
1752
|
+
if (rel.startsWith("..") || pathMod.isAbsolute(rel)) {
|
|
1753
|
+
throw new Error(`path escapes sandbox root (${normRoot}): ${raw}`);
|
|
1754
|
+
}
|
|
1755
|
+
return resolved;
|
|
1756
|
+
};
|
|
1757
|
+
registry.register({
|
|
1758
|
+
name: "read_file",
|
|
1759
|
+
description: "Read a file under the sandbox root. Returns the full contents (truncated with a notice if larger than the per-call cap). Paths may be relative to the root or absolute-under-root.",
|
|
1760
|
+
parameters: {
|
|
1761
|
+
type: "object",
|
|
1762
|
+
properties: {
|
|
1763
|
+
path: { type: "string", description: "Path to read (relative to rootDir or absolute)." },
|
|
1764
|
+
head: { type: "integer", description: "If set, return only the first N lines." },
|
|
1765
|
+
tail: { type: "integer", description: "If set, return only the last N lines." }
|
|
1766
|
+
},
|
|
1767
|
+
required: ["path"]
|
|
1768
|
+
},
|
|
1769
|
+
fn: async (args) => {
|
|
1770
|
+
const abs = safePath(args.path);
|
|
1771
|
+
const stat = await fs.stat(abs);
|
|
1772
|
+
if (stat.isDirectory()) {
|
|
1773
|
+
throw new Error(`not a file: ${args.path} (it's a directory)`);
|
|
1774
|
+
}
|
|
1775
|
+
const raw = await fs.readFile(abs);
|
|
1776
|
+
if (raw.length > maxReadBytes) {
|
|
1777
|
+
const head = raw.slice(0, maxReadBytes).toString("utf8");
|
|
1778
|
+
return `${head}
|
|
1779
|
+
|
|
1780
|
+
[\u2026truncated ${raw.length - maxReadBytes} bytes \u2014 file is ${raw.length} B, cap ${maxReadBytes} B. Retry with head/tail for targeted view.]`;
|
|
1781
|
+
}
|
|
1782
|
+
const text = raw.toString("utf8");
|
|
1783
|
+
if (typeof args.head === "number" && args.head > 0) {
|
|
1784
|
+
return text.split(/\r?\n/).slice(0, args.head).join("\n");
|
|
1785
|
+
}
|
|
1786
|
+
if (typeof args.tail === "number" && args.tail > 0) {
|
|
1787
|
+
let lines = text.split(/\r?\n/);
|
|
1788
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
1789
|
+
return lines.slice(Math.max(0, lines.length - args.tail)).join("\n");
|
|
1790
|
+
}
|
|
1791
|
+
return text;
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
registry.register({
|
|
1795
|
+
name: "list_directory",
|
|
1796
|
+
description: "List entries in a directory under the sandbox root. Returns one line per entry, marking directories with a trailing slash. Not recursive \u2014 use directory_tree for that.",
|
|
1797
|
+
parameters: {
|
|
1798
|
+
type: "object",
|
|
1799
|
+
properties: {
|
|
1800
|
+
path: { type: "string", description: "Directory to list (default: root)." }
|
|
1801
|
+
}
|
|
1802
|
+
},
|
|
1803
|
+
fn: async (args) => {
|
|
1804
|
+
const abs = safePath(args.path ?? ".");
|
|
1805
|
+
const entries = await fs.readdir(abs, { withFileTypes: true });
|
|
1806
|
+
const lines = [];
|
|
1807
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1808
|
+
lines.push(e.isDirectory() ? `${e.name}/` : e.name);
|
|
1809
|
+
}
|
|
1810
|
+
return lines.join("\n") || "(empty directory)";
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
registry.register({
|
|
1814
|
+
name: "directory_tree",
|
|
1815
|
+
description: "Recursively list entries in a directory. Shows indented tree structure with directories marked '/'. Caps output so a huge tree doesn't drown the context.",
|
|
1816
|
+
parameters: {
|
|
1817
|
+
type: "object",
|
|
1818
|
+
properties: {
|
|
1819
|
+
path: { type: "string", description: "Root of the tree (default: sandbox root)." },
|
|
1820
|
+
maxDepth: { type: "integer", description: "Max recursion depth (default 4)." }
|
|
1821
|
+
}
|
|
1822
|
+
},
|
|
1823
|
+
fn: async (args) => {
|
|
1824
|
+
const startAbs = safePath(args.path ?? ".");
|
|
1825
|
+
const maxDepth = typeof args.maxDepth === "number" ? args.maxDepth : 4;
|
|
1826
|
+
const lines = [];
|
|
1827
|
+
let totalBytes = 0;
|
|
1828
|
+
let truncated = false;
|
|
1829
|
+
const walk2 = async (dir, depth) => {
|
|
1830
|
+
if (truncated) return;
|
|
1831
|
+
if (depth > maxDepth) return;
|
|
1832
|
+
let entries;
|
|
1833
|
+
try {
|
|
1834
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1835
|
+
} catch {
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1839
|
+
for (const e of entries) {
|
|
1840
|
+
if (truncated) return;
|
|
1841
|
+
const indent = " ".repeat(depth);
|
|
1842
|
+
const line = e.isDirectory() ? `${indent}${e.name}/` : `${indent}${e.name}`;
|
|
1843
|
+
totalBytes += line.length + 1;
|
|
1844
|
+
if (totalBytes > maxListBytes) {
|
|
1845
|
+
lines.push(` [\u2026 tree truncated at ${maxListBytes} bytes \u2026]`);
|
|
1846
|
+
truncated = true;
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
lines.push(line);
|
|
1850
|
+
if (e.isDirectory()) {
|
|
1851
|
+
await walk2(pathMod.join(dir, e.name), depth + 1);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1855
|
+
await walk2(startAbs, 0);
|
|
1856
|
+
return lines.join("\n") || "(empty tree)";
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
registry.register({
|
|
1860
|
+
name: "search_files",
|
|
1861
|
+
description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line.",
|
|
1862
|
+
parameters: {
|
|
1863
|
+
type: "object",
|
|
1864
|
+
properties: {
|
|
1865
|
+
path: { type: "string", description: "Directory to start the search at (default: root)." },
|
|
1866
|
+
pattern: {
|
|
1867
|
+
type: "string",
|
|
1868
|
+
description: "Substring (or regex) to match against filenames."
|
|
1869
|
+
}
|
|
1870
|
+
},
|
|
1871
|
+
required: ["pattern"]
|
|
1872
|
+
},
|
|
1873
|
+
fn: async (args) => {
|
|
1874
|
+
const startAbs = safePath(args.path ?? ".");
|
|
1875
|
+
const needle = args.pattern.toLowerCase();
|
|
1876
|
+
let re = null;
|
|
1877
|
+
try {
|
|
1878
|
+
re = new RegExp(args.pattern, "i");
|
|
1879
|
+
} catch {
|
|
1880
|
+
re = null;
|
|
1881
|
+
}
|
|
1882
|
+
const matches = [];
|
|
1883
|
+
let totalBytes = 0;
|
|
1884
|
+
const walk2 = async (dir) => {
|
|
1885
|
+
let entries;
|
|
1886
|
+
try {
|
|
1887
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1888
|
+
} catch {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
for (const e of entries) {
|
|
1892
|
+
const full = pathMod.join(dir, e.name);
|
|
1893
|
+
const lower = e.name.toLowerCase();
|
|
1894
|
+
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
1895
|
+
if (hit) {
|
|
1896
|
+
const rel = pathMod.relative(rootDir, full);
|
|
1897
|
+
if (totalBytes + rel.length + 1 > maxListBytes) {
|
|
1898
|
+
matches.push("[\u2026 search truncated \u2014 refine pattern \u2026]");
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
matches.push(rel);
|
|
1902
|
+
totalBytes += rel.length + 1;
|
|
1903
|
+
}
|
|
1904
|
+
if (e.isDirectory()) await walk2(full);
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
await walk2(startAbs);
|
|
1908
|
+
return matches.length === 0 ? "(no matches)" : matches.join("\n");
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
registry.register({
|
|
1912
|
+
name: "get_file_info",
|
|
1913
|
+
description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
|
|
1914
|
+
parameters: {
|
|
1915
|
+
type: "object",
|
|
1916
|
+
properties: {
|
|
1917
|
+
path: { type: "string" }
|
|
1918
|
+
},
|
|
1919
|
+
required: ["path"]
|
|
1920
|
+
},
|
|
1921
|
+
fn: async (args) => {
|
|
1922
|
+
const abs = safePath(args.path);
|
|
1923
|
+
const st = await fs.lstat(abs);
|
|
1924
|
+
const type = st.isDirectory() ? "directory" : st.isSymbolicLink() ? "symlink" : "file";
|
|
1925
|
+
return JSON.stringify({
|
|
1926
|
+
type,
|
|
1927
|
+
size: st.size,
|
|
1928
|
+
mtime: st.mtime.toISOString()
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
if (!allowWriting) return registry;
|
|
1933
|
+
registry.register({
|
|
1934
|
+
name: "write_file",
|
|
1935
|
+
description: "Create or overwrite a file under the sandbox root with the given content. Parent directories are created as needed.",
|
|
1936
|
+
parameters: {
|
|
1937
|
+
type: "object",
|
|
1938
|
+
properties: {
|
|
1939
|
+
path: { type: "string" },
|
|
1940
|
+
content: { type: "string" }
|
|
1941
|
+
},
|
|
1942
|
+
required: ["path", "content"]
|
|
1943
|
+
},
|
|
1944
|
+
fn: async (args) => {
|
|
1945
|
+
const abs = safePath(args.path);
|
|
1946
|
+
await fs.mkdir(pathMod.dirname(abs), { recursive: true });
|
|
1947
|
+
await fs.writeFile(abs, args.content, "utf8");
|
|
1948
|
+
return `wrote ${args.content.length} chars to ${pathMod.relative(rootDir, abs)}`;
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
registry.register({
|
|
1952
|
+
name: "edit_file",
|
|
1953
|
+
description: "Apply a SEARCH/REPLACE edit to an existing file. `search` must match exactly (whitespace sensitive) \u2014 no regex. The match must be unique in the file; otherwise the edit is refused to avoid surprise rewrites. This flat-string shape replaces the `{oldText, newText}[]` JSON array form that previously triggered R1 DSML hallucinations.",
|
|
1954
|
+
parameters: {
|
|
1955
|
+
type: "object",
|
|
1956
|
+
properties: {
|
|
1957
|
+
path: { type: "string" },
|
|
1958
|
+
search: { type: "string", description: "Exact text to find (must be unique)." },
|
|
1959
|
+
replace: { type: "string", description: "Text to substitute in place of `search`." }
|
|
1960
|
+
},
|
|
1961
|
+
required: ["path", "search", "replace"]
|
|
1962
|
+
},
|
|
1963
|
+
fn: async (args) => {
|
|
1964
|
+
const abs = safePath(args.path);
|
|
1965
|
+
const before = await fs.readFile(abs, "utf8");
|
|
1966
|
+
if (args.search.length === 0) {
|
|
1967
|
+
throw new Error("edit_file: search cannot be empty");
|
|
1968
|
+
}
|
|
1969
|
+
const firstIdx = before.indexOf(args.search);
|
|
1970
|
+
if (firstIdx < 0) {
|
|
1971
|
+
throw new Error(`edit_file: search text not found in ${pathMod.relative(rootDir, abs)}`);
|
|
1972
|
+
}
|
|
1973
|
+
const nextIdx = before.indexOf(args.search, firstIdx + 1);
|
|
1974
|
+
if (nextIdx >= 0) {
|
|
1975
|
+
throw new Error(
|
|
1976
|
+
`edit_file: search text appears multiple times in ${pathMod.relative(rootDir, abs)} \u2014 include more context to disambiguate`
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
const after = before.slice(0, firstIdx) + args.replace + before.slice(firstIdx + args.search.length);
|
|
1980
|
+
await fs.writeFile(abs, after, "utf8");
|
|
1981
|
+
return `edited ${pathMod.relative(rootDir, abs)} (${args.search.length}\u2192${args.replace.length} chars)`;
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
registry.register({
|
|
1985
|
+
name: "create_directory",
|
|
1986
|
+
description: "Create a directory (and any missing parents) under the sandbox root.",
|
|
1987
|
+
parameters: {
|
|
1988
|
+
type: "object",
|
|
1989
|
+
properties: { path: { type: "string" } },
|
|
1990
|
+
required: ["path"]
|
|
1991
|
+
},
|
|
1992
|
+
fn: async (args) => {
|
|
1993
|
+
const abs = safePath(args.path);
|
|
1994
|
+
await fs.mkdir(abs, { recursive: true });
|
|
1995
|
+
return `created ${pathMod.relative(rootDir, abs)}/`;
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
registry.register({
|
|
1999
|
+
name: "move_file",
|
|
2000
|
+
description: "Rename/move a file or directory under the sandbox root.",
|
|
2001
|
+
parameters: {
|
|
2002
|
+
type: "object",
|
|
2003
|
+
properties: {
|
|
2004
|
+
source: { type: "string" },
|
|
2005
|
+
destination: { type: "string" }
|
|
2006
|
+
},
|
|
2007
|
+
required: ["source", "destination"]
|
|
2008
|
+
},
|
|
2009
|
+
fn: async (args) => {
|
|
2010
|
+
const src = safePath(args.source);
|
|
2011
|
+
const dst = safePath(args.destination);
|
|
2012
|
+
await fs.mkdir(pathMod.dirname(dst), { recursive: true });
|
|
2013
|
+
await fs.rename(src, dst);
|
|
2014
|
+
return `moved ${pathMod.relative(rootDir, src)} \u2192 ${pathMod.relative(rootDir, dst)}`;
|
|
2015
|
+
}
|
|
2016
|
+
});
|
|
2017
|
+
return registry;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
1715
2020
|
// src/env.ts
|
|
1716
2021
|
import { readFileSync as readFileSync3 } from "fs";
|
|
1717
|
-
import { resolve } from "path";
|
|
2022
|
+
import { resolve as resolve2 } from "path";
|
|
1718
2023
|
function loadDotenv(path = ".env") {
|
|
1719
2024
|
let raw;
|
|
1720
2025
|
try {
|
|
1721
|
-
raw = readFileSync3(
|
|
2026
|
+
raw = readFileSync3(resolve2(process.cwd(), path), "utf8");
|
|
1722
2027
|
} catch {
|
|
1723
2028
|
return;
|
|
1724
2029
|
}
|
|
@@ -2276,6 +2581,16 @@ var McpClient = class {
|
|
|
2276
2581
|
readerStarted = false;
|
|
2277
2582
|
initialized = false;
|
|
2278
2583
|
_serverCapabilities = {};
|
|
2584
|
+
_serverInfo = { name: "", version: "" };
|
|
2585
|
+
_protocolVersion = "";
|
|
2586
|
+
_instructions;
|
|
2587
|
+
// Progress-token → handler for notifications/progress routing. Tokens
|
|
2588
|
+
// are minted per call when the caller supplies an onProgress
|
|
2589
|
+
// callback; cleared when the final response lands (or the pending
|
|
2590
|
+
// request rejects). No leaks — the `try/finally` in callTool
|
|
2591
|
+
// guarantees cleanup even on timeout.
|
|
2592
|
+
progressHandlers = /* @__PURE__ */ new Map();
|
|
2593
|
+
nextProgressToken = 1;
|
|
2279
2594
|
constructor(opts) {
|
|
2280
2595
|
this.transport = opts.transport;
|
|
2281
2596
|
this.clientInfo = opts.clientInfo ?? { name: "reasonix", version: "0.3.0-dev" };
|
|
@@ -2285,6 +2600,18 @@ var McpClient = class {
|
|
|
2285
2600
|
get serverCapabilities() {
|
|
2286
2601
|
return this._serverCapabilities;
|
|
2287
2602
|
}
|
|
2603
|
+
/** Server's self-reported name + version, available after initialize(). */
|
|
2604
|
+
get serverInfo() {
|
|
2605
|
+
return this._serverInfo;
|
|
2606
|
+
}
|
|
2607
|
+
/** Protocol version the server agreed to during the handshake. */
|
|
2608
|
+
get protocolVersion() {
|
|
2609
|
+
return this._protocolVersion;
|
|
2610
|
+
}
|
|
2611
|
+
/** Optional free-form instructions the server provides at handshake. */
|
|
2612
|
+
get serverInstructions() {
|
|
2613
|
+
return this._instructions;
|
|
2614
|
+
}
|
|
2288
2615
|
/**
|
|
2289
2616
|
* Complete the initialize → initialized handshake. Must be called
|
|
2290
2617
|
* before any other method (otherwise compliant servers reject).
|
|
@@ -2303,6 +2630,9 @@ var McpClient = class {
|
|
|
2303
2630
|
clientInfo: this.clientInfo
|
|
2304
2631
|
});
|
|
2305
2632
|
this._serverCapabilities = result.capabilities ?? {};
|
|
2633
|
+
this._serverInfo = result.serverInfo ?? { name: "", version: "" };
|
|
2634
|
+
this._protocolVersion = result.protocolVersion ?? "";
|
|
2635
|
+
this._instructions = result.instructions;
|
|
2306
2636
|
await this.transport.send({
|
|
2307
2637
|
jsonrpc: "2.0",
|
|
2308
2638
|
method: "notifications/initialized"
|
|
@@ -2315,13 +2645,36 @@ var McpClient = class {
|
|
|
2315
2645
|
this.assertInitialized();
|
|
2316
2646
|
return this.request("tools/list", {});
|
|
2317
2647
|
}
|
|
2318
|
-
/**
|
|
2319
|
-
|
|
2648
|
+
/**
|
|
2649
|
+
* Invoke a tool by name. When `onProgress` is supplied, attaches a
|
|
2650
|
+
* fresh progress token so the server can send incremental updates
|
|
2651
|
+
* via `notifications/progress`; they're routed to the callback until
|
|
2652
|
+
* the final response arrives (or the request times out, in which
|
|
2653
|
+
* case the handler is simply dropped — no extra notification).
|
|
2654
|
+
*
|
|
2655
|
+
* When `signal` is supplied, aborting it:
|
|
2656
|
+
* 1) fires `notifications/cancelled` to the server (MCP 2024-11-05
|
|
2657
|
+
* way of saying "forget this request, I no longer care"), and
|
|
2658
|
+
* 2) rejects the pending promise immediately with an AbortError,
|
|
2659
|
+
* so the caller doesn't have to wait for the subprocess to
|
|
2660
|
+
* finish its in-flight file write or network request.
|
|
2661
|
+
* The server MAY still emit a late response; we drop it in dispatch
|
|
2662
|
+
* since the request id is gone from `pending`.
|
|
2663
|
+
*/
|
|
2664
|
+
async callTool(name, args, opts = {}) {
|
|
2320
2665
|
this.assertInitialized();
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2666
|
+
const params = { name, arguments: args ?? {} };
|
|
2667
|
+
let token;
|
|
2668
|
+
if (opts.onProgress) {
|
|
2669
|
+
token = this.nextProgressToken++;
|
|
2670
|
+
this.progressHandlers.set(token, opts.onProgress);
|
|
2671
|
+
params._meta = { progressToken: token };
|
|
2672
|
+
}
|
|
2673
|
+
try {
|
|
2674
|
+
return await this.request("tools/call", params, opts.signal);
|
|
2675
|
+
} finally {
|
|
2676
|
+
if (token !== void 0) this.progressHandlers.delete(token);
|
|
2677
|
+
}
|
|
2325
2678
|
}
|
|
2326
2679
|
/**
|
|
2327
2680
|
* List resources the server exposes. Supports a pagination cursor;
|
|
@@ -2375,24 +2728,56 @@ var McpClient = class {
|
|
|
2375
2728
|
assertInitialized() {
|
|
2376
2729
|
if (!this.initialized) throw new Error("MCP client not initialized \u2014 call initialize() first");
|
|
2377
2730
|
}
|
|
2378
|
-
async request(method, params) {
|
|
2731
|
+
async request(method, params, signal) {
|
|
2379
2732
|
const id = this.nextId++;
|
|
2380
2733
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
2381
|
-
|
|
2734
|
+
let abortHandler = null;
|
|
2735
|
+
const promise = new Promise((resolve5, reject) => {
|
|
2382
2736
|
const timeout = setTimeout(() => {
|
|
2383
2737
|
this.pending.delete(id);
|
|
2738
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
2384
2739
|
reject(
|
|
2385
2740
|
new Error(`MCP request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`)
|
|
2386
2741
|
);
|
|
2387
2742
|
}, this.requestTimeoutMs);
|
|
2388
2743
|
this.pending.set(id, {
|
|
2389
|
-
resolve:
|
|
2744
|
+
resolve: resolve5,
|
|
2390
2745
|
reject,
|
|
2391
2746
|
timeout
|
|
2392
2747
|
});
|
|
2748
|
+
if (signal) {
|
|
2749
|
+
if (signal.aborted) {
|
|
2750
|
+
this.pending.delete(id);
|
|
2751
|
+
clearTimeout(timeout);
|
|
2752
|
+
reject(new Error(`MCP request ${method} (id=${id}) aborted before send`));
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
abortHandler = () => {
|
|
2756
|
+
this.pending.delete(id);
|
|
2757
|
+
clearTimeout(timeout);
|
|
2758
|
+
void this.transport.send({
|
|
2759
|
+
jsonrpc: "2.0",
|
|
2760
|
+
method: "notifications/cancelled",
|
|
2761
|
+
params: { requestId: id, reason: "aborted by user" }
|
|
2762
|
+
}).catch(() => {
|
|
2763
|
+
});
|
|
2764
|
+
reject(new Error(`MCP request ${method} (id=${id}) aborted by user`));
|
|
2765
|
+
};
|
|
2766
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
2767
|
+
}
|
|
2393
2768
|
});
|
|
2394
|
-
|
|
2395
|
-
|
|
2769
|
+
try {
|
|
2770
|
+
await this.transport.send(frame);
|
|
2771
|
+
} catch (err) {
|
|
2772
|
+
this.pending.delete(id);
|
|
2773
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
2774
|
+
throw err;
|
|
2775
|
+
}
|
|
2776
|
+
try {
|
|
2777
|
+
return await promise;
|
|
2778
|
+
} finally {
|
|
2779
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
2780
|
+
}
|
|
2396
2781
|
}
|
|
2397
2782
|
startReaderIfNeeded() {
|
|
2398
2783
|
if (this.readerStarted) return;
|
|
@@ -2413,7 +2798,16 @@ var McpClient = class {
|
|
|
2413
2798
|
}
|
|
2414
2799
|
}
|
|
2415
2800
|
dispatch(msg) {
|
|
2416
|
-
if (!("id" in msg) || msg.id === null || msg.id === void 0)
|
|
2801
|
+
if (!("id" in msg) || msg.id === null || msg.id === void 0) {
|
|
2802
|
+
if ("method" in msg && msg.method === "notifications/progress") {
|
|
2803
|
+
const p = msg.params;
|
|
2804
|
+
if (!p || p.progressToken === void 0) return;
|
|
2805
|
+
const handler = this.progressHandlers.get(p.progressToken);
|
|
2806
|
+
if (!handler) return;
|
|
2807
|
+
handler({ progress: p.progress, total: p.total, message: p.message });
|
|
2808
|
+
}
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2417
2811
|
if (!("result" in msg) && !("error" in msg)) return;
|
|
2418
2812
|
const pending = this.pending.get(msg.id);
|
|
2419
2813
|
if (!pending) return;
|
|
@@ -2470,12 +2864,12 @@ var StdioTransport = class {
|
|
|
2470
2864
|
}
|
|
2471
2865
|
async send(message) {
|
|
2472
2866
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
2473
|
-
return new Promise((
|
|
2867
|
+
return new Promise((resolve5, reject) => {
|
|
2474
2868
|
const line = `${JSON.stringify(message)}
|
|
2475
2869
|
`;
|
|
2476
2870
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
2477
2871
|
if (err) reject(err);
|
|
2478
|
-
else
|
|
2872
|
+
else resolve5();
|
|
2479
2873
|
});
|
|
2480
2874
|
});
|
|
2481
2875
|
}
|
|
@@ -2486,8 +2880,8 @@ var StdioTransport = class {
|
|
|
2486
2880
|
continue;
|
|
2487
2881
|
}
|
|
2488
2882
|
if (this.closed) return;
|
|
2489
|
-
const next = await new Promise((
|
|
2490
|
-
this.waiters.push(
|
|
2883
|
+
const next = await new Promise((resolve5) => {
|
|
2884
|
+
this.waiters.push(resolve5);
|
|
2491
2885
|
});
|
|
2492
2886
|
if (next === null) return;
|
|
2493
2887
|
yield next;
|
|
@@ -2553,8 +2947,8 @@ var SseTransport = class {
|
|
|
2553
2947
|
constructor(opts) {
|
|
2554
2948
|
this.url = opts.url;
|
|
2555
2949
|
this.headers = opts.headers ?? {};
|
|
2556
|
-
this.endpointReady = new Promise((
|
|
2557
|
-
this.resolveEndpoint =
|
|
2950
|
+
this.endpointReady = new Promise((resolve5, reject) => {
|
|
2951
|
+
this.resolveEndpoint = resolve5;
|
|
2558
2952
|
this.rejectEndpoint = reject;
|
|
2559
2953
|
});
|
|
2560
2954
|
this.endpointReady.catch(() => void 0);
|
|
@@ -2581,8 +2975,8 @@ var SseTransport = class {
|
|
|
2581
2975
|
continue;
|
|
2582
2976
|
}
|
|
2583
2977
|
if (this.closed) return;
|
|
2584
|
-
const next = await new Promise((
|
|
2585
|
-
this.waiters.push(
|
|
2978
|
+
const next = await new Promise((resolve5) => {
|
|
2979
|
+
this.waiters.push(resolve5);
|
|
2586
2980
|
});
|
|
2587
2981
|
if (next === null) return;
|
|
2588
2982
|
yield next;
|
|
@@ -2750,9 +3144,39 @@ function parseMcpSpec(input) {
|
|
|
2750
3144
|
return { transport: "stdio", name, command, args };
|
|
2751
3145
|
}
|
|
2752
3146
|
|
|
3147
|
+
// src/mcp/inspect.ts
|
|
3148
|
+
async function inspectMcpServer(client) {
|
|
3149
|
+
const tools = await trySection(() => client.listTools().then((r) => r.tools));
|
|
3150
|
+
const resources = await trySection(
|
|
3151
|
+
() => client.listResources().then((r) => r.resources)
|
|
3152
|
+
);
|
|
3153
|
+
const prompts = await trySection(() => client.listPrompts().then((r) => r.prompts));
|
|
3154
|
+
return {
|
|
3155
|
+
protocolVersion: client.protocolVersion || "(unknown)",
|
|
3156
|
+
serverInfo: client.serverInfo,
|
|
3157
|
+
capabilities: client.serverCapabilities ?? {},
|
|
3158
|
+
instructions: client.serverInstructions,
|
|
3159
|
+
tools,
|
|
3160
|
+
resources,
|
|
3161
|
+
prompts
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
async function trySection(load) {
|
|
3165
|
+
try {
|
|
3166
|
+
const items = await load();
|
|
3167
|
+
return { supported: true, items };
|
|
3168
|
+
} catch (err) {
|
|
3169
|
+
const msg = err.message ?? String(err);
|
|
3170
|
+
if (/-32601/.test(msg) || /method not found/i.test(msg)) {
|
|
3171
|
+
return { supported: false, reason: "method not found (-32601)" };
|
|
3172
|
+
}
|
|
3173
|
+
return { supported: false, reason: msg };
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
|
|
2753
3177
|
// src/code/edit-blocks.ts
|
|
2754
3178
|
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2755
|
-
import { dirname as
|
|
3179
|
+
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
2756
3180
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
2757
3181
|
function parseEditBlocks(text) {
|
|
2758
3182
|
const out = [];
|
|
@@ -2770,8 +3194,8 @@ function parseEditBlocks(text) {
|
|
|
2770
3194
|
return out;
|
|
2771
3195
|
}
|
|
2772
3196
|
function applyEditBlock(block, rootDir) {
|
|
2773
|
-
const absRoot =
|
|
2774
|
-
const absTarget =
|
|
3197
|
+
const absRoot = resolve3(rootDir);
|
|
3198
|
+
const absTarget = resolve3(absRoot, block.path);
|
|
2775
3199
|
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
|
|
2776
3200
|
return {
|
|
2777
3201
|
path: block.path,
|
|
@@ -2790,7 +3214,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
2790
3214
|
message: "file does not exist; to create it, use an empty SEARCH block"
|
|
2791
3215
|
};
|
|
2792
3216
|
}
|
|
2793
|
-
mkdirSync3(
|
|
3217
|
+
mkdirSync3(dirname4(absTarget), { recursive: true });
|
|
2794
3218
|
writeFileSync3(absTarget, block.replace, "utf8");
|
|
2795
3219
|
return { path: block.path, status: "created" };
|
|
2796
3220
|
}
|
|
@@ -2821,13 +3245,13 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
2821
3245
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
2822
3246
|
}
|
|
2823
3247
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
2824
|
-
const absRoot =
|
|
3248
|
+
const absRoot = resolve3(rootDir);
|
|
2825
3249
|
const seen = /* @__PURE__ */ new Set();
|
|
2826
3250
|
const snapshots = [];
|
|
2827
3251
|
for (const b of blocks) {
|
|
2828
3252
|
if (seen.has(b.path)) continue;
|
|
2829
3253
|
seen.add(b.path);
|
|
2830
|
-
const abs =
|
|
3254
|
+
const abs = resolve3(absRoot, b.path);
|
|
2831
3255
|
if (!existsSync2(abs)) {
|
|
2832
3256
|
snapshots.push({ path: b.path, prevContent: null });
|
|
2833
3257
|
continue;
|
|
@@ -2841,9 +3265,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
2841
3265
|
return snapshots;
|
|
2842
3266
|
}
|
|
2843
3267
|
function restoreSnapshots(snapshots, rootDir) {
|
|
2844
|
-
const absRoot =
|
|
3268
|
+
const absRoot = resolve3(rootDir);
|
|
2845
3269
|
return snapshots.map((snap) => {
|
|
2846
|
-
const abs =
|
|
3270
|
+
const abs = resolve3(absRoot, snap.path);
|
|
2847
3271
|
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
|
|
2848
3272
|
return {
|
|
2849
3273
|
path: snap.path,
|
|
@@ -2880,11 +3304,11 @@ var VERSION = "0.4.3";
|
|
|
2880
3304
|
|
|
2881
3305
|
// src/cli/commands/chat.tsx
|
|
2882
3306
|
import { render } from "ink";
|
|
2883
|
-
import
|
|
3307
|
+
import React9, { useState as useState5 } from "react";
|
|
2884
3308
|
|
|
2885
3309
|
// src/cli/ui/App.tsx
|
|
2886
|
-
import { Box as
|
|
2887
|
-
import
|
|
3310
|
+
import { Box as Box7, Static, Text as Text7, useApp, useInput as useInput2 } from "ink";
|
|
3311
|
+
import React7, { useCallback, useEffect as useEffect3, useMemo, useRef, useState as useState3 } from "react";
|
|
2888
3312
|
|
|
2889
3313
|
// src/cli/ui/EventLog.tsx
|
|
2890
3314
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -3223,9 +3647,44 @@ function truncate2(s, max) {
|
|
|
3223
3647
|
}
|
|
3224
3648
|
|
|
3225
3649
|
// src/cli/ui/PromptInput.tsx
|
|
3226
|
-
import { Box as Box4, Text as Text4 } from "ink";
|
|
3227
|
-
import
|
|
3228
|
-
|
|
3650
|
+
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
3651
|
+
import React4, { useEffect as useEffect2, useState as useState2 } from "react";
|
|
3652
|
+
|
|
3653
|
+
// src/cli/ui/multiline-keys.ts
|
|
3654
|
+
var BACKSLASH_SUFFIX = /\\$/;
|
|
3655
|
+
function processMultilineKey(value, key) {
|
|
3656
|
+
if (key.tab || key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.escape || key.pageUp || key.pageDown) {
|
|
3657
|
+
return { next: null, submit: false };
|
|
3658
|
+
}
|
|
3659
|
+
if (key.input === "\n" || key.ctrl && key.input === "j") {
|
|
3660
|
+
return { next: `${value}
|
|
3661
|
+
`, submit: false };
|
|
3662
|
+
}
|
|
3663
|
+
if (key.return) {
|
|
3664
|
+
if (key.shift) {
|
|
3665
|
+
return { next: `${value}
|
|
3666
|
+
`, submit: false };
|
|
3667
|
+
}
|
|
3668
|
+
if (BACKSLASH_SUFFIX.test(value)) {
|
|
3669
|
+
return { next: `${value.slice(0, -1)}
|
|
3670
|
+
`, submit: false };
|
|
3671
|
+
}
|
|
3672
|
+
return { next: null, submit: true, submitValue: value };
|
|
3673
|
+
}
|
|
3674
|
+
if (key.backspace || key.delete) {
|
|
3675
|
+
if (value.length === 0) return { next: null, submit: false };
|
|
3676
|
+
return { next: value.slice(0, -1), submit: false };
|
|
3677
|
+
}
|
|
3678
|
+
if ((key.ctrl || key.meta) && key.input.length === 0) {
|
|
3679
|
+
return { next: null, submit: false };
|
|
3680
|
+
}
|
|
3681
|
+
if (key.input.length > 0 && !key.ctrl && !key.meta) {
|
|
3682
|
+
return { next: value + key.input, submit: false };
|
|
3683
|
+
}
|
|
3684
|
+
return { next: null, submit: false };
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
// src/cli/ui/PromptInput.tsx
|
|
3229
3688
|
function PromptInput({
|
|
3230
3689
|
value,
|
|
3231
3690
|
onChange,
|
|
@@ -3233,22 +3692,87 @@ function PromptInput({
|
|
|
3233
3692
|
disabled,
|
|
3234
3693
|
placeholder
|
|
3235
3694
|
}) {
|
|
3236
|
-
const
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
onChange,
|
|
3242
|
-
onSubmit,
|
|
3243
|
-
focus: !disabled,
|
|
3244
|
-
placeholder: effectivePlaceholder
|
|
3695
|
+
const [showCursor, setShowCursor] = useState2(true);
|
|
3696
|
+
useEffect2(() => {
|
|
3697
|
+
if (disabled) {
|
|
3698
|
+
setShowCursor(false);
|
|
3699
|
+
return;
|
|
3245
3700
|
}
|
|
3246
|
-
|
|
3701
|
+
setShowCursor(true);
|
|
3702
|
+
const id = setInterval(() => setShowCursor((s) => !s), 500);
|
|
3703
|
+
return () => clearInterval(id);
|
|
3704
|
+
}, [disabled]);
|
|
3705
|
+
useInput(
|
|
3706
|
+
(input, key) => {
|
|
3707
|
+
const keyEvent = {
|
|
3708
|
+
input,
|
|
3709
|
+
return: key.return,
|
|
3710
|
+
shift: key.shift,
|
|
3711
|
+
ctrl: key.ctrl,
|
|
3712
|
+
meta: key.meta,
|
|
3713
|
+
backspace: key.backspace,
|
|
3714
|
+
delete: key.delete,
|
|
3715
|
+
tab: key.tab,
|
|
3716
|
+
upArrow: key.upArrow,
|
|
3717
|
+
downArrow: key.downArrow,
|
|
3718
|
+
leftArrow: key.leftArrow,
|
|
3719
|
+
rightArrow: key.rightArrow,
|
|
3720
|
+
escape: key.escape,
|
|
3721
|
+
pageUp: key.pageUp,
|
|
3722
|
+
pageDown: key.pageDown
|
|
3723
|
+
};
|
|
3724
|
+
const action = processMultilineKey(value, keyEvent);
|
|
3725
|
+
if (action.next !== null) onChange(action.next);
|
|
3726
|
+
if (action.submit) onSubmit(action.submitValue ?? value);
|
|
3727
|
+
},
|
|
3728
|
+
{ isActive: !disabled }
|
|
3729
|
+
);
|
|
3730
|
+
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? "type a message, or /command \xB7 Ctrl+J for newline";
|
|
3731
|
+
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
3732
|
+
const borderColor = disabled ? "gray" : "cyan";
|
|
3733
|
+
return /* @__PURE__ */ React4.createElement(Box4, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
3734
|
+
const isLast = i === lines.length - 1;
|
|
3735
|
+
const isFirst = i === 0;
|
|
3736
|
+
const showPlaceholder = isFirst && value.length === 0;
|
|
3737
|
+
return (
|
|
3738
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
3739
|
+
/* @__PURE__ */ React4.createElement(Box4, { key: i }, isFirst ? /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " "), showPlaceholder && isLast && !disabled ? /* @__PURE__ */ React4.createElement(Text4, { color: borderColor }, showCursor ? "\u258C" : " ") : null, showPlaceholder ? /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, effectivePlaceholder) : /* @__PURE__ */ React4.createElement(Text4, null, line), !showPlaceholder && isLast && !disabled ? /* @__PURE__ */ React4.createElement(Text4, { color: borderColor }, showCursor ? "\u258C" : " ") : null)
|
|
3740
|
+
);
|
|
3741
|
+
}));
|
|
3247
3742
|
}
|
|
3248
3743
|
|
|
3249
|
-
// src/cli/ui/
|
|
3744
|
+
// src/cli/ui/SlashSuggestions.tsx
|
|
3250
3745
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
3251
3746
|
import React5 from "react";
|
|
3747
|
+
function SlashSuggestions({
|
|
3748
|
+
matches,
|
|
3749
|
+
selectedIndex
|
|
3750
|
+
}) {
|
|
3751
|
+
if (matches === null) return null;
|
|
3752
|
+
if (matches.length === 0) {
|
|
3753
|
+
return /* @__PURE__ */ React5.createElement(Box5, { paddingX: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, "no slash command matches that prefix"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \u2014 Backspace to edit, or /help for the full list"));
|
|
3754
|
+
}
|
|
3755
|
+
const MAX = 8;
|
|
3756
|
+
const total = matches.length;
|
|
3757
|
+
const windowStart = total <= MAX ? 0 : Math.max(0, Math.min(selectedIndex - Math.floor(MAX / 2), total - MAX));
|
|
3758
|
+
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
3759
|
+
const hiddenAbove = windowStart;
|
|
3760
|
+
const hiddenBelow = total - windowStart - shown.length;
|
|
3761
|
+
return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", paddingX: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((spec, i) => /* @__PURE__ */ React5.createElement(SuggestionRow, { key: spec.cmd, spec, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \u2191/\u2193 navigate \xB7 Tab or Enter to pick"));
|
|
3762
|
+
}
|
|
3763
|
+
function SuggestionRow({ spec, isSelected }) {
|
|
3764
|
+
const marker = isSelected ? "\u25B8" : " ";
|
|
3765
|
+
const name = `/${spec.cmd}`;
|
|
3766
|
+
const argsSuffix = spec.argsHint ? ` ${spec.argsHint}` : "";
|
|
3767
|
+
if (isSelected) {
|
|
3768
|
+
return /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "cyan" }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16)), /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, " ", spec.summary));
|
|
3769
|
+
}
|
|
3770
|
+
return /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, marker, " ", name.padEnd(12), argsSuffix.padEnd(16), " ", spec.summary));
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
// src/cli/ui/StatsPanel.tsx
|
|
3774
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
3775
|
+
import React6 from "react";
|
|
3252
3776
|
function StatsPanel({
|
|
3253
3777
|
summary,
|
|
3254
3778
|
model,
|
|
@@ -3262,7 +3786,7 @@ function StatsPanel({
|
|
|
3262
3786
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
3263
3787
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
3264
3788
|
const ctxColor = ctxRatio >= 0.8 ? "red" : ctxRatio >= 0.5 ? "yellow" : void 0;
|
|
3265
|
-
return /* @__PURE__ */
|
|
3789
|
+
return /* @__PURE__ */ React6.createElement(Box6, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React6.createElement(Box6, { justifyContent: "space-between" }, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, model), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, " \xB7 branch", branchBudget) : null), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "cache hit "), /* @__PURE__ */ React6.createElement(Text6, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "cost "), /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, "$", summary.totalCostUsd.toFixed(6))), /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "vs Claude "), /* @__PURE__ */ React6.createElement(Text6, null, "$", summary.claudeEquivalentUsd.toFixed(6))), /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "saving "), /* @__PURE__ */ React6.createElement(Text6, { color: "green", bold: true }, summary.savingsVsClaudePct.toFixed(1), "%")), summary.lastPromptTokens > 0 ? /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "ctx "), /* @__PURE__ */ React6.createElement(Text6, { color: ctxColor, bold: ctxColor !== void 0 }, formatTokens(summary.lastPromptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (", (ctxRatio * 100).toFixed(0), "%)"), ctxRatio >= 0.8 ? /* @__PURE__ */ React6.createElement(Text6, { color: "red", bold: true }, " ", "\xB7 /compact") : null) : null));
|
|
3266
3790
|
}
|
|
3267
3791
|
function formatTokens(n) {
|
|
3268
3792
|
if (n < 1e3) return String(n);
|
|
@@ -3272,6 +3796,45 @@ function formatTokens(n) {
|
|
|
3272
3796
|
|
|
3273
3797
|
// src/cli/ui/slash.ts
|
|
3274
3798
|
import { spawnSync } from "child_process";
|
|
3799
|
+
var SLASH_COMMANDS = [
|
|
3800
|
+
{ cmd: "help", summary: "show the full command reference" },
|
|
3801
|
+
{ cmd: "status", summary: "current model, flags, context, session" },
|
|
3802
|
+
{
|
|
3803
|
+
cmd: "preset",
|
|
3804
|
+
argsHint: "<fast|smart|max>",
|
|
3805
|
+
summary: "one-tap model + harvest + branch bundle"
|
|
3806
|
+
},
|
|
3807
|
+
{ cmd: "model", argsHint: "<id>", summary: "switch DeepSeek model id" },
|
|
3808
|
+
{ cmd: "harvest", argsHint: "[on|off]", summary: "toggle Pillar-2 plan-state extraction" },
|
|
3809
|
+
{ cmd: "branch", argsHint: "<N|off>", summary: "run N parallel samples per turn (N>=2)" },
|
|
3810
|
+
{ cmd: "mcp", summary: "list MCP servers + tools attached to this session" },
|
|
3811
|
+
{ cmd: "tool", argsHint: "[N]", summary: "dump full output of the Nth tool call (1=latest)" },
|
|
3812
|
+
{ cmd: "think", summary: "dump the last turn's full R1 reasoning (reasoner only)" },
|
|
3813
|
+
{ cmd: "retry", summary: "truncate & resend your last message (fresh sample)" },
|
|
3814
|
+
{ cmd: "compact", argsHint: "[cap]", summary: "shrink oversized tool results in the log" },
|
|
3815
|
+
{ cmd: "sessions", summary: "list saved sessions (current marked with \u25B8)" },
|
|
3816
|
+
{ cmd: "forget", summary: "delete the current session from disk" },
|
|
3817
|
+
{ cmd: "setup", summary: "reminds you to exit and run `reasonix setup`" },
|
|
3818
|
+
{ cmd: "clear", summary: "clear the visible scrollback (log + session kept)" },
|
|
3819
|
+
{ cmd: "exit", summary: "quit the TUI" },
|
|
3820
|
+
// Code-mode only
|
|
3821
|
+
{ cmd: "apply", summary: "commit pending edit blocks to disk", contextual: "code" },
|
|
3822
|
+
{ cmd: "discard", summary: "drop pending edit blocks without writing", contextual: "code" },
|
|
3823
|
+
{ cmd: "undo", summary: "roll back the last applied edit batch", contextual: "code" },
|
|
3824
|
+
{
|
|
3825
|
+
cmd: "commit",
|
|
3826
|
+
argsHint: '"msg"',
|
|
3827
|
+
summary: "git add -A && git commit -m ...",
|
|
3828
|
+
contextual: "code"
|
|
3829
|
+
}
|
|
3830
|
+
];
|
|
3831
|
+
function suggestSlashCommands(prefix, codeMode = false) {
|
|
3832
|
+
const p = prefix.toLowerCase();
|
|
3833
|
+
return SLASH_COMMANDS.filter((c) => {
|
|
3834
|
+
if (c.contextual === "code" && !codeMode) return false;
|
|
3835
|
+
return c.cmd.startsWith(p);
|
|
3836
|
+
});
|
|
3837
|
+
}
|
|
3275
3838
|
function parseSlash(text) {
|
|
3276
3839
|
if (!text.startsWith("/")) return null;
|
|
3277
3840
|
const parts = text.slice(1).trim().split(/\s+/);
|
|
@@ -3323,13 +3886,34 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
3323
3886
|
].join("\n")
|
|
3324
3887
|
};
|
|
3325
3888
|
case "mcp": {
|
|
3889
|
+
const servers = ctx.mcpServers ?? [];
|
|
3326
3890
|
const specs = ctx.mcpSpecs ?? [];
|
|
3327
3891
|
const toolSpecs = loop.prefix.toolSpecs ?? [];
|
|
3328
|
-
if (specs.length === 0 && toolSpecs.length === 0) {
|
|
3892
|
+
if (servers.length === 0 && specs.length === 0 && toolSpecs.length === 0) {
|
|
3329
3893
|
return {
|
|
3330
3894
|
info: 'no MCP servers attached. Run `reasonix setup` to pick some, or launch with --mcp "<spec>". `reasonix mcp list` shows the catalog.'
|
|
3331
3895
|
};
|
|
3332
3896
|
}
|
|
3897
|
+
if (servers.length > 0) {
|
|
3898
|
+
const lines2 = [];
|
|
3899
|
+
for (const s of servers) {
|
|
3900
|
+
const { report } = s;
|
|
3901
|
+
const serverName = report.serverInfo.name || "(unknown)";
|
|
3902
|
+
const serverVer = report.serverInfo.version ? ` v${report.serverInfo.version}` : "";
|
|
3903
|
+
lines2.push(`[${s.label}] ${serverName}${serverVer} \u2014 ${s.spec}`);
|
|
3904
|
+
lines2.push(` tools ${s.toolCount}`);
|
|
3905
|
+
appendSection(lines2, "resources", report.resources);
|
|
3906
|
+
appendSection(lines2, "prompts ", report.prompts);
|
|
3907
|
+
lines2.push("");
|
|
3908
|
+
}
|
|
3909
|
+
lines2.push(
|
|
3910
|
+
"Chat mode consumes tools today; resources+prompts are surfaced here for awareness."
|
|
3911
|
+
);
|
|
3912
|
+
lines2.push(
|
|
3913
|
+
"Full catalog: `reasonix mcp list` \xB7 deeper diagnosis: `reasonix mcp inspect <spec>`."
|
|
3914
|
+
);
|
|
3915
|
+
return { info: lines2.join("\n") };
|
|
3916
|
+
}
|
|
3333
3917
|
const lines = [];
|
|
3334
3918
|
if (specs.length > 0) {
|
|
3335
3919
|
lines.push(`MCP servers (${specs.length}):`);
|
|
@@ -3559,6 +4143,22 @@ ${entry.text}`
|
|
|
3559
4143
|
return { unknown: true, info: `unknown command: /${cmd} (try /help)` };
|
|
3560
4144
|
}
|
|
3561
4145
|
}
|
|
4146
|
+
function appendSection(lines, label, section) {
|
|
4147
|
+
if (!section || !section.supported) {
|
|
4148
|
+
lines.push(
|
|
4149
|
+
` ${label.trim()} ${section?.supported === false ? "(not supported)" : "(none)"}`
|
|
4150
|
+
);
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
const names = section.items.map((i) => i.name);
|
|
4154
|
+
if (names.length === 0) {
|
|
4155
|
+
lines.push(` ${label.trim()} (none)`);
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
4158
|
+
const head = names.slice(0, 5).join(", ");
|
|
4159
|
+
const more = names.length > 5 ? ` +${names.length - 5} more` : "";
|
|
4160
|
+
lines.push(` ${label.trim()} ${names.length} [${head}${more}]`);
|
|
4161
|
+
}
|
|
3562
4162
|
function formatToolList(history) {
|
|
3563
4163
|
const total = history.length;
|
|
3564
4164
|
const header = `Tool calls in this session (${total}, most recent first):`;
|
|
@@ -3631,21 +4231,25 @@ function App({
|
|
|
3631
4231
|
session,
|
|
3632
4232
|
tools,
|
|
3633
4233
|
mcpSpecs,
|
|
4234
|
+
mcpServers,
|
|
4235
|
+
progressSink,
|
|
3634
4236
|
codeMode
|
|
3635
4237
|
}) {
|
|
3636
4238
|
const { exit } = useApp();
|
|
3637
|
-
const [historical, setHistorical] =
|
|
3638
|
-
const [streaming, setStreaming] =
|
|
3639
|
-
const [input, setInput] =
|
|
3640
|
-
const [busy, setBusy] =
|
|
4239
|
+
const [historical, setHistorical] = useState3([]);
|
|
4240
|
+
const [streaming, setStreaming] = useState3(null);
|
|
4241
|
+
const [input, setInput] = useState3("");
|
|
4242
|
+
const [busy, setBusy] = useState3(false);
|
|
3641
4243
|
const abortedThisTurn = useRef(false);
|
|
3642
|
-
const [ongoingTool, setOngoingTool] =
|
|
4244
|
+
const [ongoingTool, setOngoingTool] = useState3(null);
|
|
4245
|
+
const [toolProgress, setToolProgress] = useState3(null);
|
|
3643
4246
|
const lastEditSnapshots = useRef(null);
|
|
3644
4247
|
const pendingEdits = useRef([]);
|
|
3645
4248
|
const promptHistory = useRef([]);
|
|
3646
4249
|
const historyCursor = useRef(-1);
|
|
3647
4250
|
const toolHistoryRef = useRef([]);
|
|
3648
|
-
const [
|
|
4251
|
+
const [slashSelected, setSlashSelected] = useState3(0);
|
|
4252
|
+
const [summary, setSummary] = useState3({
|
|
3649
4253
|
turns: 0,
|
|
3650
4254
|
totalCostUsd: 0,
|
|
3651
4255
|
claudeEquivalentUsd: 0,
|
|
@@ -3662,11 +4266,22 @@ function App({
|
|
|
3662
4266
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3663
4267
|
});
|
|
3664
4268
|
}
|
|
3665
|
-
|
|
4269
|
+
useEffect3(() => {
|
|
3666
4270
|
return () => {
|
|
3667
4271
|
transcriptRef.current?.end();
|
|
3668
4272
|
};
|
|
3669
4273
|
}, []);
|
|
4274
|
+
const slashMatches = useMemo(() => {
|
|
4275
|
+
if (!input.startsWith("/") || input.includes(" ")) return null;
|
|
4276
|
+
return suggestSlashCommands(input.slice(1), !!codeMode);
|
|
4277
|
+
}, [input, codeMode]);
|
|
4278
|
+
useEffect3(() => {
|
|
4279
|
+
setSlashSelected((prev) => {
|
|
4280
|
+
if (!slashMatches || slashMatches.length === 0) return 0;
|
|
4281
|
+
if (prev >= slashMatches.length) return slashMatches.length - 1;
|
|
4282
|
+
return prev;
|
|
4283
|
+
});
|
|
4284
|
+
}, [slashMatches]);
|
|
3670
4285
|
const loopRef = useRef(null);
|
|
3671
4286
|
const loop = useMemo(() => {
|
|
3672
4287
|
if (loopRef.current) return loopRef.current;
|
|
@@ -3679,8 +4294,21 @@ function App({
|
|
|
3679
4294
|
loopRef.current = l;
|
|
3680
4295
|
return l;
|
|
3681
4296
|
}, [model, system, harvest2, branch, session, tools]);
|
|
4297
|
+
useEffect3(() => {
|
|
4298
|
+
if (!progressSink) return;
|
|
4299
|
+
progressSink.current = (info) => {
|
|
4300
|
+
setToolProgress({
|
|
4301
|
+
progress: info.progress,
|
|
4302
|
+
total: info.total,
|
|
4303
|
+
message: info.message
|
|
4304
|
+
});
|
|
4305
|
+
};
|
|
4306
|
+
return () => {
|
|
4307
|
+
if (progressSink.current) progressSink.current = null;
|
|
4308
|
+
};
|
|
4309
|
+
}, [progressSink]);
|
|
3682
4310
|
const sessionBannerShown = useRef(false);
|
|
3683
|
-
|
|
4311
|
+
useEffect3(() => {
|
|
3684
4312
|
if (sessionBannerShown.current) return;
|
|
3685
4313
|
sessionBannerShown.current = true;
|
|
3686
4314
|
if (!session) {
|
|
@@ -3712,7 +4340,7 @@ function App({
|
|
|
3712
4340
|
]);
|
|
3713
4341
|
}
|
|
3714
4342
|
}, [session, loop]);
|
|
3715
|
-
|
|
4343
|
+
useInput2((_input, key) => {
|
|
3716
4344
|
if (key.escape && busy) {
|
|
3717
4345
|
if (abortedThisTurn.current) return;
|
|
3718
4346
|
abortedThisTurn.current = true;
|
|
@@ -3720,6 +4348,21 @@ function App({
|
|
|
3720
4348
|
return;
|
|
3721
4349
|
}
|
|
3722
4350
|
if (busy) return;
|
|
4351
|
+
if (slashMatches && slashMatches.length > 0) {
|
|
4352
|
+
if (key.upArrow) {
|
|
4353
|
+
setSlashSelected((i) => Math.max(0, i - 1));
|
|
4354
|
+
return;
|
|
4355
|
+
}
|
|
4356
|
+
if (key.downArrow) {
|
|
4357
|
+
setSlashSelected((i) => Math.min(slashMatches.length - 1, i + 1));
|
|
4358
|
+
return;
|
|
4359
|
+
}
|
|
4360
|
+
if (key.tab) {
|
|
4361
|
+
const sel = slashMatches[slashSelected] ?? slashMatches[0];
|
|
4362
|
+
if (sel) setInput(`/${sel.cmd}`);
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
3723
4366
|
const hist = promptHistory.current;
|
|
3724
4367
|
if (key.upArrow) {
|
|
3725
4368
|
if (hist.length === 0) return;
|
|
@@ -3778,6 +4421,15 @@ function App({
|
|
|
3778
4421
|
async (raw) => {
|
|
3779
4422
|
let text = raw.trim();
|
|
3780
4423
|
if (!text || busy) return;
|
|
4424
|
+
if (text.startsWith("/") && !text.includes(" ")) {
|
|
4425
|
+
const typed = text.slice(1).toLowerCase();
|
|
4426
|
+
const matches = suggestSlashCommands(typed, !!codeMode);
|
|
4427
|
+
const exact = matches.find((m) => m.cmd === typed);
|
|
4428
|
+
if (!exact && matches.length > 0) {
|
|
4429
|
+
const chosen = matches[slashSelected] ?? matches[0];
|
|
4430
|
+
if (chosen) text = `/${chosen.cmd}`;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
3781
4433
|
setInput("");
|
|
3782
4434
|
historyCursor.current = -1;
|
|
3783
4435
|
if (codeMode && pendingEdits.current.length > 0 && (text === "y" || text === "n")) {
|
|
@@ -3790,6 +4442,7 @@ function App({
|
|
|
3790
4442
|
if (slash) {
|
|
3791
4443
|
const result = handleSlash(slash.cmd, slash.args, loop, {
|
|
3792
4444
|
mcpSpecs,
|
|
4445
|
+
mcpServers,
|
|
3793
4446
|
codeUndo: codeMode ? codeUndo : void 0,
|
|
3794
4447
|
codeApply: codeMode ? codeApply : void 0,
|
|
3795
4448
|
codeDiscard: codeMode ? codeDiscard : void 0,
|
|
@@ -3905,9 +4558,11 @@ function App({
|
|
|
3905
4558
|
}
|
|
3906
4559
|
} else if (ev.role === "tool_start") {
|
|
3907
4560
|
setOngoingTool({ name: ev.toolName ?? "?", args: ev.toolArgs });
|
|
4561
|
+
setToolProgress(null);
|
|
3908
4562
|
} else if (ev.role === "tool") {
|
|
3909
4563
|
flush();
|
|
3910
4564
|
setOngoingTool(null);
|
|
4565
|
+
setToolProgress(null);
|
|
3911
4566
|
toolHistoryRef.current.push({
|
|
3912
4567
|
toolName: ev.toolName ?? "?",
|
|
3913
4568
|
text: ev.content
|
|
@@ -3938,13 +4593,26 @@ function App({
|
|
|
3938
4593
|
clearInterval(timer);
|
|
3939
4594
|
setStreaming(null);
|
|
3940
4595
|
setOngoingTool(null);
|
|
4596
|
+
setToolProgress(null);
|
|
3941
4597
|
setSummary(loop.stats.summary());
|
|
3942
4598
|
setBusy(false);
|
|
3943
4599
|
}
|
|
3944
4600
|
},
|
|
3945
|
-
[
|
|
4601
|
+
[
|
|
4602
|
+
busy,
|
|
4603
|
+
codeApply,
|
|
4604
|
+
codeDiscard,
|
|
4605
|
+
codeMode,
|
|
4606
|
+
codeUndo,
|
|
4607
|
+
exit,
|
|
4608
|
+
loop,
|
|
4609
|
+
mcpSpecs,
|
|
4610
|
+
mcpServers,
|
|
4611
|
+
slashSelected,
|
|
4612
|
+
writeTranscript
|
|
4613
|
+
]
|
|
3946
4614
|
);
|
|
3947
|
-
return /* @__PURE__ */
|
|
4615
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(
|
|
3948
4616
|
StatsPanel,
|
|
3949
4617
|
{
|
|
3950
4618
|
summary,
|
|
@@ -3953,12 +4621,15 @@ function App({
|
|
|
3953
4621
|
harvestOn: loop.harvestEnabled,
|
|
3954
4622
|
branchBudget: loop.branchOptions.budget
|
|
3955
4623
|
}
|
|
3956
|
-
), /* @__PURE__ */
|
|
4624
|
+
), /* @__PURE__ */ React7.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React7.createElement(EventRow, { key: item.id, event: item })), streaming ? /* @__PURE__ */ React7.createElement(Box7, { marginY: 1 }, /* @__PURE__ */ React7.createElement(EventRow, { event: streaming })) : null, ongoingTool ? /* @__PURE__ */ React7.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, /* @__PURE__ */ React7.createElement(PromptInput, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: busy }), /* @__PURE__ */ React7.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }));
|
|
3957
4625
|
}
|
|
3958
|
-
function OngoingToolRow({
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
4626
|
+
function OngoingToolRow({
|
|
4627
|
+
tool,
|
|
4628
|
+
progress
|
|
4629
|
+
}) {
|
|
4630
|
+
const [tick, setTick] = useState3(0);
|
|
4631
|
+
const [elapsed, setElapsed] = useState3(0);
|
|
4632
|
+
useEffect3(() => {
|
|
3962
4633
|
const start = Date.now();
|
|
3963
4634
|
const frameId = setInterval(() => setTick((t) => t + 1), 120);
|
|
3964
4635
|
const secId = setInterval(() => setElapsed(Math.floor((Date.now() - start) / 1e3)), 1e3);
|
|
@@ -3969,7 +4640,19 @@ function OngoingToolRow({ tool }) {
|
|
|
3969
4640
|
}, []);
|
|
3970
4641
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3971
4642
|
const summary = summarizeToolArgs(tool.name, tool.args);
|
|
3972
|
-
return /* @__PURE__ */
|
|
4643
|
+
return /* @__PURE__ */ React7.createElement(Box7, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, frames[tick % frames.length]), /* @__PURE__ */ React7.createElement(Text7, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React7.createElement(Box7, { paddingLeft: 2 }, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React7.createElement(Box7, { paddingLeft: 2 }, /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, summary)) : null);
|
|
4644
|
+
}
|
|
4645
|
+
function renderProgressLine(p) {
|
|
4646
|
+
const msg = p.message ? ` ${p.message}` : "";
|
|
4647
|
+
if (p.total && p.total > 0) {
|
|
4648
|
+
const ratio = Math.max(0, Math.min(1, p.progress / p.total));
|
|
4649
|
+
const width = 20;
|
|
4650
|
+
const filled = Math.round(ratio * width);
|
|
4651
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
4652
|
+
const pct2 = (ratio * 100).toFixed(0);
|
|
4653
|
+
return `[${bar}] ${p.progress}/${p.total} ${pct2}%${msg}`;
|
|
4654
|
+
}
|
|
4655
|
+
return `progress: ${p.progress}${msg}`;
|
|
3973
4656
|
}
|
|
3974
4657
|
function summarizeToolArgs(name, args) {
|
|
3975
4658
|
if (!args || args === "{}") return "";
|
|
@@ -4011,9 +4694,6 @@ function summarizeToolArgs(name, args) {
|
|
|
4011
4694
|
}
|
|
4012
4695
|
return args.length > 80 ? `${args.slice(0, 80)}\u2026` : args;
|
|
4013
4696
|
}
|
|
4014
|
-
function CommandStrip({ codeMode }) {
|
|
4015
|
-
return /* @__PURE__ */ React6.createElement(Box6, { paddingX: 2, flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "/help \xB7 /preset ", "<fast|smart|max>", " \xB7 /mcp \xB7 /compact \xB7 /sessions \xB7 /setup \xB7 /clear \xB7 /exit"), codeMode ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, 'code mode: /apply (y) \xB7 /discard (n) \xB7 /undo \xB7 /commit "msg" \u2014 edits NEVER write without /apply') : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\u2191/\u2193 recall prompts \xB7 /retry re-send last \xB7 /think see R1 reasoning \xB7 /tool N full tool output"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "Esc (while thinking) \u2014 abort & summarize what was found so far"));
|
|
4016
|
-
}
|
|
4017
4697
|
function formatEditResults(results) {
|
|
4018
4698
|
const lines = results.map((r) => {
|
|
4019
4699
|
const mark = r.status === "applied" || r.status === "created" ? "\u2713" : "\u2717";
|
|
@@ -4056,12 +4736,12 @@ function describeRepair(repair) {
|
|
|
4056
4736
|
}
|
|
4057
4737
|
|
|
4058
4738
|
// src/cli/ui/Setup.tsx
|
|
4059
|
-
import { Box as
|
|
4060
|
-
import
|
|
4061
|
-
import
|
|
4739
|
+
import { Box as Box8, Text as Text8, useApp as useApp2 } from "ink";
|
|
4740
|
+
import TextInput from "ink-text-input";
|
|
4741
|
+
import React8, { useState as useState4 } from "react";
|
|
4062
4742
|
function Setup({ onReady }) {
|
|
4063
|
-
const [value, setValue] =
|
|
4064
|
-
const [error, setError] =
|
|
4743
|
+
const [value, setValue] = useState4("");
|
|
4744
|
+
const [error, setError] = useState4(null);
|
|
4065
4745
|
const { exit } = useApp2();
|
|
4066
4746
|
const handleSubmit = (raw) => {
|
|
4067
4747
|
const trimmed = raw.trim();
|
|
@@ -4082,8 +4762,8 @@ function Setup({ onReady }) {
|
|
|
4082
4762
|
}
|
|
4083
4763
|
onReady(trimmed);
|
|
4084
4764
|
};
|
|
4085
|
-
return /* @__PURE__ */
|
|
4086
|
-
|
|
4765
|
+
return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React8.createElement(
|
|
4766
|
+
TextInput,
|
|
4087
4767
|
{
|
|
4088
4768
|
value,
|
|
4089
4769
|
onChange: setValue,
|
|
@@ -4091,14 +4771,14 @@ function Setup({ onReady }) {
|
|
|
4091
4771
|
mask: "\u2022",
|
|
4092
4772
|
placeholder: "sk-..."
|
|
4093
4773
|
}
|
|
4094
|
-
)), error ? /* @__PURE__ */
|
|
4774
|
+
)), error ? /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "red" }, error)) : value ? /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "(Type /exit to abort.)")));
|
|
4095
4775
|
}
|
|
4096
4776
|
|
|
4097
4777
|
// src/cli/commands/chat.tsx
|
|
4098
|
-
function Root({ initialKey, tools, mcpSpecs, ...appProps }) {
|
|
4099
|
-
const [key, setKey] =
|
|
4778
|
+
function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appProps }) {
|
|
4779
|
+
const [key, setKey] = useState5(initialKey);
|
|
4100
4780
|
if (!key) {
|
|
4101
|
-
return /* @__PURE__ */
|
|
4781
|
+
return /* @__PURE__ */ React9.createElement(
|
|
4102
4782
|
Setup,
|
|
4103
4783
|
{
|
|
4104
4784
|
onReady: (k) => {
|
|
@@ -4109,7 +4789,7 @@ function Root({ initialKey, tools, mcpSpecs, ...appProps }) {
|
|
|
4109
4789
|
);
|
|
4110
4790
|
}
|
|
4111
4791
|
process.env.DEEPSEEK_API_KEY = key;
|
|
4112
|
-
return /* @__PURE__ */
|
|
4792
|
+
return /* @__PURE__ */ React9.createElement(
|
|
4113
4793
|
App,
|
|
4114
4794
|
{
|
|
4115
4795
|
model: appProps.model,
|
|
@@ -4120,6 +4800,8 @@ function Root({ initialKey, tools, mcpSpecs, ...appProps }) {
|
|
|
4120
4800
|
session: appProps.session,
|
|
4121
4801
|
tools,
|
|
4122
4802
|
mcpSpecs,
|
|
4803
|
+
mcpServers,
|
|
4804
|
+
progressSink,
|
|
4123
4805
|
codeMode: appProps.codeMode
|
|
4124
4806
|
}
|
|
4125
4807
|
);
|
|
@@ -4131,9 +4813,11 @@ async function chatCommand(opts) {
|
|
|
4131
4813
|
const clients = [];
|
|
4132
4814
|
const successfulSpecs = [];
|
|
4133
4815
|
const failedSpecs = [];
|
|
4134
|
-
|
|
4816
|
+
const mcpServers = [];
|
|
4817
|
+
const progressSink = { current: null };
|
|
4818
|
+
let tools = opts.seedTools;
|
|
4135
4819
|
if (requestedSpecs.length > 0) {
|
|
4136
|
-
tools = new ToolRegistry();
|
|
4820
|
+
if (!tools) tools = new ToolRegistry();
|
|
4137
4821
|
for (const raw of requestedSpecs) {
|
|
4138
4822
|
try {
|
|
4139
4823
|
const spec = parseMcpSpec(raw);
|
|
@@ -4141,7 +4825,24 @@ async function chatCommand(opts) {
|
|
|
4141
4825
|
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
4142
4826
|
const mcp2 = new McpClient({ transport });
|
|
4143
4827
|
await mcp2.initialize();
|
|
4144
|
-
const bridge = await bridgeMcpTools(mcp2, {
|
|
4828
|
+
const bridge = await bridgeMcpTools(mcp2, {
|
|
4829
|
+
registry: tools,
|
|
4830
|
+
namePrefix: prefix,
|
|
4831
|
+
onProgress: (info) => progressSink.current?.(info)
|
|
4832
|
+
});
|
|
4833
|
+
let report;
|
|
4834
|
+
try {
|
|
4835
|
+
report = await inspectMcpServer(mcp2);
|
|
4836
|
+
} catch {
|
|
4837
|
+
report = {
|
|
4838
|
+
protocolVersion: mcp2.protocolVersion,
|
|
4839
|
+
serverInfo: mcp2.serverInfo,
|
|
4840
|
+
capabilities: mcp2.serverCapabilities ?? {},
|
|
4841
|
+
tools: { supported: true, items: [] },
|
|
4842
|
+
resources: { supported: false, reason: "inspect failed" },
|
|
4843
|
+
prompts: { supported: false, reason: "inspect failed" }
|
|
4844
|
+
};
|
|
4845
|
+
}
|
|
4145
4846
|
const label = spec.name ?? "anon";
|
|
4146
4847
|
const source = spec.transport === "sse" ? spec.url : `${spec.command} ${spec.args.join(" ")}`;
|
|
4147
4848
|
process.stderr.write(
|
|
@@ -4150,6 +4851,12 @@ async function chatCommand(opts) {
|
|
|
4150
4851
|
);
|
|
4151
4852
|
clients.push(mcp2);
|
|
4152
4853
|
successfulSpecs.push(raw);
|
|
4854
|
+
mcpServers.push({
|
|
4855
|
+
label,
|
|
4856
|
+
spec: raw,
|
|
4857
|
+
toolCount: bridge.registeredNames.length,
|
|
4858
|
+
report
|
|
4859
|
+
});
|
|
4153
4860
|
} catch (err) {
|
|
4154
4861
|
const reason = err.message;
|
|
4155
4862
|
failedSpecs.push({ spec: raw, reason });
|
|
@@ -4160,13 +4867,23 @@ async function chatCommand(opts) {
|
|
|
4160
4867
|
);
|
|
4161
4868
|
}
|
|
4162
4869
|
}
|
|
4163
|
-
if (successfulSpecs.length === 0) {
|
|
4870
|
+
if (successfulSpecs.length === 0 && !opts.seedTools) {
|
|
4164
4871
|
tools = void 0;
|
|
4165
4872
|
}
|
|
4166
4873
|
}
|
|
4167
4874
|
const mcpSpecs = successfulSpecs;
|
|
4168
4875
|
const { waitUntilExit } = render(
|
|
4169
|
-
/* @__PURE__ */
|
|
4876
|
+
/* @__PURE__ */ React9.createElement(
|
|
4877
|
+
Root,
|
|
4878
|
+
{
|
|
4879
|
+
initialKey,
|
|
4880
|
+
tools,
|
|
4881
|
+
mcpSpecs,
|
|
4882
|
+
mcpServers,
|
|
4883
|
+
progressSink,
|
|
4884
|
+
...opts
|
|
4885
|
+
}
|
|
4886
|
+
),
|
|
4170
4887
|
{ exitOnCtrlC: true }
|
|
4171
4888
|
);
|
|
4172
4889
|
try {
|
|
@@ -4177,14 +4894,15 @@ async function chatCommand(opts) {
|
|
|
4177
4894
|
}
|
|
4178
4895
|
|
|
4179
4896
|
// src/cli/commands/code.tsx
|
|
4180
|
-
import { basename, resolve as
|
|
4897
|
+
import { basename, resolve as resolve4 } from "path";
|
|
4181
4898
|
async function codeCommand(opts = {}) {
|
|
4182
4899
|
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-MMANQ36Z.js");
|
|
4183
|
-
const rootDir =
|
|
4900
|
+
const rootDir = resolve4(opts.dir ?? process.cwd());
|
|
4184
4901
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
4185
|
-
const
|
|
4902
|
+
const tools = new ToolRegistry();
|
|
4903
|
+
registerFilesystemTools(tools, { rootDir });
|
|
4186
4904
|
process.stderr.write(
|
|
4187
|
-
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}"
|
|
4905
|
+
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native fs tool(s)
|
|
4188
4906
|
`
|
|
4189
4907
|
);
|
|
4190
4908
|
await chatCommand({
|
|
@@ -4194,46 +4912,43 @@ async function codeCommand(opts = {}) {
|
|
|
4194
4912
|
system: codeSystemPrompt2(rootDir),
|
|
4195
4913
|
transcript: opts.transcript,
|
|
4196
4914
|
session,
|
|
4197
|
-
|
|
4915
|
+
seedTools: tools,
|
|
4198
4916
|
codeMode: { rootDir }
|
|
4199
4917
|
});
|
|
4200
4918
|
}
|
|
4201
|
-
function quoteIfNeeded(s) {
|
|
4202
|
-
return /\s|"/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
|
|
4203
|
-
}
|
|
4204
4919
|
|
|
4205
4920
|
// src/cli/commands/diff.ts
|
|
4206
4921
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
4207
4922
|
import { basename as basename2 } from "path";
|
|
4208
4923
|
import { render as render2 } from "ink";
|
|
4209
|
-
import
|
|
4924
|
+
import React12 from "react";
|
|
4210
4925
|
|
|
4211
4926
|
// src/cli/ui/DiffApp.tsx
|
|
4212
|
-
import { Box as
|
|
4213
|
-
import
|
|
4927
|
+
import { Box as Box10, Static as Static2, Text as Text10, useApp as useApp3, useInput as useInput3 } from "ink";
|
|
4928
|
+
import React11, { useState as useState6 } from "react";
|
|
4214
4929
|
|
|
4215
4930
|
// src/cli/ui/RecordView.tsx
|
|
4216
|
-
import { Box as
|
|
4217
|
-
import
|
|
4931
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
4932
|
+
import React10 from "react";
|
|
4218
4933
|
function RecordView({ rec, compact = false }) {
|
|
4219
4934
|
const toolArgsMax = compact ? 120 : 200;
|
|
4220
4935
|
const toolContentMax = compact ? 200 : 400;
|
|
4221
4936
|
if (rec.role === "user") {
|
|
4222
|
-
return /* @__PURE__ */
|
|
4937
|
+
return /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React10.createElement(Text9, null, rec.content));
|
|
4223
4938
|
}
|
|
4224
4939
|
if (rec.role === "assistant_final") {
|
|
4225
|
-
return /* @__PURE__ */
|
|
4940
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React10.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React10.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React10.createElement(Text9, null, rec.content) : /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, italic: true }, "(tool-call response only)"));
|
|
4226
4941
|
}
|
|
4227
4942
|
if (rec.role === "tool") {
|
|
4228
|
-
return /* @__PURE__ */
|
|
4943
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
|
|
4229
4944
|
}
|
|
4230
4945
|
if (rec.role === "error") {
|
|
4231
|
-
return /* @__PURE__ */
|
|
4946
|
+
return /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React10.createElement(Text9, { color: "red" }, rec.error ?? rec.content));
|
|
4232
4947
|
}
|
|
4233
4948
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
4234
4949
|
return null;
|
|
4235
4950
|
}
|
|
4236
|
-
return /* @__PURE__ */
|
|
4951
|
+
return /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
4237
4952
|
}
|
|
4238
4953
|
function CacheBadge({ usage }) {
|
|
4239
4954
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -4242,7 +4957,7 @@ function CacheBadge({ usage }) {
|
|
|
4242
4957
|
if (total === 0) return null;
|
|
4243
4958
|
const pct2 = hit / total * 100;
|
|
4244
4959
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
4245
|
-
return /* @__PURE__ */
|
|
4960
|
+
return /* @__PURE__ */ React10.createElement(Text9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React10.createElement(Text9, { color }, pct2.toFixed(1), "%"));
|
|
4246
4961
|
}
|
|
4247
4962
|
function truncate3(s, max) {
|
|
4248
4963
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -4253,8 +4968,8 @@ function DiffApp({ report }) {
|
|
|
4253
4968
|
const { exit } = useApp3();
|
|
4254
4969
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
4255
4970
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
4256
|
-
const [idx, setIdx] =
|
|
4257
|
-
|
|
4971
|
+
const [idx, setIdx] = useState6(Math.max(0, initialIdx));
|
|
4972
|
+
useInput3((input, key) => {
|
|
4258
4973
|
if (input === "q" || key.ctrl && input === "c") {
|
|
4259
4974
|
exit();
|
|
4260
4975
|
return;
|
|
@@ -4276,7 +4991,7 @@ function DiffApp({ report }) {
|
|
|
4276
4991
|
}
|
|
4277
4992
|
});
|
|
4278
4993
|
const pair = report.pairs[idx];
|
|
4279
|
-
return /* @__PURE__ */
|
|
4994
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React11.createElement(DiffHeader, { report }), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React11.createElement(Text10, null, pair ? /* @__PURE__ */ React11.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React11.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React11.createElement(Text10, null, pair.divergenceNote)) : null, /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "j"), "/", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "k"), "/", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "N"), "/", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "g"), "/", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React11.createElement(Text10, { bold: true }, "q"), " ", "quit")));
|
|
4280
4995
|
}
|
|
4281
4996
|
function DiffHeader({ report }) {
|
|
4282
4997
|
const a = report.a;
|
|
@@ -4294,15 +5009,15 @@ function DiffHeader({ report }) {
|
|
|
4294
5009
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
4295
5010
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
4296
5011
|
}
|
|
4297
|
-
return /* @__PURE__ */
|
|
5012
|
+
return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Box10, { justifyContent: "space-between" }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React11.createElement(Text10, { color: "blue" }, a.label), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " vs B="), /* @__PURE__ */ React11.createElement(Text10, { color: "magenta" }, b.label)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cache "), /* @__PURE__ */ React11.createElement(Text10, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React11.createElement(Text10, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React11.createElement(Text10, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cost "), /* @__PURE__ */ React11.createElement(Text10, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React11.createElement(Text10, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React11.createElement(Text10, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "model calls "), /* @__PURE__ */ React11.createElement(Text10, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true, italic: true }, prefixLine)) : null);
|
|
4298
5013
|
}
|
|
4299
5014
|
function Pane({
|
|
4300
5015
|
label,
|
|
4301
5016
|
headerColor,
|
|
4302
5017
|
records
|
|
4303
5018
|
}) {
|
|
4304
|
-
return /* @__PURE__ */
|
|
4305
|
-
|
|
5019
|
+
return /* @__PURE__ */ React11.createElement(
|
|
5020
|
+
Box10,
|
|
4306
5021
|
{
|
|
4307
5022
|
flexDirection: "column",
|
|
4308
5023
|
flexGrow: 1,
|
|
@@ -4310,21 +5025,21 @@ function Pane({
|
|
|
4310
5025
|
borderStyle: "single",
|
|
4311
5026
|
borderColor: headerColor
|
|
4312
5027
|
},
|
|
4313
|
-
/* @__PURE__ */
|
|
4314
|
-
records.length === 0 ? /* @__PURE__ */
|
|
5028
|
+
/* @__PURE__ */ React11.createElement(Text10, { color: headerColor, bold: true }, label),
|
|
5029
|
+
records.length === 0 ? /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React11.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React11.createElement(RecordView, { key, rec, compact: true }))
|
|
4315
5030
|
);
|
|
4316
5031
|
}
|
|
4317
5032
|
function KindBadge({ kind }) {
|
|
4318
5033
|
if (kind === "match") {
|
|
4319
|
-
return /* @__PURE__ */
|
|
5034
|
+
return /* @__PURE__ */ React11.createElement(Text10, { color: "green" }, "\u2713 match");
|
|
4320
5035
|
}
|
|
4321
5036
|
if (kind === "diverge") {
|
|
4322
|
-
return /* @__PURE__ */
|
|
5037
|
+
return /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, "\u2605 diverge");
|
|
4323
5038
|
}
|
|
4324
5039
|
if (kind === "only_in_a") {
|
|
4325
|
-
return /* @__PURE__ */
|
|
5040
|
+
return /* @__PURE__ */ React11.createElement(Text10, { color: "blue" }, "\u2190 only in A");
|
|
4326
5041
|
}
|
|
4327
|
-
return /* @__PURE__ */
|
|
5042
|
+
return /* @__PURE__ */ React11.createElement(Text10, { color: "magenta" }, "\u2192 only in B");
|
|
4328
5043
|
}
|
|
4329
5044
|
function paneRecords(pair, side) {
|
|
4330
5045
|
if (!pair) return [];
|
|
@@ -4355,7 +5070,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
4355
5070
|
return;
|
|
4356
5071
|
}
|
|
4357
5072
|
if (wantTui) {
|
|
4358
|
-
const { waitUntilExit } = render2(
|
|
5073
|
+
const { waitUntilExit } = render2(React12.createElement(DiffApp, { report }), {
|
|
4359
5074
|
exitOnCtrlC: true
|
|
4360
5075
|
});
|
|
4361
5076
|
await waitUntilExit();
|
|
@@ -4364,6 +5079,70 @@ markdown report written to ${opts.mdPath}`);
|
|
|
4364
5079
|
console.log(renderSummaryTable(report));
|
|
4365
5080
|
}
|
|
4366
5081
|
|
|
5082
|
+
// src/cli/commands/mcp-inspect.ts
|
|
5083
|
+
async function mcpInspectCommand(opts) {
|
|
5084
|
+
const spec = parseMcpSpec(opts.spec);
|
|
5085
|
+
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
5086
|
+
const client = new McpClient({ transport });
|
|
5087
|
+
try {
|
|
5088
|
+
await client.initialize();
|
|
5089
|
+
const report = await inspectMcpServer(client);
|
|
5090
|
+
if (opts.json) {
|
|
5091
|
+
console.log(JSON.stringify(report, null, 2));
|
|
5092
|
+
} else {
|
|
5093
|
+
console.log(formatReport(spec.name ?? "(anon)", report));
|
|
5094
|
+
}
|
|
5095
|
+
} finally {
|
|
5096
|
+
await client.close();
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
function formatReport(nsName, r) {
|
|
5100
|
+
const lines = [];
|
|
5101
|
+
lines.push(`MCP server [${nsName}]`);
|
|
5102
|
+
lines.push(
|
|
5103
|
+
` server ${r.serverInfo.name || "(unknown)"}${r.serverInfo.version ? ` v${r.serverInfo.version}` : ""}`
|
|
5104
|
+
);
|
|
5105
|
+
lines.push(` protocol ${r.protocolVersion}`);
|
|
5106
|
+
const capKeys = Object.keys(r.capabilities);
|
|
5107
|
+
lines.push(` caps ${capKeys.length > 0 ? capKeys.join(", ") : "(none advertised)"}`);
|
|
5108
|
+
if (r.instructions) {
|
|
5109
|
+
lines.push(` notes ${r.instructions.trim().slice(0, 200)}`);
|
|
5110
|
+
}
|
|
5111
|
+
lines.push("");
|
|
5112
|
+
lines.push(formatSection("Tools", r.tools, toolLine));
|
|
5113
|
+
lines.push(formatSection("Resources", r.resources, resourceLine));
|
|
5114
|
+
lines.push(formatSection("Prompts", r.prompts, promptLine));
|
|
5115
|
+
return lines.join("\n");
|
|
5116
|
+
}
|
|
5117
|
+
function formatSection(title, section, render5) {
|
|
5118
|
+
if (!section.supported) {
|
|
5119
|
+
return `${title}: (not supported \u2014 ${section.reason})`;
|
|
5120
|
+
}
|
|
5121
|
+
if (section.items.length === 0) {
|
|
5122
|
+
return `${title}: (none)`;
|
|
5123
|
+
}
|
|
5124
|
+
const lines = [`${title} (${section.items.length}):`];
|
|
5125
|
+
for (const item of section.items) lines.push(` ${render5(item)}`);
|
|
5126
|
+
return lines.join("\n");
|
|
5127
|
+
}
|
|
5128
|
+
function toolLine(t) {
|
|
5129
|
+
const desc = t.description ? ` \u2014 ${oneLine(t.description, 80)}` : "";
|
|
5130
|
+
return `\xB7 ${t.name}${desc}`;
|
|
5131
|
+
}
|
|
5132
|
+
function resourceLine(r) {
|
|
5133
|
+
const mime = r.mimeType ? ` [${r.mimeType}]` : "";
|
|
5134
|
+
return `\xB7 ${r.name}${mime} ${r.uri}`;
|
|
5135
|
+
}
|
|
5136
|
+
function promptLine(p) {
|
|
5137
|
+
const argPart = p.arguments && p.arguments.length > 0 ? ` (${p.arguments.map((a) => a.required ? a.name : `${a.name}?`).join(", ")})` : "";
|
|
5138
|
+
const desc = p.description ? ` \u2014 ${oneLine(p.description, 80)}` : "";
|
|
5139
|
+
return `\xB7 ${p.name}${argPart}${desc}`;
|
|
5140
|
+
}
|
|
5141
|
+
function oneLine(s, max) {
|
|
5142
|
+
const flat = s.replace(/\s+/g, " ").trim();
|
|
5143
|
+
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}\u2026`;
|
|
5144
|
+
}
|
|
5145
|
+
|
|
4367
5146
|
// src/mcp/catalog.ts
|
|
4368
5147
|
var MCP_CATALOG = [
|
|
4369
5148
|
{
|
|
@@ -4431,16 +5210,16 @@ function pad(s, width) {
|
|
|
4431
5210
|
|
|
4432
5211
|
// src/cli/commands/replay.ts
|
|
4433
5212
|
import { render as render3 } from "ink";
|
|
4434
|
-
import
|
|
5213
|
+
import React14 from "react";
|
|
4435
5214
|
|
|
4436
5215
|
// src/cli/ui/ReplayApp.tsx
|
|
4437
|
-
import { Box as
|
|
4438
|
-
import
|
|
5216
|
+
import { Box as Box11, Static as Static3, Text as Text11, useApp as useApp4, useInput as useInput4 } from "ink";
|
|
5217
|
+
import React13, { useMemo as useMemo2, useState as useState7 } from "react";
|
|
4439
5218
|
function ReplayApp({ meta, pages }) {
|
|
4440
5219
|
const { exit } = useApp4();
|
|
4441
5220
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
4442
|
-
const [idx, setIdx] =
|
|
4443
|
-
|
|
5221
|
+
const [idx, setIdx] = useState7(maxIdx);
|
|
5222
|
+
useInput4((input, key) => {
|
|
4444
5223
|
if (input === "q" || key.ctrl && input === "c") {
|
|
4445
5224
|
exit();
|
|
4446
5225
|
return;
|
|
@@ -4472,14 +5251,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
4472
5251
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
4473
5252
|
const currentPage = pages[idx];
|
|
4474
5253
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
4475
|
-
return /* @__PURE__ */
|
|
5254
|
+
return /* @__PURE__ */ React13.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React13.createElement(
|
|
4476
5255
|
StatsPanel,
|
|
4477
5256
|
{
|
|
4478
5257
|
summary,
|
|
4479
5258
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
4480
5259
|
prefixHash
|
|
4481
5260
|
}
|
|
4482
|
-
), /* @__PURE__ */
|
|
5261
|
+
), /* @__PURE__ */ React13.createElement(Box11, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React13.createElement(Box11, { justifyContent: "space-between" }, /* @__PURE__ */ React13.createElement(Text11, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React13.createElement(Text11, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React13.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React13.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React13.createElement(Text11, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React13.createElement(Box11, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React13.createElement(Text11, { dimColor: true }, /* @__PURE__ */ React13.createElement(Text11, { bold: true }, "j"), "/", /* @__PURE__ */ React13.createElement(Text11, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React13.createElement(Text11, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React13.createElement(Text11, { bold: true }, "k"), "/", /* @__PURE__ */ React13.createElement(Text11, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React13.createElement(Text11, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React13.createElement(Text11, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React13.createElement(Text11, { bold: true }, "q"), " quit")));
|
|
4483
5262
|
}
|
|
4484
5263
|
|
|
4485
5264
|
// src/cli/commands/replay.ts
|
|
@@ -4491,7 +5270,7 @@ async function replayCommand(opts) {
|
|
|
4491
5270
|
}
|
|
4492
5271
|
const { parsed } = replayFromFile(opts.path);
|
|
4493
5272
|
const pages = groupRecordsByTurn(parsed.records);
|
|
4494
|
-
const { waitUntilExit } = render3(
|
|
5273
|
+
const { waitUntilExit } = render3(React14.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
4495
5274
|
exitOnCtrlC: true
|
|
4496
5275
|
});
|
|
4497
5276
|
await waitUntilExit();
|
|
@@ -4543,7 +5322,7 @@ function sliceRecords(records, opts) {
|
|
|
4543
5322
|
function renderRecord(rec) {
|
|
4544
5323
|
const turn = `[t${rec.turn}]`;
|
|
4545
5324
|
if (rec.role === "user") {
|
|
4546
|
-
console.log(`${turn} USER: ${
|
|
5325
|
+
console.log(`${turn} USER: ${oneLine2(rec.content)}`);
|
|
4547
5326
|
} else if (rec.role === "assistant_final") {
|
|
4548
5327
|
const cost = rec.cost !== void 0 ? ` $${rec.cost.toFixed(6)}` : "";
|
|
4549
5328
|
const cache = rec.usage && (rec.usage.prompt_cache_hit_tokens !== void 0 || rec.usage.prompt_cache_miss_tokens !== void 0) ? (() => {
|
|
@@ -4552,7 +5331,7 @@ function renderRecord(rec) {
|
|
|
4552
5331
|
const total = hit + miss;
|
|
4553
5332
|
return total > 0 ? ` cache=${(hit / total * 100).toFixed(1)}%` : "";
|
|
4554
5333
|
})() : "";
|
|
4555
|
-
console.log(`${turn} AGENT:${cost}${cache} ${
|
|
5334
|
+
console.log(`${turn} AGENT:${cost}${cache} ${oneLine2(rec.content)}`);
|
|
4556
5335
|
if (rec.planState) {
|
|
4557
5336
|
const ps = rec.planState;
|
|
4558
5337
|
if (ps.subgoals.length)
|
|
@@ -4569,16 +5348,16 @@ function renderRecord(rec) {
|
|
|
4569
5348
|
);
|
|
4570
5349
|
}
|
|
4571
5350
|
} else if (rec.role === "tool") {
|
|
4572
|
-
const args = rec.args ? ` args=${
|
|
4573
|
-
console.log(`${turn} TOOL ${rec.tool ?? "?"}:${args} \u2192 ${
|
|
5351
|
+
const args = rec.args ? ` args=${oneLine2(rec.args, 80)}` : "";
|
|
5352
|
+
console.log(`${turn} TOOL ${rec.tool ?? "?"}:${args} \u2192 ${oneLine2(rec.content, 120)}`);
|
|
4574
5353
|
} else if (rec.role === "error") {
|
|
4575
5354
|
console.log(`${turn} ERROR: ${rec.error ?? rec.content}`);
|
|
4576
5355
|
} else if (rec.role === "done") {
|
|
4577
5356
|
} else {
|
|
4578
|
-
console.log(`${turn} ${rec.role}: ${
|
|
5357
|
+
console.log(`${turn} ${rec.role}: ${oneLine2(rec.content)}`);
|
|
4579
5358
|
}
|
|
4580
5359
|
}
|
|
4581
|
-
function
|
|
5360
|
+
function oneLine2(s, max = 200) {
|
|
4582
5361
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
4583
5362
|
return collapsed.length > max ? `${collapsed.slice(0, max)}\u2026` : collapsed;
|
|
4584
5363
|
}
|
|
@@ -4764,7 +5543,7 @@ function inspectSession(name, verbose) {
|
|
|
4764
5543
|
function renderMessage(msg, turnIdx, verbose) {
|
|
4765
5544
|
const turn = turnIdx > 0 ? `[t${turnIdx}]` : "[start]";
|
|
4766
5545
|
const content = typeof msg.content === "string" ? msg.content : "";
|
|
4767
|
-
const flat =
|
|
5546
|
+
const flat = oneLine3(content);
|
|
4768
5547
|
if (msg.role === "user") {
|
|
4769
5548
|
console.log(`${turn} USER: ${flat}`);
|
|
4770
5549
|
} else if (msg.role === "assistant") {
|
|
@@ -4782,7 +5561,7 @@ function renderMessage(msg, turnIdx, verbose) {
|
|
|
4782
5561
|
if (verbose) console.log(`${turn} SYSTEM: ${truncate4(flat, 160)}`);
|
|
4783
5562
|
}
|
|
4784
5563
|
}
|
|
4785
|
-
function
|
|
5564
|
+
function oneLine3(s, max = 200) {
|
|
4786
5565
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
4787
5566
|
return collapsed.length > max ? `${collapsed.slice(0, max)}\u2026` : collapsed;
|
|
4788
5567
|
}
|
|
@@ -4792,16 +5571,16 @@ function truncate4(s, max) {
|
|
|
4792
5571
|
|
|
4793
5572
|
// src/cli/commands/setup.tsx
|
|
4794
5573
|
import { render as render4 } from "ink";
|
|
4795
|
-
import
|
|
5574
|
+
import React17 from "react";
|
|
4796
5575
|
|
|
4797
5576
|
// src/cli/ui/Wizard.tsx
|
|
4798
|
-
import { Box as
|
|
4799
|
-
import
|
|
4800
|
-
import
|
|
5577
|
+
import { Box as Box13, Text as Text13, useApp as useApp5, useInput as useInput6 } from "ink";
|
|
5578
|
+
import TextInput2 from "ink-text-input";
|
|
5579
|
+
import React16, { useState as useState9 } from "react";
|
|
4801
5580
|
|
|
4802
5581
|
// src/cli/ui/Select.tsx
|
|
4803
|
-
import { Box as
|
|
4804
|
-
import
|
|
5582
|
+
import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
|
|
5583
|
+
import React15, { useState as useState8 } from "react";
|
|
4805
5584
|
function SingleSelect({
|
|
4806
5585
|
items,
|
|
4807
5586
|
initialValue,
|
|
@@ -4812,8 +5591,8 @@ function SingleSelect({
|
|
|
4812
5591
|
0,
|
|
4813
5592
|
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
4814
5593
|
);
|
|
4815
|
-
const [index, setIndex] =
|
|
4816
|
-
|
|
5594
|
+
const [index, setIndex] = useState8(initialIndex === -1 ? 0 : initialIndex);
|
|
5595
|
+
useInput5((_input, key) => {
|
|
4817
5596
|
if (key.upArrow) {
|
|
4818
5597
|
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4819
5598
|
} else if (key.downArrow) {
|
|
@@ -4825,7 +5604,7 @@ function SingleSelect({
|
|
|
4825
5604
|
onCancel();
|
|
4826
5605
|
}
|
|
4827
5606
|
});
|
|
4828
|
-
return /* @__PURE__ */
|
|
5607
|
+
return /* @__PURE__ */ React15.createElement(Box12, { flexDirection: "column" }, items.map((item, i) => /* @__PURE__ */ React15.createElement(
|
|
4829
5608
|
SelectRow,
|
|
4830
5609
|
{
|
|
4831
5610
|
key: item.value,
|
|
@@ -4842,12 +5621,12 @@ function MultiSelect({
|
|
|
4842
5621
|
onCancel,
|
|
4843
5622
|
footer
|
|
4844
5623
|
}) {
|
|
4845
|
-
const [index, setIndex] =
|
|
5624
|
+
const [index, setIndex] = useState8(() => {
|
|
4846
5625
|
const first = items.findIndex((i) => !i.disabled);
|
|
4847
5626
|
return first === -1 ? 0 : first;
|
|
4848
5627
|
});
|
|
4849
|
-
const [selected, setSelected] =
|
|
4850
|
-
|
|
5628
|
+
const [selected, setSelected] = useState8(new Set(initialSelected));
|
|
5629
|
+
useInput5((input, key) => {
|
|
4851
5630
|
if (key.upArrow) {
|
|
4852
5631
|
setIndex((i) => findNextEnabled(items, i, -1));
|
|
4853
5632
|
} else if (key.downArrow) {
|
|
@@ -4868,10 +5647,10 @@ function MultiSelect({
|
|
|
4868
5647
|
onCancel();
|
|
4869
5648
|
}
|
|
4870
5649
|
});
|
|
4871
|
-
return /* @__PURE__ */
|
|
5650
|
+
return /* @__PURE__ */ React15.createElement(Box12, { flexDirection: "column" }, items.map((item, i) => {
|
|
4872
5651
|
const checked = selected.has(item.value);
|
|
4873
5652
|
const marker = checked ? "[x]" : "[ ]";
|
|
4874
|
-
return /* @__PURE__ */
|
|
5653
|
+
return /* @__PURE__ */ React15.createElement(
|
|
4875
5654
|
SelectRow,
|
|
4876
5655
|
{
|
|
4877
5656
|
key: item.value,
|
|
@@ -4880,7 +5659,7 @@ function MultiSelect({
|
|
|
4880
5659
|
marker: `${i === index ? "\u25B8" : " "} ${marker}`
|
|
4881
5660
|
}
|
|
4882
5661
|
);
|
|
4883
|
-
}), footer ? /* @__PURE__ */
|
|
5662
|
+
}), footer ? /* @__PURE__ */ React15.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React15.createElement(Text12, { dimColor: true }, footer)) : null);
|
|
4884
5663
|
}
|
|
4885
5664
|
function SelectRow({
|
|
4886
5665
|
item,
|
|
@@ -4888,7 +5667,7 @@ function SelectRow({
|
|
|
4888
5667
|
marker
|
|
4889
5668
|
}) {
|
|
4890
5669
|
const color = item.disabled ? "gray" : active ? "cyan" : void 0;
|
|
4891
|
-
return /* @__PURE__ */
|
|
5670
|
+
return /* @__PURE__ */ React15.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React15.createElement(Box12, null, /* @__PURE__ */ React15.createElement(Text12, { color }, marker, " ", item.label)), item.hint ? /* @__PURE__ */ React15.createElement(Box12, { paddingLeft: marker.length + 1 }, /* @__PURE__ */ React15.createElement(Text12, { dimColor: true }, item.hint)) : null);
|
|
4892
5671
|
}
|
|
4893
5672
|
function findNextEnabled(items, from, step) {
|
|
4894
5673
|
if (items.length === 0) return 0;
|
|
@@ -4925,19 +5704,19 @@ var PRESET_DESCRIPTIONS = {
|
|
|
4925
5704
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
4926
5705
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
4927
5706
|
const { exit } = useApp5();
|
|
4928
|
-
const [step, setStep] =
|
|
4929
|
-
const [data, setData] =
|
|
5707
|
+
const [step, setStep] = useState9(existingApiKey ? "preset" : "apiKey");
|
|
5708
|
+
const [data, setData] = useState9({
|
|
4930
5709
|
apiKey: existingApiKey ?? "",
|
|
4931
5710
|
preset: initial?.preset ?? "fast",
|
|
4932
5711
|
selectedCatalog: deriveInitialCatalog(initial?.mcp ?? []),
|
|
4933
5712
|
catalogArgs: {}
|
|
4934
5713
|
});
|
|
4935
|
-
const [error, setError] =
|
|
4936
|
-
|
|
5714
|
+
const [error, setError] = useState9(null);
|
|
5715
|
+
useInput6((_input, key) => {
|
|
4937
5716
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
4938
5717
|
});
|
|
4939
5718
|
if (step === "apiKey") {
|
|
4940
|
-
return /* @__PURE__ */
|
|
5719
|
+
return /* @__PURE__ */ React16.createElement(
|
|
4941
5720
|
ApiKeyStep,
|
|
4942
5721
|
{
|
|
4943
5722
|
onSubmit: (key) => {
|
|
@@ -4951,7 +5730,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
4951
5730
|
);
|
|
4952
5731
|
}
|
|
4953
5732
|
if (step === "preset") {
|
|
4954
|
-
return /* @__PURE__ */
|
|
5733
|
+
return /* @__PURE__ */ React16.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React16.createElement(
|
|
4955
5734
|
SingleSelect,
|
|
4956
5735
|
{
|
|
4957
5736
|
items: presetItems(),
|
|
@@ -4961,10 +5740,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
4961
5740
|
setStep("mcp");
|
|
4962
5741
|
}
|
|
4963
5742
|
}
|
|
4964
|
-
), /* @__PURE__ */
|
|
5743
|
+
), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "\u2191/\u2193 move \xB7 enter confirm \xB7 esc cancel")));
|
|
4965
5744
|
}
|
|
4966
5745
|
if (step === "mcp") {
|
|
4967
|
-
return /* @__PURE__ */
|
|
5746
|
+
return /* @__PURE__ */ React16.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React16.createElement(
|
|
4968
5747
|
MultiSelect,
|
|
4969
5748
|
{
|
|
4970
5749
|
items: mcpItems(),
|
|
@@ -4989,7 +5768,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
4989
5768
|
}
|
|
4990
5769
|
const currentName = pending[0];
|
|
4991
5770
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
4992
|
-
return /* @__PURE__ */
|
|
5771
|
+
return /* @__PURE__ */ React16.createElement(
|
|
4993
5772
|
McpArgsStep,
|
|
4994
5773
|
{
|
|
4995
5774
|
entry,
|
|
@@ -5007,7 +5786,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
5007
5786
|
}
|
|
5008
5787
|
if (step === "review") {
|
|
5009
5788
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
5010
|
-
return /* @__PURE__ */
|
|
5789
|
+
return /* @__PURE__ */ React16.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React16.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React16.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React16.createElement(
|
|
5011
5790
|
SummaryLine,
|
|
5012
5791
|
{
|
|
5013
5792
|
label: "MCP",
|
|
@@ -5015,8 +5794,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
5015
5794
|
}
|
|
5016
5795
|
), specs.map((spec, i) => (
|
|
5017
5796
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
5018
|
-
/* @__PURE__ */
|
|
5019
|
-
)), /* @__PURE__ */
|
|
5797
|
+
/* @__PURE__ */ React16.createElement(Box13, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "\xB7 ", spec))
|
|
5798
|
+
)), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { color: "red" }, error)) : null, /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "enter save \xB7 esc cancel"))), /* @__PURE__ */ React16.createElement(
|
|
5020
5799
|
ReviewConfirm,
|
|
5021
5800
|
{
|
|
5022
5801
|
onConfirm: () => {
|
|
@@ -5042,16 +5821,16 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
5042
5821
|
}
|
|
5043
5822
|
));
|
|
5044
5823
|
}
|
|
5045
|
-
return /* @__PURE__ */
|
|
5824
|
+
return /* @__PURE__ */ React16.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React16.createElement(Text13, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "Press enter to exit.")), /* @__PURE__ */ React16.createElement(ExitOnEnter, { onExit: exit }));
|
|
5046
5825
|
}
|
|
5047
5826
|
function ApiKeyStep({
|
|
5048
5827
|
onSubmit,
|
|
5049
5828
|
error,
|
|
5050
5829
|
onError
|
|
5051
5830
|
}) {
|
|
5052
|
-
const [value, setValue] =
|
|
5053
|
-
return /* @__PURE__ */
|
|
5054
|
-
|
|
5831
|
+
const [value, setValue] = useState9("");
|
|
5832
|
+
return /* @__PURE__ */ React16.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React16.createElement(Text13, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React16.createElement(
|
|
5833
|
+
TextInput2,
|
|
5055
5834
|
{
|
|
5056
5835
|
value,
|
|
5057
5836
|
onChange: setValue,
|
|
@@ -5067,7 +5846,7 @@ function ApiKeyStep({
|
|
|
5067
5846
|
mask: "\u2022",
|
|
5068
5847
|
placeholder: "sk-..."
|
|
5069
5848
|
}
|
|
5070
|
-
)), error ? /* @__PURE__ */
|
|
5849
|
+
)), error ? /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { color: "red" }, error)) : value ? /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "preview: ", redactKey(value))) : null);
|
|
5071
5850
|
}
|
|
5072
5851
|
function McpArgsStep({
|
|
5073
5852
|
entry,
|
|
@@ -5075,9 +5854,9 @@ function McpArgsStep({
|
|
|
5075
5854
|
onSubmit,
|
|
5076
5855
|
onError
|
|
5077
5856
|
}) {
|
|
5078
|
-
const [value, setValue] =
|
|
5079
|
-
return /* @__PURE__ */
|
|
5080
|
-
|
|
5857
|
+
const [value, setValue] = useState9("");
|
|
5858
|
+
return /* @__PURE__ */ React16.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React16.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(Text13, null, entry.summary), entry.note ? /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, null, "Required parameter: "), /* @__PURE__ */ React16.createElement(Text13, { bold: true }, entry.userArgs)), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React16.createElement(
|
|
5859
|
+
TextInput2,
|
|
5081
5860
|
{
|
|
5082
5861
|
value,
|
|
5083
5862
|
onChange: setValue,
|
|
@@ -5092,16 +5871,16 @@ function McpArgsStep({
|
|
|
5092
5871
|
},
|
|
5093
5872
|
placeholder: placeholderFor(entry)
|
|
5094
5873
|
}
|
|
5095
|
-
)), error ? /* @__PURE__ */
|
|
5874
|
+
)), error ? /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { color: "red" }, error)) : null));
|
|
5096
5875
|
}
|
|
5097
5876
|
function ReviewConfirm({ onConfirm }) {
|
|
5098
|
-
|
|
5877
|
+
useInput6((_i, key) => {
|
|
5099
5878
|
if (key.return) onConfirm();
|
|
5100
5879
|
});
|
|
5101
5880
|
return null;
|
|
5102
5881
|
}
|
|
5103
5882
|
function ExitOnEnter({ onExit }) {
|
|
5104
|
-
|
|
5883
|
+
useInput6((_i, key) => {
|
|
5105
5884
|
if (key.return) onExit();
|
|
5106
5885
|
});
|
|
5107
5886
|
return null;
|
|
@@ -5112,10 +5891,10 @@ function StepFrame({
|
|
|
5112
5891
|
total,
|
|
5113
5892
|
children
|
|
5114
5893
|
}) {
|
|
5115
|
-
return /* @__PURE__ */
|
|
5894
|
+
return /* @__PURE__ */ React16.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React16.createElement(Box13, null, /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React16.createElement(Text13, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1, flexDirection: "column" }, children));
|
|
5116
5895
|
}
|
|
5117
5896
|
function SummaryLine({ label, value }) {
|
|
5118
|
-
return /* @__PURE__ */
|
|
5897
|
+
return /* @__PURE__ */ React16.createElement(Box13, null, /* @__PURE__ */ React16.createElement(Text13, null, label.padEnd(12)), /* @__PURE__ */ React16.createElement(Text13, { bold: true }, value));
|
|
5119
5898
|
}
|
|
5120
5899
|
function presetItems() {
|
|
5121
5900
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -5158,10 +5937,10 @@ function buildSpec(name, argsByName) {
|
|
|
5158
5937
|
const entry = CATALOG_BY_NAME.get(name);
|
|
5159
5938
|
if (!entry) return name;
|
|
5160
5939
|
const userArg = entry.userArgs ? argsByName[name] : void 0;
|
|
5161
|
-
const tail = userArg ? ` ${
|
|
5940
|
+
const tail = userArg ? ` ${quoteIfNeeded(userArg)}` : "";
|
|
5162
5941
|
return `${entry.name}=npx -y ${entry.package}${tail}`;
|
|
5163
5942
|
}
|
|
5164
|
-
function
|
|
5943
|
+
function quoteIfNeeded(s) {
|
|
5165
5944
|
return /\s|"/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
|
|
5166
5945
|
}
|
|
5167
5946
|
|
|
@@ -5171,7 +5950,7 @@ async function setupCommand(_opts = {}) {
|
|
|
5171
5950
|
const existingKey = loadApiKey();
|
|
5172
5951
|
const existing = readConfig();
|
|
5173
5952
|
const { waitUntilExit, unmount } = render4(
|
|
5174
|
-
/* @__PURE__ */
|
|
5953
|
+
/* @__PURE__ */ React17.createElement(
|
|
5175
5954
|
Wizard,
|
|
5176
5955
|
{
|
|
5177
5956
|
existingApiKey: existingKey,
|
|
@@ -5390,6 +6169,17 @@ var mcp = program.command("mcp").description("Model Context Protocol helpers \u2
|
|
|
5390
6169
|
mcp.command("list").description("Show a curated catalog of popular MCP servers with ready-to-use --mcp commands.").option("--json", "Emit the catalog as JSON instead of the human-readable table").action((opts) => {
|
|
5391
6170
|
mcpListCommand({ json: !!opts.json });
|
|
5392
6171
|
});
|
|
6172
|
+
mcp.command("inspect <spec>").description(
|
|
6173
|
+
'Connect to one MCP server and print its server info + tools/resources/prompts. <spec> takes the same forms as --mcp ("name=cmd args", "cmd args", or an SSE URL).'
|
|
6174
|
+
).option("--json", "Emit the full inspection report as JSON instead of the human-readable summary").action(async (spec, opts) => {
|
|
6175
|
+
try {
|
|
6176
|
+
await mcpInspectCommand({ spec, json: !!opts.json });
|
|
6177
|
+
} catch (err) {
|
|
6178
|
+
process.stderr.write(`mcp inspect failed: ${err.message}
|
|
6179
|
+
`);
|
|
6180
|
+
process.exit(1);
|
|
6181
|
+
}
|
|
6182
|
+
});
|
|
5393
6183
|
program.command("version").description("Print Reasonix version.").action(versionCommand);
|
|
5394
6184
|
program.parseAsync(process.argv).catch((err) => {
|
|
5395
6185
|
console.error(err);
|