sprag-cli 3.67.0 → 3.69.0
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.ko.md +33 -66
- package/README.md +41 -74
- package/bin/cli.js +150 -62
- package/docs/CODEX.ko.md +32 -0
- package/docs/CODEX.md +34 -0
- package/package.json +15 -4
- package/src/{codex-brief.js → codex/brief.js} +7 -7
- package/src/{codex-budget.js → codex/budget.js} +2 -2
- package/src/{codex-cache-policy.js → codex/cache-policy.js} +9 -9
- package/src/{codex-delegation-diagnostics.js → codex/delegation-diagnostics.js} +2 -2
- package/src/{codex-delegation.js → codex/delegation.js} +11 -11
- package/src/{codex-doc2md-ledger.js → codex/doc2md-ledger.js} +5 -5
- package/src/{codex-doc2md.js → codex/doc2md.js} +4 -4
- package/src/{codex-doctor.js → codex/doctor.js} +4 -4
- package/src/{codex-harness.js → codex/harness.js} +5 -5
- package/src/{codex-hooks.js → codex/hooks.js} +20 -20
- package/src/{codex-installer.js → codex/installer.js} +2 -2
- package/src/{codex-ledger.js → codex/ledger.js} +6 -6
- package/src/{codex-panel-launcher.js → codex/panel-launcher.js} +2 -2
- package/src/{codex-panel-runner.js → codex/panel-runner.js} +2 -2
- package/src/{codex-panel-session.js → codex/panel-session.js} +1 -1
- package/src/{codex-panel-setup.js → codex/panel-setup.js} +1 -1
- package/src/{codex-panel-state.js → codex/panel-state.js} +3 -3
- package/src/{codex-panel.js → codex/panel.js} +43 -34
- package/src/{codex-parser.js → codex/parser.js} +5 -9
- package/src/{codex-reply-language.js → codex/reply-language.js} +2 -2
- package/src/{codex-route-scan.js → codex/route-scan.js} +10 -10
- package/src/{codex-rule-health.js → codex/rule-health.js} +4 -4
- package/src/commands/codex-delegate.js +6 -6
- package/src/commands/codex.js +44 -44
- package/src/commands/install.js +10 -7
- package/src/commands/korean.js +1 -1
- package/src/commands/litellm-budget.js +56 -0
- package/src/commands/saved.js +60 -5
- package/src/commands/uninstall.js +3 -3
- package/src/config.js +4 -0
- package/src/delegation-agents.js +13 -0
- package/src/first-run-note.js +16 -0
- package/src/formatters/statusline.js +725 -504
- package/src/formatters/table.js +33 -2
- package/src/glyphs.js +2 -0
- package/src/installer.js +77 -1
- package/src/model-alias.js +45 -60
- package/src/model-rules.js +25 -4
- package/src/model-share.js +136 -0
- package/src/parser.js +4 -13
- package/src/session-records.js +2 -8
- package/src/star-note.js +62 -0
- package/src/statusline-base.js +9 -3
- package/src/stdin-payload.js +1 -1
- package/src/transcript-lines.js +40 -0
- package/src/changelog.js +0 -70
- /package/src/{codex-cache.js → codex/cache.js} +0 -0
- /package/src/{codex-panel-shell.js → codex/panel-shell.js} +0 -0
package/src/commands/codex.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createArgs } from '../cli-args.js';
|
|
2
|
-
import { configureCodexHooks } from '../codex
|
|
3
|
-
import { initCodexHarness, uninitCodexHarness, codexHarnessStatus, syncCodexKoreanBlock } from '../codex
|
|
2
|
+
import { configureCodexHooks } from '../codex/installer.js';
|
|
3
|
+
import { initCodexHarness, uninitCodexHarness, codexHarnessStatus, syncCodexKoreanBlock } from '../codex/harness.js';
|
|
4
4
|
import { findProjectRoot, harnessPromote, harnessListRules, harnessRmRule, harnessPrune } from '../harness.js';
|
|
5
5
|
import { loadConfig, saveConfig } from '../config.js';
|
|
6
6
|
import { resolve } from 'node:path';
|
|
@@ -103,7 +103,7 @@ export async function run({ args, version }) {
|
|
|
103
103
|
const selectedRoot = !['harness', 'delegate', 'seed'].includes(cmd) && getArg('--project') ? resolve(getArg('--project')) : root;
|
|
104
104
|
const format = getArg('--format') || getArg('-f') || 'table';
|
|
105
105
|
if (cmd === 'cache') {
|
|
106
|
-
const { parseCodexCacheTtl } = await import('../codex
|
|
106
|
+
const { parseCodexCacheTtl } = await import('../codex/cache.js');
|
|
107
107
|
const mode = args[1] || 'status';
|
|
108
108
|
const cfg = loadConfig();
|
|
109
109
|
if (mode !== 'status') {
|
|
@@ -113,13 +113,13 @@ export async function run({ args, version }) {
|
|
|
113
113
|
}
|
|
114
114
|
const ttl = cfg.codex?.cacheTtl ?? 'auto';
|
|
115
115
|
console.log(`Codex cache timer: ${typeof ttl === 'number' ? `${ttl / 60}m (configured estimate)` : ttl}`);
|
|
116
|
-
const { createPanelReader } = await import('../codex
|
|
117
|
-
const { refreshCodexCachePolicy } = await import('../codex
|
|
118
|
-
const { codexCacheTimer } = await import('../codex
|
|
116
|
+
const { createPanelReader } = await import('../codex/panel.js');
|
|
117
|
+
const { refreshCodexCachePolicy } = await import('../codex/cache-policy.js');
|
|
118
|
+
const { codexCacheTimer } = await import('../codex/cache.js');
|
|
119
119
|
const data = await createPanelReader({ root: selectedRoot, sessionId: getArg('--session'),
|
|
120
120
|
...(hasFlag('--refresh') ? { readCachePolicy: refreshCodexCachePolicy } : {}) })();
|
|
121
121
|
if (data.session) {
|
|
122
|
-
const { terminalText } = await import('../codex
|
|
122
|
+
const { terminalText } = await import('../codex/panel.js');
|
|
123
123
|
console.log(`Session: ${terminalText(data.session.sessionId)} | ${terminalText(data.session.provider)} / ${terminalText(data.session.model)}`);
|
|
124
124
|
if (data.cachePolicy) {
|
|
125
125
|
const p = data.cachePolicy;
|
|
@@ -133,11 +133,11 @@ export async function run({ args, version }) {
|
|
|
133
133
|
}
|
|
134
134
|
if (cmd === 'capabilities' || cmd === 'doctor') {
|
|
135
135
|
if (!['table', 'json'].includes(format)) throw new Error('Diagnostics format must be table or json.');
|
|
136
|
-
const { codexDoctor, CODEX_CAPABILITIES } = await import('../codex
|
|
136
|
+
const { codexDoctor, CODEX_CAPABILITIES } = await import('../codex/doctor.js');
|
|
137
137
|
const report = cmd === 'capabilities' ? { agent: 'codex', capabilities: CODEX_CAPABILITIES }
|
|
138
138
|
: await codexDoctor({ root: selectedRoot, sessionId: getArg('--session'), days: numArg('--days', { dflt: 7, min: 0 }) });
|
|
139
139
|
if (format === 'json') { console.log(JSON.stringify(report, null, 2)); return; }
|
|
140
|
-
const { terminalText } = await import('../codex
|
|
140
|
+
const { terminalText } = await import('../codex/panel.js');
|
|
141
141
|
if (cmd === 'doctor') {
|
|
142
142
|
console.log(`Codex home: ${terminalText(report.home)}`);
|
|
143
143
|
console.log(`Config: ${report.config.error || (report.config.present ? 'readable' : 'absent (defaults)')}`);
|
|
@@ -161,10 +161,10 @@ export async function run({ args, version }) {
|
|
|
161
161
|
if (cmd === 'saved') return (await import('./saved.js')).run({ args, hasFlag, agent: 'codex' });
|
|
162
162
|
if (cmd === 'seed') return (await import('./seed.js')).run({ args, hasFlag, agent: 'codex', root });
|
|
163
163
|
if (cmd === 'route-scan') {
|
|
164
|
-
const rs = await import('../codex
|
|
164
|
+
const rs = await import('../codex/route-scan.js');
|
|
165
165
|
const days = numArg('--days', { dflt: 14, min: 1 });
|
|
166
166
|
if (args[1] === 'savings') {
|
|
167
|
-
const { refreshCodexLedger, loadCodexLedger, codexRoutingSavedTotals } = await import('../codex
|
|
167
|
+
const { refreshCodexLedger, loadCodexLedger, codexRoutingSavedTotals } = await import('../codex/ledger.js');
|
|
168
168
|
if (hasFlag('--refresh')) await refreshCodexLedger();
|
|
169
169
|
// The prompt hook runs this detached to price finished delegations.
|
|
170
170
|
if (hasFlag('--quiet')) return;
|
|
@@ -190,7 +190,7 @@ export async function run({ args, version }) {
|
|
|
190
190
|
let cache = rs.readCodexRouteScan();
|
|
191
191
|
if (hasFlag('--refresh') || !cache) {
|
|
192
192
|
// Prices live in the gateway snapshot; refresh it when stale so candidates can name a target.
|
|
193
|
-
const { readCodexPrices, refreshCodexPrices } = await import('../codex
|
|
193
|
+
const { readCodexPrices, refreshCodexPrices } = await import('../codex/cache-policy.js');
|
|
194
194
|
if (!readCodexPrices()) { try { await refreshCodexPrices(); } catch (e) { debug('codex:prices', e); } }
|
|
195
195
|
cache = await rs.runCodexRouteScan({ days });
|
|
196
196
|
}
|
|
@@ -225,8 +225,8 @@ export async function run({ args, version }) {
|
|
|
225
225
|
console.log('Run sprag install --agent codex and review/trust hooks in Codex /hooks.');
|
|
226
226
|
return;
|
|
227
227
|
}
|
|
228
|
-
const { createPanelReader, terminalText } = await import('../codex
|
|
229
|
-
const { codexIssues } = await import('../codex
|
|
228
|
+
const { createPanelReader, terminalText } = await import('../codex/panel.js');
|
|
229
|
+
const { codexIssues } = await import('../codex/brief.js');
|
|
230
230
|
const data = await createPanelReader({ root: selectedRoot, sessionId: getArg('--session'), days: numArg('--days', { dflt: 7, min: 0 }) })();
|
|
231
231
|
const issues = codexIssues(data.session);
|
|
232
232
|
if (!issues.length) console.log('No fresh Codex context or rate-limit warnings. Missing/stale logs do not prove healthy usage.');
|
|
@@ -238,8 +238,8 @@ export async function run({ args, version }) {
|
|
|
238
238
|
}
|
|
239
239
|
if (cmd === 'history' || cmd === 'last') {
|
|
240
240
|
if (!['table', 'json'].includes(format)) throw new Error('History format must be table or json.');
|
|
241
|
-
const { readCodexHistory } = await import('../codex
|
|
242
|
-
const { terminalText } = await import('../codex
|
|
241
|
+
const { readCodexHistory } = await import('../codex/brief.js');
|
|
242
|
+
const { terminalText } = await import('../codex/panel.js');
|
|
243
243
|
let events = readCodexHistory({ days: numArg('--days', { dflt: cmd === 'last' ? 1 : 7, min: 0 }),
|
|
244
244
|
sessionId: getArg('--session'), project: getArg('--project') || getArg('-p') });
|
|
245
245
|
if (cmd === 'last') events = events.slice(-1);
|
|
@@ -253,13 +253,13 @@ export async function run({ args, version }) {
|
|
|
253
253
|
return;
|
|
254
254
|
}
|
|
255
255
|
if (cmd === 'handoff') {
|
|
256
|
-
const { createPanelReader } = await import('../codex
|
|
256
|
+
const { createPanelReader } = await import('../codex/panel.js');
|
|
257
257
|
const { writeHandoff } = await import('../handoff.js');
|
|
258
258
|
const cwd = getArg('--cwd') || process.cwd();
|
|
259
259
|
const data = await createPanelReader({ root: findProjectRoot(resolve(cwd), { agent: 'codex' }), sessionId: getArg('--session'), days: numArg('--days', { dflt: 7, min: 0 }) })();
|
|
260
260
|
const result = writeHandoff({ cwd, agent: 'codex', session: data.session });
|
|
261
261
|
try {
|
|
262
|
-
const { recordCodexHistoryEvent } = await import('../codex
|
|
262
|
+
const { recordCodexHistoryEvent } = await import('../codex/brief.js');
|
|
263
263
|
recordCodexHistoryEvent({ sessionId: data.session?.sessionId, project: cwd, key: 'handoff',
|
|
264
264
|
message: `Handoff written: ${result.path}` });
|
|
265
265
|
} catch (e) { debug('codex:handoff-record', e); }
|
|
@@ -269,7 +269,7 @@ export async function run({ args, version }) {
|
|
|
269
269
|
}
|
|
270
270
|
const statusline = hasFlag('--statusline') || format === 'statusline';
|
|
271
271
|
if (statusline && (!cmd || cmd.startsWith('-'))) {
|
|
272
|
-
const { createPanelReader, formatCodexPanel } = await import('../codex
|
|
272
|
+
const { createPanelReader, formatCodexPanel } = await import('../codex/panel.js');
|
|
273
273
|
const data = await createPanelReader({ root: selectedRoot, sessionId: getArg('--session'), days: numArg('--days', { dflt: 7, min: 0 }), hasFlag, version })();
|
|
274
274
|
const columns = numArg('--columns', { dflt: process.stdout.columns || 100, min: 1, max: 10000 });
|
|
275
275
|
const text = formatCodexPanel(data, { compact: true, columns: hasFlag('--single-line') ? 100000 : columns,
|
|
@@ -281,7 +281,7 @@ export async function run({ args, version }) {
|
|
|
281
281
|
if (args[1] === 'run') {
|
|
282
282
|
const separator = args.indexOf('--');
|
|
283
283
|
const forwarded = separator < 0 ? [] : args.slice(separator + 1);
|
|
284
|
-
const { runCodexWithPanel } = await import('../codex
|
|
284
|
+
const { runCodexWithPanel } = await import('../codex/panel-runner.js');
|
|
285
285
|
let project = process.cwd();
|
|
286
286
|
for (let i = 0; i < forwarded.length; i++) {
|
|
287
287
|
if (forwarded[i] === '--') break;
|
|
@@ -294,7 +294,7 @@ export async function run({ args, version }) {
|
|
|
294
294
|
if (args[1] === 'shell') {
|
|
295
295
|
if (process.platform !== 'darwin') throw new Error('Immediate panel launch currently supports macOS zsh.');
|
|
296
296
|
if (!['install', 'remove'].includes(args[2])) throw new Error('Usage: sprag panel shell install|remove --agent codex');
|
|
297
|
-
const { installPanelShell } = await import('../codex
|
|
297
|
+
const { installPanelShell } = await import('../codex/panel-shell.js');
|
|
298
298
|
const file = installPanelShell({ remove: args[2] === 'remove' });
|
|
299
299
|
// install adds the integration by default, so a removal must be
|
|
300
300
|
// remembered or the next upgrade puts it back.
|
|
@@ -304,10 +304,10 @@ export async function run({ args, version }) {
|
|
|
304
304
|
console.log(`Shell integration ${args[2]}: ${file}. Open a new terminal to apply it.`);
|
|
305
305
|
return;
|
|
306
306
|
}
|
|
307
|
-
const { recordPanelState, panelHealth } = await import('../codex
|
|
307
|
+
const { recordPanelState, panelHealth } = await import('../codex/panel-state.js');
|
|
308
308
|
const panelRoot = getArg('--project') ? resolve(getArg('--project')) : root;
|
|
309
309
|
if (args[1] === 'doctor') {
|
|
310
|
-
const { codexPanelAutoEnabled } = await import('../codex
|
|
310
|
+
const { codexPanelAutoEnabled } = await import('../codex/installer.js');
|
|
311
311
|
const health = panelHealth(panelRoot, { sessionId: getArg('--session') });
|
|
312
312
|
console.log(`Panel hook registered: ${codexPanelAutoEnabled() ? 'yes' : 'no'}`);
|
|
313
313
|
console.log(`Project: ${panelRoot}`);
|
|
@@ -320,7 +320,7 @@ export async function run({ args, version }) {
|
|
|
320
320
|
return;
|
|
321
321
|
}
|
|
322
322
|
if (args[1] === 'auto') {
|
|
323
|
-
const { codexPanelAutoEnabled } = await import('../codex
|
|
323
|
+
const { codexPanelAutoEnabled } = await import('../codex/installer.js');
|
|
324
324
|
const mode = args[2] || 'status';
|
|
325
325
|
if (!['on', 'off', 'status'].includes(mode)) throw new Error('Usage: sprag panel auto on|off|status --agent codex');
|
|
326
326
|
if (mode === 'on' && process.platform !== 'darwin') throw new Error('Automatic panel windows currently require macOS Terminal.');
|
|
@@ -335,13 +335,13 @@ export async function run({ args, version }) {
|
|
|
335
335
|
return;
|
|
336
336
|
}
|
|
337
337
|
if (hasFlag('--open')) {
|
|
338
|
-
const { openCodexPanel } = await import('../codex
|
|
338
|
+
const { openCodexPanel } = await import('../codex/panel-launcher.js');
|
|
339
339
|
console.log(`Codex panel: ${openCodexPanel(getArg('--project') || root, { sessionId: getArg('--session') })}`);
|
|
340
340
|
return;
|
|
341
341
|
}
|
|
342
|
-
const { createPanelReader, runCodexPanel } = await import('../codex
|
|
343
|
-
const { createCodexCachePolicyReader, refreshCodexCachePolicy } = await import('../codex
|
|
344
|
-
const { createCodexBudgetReader, codexBudgetProvider, fetchCodexBudget } = await import('../codex
|
|
342
|
+
const { createPanelReader, runCodexPanel } = await import('../codex/panel.js');
|
|
343
|
+
const { createCodexCachePolicyReader, refreshCodexCachePolicy } = await import('../codex/cache-policy.js');
|
|
344
|
+
const { createCodexBudgetReader, codexBudgetProvider, fetchCodexBudget } = await import('../codex/budget.js');
|
|
345
345
|
const readBudget = hasFlag('--once') ? (() => {
|
|
346
346
|
let snapshot = null;
|
|
347
347
|
return { read: () => snapshot, set: (value) => { snapshot = value; } };
|
|
@@ -350,7 +350,7 @@ export async function run({ args, version }) {
|
|
|
350
350
|
const provider = codexBudgetProvider();
|
|
351
351
|
if (provider) { try { readBudget.set(await fetchCodexBudget(provider)); } catch (e) { debug('codex:budget', e); } }
|
|
352
352
|
}
|
|
353
|
-
const { createInlinePanelResizer } = await import('../codex
|
|
353
|
+
const { createInlinePanelResizer } = await import('../codex/panel-runner.js');
|
|
354
354
|
const rawInterval = getArg('--interval');
|
|
355
355
|
const interval = rawInterval === undefined ? 2 : Number(rawInterval);
|
|
356
356
|
if (!Number.isFinite(interval) || interval < 0.5 || interval > 60) throw new Error('--interval must be between 0.5 and 60 seconds');
|
|
@@ -362,7 +362,7 @@ export async function run({ args, version }) {
|
|
|
362
362
|
read: createPanelReader({ root: getArg('--project') ? resolve(getArg('--project')) : root,
|
|
363
363
|
sessionId: getArg('--session'), sessionFile: getArg('--session-file'), days: numArg('--days', { dflt: 7, min: 0 }), readBudget: readBudget.read,
|
|
364
364
|
version, refreshUpdate: !hasFlag('--once'), readCachePolicy: hasFlag('--once') ? async (session) => {
|
|
365
|
-
const { readCodexCachePolicy } = await import('../codex
|
|
365
|
+
const { readCodexCachePolicy } = await import('../codex/cache-policy.js');
|
|
366
366
|
try { return readCodexCachePolicy(session) || await refreshCodexCachePolicy(session); }
|
|
367
367
|
catch (e) { debug('codex:cache-policy', e); return null; }
|
|
368
368
|
} : createCodexCachePolicyReader(), hasFlag }),
|
|
@@ -378,16 +378,16 @@ export async function run({ args, version }) {
|
|
|
378
378
|
}
|
|
379
379
|
if (cmd === 'codex-hook') {
|
|
380
380
|
const { readStdinJson } = await import('../stdin-payload.js');
|
|
381
|
-
const { codexHookOutput } = await import('../codex
|
|
381
|
+
const { codexHookOutput } = await import('../codex/hooks.js');
|
|
382
382
|
try {
|
|
383
383
|
const payload = readStdinJson();
|
|
384
|
-
const { bindPanelSession } = await import('../codex
|
|
384
|
+
const { bindPanelSession } = await import('../codex/panel-session.js');
|
|
385
385
|
bindPanelSession(getArg('--event'), payload);
|
|
386
386
|
if (getArg('--event') === 'panel-start') {
|
|
387
387
|
if (process.env.SPRAG_CODEX_PANEL === '1') return;
|
|
388
388
|
if (!payload?.cwd || typeof payload.session_id !== 'string' || !payload.session_id.trim() || !['startup', 'resume'].includes(payload.source)) return;
|
|
389
|
-
const { openCodexPanel } = await import('../codex
|
|
390
|
-
const { recordPanelState } = await import('../codex
|
|
389
|
+
const { openCodexPanel } = await import('../codex/panel-launcher.js');
|
|
390
|
+
const { recordPanelState } = await import('../codex/panel-state.js');
|
|
391
391
|
const panelRoot = findProjectRoot(payload.cwd, { agent: 'codex' });
|
|
392
392
|
recordPanelState(panelRoot, 'hook', { status: 'started', sessionId: payload.session_id });
|
|
393
393
|
try {
|
|
@@ -406,7 +406,7 @@ export async function run({ args, version }) {
|
|
|
406
406
|
}
|
|
407
407
|
if (cmd === 'install') {
|
|
408
408
|
const cfg = loadConfig();
|
|
409
|
-
const { setupCodexPanel, describePanelSetup } = await import('../codex
|
|
409
|
+
const { setupCodexPanel, describePanelSetup } = await import('../codex/panel-setup.js');
|
|
410
410
|
const panel = setupCodexPanel({ config: cfg || {}, noPanel: hasFlag('--no-panel') });
|
|
411
411
|
const panelAuto = panel.panelAuto;
|
|
412
412
|
const hook = configureCodexHooks({ panelAuto });
|
|
@@ -432,7 +432,7 @@ export async function run({ args, version }) {
|
|
|
432
432
|
console.log('Existing config.toml, Claude Code settings, and ratchet rules are preserved.');
|
|
433
433
|
try {
|
|
434
434
|
const { codexModelName, codexProviderName } = await import('../agent.js');
|
|
435
|
-
const { autoCodexSharedTargets } = await import('../codex
|
|
435
|
+
const { autoCodexSharedTargets } = await import('../codex/delegation.js');
|
|
436
436
|
const parent = codexModelName(), provider = codexProviderName();
|
|
437
437
|
const auto = parent ? autoCodexSharedTargets({ model: parent, provider }) : { targets: [], reason: 'Codex config names no default model' };
|
|
438
438
|
console.log(auto.targets.length
|
|
@@ -447,7 +447,7 @@ export async function run({ args, version }) {
|
|
|
447
447
|
if (hasFlag('--purge')) throw new Error('Codex uninstall keeps shared Sprag state; --purge is not supported.');
|
|
448
448
|
const hook = configureCodexHooks({ remove: true });
|
|
449
449
|
const h = uninitCodexHarness({ root, scope: 'global' });
|
|
450
|
-
const { installPanelShell } = await import('../codex
|
|
450
|
+
const { installPanelShell } = await import('../codex/panel-shell.js');
|
|
451
451
|
const shellFile = installPanelShell({ remove: true });
|
|
452
452
|
console.log(`Codex hooks: ${hook.file} (${hook.action})`);
|
|
453
453
|
console.log(`Codex harness: ${h.file} (${h.removed ? 'removed' : 'absent'})`);
|
|
@@ -481,8 +481,8 @@ export async function run({ args, version }) {
|
|
|
481
481
|
if (!rule) throw new Error('Provide rule text or a ratchet candidate number (see ratchet? in sprag --statusline --agent codex).');
|
|
482
482
|
if (/^R\d+$/i.test(rule)) throw new Error(`Approve route candidates with: sprag delegate rules add ${rule.toUpperCase()} --global|--project --agent codex`);
|
|
483
483
|
if (/^\d+$/.test(rule)) {
|
|
484
|
-
const { createPanelReader } = await import('../codex
|
|
485
|
-
const { codexCandidateRule } = await import('../codex
|
|
484
|
+
const { createPanelReader } = await import('../codex/panel.js');
|
|
485
|
+
const { codexCandidateRule } = await import('../codex/harness.js');
|
|
486
486
|
const { userLanguage } = await import('../config.js');
|
|
487
487
|
const data = await createPanelReader({ root, sessionId: getArg('--session') })();
|
|
488
488
|
const cand = (data.ratchet || []).find((c) => c.id === Number(rule));
|
|
@@ -543,7 +543,7 @@ export async function run({ args, version }) {
|
|
|
543
543
|
if (cmd === 'doc2md' && (!args[1] || args[1] === 'status')) {
|
|
544
544
|
const { createRequire } = await import('node:module');
|
|
545
545
|
const doc2md = createRequire(import.meta.url)('../doc2md.cjs');
|
|
546
|
-
const { codexHookStatus } = await import('../codex
|
|
546
|
+
const { codexHookStatus } = await import('../codex/installer.js');
|
|
547
547
|
console.log(`Codex document conversion: ${loadConfig()?.codex?.doc2md === false || process.env.CTS_NO_DOC2MD === '1' ? 'off' : 'on'}`);
|
|
548
548
|
console.log(`Formats: ${doc2md.TARGET_EXTENSIONS.join(' ')}`);
|
|
549
549
|
console.log(`Converter: ${doc2md.findInterpreter() || 'missing; run sprag doc2md install-converter --agent codex'}`);
|
|
@@ -558,7 +558,7 @@ export async function run({ args, version }) {
|
|
|
558
558
|
return;
|
|
559
559
|
}
|
|
560
560
|
if (cmd === 'doc2md' && args[1] === 'savings') {
|
|
561
|
-
const { codexDocumentTotals } = await import('../codex
|
|
561
|
+
const { codexDocumentTotals } = await import('../codex/doc2md-ledger.js');
|
|
562
562
|
const totals = codexDocumentTotals();
|
|
563
563
|
if (format === 'json') console.log(JSON.stringify({ agent: 'codex', ...totals }, null, 2));
|
|
564
564
|
else {
|
|
@@ -569,11 +569,11 @@ export async function run({ args, version }) {
|
|
|
569
569
|
return;
|
|
570
570
|
}
|
|
571
571
|
if (cmd === 'doc2md' && args[1] && !args[1].startsWith('-') && args[1] !== 'install-converter') {
|
|
572
|
-
const { convertCodexDocument } = await import('../codex
|
|
572
|
+
const { convertCodexDocument } = await import('../codex/doc2md.js');
|
|
573
573
|
const sessionId = getArg('--session') || process.env.CODEX_THREAD_ID;
|
|
574
574
|
let identity = {};
|
|
575
575
|
if (sessionId) {
|
|
576
|
-
const { createPanelReader } = await import('../codex
|
|
576
|
+
const { createPanelReader } = await import('../codex/panel.js');
|
|
577
577
|
const { session } = await createPanelReader({ root, sessionId })();
|
|
578
578
|
if (session) identity = { model: session.model, provider: session.provider };
|
|
579
579
|
}
|
|
@@ -617,7 +617,7 @@ export async function run({ args, version }) {
|
|
|
617
617
|
const days = numArg('--hours', { min: 0 }) !== undefined ? numArg('--hours', { min: 0 }) / 24
|
|
618
618
|
: (numArg('--days', { min: 0 }) ?? numArg('-d', { min: 0 }) ?? 30);
|
|
619
619
|
if (!['table', 'json', 'csv'].includes(format)) throw new Error('Codex report format must be table, json, or csv.');
|
|
620
|
-
const { parseAllCodexSessions } = await import('../codex
|
|
620
|
+
const { parseAllCodexSessions } = await import('../codex/parser.js');
|
|
621
621
|
const { summary, dailyTrend } = await import('../stats.js');
|
|
622
622
|
const sessions = (await parseAllCodexSessions({ days, projectFilter: getArg('--project') || getArg('-p') }))
|
|
623
623
|
.filter((session) => !getArg('--session') || session.sessionId === getArg('--session'));
|
package/src/commands/install.js
CHANGED
|
@@ -102,6 +102,9 @@ export async function run({ hasFlag }) {
|
|
|
102
102
|
console.log(lang === 'ko'
|
|
103
103
|
? ` statusline: 기존 statusline이 이미 설정되어 있습니다: ${s.existingCommand}`
|
|
104
104
|
: ` statusline: an existing statusline is already configured: ${s.existingCommand}`);
|
|
105
|
+
// A wrapper around our own CLI is worth naming here too: the non-tty line
|
|
106
|
+
// below prints the same sentence, so both paths say what the entry is.
|
|
107
|
+
if (s.wrapper) console.log(` ${s.reason}`);
|
|
105
108
|
const pick = await choose(lang === 'ko'
|
|
106
109
|
? ' statusline을 어떻게 표시할까요?'
|
|
107
110
|
: ' How should the statusline be shown?', [
|
|
@@ -212,8 +215,8 @@ export async function run({ hasFlag }) {
|
|
|
212
215
|
console.log(lang === 'ko' ? ` harness: 작성 ${p}` : ` harness: wrote ${p}`);
|
|
213
216
|
}
|
|
214
217
|
console.log(lang === 'ko'
|
|
215
|
-
? ' harness: 5원칙을 ~/.claude/CLAUDE.md 에
|
|
216
|
-
: ' harness: 5 principles installed in ~/.claude/CLAUDE.md
|
|
218
|
+
? ' harness: 5원칙을 ~/.claude/CLAUDE.md 에 설정했습니다. 점수가 5/5에 못 미치거나 경고가 있을 때 statusline에 🅷 가 표시됩니다.'
|
|
219
|
+
: ' harness: 5 principles installed in ~/.claude/CLAUDE.md. The statusline shows 🅷 when the score drops below 5/5 or a warning is due.');
|
|
217
220
|
console.log(lang === 'ko'
|
|
218
221
|
? ' 되돌리려면: sprag harness uninit --global'
|
|
219
222
|
: ' to undo: sprag harness uninit --global');
|
|
@@ -436,15 +439,15 @@ export async function run({ hasFlag }) {
|
|
|
436
439
|
: ' codex: Codex is installed here. Set up its hooks and harness too?', { defaultValue: true });
|
|
437
440
|
}
|
|
438
441
|
if (proceed) {
|
|
439
|
-
const { configureCodexHooks } = await import('../codex
|
|
440
|
-
const { initCodexHarness } = await import('../codex
|
|
442
|
+
const { configureCodexHooks } = await import('../codex/installer.js');
|
|
443
|
+
const { initCodexHarness } = await import('../codex/harness.js');
|
|
441
444
|
const { loadConfig } = await import('../config.js');
|
|
442
|
-
const { setupCodexPanel, describePanelSetup } = await import('../codex
|
|
445
|
+
const { setupCodexPanel, describePanelSetup } = await import('../codex/panel-setup.js');
|
|
443
446
|
const config = loadConfig() || {};
|
|
444
447
|
// A manual install asks before touching the system package manager.
|
|
445
448
|
let env = process.env;
|
|
446
449
|
if (interactive && process.platform === 'darwin') {
|
|
447
|
-
const { tmuxAvailable } = await import('../codex
|
|
450
|
+
const { tmuxAvailable } = await import('../codex/panel-setup.js');
|
|
448
451
|
if (!tmuxAvailable() && !await confirm(lang === 'ko'
|
|
449
452
|
? ' codex: tmux 가 없습니다. Homebrew 로 설치해 같은 창에 패널을 붙일까요?'
|
|
450
453
|
: ' codex: tmux is missing. Install it with Homebrew so the panel shares the Codex window?', { defaultValue: true })) {
|
|
@@ -466,7 +469,7 @@ export async function run({ hasFlag }) {
|
|
|
466
469
|
const h = initCodexHarness({ scope: 'global' });
|
|
467
470
|
console.log(` codex: harness ${h.file}`);
|
|
468
471
|
try {
|
|
469
|
-
const { syncCodexKoreanBlock } = await import('../codex
|
|
472
|
+
const { syncCodexKoreanBlock } = await import('../codex/harness.js');
|
|
470
473
|
const k = syncCodexKoreanBlock({ cfg: config });
|
|
471
474
|
console.log(` codex: korean-style ${k.action} (${k.file})`);
|
|
472
475
|
} catch (e) { debug('install:codex-korean', e); }
|
package/src/commands/korean.js
CHANGED
|
@@ -224,7 +224,7 @@ export async function run({ args, hasFlag }) {
|
|
|
224
224
|
const enabled = sub === 'on';
|
|
225
225
|
ks.setKoreanStyleEnabled(enabled);
|
|
226
226
|
// Codex reads the guide from its global AGENTS.md; keep that block in step.
|
|
227
|
-
try { (await import('../codex
|
|
227
|
+
try { (await import('../codex/harness.js')).syncCodexKoreanBlock({ cfg: loadConfig() }); } catch (e) { debug('korean:codex-sync', e); }
|
|
228
228
|
const {
|
|
229
229
|
installKoreanLintHook, removeKoreanLintHook,
|
|
230
230
|
installReplyLanguageHook, removeReplyLanguageHook,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subcommand: litellm-budget · LiteLLM 게이트웨이 키의 max_budget/spend 조회.
|
|
3
|
+
* sprag litellm-budget # 캐시된 예산을 게이지·금액으로 출력
|
|
4
|
+
* sprag litellm-budget --json # 캐시 원본 JSON 출력
|
|
5
|
+
* sprag litellm-budget --refresh # 지금 프록시에 물어봄 (detached 자식이 사용)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { debug } from '../debug.js';
|
|
9
|
+
|
|
10
|
+
export async function run({ hasFlag }) {
|
|
11
|
+
const { gatewayBase, readBudgetState, refreshBudgetState, formatBudgetReport, resolveKey } =
|
|
12
|
+
await import('../litellm-budget.js');
|
|
13
|
+
const quiet = hasFlag('--quiet');
|
|
14
|
+
if (hasFlag('--refresh')) {
|
|
15
|
+
try {
|
|
16
|
+
// apiKeyHelper 가 토큰 수명(TTL)을 소유합니다. 헬퍼를 한 번만 불러 그
|
|
17
|
+
// 키를 예산 갱신과 게이트웨이 모델맵 갱신 양쪽에 재사용합니다. 갱신
|
|
18
|
+
// 경로마다 resolveKey() 를 다시 부르면 사본을 만드는 셈이 되고, 헬퍼
|
|
19
|
+
// 호출 횟수도 그만큼 늘어납니다.
|
|
20
|
+
const key = resolveKey();
|
|
21
|
+
let next = null;
|
|
22
|
+
if (key) {
|
|
23
|
+
next = await refreshBudgetState(process.env, fetch, key);
|
|
24
|
+
const { maybeRefreshGatewayModelMap } = await import('../litellm-models.js');
|
|
25
|
+
await maybeRefreshGatewayModelMap({ base: gatewayBase(), key });
|
|
26
|
+
}
|
|
27
|
+
if (!quiet) console.log(JSON.stringify(next, null, 2));
|
|
28
|
+
} catch (e) {
|
|
29
|
+
debug('litellm-budget:refresh', e);
|
|
30
|
+
if (!quiet) console.error(`litellm-budget refresh failed: ${e.message}`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// 읽기 경로는 캐시만 보므로 키를 요구하지 않습니다 (apiKeyHelper 환경 배려).
|
|
36
|
+
const base = gatewayBase();
|
|
37
|
+
if (!base) {
|
|
38
|
+
console.log('게이트웨이가 감지되지 않았습니다 (ANTHROPIC_BASE_URL 필요).');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const state = readBudgetState();
|
|
42
|
+
if (hasFlag('--json')) {
|
|
43
|
+
console.log(JSON.stringify(state, null, 2));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (state.base !== base || state.maxBudget === undefined) {
|
|
47
|
+
console.log('예산 캐시가 비어 있습니다. `sprag litellm-budget --refresh` 로 먼저 조회하십시오.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Same resolver the statusline uses: this report draws a gauge too, and a
|
|
51
|
+
// glyph the terminal's font lacks garbles here exactly as it does there.
|
|
52
|
+
const { resolveLabelMode } = await import('../statusline-mode.js');
|
|
53
|
+
const { statuslineDefaults } = await import('../config.js');
|
|
54
|
+
const { mode: labelMode } = resolveLabelMode({ hasFlag, cfg: statuslineDefaults() });
|
|
55
|
+
for (const line of formatBudgetReport(state, new Date(), labelMode)) console.log(line);
|
|
56
|
+
}
|
package/src/commands/saved.js
CHANGED
|
@@ -26,18 +26,19 @@ const SCOPE_SETS = {
|
|
|
26
26
|
async function currentTotals(scope, dir) {
|
|
27
27
|
if (scope === 'routing') {
|
|
28
28
|
const { delegationSavedTotals } = await import('../savings-ledger.js');
|
|
29
|
-
|
|
29
|
+
const t = delegationSavedTotals(Date.now(), dir);
|
|
30
|
+
return { usd: t.total, spent: t.spent, spentMissing: t.spentMissing };
|
|
30
31
|
}
|
|
31
32
|
if (scope === 'doc2md') {
|
|
32
33
|
const { doc2mdSavedTotals } = await import('../doc2md-ledger.cjs');
|
|
33
34
|
return { usd: doc2mdSavedTotals(dir).total };
|
|
34
35
|
}
|
|
35
36
|
if (scope === 'codex-routing') {
|
|
36
|
-
const { codexRoutingSavedTotals } = await import('../codex
|
|
37
|
+
const { codexRoutingSavedTotals } = await import('../codex/ledger.js');
|
|
37
38
|
const totals = codexRoutingSavedTotals({ dir });
|
|
38
39
|
return { usd: totals.priced ? totals.total : null, unpriced: totals.runs - totals.priced };
|
|
39
40
|
}
|
|
40
|
-
const { codexDocumentTotals } = await import('../codex
|
|
41
|
+
const { codexDocumentTotals } = await import('../codex/doc2md-ledger.js');
|
|
41
42
|
const totals = codexDocumentTotals({ dir });
|
|
42
43
|
return { docs: totals.docs, usd: totals.savings.priced ? totals.savings.total : null, unpriced: totals.savings.unpriced };
|
|
43
44
|
}
|
|
@@ -67,6 +68,54 @@ async function refreshRuleSavings(ko, rescan) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
const FAMILY_NAMES = { opus: 'Opus', sonnet: 'Sonnet', haiku: 'Haiku', fable: 'Fable', mythos: 'Mythos' };
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The model-share table for the routing counter's period. Best-effort like the
|
|
75
|
+
* statusline: a failure to read transcripts must not break the totals above it.
|
|
76
|
+
*/
|
|
77
|
+
async function printModelShare({ ko, dir, money }) {
|
|
78
|
+
const { loadModelShare, roundShares } = await import('../model-share.js');
|
|
79
|
+
let share;
|
|
80
|
+
try {
|
|
81
|
+
share = await loadModelShare({ dir, env: process.env });
|
|
82
|
+
} catch (e) {
|
|
83
|
+
debug('saved:model-share', e);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let title = ko ? '모델별 사용 비중 (같은 기간, 비용 기준)' : 'Model share (same period, by cost)';
|
|
87
|
+
if (share.sinceKind === 'month') title += ko ? ' · 초기화 기록이 없어 이번 달 기준' : ' · no reset on record, this month';
|
|
88
|
+
else if (share.sinceKind === 'capped') title += ko ? ' · 최근 90일' : ' · last 90 days';
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(title);
|
|
91
|
+
if (!(share.totalUsd > 0)) {
|
|
92
|
+
console.log(ko ? '이 기간에 집계할 사용 기록이 없습니다.' : 'No usage to count in this period.');
|
|
93
|
+
} else {
|
|
94
|
+
const names = share.families.map((f) => FAMILY_NAMES[f.family] || f.family);
|
|
95
|
+
const nameW = Math.max(...names.map((n) => n.length));
|
|
96
|
+
const pcts = share.families.map((f) => f.pct.toFixed(1));
|
|
97
|
+
const pctW = Math.max(...pcts.map((p) => p.length));
|
|
98
|
+
share.families.forEach((f, i) => {
|
|
99
|
+
const filled = roundShares([f.usd, share.totalUsd - f.usd], 20)[0];
|
|
100
|
+
const bar = '█'.repeat(filled) + '░'.repeat(20 - filled);
|
|
101
|
+
console.log(` ${names[i].padEnd(nameW)} ${bar} ${pcts[i].padStart(pctW)}% ${money(f.usd)}`);
|
|
102
|
+
});
|
|
103
|
+
if (share.subagent.usd > 0) {
|
|
104
|
+
const subs = share.subagent.families;
|
|
105
|
+
const subPct = Math.round(share.subagent.pct);
|
|
106
|
+
// A share that rounds to 0 still spent money; "0%" would read as unused.
|
|
107
|
+
const pctText = (p) => (p > 0 && Math.round(p) === 0 ? '<1' : String(Math.round(p)));
|
|
108
|
+
const detail = subs
|
|
109
|
+
.map((f) => `${f.family} ${pctText(f.pct)}%`)
|
|
110
|
+
.join(', ');
|
|
111
|
+
console.log(` ${ko ? '서브에이전트 몫' : 'Subagents'}: ${subPct}% (${detail})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (share.unpriced > 0) {
|
|
115
|
+
console.log(ko ? `단가가 없어 제외한 부분 ${share.unpriced}건` : `${share.unpriced} part(s) left out: no price`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
70
119
|
export async function run({ args, hasFlag, agent = 'claude', dir: dirArg, rescan }) {
|
|
71
120
|
const { userLanguage } = await import('../config.js');
|
|
72
121
|
const { userDataDir } = await import('../paths.js');
|
|
@@ -79,8 +128,13 @@ export async function run({ args, hasFlag, agent = 'claude', dir: dirArg, rescan
|
|
|
79
128
|
const money = (v) => (v < 0 ? `-$${(-v).toFixed(2)}` : `$${v.toFixed(2)}`);
|
|
80
129
|
const fmtTotal = (t) => {
|
|
81
130
|
const value = Number.isFinite(t.usd) ? money(t.usd) : 'n/a';
|
|
82
|
-
|
|
83
|
-
|
|
131
|
+
if (t.docs !== undefined) return `${ko ? `${t.docs}건` : `${t.docs} doc(s)`}, ${value} (${ko ? 'XML 대비 추정' : 'XML estimate'}; ${t.unpriced} unpriced)`;
|
|
132
|
+
// What the delegated runs cost on the models they ran on. The statusline
|
|
133
|
+
// no longer draws it beside the saving, so this is where it is read.
|
|
134
|
+
const spent = Number(t.spent) > 0
|
|
135
|
+
? ` · ${ko ? '위임 실행 비용' : 'spent on delegated runs'} ${money(t.spent)}${t.spentMissing > 0 ? ` +${t.spentMissing} unpriced` : ''}`
|
|
136
|
+
: '';
|
|
137
|
+
return `${value}${t.unpriced ? ` +${t.unpriced} unpriced` : ''}${spent}`;
|
|
84
138
|
};
|
|
85
139
|
const fmtTime = (ms) => new Date(ms).toLocaleString(ko ? 'ko-KR' : 'en-US');
|
|
86
140
|
const label = (scope) => ({
|
|
@@ -101,6 +155,7 @@ export async function run({ args, hasFlag, agent = 'claude', dir: dirArg, rescan
|
|
|
101
155
|
: (ko ? `${fmtTime(marks[s])} 이후` : `since ${fmtTime(marks[s])}`);
|
|
102
156
|
console.log(`${label(s)}: ${fmtTotal(t)} (${since})`);
|
|
103
157
|
}
|
|
158
|
+
if (agent !== 'codex') await printModelShare({ ko, dir, money });
|
|
104
159
|
return;
|
|
105
160
|
}
|
|
106
161
|
|
|
@@ -43,8 +43,8 @@ export async function run({ hasFlag, args = [] }) {
|
|
|
43
43
|
// undo that as well; otherwise Codex keeps calling a command that may no
|
|
44
44
|
// longer exist. Only our own entries go, exactly as `uninstall --agent codex`.
|
|
45
45
|
try {
|
|
46
|
-
const { codexHookStatus, configureCodexHooks } = await import('../codex
|
|
47
|
-
const { codexHarnessStatus, uninitCodexHarness } = await import('../codex
|
|
46
|
+
const { codexHookStatus, configureCodexHooks } = await import('../codex/installer.js');
|
|
47
|
+
const { codexHarnessStatus, uninitCodexHarness } = await import('../codex/harness.js');
|
|
48
48
|
const hooked = codexHookStatus().some((h) => h.registered);
|
|
49
49
|
let harness = false;
|
|
50
50
|
try { harness = codexHarnessStatus({ scope: 'global' }).configured > 0; } catch { /* no AGENTS.md */ }
|
|
@@ -56,7 +56,7 @@ export async function run({ hasFlag, args = [] }) {
|
|
|
56
56
|
: ` Codex: hooks ${hook.action} (${hook.file}), harness ${h.removed ? 'removed' : 'absent'}`);
|
|
57
57
|
if (process.platform === 'darwin') {
|
|
58
58
|
try {
|
|
59
|
-
const { installPanelShell } = await import('../codex
|
|
59
|
+
const { installPanelShell } = await import('../codex/panel-shell.js');
|
|
60
60
|
installPanelShell({ remove: true });
|
|
61
61
|
} catch (e) { /* a malformed or foreign block stays for the user to repair */ }
|
|
62
62
|
}
|
package/src/config.js
CHANGED
|
@@ -56,6 +56,9 @@ const KEYWORDS = {
|
|
|
56
56
|
'no-timer': { key: 'timer', value: false },
|
|
57
57
|
color: { key: 'color', value: true },
|
|
58
58
|
'no-color': { key: 'color', value: false },
|
|
59
|
+
// Model-share bar and letters beside the routing chip; on unless switched off.
|
|
60
|
+
share: { key: 'modelShare', value: true },
|
|
61
|
+
'no-share': { key: 'modelShare', value: false },
|
|
59
62
|
};
|
|
60
63
|
|
|
61
64
|
// Output language for `last`/`history`/advice. Stored at top level
|
|
@@ -195,6 +198,7 @@ export function statuslineDefaults() {
|
|
|
195
198
|
verbose: s.verbose !== false,
|
|
196
199
|
timer: s.timer !== false,
|
|
197
200
|
color: s.color !== false,
|
|
201
|
+
modelShare: s.modelShare !== false,
|
|
198
202
|
windowHours,
|
|
199
203
|
windowLabel: formatWindow(windowHours),
|
|
200
204
|
// 'auto' | '5m' | '1h'. Auto lets the measured split decide and falls back
|
package/src/delegation-agents.js
CHANGED
|
@@ -22,10 +22,19 @@ const MARK_LINE = `# managed by sprag (${OWNER_MARK}); edits are overwritten`;
|
|
|
22
22
|
|
|
23
23
|
// Models are aliases on purpose: behind a gateway they resolve through
|
|
24
24
|
// ANTHROPIC_DEFAULT_*_MODEL, and a full model id would bypass that mapping.
|
|
25
|
+
//
|
|
26
|
+
// Effort is a separate axis from the tier and is set by how the work gets
|
|
27
|
+
// verified, not by how hard it is. Where the check is command output or a file
|
|
28
|
+
// comparison (lookups, test runs, mechanical conversion) extra thinking buys
|
|
29
|
+
// nothing, so those agents run at low. The worker implements a design the
|
|
30
|
+
// caller already fixed, against tests, so it takes the default for well-specified
|
|
31
|
+
// work, medium. The main session's effort is never touched here, and
|
|
32
|
+
// CLAUDE_CODE_EFFORT_LEVEL must stay unset: it overrides every agent's own value.
|
|
25
33
|
export const DEFINITIONS = [
|
|
26
34
|
{
|
|
27
35
|
name: 'haiku-explore',
|
|
28
36
|
model: 'haiku',
|
|
37
|
+
effort: 'low',
|
|
29
38
|
tools: 'Read, Grep, Glob, Bash',
|
|
30
39
|
role: 'Your role: read-only exploration, lookups, status checks, and questions about pasted logs or text.',
|
|
31
40
|
description: 'Cheap read-only exploration, lookup, status checks and summaries for delegated tasks. It has no MCP tools.',
|
|
@@ -33,6 +42,7 @@ export const DEFINITIONS = [
|
|
|
33
42
|
{
|
|
34
43
|
name: 'haiku-runner',
|
|
35
44
|
model: 'haiku',
|
|
45
|
+
effort: 'low',
|
|
36
46
|
tools: 'Bash, Read, Grep, Glob',
|
|
37
47
|
role: 'Your role: run the build, test, or git commands you are given and report the result.',
|
|
38
48
|
description: 'Cheap runner for build, test and git commands in delegated tasks. It has no MCP tools.',
|
|
@@ -40,6 +50,7 @@ export const DEFINITIONS = [
|
|
|
40
50
|
{
|
|
41
51
|
name: 'haiku-translate',
|
|
42
52
|
model: 'haiku',
|
|
53
|
+
effort: 'low',
|
|
43
54
|
tools: 'Read, Grep, Glob, Edit, Write',
|
|
44
55
|
role: 'Your role: batch translation and mechanical text conversion of the files or text you are given.',
|
|
45
56
|
description: 'Cheap batch translation and structured text conversion for delegated tasks. It has no MCP tools.',
|
|
@@ -47,6 +58,7 @@ export const DEFINITIONS = [
|
|
|
47
58
|
{
|
|
48
59
|
name: 'sonnet-worker',
|
|
49
60
|
model: 'sonnet',
|
|
61
|
+
effort: 'medium',
|
|
50
62
|
tools: 'Read, Grep, Glob, Bash, Edit, Write, NotebookEdit',
|
|
51
63
|
role: 'Your role: multi-step work and the implementation half of a design the caller already fixed.',
|
|
52
64
|
description: 'Mid-tier worker for multi-step tasks and implementation handed over with a fixed design. It has no MCP tools.',
|
|
@@ -60,6 +72,7 @@ function render(def) {
|
|
|
60
72
|
`description: ${def.description}`,
|
|
61
73
|
`tools: ${def.tools}`,
|
|
62
74
|
`model: ${def.model}`,
|
|
75
|
+
`effort: ${def.effort}`,
|
|
63
76
|
MARK_LINE,
|
|
64
77
|
'---',
|
|
65
78
|
def.role,
|
package/src/first-run-note.js
CHANGED
|
@@ -34,6 +34,22 @@ function markShown(key, now) {
|
|
|
34
34
|
writeFileSync(firstRunStatePath(), JSON.stringify(state) + '\n');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** When `key` was marked shown (epoch ms), or null. For notes that stay
|
|
38
|
+
* visible for a window after the first time instead of printing once. */
|
|
39
|
+
export function noteShownAt(key) {
|
|
40
|
+
const at = Number(load()[key]);
|
|
41
|
+
return Number.isFinite(at) && at > 0 ? at : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Persist `key` as shown at `now`. Best-effort, like `shouldShowOnce`. */
|
|
45
|
+
export function markNoteShown(key, now = Date.now()) {
|
|
46
|
+
try {
|
|
47
|
+
markShown(key, now);
|
|
48
|
+
} catch {
|
|
49
|
+
/* state dir unwritable — the note may show again, never a failure */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
/** True only the first time this key is asked about. Best-effort — a
|
|
38
54
|
* read-only state dir just means the note repeats, never that we crash. */
|
|
39
55
|
export function shouldShowOnce(key, { now = Date.now() } = {}) {
|