claude-token-saver 3.44.0 → 3.55.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.
Files changed (84) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +35 -0
  3. package/README.ko.md +150 -48
  4. package/README.md +205 -192
  5. package/bin/cli.js +108 -39
  6. package/docs/CODEX.ko.md +184 -0
  7. package/docs/CODEX.md +497 -0
  8. package/examples/statusline-command.ps1 +11 -5
  9. package/examples/statusline-command.sh +9 -4
  10. package/package.json +20 -6
  11. package/presets/delegation/bounds.md +29 -0
  12. package/presets/model-rules.json +50 -9
  13. package/presets/ratchet-rules.json +4 -1
  14. package/src/agent.js +33 -0
  15. package/src/caps-cache.js +6 -2
  16. package/src/changelog.js +70 -0
  17. package/src/cli-name.js +25 -0
  18. package/src/codex-brief.js +89 -0
  19. package/src/codex-budget.js +73 -0
  20. package/src/codex-cache-policy.js +203 -0
  21. package/src/codex-cache.js +46 -0
  22. package/src/codex-delegation.js +154 -0
  23. package/src/codex-doc2md-ledger.js +45 -0
  24. package/src/codex-doc2md.js +99 -0
  25. package/src/codex-doctor.js +53 -0
  26. package/src/codex-harness.js +200 -0
  27. package/src/codex-hooks.js +138 -0
  28. package/src/codex-installer.js +79 -0
  29. package/src/codex-ledger.js +143 -0
  30. package/src/codex-panel-launcher.js +48 -0
  31. package/src/codex-panel-runner.js +74 -0
  32. package/src/codex-panel-session.js +35 -0
  33. package/src/codex-panel-setup.js +98 -0
  34. package/src/codex-panel-shell.js +51 -0
  35. package/src/codex-panel-state.js +40 -0
  36. package/src/codex-panel.js +448 -0
  37. package/src/codex-parser.js +397 -0
  38. package/src/codex-route-scan.js +171 -0
  39. package/src/commands/brief.js +24 -1
  40. package/src/commands/codex-delegate.js +59 -0
  41. package/src/commands/codex.js +579 -0
  42. package/src/commands/delegate.js +133 -0
  43. package/src/commands/harness.js +16 -3
  44. package/src/commands/install.js +109 -12
  45. package/src/commands/korean.js +2 -2
  46. package/src/commands/mode.js +22 -2
  47. package/src/commands/profile-map.js +164 -0
  48. package/src/commands/route-scan.js +74 -7
  49. package/src/commands/seed.js +16 -12
  50. package/src/commands/uninstall.js +28 -4
  51. package/src/commands/upgrade.js +13 -0
  52. package/src/compact-window.js +7 -1
  53. package/src/config.js +22 -3
  54. package/src/cost.js +34 -0
  55. package/src/delegation-guard.js +278 -0
  56. package/src/doc2md-ledger.cjs +3 -3
  57. package/src/doc2md.cjs +72 -28
  58. package/src/effort-palette.js +145 -0
  59. package/src/formatters/statusline.js +352 -123
  60. package/src/formatters/table.js +5 -4
  61. package/src/gateway-auth.js +86 -0
  62. package/src/glyphs.js +103 -0
  63. package/src/handoff.js +27 -7
  64. package/src/harness-analyzer.cjs +7 -2
  65. package/src/harness-templates.js +33 -1
  66. package/src/harness.js +51 -17
  67. package/src/hook-manager.js +42 -14
  68. package/src/hook.cjs +34 -2
  69. package/src/installer.js +300 -16
  70. package/src/litellm-budget.js +38 -67
  71. package/src/litellm-models.js +264 -0
  72. package/src/model-alias.js +170 -16
  73. package/src/model-rules.js +53 -17
  74. package/src/parser.js +89 -1
  75. package/src/preset-ratchet.js +133 -0
  76. package/src/route-inject.js +161 -0
  77. package/src/route-scan.js +68 -8
  78. package/src/seed-rules.js +117 -55
  79. package/src/session-paths.js +242 -0
  80. package/src/statusline-mode.js +106 -0
  81. package/src/stdin-payload.js +40 -0
  82. package/src/ultracode.js +202 -0
  83. package/src/update-check.js +217 -3
  84. package/src/window-labels.js +19 -8
package/bin/cli.js CHANGED
@@ -10,8 +10,8 @@
10
10
  * npx sprag --format csv # CSV output
11
11
  * npx sprag --project myproj # filter by project
12
12
  * npx sprag route-scan # detect recurring easy work → haiku-delegation candidates
