reasonix 0.4.20 → 0.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -294
- package/dist/cli/{chunk-DDIKQZVD.js → chunk-K6MR4SWS.js} +190 -34
- package/dist/cli/chunk-K6MR4SWS.js.map +1 -0
- package/dist/cli/index.js +369 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-YEJEJ3IZ.js → prompt-VDN5U3YE.js} +2 -2
- package/dist/index.d.ts +94 -8
- package/dist/index.js +355 -77
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-DDIKQZVD.js.map +0 -1
- /package/dist/cli/{prompt-YEJEJ3IZ.js.map → prompt-VDN5U3YE.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
MemoryStore,
|
|
4
4
|
PROJECT_MEMORY_FILE,
|
|
5
|
+
SkillStore,
|
|
5
6
|
applyMemoryStack,
|
|
6
7
|
memoryEnabled,
|
|
7
8
|
readProjectMemory,
|
|
8
9
|
sanitizeMemoryName
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-K6MR4SWS.js";
|
|
10
11
|
|
|
11
12
|
// src/cli/index.ts
|
|
12
13
|
import { Command } from "commander";
|
|
@@ -2696,8 +2697,23 @@ function prepareSpawn(argv, opts = {}) {
|
|
|
2696
2697
|
spawnOverrides: { windowsVerbatimArguments: true }
|
|
2697
2698
|
};
|
|
2698
2699
|
}
|
|
2700
|
+
if (isBareWindowsName(resolved) && resolved === head) {
|
|
2701
|
+
const cmdline = [head, ...tail].map(quoteForCmdExe).join(" ");
|
|
2702
|
+
return {
|
|
2703
|
+
bin: "cmd.exe",
|
|
2704
|
+
args: ["/d", "/s", "/c", cmdline],
|
|
2705
|
+
spawnOverrides: { windowsVerbatimArguments: true }
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2699
2708
|
return { bin: resolved, args: [...tail], spawnOverrides: {} };
|
|
2700
2709
|
}
|
|
2710
|
+
function isBareWindowsName(s) {
|
|
2711
|
+
if (!s) return false;
|
|
2712
|
+
if (s.includes("/") || s.includes("\\")) return false;
|
|
2713
|
+
if (pathMod2.isAbsolute(s)) return false;
|
|
2714
|
+
if (pathMod2.extname(s)) return false;
|
|
2715
|
+
return true;
|
|
2716
|
+
}
|
|
2701
2717
|
function quoteForCmdExe(arg) {
|
|
2702
2718
|
if (arg === "") return '""';
|
|
2703
2719
|
if (!/[\s"&|<>^%(),;!]/.test(arg)) return arg;
|
|
@@ -2717,7 +2733,10 @@ function registerShellTools(registry, opts) {
|
|
|
2717
2733
|
const rootDir = pathMod2.resolve(opts.rootDir);
|
|
2718
2734
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
2719
2735
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
2720
|
-
const
|
|
2736
|
+
const getExtraAllowed = typeof opts.extraAllowed === "function" ? opts.extraAllowed : (() => {
|
|
2737
|
+
const snapshot = opts.extraAllowed ?? [];
|
|
2738
|
+
return () => snapshot;
|
|
2739
|
+
})();
|
|
2721
2740
|
const allowAll = opts.allowAll ?? false;
|
|
2722
2741
|
registry.register({
|
|
2723
2742
|
name: "run_command",
|
|
@@ -2730,7 +2749,7 @@ function registerShellTools(registry, opts) {
|
|
|
2730
2749
|
if (allowAll) return true;
|
|
2731
2750
|
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
2732
2751
|
if (!cmd) return false;
|
|
2733
|
-
return isAllowed(cmd,
|
|
2752
|
+
return isAllowed(cmd, getExtraAllowed());
|
|
2734
2753
|
},
|
|
2735
2754
|
parameters: {
|
|
2736
2755
|
type: "object",
|
|
@@ -2749,7 +2768,7 @@ function registerShellTools(registry, opts) {
|
|
|
2749
2768
|
fn: async (args, ctx) => {
|
|
2750
2769
|
const cmd = args.command.trim();
|
|
2751
2770
|
if (!cmd) throw new Error("run_command: empty command");
|
|
2752
|
-
if (!allowAll && !isAllowed(cmd,
|
|
2771
|
+
if (!allowAll && !isAllowed(cmd, getExtraAllowed())) {
|
|
2753
2772
|
throw new NeedsConfirmationError(cmd);
|
|
2754
2773
|
}
|
|
2755
2774
|
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
@@ -4241,14 +4260,163 @@ function sep() {
|
|
|
4241
4260
|
return process.platform === "win32" ? "\\" : "/";
|
|
4242
4261
|
}
|
|
4243
4262
|
|
|
4244
|
-
// src/
|
|
4245
|
-
|
|
4263
|
+
// src/version.ts
|
|
4264
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
4265
|
+
import { homedir as homedir3 } from "os";
|
|
4266
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
4267
|
+
import { fileURLToPath } from "url";
|
|
4268
|
+
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
4269
|
+
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4270
|
+
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
4271
|
+
function readPackageVersion() {
|
|
4272
|
+
try {
|
|
4273
|
+
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
4274
|
+
for (let i = 0; i < 6; i++) {
|
|
4275
|
+
const p = join4(dir, "package.json");
|
|
4276
|
+
if (existsSync4(p)) {
|
|
4277
|
+
const pkg = JSON.parse(readFileSync6(p, "utf8"));
|
|
4278
|
+
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
4279
|
+
return pkg.version;
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
const parent = dirname5(dir);
|
|
4283
|
+
if (parent === dir) break;
|
|
4284
|
+
dir = parent;
|
|
4285
|
+
}
|
|
4286
|
+
} catch {
|
|
4287
|
+
}
|
|
4288
|
+
return "0.0.0-dev";
|
|
4289
|
+
}
|
|
4290
|
+
var VERSION = readPackageVersion();
|
|
4291
|
+
function cachePath(homeDirOverride) {
|
|
4292
|
+
return join4(homeDirOverride ?? homedir3(), ".reasonix", "version-cache.json");
|
|
4293
|
+
}
|
|
4294
|
+
function readCache(homeDirOverride) {
|
|
4295
|
+
try {
|
|
4296
|
+
const raw = readFileSync6(cachePath(homeDirOverride), "utf8");
|
|
4297
|
+
const parsed = JSON.parse(raw);
|
|
4298
|
+
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
4299
|
+
return parsed;
|
|
4300
|
+
}
|
|
4301
|
+
} catch {
|
|
4302
|
+
}
|
|
4303
|
+
return null;
|
|
4304
|
+
}
|
|
4305
|
+
function writeCache(entry, homeDirOverride) {
|
|
4306
|
+
try {
|
|
4307
|
+
const p = cachePath(homeDirOverride);
|
|
4308
|
+
mkdirSync4(dirname5(p), { recursive: true });
|
|
4309
|
+
writeFileSync4(p, JSON.stringify(entry), "utf8");
|
|
4310
|
+
} catch {
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
async function getLatestVersion(opts = {}) {
|
|
4314
|
+
const ttl = opts.ttlMs ?? LATEST_CACHE_TTL_MS;
|
|
4315
|
+
if (!opts.force) {
|
|
4316
|
+
const cached = readCache(opts.homeDir);
|
|
4317
|
+
if (cached && Date.now() - cached.checkedAt < ttl) return cached.version;
|
|
4318
|
+
}
|
|
4319
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
4320
|
+
if (!fetchImpl) return null;
|
|
4321
|
+
const url = opts.registryUrl ?? REGISTRY_URL;
|
|
4322
|
+
const timeout = opts.timeoutMs ?? LATEST_FETCH_TIMEOUT_MS;
|
|
4323
|
+
const controller = new AbortController();
|
|
4324
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
4325
|
+
try {
|
|
4326
|
+
const res = await fetchImpl(url, {
|
|
4327
|
+
signal: controller.signal,
|
|
4328
|
+
headers: { accept: "application/json" }
|
|
4329
|
+
});
|
|
4330
|
+
if (!res.ok) return null;
|
|
4331
|
+
const body = await res.json();
|
|
4332
|
+
if (typeof body.version !== "string") return null;
|
|
4333
|
+
writeCache({ version: body.version, checkedAt: Date.now() }, opts.homeDir);
|
|
4334
|
+
return body.version;
|
|
4335
|
+
} catch {
|
|
4336
|
+
return null;
|
|
4337
|
+
} finally {
|
|
4338
|
+
clearTimeout(timer);
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
function compareVersions(a, b) {
|
|
4342
|
+
const [aCore = "0", aPre = ""] = a.split("-", 2);
|
|
4343
|
+
const [bCore = "0", bPre = ""] = b.split("-", 2);
|
|
4344
|
+
const aParts = aCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
4345
|
+
const bParts = bCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
4346
|
+
for (let i = 0; i < 3; i++) {
|
|
4347
|
+
const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
|
|
4348
|
+
if (diff !== 0) return diff;
|
|
4349
|
+
}
|
|
4350
|
+
if (!aPre && !bPre) return 0;
|
|
4351
|
+
if (!aPre) return 1;
|
|
4352
|
+
if (!bPre) return -1;
|
|
4353
|
+
return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
|
|
4354
|
+
}
|
|
4355
|
+
function isNpxInstall() {
|
|
4356
|
+
const bin = process.argv[1] ?? "";
|
|
4357
|
+
if (/[/\\]_npx[/\\]/.test(bin)) return true;
|
|
4358
|
+
if (/[/\\]\.pnpm[/\\]/.test(bin) && /dlx/i.test(bin)) return true;
|
|
4359
|
+
const ua = process.env.npm_config_user_agent ?? "";
|
|
4360
|
+
if (ua.includes("npx/")) return true;
|
|
4361
|
+
return false;
|
|
4362
|
+
}
|
|
4246
4363
|
|
|
4247
4364
|
// src/cli/commands/chat.tsx
|
|
4248
|
-
import { existsSync as
|
|
4365
|
+
import { existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
4249
4366
|
import { render } from "ink";
|
|
4250
4367
|
import React15, { useState as useState7 } from "react";
|
|
4251
4368
|
|
|
4369
|
+
// src/tools/skills.ts
|
|
4370
|
+
function registerSkillTools(registry, opts = {}) {
|
|
4371
|
+
const store = new SkillStore({ homeDir: opts.homeDir, projectRoot: opts.projectRoot });
|
|
4372
|
+
registry.register({
|
|
4373
|
+
name: "run_skill",
|
|
4374
|
+
description: "Load the full body of a user-defined skill into this conversation. Call when the pinned Skills index (in the system prompt) lists a skill whose description matches what's being asked. Returns the skill's markdown instructions \u2014 read them and continue the loop, calling whatever filesystem / shell / web tools the skill's prose requires. Skills are user content; follow their instructions, but keep Reasonix's own safety rules (no destructive ops without confirmation, etc.).",
|
|
4375
|
+
readOnly: true,
|
|
4376
|
+
parameters: {
|
|
4377
|
+
type: "object",
|
|
4378
|
+
properties: {
|
|
4379
|
+
name: {
|
|
4380
|
+
type: "string",
|
|
4381
|
+
description: "Skill identifier as it appears in the pinned Skills index (e.g. 'review', 'security-review'). Case-sensitive."
|
|
4382
|
+
},
|
|
4383
|
+
arguments: {
|
|
4384
|
+
type: "string",
|
|
4385
|
+
description: "Optional free-form arguments the caller wants the skill to act on. Forwarded verbatim as an 'Arguments:' line appended to the skill body; the skill's own instructions decide how to consume them."
|
|
4386
|
+
}
|
|
4387
|
+
},
|
|
4388
|
+
required: ["name"]
|
|
4389
|
+
},
|
|
4390
|
+
fn: async (args) => {
|
|
4391
|
+
const name = typeof args.name === "string" ? args.name.trim() : "";
|
|
4392
|
+
if (!name) {
|
|
4393
|
+
return JSON.stringify({ error: "run_skill requires a 'name' argument" });
|
|
4394
|
+
}
|
|
4395
|
+
const skill = store.read(name);
|
|
4396
|
+
if (!skill) {
|
|
4397
|
+
const available = store.list().map((s) => s.name).join(", ");
|
|
4398
|
+
return JSON.stringify({
|
|
4399
|
+
error: `unknown skill: ${JSON.stringify(name)}`,
|
|
4400
|
+
available: available || "(none \u2014 user has not defined any skills)"
|
|
4401
|
+
});
|
|
4402
|
+
}
|
|
4403
|
+
const rawArgs = typeof args.arguments === "string" ? args.arguments.trim() : "";
|
|
4404
|
+
const header = [
|
|
4405
|
+
`# Skill: ${skill.name}`,
|
|
4406
|
+
skill.description ? `> ${skill.description}` : "",
|
|
4407
|
+
`(scope: ${skill.scope} \xB7 ${skill.path})`
|
|
4408
|
+
].filter(Boolean).join("\n");
|
|
4409
|
+
const argsBlock = rawArgs ? `
|
|
4410
|
+
|
|
4411
|
+
Arguments: ${rawArgs}` : "";
|
|
4412
|
+
return `${header}
|
|
4413
|
+
|
|
4414
|
+
${skill.body}${argsBlock}`;
|
|
4415
|
+
}
|
|
4416
|
+
});
|
|
4417
|
+
return registry;
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4252
4420
|
// src/cli/ui/App.tsx
|
|
4253
4421
|
import { Box as Box11, Static, Text as Text11, useApp, useInput as useInput4 } from "ink";
|
|
4254
4422
|
import React12, { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState5 } from "react";
|
|
@@ -5185,7 +5353,8 @@ function StatsPanel({
|
|
|
5185
5353
|
harvestOn,
|
|
5186
5354
|
branchBudget,
|
|
5187
5355
|
planMode,
|
|
5188
|
-
balance
|
|
5356
|
+
balance,
|
|
5357
|
+
updateAvailable
|
|
5189
5358
|
}) {
|
|
5190
5359
|
const hitPct = (summary.cacheHitRatio * 100).toFixed(1);
|
|
5191
5360
|
const hitColor = summary.cacheHitRatio >= 0.7 ? "green" : summary.cacheHitRatio >= 0.4 ? "yellow" : "red";
|
|
@@ -5193,7 +5362,7 @@ function StatsPanel({
|
|
|
5193
5362
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
5194
5363
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
5195
5364
|
const ctxColor = ctxRatio >= 0.8 ? "red" : ctxRatio >= 0.5 ? "yellow" : void 0;
|
|
5196
|
-
return /* @__PURE__ */ React11.createElement(Box10, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Box10, { justifyContent: "space-between" }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, model), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "blue" }, " \xB7 branch", branchBudget) : null, planMode ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " ", "\xB7 PLAN") : null), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cache hit "), /* @__PURE__ */ React11.createElement(Text10, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cost "), /* @__PURE__ */ React11.createElement(Text10, { color: "green", bold: true }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")")), summary.lastPromptTokens > 0 ? /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "ctx "), /* @__PURE__ */ React11.createElement(Text10, { color: ctxColor, bold: ctxColor !== void 0 }, formatTokens(summary.lastPromptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (", (ctxRatio * 100).toFixed(0), "%)"), ctxRatio >= 0.8 ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " ", "\xB7 /compact") : null) : null, balance ? /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "balance "), /* @__PURE__ */ React11.createElement(Text10, { color: balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green", bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : "")) : null));
|
|
5365
|
+
return /* @__PURE__ */ React11.createElement(Box10, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Box10, { justifyContent: "space-between" }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, ` v${VERSION}`), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React11.createElement(Text10, { color: "yellow" }, model), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React11.createElement(Text10, { color: "blue" }, " \xB7 branch", branchBudget) : null, planMode ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " ", "\xB7 PLAN") : null), /* @__PURE__ */ React11.createElement(Text10, null, updateAvailable ? /* @__PURE__ */ React11.createElement(Text10, { color: "yellow", bold: true }, `update: ${updateAvailable} \xB7 `) : null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help"))), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cache hit "), /* @__PURE__ */ React11.createElement(Text10, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "cost "), /* @__PURE__ */ React11.createElement(Text10, { color: "green", bold: true }, "$", summary.totalCostUsd.toFixed(6)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (in ", "$", summary.totalInputCostUsd.toFixed(6), " \xB7 out ", "$", summary.totalOutputCostUsd.toFixed(6), ")")), summary.lastPromptTokens > 0 ? /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "ctx "), /* @__PURE__ */ React11.createElement(Text10, { color: ctxColor, bold: ctxColor !== void 0 }, formatTokens(summary.lastPromptTokens), "/", formatTokens(ctxMax)), /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, " (", (ctxRatio * 100).toFixed(0), "%)"), ctxRatio >= 0.8 ? /* @__PURE__ */ React11.createElement(Text10, { color: "red", bold: true }, " ", "\xB7 /compact") : null) : null, balance ? /* @__PURE__ */ React11.createElement(Text10, null, /* @__PURE__ */ React11.createElement(Text10, { dimColor: true }, "balance "), /* @__PURE__ */ React11.createElement(Text10, { color: balance.total < 1 ? "red" : balance.total < 5 ? "yellow" : "green", bold: true }, balance.currency === "USD" ? "$" : "", balance.total.toFixed(2), balance.currency !== "USD" ? ` ${balance.currency}` : "")) : null));
|
|
5197
5366
|
}
|
|
5198
5367
|
function formatTokens(n) {
|
|
5199
5368
|
if (n < 1e3) return String(n);
|
|
@@ -5221,6 +5390,11 @@ var SLASH_COMMANDS = [
|
|
|
5221
5390
|
argsHint: "[list|show <name>|forget <name>|clear <scope> confirm]",
|
|
5222
5391
|
summary: "show / manage pinned memory (REASONIX.md + ~/.reasonix/memory)"
|
|
5223
5392
|
},
|
|
5393
|
+
{
|
|
5394
|
+
cmd: "skill",
|
|
5395
|
+
argsHint: "[list|show <name>|<name> [args]]",
|
|
5396
|
+
summary: "list / run user skills (<project>/.reasonix/skills + ~/.reasonix/skills)"
|
|
5397
|
+
},
|
|
5224
5398
|
{ cmd: "think", summary: "dump the last turn's full R1 reasoning (reasoner only)" },
|
|
5225
5399
|
{ cmd: "retry", summary: "truncate & resend your last message (fresh sample)" },
|
|
5226
5400
|
{ cmd: "compact", argsHint: "[cap]", summary: "shrink oversized tool results in the log" },
|
|
@@ -5302,6 +5476,8 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
5302
5476
|
" /tool [N] list tool calls (or dump full output of #N, 1=most recent)",
|
|
5303
5477
|
" /memory [sub] show pinned memory (REASONIX.md + ~/.reasonix/memory).",
|
|
5304
5478
|
" subs: list | show <name> | forget <name> | clear <scope> confirm",
|
|
5479
|
+
" /skill [sub] list / run user skills (project/.reasonix/skills + ~/.reasonix/skills).",
|
|
5480
|
+
" subs: list | show <name> | <name> [args] (injects skill body as user turn)",
|
|
5305
5481
|
" /retry truncate & resend your last message (fresh sample from the model)",
|
|
5306
5482
|
" /apply (code mode) commit the pending edit blocks to disk",
|
|
5307
5483
|
" /discard (code mode) drop pending edits without writing",
|
|
@@ -5388,6 +5564,10 @@ function handleSlash(cmd, args, loop, ctx = {}) {
|
|
|
5388
5564
|
case "memory": {
|
|
5389
5565
|
return handleMemorySlash(args, ctx);
|
|
5390
5566
|
}
|
|
5567
|
+
case "skill":
|
|
5568
|
+
case "skills": {
|
|
5569
|
+
return handleSkillSlash(args, ctx);
|
|
5570
|
+
}
|
|
5391
5571
|
case "think":
|
|
5392
5572
|
case "reasoning": {
|
|
5393
5573
|
const raw = loop.scratch.reasoning;
|
|
@@ -5624,6 +5804,76 @@ ${entry.text}`
|
|
|
5624
5804
|
return { unknown: true, info: `unknown command: /${cmd} (try /help)` };
|
|
5625
5805
|
}
|
|
5626
5806
|
}
|
|
5807
|
+
function handleSkillSlash(args, ctx) {
|
|
5808
|
+
const store = new SkillStore({ projectRoot: ctx.codeRoot });
|
|
5809
|
+
const sub = (args[0] ?? "").toLowerCase();
|
|
5810
|
+
if (sub === "" || sub === "list" || sub === "ls") {
|
|
5811
|
+
const skills = store.list();
|
|
5812
|
+
if (skills.length === 0) {
|
|
5813
|
+
const lines2 = ["no skills found. Reasonix reads skills from:"];
|
|
5814
|
+
if (store.hasProjectScope()) {
|
|
5815
|
+
lines2.push(
|
|
5816
|
+
" \xB7 <project>/.reasonix/skills/<name>/SKILL.md (or <name>.md) \u2014 project scope"
|
|
5817
|
+
);
|
|
5818
|
+
}
|
|
5819
|
+
lines2.push(" \xB7 ~/.reasonix/skills/<name>/SKILL.md (or <name>.md) \u2014 global scope");
|
|
5820
|
+
if (!store.hasProjectScope()) {
|
|
5821
|
+
lines2.push(" (project scope is only active in `reasonix code`)");
|
|
5822
|
+
}
|
|
5823
|
+
lines2.push(
|
|
5824
|
+
"",
|
|
5825
|
+
"Each file's frontmatter needs at least `name` and `description`.",
|
|
5826
|
+
"Invoke a skill with `/skill <name> [args]` or by asking the model to call `run_skill`."
|
|
5827
|
+
);
|
|
5828
|
+
return { info: lines2.join("\n") };
|
|
5829
|
+
}
|
|
5830
|
+
const lines = [`User skills (${skills.length}):`];
|
|
5831
|
+
for (const s of skills) {
|
|
5832
|
+
const scope = `(${s.scope})`.padEnd(11);
|
|
5833
|
+
const name2 = s.name.padEnd(24);
|
|
5834
|
+
const desc = s.description.length > 70 ? `${s.description.slice(0, 69)}\u2026` : s.description;
|
|
5835
|
+
lines.push(` ${scope} ${name2} ${desc}`);
|
|
5836
|
+
}
|
|
5837
|
+
lines.push("");
|
|
5838
|
+
lines.push("View body: /skill show <name> Run: /skill <name> [args]");
|
|
5839
|
+
return { info: lines.join("\n") };
|
|
5840
|
+
}
|
|
5841
|
+
if (sub === "show" || sub === "cat") {
|
|
5842
|
+
const target = args[1];
|
|
5843
|
+
if (!target) return { info: "usage: /skill show <name>" };
|
|
5844
|
+
const skill2 = store.read(target);
|
|
5845
|
+
if (!skill2) return { info: `no skill found: ${target}` };
|
|
5846
|
+
return {
|
|
5847
|
+
info: [
|
|
5848
|
+
`\u25B8 ${skill2.name} (${skill2.scope})`,
|
|
5849
|
+
skill2.description ? ` ${skill2.description}` : "",
|
|
5850
|
+
` ${skill2.path}`,
|
|
5851
|
+
"",
|
|
5852
|
+
skill2.body
|
|
5853
|
+
].filter((l) => l !== "").join("\n")
|
|
5854
|
+
};
|
|
5855
|
+
}
|
|
5856
|
+
const name = args[0] ?? "";
|
|
5857
|
+
const skill = store.read(name);
|
|
5858
|
+
if (!skill) {
|
|
5859
|
+
return {
|
|
5860
|
+
info: `no skill found: ${name} (try /skill list)`
|
|
5861
|
+
};
|
|
5862
|
+
}
|
|
5863
|
+
const extra = args.slice(1).join(" ").trim();
|
|
5864
|
+
const header = `# Skill: ${skill.name}${skill.description ? `
|
|
5865
|
+
> ${skill.description}` : ""}`;
|
|
5866
|
+
const argsLine = extra ? `
|
|
5867
|
+
|
|
5868
|
+
Arguments: ${extra}` : "";
|
|
5869
|
+
const payload = `${header}
|
|
5870
|
+
|
|
5871
|
+
${skill.body}${argsLine}`;
|
|
5872
|
+
return {
|
|
5873
|
+
info: `\u25B8 running skill: ${skill.name}${extra ? ` \u2014 ${extra}` : ""}`,
|
|
5874
|
+
resubmit: payload
|
|
5875
|
+
};
|
|
5876
|
+
}
|
|
5627
5877
|
function handleMemorySlash(args, ctx) {
|
|
5628
5878
|
if (!memoryEnabled()) {
|
|
5629
5879
|
return {
|
|
@@ -5879,6 +6129,7 @@ function App({
|
|
|
5879
6129
|
const [toolProgress, setToolProgress] = useState5(null);
|
|
5880
6130
|
const [statusLine, setStatusLine] = useState5(null);
|
|
5881
6131
|
const [balance, setBalance] = useState5(null);
|
|
6132
|
+
const [updateAvailable, setUpdateAvailable] = useState5(null);
|
|
5882
6133
|
const lastEditSnapshots = useRef2(null);
|
|
5883
6134
|
const pendingEdits = useRef2([]);
|
|
5884
6135
|
const [pendingShell, setPendingShell] = useState5(null);
|
|
@@ -5950,6 +6201,17 @@ function App({
|
|
|
5950
6201
|
cancelled = true;
|
|
5951
6202
|
};
|
|
5952
6203
|
}, [loop]);
|
|
6204
|
+
useEffect2(() => {
|
|
6205
|
+
let cancelled = false;
|
|
6206
|
+
void (async () => {
|
|
6207
|
+
const latest = await getLatestVersion();
|
|
6208
|
+
if (cancelled || !latest) return;
|
|
6209
|
+
if (compareVersions(VERSION, latest) < 0) setUpdateAvailable(latest);
|
|
6210
|
+
})();
|
|
6211
|
+
return () => {
|
|
6212
|
+
cancelled = true;
|
|
6213
|
+
};
|
|
6214
|
+
}, []);
|
|
5953
6215
|
useEffect2(() => {
|
|
5954
6216
|
if (!progressSink) return;
|
|
5955
6217
|
progressSink.current = (info) => {
|
|
@@ -6508,7 +6770,8 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
|
|
|
6508
6770
|
harvestOn: loop.harvestEnabled,
|
|
6509
6771
|
branchBudget: loop.branchOptions.budget,
|
|
6510
6772
|
planMode,
|
|
6511
|
-
balance
|
|
6773
|
+
balance,
|
|
6774
|
+
updateAvailable
|
|
6512
6775
|
}
|
|
6513
6776
|
), /* @__PURE__ */ React12.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React12.createElement(EventRow, { key: item.id, event: item })), !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && streaming ? /* @__PURE__ */ React12.createElement(Box11, { marginY: 1 }, /* @__PURE__ */ React12.createElement(EventRow, { event: streaming })) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && ongoingTool ? /* @__PURE__ */ React12.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && !ongoingTool && statusLine ? /* @__PURE__ */ React12.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && !pendingShell && !pendingPlan && !stagedInput && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React12.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React12.createElement(
|
|
6514
6777
|
PlanRefineInput,
|
|
@@ -6858,13 +7121,14 @@ async function chatCommand(opts) {
|
|
|
6858
7121
|
if (!opts.seedTools) {
|
|
6859
7122
|
if (!tools) tools = new ToolRegistry();
|
|
6860
7123
|
registerMemoryTools(tools, {});
|
|
7124
|
+
registerSkillTools(tools);
|
|
6861
7125
|
}
|
|
6862
7126
|
let sessionPreview;
|
|
6863
7127
|
if (opts.session && !opts.forceResume && !opts.forceNew) {
|
|
6864
7128
|
const prior = loadSessionMessages(opts.session);
|
|
6865
7129
|
if (prior.length > 0) {
|
|
6866
7130
|
const p = sessionPath(opts.session);
|
|
6867
|
-
const mtime =
|
|
7131
|
+
const mtime = existsSync5(p) ? statSync3(p).mtime : /* @__PURE__ */ new Date();
|
|
6868
7132
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
6869
7133
|
}
|
|
6870
7134
|
} else if (opts.session && opts.forceNew) {
|
|
@@ -6897,7 +7161,7 @@ async function chatCommand(opts) {
|
|
|
6897
7161
|
// src/cli/commands/code.tsx
|
|
6898
7162
|
import { basename, resolve as resolve5 } from "path";
|
|
6899
7163
|
async function codeCommand(opts = {}) {
|
|
6900
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
7164
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-VDN5U3YE.js");
|
|
6901
7165
|
const rootDir = resolve5(opts.dir ?? process.cwd());
|
|
6902
7166
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
6903
7167
|
const tools = new ToolRegistry();
|
|
@@ -6906,10 +7170,14 @@ async function codeCommand(opts = {}) {
|
|
|
6906
7170
|
rootDir,
|
|
6907
7171
|
// Per-project "always allow" list persisted from prior ShellConfirm
|
|
6908
7172
|
// choices; merged on top of the built-in allowlist in shell.ts.
|
|
6909
|
-
|
|
7173
|
+
// GETTER form — re-read every dispatch so a prefix the user adds
|
|
7174
|
+
// via ShellConfirm mid-session takes effect on the next shell call
|
|
7175
|
+
// instead of waiting for `/new` or a relaunch.
|
|
7176
|
+
extraAllowed: () => loadProjectShellAllowed(rootDir)
|
|
6910
7177
|
});
|
|
6911
7178
|
registerPlanTool(tools);
|
|
6912
7179
|
registerMemoryTools(tools, { projectRoot: rootDir });
|
|
7180
|
+
registerSkillTools(tools, { projectRoot: rootDir });
|
|
6913
7181
|
process.stderr.write(
|
|
6914
7182
|
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
6915
7183
|
`
|
|
@@ -6929,7 +7197,7 @@ async function codeCommand(opts = {}) {
|
|
|
6929
7197
|
}
|
|
6930
7198
|
|
|
6931
7199
|
// src/cli/commands/diff.ts
|
|
6932
|
-
import { writeFileSync as
|
|
7200
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
6933
7201
|
import { basename as basename2 } from "path";
|
|
6934
7202
|
import { render as render2 } from "ink";
|
|
6935
7203
|
import React18 from "react";
|
|
@@ -7075,7 +7343,7 @@ async function diffCommand(opts) {
|
|
|
7075
7343
|
if (wantMarkdown) {
|
|
7076
7344
|
console.log(renderSummaryTable(report));
|
|
7077
7345
|
const md = renderMarkdown(report);
|
|
7078
|
-
|
|
7346
|
+
writeFileSync5(opts.mdPath, md, "utf8");
|
|
7079
7347
|
console.log(`
|
|
7080
7348
|
markdown report written to ${opts.mdPath}`);
|
|
7081
7349
|
return;
|
|
@@ -7882,13 +8150,13 @@ async function setupCommand(_opts = {}) {
|
|
|
7882
8150
|
}
|
|
7883
8151
|
|
|
7884
8152
|
// src/cli/commands/stats.ts
|
|
7885
|
-
import { existsSync as
|
|
8153
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
7886
8154
|
function statsCommand(opts) {
|
|
7887
|
-
if (!
|
|
8155
|
+
if (!existsSync6(opts.transcript)) {
|
|
7888
8156
|
console.error(`no such transcript: ${opts.transcript}`);
|
|
7889
8157
|
process.exit(1);
|
|
7890
8158
|
}
|
|
7891
|
-
const lines =
|
|
8159
|
+
const lines = readFileSync7(opts.transcript, "utf8").split(/\r?\n/).filter(Boolean);
|
|
7892
8160
|
let assistantTurns = 0;
|
|
7893
8161
|
let toolCalls = 0;
|
|
7894
8162
|
let lastTurn = 0;
|
|
@@ -7907,6 +8175,84 @@ function statsCommand(opts) {
|
|
|
7907
8175
|
console.log(`last turn index: ${lastTurn}`);
|
|
7908
8176
|
}
|
|
7909
8177
|
|
|
8178
|
+
// src/cli/commands/update.ts
|
|
8179
|
+
import { spawn as spawn3 } from "child_process";
|
|
8180
|
+
function planUpdate(input) {
|
|
8181
|
+
const diff = compareVersions(input.current, input.latest);
|
|
8182
|
+
if (diff > 0) {
|
|
8183
|
+
return {
|
|
8184
|
+
action: "newer-local",
|
|
8185
|
+
message: `current (${input.current}) is newer than the published ${input.latest} \u2014 nothing to do.`
|
|
8186
|
+
};
|
|
8187
|
+
}
|
|
8188
|
+
if (diff === 0) {
|
|
8189
|
+
return { action: "up-to-date", message: `reasonix ${input.current} is up to date.` };
|
|
8190
|
+
}
|
|
8191
|
+
if (input.npx) {
|
|
8192
|
+
return {
|
|
8193
|
+
action: "npx-hint",
|
|
8194
|
+
message: [
|
|
8195
|
+
`reasonix ${input.latest} is available.`,
|
|
8196
|
+
"you're running via npx \u2014 the next `npx reasonix ...` launch will auto-fetch",
|
|
8197
|
+
"the latest (npx caches packages for a short window). to force a refresh",
|
|
8198
|
+
"sooner, clear the cache: `npm cache clean --force`."
|
|
8199
|
+
].join("\n")
|
|
8200
|
+
};
|
|
8201
|
+
}
|
|
8202
|
+
return {
|
|
8203
|
+
action: "run-npm-install",
|
|
8204
|
+
message: `upgrading reasonix ${input.current} \u2192 ${input.latest}`,
|
|
8205
|
+
command: ["npm", "install", "-g", "reasonix@latest"]
|
|
8206
|
+
};
|
|
8207
|
+
}
|
|
8208
|
+
function defaultSpawn(argv) {
|
|
8209
|
+
return new Promise((resolve6, reject) => {
|
|
8210
|
+
const child = spawn3(argv[0], argv.slice(1), {
|
|
8211
|
+
stdio: "inherit",
|
|
8212
|
+
shell: process.platform === "win32"
|
|
8213
|
+
});
|
|
8214
|
+
child.once("error", reject);
|
|
8215
|
+
child.once("exit", (code) => resolve6(code ?? 1));
|
|
8216
|
+
});
|
|
8217
|
+
}
|
|
8218
|
+
async function updateCommand(opts = {}) {
|
|
8219
|
+
const write = opts.write ?? ((m) => process.stdout.write(m));
|
|
8220
|
+
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
8221
|
+
const fetchLatest = opts.fetchLatest ?? (() => getLatestVersion({ force: true }));
|
|
8222
|
+
const isNpx = opts.isNpx ?? isNpxInstall;
|
|
8223
|
+
const doSpawn = opts.spawnInstall ?? defaultSpawn;
|
|
8224
|
+
write(`current: reasonix ${VERSION}
|
|
8225
|
+
`);
|
|
8226
|
+
const latest = await fetchLatest();
|
|
8227
|
+
if (!latest) {
|
|
8228
|
+
write("could not reach registry.npmjs.org \u2014 check your network.\n");
|
|
8229
|
+
exit(1);
|
|
8230
|
+
return;
|
|
8231
|
+
}
|
|
8232
|
+
write(`latest: reasonix ${latest}
|
|
8233
|
+
`);
|
|
8234
|
+
const plan = planUpdate({ current: VERSION, latest, npx: isNpx() });
|
|
8235
|
+
write(`
|
|
8236
|
+
${plan.message}
|
|
8237
|
+
`);
|
|
8238
|
+
if (plan.action !== "run-npm-install" || !plan.command) return;
|
|
8239
|
+
if (opts.dryRun) {
|
|
8240
|
+
write(`(dry run) would run: ${plan.command.join(" ")}
|
|
8241
|
+
`);
|
|
8242
|
+
return;
|
|
8243
|
+
}
|
|
8244
|
+
write(`
|
|
8245
|
+
running: ${plan.command.join(" ")}
|
|
8246
|
+
`);
|
|
8247
|
+
const code = await doSpawn(plan.command);
|
|
8248
|
+
if (code !== 0) {
|
|
8249
|
+
write(`
|
|
8250
|
+
npm exited with code ${code}. upgrade did not complete.
|
|
8251
|
+
`);
|
|
8252
|
+
exit(code);
|
|
8253
|
+
}
|
|
8254
|
+
}
|
|
8255
|
+
|
|
7910
8256
|
// src/cli/commands/version.ts
|
|
7911
8257
|
function versionCommand() {
|
|
7912
8258
|
console.log(`reasonix ${VERSION}`);
|
|
@@ -8099,6 +8445,11 @@ mcp.command("inspect <spec>").description(
|
|
|
8099
8445
|
}
|
|
8100
8446
|
});
|
|
8101
8447
|
program.command("version").description("Print Reasonix version.").action(versionCommand);
|
|
8448
|
+
program.command("update").description(
|
|
8449
|
+
"Check the npm registry for a newer Reasonix and install it. Detects npx vs global install; for npx users, prints a cache-refresh hint instead of running `npm i -g`."
|
|
8450
|
+
).option("--dry-run", "Print the plan without executing the install").action(async (opts) => {
|
|
8451
|
+
await updateCommand({ dryRun: !!opts.dryRun });
|
|
8452
|
+
});
|
|
8102
8453
|
program.parseAsync(process.argv).catch((err) => {
|
|
8103
8454
|
console.error(err);
|
|
8104
8455
|
process.exit(1);
|