reasonix 0.32.0 → 0.33.1
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/chat-Q5ZCVIOO.js +39 -0
- package/dist/cli/chunk-2AWTGJ2C.js +110 -0
- package/dist/cli/chunk-2AWTGJ2C.js.map +1 -0
- package/dist/cli/chunk-3Q3C4W66.js +30 -0
- package/dist/cli/chunk-3Q3C4W66.js.map +1 -0
- package/dist/cli/chunk-4DCHFFEY.js +149 -0
- package/dist/cli/chunk-4DCHFFEY.js.map +1 -0
- package/dist/cli/chunk-5X7LZJDE.js +36 -0
- package/dist/cli/chunk-5X7LZJDE.js.map +1 -0
- package/dist/cli/chunk-63KAV5DX.js +106 -0
- package/dist/cli/chunk-63KAV5DX.js.map +1 -0
- package/dist/cli/chunk-6TMHAK5D.js +576 -0
- package/dist/cli/chunk-6TMHAK5D.js.map +1 -0
- package/dist/cli/chunk-APPB3ZPQ.js +43 -0
- package/dist/cli/chunk-APPB3ZPQ.js.map +1 -0
- package/dist/cli/chunk-BQNUJJN7.js +42 -0
- package/dist/cli/chunk-BQNUJJN7.js.map +1 -0
- package/dist/cli/chunk-CPOV2O73.js +39 -0
- package/dist/cli/chunk-CPOV2O73.js.map +1 -0
- package/dist/cli/chunk-D5DKXIP5.js +368 -0
- package/dist/cli/chunk-D5DKXIP5.js.map +1 -0
- package/dist/cli/chunk-DFP4YSVM.js +247 -0
- package/dist/cli/chunk-DFP4YSVM.js.map +1 -0
- package/dist/cli/chunk-DULSP7JH.js +410 -0
- package/dist/cli/chunk-DULSP7JH.js.map +1 -0
- package/dist/cli/chunk-FM57FNPJ.js +46 -0
- package/dist/cli/chunk-FM57FNPJ.js.map +1 -0
- package/dist/cli/chunk-FWGEHRB7.js +54 -0
- package/dist/cli/chunk-FWGEHRB7.js.map +1 -0
- package/dist/cli/chunk-FXGQ5NHE.js +513 -0
- package/dist/cli/chunk-FXGQ5NHE.js.map +1 -0
- package/dist/cli/chunk-G3XNWSFN.js +53 -0
- package/dist/cli/chunk-G3XNWSFN.js.map +1 -0
- package/dist/cli/chunk-I6YIAK6C.js +757 -0
- package/dist/cli/chunk-I6YIAK6C.js.map +1 -0
- package/dist/cli/chunk-J5VLP23S.js +94 -0
- package/dist/cli/chunk-J5VLP23S.js.map +1 -0
- package/dist/cli/chunk-KMWKGPFZ.js +303 -0
- package/dist/cli/chunk-KMWKGPFZ.js.map +1 -0
- package/dist/cli/chunk-MDHVWCJ4.js +14965 -0
- package/dist/cli/chunk-MDHVWCJ4.js.map +1 -0
- package/dist/cli/chunk-MHDNZXJJ.js +48 -0
- package/dist/cli/chunk-MHDNZXJJ.js.map +1 -0
- package/dist/cli/chunk-ORM6PK57.js +140 -0
- package/dist/cli/chunk-ORM6PK57.js.map +1 -0
- package/dist/cli/chunk-Q6YFXW7H.js +4986 -0
- package/dist/cli/chunk-Q6YFXW7H.js.map +1 -0
- package/dist/cli/chunk-QGE6AF76.js +1467 -0
- package/dist/cli/chunk-QGE6AF76.js.map +1 -0
- package/dist/cli/chunk-RFX7TYVV.js +28 -0
- package/dist/cli/chunk-RFX7TYVV.js.map +1 -0
- package/dist/cli/chunk-RZILUXUC.js +940 -0
- package/dist/cli/chunk-RZILUXUC.js.map +1 -0
- package/dist/cli/chunk-SDE5U32Z.js +535 -0
- package/dist/cli/chunk-SDE5U32Z.js.map +1 -0
- package/dist/cli/chunk-SOZE7V7V.js +340 -0
- package/dist/cli/chunk-SOZE7V7V.js.map +1 -0
- package/dist/cli/chunk-U3V2ZQ5J.js +479 -0
- package/dist/cli/chunk-U3V2ZQ5J.js.map +1 -0
- package/dist/cli/chunk-W4LDFAZ6.js +1544 -0
- package/dist/cli/chunk-W4LDFAZ6.js.map +1 -0
- package/dist/cli/chunk-WBDE4IRI.js +208 -0
- package/dist/cli/chunk-WBDE4IRI.js.map +1 -0
- package/dist/cli/chunk-XHQIK7B6.js +189 -0
- package/dist/cli/chunk-XHQIK7B6.js.map +1 -0
- package/dist/cli/chunk-XJLZ4HKU.js +307 -0
- package/dist/cli/chunk-XJLZ4HKU.js.map +1 -0
- package/dist/cli/chunk-ZPTSJGX5.js +88 -0
- package/dist/cli/chunk-ZPTSJGX5.js.map +1 -0
- package/dist/cli/chunk-ZTLZO42A.js +231 -0
- package/dist/cli/chunk-ZTLZO42A.js.map +1 -0
- package/dist/cli/code-DLR77NPZ.js +151 -0
- package/dist/cli/code-DLR77NPZ.js.map +1 -0
- package/dist/cli/commands-JWT2MWVH.js +352 -0
- package/dist/cli/commands-JWT2MWVH.js.map +1 -0
- package/dist/cli/commit-RPZBOZS2.js +288 -0
- package/dist/cli/commit-RPZBOZS2.js.map +1 -0
- package/dist/cli/diff-NTEHCSDW.js +145 -0
- package/dist/cli/diff-NTEHCSDW.js.map +1 -0
- package/dist/cli/doctor-3TGB2NZN.js +19 -0
- package/dist/cli/doctor-3TGB2NZN.js.map +1 -0
- package/dist/cli/events-P27CX7LN.js +338 -0
- package/dist/cli/events-P27CX7LN.js.map +1 -0
- package/dist/cli/index.js +80 -33693
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-ARTNQ24O.js +266 -0
- package/dist/cli/mcp-ARTNQ24O.js.map +1 -0
- package/dist/cli/mcp-browse-HLO2ENDL.js +163 -0
- package/dist/cli/mcp-browse-HLO2ENDL.js.map +1 -0
- package/dist/cli/mcp-inspect-T2HBR22P.js +103 -0
- package/dist/cli/mcp-inspect-T2HBR22P.js.map +1 -0
- package/dist/cli/{prompt-XHICFAYN.js → prompt-V47QKSAR.js} +3 -2
- package/dist/cli/prompt-V47QKSAR.js.map +1 -0
- package/dist/cli/prune-sessions-ERL6B4G5.js +42 -0
- package/dist/cli/prune-sessions-ERL6B4G5.js.map +1 -0
- package/dist/cli/replay-Q43DSMG6.js +273 -0
- package/dist/cli/replay-Q43DSMG6.js.map +1 -0
- package/dist/cli/run-JMEOTQCG.js +215 -0
- package/dist/cli/run-JMEOTQCG.js.map +1 -0
- package/dist/cli/server-SYC3OVOP.js +2967 -0
- package/dist/cli/server-SYC3OVOP.js.map +1 -0
- package/dist/cli/sessions-MOJAALJI.js +102 -0
- package/dist/cli/sessions-MOJAALJI.js.map +1 -0
- package/dist/cli/setup-CCJZAWTY.js +404 -0
- package/dist/cli/setup-CCJZAWTY.js.map +1 -0
- package/dist/cli/stats-5RJCATCE.js +12 -0
- package/dist/cli/stats-5RJCATCE.js.map +1 -0
- package/dist/cli/update-4TJWRUIN.js +90 -0
- package/dist/cli/update-4TJWRUIN.js.map +1 -0
- package/dist/cli/version-3MYFE4G6.js +29 -0
- package/dist/cli/version-3MYFE4G6.js.map +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +493 -89
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-VWFJNLIK.js +0 -1031
- package/dist/cli/chunk-VWFJNLIK.js.map +0 -1
- /package/dist/cli/{prompt-XHICFAYN.js.map → chat-Q5ZCVIOO.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/ui/theme.ts"],"sourcesContent":["/** Brand gradient — REASONIX wordmark sweep, reused for accents / progress / dividers. */\nexport const GRADIENT: ReadonlyArray<string> = [\n \"#5eead4\", // teal\n \"#67e8f9\", // cyan\n \"#7dd3fc\", // sky\n \"#93c5fd\", // blue\n \"#a5b4fc\", // indigo\n \"#c4b5fd\", // violet\n \"#d8b4fe\", // purple\n \"#f0abfc\", // fuchsia\n];\n\n/** Tailwind 400/500 row — keeps tone consistent with GRADIENT. */\nexport const COLOR = {\n primary: \"#67e8f9\", // cyan-300\n accent: \"#c4b5fd\", // violet-300\n brand: \"#5eead4\", // teal-300\n\n user: \"#67e8f9\", // user message glyph + bar\n assistant: \"#86efac\", // green-300, assistant glyph + bar\n tool: \"#fcd34d\", // amber-300, tool ok pill bg\n toolErr: \"#fda4af\", // rose-300, tool err pill bg\n info: \"#94a3b8\", // slate-400, info / dim\n warn: \"#fbbf24\", // amber-400\n err: \"#f87171\", // red-400\n ok: \"#4ade80\", // green-400\n} as const;\n\nexport const GLYPH = {\n brand: \"◈\",\n user: \"◇\",\n assistant: \"◆\",\n toolOk: \"▣\",\n toolErr: \"▥\",\n warn: \"▲\",\n err: \"✦\",\n arrow: \"›\",\n bullet: \"·\",\n bar: \"▎\",\n thinBar: \"▏\",\n block: \"█\",\n shade1: \"░\",\n shade2: \"▒\",\n shade3: \"▓\",\n\n // Status icons — checkbox-style states used across plan steps,\n // job rows, history entries. Pair with the COLOR semantics:\n // done→ok, cur→primary, pending→info-faint, fail→err.\n done: \"✓\",\n cur: \"▸\",\n pending: \"○\",\n fail: \"✗\",\n running: \"●\",\n\n // Tree-drawing chars for hierarchical lists (plan steps, sub-loops,\n // hook attachments). 1 cell each; render fine in every monospace\n // font we've tested.\n branch: \"┣\",\n branchEnd: \"┗\",\n branchStub: \"┃\",\n rule: \"─\",\n\n // Spinner frames — 4-step rotation. Cycle every 200ms via setInterval\n // (Ink's useEffect setState pattern). Equivalent to ink-spinner but\n // with our own cadence + character set.\n spinFrames: [\"◐\", \"◓\", \"◑\", \"◒\"] as readonly string[],\n} as const;\n\n/** Ordering survives 256-/16-color quantization — canvas always darker than sel. */\nexport const SURFACE = {\n canvas: \"#070a10\",\n shell: \"#0b1019\",\n card: \"#101721\",\n elev: \"#161f2c\",\n sel: \"#1a2433\",\n line: \"#1c2433\",\n lineSoft: \"#141b27\",\n} as const;\n\nexport const FG = {\n strong: \"#e6edf6\",\n default: \"#cbd5e1\",\n dim: \"#94a3b8\",\n faint: \"#64748b\",\n ghost: \"#475569\",\n} as const;\n\nexport function gradientCells(\n width: number,\n glyph: string = GLYPH.block,\n): Array<{ ch: string; color: string }> {\n const cells: Array<{ ch: string; color: string }> = [];\n if (width <= 0) return cells;\n const last = GRADIENT.length - 1;\n for (let i = 0; i < width; i++) {\n const t = width === 1 ? 0 : (i * last) / (width - 1);\n const lo = Math.floor(t);\n const hi = Math.min(last, lo + 1);\n // Pick the closer of the two anchor colors for this cell. Linear\n // hex blending could be fancier but the discrete steps already\n // read as a smooth fade at any reasonable width.\n const color = t - lo < 0.5 ? GRADIENT[lo]! : GRADIENT[hi]!;\n cells.push({ ch: glyph, color });\n }\n return cells;\n}\n"],"mappings":";;;AACO,IAAM,WAAkC;AAAA,EAC7C;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAGO,IAAM,QAAQ;AAAA,EACnB,SAAS;AAAA;AAAA,EACT,QAAQ;AAAA;AAAA,EACR,OAAO;AAAA;AAAA,EAEP,MAAM;AAAA;AAAA,EACN,WAAW;AAAA;AAAA,EACX,MAAM;AAAA;AAAA,EACN,SAAS;AAAA;AAAA,EACT,MAAM;AAAA;AAAA,EACN,MAAM;AAAA;AAAA,EACN,KAAK;AAAA;AAAA,EACL,IAAI;AAAA;AACN;AAEO,IAAM,QAAQ;AAAA,EACnB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA;AAAA;AAAA;AAAA,EAKR,MAAM;AAAA,EACN,KAAK;AAAA,EACL,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA;AAAA;AAAA;AAAA,EAKT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,MAAM;AAAA;AAAA;AAAA;AAAA,EAKN,YAAY,CAAC,UAAK,UAAK,UAAK,QAAG;AACjC;","names":[]}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
cacheSavingsUsd,
|
|
4
|
+
claudeEquivalentCost,
|
|
5
|
+
costUsd
|
|
6
|
+
} from "./chunk-ORM6PK57.js";
|
|
7
|
+
|
|
8
|
+
// src/telemetry/usage.ts
|
|
9
|
+
import {
|
|
10
|
+
appendFileSync,
|
|
11
|
+
closeSync,
|
|
12
|
+
existsSync,
|
|
13
|
+
fstatSync,
|
|
14
|
+
mkdirSync,
|
|
15
|
+
openSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
readSync,
|
|
18
|
+
renameSync,
|
|
19
|
+
statSync,
|
|
20
|
+
unlinkSync,
|
|
21
|
+
writeFileSync
|
|
22
|
+
} from "fs";
|
|
23
|
+
import { homedir } from "os";
|
|
24
|
+
import { dirname, join } from "path";
|
|
25
|
+
function defaultUsageLogPath(homeDirOverride) {
|
|
26
|
+
return join(homeDirOverride ?? homedir(), ".reasonix", "usage.jsonl");
|
|
27
|
+
}
|
|
28
|
+
var USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
29
|
+
var USAGE_RETENTION_DAYS = 365;
|
|
30
|
+
function compactUsageLogIfLarge(path, now) {
|
|
31
|
+
let raw;
|
|
32
|
+
try {
|
|
33
|
+
const fd = openSync(path, "r");
|
|
34
|
+
try {
|
|
35
|
+
const stat = fstatSync(fd);
|
|
36
|
+
if (stat.size < USAGE_COMPACTION_THRESHOLD_BYTES) return;
|
|
37
|
+
const buf = Buffer.alloc(stat.size);
|
|
38
|
+
let read = 0;
|
|
39
|
+
while (read < stat.size) {
|
|
40
|
+
const n = readSync(fd, buf, read, stat.size - read, read);
|
|
41
|
+
if (n <= 0) break;
|
|
42
|
+
read += n;
|
|
43
|
+
}
|
|
44
|
+
raw = buf.toString("utf8", 0, read);
|
|
45
|
+
} finally {
|
|
46
|
+
closeSync(fd);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const cutoff = now - USAGE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
52
|
+
const lines = raw.split(/\r?\n/);
|
|
53
|
+
const kept = [];
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
if (!line.trim()) continue;
|
|
56
|
+
try {
|
|
57
|
+
const rec = JSON.parse(line);
|
|
58
|
+
if (isValidRecord(rec) && rec.ts >= cutoff) kept.push(line);
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (kept.length === lines.filter((l) => l.trim()).length) return;
|
|
63
|
+
const tmp = `${path}.compacting`;
|
|
64
|
+
try {
|
|
65
|
+
writeFileSync(tmp, kept.length > 0 ? `${kept.join("\n")}
|
|
66
|
+
` : "", "utf8");
|
|
67
|
+
renameSync(tmp, path);
|
|
68
|
+
} catch {
|
|
69
|
+
try {
|
|
70
|
+
unlinkSync(tmp);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function appendUsage(input) {
|
|
76
|
+
const record = {
|
|
77
|
+
ts: input.now ?? Date.now(),
|
|
78
|
+
session: input.session,
|
|
79
|
+
model: input.model,
|
|
80
|
+
promptTokens: input.usage.promptTokens,
|
|
81
|
+
completionTokens: input.usage.completionTokens,
|
|
82
|
+
cacheHitTokens: input.usage.promptCacheHitTokens,
|
|
83
|
+
cacheMissTokens: input.usage.promptCacheMissTokens,
|
|
84
|
+
costUsd: costUsd(input.model, input.usage),
|
|
85
|
+
claudeEquivUsd: claudeEquivalentCost(input.usage)
|
|
86
|
+
};
|
|
87
|
+
if (input.kind === "subagent") record.kind = "subagent";
|
|
88
|
+
if (input.subagent) record.subagent = input.subagent;
|
|
89
|
+
const path = input.path ?? defaultUsageLogPath();
|
|
90
|
+
try {
|
|
91
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
92
|
+
appendFileSync(path, `${JSON.stringify(record)}
|
|
93
|
+
`, "utf8");
|
|
94
|
+
compactUsageLogIfLarge(path, record.ts);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
return record;
|
|
98
|
+
}
|
|
99
|
+
function readUsageLog(path = defaultUsageLogPath()) {
|
|
100
|
+
if (!existsSync(path)) return [];
|
|
101
|
+
let raw;
|
|
102
|
+
try {
|
|
103
|
+
raw = readFileSync(path, "utf8");
|
|
104
|
+
} catch {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
109
|
+
if (!line.trim()) continue;
|
|
110
|
+
try {
|
|
111
|
+
const rec = JSON.parse(line);
|
|
112
|
+
if (isValidRecord(rec)) out.push(rec);
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
function isValidRecord(rec) {
|
|
119
|
+
if (!rec || typeof rec !== "object") return false;
|
|
120
|
+
const r = rec;
|
|
121
|
+
return typeof r.ts === "number" && typeof r.model === "string" && typeof r.promptTokens === "number" && typeof r.completionTokens === "number" && typeof r.cacheHitTokens === "number" && typeof r.cacheMissTokens === "number" && typeof r.costUsd === "number" && typeof r.claudeEquivUsd === "number";
|
|
122
|
+
}
|
|
123
|
+
function bucketCacheHitRatio(b) {
|
|
124
|
+
const denom = b.cacheHitTokens + b.cacheMissTokens;
|
|
125
|
+
return denom > 0 ? b.cacheHitTokens / denom : 0;
|
|
126
|
+
}
|
|
127
|
+
function bucketSavingsFraction(b) {
|
|
128
|
+
return b.claudeEquivUsd > 0 ? 1 - b.costUsd / b.claudeEquivUsd : 0;
|
|
129
|
+
}
|
|
130
|
+
function emptyBucket(label, since) {
|
|
131
|
+
return {
|
|
132
|
+
label,
|
|
133
|
+
since,
|
|
134
|
+
turns: 0,
|
|
135
|
+
promptTokens: 0,
|
|
136
|
+
completionTokens: 0,
|
|
137
|
+
cacheHitTokens: 0,
|
|
138
|
+
cacheMissTokens: 0,
|
|
139
|
+
costUsd: 0,
|
|
140
|
+
claudeEquivUsd: 0,
|
|
141
|
+
cacheSavingsUsd: 0
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function addToBucket(b, r) {
|
|
145
|
+
b.turns += 1;
|
|
146
|
+
b.promptTokens += r.promptTokens;
|
|
147
|
+
b.completionTokens += r.completionTokens;
|
|
148
|
+
b.cacheHitTokens += r.cacheHitTokens;
|
|
149
|
+
b.cacheMissTokens += r.cacheMissTokens;
|
|
150
|
+
b.costUsd += r.costUsd;
|
|
151
|
+
b.claudeEquivUsd += r.claudeEquivUsd;
|
|
152
|
+
b.cacheSavingsUsd += cacheSavingsUsd(r.model, r.cacheHitTokens);
|
|
153
|
+
}
|
|
154
|
+
function aggregateUsage(records, opts = {}) {
|
|
155
|
+
const now = opts.now ?? Date.now();
|
|
156
|
+
const day = 24 * 60 * 60 * 1e3;
|
|
157
|
+
const today = emptyBucket("today", now - day);
|
|
158
|
+
const week = emptyBucket("week", now - 7 * day);
|
|
159
|
+
const month = emptyBucket("month", now - 30 * day);
|
|
160
|
+
const all = emptyBucket("all-time", 0);
|
|
161
|
+
const modelCounts = /* @__PURE__ */ new Map();
|
|
162
|
+
const sessionCounts = /* @__PURE__ */ new Map();
|
|
163
|
+
let firstSeen = null;
|
|
164
|
+
let lastSeen = null;
|
|
165
|
+
const skillCounts = /* @__PURE__ */ new Map();
|
|
166
|
+
let subagentTotal = 0;
|
|
167
|
+
let subagentCost = 0;
|
|
168
|
+
let subagentDuration = 0;
|
|
169
|
+
for (const r of records) {
|
|
170
|
+
addToBucket(all, r);
|
|
171
|
+
if (r.ts >= today.since) addToBucket(today, r);
|
|
172
|
+
if (r.ts >= week.since) addToBucket(week, r);
|
|
173
|
+
if (r.ts >= month.since) addToBucket(month, r);
|
|
174
|
+
modelCounts.set(r.model, (modelCounts.get(r.model) ?? 0) + 1);
|
|
175
|
+
const sessKey = r.session ?? "(ephemeral)";
|
|
176
|
+
sessionCounts.set(sessKey, (sessionCounts.get(sessKey) ?? 0) + 1);
|
|
177
|
+
if (firstSeen === null || r.ts < firstSeen) firstSeen = r.ts;
|
|
178
|
+
if (lastSeen === null || r.ts > lastSeen) lastSeen = r.ts;
|
|
179
|
+
if (r.kind === "subagent") {
|
|
180
|
+
subagentTotal += 1;
|
|
181
|
+
subagentCost += r.costUsd;
|
|
182
|
+
const dur = r.subagent?.durationMs ?? 0;
|
|
183
|
+
subagentDuration += dur;
|
|
184
|
+
const key = r.subagent?.skillName?.trim() || "(adhoc)";
|
|
185
|
+
const prev = skillCounts.get(key) ?? { count: 0, costUsd: 0, durationMs: 0 };
|
|
186
|
+
prev.count += 1;
|
|
187
|
+
prev.costUsd += r.costUsd;
|
|
188
|
+
prev.durationMs += dur;
|
|
189
|
+
skillCounts.set(key, prev);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const byModel = Array.from(modelCounts.entries()).map(([model, turns]) => ({ model, turns })).sort((a, b) => b.turns - a.turns);
|
|
193
|
+
const bySession = Array.from(sessionCounts.entries()).map(([session, turns]) => ({ session, turns })).sort((a, b) => b.turns - a.turns);
|
|
194
|
+
const subagents = subagentTotal > 0 ? {
|
|
195
|
+
total: subagentTotal,
|
|
196
|
+
costUsd: subagentCost,
|
|
197
|
+
totalDurationMs: subagentDuration,
|
|
198
|
+
bySkill: Array.from(skillCounts.entries()).map(([skillName, v]) => ({ skillName, ...v })).sort((a, b) => b.count - a.count)
|
|
199
|
+
} : void 0;
|
|
200
|
+
return {
|
|
201
|
+
buckets: [today, week, month, all],
|
|
202
|
+
byModel,
|
|
203
|
+
bySession,
|
|
204
|
+
firstSeen,
|
|
205
|
+
lastSeen,
|
|
206
|
+
subagents
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function formatLogSize(path = defaultUsageLogPath()) {
|
|
210
|
+
if (!existsSync(path)) return "";
|
|
211
|
+
try {
|
|
212
|
+
const s = statSync(path);
|
|
213
|
+
const bytes = s.size;
|
|
214
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
215
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
216
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
217
|
+
} catch {
|
|
218
|
+
return "";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export {
|
|
223
|
+
defaultUsageLogPath,
|
|
224
|
+
appendUsage,
|
|
225
|
+
readUsageLog,
|
|
226
|
+
bucketCacheHitRatio,
|
|
227
|
+
bucketSavingsFraction,
|
|
228
|
+
aggregateUsage,
|
|
229
|
+
formatLogSize
|
|
230
|
+
};
|
|
231
|
+
//# sourceMappingURL=chunk-ZTLZO42A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/telemetry/usage.ts"],"sourcesContent":["/** Append-only JSONL of per-turn tokens + cost; best-effort writes, never blocks the turn. No prompts/completions logged. */\n\nimport {\n appendFileSync,\n closeSync,\n existsSync,\n fstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n readSync,\n renameSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { Usage } from \"../client.js\";\nimport {\n CLAUDE_SONNET_PRICING,\n DEEPSEEK_PRICING,\n cacheSavingsUsd,\n claudeEquivalentCost,\n costUsd,\n} from \"./stats.js\";\n\n/** One turn's snapshot — serialized verbatim as a JSONL line. */\nexport interface UsageRecord {\n /** Epoch millis when the record was written. */\n ts: number;\n /** Session name if the turn ran inside a persisted session, `null` for ephemeral. */\n session: string | null;\n /** Model id the turn ran against (drives the pricing lookup). */\n model: string;\n promptTokens: number;\n completionTokens: number;\n cacheHitTokens: number;\n cacheMissTokens: number;\n /** Total cost of the turn in USD. */\n costUsd: number;\n /** What the same turn would have cost at Claude Sonnet 4.6 rates. */\n claudeEquivUsd: number;\n /** Absent on legacy records — treat as \"turn\" when missing. */\n kind?: \"turn\" | \"subagent\";\n /** Present when `kind === \"subagent\"`. Attribution metadata for the /stats roll-up. */\n subagent?: {\n /** Skill that spawned it, when the spawn came from a `runAs: subagent` skill. */\n skillName?: string;\n /** First ~60 chars of the task prompt — enough context to recognize a run, never the full text. */\n taskPreview: string;\n /** Tool calls the child loop dispatched before returning. */\n toolIters: number;\n /** Wall-clock ms. */\n durationMs: number;\n };\n}\n\n/** Where the log lives. Tests override via `opts.path`. */\nexport function defaultUsageLogPath(homeDirOverride?: string): string {\n return join(homeDirOverride ?? homedir(), \".reasonix\", \"usage.jsonl\");\n}\n\nexport interface AppendUsageInput {\n session: string | null;\n model: string;\n usage: Usage;\n /** Override the timestamp (tests). */\n now?: number;\n /** Override the log path (tests). */\n path?: string;\n /** When appending a subagent summary row, set `kind: \"subagent\"` and populate `subagent`. */\n kind?: \"turn\" | \"subagent\";\n subagent?: UsageRecord[\"subagent\"];\n}\n\nconst USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;\nconst USAGE_RETENTION_DAYS = 365;\n\nfunction compactUsageLogIfLarge(path: string, now: number): void {\n // Open once for the size check + read so they bind to the same fd\n // (CodeQL js/file-system-race). Concurrent appenders that grow the\n // log between check and read can no longer cause us to act on a\n // stale size and rewrite based on partial content.\n let raw: string;\n try {\n const fd = openSync(path, \"r\");\n try {\n const stat = fstatSync(fd);\n if (stat.size < USAGE_COMPACTION_THRESHOLD_BYTES) return;\n const buf = Buffer.alloc(stat.size);\n let read = 0;\n while (read < stat.size) {\n const n = readSync(fd, buf, read, stat.size - read, read);\n if (n <= 0) break;\n read += n;\n }\n raw = buf.toString(\"utf8\", 0, read);\n } finally {\n closeSync(fd);\n }\n } catch {\n return;\n }\n const cutoff = now - USAGE_RETENTION_DAYS * 24 * 60 * 60 * 1000;\n const lines = raw.split(/\\r?\\n/);\n const kept: string[] = [];\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const rec = JSON.parse(line);\n if (isValidRecord(rec) && rec.ts >= cutoff) kept.push(line);\n } catch {\n /* skip malformed */\n }\n }\n // No-op when nothing aged out — avoids rewrite storms on fresh logs.\n if (kept.length === lines.filter((l) => l.trim()).length) return;\n // Write to a sibling tmp path then rename — atomic from a reader's\n // POV and severs CodeQL's stat→write taint chain. Concurrent\n // appenders during the compaction window lose their entries; we\n // accept that for a best-effort usage log.\n const tmp = `${path}.compacting`;\n try {\n writeFileSync(tmp, kept.length > 0 ? `${kept.join(\"\\n\")}\\n` : \"\", \"utf8\");\n renameSync(tmp, path);\n } catch {\n try {\n unlinkSync(tmp);\n } catch {\n /* tmp may not exist — ignore */\n }\n }\n}\n\n/** Returns the record so tests can assert cost fields without re-reading the log. */\nexport function appendUsage(input: AppendUsageInput): UsageRecord {\n const record: UsageRecord = {\n ts: input.now ?? Date.now(),\n session: input.session,\n model: input.model,\n promptTokens: input.usage.promptTokens,\n completionTokens: input.usage.completionTokens,\n cacheHitTokens: input.usage.promptCacheHitTokens,\n cacheMissTokens: input.usage.promptCacheMissTokens,\n costUsd: costUsd(input.model, input.usage),\n claudeEquivUsd: claudeEquivalentCost(input.usage),\n };\n if (input.kind === \"subagent\") record.kind = \"subagent\";\n if (input.subagent) record.subagent = input.subagent;\n\n const path = input.path ?? defaultUsageLogPath();\n try {\n mkdirSync(dirname(path), { recursive: true });\n appendFileSync(path, `${JSON.stringify(record)}\\n`, \"utf8\");\n compactUsageLogIfLarge(path, record.ts);\n } catch {\n /* best-effort — disk failure shouldn't break the chat */\n }\n return record;\n}\n\nexport function readUsageLog(path: string = defaultUsageLogPath()): UsageRecord[] {\n if (!existsSync(path)) return [];\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return [];\n }\n const out: UsageRecord[] = [];\n for (const line of raw.split(/\\r?\\n/)) {\n if (!line.trim()) continue;\n try {\n const rec = JSON.parse(line);\n if (isValidRecord(rec)) out.push(rec);\n } catch {\n /* skip malformed */\n }\n }\n return out;\n}\n\nfunction isValidRecord(rec: unknown): rec is UsageRecord {\n if (!rec || typeof rec !== \"object\") return false;\n const r = rec as Partial<UsageRecord>;\n return (\n typeof r.ts === \"number\" &&\n typeof r.model === \"string\" &&\n typeof r.promptTokens === \"number\" &&\n typeof r.completionTokens === \"number\" &&\n typeof r.cacheHitTokens === \"number\" &&\n typeof r.cacheMissTokens === \"number\" &&\n typeof r.costUsd === \"number\" &&\n typeof r.claudeEquivUsd === \"number\"\n );\n}\n\n/** One row of the `reasonix stats` dashboard — a rolled-up window. */\nexport interface UsageBucket {\n label: string;\n /** Start of the window as epoch millis. `0` = unbounded (all-time). */\n since: number;\n turns: number;\n promptTokens: number;\n completionTokens: number;\n cacheHitTokens: number;\n cacheMissTokens: number;\n costUsd: number;\n claudeEquivUsd: number;\n /** Recomputed from current pricing each aggregate — intentionally NOT frozen with `costUsd`. */\n cacheSavingsUsd: number;\n}\n\n/** Cache hit ratio for a bucket — zero denominator returns 0. */\nexport function bucketCacheHitRatio(b: UsageBucket): number {\n const denom = b.cacheHitTokens + b.cacheMissTokens;\n return denom > 0 ? b.cacheHitTokens / denom : 0;\n}\n\n/** Savings vs Claude as a fraction (0.94 = 94% savings). 0 if Claude cost is 0. */\nexport function bucketSavingsFraction(b: UsageBucket): number {\n return b.claudeEquivUsd > 0 ? 1 - b.costUsd / b.claudeEquivUsd : 0;\n}\n\nfunction emptyBucket(label: string, since: number): UsageBucket {\n return {\n label,\n since,\n turns: 0,\n promptTokens: 0,\n completionTokens: 0,\n cacheHitTokens: 0,\n cacheMissTokens: 0,\n costUsd: 0,\n claudeEquivUsd: 0,\n cacheSavingsUsd: 0,\n };\n}\n\nfunction addToBucket(b: UsageBucket, r: UsageRecord): void {\n b.turns += 1;\n b.promptTokens += r.promptTokens;\n b.completionTokens += r.completionTokens;\n b.cacheHitTokens += r.cacheHitTokens;\n b.cacheMissTokens += r.cacheMissTokens;\n b.costUsd += r.costUsd;\n b.claudeEquivUsd += r.claudeEquivUsd;\n b.cacheSavingsUsd += cacheSavingsUsd(r.model, r.cacheHitTokens);\n}\n\nexport interface AggregateOptions {\n /** Override `Date.now()` for deterministic tests. */\n now?: number;\n}\n\nexport interface UsageAggregate {\n /** Fixed-order rolling windows: today, week, month, all-time. */\n buckets: UsageBucket[];\n /** Model id → turn count. Sorted descending; top entry is the \"most used.\" */\n byModel: Array<{ model: string; turns: number }>;\n /** Session name → turn count. Sorted descending. Null sessions are grouped under `\"(ephemeral)\"`. */\n bySession: Array<{ session: string; turns: number }>;\n /** Earliest record's ts, or `null` when the log is empty. Drives \"saved $X since <date>\". */\n firstSeen: number | null;\n /** Latest record's ts, or `null` when the log is empty. */\n lastSeen: number | null;\n /** Undefined when no subagent records exist; counts spawns, not internal child-loop turns. */\n subagents?: SubagentAggregate;\n}\n\n/** Rolled-up view of all `kind: \"subagent\"` records. */\nexport interface SubagentAggregate {\n total: number;\n costUsd: number;\n totalDurationMs: number;\n /** Per-skill breakdown. Records without `skillName` (raw spawn_subagent calls) group under `\"(adhoc)\"`. */\n bySkill: Array<{ skillName: string; count: number; costUsd: number; durationMs: number }>;\n}\n\n/** Rolling 24h/7d/30d windows — avoids \"it's 00:03, 'today' is empty\" surprises. */\nexport function aggregateUsage(\n records: UsageRecord[],\n opts: AggregateOptions = {},\n): UsageAggregate {\n const now = opts.now ?? Date.now();\n const day = 24 * 60 * 60 * 1000;\n const today = emptyBucket(\"today\", now - day);\n const week = emptyBucket(\"week\", now - 7 * day);\n const month = emptyBucket(\"month\", now - 30 * day);\n const all = emptyBucket(\"all-time\", 0);\n\n const modelCounts = new Map<string, number>();\n const sessionCounts = new Map<string, number>();\n let firstSeen: number | null = null;\n let lastSeen: number | null = null;\n const skillCounts = new Map<string, { count: number; costUsd: number; durationMs: number }>();\n let subagentTotal = 0;\n let subagentCost = 0;\n let subagentDuration = 0;\n\n for (const r of records) {\n addToBucket(all, r);\n if (r.ts >= today.since) addToBucket(today, r);\n if (r.ts >= week.since) addToBucket(week, r);\n if (r.ts >= month.since) addToBucket(month, r);\n\n modelCounts.set(r.model, (modelCounts.get(r.model) ?? 0) + 1);\n const sessKey = r.session ?? \"(ephemeral)\";\n sessionCounts.set(sessKey, (sessionCounts.get(sessKey) ?? 0) + 1);\n\n if (firstSeen === null || r.ts < firstSeen) firstSeen = r.ts;\n if (lastSeen === null || r.ts > lastSeen) lastSeen = r.ts;\n\n if (r.kind === \"subagent\") {\n subagentTotal += 1;\n subagentCost += r.costUsd;\n const dur = r.subagent?.durationMs ?? 0;\n subagentDuration += dur;\n const key = r.subagent?.skillName?.trim() || \"(adhoc)\";\n const prev = skillCounts.get(key) ?? { count: 0, costUsd: 0, durationMs: 0 };\n prev.count += 1;\n prev.costUsd += r.costUsd;\n prev.durationMs += dur;\n skillCounts.set(key, prev);\n }\n }\n\n const byModel = Array.from(modelCounts.entries())\n .map(([model, turns]) => ({ model, turns }))\n .sort((a, b) => b.turns - a.turns);\n const bySession = Array.from(sessionCounts.entries())\n .map(([session, turns]) => ({ session, turns }))\n .sort((a, b) => b.turns - a.turns);\n\n const subagents: SubagentAggregate | undefined =\n subagentTotal > 0\n ? {\n total: subagentTotal,\n costUsd: subagentCost,\n totalDurationMs: subagentDuration,\n bySkill: Array.from(skillCounts.entries())\n .map(([skillName, v]) => ({ skillName, ...v }))\n .sort((a, b) => b.count - a.count),\n }\n : undefined;\n\n return {\n buckets: [today, week, month, all],\n byModel,\n bySession,\n firstSeen,\n lastSeen,\n subagents,\n };\n}\n\n/** File-size helper for the stats header — \"1.2 MB\" etc. Returns \"\" if missing. */\nexport function formatLogSize(path: string = defaultUsageLogPath()): string {\n if (!existsSync(path)) return \"\";\n try {\n const s = statSync(path);\n const bytes = s.size;\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n } catch {\n return \"\";\n }\n}\n\n/** Re-exports for downstream consumers that also want the pricing constants. */\nexport { CLAUDE_SONNET_PRICING, DEEPSEEK_PRICING };\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
bootstrapSemanticSearchInCodeMode
|
|
4
|
+
} from "./chunk-J5VLP23S.js";
|
|
5
|
+
import {
|
|
6
|
+
chatCommand
|
|
7
|
+
} from "./chunk-MDHVWCJ4.js";
|
|
8
|
+
import "./chunk-BQNUJJN7.js";
|
|
9
|
+
import "./chunk-RFX7TYVV.js";
|
|
10
|
+
import "./chunk-63KAV5DX.js";
|
|
11
|
+
import {
|
|
12
|
+
markPhase
|
|
13
|
+
} from "./chunk-CPOV2O73.js";
|
|
14
|
+
import {
|
|
15
|
+
ToolRegistry,
|
|
16
|
+
registerChoiceTool,
|
|
17
|
+
registerFilesystemTools,
|
|
18
|
+
registerMemoryTools,
|
|
19
|
+
registerPlanTool,
|
|
20
|
+
registerTodoTool
|
|
21
|
+
} from "./chunk-Q6YFXW7H.js";
|
|
22
|
+
import "./chunk-I6YIAK6C.js";
|
|
23
|
+
import "./chunk-XJLZ4HKU.js";
|
|
24
|
+
import "./chunk-XHQIK7B6.js";
|
|
25
|
+
import "./chunk-6TMHAK5D.js";
|
|
26
|
+
import "./chunk-SDE5U32Z.js";
|
|
27
|
+
import "./chunk-ZPTSJGX5.js";
|
|
28
|
+
import "./chunk-MHDNZXJJ.js";
|
|
29
|
+
import "./chunk-D5DKXIP5.js";
|
|
30
|
+
import "./chunk-KMWKGPFZ.js";
|
|
31
|
+
import "./chunk-3Q3C4W66.js";
|
|
32
|
+
import "./chunk-4DCHFFEY.js";
|
|
33
|
+
import "./chunk-FXGQ5NHE.js";
|
|
34
|
+
import "./chunk-G3XNWSFN.js";
|
|
35
|
+
import "./chunk-SOZE7V7V.js";
|
|
36
|
+
import {
|
|
37
|
+
JobRegistry,
|
|
38
|
+
registerShellTools
|
|
39
|
+
} from "./chunk-W4LDFAZ6.js";
|
|
40
|
+
import "./chunk-U3V2ZQ5J.js";
|
|
41
|
+
import "./chunk-FM57FNPJ.js";
|
|
42
|
+
import "./chunk-RZILUXUC.js";
|
|
43
|
+
import "./chunk-WBDE4IRI.js";
|
|
44
|
+
import "./chunk-2AWTGJ2C.js";
|
|
45
|
+
import "./chunk-5X7LZJDE.js";
|
|
46
|
+
import {
|
|
47
|
+
sanitizeName
|
|
48
|
+
} from "./chunk-DFP4YSVM.js";
|
|
49
|
+
import "./chunk-QGE6AF76.js";
|
|
50
|
+
import {
|
|
51
|
+
loadEditMode,
|
|
52
|
+
loadProjectShellAllowed,
|
|
53
|
+
readConfig
|
|
54
|
+
} from "./chunk-DULSP7JH.js";
|
|
55
|
+
import "./chunk-ZTLZO42A.js";
|
|
56
|
+
import "./chunk-ORM6PK57.js";
|
|
57
|
+
|
|
58
|
+
// src/cli/commands/code.tsx
|
|
59
|
+
import { readFileSync } from "fs";
|
|
60
|
+
import { basename, resolve } from "path";
|
|
61
|
+
async function codeCommand(opts = {}) {
|
|
62
|
+
markPhase("code_command_enter");
|
|
63
|
+
const { codeSystemPrompt } = await import("./prompt-V47QKSAR.js");
|
|
64
|
+
const rootDir = resolve(opts.dir ?? process.cwd());
|
|
65
|
+
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
66
|
+
const tools = new ToolRegistry();
|
|
67
|
+
const jobs = new JobRegistry();
|
|
68
|
+
const registerRootedTools = (root) => {
|
|
69
|
+
registerFilesystemTools(tools, { rootDir: root });
|
|
70
|
+
registerShellTools(tools, {
|
|
71
|
+
rootDir: root,
|
|
72
|
+
// Per-project "always allow" list persisted from prior ShellConfirm
|
|
73
|
+
// choices; merged on top of the built-in allowlist in shell.ts.
|
|
74
|
+
// GETTER form — re-read every dispatch so a prefix the user adds
|
|
75
|
+
// via ShellConfirm mid-session takes effect on the next shell call
|
|
76
|
+
// instead of waiting for `/new` or a relaunch.
|
|
77
|
+
extraAllowed: () => loadProjectShellAllowed(root),
|
|
78
|
+
// `yolo` edit-mode disables shell confirmations entirely. Re-read
|
|
79
|
+
// from config on each dispatch so /mode yolo (or Shift+Tab cycling
|
|
80
|
+
// through to it) flips the gate live without forcing a relaunch.
|
|
81
|
+
allowAll: () => loadEditMode() === "yolo",
|
|
82
|
+
jobs
|
|
83
|
+
});
|
|
84
|
+
registerMemoryTools(tools, { projectRoot: root });
|
|
85
|
+
};
|
|
86
|
+
const reBootstrapSemantic = async (root) => {
|
|
87
|
+
const result = await bootstrapSemanticSearchInCodeMode(tools, root);
|
|
88
|
+
if (!result.enabled) tools.unregister("semantic_search");
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
registerRootedTools(rootDir);
|
|
92
|
+
registerPlanTool(tools);
|
|
93
|
+
registerChoiceTool(tools);
|
|
94
|
+
registerTodoTool(tools);
|
|
95
|
+
markPhase("semantic_bootstrap_start");
|
|
96
|
+
const semantic = await reBootstrapSemantic(rootDir);
|
|
97
|
+
markPhase(
|
|
98
|
+
semantic.enabled ? "semantic_bootstrap_done_enabled" : "semantic_bootstrap_done_skipped"
|
|
99
|
+
);
|
|
100
|
+
process.stderr.write(
|
|
101
|
+
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)${semantic.enabled ? " \xB7 semantic_search on" : ""}
|
|
102
|
+
`
|
|
103
|
+
);
|
|
104
|
+
process.once("exit", () => {
|
|
105
|
+
void jobs.shutdown();
|
|
106
|
+
});
|
|
107
|
+
let systemAppendFileContents;
|
|
108
|
+
if (opts.systemAppend !== void 0 && opts.systemAppend.trim().length === 0) {
|
|
109
|
+
process.stderr.write("--system-append is empty \u2014 no prompt text will be appended\n");
|
|
110
|
+
}
|
|
111
|
+
if (opts.systemAppendFile) {
|
|
112
|
+
const filePath = resolve(opts.systemAppendFile);
|
|
113
|
+
try {
|
|
114
|
+
systemAppendFileContents = readFileSync(filePath, "utf8");
|
|
115
|
+
} catch (err) {
|
|
116
|
+
const e = err;
|
|
117
|
+
process.stderr.write(
|
|
118
|
+
`Error: cannot read --system-append-file "${filePath}": ${e.code ? `[${e.code}] ` : ""}${e.message}
|
|
119
|
+
`
|
|
120
|
+
);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
await chatCommand({
|
|
125
|
+
model: opts.model ?? "deepseek-v4-flash",
|
|
126
|
+
budgetUsd: opts.budgetUsd,
|
|
127
|
+
system: codeSystemPrompt(rootDir, {
|
|
128
|
+
hasSemanticSearch: semantic.enabled,
|
|
129
|
+
systemAppend: opts.systemAppend,
|
|
130
|
+
systemAppendFile: systemAppendFileContents
|
|
131
|
+
}),
|
|
132
|
+
transcript: opts.transcript,
|
|
133
|
+
session,
|
|
134
|
+
seedTools: tools,
|
|
135
|
+
codeMode: {
|
|
136
|
+
rootDir,
|
|
137
|
+
jobs,
|
|
138
|
+
reregisterTools: registerRootedTools,
|
|
139
|
+
reBootstrapSemantic
|
|
140
|
+
},
|
|
141
|
+
mcp: readConfig().mcp,
|
|
142
|
+
forceResume: opts.forceResume,
|
|
143
|
+
forceNew: opts.forceNew,
|
|
144
|
+
noDashboard: opts.noDashboard,
|
|
145
|
+
altScreen: opts.altScreen
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
codeCommand
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=code-DLR77NPZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/commands/code.tsx"],"sourcesContent":["/**\n * `reasonix code [dir]` — opinionated wrapper around `reasonix chat` for\n * code-editing workflows.\n *\n * What it does differently from plain chat:\n * - Registers native filesystem tools rooted at the given directory\n * (CWD by default). No subprocess, no `npx install` step, R1-\n * friendly schemas. Replaced the old `@modelcontextprotocol/server-filesystem`\n * subprocess in 0.4.9 because its `edit_file` argv shape was the\n * biggest driver of R1 DSML hallucinations.\n * - Uses a coding-focused system prompt (src/code/prompt.ts) that\n * teaches the model to propose edits as SEARCH/REPLACE blocks.\n * - Defaults to the `smart` preset (reasoner + harvest) because\n * coding tasks pay back R1 thinking.\n * - Scopes its session to the directory so projects don't share\n * conversation history.\n * - Hooks `codeMode` into the TUI so assistant replies get parsed\n * for SEARCH/REPLACE blocks and applied on disk after each turn.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename, resolve } from \"node:path\";\nimport { loadEditMode, loadProjectShellAllowed, readConfig } from \"../../config.js\";\nimport { bootstrapSemanticSearchInCodeMode } from \"../../index/semantic/tool.js\";\nimport { sanitizeName } from \"../../memory/session.js\";\nimport { ToolRegistry } from \"../../tools.js\";\nimport { registerChoiceTool } from \"../../tools/choice.js\";\nimport { registerFilesystemTools } from \"../../tools/filesystem.js\";\nimport { JobRegistry } from \"../../tools/jobs.js\";\nimport { registerMemoryTools } from \"../../tools/memory.js\";\nimport { registerPlanTool } from \"../../tools/plan.js\";\nimport { registerShellTools } from \"../../tools/shell.js\";\nimport { registerTodoTool } from \"../../tools/todo.js\";\nimport { markPhase } from \"../startup-profile.js\";\nimport { chatCommand } from \"./chat.js\";\n\nexport interface CodeOptions {\n /** Directory to root the filesystem tools at. Defaults to process.cwd(). */\n dir?: string;\n /** Override the default `smart` model. */\n model?: string;\n /** Disable session persistence. */\n noSession?: boolean;\n /** Transcript file for replay/diff. */\n transcript?: string;\n /** Skip the session picker — always resume prior messages. */\n forceResume?: boolean;\n /** Skip the session picker — always wipe prior messages and start fresh. */\n forceNew?: boolean;\n /**\n * Soft USD spend cap. Off by default. Same semantics as `chat`:\n * warns at 80%, refuses next turn at 100%. Mid-session adjustable\n * via `/budget <usd>` slash command.\n */\n budgetUsd?: number;\n /** Suppress the auto-launched embedded web dashboard. */\n noDashboard?: boolean;\n /** Inline string appended to the code system prompt after the generated base prompt. */\n systemAppend?: string;\n /** Path to a UTF-8 text file whose contents are appended to the code system prompt. */\n systemAppendFile?: string;\n /** Default true. Pass false (CLI: `--no-alt-screen`) to keep chat output in shell scrollback. */\n altScreen?: boolean;\n}\n\nexport async function codeCommand(opts: CodeOptions = {}): Promise<void> {\n markPhase(\"code_command_enter\");\n const { codeSystemPrompt } = await import(\"../../code/prompt.js\");\n const rootDir = resolve(opts.dir ?? process.cwd());\n // Per-directory session so switching projects doesn't mix histories.\n // `code-<sanitized-basename>` fits the session name rules without\n // truncating most project names.\n const session = opts.noSession ? undefined : `code-${sanitizeName(basename(rootDir))}`;\n\n // Native filesystem tools. No subprocess, ~50-200 ms faster per call\n // than the MCP server was, and `edit_file` takes a flat SEARCH/REPLACE\n // shape instead of the `string=\"false\"` JSON-in-string array that\n // triggered R1's DSML hallucinations all through 0.4.x.\n const tools = new ToolRegistry();\n // Background-process registry shared between the shell tools and the\n // TUI's /jobs + /kill slashes + exit cleanup. One per `reasonix code`\n // run — orphan prevention on SIGINT / process exit kills everything\n // it owns, so dev servers don't outlive the Reasonix process.\n const jobs = new JobRegistry();\n // Bundled re-registration so `/cwd <path>` can swap every rootDir-\n // dependent tool atomically. ToolRegistry.register is keyed by name\n // and overwrites in-place, so re-calling these against the existing\n // registry replaces the closures cleanly without disturbing tool\n // specs (names/descriptions/params don't reference rootDir, so the\n // prefix cache survives).\n const registerRootedTools = (root: string): void => {\n registerFilesystemTools(tools, { rootDir: root });\n registerShellTools(tools, {\n rootDir: root,\n // Per-project \"always allow\" list persisted from prior ShellConfirm\n // choices; merged on top of the built-in allowlist in shell.ts.\n // GETTER form — re-read every dispatch so a prefix the user adds\n // via ShellConfirm mid-session takes effect on the next shell call\n // instead of waiting for `/new` or a relaunch.\n extraAllowed: () => loadProjectShellAllowed(root),\n // `yolo` edit-mode disables shell confirmations entirely. Re-read\n // from config on each dispatch so /mode yolo (or Shift+Tab cycling\n // through to it) flips the gate live without forcing a relaunch.\n allowAll: () => loadEditMode() === \"yolo\",\n jobs,\n });\n // `remember` / `forget` / `recall_memory` — cross-session user memory.\n // Project scope hashes off rootDir so switching projects gets a fresh\n // per-project memory store; the global scope is shared across runs.\n registerMemoryTools(tools, { projectRoot: root });\n };\n // Async tail to `registerRootedTools`. Kept separate because the FS /\n // shell / memory re-registration above is sync and must happen before\n // the next tool dispatch, while semantic-index probing reads disk and\n // can race ahead in the background. On `/cwd`, App.tsx fires this\n // after the sync swap and surfaces the result via postInfo.\n const reBootstrapSemantic = async (root: string): Promise<{ enabled: boolean }> => {\n const result = await bootstrapSemanticSearchInCodeMode(tools, root);\n if (!result.enabled) tools.unregister(\"semantic_search\");\n return result;\n };\n registerRootedTools(rootDir);\n // `submit_plan` is always in the spec list so the prefix cache stays\n // stable across plan-mode toggles (Pillar 1). The tool itself is a\n // no-op outside plan mode and throws `PlanProposedError` when the\n // user has `/plan`-enabled the session.\n registerPlanTool(tools);\n // `ask_choice` — branching primitive. Independent of plan mode: the\n // model uses it to put a 2–4 way choice in front of the user\n // (strategy, style, library pick) without trying to squeeze the\n // menu into a submit_plan body. Keeping it always-registered\n // preserves the prefix cache across plan-mode toggles.\n registerChoiceTool(tools);\n // `todo_write` — lightweight in-session task tracker, no approval gate.\n // Independent of plan mode (readOnly=true so it stays callable in /plan).\n registerTodoTool(tools);\n // `run_skill` is intentionally NOT registered here — App.tsx wires it\n // up with the subagent runner attached, so `runAs: subagent` skills\n // can spawn isolated child loops. Doing it here would mean the App's\n // re-registration would shadow the no-runner version, which works\n // (last write wins) but obscures the wiring.\n\n // Bootstrap semantic_search. Silent: registers the tool when an\n // on-disk index already exists, skips entirely otherwise. Setup\n // happens via the explicit `reasonix index` command — never\n // by surprise on launch.\n markPhase(\"semantic_bootstrap_start\");\n const semantic = await reBootstrapSemantic(rootDir);\n markPhase(\n semantic.enabled ? \"semantic_bootstrap_done_enabled\" : \"semantic_bootstrap_done_skipped\",\n );\n\n process.stderr.write(\n `▸ reasonix code: rooted at ${rootDir}, session \"${session ?? \"(ephemeral)\"}\" · ${tools.size} native tool(s)${\n semantic.enabled ? \" · semantic_search on\" : \"\"\n }\\n`,\n );\n\n // Belt-and-suspenders cleanup: even though spawn(detached:false)\n // should tie child processes to the parent's lifetime, Windows cmd.exe\n // wrappers occasionally leak. We DON'T install SIGINT/SIGTERM\n // handlers here — that overrode Node's default \"exit on Ctrl+C\" with\n // a silent no-op, which made Ctrl+C feel broken in the TUI. App.tsx\n // owns the SIGINT path now (it shows the quit-armed banner and calls\n // exit() on confirmation); this 'exit' hook just guarantees the job\n // registry is drained on the way out, regardless of which exit path\n // fired.\n process.once(\"exit\", () => {\n void jobs.shutdown();\n });\n\n let systemAppendFileContents: string | undefined;\n if (opts.systemAppend !== undefined && opts.systemAppend.trim().length === 0) {\n process.stderr.write(\"--system-append is empty — no prompt text will be appended\\n\");\n }\n if (opts.systemAppendFile) {\n const filePath = resolve(opts.systemAppendFile);\n try {\n systemAppendFileContents = readFileSync(filePath, \"utf8\");\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n process.stderr.write(\n `Error: cannot read --system-append-file \"${filePath}\": ${e.code ? `[${e.code}] ` : \"\"}${e.message}\\n`,\n );\n process.exit(1);\n }\n }\n\n await chatCommand({\n model: opts.model ?? \"deepseek-v4-flash\",\n budgetUsd: opts.budgetUsd,\n system: codeSystemPrompt(rootDir, {\n hasSemanticSearch: semantic.enabled,\n systemAppend: opts.systemAppend,\n systemAppendFile: systemAppendFileContents,\n }),\n transcript: opts.transcript,\n session,\n seedTools: tools,\n codeMode: {\n rootDir,\n jobs,\n reregisterTools: registerRootedTools,\n reBootstrapSemantic,\n },\n mcp: readConfig().mcp,\n forceResume: opts.forceResume,\n forceNew: opts.forceNew,\n noDashboard: opts.noDashboard,\n altScreen: opts.altScreen,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoBA,SAAS,oBAAoB;AAC7B,SAAS,UAAU,eAAe;AA4ClC,eAAsB,YAAY,OAAoB,CAAC,GAAkB;AACvE,YAAU,oBAAoB;AAC9B,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,sBAAsB;AAChE,QAAM,UAAU,QAAQ,KAAK,OAAO,QAAQ,IAAI,CAAC;AAIjD,QAAM,UAAU,KAAK,YAAY,SAAY,QAAQ,aAAa,SAAS,OAAO,CAAC,CAAC;AAMpF,QAAM,QAAQ,IAAI,aAAa;AAK/B,QAAM,OAAO,IAAI,YAAY;AAO7B,QAAM,sBAAsB,CAAC,SAAuB;AAClD,4BAAwB,OAAO,EAAE,SAAS,KAAK,CAAC;AAChD,uBAAmB,OAAO;AAAA,MACxB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMT,cAAc,MAAM,wBAAwB,IAAI;AAAA;AAAA;AAAA;AAAA,MAIhD,UAAU,MAAM,aAAa,MAAM;AAAA,MACnC;AAAA,IACF,CAAC;AAID,wBAAoB,OAAO,EAAE,aAAa,KAAK,CAAC;AAAA,EAClD;AAMA,QAAM,sBAAsB,OAAO,SAAgD;AACjF,UAAM,SAAS,MAAM,kCAAkC,OAAO,IAAI;AAClE,QAAI,CAAC,OAAO,QAAS,OAAM,WAAW,iBAAiB;AACvD,WAAO;AAAA,EACT;AACA,sBAAoB,OAAO;AAK3B,mBAAiB,KAAK;AAMtB,qBAAmB,KAAK;AAGxB,mBAAiB,KAAK;AAWtB,YAAU,0BAA0B;AACpC,QAAM,WAAW,MAAM,oBAAoB,OAAO;AAClD;AAAA,IACE,SAAS,UAAU,oCAAoC;AAAA,EACzD;AAEA,UAAQ,OAAO;AAAA,IACb,mCAA8B,OAAO,cAAc,WAAW,aAAa,UAAO,MAAM,IAAI,kBAC1F,SAAS,UAAU,6BAA0B,EAC/C;AAAA;AAAA,EACF;AAWA,UAAQ,KAAK,QAAQ,MAAM;AACzB,SAAK,KAAK,SAAS;AAAA,EACrB,CAAC;AAED,MAAI;AACJ,MAAI,KAAK,iBAAiB,UAAa,KAAK,aAAa,KAAK,EAAE,WAAW,GAAG;AAC5E,YAAQ,OAAO,MAAM,mEAA8D;AAAA,EACrF;AACA,MAAI,KAAK,kBAAkB;AACzB,UAAM,WAAW,QAAQ,KAAK,gBAAgB;AAC9C,QAAI;AACF,iCAA2B,aAAa,UAAU,MAAM;AAAA,IAC1D,SAAS,KAAK;AACZ,YAAM,IAAI;AACV,cAAQ,OAAO;AAAA,QACb,4CAA4C,QAAQ,MAAM,EAAE,OAAO,IAAI,EAAE,IAAI,OAAO,EAAE,GAAG,EAAE,OAAO;AAAA;AAAA,MACpG;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,YAAY;AAAA,IAChB,OAAO,KAAK,SAAS;AAAA,IACrB,WAAW,KAAK;AAAA,IAChB,QAAQ,iBAAiB,SAAS;AAAA,MAChC,mBAAmB,SAAS;AAAA,MAC5B,cAAc,KAAK;AAAA,MACnB,kBAAkB;AAAA,IACpB,CAAC;AAAA,IACD,YAAY,KAAK;AAAA,IACjB;AAAA,IACA,WAAW;AAAA,IACX,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,IACF;AAAA,IACA,KAAK,WAAW,EAAE;AAAA,IAClB,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,IACf,aAAa,KAAK;AAAA,IAClB,WAAW,KAAK;AAAA,EAClB,CAAC;AACH;","names":[]}
|