@rex_koh/subagent-budget-guard 0.1.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.
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "subagent-budget-guard",
3
+ "displayName": "Subagent Budget Guard",
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.0",
6
+ "author": {
7
+ "name": "ClaudeSubAgentSuppressor"
8
+ },
9
+ "license": "MIT",
10
+ "keywords": [
11
+ "subagents",
12
+ "budget",
13
+ "rate-limit",
14
+ "tokens",
15
+ "hooks"
16
+ ],
17
+ "userConfig": {
18
+ "max_subagents_per_session": {
19
+ "type": "number",
20
+ "title": "Max subagents per session",
21
+ "description": "Maximum normal Agent tool subagents allowed in one Claude Code session. The default 0 blocks all new subagents.",
22
+ "default": 0,
23
+ "min": 0,
24
+ "required": true
25
+ },
26
+ "max_concurrent_subagents": {
27
+ "type": "number",
28
+ "title": "Max concurrent subagents",
29
+ "description": "Maximum simultaneously active subagents. The default 0 blocks all new subagents.",
30
+ "default": 0,
31
+ "min": 0,
32
+ "required": true
33
+ },
34
+ "max_agent_team_tasks_per_session": {
35
+ "type": "number",
36
+ "title": "Max agent-team tasks per session",
37
+ "description": "Maximum agent-team tasks that may be created in one session. The default 0 suppresses agent-team task creation.",
38
+ "default": 0,
39
+ "min": 0,
40
+ "required": true
41
+ },
42
+ "max_subagent_tokens_per_session": {
43
+ "type": "number",
44
+ "title": "Max verified subagent tokens per session",
45
+ "description": "Maximum verified subagent tokens allowed in one session. Set 0 for no token cap.",
46
+ "default": 0,
47
+ "min": 0,
48
+ "required": true
49
+ },
50
+ "session_five_hour_budget_percent": {
51
+ "type": "number",
52
+ "title": "Session 5-hour budget percent",
53
+ "description": "Maximum percentage points of the current 5-hour Claude Code limit this session may consume after the statusLine bridge records its baseline.",
54
+ "default": 25,
55
+ "min": 0,
56
+ "max": 100,
57
+ "required": true
58
+ },
59
+ "absolute_five_hour_ceiling_percent": {
60
+ "type": "number",
61
+ "title": "Absolute 5-hour ceiling percent",
62
+ "description": "Block new prompts once Claude Code reports this absolute 5-hour usage percentage, regardless of this session's baseline.",
63
+ "default": 95,
64
+ "min": 0,
65
+ "max": 100,
66
+ "required": true
67
+ },
68
+ "enforcement_enabled": {
69
+ "type": "boolean",
70
+ "title": "Enable enforcement",
71
+ "description": "When true, hooks deny over-budget subagents, agent-team tasks, and prompts. When false, the plugin records usage without blocking.",
72
+ "default": true,
73
+ "required": true
74
+ }
75
+ }
76
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ClaudeSubAgentSuppressor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Subagent Budget Guard
2
+
3
+ Claude Code plugin that blocks subagents by default, records verified subagent usage, and enforces a session budget against Claude Code's 5-hour usage percentage.
4
+
5
+ ## Install
6
+
7
+ Recommended Claude Code install:
8
+
9
+ ```text
10
+ /plugin marketplace add rexkoh425/ClaudeSubAgentSuppressor
11
+ /plugin install subagent-budget-guard@subagent-budget-tools
12
+ /reload-plugins
13
+ ```
14
+
15
+ Run after install:
16
+
17
+ ```text
18
+ /subagent-budget-guard:setup
19
+ /subagent-budget-guard:verify
20
+ /subagent-budget-guard:report
21
+ ```
22
+
23
+ ## NPM Package
24
+
25
+ This package is npm-ready as `@rex_koh/subagent-budget-guard`.
26
+
27
+ Claude Code plugin discovery is marketplace-based, so npm is mainly useful as a plugin source in a marketplace entry or for installing the helper CLIs:
28
+
29
+ ```bash
30
+ npm install -g @rex_koh/subagent-budget-guard
31
+ subagent-budget-guard-verify --offline
32
+ ```
33
+
34
+ Maintainer publish command:
35
+
36
+ ```bash
37
+ npm publish --access public
38
+ ```
39
+
40
+ Offline verification:
41
+
42
+ ```bash
43
+ node bin/verify.js --offline
44
+ ```
45
+
46
+ The plugin is strict by default: `max_subagents_per_session`, `max_concurrent_subagents`, and `max_agent_team_tasks_per_session` all default to `0`.
package/bin/hook.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ handlePostToolUseAgent,
4
+ handlePreToolUseAgent,
5
+ handleSubagentStart,
6
+ handleSubagentStop,
7
+ handleTaskCompleted,
8
+ handleTaskCreated,
9
+ handleUserPromptSubmit
10
+ } from '../lib/guard.js';
11
+
12
+ async function readStdin() {
13
+ let input = '';
14
+ process.stdin.setEncoding('utf8');
15
+ for await (const chunk of process.stdin) {
16
+ input += chunk;
17
+ }
18
+ return input ? JSON.parse(input) : {};
19
+ }
20
+
21
+ function emit(result) {
22
+ if (result.stdout) {
23
+ process.stdout.write(`${JSON.stringify(result.stdout)}\n`);
24
+ }
25
+ if (result.stderr) {
26
+ process.stderr.write(`${result.stderr}\n`);
27
+ }
28
+ process.exitCode = result.exitCode ?? 0;
29
+ }
30
+
31
+ const handlers = {
32
+ 'pretool-agent': handlePreToolUseAgent,
33
+ 'posttool-agent': handlePostToolUseAgent,
34
+ 'subagent-start': handleSubagentStart,
35
+ 'subagent-stop': handleSubagentStop,
36
+ 'task-created': handleTaskCreated,
37
+ 'task-completed': handleTaskCompleted,
38
+ 'user-prompt-submit': handleUserPromptSubmit
39
+ };
40
+
41
+ async function main() {
42
+ const action = process.argv[2];
43
+ const handler = handlers[action];
44
+
45
+ if (!handler) {
46
+ throw new Error(`Unknown hook action: ${action || '(missing)'}`);
47
+ }
48
+
49
+ const input = await readStdin();
50
+ emit(await handler(input, process.env));
51
+ }
52
+
53
+ main().catch((error) => {
54
+ process.stderr.write(`subagent-budget-guard hook error: ${error.stack || error.message}\n`);
55
+ process.exitCode = 1;
56
+ });
package/bin/report.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { buildReport, formatReport } from '../lib/guard.js';
3
+
4
+ function argValue(name) {
5
+ const index = process.argv.indexOf(name);
6
+ if (index === -1) return null;
7
+ return process.argv[index + 1] || null;
8
+ }
9
+
10
+ async function main() {
11
+ const asJson = process.argv.includes('--json');
12
+ const sessionId = argValue('--session');
13
+ const report = await buildReport(sessionId, process.env);
14
+
15
+ process.stdout.write(
16
+ asJson ? `${JSON.stringify(report, null, 2)}\n` : `${formatReport(report)}\n`
17
+ );
18
+ }
19
+
20
+ main().catch((error) => {
21
+ process.stderr.write(`report failed: ${error.stack || error.message}\n`);
22
+ process.exitCode = 1;
23
+ });
package/bin/setup.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { getDataDir, getHomeDir, getPluginRoot, installStatusLineBridge } from '../lib/guard.js';
3
+
4
+ async function main() {
5
+ const result = await installStatusLineBridge({
6
+ homeDir: getHomeDir(process.env),
7
+ pluginRoot: getPluginRoot(process.env),
8
+ pluginData: getDataDir(process.env)
9
+ });
10
+
11
+ process.stdout.write(
12
+ [
13
+ 'Subagent Budget Guard statusLine bridge installed.',
14
+ `Settings: ${result.settingsPath}`,
15
+ `Bridge state: ${result.bridgePath}`,
16
+ result.previousStatusLine
17
+ ? 'Existing statusLine command preserved and wrapped.'
18
+ : 'No previous statusLine command was configured.'
19
+ ].join('\n') + '\n'
20
+ );
21
+ }
22
+
23
+ main().catch((error) => {
24
+ process.stderr.write(`setup failed: ${error.stack || error.message}\n`);
25
+ process.exitCode = 1;
26
+ });
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { renderStatusLine } from '../lib/guard.js';
3
+
4
+ function argValue(name) {
5
+ const index = process.argv.indexOf(name);
6
+ if (index === -1) return null;
7
+ return process.argv[index + 1] || null;
8
+ }
9
+
10
+ async function readStdin() {
11
+ let input = '';
12
+ process.stdin.setEncoding('utf8');
13
+ for await (const chunk of process.stdin) {
14
+ input += chunk;
15
+ }
16
+ return input ? JSON.parse(input) : {};
17
+ }
18
+
19
+ async function main() {
20
+ const pluginData = argValue('--data') || process.env.CLAUDE_PLUGIN_DATA;
21
+ const input = await readStdin();
22
+ const output = await renderStatusLine(input, { pluginData, env: process.env });
23
+ process.stdout.write(`${output}\n`);
24
+ }
25
+
26
+ main().catch((error) => {
27
+ process.stdout.write(`SBG error: ${error.message}\n`);
28
+ });
package/bin/verify.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ formatVerificationResult,
4
+ runLiveVerification,
5
+ runOfflineVerification
6
+ } from '../lib/verifier.js';
7
+
8
+ function modeFromArgs() {
9
+ if (process.argv.includes('--live')) return 'live';
10
+ return 'offline';
11
+ }
12
+
13
+ async function main() {
14
+ const mode = modeFromArgs();
15
+ const result =
16
+ mode === 'live'
17
+ ? await runLiveVerification({ repoRoot: process.cwd(), env: process.env })
18
+ : await runOfflineVerification({ repoRoot: process.cwd(), env: process.env });
19
+
20
+ process.stdout.write(`${formatVerificationResult(result)}\n`);
21
+ process.exitCode = result.ok ? 0 : 1;
22
+ }
23
+
24
+ main().catch((error) => {
25
+ process.stderr.write(`verification failed: ${error.stack || error.message}\n`);
26
+ process.exitCode = 1;
27
+ });
@@ -0,0 +1,120 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Agent",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node",
10
+ "args": [
11
+ "${CLAUDE_PLUGIN_ROOT}/bin/hook.js",
12
+ "pretool-agent"
13
+ ],
14
+ "timeout": 10,
15
+ "statusMessage": "Checking subagent budget"
16
+ }
17
+ ]
18
+ }
19
+ ],
20
+ "PostToolUse": [
21
+ {
22
+ "matcher": "Agent",
23
+ "hooks": [
24
+ {
25
+ "type": "command",
26
+ "command": "node",
27
+ "args": [
28
+ "${CLAUDE_PLUGIN_ROOT}/bin/hook.js",
29
+ "posttool-agent"
30
+ ],
31
+ "timeout": 10,
32
+ "statusMessage": "Recording subagent usage"
33
+ }
34
+ ]
35
+ }
36
+ ],
37
+ "SubagentStart": [
38
+ {
39
+ "matcher": "",
40
+ "hooks": [
41
+ {
42
+ "type": "command",
43
+ "command": "node",
44
+ "args": [
45
+ "${CLAUDE_PLUGIN_ROOT}/bin/hook.js",
46
+ "subagent-start"
47
+ ],
48
+ "timeout": 10,
49
+ "statusMessage": "Recording subagent start"
50
+ }
51
+ ]
52
+ }
53
+ ],
54
+ "SubagentStop": [
55
+ {
56
+ "matcher": "",
57
+ "hooks": [
58
+ {
59
+ "type": "command",
60
+ "command": "node",
61
+ "args": [
62
+ "${CLAUDE_PLUGIN_ROOT}/bin/hook.js",
63
+ "subagent-stop"
64
+ ],
65
+ "timeout": 10,
66
+ "statusMessage": "Recording subagent stop"
67
+ }
68
+ ]
69
+ }
70
+ ],
71
+ "TaskCreated": [
72
+ {
73
+ "hooks": [
74
+ {
75
+ "type": "command",
76
+ "command": "node",
77
+ "args": [
78
+ "${CLAUDE_PLUGIN_ROOT}/bin/hook.js",
79
+ "task-created"
80
+ ],
81
+ "timeout": 10,
82
+ "statusMessage": "Checking agent-team budget"
83
+ }
84
+ ]
85
+ }
86
+ ],
87
+ "TaskCompleted": [
88
+ {
89
+ "hooks": [
90
+ {
91
+ "type": "command",
92
+ "command": "node",
93
+ "args": [
94
+ "${CLAUDE_PLUGIN_ROOT}/bin/hook.js",
95
+ "task-completed"
96
+ ],
97
+ "timeout": 10,
98
+ "statusMessage": "Recording agent-team completion"
99
+ }
100
+ ]
101
+ }
102
+ ],
103
+ "UserPromptSubmit": [
104
+ {
105
+ "hooks": [
106
+ {
107
+ "type": "command",
108
+ "command": "node",
109
+ "args": [
110
+ "${CLAUDE_PLUGIN_ROOT}/bin/hook.js",
111
+ "user-prompt-submit"
112
+ ],
113
+ "timeout": 10,
114
+ "statusMessage": "Checking 5-hour budget"
115
+ }
116
+ ]
117
+ }
118
+ ]
119
+ }
120
+ }