13
- * npx sprag install # set up skill/hooks/statusline; asks about harness + Korean guidance
14
- * npx sprag install --yes # take the defaults without asking (same as --no-input)
13
+ * npx sprag install # set up everything, presets and Codex included, without asking
14
+ * npx sprag install --manual # confirm each optional step
15
15
  * npx sprag --install-hook # install PostToolUse hook
16
16
  * npx sprag --uninstall-hook # remove hook
17
17
  * npx claude-token-saver --hook-run # internal: called by hook
@@ -34,9 +34,13 @@ import {
34
34
  readStdinJson,
35
35
  extractCaps,
36
36
  extractContextUsage,
37
+ extractEffort,
37
38
  extractModel,
39
+ extractTranscriptPath,
38
40
  } from '../src/stdin-payload.js';
39
41
 
42
+ import { effortChipLevel } from '../src/ultracode.js';
43
+
40
44
  import { parseAllSessions, getLastUserMessageTime } from '../src/parser.js';
41
45
  import {
42
46
  dailyTrend,
@@ -48,13 +52,18 @@ import {
48
52
  sessionMetrics,
49
53
  diagnoseSession,
50
54
  } from '../src/stats.js';
51
- import { estimateCost } from '../src/cost.js';
55
+ import { estimateCostAcross } from '../src/cost.js';
52
56
  import { chipForIssues } from '../src/advice.js';
53
57
  import { debug } from '../src/debug.js';
54
58
  import { createArgs } from '../src/cli-args.js';
55
59
  import { updateStatus, maybeSpawnUpdateCheck } from '../src/update-check.js';
60
+ import { selectAgent } from '../src/agent.js';
56
61
 
57
- const args = process.argv.slice(2);
62
+ const selection = (() => {
63
+ try { return selectAgent(process.argv.slice(2)); }
64
+ catch (e) { console.error(`Error: ${e.message}`); process.exit(1); }
65
+ })();
66
+ const { args, agent } = selection;
58
67
 
59
68
  const PKG_VERSION = (() => {
60
69
  try {
@@ -86,11 +95,11 @@ function readUpdateChip() {
86
95
  // Subcommands this build knows how to run. Used only by the guard below.
87
96
  const KNOWN_SUBCOMMANDS = new Set([
88
97
  'last', 'brief', 'history', 'handoff', 'install', 'uninstall', 'mode', 'korean', 'cohesion',
89
- 'doc2md', 'harness', 'route-scan', 'compact-window', 'update-check', 'upgrade',
90
- 'seed', 'litellm-budget', 'feedback',
98
+ 'doc2md', 'delegate', 'harness', 'route-scan', 'compact-window', 'update-check', 'upgrade',
99
+ 'seed', 'litellm-budget', 'profile-map', 'feedback',
91
100
  ]);
92
101
 
93
- const USAGE = `sprag — Claude Code token usage, cache health, and model routing
102
+ const USAGE = `sprag — Claude Code and Codex agent harness
94
103
 
95
104
  Usage:
96
105
  sprag default report (last 30 days)
@@ -98,9 +107,24 @@ Usage:
98
107
  sprag --format json JSON output
99
108
  sprag --format csv CSV output
100
109
  sprag --project myproj filter by project
110
+ sprag --agent codex Codex token report (table/json/csv)
111
+ sprag --statusline --agent codex one-shot compact terminal status
112
+ sprag doctor --agent codex diagnose hooks, config, harness, and session logs
113
+ sprag capabilities --agent codex full support matrix and limitations
114
+ sprag panel --agent codex live companion panel in a separate terminal
115
+ sprag panel auto on --agent codex open panel on Codex startup/resume (macOS)
116
+ sprag panel --open --agent codex open/reuse a macOS Terminal panel now
117
+ sprag panel run --agent codex -- run Codex above a compact panel (tmux)
118
+ sprag panel doctor --agent codex check registration, hook execution, and rendering
119
+ --project <path> / --session <id> / --interval 2 / --once / --no-color
120
+ sprag install --agent codex install Codex hooks + AGENTS.md harness
121
+ sprag harness init --agent codex initialize this project's Codex harness
122
+ sprag uninstall --agent codex remove the global Codex integration
123
+ --agent claude|codex (default: claude; accepted before or after subcommands)
101
124
  sprag route-scan detect recurring easy work → delegation candidates
102
- sprag install set up skill/hooks/statusline
103
- sprag install --yes take the defaults without asking
125
+ sprag profile-map show/refresh the gateway model map (LiteLLM /model/info)
126
+ sprag install set up everything (presets, and Codex when present) without asking
127
+ sprag install --manual confirm each optional step
104
128
  sprag uninstall remove everything install added
105
129
  sprag harness check score the harness setup in CLAUDE.md
106
130
  sprag harness analyze run the harness transcript analysis manually
@@ -119,6 +143,10 @@ Bug reports & feature requests: https://github.com/rootstudioyaml/sprag/issues
119
143
  `;
120
144
 
121
145
  async function main() {
146
+ if (agent === 'codex') {
147
+ if (hasFlag('--version') || hasFlag('-v')) { console.log(PKG_VERSION); return; }
148
+ return (await import('../src/commands/codex.js')).run({ args, version: PKG_VERSION });
149
+ }
122
150
  // Help must never fall through to the default report — that runs a full
123
151
  // 30-day scan, which is the opposite of what someone asking for help wants.
124
152
  if (hasFlag('--help') || hasFlag('-h') || args[0] === 'help') {
@@ -242,6 +270,14 @@ async function main() {
242
270
  return (await import('../src/commands/doc2md.js')).run({ args, hasFlag });
243
271
  }
244
272
 
273
+ // Subcommand: delegate — rewrite Task/Agent delegation prompts so a
274
+ // subagent gets bounds, Korean guidance when the prompt needs it, and paths
275
+ // this session already read, instead of starting from nothing.
276
+ // sprag delegate on | off | --hook
277
+ if (args[0] === 'delegate') {
278
+ return (await import('../src/commands/delegate.js')).run({ args, hasFlag });
279
+ }
280
+
245
281
  // Subcommand: seed — register the bundled starter rules (model-fitting
246
282
  // presets + curated ratchet rules), one answer at a time.
247
283
  // sprag seed | seed accept <id> --global|--project | seed skip <id>
@@ -296,12 +332,22 @@ async function main() {
296
332
  // sprag litellm-budget --json # 캐시 원본 JSON 출력
297
333
  // sprag litellm-budget --refresh # 지금 프록시에 물어봄 (detached 자식이 사용)
298
334
  if (args[0] === 'litellm-budget') {
299
- const { gatewayBase, readBudgetState, refreshBudgetState, formatBudgetReport } =
335
+ const { gatewayBase, readBudgetState, refreshBudgetState, formatBudgetReport, resolveKey } =
300
336
  await import('../src/litellm-budget.js');
301
337
  const quiet = hasFlag('--quiet');
302
338
  if (hasFlag('--refresh')) {
303
339
  try {
304
- const next = await refreshBudgetState();
340
+ // apiKeyHelper 가 토큰 수명(TTL)을 소유합니다. 헬퍼를 한 번만 불러 그
341
+ // 키를 예산 갱신과 게이트웨이 모델맵 갱신 양쪽에 재사용합니다. 갱신
342
+ // 경로마다 resolveKey() 를 다시 부르면 사본을 만드는 셈이 되고, 헬퍼
343
+ // 호출 횟수도 그만큼 늘어납니다.
344
+ const key = resolveKey();
345
+ let next = null;
346
+ if (key) {
347
+ next = await refreshBudgetState(process.env, fetch, key);
348
+ const { maybeRefreshGatewayModelMap } = await import('../src/litellm-models.js');
349
+ await maybeRefreshGatewayModelMap({ base: gatewayBase(), key });
350
+ }
305
351
  if (!quiet) console.log(JSON.stringify(next, null, 2));
306
352
  } catch (e) {
307
353
  debug('litellm-budget:refresh', e);
@@ -325,10 +371,24 @@ async function main() {
325
371
  console.log('예산 캐시가 비어 있습니다. `sprag litellm-budget --refresh` 로 먼저 조회하십시오.');
326
372
  return;
327
373
  }
328
- for (const line of formatBudgetReport(state)) console.log(line);
374
+ // Same resolver the statusline uses: this report draws a gauge too, and a
375
+ // glyph the terminal's font lacks garbles here exactly as it does there.
376
+ const { resolveLabelMode } = await import('../src/statusline-mode.js');
377
+ const { statuslineDefaults } = await import('../src/config.js');
378
+ const { mode: labelMode } = resolveLabelMode({ hasFlag, cfg: statuslineDefaults() });
379
+ for (const line of formatBudgetReport(state, new Date(), labelMode)) console.log(line);
329
380
  return;
330
381
  }
331
382
 
383
+ // Subcommand: profile-map · LiteLLM 게이트웨이의 프로파일 ID → 모델 별칭
384
+ // 매핑을 보여 주거나(GET /model/info) 갱신합니다.
385
+ // sprag profile-map # 캐시된 게이트웨이 모델맵 출력
386
+ // sprag profile-map --json # 캐시 원본 JSON 출력
387
+ // sprag profile-map --refresh # 지금 프록시에 물어봄 (사람이 직접 실행)
388
+ if (args[0] === 'profile-map') {
389
+ return (await import('../src/commands/profile-map.js')).run({ args, hasFlag });
390
+ }
391
+
332
392
  // Subcommand: upgrade — run the install command that matches how this copy
333
393
  // got here, then confirm the new version.
334
394
  // sprag upgrade # install the latest release
@@ -397,9 +457,10 @@ async function main() {
397
457
  process.exit(1);
398
458
  }
399
459
  const { formatReport } = await import('../src/formatters/statusline.js');
400
- const isIcon = hasFlag('--icon')
401
- ? true
402
- : (hasFlag('--no-icon') || hasFlag('--text') ? false : cfg.icon);
460
+ // Same resolver as the real statusline path: a demo that renders emoji
461
+ // where the real line would not is worse than no demo at all.
462
+ const { resolveLabelMode } = await import('../src/statusline-mode.js');
463
+ const { mode: labelMode } = resolveLabelMode({ hasFlag, cfg });
403
464
  const isVerbose = hasFlag('--verbose')
404
465
  ? true
405
466
  : (hasFlag('--no-verbose') || hasFlag('--compact') ? false : cfg.verbose);
@@ -413,7 +474,7 @@ async function main() {
413
474
  color: colorOk,
414
475
  verbose: isVerbose,
415
476
  timer: showTimer,
416
- mode: isIcon ? 'icon' : 'text',
477
+ mode: labelMode,
417
478
  segments,
418
479
  singleLine: hasFlag('--single-line'),
419
480
  });
@@ -480,12 +541,17 @@ async function main() {
480
541
  const { statuslineDefaults } = await import('../src/config.js');
481
542
  const cfg = statuslineDefaults();
482
543
  const colorOk = !hasFlag('--no-color') && !process.env.NO_COLOR && cfg.color;
483
- const isIcon = hasFlag('--icon')
484
- ? true
485
- : (hasFlag('--no-icon') || hasFlag('--text') ? false : cfg.icon);
544
+ // Third caller of the same decision — the no-session line has to agree
545
+ // with the populated one, or IntelliJ users watch icons appear and
546
+ // disappear as the analysis window empties and fills.
547
+ const { resolveLabelMode } = await import('../src/statusline-mode.js');
548
+ const { mode: labelMode } = resolveLabelMode({ hasFlag, cfg });
486
549
  const stdinJson = readStdinJson();
487
550
  const caps = extractCaps(stdinJson);
488
551
  const model = extractModel(stdinJson);
552
+ // Resolved the same way as on the populated line below, so the chip does
553
+ // not change its mind about the level when the analysis window empties.
554
+ const effort = effortChipLevel(extractEffort(stdinJson), extractTranscriptPath(stdinJson));
489
555
  if (caps || model) {
490
556
  try {
491
557
  const { persistSnapshot } = await import('../src/caps-cache.js');
@@ -493,8 +559,8 @@ async function main() {
493
559
  } catch (e) { debug('caps-cache:persist', e); }
494
560
  }
495
561
  console.log(formatNoSession(
496
- { caps, model, windowLabel, version: PKG_VERSION, update: readUpdateChip() },
497
- { color: colorOk, mode: isIcon ? 'icon' : 'text' },
562
+ { caps, model, effort, windowLabel, version: PKG_VERSION, update: readUpdateChip() },
563
+ { color: colorOk, mode: labelMode },
498
564
  ));
499
565
  return;
500
566
  }
@@ -526,7 +592,7 @@ async function main() {
526
592
  const ttl = ttlBreakdown(sessions);
527
593
  const sum = summary(sessions);
528
594
  const anomalies = detectAnomalies(trend);
529
- const cost = estimateCost(sum, sessions[0]?.model);
595
+ const cost = estimateCostAcross(sessions);
530
596
  const spikeReport = detectSpikes(sessions, { recentHours: 24, multiplier: 3 });
531
597
  const contextWindow = detectContextWindow(sessions, { recentHours: 24 });
532
598
 
@@ -539,6 +605,18 @@ async function main() {
539
605
  const stdinJson = readStdinJson();
540
606
  let caps = extractCaps(stdinJson);
541
607
  let model = extractModel(stdinJson);
608
+ // Not persisted to the caps cache the way caps/model are: the cache exists so
609
+ // the table view (which pipes no stdin) can still show them, and a level the
610
+ // user has since changed would be worse there than no chip at all. The effort
611
+ // chip is a live-statusline reading only.
612
+ //
613
+ // `ultracode` is the one level the payload cannot name — Claude Code raises it
614
+ // to xhigh and reports that — so the transcript settles which of the two this
615
+ // session is on. Only an xhigh payload can resolve to ultracode, so a session
616
+ // on any other level pays nothing for the check (see src/ultracode.js).
617
+ const effort = isStatusline
618
+ ? effortChipLevel(extractEffort(stdinJson), extractTranscriptPath(stdinJson))
619
+ : extractEffort(stdinJson);
542
620
  const ctxLive = extractContextUsage(stdinJson);
543
621
  if (isStatusline && (caps || model)) {
544
622
  try {
@@ -730,6 +808,7 @@ async function main() {
730
808
  spikeChip,
731
809
  caps,
732
810
  model,
811
+ effort,
733
812
  delegationSaved,
734
813
  delegationTotals,
735
814
  doc2mdTotals,
@@ -749,21 +828,11 @@ async function main() {
749
828
  const { statuslineDefaults } = await import('../src/config.js');
750
829
  const cfg = statuslineDefaults();
751
830
 
752
- // IntelliJ's Claude Code plugin renders the statusline through a custom
753
- // widget that fuses prior frames with the new one when emoji are present,
754
- // producing garbage like "59:548" that no ANSI escape can clean up
755
- // (verified: emitting the same output directly into JediTerm renders
756
- // cleanly, so the bug is in the plugin's render path, not the terminal).
757
- // Force text mode unconditionally inside IntelliJ — even past an explicit
758
- // `--icon` flag, since wrappers commonly hardcode `--icon` and the user
759
- // can't easily edit them; icon mode is just broken there.
760
- const isIntelliJ = process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
761
- // CLI flags take precedence; otherwise fall back to persisted config.
762
- const isIcon = isIntelliJ
763
- ? false
764
- : (hasFlag('--icon')
765
- ? true
766
- : (hasFlag('--no-icon') || hasFlag('--text') ? false : cfg.icon));
831
+ // Icon vs text is decided in one module shared with the `--demo` path, so
832
+ // the two cannot drift apart again. It also owns the IntelliJ downgrade and
833
+ // the escape hatch out of it (SPRAG_ICON=force / `sprag mode icon-force`).
834
+ const { resolveLabelMode } = await import('../src/statusline-mode.js');
835
+ const { mode: labelMode } = resolveLabelMode({ hasFlag, cfg });
767
836
  const isVerbose = hasFlag('--verbose')
768
837
  ? true
769
838
  : (hasFlag('--no-verbose') || hasFlag('--compact') ? false : cfg.verbose);
@@ -779,7 +848,7 @@ async function main() {
779
848
  color: colorOk,
780
849
  verbose: isVerbose,
781
850
  timer: showTimer,
782
- mode: isIcon ? 'icon' : 'text',
851
+ mode: labelMode,
783
852
  segments,
784
853
  // macOS builds of Claude Code have rendered only the first line of a
785
854
  // multi-line statusline in some versions (anthropics/claude-code#35176)
@@ -796,7 +865,7 @@ async function main() {
796
865
 
797
866
  main().catch((err) => {
798
867
  // Statusline mode must never spam multi-line errors (called every ~300ms)
799
- const isStatusline = process.argv.includes('--statusline') || process.argv.includes('statusline');
868
+ const isStatusline = agent === 'claude' && (process.argv.includes('--statusline') || process.argv.includes('statusline'));
800
869
  if (isStatusline) {
801
870
  const colorOk = !process.argv.includes('--no-color') && !process.env.NO_COLOR;
802
871
  const red = colorOk ? '\x1b[31m' : '';
@@ -0,0 +1,184 @@
1
+ # Codex 연동
2
+
3
+ [← README](../README.ko.md) · [English](./CODEX.md)
4
+
5
+ Sprag는 Codex의 기본 훅과 `AGENTS.md`를 사용합니다. Claude Code와 함께 설치할 수 있으며,
6
+ 명령에 `--agent codex`를 붙이면 Codex 기록과 설정을 선택합니다. 이 옵션은 전역 기본값을 바꾸지 않습니다.
7
+
8
+ ## 설치
9
+
10
+ ```sh
11
+ npm i -g sprag-cli
12
+ ```
13
+
14
+ Codex가 설치되어 있으면 패키지 설치가 Claude Code 연동, 추천 룰과 함께 Codex도 설정합니다.
15
+ 설치 중에 묻는 것은 없으며, 단계마다 확인하려면 `sprag install --manual`을 쓰십시오.
16
+ Claude Code 설정을 건드리지 않고 Codex만 쓰려면 postinstall을 건너뛰고 Codex 설치를 직접 실행합니다.
17
+
18
+ ```sh
19
+ npm i -g sprag-cli --ignore-scripts
20
+ sprag install --agent codex
21
+ ```
22
+
23
+ `sprag doctor --agent codex`는 문제가 생겼을 때 쓰는 진단 명령입니다. Codex의 `/hooks`에서 등록된 명령을
24
+ 검토하고 신뢰해야 훅이 동작합니다. 설치는 훅을 등록할 뿐 신뢰 설정을 바꾸지 않습니다.
25
+ 관리자 정책이나 `features.hooks = false` 설정이 훅을 막을 수도 있습니다.
26
+
27
+ 설정 위치는 `$CODEX_HOME`이며, 지정하지 않으면 `~/.codex`입니다. 설치는 `hooks.json`과
28
+ 전역 `AGENTS.md`에 Sprag 항목을 추가하고 기존 내용을 보존합니다. 비어 있지 않은
29
+ `AGENTS.override.md`가 있으면 그 파일을 사용하며, 변경 전 사본을 남깁니다.
30
+ Claude 설정이나 `config.toml`은 고치지 않습니다.
31
+
32
+ macOS zsh 환경에서는 설치가 tmux 설치 여부를 먼저 확인합니다. tmux가 없으면 Homebrew로 설치하고,
33
+ `CTS_NO_TMUX=1`을 지정하면 이 단계를 건너뜁니다. 이어서 zsh 연동을 추가해 `codex`를 입력하면 같은
34
+ 터미널 안에 패널이 인라인으로 붙으며, 이때는 별도 창을 여는 훅을 등록하지 않습니다. tmux를 설치할 수
35
+ 없거나 로그인 셸이 zsh가 아니거나 이미 사용자의 `codex` 별칭·함수가 있으면 별도 Terminal 창 방식으로
36
+ 대신 동작합니다. `sprag panel shell remove --agent codex`로 끈 설정은 재설치와 업그레이드에도 그대로
37
+ 유지되며, `--no-panel`은 tmux 설정과 zsh 연동을 모두 건너뜁니다.
38
+ `sprag panel auto off --agent codex`로 끈 설정은 재설치해도 유지합니다.
39
+ 패널 실행 여부는 `sprag panel doctor --agent codex`로 확인하십시오.
40
+
41
+ ## 지원 기능
42
+
43
+ | 기능 | Codex 동작 |
44
+ |---|---|
45
+ | 하네스 | 전역·프로젝트 AGENTS.md에 다섯 원칙을 등록합니다 |
46
+ | 래칫 규칙 | 전역·프로젝트 규칙을 세션 시작과 재개 시 전달합니다 |
47
+ | 반복 실패 탐지 | 최근 명령·패치 실패에서 `ratchet?` 후보를 찾습니다 |
48
+ | 위임 후보 | 프로젝트·공급자·상위 모델별로 반복된 단순 작업을 찾습니다 |
49
+ | seed | 호환되는 래칫 프리셋을 제안하고 응답을 별도 저장합니다 |
50
+ | 문서 변환 | PDF·Office·Figma 문서를 Markdown으로 변환하고 캐시 덮어쓰기를 막습니다 |
51
+ | 한국어 검사 | 파일 쓰기 후 문체 위반을 검사하며 block·warn·off를 지원합니다 |
52
+ | 상태 표시 | 별도 패널, tmux 패널, 일회성 상태줄을 제공합니다 |
53
+ | 브리핑·이력 | 최신 컨텍스트와 사용량 경고를 알리고 세션별로 저장합니다 |
54
+ | 위임 원장 | Sprag가 위임한 하위 실행과 LiteLLM 단가로 예상 차액을 계산합니다 |
55
+
56
+ ## 패널과 보고서
57
+
58
+ ```sh
59
+ sprag panel --agent codex
60
+ sprag panel run --agent codex --
61
+ sprag --statusline --text --agent codex
62
+ sprag --agent codex --days 7 --format json
63
+ sprag brief --agent codex
64
+ sprag history --agent codex --days 7
65
+ sprag handoff --agent codex
66
+ ```
67
+
68
+ 별도 패널은 현재 프로젝트의 최신 기본 세션을 따릅니다. `--session ID`를 붙이면 특정 세션을
69
+ 고정합니다. tmux 패널은 실행마다 별도 연결 파일을 사용하므로 같은 프로젝트의 다른 세션이
70
+ 화면을 차지하지 않습니다. 연결 전에는 대기 상태를 표시합니다. 일부 Codex 버전은 첫 프롬프트를
71
+ 제출한 뒤에 SessionStart 훅을 실행하므로 그때 패널 연결이 이루어집니다.
72
+
73
+ tmux 실행은 `--no-daemon`과 호출별 상태줄 설정을 사용합니다. Codex의 모델·추론 수준과
74
+ 설정 파일은 바꾸지 않으며, `--keep-native-statusline`으로 기존 상태줄을 유지할 수 있습니다.
75
+ 패널은 Codex 기본 상태줄에 셸 명령을 넣는 방식이 아닙니다.
76
+
77
+ 설치는 이제 이 연동을 기본으로 추가합니다. macOS zsh에서 `codex`를 입력할 때 tmux 패널을 함께 열려면
78
+ 다음 명령을 사용하십시오. 기존 별칭이나 함수는 덮어쓰지 않습니다.
79
+
80
+ ```sh
81
+ sprag panel shell install --agent codex
82
+ sprag panel shell remove --agent codex
83
+ ```
84
+
85
+ 사용량은 로컬 `sessions`와 `archived_sessions`의 rollout 기록에서 읽습니다. 중복된 누적 토큰을
86
+ 다시 더하지 않으며, 캐시 입력과 추론 출력도 각각 입력·출력에 이미 포함된 부분으로 처리합니다.
87
+ 기록은 안정된 공개 API가 아니므로 일부가 빠지거나 형식이 바뀌면 값이 불완전할 수 있습니다.
88
+ 사용량 갱신 횟수는 정확한 API 호출 횟수가 아닙니다.
89
+
90
+ ## 위임 후보와 프리셋
91
+
92
+ ```sh
93
+ sprag route-scan --refresh --agent codex
94
+ sprag delegate rules add R1 --project --agent codex
95
+ sprag route-scan dismiss R1 --agent codex
96
+ sprag delegate on --agent codex
97
+ sprag seed --agent codex
98
+ sprag seed accept all --project --agent codex
99
+ sprag seed skip all --agent codex
100
+ sprag seed reset --agent codex
101
+ ```
102
+
103
+ 위임 후보에는 같은 유형의 완료된 단순 작업이 세 번 이상 필요합니다. 중단된 작업과 이미 위임한
104
+ 작업, 오류가 많은 작업, 도중에 사용자가 방향을 바꾼 작업은 제외합니다. 같은 세션의 보관 사본도
105
+ 중복 집계하지 않습니다. 등록 전에는 사용자가 전역과 프로젝트 중 적용 범위를 정해야 합니다.
106
+
107
+ 단가와 실행 근거를 확인할 수 있으면 더 저렴한 모델을 제안합니다. 단가가 없으면 후보를 유지하되
108
+ 등록할 때 `--model ID`를 요구합니다. 모델 사용 가능 여부는 실제 공급자 설정에 달려 있습니다.
109
+ 명시한 모델과 사용자 정의 역할은 유지하며, 사용할 수 없는 하위 에이전트 도구를 만들어 내지 않습니다.
110
+
111
+ SessionStart는 캐시된 후보를 읽고 필요할 때 별도 프로세스에서 갱신합니다. 명시적인 `--refresh`는
112
+ 대기 주기를 건너뜁니다. `seed`는 Claude의 Haiku·Sonnet 프리셋을 가져오지 않으며,
113
+ Codex의 수락·거절 기록도 Claude 기록과 분리합니다.
114
+
115
+ ## 래칫 규칙
116
+
117
+ ```sh
118
+ sprag harness init --project --agent codex
119
+ sprag harness promote "Run the changed module's tests before completion." --project --agent codex
120
+ sprag harness list --project --agent codex
121
+ sprag harness promote 1 --session SESSION_ID --project --agent codex
122
+ sprag harness prune --global --older-than 6 --dry-run --agent codex
123
+ ```
124
+
125
+ 규칙은 `<프로젝트>/.codex/ratchet.md`나 `$CODEX_HOME/ratchet.md`에 저장합니다.
126
+ 반복 실패 탐지는 최근 2 MiB, 도구가 실행된 30턴, 30분 범위를 확인합니다. 검색 결과가 없다는
127
+ 종료 코드 등은 제외합니다. 숫자로 후보를 등록하면 원인과 예방책을 적을 TODO가 생기므로,
128
+ 원인을 확인한 뒤 완전한 조건·행동 규칙으로 다듬어야 합니다. 후보 번호는 세션별 임시 값입니다.
129
+
130
+ ## 문서와 문체
131
+
132
+ ```sh
133
+ sprag doc2md install-converter --agent codex
134
+ sprag doc2md report.pdf --agent codex
135
+ sprag korean on --agent codex
136
+ sprag korean lint warn --agent codex
137
+ sprag cohesion on --agent codex
138
+ ```
139
+
140
+ 문서 변환기는 Claude와 공유하지만 Codex는 별도 Markdown 뷰와 문서 누계를 사용합니다.
141
+ 변환 캐시를 고쳐도 원본에는 반영되지 않습니다. 문서를 수정하려면 원본을 복사하고 형식을
142
+ 지원하는 라이브러리로 복사본을 편집한 뒤 다시 변환하십시오. 텍스트 추출만으로 차트·이미지·배치를
143
+ 검증했다고 말할 수 없습니다. 실패와 잘린 결과는 그대로 보고해야 합니다.
144
+
145
+ 훅은 지원되는 파일 읽기와 단순한 `cat`·`head`·`tail`, 인식 가능한 쓰기 대상을 처리합니다.
146
+ 임의의 추출 스크립트나 복합 셸 명령을 모두 가로채지는 않습니다. `doc2md off`는 Codex의 자동
147
+ 변환과 문서 쓰기 보호를 끕니다. 문체 설정은 공유하지만 기능을 켤 때는 Codex 훅을 먼저 등록합니다.
148
+
149
+ ## 단가와 한계
150
+
151
+ ```sh
152
+ sprag route-scan savings --refresh --agent codex
153
+ sprag cache status --refresh --agent codex
154
+ sprag capabilities --agent codex
155
+ ```
156
+
157
+ 위임 원장은 Sprag가 실행 시 기록한 route ID와 하위 세션 기록이 일치할 때만 집계합니다.
158
+ 하위 실행의 토큰을 고정하고 부모·자식 단가를 각각 적용한 차이이므로, 실제 청구서의 절감액은
159
+ 아닙니다. 손해는 음수로 남기며 단가나 사용량이 없으면 0 대신 미상으로 표시합니다.
160
+
161
+ 단가는 설정된 LiteLLM의 `/model/info`에서 읽습니다. 공급자가 다른 동명 모델을 섞지 않고,
162
+ 같은 별칭 아래 배포 단가가 다르면 계산하지 않습니다. 단가 캐시는 7일 동안 사용하며 이미
163
+ 계산한 실행은 단가 사본을 보존합니다. 직접 OpenAI 연결이나 구독 요금은 추정하지 않습니다.
164
+ 위임 후 `--refresh`로 원장을 갱신할 수 있고, 아직 일치하지 않은 기록은 7일 동안 찾습니다.
165
+
166
+ 캐시 타이머는 확인된 공급자 정책과 기록된 캐시 사용 시각을 보여 줍니다. 정확한 만료 시각이나
167
+ 다음 요청의 적중을 보장하지 않습니다. 패널의 LiteLLM 예산·단가 조회는 기존 인증 정보를
168
+ 사용하며, 원본 세션 내용을 전송하지 않습니다. 일회성 상태줄은 저장된 정보만 읽습니다.
169
+
170
+ ## 제거와 업그레이드
171
+
172
+ ```sh
173
+ sprag uninstall --agent codex
174
+ npm i -g sprag-cli --ignore-scripts
175
+ sprag install --agent codex
176
+ ```
177
+
178
+ 제거는 Codex 훅과 전역 하네스 블록만 대상으로 합니다. 프로젝트 하네스, 래칫 규칙, 백업과
179
+ 공유 상태는 남습니다. 일반 npm 설치는 Claude postinstall을 실행하므로 Codex만 사용한다면
180
+ 업그레이드에도 `--ignore-scripts`를 유지하십시오.
181
+
182
+ 공식 인터페이스는 [Codex 훅](https://developers.openai.com/codex/hooks/)과
183
+ [AGENTS.md 안내](https://developers.openai.com/codex/guides/agents-md/),
184
+ [설정 참조](https://developers.openai.com/codex/config-reference/)에서 확인할 수 있습니다.