@rex_koh/subagent-budget-guard 0.1.5 → 0.1.7
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/.claude-plugin/plugin.json +2 -54
- package/README.md +2 -2
- package/lib/guard.js +38 -6
- package/lib/verifier.js +6 -9
- package/package.json +1 -1
- package/skills/setup/SKILL.md +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "subagent-budget-guard",
|
|
3
3
|
"displayName": "Subagent Budget Guard",
|
|
4
4
|
"description": "Hard-deny subagent launches, record verified subagent usage, and enforce a session budget against Claude Code's 5-hour rate-limit percentage.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.7",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ClaudeSubAgentSuppressor"
|
|
8
8
|
},
|
|
@@ -13,57 +13,5 @@
|
|
|
13
13
|
"rate-limit",
|
|
14
14
|
"tokens",
|
|
15
15
|
"hooks"
|
|
16
|
-
]
|
|
17
|
-
"userConfig": {
|
|
18
|
-
"max_concurrent_subagents": {
|
|
19
|
-
"type": "number",
|
|
20
|
-
"title": "Max concurrent subagents",
|
|
21
|
-
"description": "Maximum simultaneously active subagents. The default 0 blocks all new subagents.",
|
|
22
|
-
"default": 0,
|
|
23
|
-
"min": 0,
|
|
24
|
-
"required": false
|
|
25
|
-
},
|
|
26
|
-
"max_subagent_tokens_per_session": {
|
|
27
|
-
"type": "number",
|
|
28
|
-
"title": "Max verified subagent tokens per session",
|
|
29
|
-
"description": "Maximum verified subagent tokens allowed in one session. Set 0 for no token cap.",
|
|
30
|
-
"default": 0,
|
|
31
|
-
"min": 0,
|
|
32
|
-
"required": false
|
|
33
|
-
},
|
|
34
|
-
"subagent_token_warning_threshold_percent": {
|
|
35
|
-
"type": "number",
|
|
36
|
-
"title": "Subagent token warning threshold percent",
|
|
37
|
-
"description": "Warn Claude to stop using subagents once verified subagent token usage reaches this percentage of max_subagent_tokens_per_session.",
|
|
38
|
-
"default": 95,
|
|
39
|
-
"min": 1,
|
|
40
|
-
"max": 100,
|
|
41
|
-
"required": false
|
|
42
|
-
},
|
|
43
|
-
"session_five_hour_budget_percent": {
|
|
44
|
-
"type": "number",
|
|
45
|
-
"title": "Session 5-hour budget percent",
|
|
46
|
-
"description": "Maximum percentage points of the current 5-hour Claude Code limit this session may consume after the statusLine bridge records its baseline.",
|
|
47
|
-
"default": 25,
|
|
48
|
-
"min": 0,
|
|
49
|
-
"max": 100,
|
|
50
|
-
"required": false
|
|
51
|
-
},
|
|
52
|
-
"absolute_five_hour_ceiling_percent": {
|
|
53
|
-
"type": "number",
|
|
54
|
-
"title": "Absolute 5-hour ceiling percent",
|
|
55
|
-
"description": "Block new prompts once Claude Code reports this absolute 5-hour usage percentage, regardless of this session's baseline.",
|
|
56
|
-
"default": 95,
|
|
57
|
-
"min": 0,
|
|
58
|
-
"max": 100,
|
|
59
|
-
"required": false
|
|
60
|
-
},
|
|
61
|
-
"enforcement_enabled": {
|
|
62
|
-
"type": "boolean",
|
|
63
|
-
"title": "Enable enforcement",
|
|
64
|
-
"description": "When true, hooks deny over-budget subagents and prompts. When false, the plugin records usage without blocking.",
|
|
65
|
-
"default": true,
|
|
66
|
-
"required": false
|
|
67
|
-
}
|
|
68
|
-
}
|
|
16
|
+
]
|
|
69
17
|
}
|
package/README.md
CHANGED
|
@@ -9,12 +9,12 @@ Recommended Claude Code install:
|
|
|
9
9
|
```text
|
|
10
10
|
/plugin marketplace add rexkoh425/ClaudeSubAgentSuppressor
|
|
11
11
|
/plugin install subagent-budget-guard@subagent-budget-tools
|
|
12
|
-
/reload-plugins
|
|
13
12
|
/subagent-budget-guard:setup
|
|
14
|
-
/reload-plugins
|
|
15
13
|
/subagent-budget-guard:verify
|
|
16
14
|
```
|
|
17
15
|
|
|
16
|
+
After `/subagent-budget-guard:setup`, fully exit and reopen Claude Code before verification so the statusLine bridge from `settings.json` is active. Some Claude Code builds do not provide an in-session plugin reload command.
|
|
17
|
+
|
|
18
18
|
Useful after install:
|
|
19
19
|
|
|
20
20
|
```text
|
package/lib/guard.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { constants as fsConstants } from 'node:fs';
|
|
2
|
+
import { constants as fsConstants, readFileSync } from 'node:fs';
|
|
3
3
|
import {
|
|
4
4
|
access,
|
|
5
5
|
mkdir,
|
|
@@ -74,18 +74,40 @@ function envValue(env, key) {
|
|
|
74
74
|
return env[exact] ?? env[upper];
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
const
|
|
77
|
+
function settingsPathForEnv(env) {
|
|
78
|
+
const homeDir = env.USERPROFILE || env.HOME;
|
|
79
|
+
if (!homeDir && env !== process.env) return null;
|
|
80
|
+
return path.join(homeDir || os.homedir(), '.claude', 'settings.json');
|
|
81
|
+
}
|
|
79
82
|
|
|
83
|
+
function readSettingsOptions(env) {
|
|
84
|
+
const settingsPath = settingsPathForEnv(env);
|
|
85
|
+
if (!settingsPath) return {};
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const text = readFileSync(settingsPath, 'utf8');
|
|
89
|
+
const settings = JSON.parse(text.replace(/^\uFEFF/, ''));
|
|
90
|
+
const options = settings?.pluginConfigs?.[PLUGIN_ID]?.options;
|
|
91
|
+
return isPlainObject(options) ? options : {};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error.code === 'ENOENT') return {};
|
|
94
|
+
if (error instanceof SyntaxError) return {};
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function applyConfigValues(config, valueForKey) {
|
|
80
100
|
for (const key of CONFIG_KEYS) {
|
|
81
|
-
const value =
|
|
101
|
+
const value = valueForKey(key);
|
|
82
102
|
if (NUMBER_KEYS.has(key)) {
|
|
83
|
-
config[key] = Math.max(0, asNumber(value,
|
|
103
|
+
config[key] = Math.max(0, asNumber(value, config[key]));
|
|
84
104
|
} else if (typeof DEFAULT_CONFIG[key] === 'boolean') {
|
|
85
|
-
config[key] = asBoolean(value,
|
|
105
|
+
config[key] = asBoolean(value, config[key]);
|
|
86
106
|
}
|
|
87
107
|
}
|
|
108
|
+
}
|
|
88
109
|
|
|
110
|
+
function normalizeConfig(config) {
|
|
89
111
|
config.session_five_hour_budget_percent = Math.min(
|
|
90
112
|
100,
|
|
91
113
|
config.session_five_hour_budget_percent
|
|
@@ -102,6 +124,16 @@ export function loadConfig(env = process.env) {
|
|
|
102
124
|
return config;
|
|
103
125
|
}
|
|
104
126
|
|
|
127
|
+
export function loadConfig(env = process.env) {
|
|
128
|
+
const config = { ...DEFAULT_CONFIG };
|
|
129
|
+
const settingsOptions = readSettingsOptions(env);
|
|
130
|
+
|
|
131
|
+
applyConfigValues(config, (key) => settingsOptions[key]);
|
|
132
|
+
applyConfigValues(config, (key) => envValue(env, key));
|
|
133
|
+
|
|
134
|
+
return normalizeConfig(config);
|
|
135
|
+
}
|
|
136
|
+
|
|
105
137
|
export function getHomeDir(env = process.env) {
|
|
106
138
|
return env.USERPROFILE || env.HOME || os.homedir();
|
|
107
139
|
}
|
package/lib/verifier.js
CHANGED
|
@@ -90,7 +90,7 @@ export async function runOfflineVerification({
|
|
|
90
90
|
entry.source?.package === '@rex_koh/subagent-budget-guard',
|
|
91
91
|
'marketplace npm package mismatch'
|
|
92
92
|
);
|
|
93
|
-
assert(entry.source?.version === '0.1.
|
|
93
|
+
assert(entry.source?.version === '0.1.7', 'marketplace npm version mismatch');
|
|
94
94
|
return marketplacePath;
|
|
95
95
|
});
|
|
96
96
|
} else {
|
|
@@ -105,7 +105,7 @@ export async function runOfflineVerification({
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
await withCheck(result, 'plugin-manifest-
|
|
108
|
+
await withCheck(result, 'plugin-manifest-no-install-config', async () => {
|
|
109
109
|
const manifestPath = path.join(root, '.claude-plugin', 'plugin.json');
|
|
110
110
|
const manifest = await readJson(manifestPath);
|
|
111
111
|
assert(manifest.name === 'subagent-budget-guard', 'plugin name mismatch');
|
|
@@ -117,13 +117,10 @@ export async function runOfflineVerification({
|
|
|
117
117
|
manifest.skills === undefined,
|
|
118
118
|
'manifest.skills must be omitted for default skills/ scanning to avoid duplicate loading'
|
|
119
119
|
);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`userConfig.${key} must not be required at install time`
|
|
125
|
-
);
|
|
126
|
-
}
|
|
120
|
+
assert(
|
|
121
|
+
manifest.userConfig === undefined,
|
|
122
|
+
'manifest.userConfig must be omitted so installs do not ask for --config flags'
|
|
123
|
+
);
|
|
127
124
|
return manifestPath;
|
|
128
125
|
});
|
|
129
126
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rex_koh/subagent-budget-guard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Claude Code plugin that blocks subagents by default, records verified subagent usage, and enforces 5-hour usage budgets.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ClaudeSubAgentSuppressor",
|
package/skills/setup/SKILL.md
CHANGED
|
@@ -22,7 +22,7 @@ absolute_five_hour_ceiling_percent=95
|
|
|
22
22
|
enforcement_enabled=true
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Then tell the user to
|
|
25
|
+
Then tell the user to fully exit and reopen Claude Code, interact once so the statusLine bridge receives fresh session JSON, and run:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
node "${CLAUDE_PLUGIN_ROOT}/bin/verify.js" --live
|