sprag-cli 3.55.0 → 3.57.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 +5 -1
- package/README.md +5 -1
- package/bin/cli.js +60 -19
- package/bin/postinstall.js +22 -0
- package/docs/CODEX.ko.md +44 -5
- package/docs/CODEX.md +74 -12
- package/package.json +2 -2
- package/src/agents.js +16 -2
- package/src/brief.js +17 -8
- package/src/claude-price.js +49 -0
- package/src/codex-brief.js +55 -2
- package/src/codex-cache-policy.js +26 -41
- package/src/codex-delegation.js +56 -8
- package/src/codex-doctor.js +1 -1
- package/src/codex-harness.js +63 -18
- package/src/codex-hooks.js +129 -20
- package/src/codex-ledger.js +150 -16
- package/src/codex-panel.js +51 -26
- package/src/codex-parser.js +41 -4
- package/src/codex-route-scan.js +14 -7
- package/src/codex-rule-health.js +41 -0
- package/src/commands/brief.js +14 -3
- package/src/commands/codex-delegate.js +17 -5
- package/src/commands/codex.js +20 -10
- package/src/commands/harness.js +6 -0
- package/src/commands/route-scan.js +4 -3
- package/src/compact-window.js +12 -4
- package/src/config.js +3 -3
- package/src/cost.js +62 -5
- package/src/ctx-window-cache.js +61 -0
- package/src/delegation-guard.js +15 -9
- package/src/doc2md.cjs +22 -8
- package/src/fig2md.cjs +7 -1
- package/src/formatters/statusline.js +22 -11
- package/src/formatters/table.js +19 -10
- package/src/gateway-auth.js +31 -3
- package/src/gateway-prices.js +39 -0
- package/src/harness.js +43 -12
- package/src/history.js +6 -2
- package/src/hook-manager.js +54 -32
- package/src/installer.js +286 -153
- package/src/korean-lint.cjs +40 -7
- package/src/litellm-budget.js +10 -1
- package/src/litellm-models.js +72 -10
- package/src/model-rules.js +90 -42
- package/src/money.js +9 -0
- package/src/month-spend.js +23 -12
- package/src/openai-prices.js +33 -0
- package/src/parser.js +232 -56
- package/src/postinstall.js +32 -0
- package/src/route-caps.js +74 -0
- package/src/route-inject.js +31 -7
- package/src/route-scan.js +93 -33
- package/src/savings-ledger.js +43 -11
- package/src/seed-rules.js +3 -4
- package/src/session-cache.js +35 -1
- package/src/state-file.js +43 -0
- package/src/stats.js +20 -5
- package/src/stdin-payload.js +18 -4
package/src/hook-manager.js
CHANGED
|
@@ -9,21 +9,26 @@
|
|
|
9
9
|
* removes the copy, the staleness, and the orphan in one move.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { rm } from 'node:fs/promises';
|
|
13
|
+
import { mkdirSync } from 'node:fs';
|
|
13
14
|
import { join } from 'node:path';
|
|
14
|
-
import { homedir } from 'node:os';
|
|
15
15
|
import { CLI_NAME } from './cli-name.js';
|
|
16
|
+
import { claudeUserDir } from './paths.js';
|
|
17
|
+
import { isOurSubcommand, isLegacyCacheMonitorCommand, readSettings, writeSettings } from './installer.js';
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
// Resolved per call, not at import: the paths follow the current home directory.
|
|
20
|
+
const settingsPath = () => join(claudeUserDir(), 'settings.json');
|
|
18
21
|
// Legacy copied-file location — removed on install/uninstall so machines that
|
|
19
22
|
// installed an older version don't keep a dead hook script around.
|
|
20
|
-
const
|
|
21
|
-
const HOOK_MARKER = 'cache-monitor-hook';
|
|
23
|
+
const legacyHookDest = () => join(claudeUserDir(), 'cache-monitor-hook.cjs');
|
|
22
24
|
|
|
25
|
+
// Ours means the current subcommand form (`<cli> --hook-run ...`) or the exact
|
|
26
|
+
// legacy copied-file form. A command that only mentions either string, such as
|
|
27
|
+
// `echo "sprag --hook-run"`, belongs to somebody else.
|
|
23
28
|
function isCacheMonitorHook(nh) {
|
|
24
29
|
const cmd = nh?.command;
|
|
25
30
|
if (typeof cmd !== 'string') return false;
|
|
26
|
-
return cmd
|
|
31
|
+
return isOurSubcommand(cmd, '--hook-run') || isLegacyCacheMonitorCommand(cmd);
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
/**
|
|
@@ -51,21 +56,31 @@ function countCacheMonitorHooks(groups) {
|
|
|
51
56
|
return n;
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
function fail(message) {
|
|
60
|
+
console.log(message);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
54
65
|
export async function installHook({ threshold = 0.7 } = {}) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
const SETTINGS_PATH = settingsPath();
|
|
67
|
+
// Only a missing file starts from an empty object. A file that exists but
|
|
68
|
+
// cannot be parsed (or is not an object) is the user's configuration;
|
|
69
|
+
// replacing it with a fresh object would wipe everything in it.
|
|
70
|
+
const read = readSettings(SETTINGS_PATH);
|
|
71
|
+
if (read.state === 'unusable') {
|
|
72
|
+
return fail(`✗ ${SETTINGS_PATH} is ${read.reason} — hook not installed, file left untouched.`);
|
|
61
73
|
}
|
|
74
|
+
const settings = read.settings;
|
|
62
75
|
|
|
63
|
-
if (
|
|
76
|
+
if (settings.hooks === undefined || settings.hooks === null) settings.hooks = {};
|
|
77
|
+
if (typeof settings.hooks !== 'object' || Array.isArray(settings.hooks)) {
|
|
78
|
+
return fail(`✗ hooks in ${SETTINGS_PATH} is not an object — hook not installed, file left untouched.`);
|
|
79
|
+
}
|
|
64
80
|
if (settings.hooks.PostToolUse !== undefined && !Array.isArray(settings.hooks.PostToolUse)) {
|
|
65
81
|
// Someone else's data in a shape we do not understand; overwriting it with
|
|
66
82
|
// [] would discard it. Leave the file alone and say so.
|
|
67
|
-
|
|
68
|
-
return;
|
|
83
|
+
return fail(`✗ hooks.PostToolUse in ${SETTINGS_PATH} is not an array — hook not installed, file left untouched.`);
|
|
69
84
|
}
|
|
70
85
|
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
71
86
|
|
|
@@ -84,10 +99,12 @@ export async function installHook({ threshold = 0.7 } = {}) {
|
|
|
84
99
|
],
|
|
85
100
|
});
|
|
86
101
|
|
|
87
|
-
|
|
102
|
+
mkdirSync(claudeUserDir(), { recursive: true });
|
|
103
|
+
const writeProblem = writeSettings(SETTINGS_PATH, settings);
|
|
104
|
+
if (writeProblem) return fail(`✗ Hook not installed: ${writeProblem}`);
|
|
88
105
|
|
|
89
106
|
// Clean up the legacy copy left by older versions.
|
|
90
|
-
await rm(
|
|
107
|
+
await rm(legacyHookDest(), { force: true }).catch(() => {});
|
|
91
108
|
|
|
92
109
|
console.log(`✓ Hook installed (PostToolUse → ${CLI_NAME} --hook-run)`);
|
|
93
110
|
console.log(` Settings updated: ${SETTINGS_PATH}`);
|
|
@@ -96,29 +113,34 @@ export async function installHook({ threshold = 0.7 } = {}) {
|
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
export async function uninstallHook() {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
settings = JSON.parse(raw);
|
|
103
|
-
} catch {
|
|
116
|
+
const SETTINGS_PATH = settingsPath();
|
|
117
|
+
const read = readSettings(SETTINGS_PATH);
|
|
118
|
+
if (read.state === 'absent') {
|
|
104
119
|
console.log('No settings.json found, nothing to uninstall.');
|
|
105
120
|
return;
|
|
106
121
|
}
|
|
122
|
+
if (read.state === 'unusable') {
|
|
123
|
+
return fail(`✗ ${SETTINGS_PATH} is ${read.reason} — nothing removed, file left untouched.`);
|
|
124
|
+
}
|
|
125
|
+
const settings = read.settings;
|
|
107
126
|
|
|
108
127
|
if (Array.isArray(settings.hooks?.PostToolUse)) {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
const removed = countCacheMonitorHooks(settings.hooks.PostToolUse);
|
|
129
|
+
if (removed > 0) {
|
|
130
|
+
settings.hooks.PostToolUse = withoutCacheMonitorHooks(settings.hooks.PostToolUse);
|
|
131
|
+
if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;
|
|
132
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
133
|
+
|
|
134
|
+
const writeProblem = writeSettings(SETTINGS_PATH, settings);
|
|
135
|
+
if (writeProblem) return fail(`✗ Hook not removed: ${writeProblem}`);
|
|
136
|
+
console.log(`✓ Removed ${removed} hook(s) from settings.json`);
|
|
137
|
+
} else {
|
|
138
|
+
console.log('No cache-monitor hook found in settings.');
|
|
139
|
+
}
|
|
118
140
|
} else {
|
|
119
141
|
console.log('No cache-monitor hook found in settings.');
|
|
120
142
|
}
|
|
121
143
|
|
|
122
144
|
// The legacy copied hook file is dead weight either way.
|
|
123
|
-
await rm(
|
|
145
|
+
await rm(legacyHookDest(), { force: true }).catch(() => {});
|
|
124
146
|
}
|