sprag-cli 3.66.0 → 3.68.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 +29 -62
- package/README.md +26 -61
- package/bin/cli.js +130 -62
- package/docs/CODEX.ko.md +32 -0
- package/docs/CODEX.md +34 -0
- package/package.json +11 -4
- package/presets/ratchet-rules.json +7 -0
- package/src/agents.js +33 -11
- 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/cowork.js +107 -0
- package/src/commands/delegate.js +43 -0
- package/src/commands/install.js +21 -7
- package/src/commands/korean.js +1 -1
- package/src/commands/litellm-budget.js +56 -0
- package/src/commands/route-scan.js +6 -0
- package/src/commands/saved.js +60 -5
- package/src/commands/uninstall.js +7 -3
- package/src/config.js +4 -0
- package/src/cowork-sync.js +361 -0
- package/src/delegation-agents.js +133 -0
- package/src/formatters/statusline.js +713 -504
- package/src/glyphs.js +2 -0
- package/src/harness.js +1 -1
- package/src/implementation-delegation.js +5 -2
- package/src/installer.js +77 -1
- package/src/model-alias.js +45 -60
- package/src/model-rules.js +12 -5
- package/src/model-share.js +136 -0
- package/src/parser.js +4 -13
- package/src/route-inject.js +8 -3
- package/src/session-records.js +2 -8
- package/src/statusline-base.js +9 -3
- package/src/stdin-payload.js +1 -1
- package/src/transcript-lines.js +40 -0
- /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'));
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subcommand: cowork : keep the Claude app's Cowork tab on the same ratchet rules.
|
|
3
|
+
*
|
|
4
|
+
* sprag cowork status
|
|
5
|
+
* sprag cowork sync discover connected folders and write the copies now
|
|
6
|
+
* sprag cowork on | off turn the sync on (and sync) or off (and remove the copies)
|
|
7
|
+
* sprag cowork exclude <dir> never write into <dir> (removes its copy)
|
|
8
|
+
* sprag cowork include <dir> undo exclude (and sync)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { resolve } from 'node:path';
|
|
12
|
+
import { realpathSync } from 'node:fs';
|
|
13
|
+
|
|
14
|
+
const REASONS = {
|
|
15
|
+
ko: {
|
|
16
|
+
'not-absolute': '절대 경로가 아님', missing: '폴더가 없음', 'not-a-directory': '디렉터리가 아님',
|
|
17
|
+
'home-or-ancestor': '홈 디렉터리 또는 그 상위 경로', 'inside-claude-dir': '~/.claude 안',
|
|
18
|
+
'inside-library': '~/Library 안', 'inside-sprag-data': 'sprag 데이터 폴더 안', excluded: '제외 목록에 있음',
|
|
19
|
+
'not-owned': '같은 이름의 사용자 파일이 있어 덮어쓰지 않음', 'no-source': '복사할 원본 래칫 파일이 없음',
|
|
20
|
+
},
|
|
21
|
+
en: {
|
|
22
|
+
'not-absolute': 'not an absolute path', missing: 'folder does not exist', 'not-a-directory': 'not a directory',
|
|
23
|
+
'home-or-ancestor': 'the home directory or one of its parents', 'inside-claude-dir': 'inside ~/.claude',
|
|
24
|
+
'inside-library': 'inside ~/Library', 'inside-sprag-data': 'inside the sprag data folder', excluded: 'on the exclude list',
|
|
25
|
+
'not-owned': 'a user file of the same name exists, not overwritten', 'no-source': 'no source ratchet file to copy',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const norm = (p) => {
|
|
30
|
+
const abs = resolve(p);
|
|
31
|
+
try { return realpathSync(abs); } catch { return abs; }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export async function run({ args }) {
|
|
35
|
+
const cw = await import('../cowork-sync.js');
|
|
36
|
+
const { userLanguage } = await import('../config.js');
|
|
37
|
+
const ko = userLanguage() === 'ko';
|
|
38
|
+
const why = (r) => (REASONS[ko ? 'ko' : 'en'][r] || r);
|
|
39
|
+
const sub = args[1];
|
|
40
|
+
|
|
41
|
+
const summarize = (r) => {
|
|
42
|
+
console.log(ko
|
|
43
|
+
? `Cowork 래칫 동기화: 새로 씀 ${r.written.length}, 변경 없음 ${r.unchanged.length}, 건너뜀 ${r.skipped.length}, 제거 ${r.removed.length}`
|
|
44
|
+
: `Cowork ratchet sync: written ${r.written.length}, unchanged ${r.unchanged.length}, skipped ${r.skipped.length}, removed ${r.removed.length}`);
|
|
45
|
+
for (const d of r.written) console.log(` + ${d}`);
|
|
46
|
+
for (const d of r.removed) console.log(` - ${d}`);
|
|
47
|
+
for (const s of r.skipped) console.log(` ! ${s.dir} (${why(s.reason)})`);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (sub === 'sync') return summarize(cw.syncCoworkRatchets({ discover: true }));
|
|
51
|
+
|
|
52
|
+
if (sub === 'off') {
|
|
53
|
+
cw.saveCoworkConfig({ sync: false });
|
|
54
|
+
console.log(ko ? 'Cowork 래칫 동기화를 껐습니다. 만들어 둔 사본을 지웁니다.' : 'Cowork ratchet sync is off. Removing the copies it made.');
|
|
55
|
+
return summarize(cw.removeCoworkRatchets());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (sub === 'on') {
|
|
59
|
+
cw.saveCoworkConfig({ sync: true });
|
|
60
|
+
console.log(ko ? 'Cowork 래칫 동기화를 켰습니다.' : 'Cowork ratchet sync is on.');
|
|
61
|
+
return summarize(cw.syncCoworkRatchets({ discover: true }));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (sub === 'exclude' || sub === 'include') {
|
|
65
|
+
const target = args.slice(2).find((a) => !a.startsWith('-'));
|
|
66
|
+
if (!target) {
|
|
67
|
+
console.error(ko ? '폴더 경로를 지정하십시오.' : 'Pass a folder path.');
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const dir = norm(target);
|
|
72
|
+
const list = cw.coworkConfig().exclude;
|
|
73
|
+
if (sub === 'exclude') {
|
|
74
|
+
if (!list.includes(dir)) cw.saveCoworkConfig({ exclude: [...list, dir] });
|
|
75
|
+
const removed = cw.removeCoworkRatchetIn(dir);
|
|
76
|
+
console.log(ko
|
|
77
|
+
? `제외 목록에 추가했습니다: ${dir}${removed ? ' (사본을 지웠습니다)' : ''}`
|
|
78
|
+
: `Added to the exclude list: ${dir}${removed ? ' (copy removed)' : ''}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
cw.saveCoworkConfig({ exclude: list.filter((x) => x !== dir && norm(x) !== dir) });
|
|
82
|
+
console.log(ko ? `제외 목록에서 뺐습니다: ${dir}` : `Removed from the exclude list: ${dir}`);
|
|
83
|
+
return summarize(cw.syncCoworkRatchets({ discover: true }));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (sub !== undefined) {
|
|
87
|
+
console.error(ko ? `알 수 없는 하위 명령입니다: ${sub}` : `Unknown subcommand: ${sub}`);
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const st = cw.coworkStatus();
|
|
93
|
+
console.log(ko ? `Cowork 래칫 동기화: ${st.enabled ? '켜짐' : '꺼짐'}` : `Cowork ratchet sync: ${st.enabled ? 'on' : 'off'}`);
|
|
94
|
+
console.log(ko ? `마지막 동기화: ${st.lastSync || '없음'}` : `Last sync: ${st.lastSync || 'never'}`);
|
|
95
|
+
if (st.folders.length === 0) {
|
|
96
|
+
console.log(ko ? '발견한 Cowork 연결 폴더가 없습니다.' : 'No Cowork folders discovered.');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.log(ko ? '연결 폴더:' : 'Connected folders:');
|
|
100
|
+
for (const f of st.folders) {
|
|
101
|
+
const state = f.hasCopy ? (ko ? '사본 있음' : 'copy present')
|
|
102
|
+
: f.foreign ? why('not-owned')
|
|
103
|
+
: f.eligible ? (ko ? '사본 없음' : 'no copy yet')
|
|
104
|
+
: (ko ? `제외: ${why(f.reason)}` : `skipped: ${why(f.reason)}`);
|
|
105
|
+
console.log(` ${f.dir} [${state}]`);
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/commands/delegate.js
CHANGED
|
@@ -59,6 +59,13 @@ export async function run({ args, hasFlag }) {
|
|
|
59
59
|
const { userLanguage } = await import('../config.js');
|
|
60
60
|
const lang = userLanguage();
|
|
61
61
|
|
|
62
|
+
// `delegate agents` manages the tool-restricted subagent files. It is split
|
|
63
|
+
// off by the second word so it never mixes with the on/off PreToolUse guard.
|
|
64
|
+
if (sub === 'agents') {
|
|
65
|
+
await agentsCommand(args[2], lang);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
62
69
|
if (sub === 'on') {
|
|
63
70
|
// Install first, and only record the feature as on once that worked.
|
|
64
71
|
// Saving the flag first left `delegate: on, hook not registered` behind
|
|
@@ -131,3 +138,39 @@ export async function run({ args, hasFlag }) {
|
|
|
131
138
|
console.log('Enable with: sprag delegate on');
|
|
132
139
|
}
|
|
133
140
|
}
|
|
141
|
+
|
|
142
|
+
async function agentsCommand(action, lang) {
|
|
143
|
+
const ko = lang === 'ko';
|
|
144
|
+
const da = await import('../delegation-agents.js');
|
|
145
|
+
const report = (r) => {
|
|
146
|
+
for (const n of r.written) console.log(ko ? `✓ ${n} 에이전트를 설치했습니다.` : `✓ installed agent ${n}`);
|
|
147
|
+
for (const n of r.unchanged) console.log(ko ? ` ${n} 에이전트는 이미 최신입니다.` : ` agent ${n} is already current`);
|
|
148
|
+
for (const k of r.skipped) {
|
|
149
|
+
console.log(ko
|
|
150
|
+
? `✗ ${k.name} 에이전트는 건너뛰었습니다 (${k.reason === 'not-owned' ? '직접 만든 같은 이름의 파일이 있습니다' : k.reason}).`
|
|
151
|
+
: `✗ skipped agent ${k.name} (${k.reason})`);
|
|
152
|
+
}
|
|
153
|
+
for (const n of r.removed) console.log(ko ? `✓ ${n} 에이전트를 제거했습니다.` : `✓ removed agent ${n}`);
|
|
154
|
+
};
|
|
155
|
+
const rerender = async () => {
|
|
156
|
+
try { (await import('../model-rules.js')).syncAllFiles(); } catch { /* the next session start re-renders */ }
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (action === 'on' || action === 'off') {
|
|
160
|
+
const { loadConfig, saveConfig } = await import('../config.js');
|
|
161
|
+
saveConfig({ ...loadConfig(), delegationAgents: action === 'on' });
|
|
162
|
+
const r = action === 'on' ? da.syncDelegationAgents() : da.removeDelegationAgents();
|
|
163
|
+
await rerender();
|
|
164
|
+
report(r);
|
|
165
|
+
console.log(action === 'on'
|
|
166
|
+
? (ko ? '위임 전용 에이전트를 켰습니다. 래칫 문구가 에이전트 이름으로 다시 만들어졌습니다.' : 'Delegation agents are on. The ratchet wording was re-rendered with the agent names.')
|
|
167
|
+
: (ko ? '위임 전용 에이전트를 껐습니다. 래칫 문구가 model 지정 방식으로 다시 만들어졌습니다.' : 'Delegation agents are off. The ratchet wording was re-rendered with plain model tiers.'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const st = da.delegationAgentStatus();
|
|
172
|
+
console.log(ko ? `delegate agents: ${st.enabled ? 'on' : 'off'}` : `delegate agents: ${st.enabled ? 'on' : 'off'}`);
|
|
173
|
+
const label = { 'installed-by-sprag': ko ? 'sprag 가 설치함' : 'installed by sprag', 'user-file': ko ? '직접 만든 파일' : 'user file', absent: ko ? '없음' : 'absent' };
|
|
174
|
+
for (const a of st.agents) console.log(` ${a.name}: ${a.state} (${label[a.state]})`);
|
|
175
|
+
console.log(ko ? '켜기 또는 끄기: sprag delegate agents on|off' : 'Toggle with: sprag delegate agents on|off');
|
|
176
|
+
}
|
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');
|
|
@@ -368,6 +371,17 @@ export async function run({ hasFlag }) {
|
|
|
368
371
|
// they have not read. All the install does is say what is waiting.
|
|
369
372
|
// Presets now live in ratchet-preset.md. Moving the ones older versions
|
|
370
373
|
// appended to ratchet.md comes first, so they are not offered again below.
|
|
374
|
+
try {
|
|
375
|
+
const d = (await import('../delegation-agents.js')).syncDelegationAgents();
|
|
376
|
+
if (d.written.length) {
|
|
377
|
+
console.log(lang === 'ko'
|
|
378
|
+
? ` agents: 위임 전용 에이전트 ${d.written.length}개를 ~/.claude/agents 에 설치했습니다 (MCP 도구 없음).`
|
|
379
|
+
: ` agents: installed ${d.written.length} delegation-only agent(s) in ~/.claude/agents (no MCP tools).`);
|
|
380
|
+
}
|
|
381
|
+
} catch (e) {
|
|
382
|
+
debug('install:delegation-agents', e);
|
|
383
|
+
}
|
|
384
|
+
|
|
371
385
|
try {
|
|
372
386
|
const { preparePresetRatchet } = await import('../preset-ratchet.js');
|
|
373
387
|
const p = preparePresetRatchet();
|
|
@@ -425,15 +439,15 @@ export async function run({ hasFlag }) {
|
|
|
425
439
|
: ' codex: Codex is installed here. Set up its hooks and harness too?', { defaultValue: true });
|
|
426
440
|
}
|
|
427
441
|
if (proceed) {
|
|
428
|
-
const { configureCodexHooks } = await import('../codex
|
|
429
|
-
const { initCodexHarness } = await import('../codex
|
|
442
|
+
const { configureCodexHooks } = await import('../codex/installer.js');
|
|
443
|
+
const { initCodexHarness } = await import('../codex/harness.js');
|
|
430
444
|
const { loadConfig } = await import('../config.js');
|
|
431
|
-
const { setupCodexPanel, describePanelSetup } = await import('../codex
|
|
445
|
+
const { setupCodexPanel, describePanelSetup } = await import('../codex/panel-setup.js');
|
|
432
446
|
const config = loadConfig() || {};
|
|
433
447
|
// A manual install asks before touching the system package manager.
|
|
434
448
|
let env = process.env;
|
|
435
449
|
if (interactive && process.platform === 'darwin') {
|
|
436
|
-
const { tmuxAvailable } = await import('../codex
|
|
450
|
+
const { tmuxAvailable } = await import('../codex/panel-setup.js');
|
|
437
451
|
if (!tmuxAvailable() && !await confirm(lang === 'ko'
|
|
438
452
|
? ' codex: tmux 가 없습니다. Homebrew 로 설치해 같은 창에 패널을 붙일까요?'
|
|
439
453
|
: ' codex: tmux is missing. Install it with Homebrew so the panel shares the Codex window?', { defaultValue: true })) {
|
|
@@ -455,7 +469,7 @@ export async function run({ hasFlag }) {
|
|
|
455
469
|
const h = initCodexHarness({ scope: 'global' });
|
|
456
470
|
console.log(` codex: harness ${h.file}`);
|
|
457
471
|
try {
|
|
458
|
-
const { syncCodexKoreanBlock } = await import('../codex
|
|
472
|
+
const { syncCodexKoreanBlock } = await import('../codex/harness.js');
|
|
459
473
|
const k = syncCodexKoreanBlock({ cfg: config });
|
|
460
474
|
console.log(` codex: korean-style ${k.action} (${k.file})`);
|
|
461
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
|
+
}
|
|
@@ -296,11 +296,15 @@ export async function run({ args, hasFlag, numArg }) {
|
|
|
296
296
|
// would keep reading the old wording indefinitely. The call writes only
|
|
297
297
|
// when the rendering differs, so the common case is a read and a string
|
|
298
298
|
// compare.
|
|
299
|
+
// Agent files come first so the ratchet-model.md rendered next names them.
|
|
300
|
+
try { (await import('../delegation-agents.js')).syncDelegationAgents(); } catch (e) { debug('route-scan:delegation-agents', e); }
|
|
299
301
|
try { mrHook.syncAllFiles(); } catch (e) { debug('route-scan:sync-ratchet', e); }
|
|
300
302
|
// Same reason for the preset ratchet: an upgrade that rewords or drops a
|
|
301
303
|
// bundled rule reaches ratchet-preset.md here, without a reinstall. It
|
|
302
304
|
// also moves presets older versions appended to ratchet.md.
|
|
303
305
|
try { (await import('../preset-ratchet.js')).preparePresetRatchet(); } catch (e) { debug('route-scan:sync-preset', e); }
|
|
306
|
+
// Cowork cannot follow the ~/.claude imports, so it gets a pasted copy.
|
|
307
|
+
try { (await import('../cowork-sync.js')).syncCoworkRatchets({ discover: true }); } catch (e) { debug('route-scan:cowork-sync', e); }
|
|
304
308
|
// Registered rules whose delegated-category error rate crossed the
|
|
305
309
|
// health threshold since promotion — the user approved these, so a
|
|
306
310
|
// status change must be briefed, not just written into the md file.
|
|
@@ -466,6 +470,8 @@ export async function run({ args, hasFlag, numArg }) {
|
|
|
466
470
|
let cache = rs.readRouteScan();
|
|
467
471
|
if (hasFlag('--refresh') || (cache && cache.days !== days) || await rs.shouldRescan(cache, { days })) {
|
|
468
472
|
cache = await rs.runRouteScan({ days });
|
|
473
|
+
// model-rules were rewritten by the scan; carry them to the Cowork copies.
|
|
474
|
+
try { (await import('../cowork-sync.js')).syncCoworkRatchets({ discover: true }); } catch (e) { debug('route-scan:cowork-sync', e); }
|
|
469
475
|
}
|
|
470
476
|
if (hasFlag('--quiet')) return;
|
|
471
477
|
if (hasFlag('--json')) {
|