slaunt 0.1.17 → 0.1.18

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 (168) hide show
  1. package/README.md +17 -122
  2. package/bin/slaunt-hooks.js +96 -0
  3. package/package.json +9 -59
  4. package/release/SlauntKillBar.zip +0 -0
  5. package/release/manifest.json +7 -0
  6. package/scripts/killbar-portability-check.js +67 -0
  7. package/scripts/package-killbar-macos.sh +38 -0
  8. package/src/approval-manager.js +53 -0
  9. package/src/cli-auth.js +230 -0
  10. package/src/compact-wordmark.js +13 -0
  11. package/src/desktop/broker-daemon.js +18 -0
  12. package/src/desktop/broker-grants.js +37 -0
  13. package/src/desktop/broker-hook-command.js +23 -0
  14. package/src/desktop/broker-keychain.js +29 -0
  15. package/src/desktop/broker-launch-agent.js +34 -0
  16. package/src/desktop/broker-service.js +40 -0
  17. package/src/desktop/chat-capture-command.js +7 -0
  18. package/src/desktop/chat-hook-capture.js +445 -0
  19. package/src/desktop/claude-lifecycle.js +143 -0
  20. package/src/desktop/client-identity.js +19 -0
  21. package/src/desktop/native-claude-broker.js +34 -0
  22. package/src/desktop/native-claude-connection.js +42 -0
  23. package/src/desktop/tool-guard-command.js +13 -0
  24. package/src/desktop/tool-guard.js +225 -0
  25. package/src/discovery/catalog.js +734 -0
  26. package/src/full-screen.js +88 -0
  27. package/src/helper-release.js +27 -0
  28. package/src/hidden-oauth.js +70 -0
  29. package/src/independent-grants.js +38 -0
  30. package/src/install-flow.js +71 -0
  31. package/src/killbar-background-install.js +47 -0
  32. package/src/killbar-enrollment.js +74 -0
  33. package/src/killbar-launch-agent.js +10 -0
  34. package/src/mcp-identity.js +6 -0
  35. package/src/native-config.js +150 -0
  36. package/src/native-oauth.js +33 -0
  37. package/src/oauth-cohort.js +43 -0
  38. package/src/pty-wrapper.py +57 -0
  39. package/src/read-json.js +8 -0
  40. package/src/setup/client-installer.js +93 -0
  41. package/src/setup/codex-guard-supervisor.js +34 -0
  42. package/src/setup/hook-installer.js +496 -0
  43. package/src/setup/managed-policy.js +26 -0
  44. package/src/setup/mcp-config-installer.js +204 -0
  45. package/src/setup-flow.js +66 -0
  46. package/src/tui.js +205 -0
  47. package/test/approval-manager.test.js +27 -0
  48. package/test/broker-grants.test.js +14 -0
  49. package/test/broker-keychain.test.js +14 -0
  50. package/test/broker-launch-agent.test.js +15 -0
  51. package/test/broker-service.test.js +42 -0
  52. package/test/cli-auth-recovery.test.js +15 -0
  53. package/test/codex-guard-supervisor.test.js +25 -0
  54. package/test/full-screen.test.js +49 -0
  55. package/test/helper-release.test.js +22 -0
  56. package/test/hidden-oauth.test.js +44 -0
  57. package/test/hook-installer.test.js +232 -0
  58. package/test/independent-grants.test.js +16 -0
  59. package/test/install-flow.test.js +24 -0
  60. package/test/killbar-background-install.test.js +15 -0
  61. package/test/killbar-enrollment.test.js +115 -0
  62. package/test/killbar-launch-agent.test.js +12 -0
  63. package/test/killbar-portability.test.js +17 -0
  64. package/test/managed-policy.test.js +18 -0
  65. package/test/native-claude-broker.test.js +19 -0
  66. package/test/native-claude-connection.test.js +17 -0
  67. package/test/native-claude-hooks.test.js +9 -0
  68. package/test/native-config.test.js +3 -0
  69. package/test/native-integration.cjs +29 -0
  70. package/test/native-oauth.test.js +23 -0
  71. package/test/oauth-cohort.test.js +26 -0
  72. package/test/tui.test.js +100 -0
  73. package/LICENSE +0 -201
  74. package/dist/audit/risk-engine.d.ts +0 -4
  75. package/dist/audit/risk-engine.d.ts.map +0 -1
  76. package/dist/audit/risk-engine.js +0 -174
  77. package/dist/audit/risk-engine.js.map +0 -1
  78. package/dist/audit/run.d.ts +0 -18
  79. package/dist/audit/run.d.ts.map +0 -1
  80. package/dist/audit/run.js +0 -68
  81. package/dist/audit/run.js.map +0 -1
  82. package/dist/classification/builtin-catalog.d.ts +0 -3
  83. package/dist/classification/builtin-catalog.d.ts.map +0 -1
  84. package/dist/classification/builtin-catalog.js +0 -283
  85. package/dist/classification/builtin-catalog.js.map +0 -1
  86. package/dist/classification/catalog.d.ts +0 -10
  87. package/dist/classification/catalog.d.ts.map +0 -1
  88. package/dist/classification/catalog.js +0 -194
  89. package/dist/classification/catalog.js.map +0 -1
  90. package/dist/classification/classifier.d.ts +0 -3
  91. package/dist/classification/classifier.d.ts.map +0 -1
  92. package/dist/classification/classifier.js +0 -64
  93. package/dist/classification/classifier.js.map +0 -1
  94. package/dist/classification/public-catalog-config.d.ts +0 -3
  95. package/dist/classification/public-catalog-config.d.ts.map +0 -1
  96. package/dist/classification/public-catalog-config.js +0 -5
  97. package/dist/classification/public-catalog-config.js.map +0 -1
  98. package/dist/cli.d.ts +0 -3
  99. package/dist/cli.d.ts.map +0 -1
  100. package/dist/cli.js +0 -239
  101. package/dist/cli.js.map +0 -1
  102. package/dist/config/discover.d.ts +0 -8
  103. package/dist/config/discover.d.ts.map +0 -1
  104. package/dist/config/discover.js +0 -163
  105. package/dist/config/discover.js.map +0 -1
  106. package/dist/config/install.d.ts +0 -10
  107. package/dist/config/install.d.ts.map +0 -1
  108. package/dist/config/install.js +0 -143
  109. package/dist/config/install.js.map +0 -1
  110. package/dist/config/paths.d.ts +0 -24
  111. package/dist/config/paths.d.ts.map +0 -1
  112. package/dist/config/paths.js +0 -90
  113. package/dist/config/paths.js.map +0 -1
  114. package/dist/demo.d.ts +0 -4
  115. package/dist/demo.d.ts.map +0 -1
  116. package/dist/demo.js +0 -99
  117. package/dist/demo.js.map +0 -1
  118. package/dist/explanation/local.d.ts +0 -24
  119. package/dist/explanation/local.d.ts.map +0 -1
  120. package/dist/explanation/local.js +0 -696
  121. package/dist/explanation/local.js.map +0 -1
  122. package/dist/mcp/introspect.d.ts +0 -18
  123. package/dist/mcp/introspect.d.ts.map +0 -1
  124. package/dist/mcp/introspect.js +0 -236
  125. package/dist/mcp/introspect.js.map +0 -1
  126. package/dist/mcp/oauth.d.ts +0 -14
  127. package/dist/mcp/oauth.d.ts.map +0 -1
  128. package/dist/mcp/oauth.js +0 -257
  129. package/dist/mcp/oauth.js.map +0 -1
  130. package/dist/presentation/plain-language.d.ts +0 -12
  131. package/dist/presentation/plain-language.d.ts.map +0 -1
  132. package/dist/presentation/plain-language.js +0 -120
  133. package/dist/presentation/plain-language.js.map +0 -1
  134. package/dist/privacy/redaction.d.ts +0 -2
  135. package/dist/privacy/redaction.d.ts.map +0 -1
  136. package/dist/privacy/redaction.js +0 -50
  137. package/dist/privacy/redaction.js.map +0 -1
  138. package/dist/report/html.d.ts +0 -4
  139. package/dist/report/html.d.ts.map +0 -1
  140. package/dist/report/html.js +0 -101
  141. package/dist/report/html.js.map +0 -1
  142. package/dist/tui/analysis-path.d.ts +0 -17
  143. package/dist/tui/analysis-path.d.ts.map +0 -1
  144. package/dist/tui/analysis-path.js +0 -112
  145. package/dist/tui/analysis-path.js.map +0 -1
  146. package/dist/tui/render.d.ts +0 -4
  147. package/dist/tui/render.d.ts.map +0 -1
  148. package/dist/tui/render.js +0 -262
  149. package/dist/tui/render.js.map +0 -1
  150. package/dist/tui/splash.d.ts +0 -15
  151. package/dist/tui/splash.d.ts.map +0 -1
  152. package/dist/tui/splash.js +0 -130
  153. package/dist/tui/splash.js.map +0 -1
  154. package/dist/tui/stream.d.ts +0 -6
  155. package/dist/tui/stream.d.ts.map +0 -1
  156. package/dist/tui/stream.js +0 -56
  157. package/dist/tui/stream.js.map +0 -1
  158. package/dist/types.d.ts +0 -132
  159. package/dist/types.d.ts.map +0 -1
  160. package/dist/types.js +0 -2
  161. package/dist/types.js.map +0 -1
  162. package/dist/version.d.ts +0 -2
  163. package/dist/version.d.ts.map +0 -1
  164. package/dist/version.js +0 -8
  165. package/dist/version.js.map +0 -1
  166. package/models/APACHE-2.0.txt +0 -202
  167. package/models/MODEL.json +0 -29
  168. package/models/NOTICE.md +0 -23
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+ const {Client}=require('@modelcontextprotocol/sdk/client/index.js');
3
+ const {StreamableHTTPClientTransport}=require('@modelcontextprotocol/sdk/client/streamableHttp.js');
4
+ const {createNativeClaudeBroker}=require('./native-claude-broker');
5
+ const ENDPOINT='https://central-mcp-ccr-production.up.railway.app/mcp';
6
+
7
+ function validToken(value){return typeof value==='string' && value.length>0 && value.length<=16384 && !/[\x00-\x1f\x7f]/.test(value);}
8
+ function authorizedFetch({grantProvider,fetchImpl=fetch,endpoint=ENDPOINT}) {
9
+ if(!grantProvider || typeof grantProvider.getFreshTokens!=='function' || typeof grantProvider.refreshAfterUnauthorized!=='function')throw new Error('broker-grant-unavailable');
10
+ const allowed=new URL(endpoint);
11
+ return async (input,init)=>{
12
+ const request=new Request(input,init);
13
+ const url=new URL(request.url);
14
+ if(url.origin!==allowed.origin || url.pathname!==allowed.pathname || url.username || url.password || url.hash)throw new Error('broker-destination-invalid');
15
+ const replay=request.clone();
16
+ const send=async(request,token)=>{
17
+ if(!validToken(token))throw new Error('broker-grant-invalid');
18
+ const headers=new Headers(request.headers);headers.set('authorization',`Bearer ${token}`);headers.set('x-slaunt-client','claude-code');
19
+ return fetchImpl(new Request(request,{headers,redirect:'error',credentials:'omit',cache:'no-store'}));
20
+ };
21
+ const tokens=await grantProvider.getFreshTokens();
22
+ let response=await send(request,tokens?.accessToken);
23
+ if(response.status!==401)return response;
24
+ try{await response.body?.cancel();}catch{}
25
+ const refreshed=await grantProvider.refreshAfterUnauthorized(tokens.accessToken);
26
+ return send(replay,refreshed?.accessToken);
27
+ };
28
+ }
29
+
30
+ async function startNativeClaudeConnection({grantProvider,fetchImpl=fetch,root,makeClient=()=>new Client({name:'Slaunt Claude broker',version:'0.1.0'},{capabilities:{}}),makeTransport=(url,options)=>new StreamableHTTPClientTransport(url,options)}={}) {
31
+ const transport=makeTransport(new URL(ENDPOINT),{fetch:authorizedFetch({grantProvider,fetchImpl})});
32
+ const client=makeClient();
33
+ let broker,closed=false;
34
+ const close=async()=>{if(closed)return;closed=true;broker?.close();await client.close().catch(()=>{});await transport.close().catch(()=>{});};
35
+ try {
36
+ await client.connect(transport,{timeout:15000});
37
+ broker=createNativeClaudeBroker({client,root});
38
+ client.onclose=()=>{broker.close();closed=true;};
39
+ return {event:broker.event,close};
40
+ } catch {await close();throw new Error('broker-connection-unavailable');}
41
+ }
42
+ module.exports={ENDPOINT,authorizedFetch,startNativeClaudeConnection};
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+ // Runs with Electron's embedded Node runtime, without starting the desktop UI.
3
+ const { runGuardCommand, DENIAL } = require('./tool-guard');
4
+ const { resolveBrokerIdentity } = require('./client-identity');
5
+ let identity;
6
+ try { identity = resolveBrokerIdentity(process.argv); } catch {
7
+ process.stdout.write(`${JSON.stringify(DENIAL)}\n`, () => process.exit(0));
8
+ }
9
+ if (identity) {
10
+ void runGuardCommand({ clientKey: identity.key }).then(() => process.exit(0), () => {
11
+ process.stdout.write(`${JSON.stringify(DENIAL)}\n`, () => process.exit(0));
12
+ });
13
+ }
@@ -0,0 +1,225 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const net = require('node:net');
5
+ const path = require('node:path');
6
+ const { createHash, randomUUID } = require('node:crypto');
7
+
8
+ const DENIAL = Object.freeze({ hookSpecificOutput: {
9
+ hookEventName: 'PreToolUse', permissionDecision: 'deny',
10
+ permissionDecisionReason: 'Slaunt authorization unavailable or denied. Reconnect Slaunt and start a new turn.'
11
+ } });
12
+ const MAX_BYTES = 8192;
13
+ const hash = value => createHash('sha256').update(value).digest('hex');
14
+ const validString = value => typeof value === 'string' && Boolean(value.trim())
15
+ && value.length <= 256 && !/[\x00-\x1f\x7f]/.test(value);
16
+ const rootPath = () => {
17
+ if (process.platform === 'win32' || !process.getuid) throw new Error('guard-platform-unsupported');
18
+ return `/tmp/slaunt-guard-${process.getuid()}`;
19
+ };
20
+ function ensureRoot(root) {
21
+ try { fs.mkdirSync(root, { mode: 0o700 }); } catch (e) { if (e.code !== 'EEXIST') throw e; }
22
+ const stat = fs.lstatSync(root);
23
+ if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== process.getuid()
24
+ || (stat.mode & 0o077)) throw new Error('unsafe-guard-directory');
25
+ }
26
+ function socketPath(root, clientKey, sessionId, turnId) {
27
+ if (!['codex', 'claude-code'].includes(clientKey) || !validString(sessionId)) throw new Error('invalid-guard-session');
28
+ if (clientKey === 'codex' && !validString(turnId)) throw new Error('invalid-guard-turn');
29
+ return path.join(root, `${hash(JSON.stringify([clientKey, sessionId, clientKey === 'codex' ? turnId : null])).slice(0, 40)}.sock`);
30
+ }
31
+ function requestFromHook(input) {
32
+ if (!input || (input.hook_event_name ?? input.event_name) !== 'PreToolUse'
33
+ || !['session_id', 'tool_name', 'tool_use_id'].every(key => validString(input[key]))
34
+ || !Object.hasOwn(input, 'tool_input')) throw new Error('invalid-guard-event');
35
+ const args = { event_name: 'PreToolUse', session_id: input.session_id,
36
+ tool_name: input.tool_name, tool_use_id: input.tool_use_id,
37
+ request_id: randomUUID(), input_sha256: hash(JSON.stringify(input.tool_input)) };
38
+ if (input.turn_id !== undefined) {
39
+ if (!validString(input.turn_id)) throw new Error('invalid-guard-turn');
40
+ args.turn_id = input.turn_id;
41
+ }
42
+ return args;
43
+ }
44
+ function matchingPermit(response, args) {
45
+ const result = response?.structuredContent;
46
+ return !response?.isError && result?.decision === 'allow'
47
+ && ['request_id', 'tool_use_id', 'tool_name', 'input_sha256'].every(key => result[key] === args[key]);
48
+ }
49
+
50
+ // Keep the existing authenticated MCP connection and runtime. A new connection
51
+ // here could authorize a different thread or lose a remote stop request.
52
+ function createToolGuardServer({ client, clientKey, root = rootPath(), timeoutMs = 6000 }) {
53
+ const listeners = new Map();
54
+ const sockets = new Set();
55
+ const activeTurns = new Map();
56
+ let closed = false;
57
+ let activeSession = null;
58
+ async function bind(sessionId, { turnId, threadId = sessionId } = {}) {
59
+ if (closed) throw new Error('guard-closed');
60
+ // SessionStart has no turn yet. Codex sockets bind on UserPromptSubmit,
61
+ // using that request's native thread selector, never the last active thread.
62
+ if (clientKey === 'codex' && turnId === undefined) return;
63
+ if (!validString(threadId)) throw new Error('invalid-guard-thread');
64
+ const address = socketPath(root, clientKey, sessionId, turnId);
65
+ activeSession = sessionId;
66
+ if (clientKey === 'codex') activeTurns.set(threadId, turnId);
67
+ if (listeners.has(address)) {
68
+ const existing = listeners.get(address);
69
+ if (existing.threadId !== threadId) {
70
+ existing.conflicted = true;
71
+ throw new Error('ambiguous-guard-turn');
72
+ }
73
+ return existing.ready;
74
+ }
75
+ if (listeners.size >= 128) {
76
+ const [oldAddress, old] = listeners.entries().next().value;
77
+ old.server.close(); listeners.delete(oldAddress);
78
+ }
79
+ if (activeTurns.size > 128) activeTurns.delete(activeTurns.keys().next().value);
80
+ ensureRoot(root);
81
+ // Refuse to replace another active broker. Remove only our user's stale
82
+ // socket, checking inode identity again after probing it.
83
+ let previous;
84
+ try { previous = fs.lstatSync(address); } catch (e) { if (e.code !== 'ENOENT') throw e; }
85
+ if (previous) {
86
+ if (!previous.isSocket() || previous.uid !== process.getuid()) throw new Error('unsafe-guard-socket');
87
+ await new Promise((resolve, reject) => {
88
+ const probe = net.createConnection(address);
89
+ probe.setTimeout(500, () => { probe.destroy(); reject(new Error('guard-broker-busy')); });
90
+ probe.once('connect', () => { probe.destroy(); reject(new Error('guard-broker-busy')); });
91
+ probe.once('error', error => {
92
+ if (error.code === 'ECONNREFUSED' || error.code === 'ENOENT') resolve(); else reject(error);
93
+ });
94
+ });
95
+ const current = fs.lstatSync(address);
96
+ if (current.ino !== previous.ino || current.dev !== previous.dev) throw new Error('guard-socket-changed');
97
+ fs.unlinkSync(address);
98
+ }
99
+ const server = net.createServer(socket => {
100
+ sockets.add(socket);
101
+ socket.on('close', () => sockets.delete(socket));
102
+ socket.on('error', () => {});
103
+ let data = '';
104
+ let handled = false;
105
+ let settled = false;
106
+ const finish = result => {
107
+ if (settled) return;
108
+ settled = true;
109
+ if (!socket.destroyed) socket.end(`${JSON.stringify(result)}\n`);
110
+ };
111
+ const timer = setTimeout(() => { finish(DENIAL); socket.destroySoon(); }, timeoutMs);
112
+ socket.on('close', () => clearTimeout(timer));
113
+ socket.on('data', chunk => {
114
+ if (handled) return;
115
+ data += chunk.toString('utf8');
116
+ if (Buffer.byteLength(data) > MAX_BYTES) { handled = true; finish(DENIAL); return; }
117
+ if (!data.includes('\n')) return;
118
+ handled = true;
119
+ void (async () => {
120
+ let args;
121
+ try {
122
+ args = JSON.parse(data);
123
+ if (record.conflicted || (clientKey === 'claude-code' && activeSession !== sessionId)
124
+ || (clientKey === 'codex' && (args.turn_id !== turnId || activeTurns.get(threadId) !== turnId))
125
+ || args.session_id !== sessionId || args.event_name !== 'PreToolUse'
126
+ || !['tool_name', 'tool_use_id', 'request_id'].every(key => validString(args[key]))
127
+ || !/^[a-f0-9]{64}$/.test(args.input_sha256)
128
+ || Object.keys(args).some(key => !['event_name', 'session_id', 'tool_name', 'tool_use_id', 'request_id', 'input_sha256', 'turn_id'].includes(key))) {
129
+ throw new Error('invalid-guard-request');
130
+ }
131
+ const response = await client.callTool({ name: 'tool_authorize', arguments: args,
132
+ ...(clientKey === 'codex' ? { _meta: { 'ai.slaunt/codex-session-id': threadId } } : {})
133
+ }, undefined, { timeout: timeoutMs - 1000 });
134
+ // Do not forward upstream JSON: it must never rewrite tool inputs,
135
+ // grant native permissions, or leak diagnostic data to the model.
136
+ const stillBound = clientKey === 'claude-code' ? activeSession === sessionId : activeTurns.get(threadId) === turnId;
137
+ finish(!record.conflicted && stillBound && matchingPermit(response, args)
138
+ ? { structuredContent: { decision: 'allow', ...Object.fromEntries(
139
+ ['request_id', 'tool_use_id', 'tool_name', 'input_sha256'].map(key => [key, args[key]])) } } : DENIAL);
140
+ } catch { finish(DENIAL); }
141
+ })();
142
+ });
143
+ });
144
+ server.maxConnections = 32;
145
+ const record = { server, threadId, conflicted: false, ready: null };
146
+ record.ready = new Promise((resolve, reject) => {
147
+ server.once('error', reject);
148
+ server.listen(address, () => {
149
+ try { fs.chmodSync(address, 0o600); resolve(); }
150
+ catch { reject(new Error('guard-socket-unavailable')); }
151
+ });
152
+ });
153
+ listeners.set(address, record);
154
+ try { await record.ready; } catch (error) { listeners.delete(address); server.close(); throw error; }
155
+ }
156
+ function close() {
157
+ closed = true;
158
+ for (const socket of sockets) socket.destroy();
159
+ for (const { server } of listeners.values()) server.close();
160
+ listeners.clear();
161
+ }
162
+ return { bind, close, invalidate: () => { activeSession = null; } };
163
+ }
164
+
165
+ async function authorizeHook({ input, clientKey, root = rootPath(), timeoutMs = 8000 }) {
166
+ try {
167
+ const args = requestFromHook(input);
168
+ ensureRoot(root);
169
+ const address = socketPath(root, clientKey, args.session_id, args.turn_id);
170
+ const stat = fs.lstatSync(address);
171
+ if (!stat.isSocket() || stat.uid !== process.getuid() || (stat.mode & 0o077)) return DENIAL;
172
+ return await new Promise(resolve => {
173
+ const socket = net.createConnection(address);
174
+ let data = '';
175
+ const timer = setTimeout(() => finish(DENIAL), timeoutMs);
176
+ let done = false;
177
+ function finish(result) {
178
+ if (done) return;
179
+ done = true; clearTimeout(timer); socket.destroy(); resolve(result);
180
+ }
181
+ socket.once('connect', () => socket.write(`${JSON.stringify(args)}\n`));
182
+ socket.on('data', chunk => {
183
+ data += chunk.toString('utf8');
184
+ if (Buffer.byteLength(data) > MAX_BYTES) return finish(DENIAL);
185
+ if (!data.includes('\n')) return;
186
+ try {
187
+ const result = JSON.parse(data);
188
+ // A Slaunt permit lets the host continue its normal permission flow.
189
+ // In particular, do not bypass Claude's native approval prompts.
190
+ finish(matchingPermit(result, args)
191
+ ? { hookSpecificOutput: { hookEventName: 'PreToolUse' } } : DENIAL);
192
+ } catch { finish(DENIAL); }
193
+ });
194
+ socket.once('error', () => finish(DENIAL));
195
+ socket.once('end', () => finish(DENIAL));
196
+ });
197
+ } catch { return DENIAL; }
198
+ }
199
+
200
+ async function runGuardCommand({ stdin = process.stdin, stdout = process.stdout, clientKey, timeoutMs = 10000 }) {
201
+ // This deadline includes input reading and runs before Electron app readiness.
202
+ return new Promise(resolve => {
203
+ let done = false;
204
+ let data = '';
205
+ const finish = result => {
206
+ if (done) return;
207
+ done = true; clearTimeout(timer); stdin.pause();
208
+ stdout.write(`${JSON.stringify(result)}\n`, () => resolve());
209
+ };
210
+ const timer = setTimeout(() => finish(DENIAL), timeoutMs);
211
+ stdin.on('error', () => finish(DENIAL));
212
+ stdin.on('data', chunk => {
213
+ data += chunk.toString('utf8');
214
+ if (Buffer.byteLength(data) > 1024 * 1024) finish(DENIAL);
215
+ });
216
+ stdin.on('end', () => {
217
+ if (done) return;
218
+ try { void authorizeHook({ input: JSON.parse(data), clientKey }).then(finish, () => finish(DENIAL)); }
219
+ catch { finish(DENIAL); }
220
+ });
221
+ stdin.resume();
222
+ });
223
+ }
224
+
225
+ module.exports = { DENIAL, requestFromHook, matchingPermit, createToolGuardServer, authorizeHook, runGuardCommand };