@intutic/sync-daemon 1.10.0 → 2.0.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.md +2 -2
- package/dist/agentReporter.d.ts +205 -0
- package/dist/agentReporter.d.ts.map +1 -0
- package/dist/agentReporter.js +394 -0
- package/dist/agentReporter.js.map +1 -0
- package/dist/configReader.d.ts +39 -2
- package/dist/configReader.d.ts.map +1 -1
- package/dist/configReader.js +56 -2
- package/dist/configReader.js.map +1 -1
- package/dist/configWriter.d.ts +51 -1
- package/dist/configWriter.d.ts.map +1 -1
- package/dist/configWriter.js +490 -63
- package/dist/configWriter.js.map +1 -1
- package/dist/harness/antigravityHooks.d.ts.map +1 -1
- package/dist/harness/antigravityHooks.js +33 -55
- package/dist/harness/antigravityHooks.js.map +1 -1
- package/dist/harness/claudeCodeHooks.d.ts +47 -13
- package/dist/harness/claudeCodeHooks.d.ts.map +1 -1
- package/dist/harness/claudeCodeHooks.js +316 -54
- package/dist/harness/claudeCodeHooks.js.map +1 -1
- package/dist/harness/claudeDesktopHooks.d.ts.map +1 -1
- package/dist/harness/claudeDesktopHooks.js +35 -48
- package/dist/harness/claudeDesktopHooks.js.map +1 -1
- package/dist/harness/clineHooks.d.ts.map +1 -1
- package/dist/harness/clineHooks.js +42 -34
- package/dist/harness/clineHooks.js.map +1 -1
- package/dist/harness/codexHooks.d.ts +36 -0
- package/dist/harness/codexHooks.d.ts.map +1 -0
- package/dist/harness/codexHooks.js +227 -0
- package/dist/harness/codexHooks.js.map +1 -0
- package/dist/harness/continueHooks.d.ts +45 -5
- package/dist/harness/continueHooks.d.ts.map +1 -1
- package/dist/harness/continueHooks.js +225 -5
- package/dist/harness/continueHooks.js.map +1 -1
- package/dist/harness/cursorHooks.d.ts +31 -0
- package/dist/harness/cursorHooks.d.ts.map +1 -1
- package/dist/harness/cursorHooks.js +78 -49
- package/dist/harness/cursorHooks.js.map +1 -1
- package/dist/harness/dshHooks.d.ts +198 -0
- package/dist/harness/dshHooks.d.ts.map +1 -0
- package/dist/harness/dshHooks.js +569 -0
- package/dist/harness/dshHooks.js.map +1 -0
- package/dist/harness/gateBody.d.ts +422 -0
- package/dist/harness/gateBody.d.ts.map +1 -0
- package/dist/harness/gateBody.js +1816 -0
- package/dist/harness/gateBody.js.map +1 -0
- package/dist/harness/gateKind.d.ts +164 -0
- package/dist/harness/gateKind.d.ts.map +1 -0
- package/dist/harness/gateKind.js +143 -0
- package/dist/harness/gateKind.js.map +1 -0
- package/dist/harness/githubCopilotHooks.d.ts +54 -0
- package/dist/harness/githubCopilotHooks.d.ts.map +1 -0
- package/dist/harness/githubCopilotHooks.js +218 -0
- package/dist/harness/githubCopilotHooks.js.map +1 -0
- package/dist/harness/gooseHooks.d.ts.map +1 -1
- package/dist/harness/gooseHooks.js +67 -37
- package/dist/harness/gooseHooks.js.map +1 -1
- package/dist/harness/grokHooks.d.ts +77 -0
- package/dist/harness/grokHooks.d.ts.map +1 -0
- package/dist/harness/grokHooks.js +350 -0
- package/dist/harness/grokHooks.js.map +1 -0
- package/dist/harness/hermesHooks.d.ts.map +1 -1
- package/dist/harness/hermesHooks.js +26 -55
- package/dist/harness/hermesHooks.js.map +1 -1
- package/dist/harness/holdRedaction.d.ts +69 -0
- package/dist/harness/holdRedaction.d.ts.map +1 -0
- package/dist/harness/holdRedaction.js +211 -0
- package/dist/harness/holdRedaction.js.map +1 -0
- package/dist/harness/jetbrainsXmlConfig.d.ts +51 -0
- package/dist/harness/jetbrainsXmlConfig.d.ts.map +1 -0
- package/dist/harness/jetbrainsXmlConfig.js +151 -0
- package/dist/harness/jetbrainsXmlConfig.js.map +1 -0
- package/dist/harness/mcpAutoWrite.d.ts +100 -3
- package/dist/harness/mcpAutoWrite.d.ts.map +1 -1
- package/dist/harness/mcpAutoWrite.js +712 -90
- package/dist/harness/mcpAutoWrite.js.map +1 -1
- package/dist/harness/museHooks.d.ts +53 -0
- package/dist/harness/museHooks.d.ts.map +1 -0
- package/dist/harness/museHooks.js +273 -0
- package/dist/harness/museHooks.js.map +1 -0
- package/dist/harness/n8nHooks.d.ts +25 -9
- package/dist/harness/n8nHooks.d.ts.map +1 -1
- package/dist/harness/n8nHooks.js +174 -9
- package/dist/harness/n8nHooks.js.map +1 -1
- package/dist/harness/openCodeHooks.d.ts +93 -0
- package/dist/harness/openCodeHooks.d.ts.map +1 -0
- package/dist/harness/openCodeHooks.js +248 -0
- package/dist/harness/openCodeHooks.js.map +1 -0
- package/dist/harness/openWebuiHooks.d.ts.map +1 -1
- package/dist/harness/openWebuiHooks.js +56 -11
- package/dist/harness/openWebuiHooks.js.map +1 -1
- package/dist/harness/openclawHooks.d.ts.map +1 -1
- package/dist/harness/openclawHooks.js +30 -54
- package/dist/harness/openclawHooks.js.map +1 -1
- package/dist/harness/openhandsHooks.d.ts.map +1 -1
- package/dist/harness/openhandsHooks.js +35 -41
- package/dist/harness/openhandsHooks.js.map +1 -1
- package/dist/harness/piHooks.d.ts.map +1 -1
- package/dist/harness/piHooks.js +31 -58
- package/dist/harness/piHooks.js.map +1 -1
- package/dist/harness/protectedPaths.d.ts +353 -0
- package/dist/harness/protectedPaths.d.ts.map +1 -0
- package/dist/harness/protectedPaths.js +805 -0
- package/dist/harness/protectedPaths.js.map +1 -0
- package/dist/harness/rooCodeHooks.d.ts.map +1 -1
- package/dist/harness/rooCodeHooks.js +34 -48
- package/dist/harness/rooCodeHooks.js.map +1 -1
- package/dist/harness/windsurfHooks.d.ts +72 -0
- package/dist/harness/windsurfHooks.d.ts.map +1 -1
- package/dist/harness/windsurfHooks.js +172 -58
- package/dist/harness/windsurfHooks.js.map +1 -1
- package/dist/harness/windsurfJetBrainsProxy.d.ts +78 -0
- package/dist/harness/windsurfJetBrainsProxy.d.ts.map +1 -0
- package/dist/harness/windsurfJetBrainsProxy.js +159 -0
- package/dist/harness/windsurfJetBrainsProxy.js.map +1 -0
- package/dist/hashReporter.js +1 -1
- package/dist/hashReporter.js.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/integrityStore.d.ts +0 -8
- package/dist/integrityStore.d.ts.map +1 -1
- package/dist/integrityStore.js +6 -29
- package/dist/integrityStore.js.map +1 -1
- package/dist/lib/approvedBypasses.d.ts +76 -0
- package/dist/lib/approvedBypasses.d.ts.map +1 -0
- package/dist/lib/approvedBypasses.js +140 -0
- package/dist/lib/approvedBypasses.js.map +1 -0
- package/dist/lib/complianceProbes.d.ts.map +1 -1
- package/dist/lib/complianceProbes.js +5 -0
- package/dist/lib/complianceProbes.js.map +1 -1
- package/dist/lib/decisionsDigest.d.ts +99 -0
- package/dist/lib/decisionsDigest.d.ts.map +1 -0
- package/dist/lib/decisionsDigest.js +199 -0
- package/dist/lib/decisionsDigest.js.map +1 -0
- package/dist/lib/egressPolicy.d.ts +69 -0
- package/dist/lib/egressPolicy.d.ts.map +1 -0
- package/dist/lib/egressPolicy.js +126 -0
- package/dist/lib/egressPolicy.js.map +1 -0
- package/dist/lib/gitWorktrees.d.ts +140 -0
- package/dist/lib/gitWorktrees.d.ts.map +1 -0
- package/dist/lib/gitWorktrees.js +191 -0
- package/dist/lib/gitWorktrees.js.map +1 -0
- package/dist/lib/policySnapshot.d.ts +224 -0
- package/dist/lib/policySnapshot.d.ts.map +1 -0
- package/dist/lib/policySnapshot.js +637 -0
- package/dist/lib/policySnapshot.js.map +1 -0
- package/dist/lib/processPoller.d.ts +44 -0
- package/dist/lib/processPoller.d.ts.map +1 -1
- package/dist/lib/processPoller.js +176 -1
- package/dist/lib/processPoller.js.map +1 -1
- package/dist/lib/runtimeEnv.d.ts +8 -0
- package/dist/lib/runtimeEnv.d.ts.map +1 -1
- package/dist/lib/runtimeEnv.js +3 -0
- package/dist/lib/runtimeEnv.js.map +1 -1
- package/dist/localTrajectoryHeuristics.d.ts +51 -0
- package/dist/localTrajectoryHeuristics.d.ts.map +1 -0
- package/dist/localTrajectoryHeuristics.js +90 -0
- package/dist/localTrajectoryHeuristics.js.map +1 -0
- package/dist/sessionReporter.d.ts +70 -0
- package/dist/sessionReporter.d.ts.map +1 -0
- package/dist/sessionReporter.js +140 -0
- package/dist/sessionReporter.js.map +1 -0
- package/dist/skillWriter.d.ts +1 -1
- package/dist/skillWriter.d.ts.map +1 -1
- package/dist/skillWriter.js +5 -2
- package/dist/skillWriter.js.map +1 -1
- package/dist/syncLoop.d.ts +105 -3
- package/dist/syncLoop.d.ts.map +1 -1
- package/dist/syncLoop.js +397 -65
- package/dist/syncLoop.js.map +1 -1
- package/dist/trajectoryMonitor.d.ts +2 -0
- package/dist/trajectoryMonitor.d.ts.map +1 -1
- package/dist/trajectoryMonitor.js +57 -0
- package/dist/trajectoryMonitor.js.map +1 -1
- package/dist/watcher/driftWatcher.d.ts.map +1 -1
- package/dist/watcher/driftWatcher.js +12 -3
- package/dist/watcher/driftWatcher.js.map +1 -1
- package/dist/watcher/settingsGuard.d.ts +23 -0
- package/dist/watcher/settingsGuard.d.ts.map +1 -1
- package/dist/watcher/settingsGuard.js +202 -17
- package/dist/watcher/settingsGuard.js.map +1 -1
- package/package.json +37 -6
- package/dist/brainIndexer.d.ts +0 -51
- package/dist/brainIndexer.d.ts.map +0 -1
- package/dist/brainIndexer.js +0 -148
- package/dist/brainIndexer.js.map +0 -1
- package/dist/harness/cursorHooksJson.d.ts +0 -29
- package/dist/harness/cursorHooksJson.d.ts.map +0 -1
- package/dist/harness/cursorHooksJson.js +0 -130
- package/dist/harness/cursorHooksJson.js.map +0 -1
- package/dist/harness/gooseHooksWriter.d.ts +0 -27
- package/dist/harness/gooseHooksWriter.d.ts.map +0 -1
- package/dist/harness/gooseHooksWriter.js +0 -147
- package/dist/harness/gooseHooksWriter.js.map +0 -1
- package/dist/harness/openhandsHooksWriter.d.ts +0 -23
- package/dist/harness/openhandsHooksWriter.d.ts.map +0 -1
- package/dist/harness/openhandsHooksWriter.js +0 -146
- package/dist/harness/openhandsHooksWriter.js.map +0 -1
|
@@ -20,17 +20,9 @@ import * as path from 'node:path';
|
|
|
20
20
|
import * as os from 'node:os';
|
|
21
21
|
import { createLogger } from '@intutic/logger';
|
|
22
22
|
import { newIso } from '@intutic/id';
|
|
23
|
+
import { emitJsGate, emitJsFailClosedPrelude } from './gateBody.js';
|
|
23
24
|
const log = createLogger('sync-claude-desktop-hooks');
|
|
24
25
|
/** Governance-sensitive paths that the hook gate protects (mirrors claudeCodeHooks). */
|
|
25
|
-
const PROTECTED_PATHS = [
|
|
26
|
-
path.join(os.homedir(), '.claude', 'settings.json'),
|
|
27
|
-
path.join(os.homedir(), '.claude', 'settings.local.json'),
|
|
28
|
-
'.claude/settings.json',
|
|
29
|
-
'.claude/settings.local.json',
|
|
30
|
-
'.intutic/hooks',
|
|
31
|
-
'.intutic/integrity.json',
|
|
32
|
-
'.intutic/context_integrity.json',
|
|
33
|
-
];
|
|
34
26
|
/** Resolve the OS-specific claude_desktop_config.json path. */
|
|
35
27
|
function resolveClaudeDesktopConfigPath() {
|
|
36
28
|
switch (process.platform) {
|
|
@@ -60,6 +52,7 @@ function buildClaudeDesktopCheckScript(proxyUrl, workspaceRoot, workspaceId) {
|
|
|
60
52
|
* Workspace: ${workspaceId}
|
|
61
53
|
*/
|
|
62
54
|
'use strict';
|
|
55
|
+
${emitJsFailClosedPrelude({ harness: 'claude-desktop', contract: 'exit2' })}
|
|
63
56
|
const path = require('path');
|
|
64
57
|
const os = require('os');
|
|
65
58
|
const fs = require('fs');
|
|
@@ -91,11 +84,12 @@ function loadRuntimeEnv() {
|
|
|
91
84
|
}
|
|
92
85
|
|
|
93
86
|
// ── Governance-sensitive paths ────────────────────────────────────────────────
|
|
94
|
-
|
|
87
|
+
${emitJsGate({ harness: "claude-desktop", contract: 'exit2' })}
|
|
95
88
|
|
|
96
89
|
// ── Dual-path logEvent ────────────────────────────────────────────────────────
|
|
97
90
|
const HOOK_EVENTS_LOG = ${JSON.stringify(hookEventsLog)};
|
|
98
91
|
|
|
92
|
+
let _intuticSessionId = '';
|
|
99
93
|
function logEvent(env, verdict, toolName, reason) {
|
|
100
94
|
const ts = new Date().toISOString();
|
|
101
95
|
const incidentId = crypto.createHash('sha1')
|
|
@@ -103,13 +97,16 @@ function logEvent(env, verdict, toolName, reason) {
|
|
|
103
97
|
.digest('hex').slice(0, 16);
|
|
104
98
|
|
|
105
99
|
const entry = {
|
|
106
|
-
|
|
100
|
+
// Passed through, not collapsed to two values: the advisory tier emits
|
|
101
|
+
// 'tool_flagged', and a ternary here silently recorded it as an allow.
|
|
102
|
+
event: verdict,
|
|
107
103
|
toolName,
|
|
108
104
|
reason: reason || '',
|
|
109
105
|
workspaceId: env.INTUTIC_WORKSPACE_ID,
|
|
110
106
|
harnessType: 'claude-desktop',
|
|
111
107
|
timestamp: ts,
|
|
112
108
|
incidentId,
|
|
109
|
+
...(_intuticSessionId ? { sessionId: _intuticSessionId } : {}),
|
|
113
110
|
};
|
|
114
111
|
|
|
115
112
|
// Path B — reliable file append
|
|
@@ -149,49 +146,36 @@ process.stdin.on('data', (chunk) => { inputData += chunk; });
|
|
|
149
146
|
process.stdin.on('end', () => {
|
|
150
147
|
try {
|
|
151
148
|
const ctx = JSON.parse(inputData);
|
|
152
|
-
|
|
149
|
+
_intuticSessionId = ctx.session_id || ctx.sessionId || ctx.conversation_id || ctx.conversationId || ctx.task_id || ctx.taskId || '';
|
|
150
|
+
// An envelope carrying none of the tool fields extracts to empty strings,
|
|
151
|
+
// which match no rule — an allow. Refused instead; see the guard itself.
|
|
152
|
+
intuticGuardEnvelope(ctx, ['tool_name', 'toolName', 'tool_input', 'toolInput'],
|
|
153
|
+
(v, t, r) => logEvent(runtimeEnv, v, t, r));
|
|
154
|
+
const toolName = (ctx.tool_name || ctx.toolName || '').toLowerCase()
|
|
155
|
+
// Case preserved for the gate. A BLOCK: SOP compiles to a tool-name
|
|
156
|
+
// pattern the operator wrote as they see it (Bash, Write) and this
|
|
157
|
+
// harness lowercases the tool for its own matching. Handing the gate the
|
|
158
|
+
// lowercased form means every SOP tool rule silently matches nothing here
|
|
159
|
+
// while appearing active everywhere else.
|
|
160
|
+
const rawToolName = ctx.tool_name || ctx.toolName || '';
|
|
153
161
|
const toolInput = ctx.tool_input || ctx.toolInput || {};
|
|
154
162
|
const toolInputStr = JSON.stringify(toolInput);
|
|
155
163
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
logEvent(runtimeEnv, 'blocked', toolName, reason);
|
|
166
|
-
process.exit(2);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 2. Bash/shell command bypass guard
|
|
172
|
-
if (toolName === 'bash') {
|
|
173
|
-
const cmd = toolInput.command || toolInput.cmd || '';
|
|
174
|
-
const BYPASS_PATTERNS = [
|
|
175
|
-
'chflags nouchg',
|
|
176
|
-
'chattr -i',
|
|
177
|
-
'rm -rf .intutic',
|
|
178
|
-
'CLAUDE_DESKTOP_HOOKS=',
|
|
179
|
-
];
|
|
180
|
-
for (const pattern of BYPASS_PATTERNS) {
|
|
181
|
-
if (cmd.includes(pattern)) {
|
|
182
|
-
const reason = 'Shell command matches governance bypass pattern: "' + pattern + '"';
|
|
183
|
-
console.error('[Intutic Guardrail] BLOCKED: ' + reason);
|
|
184
|
-
logEvent(runtimeEnv, 'blocked', toolName, reason);
|
|
185
|
-
process.exit(2);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
164
|
+
// Both guards used to be gated on the tool name — paths only for
|
|
165
|
+
// edit/write/multiedit, commands only for a tool literally called "bash".
|
|
166
|
+
// A harness that names its shell tool anything else (run_command, terminal,
|
|
167
|
+
// execute) walked past both. The shared gate keys on the *arguments*, so a
|
|
168
|
+
// tool name nobody anticipated is no longer a bypass.
|
|
169
|
+
const targetPath = toolInput.path || toolInput.file_path || toolInput.filePath ||
|
|
170
|
+
toolInput.new_path || toolInput.target || toolInput.notebook_path || '';
|
|
171
|
+
const command = toolInput.command || toolInput.cmd || toolInput.script || '';
|
|
172
|
+
intuticGate(rawToolName, targetPath, command, (v, t, r) => logEvent(runtimeEnv, v, t, r), runtimeEnv.INTUTIC_WORKSPACE_ID, toolInput);
|
|
189
173
|
|
|
190
|
-
logEvent(runtimeEnv, '
|
|
174
|
+
logEvent(runtimeEnv, 'tool_allowed', toolName, '');
|
|
191
175
|
process.exit(0);
|
|
192
176
|
} catch (err) {
|
|
193
177
|
console.error('[Intutic Guardrail] Hook error (blocking for safety):', err);
|
|
194
|
-
logEvent(runtimeEnv, '
|
|
178
|
+
logEvent(runtimeEnv, 'tool_blocked', 'unknown', String(err));
|
|
195
179
|
process.exit(2);
|
|
196
180
|
}
|
|
197
181
|
});
|
|
@@ -238,7 +222,10 @@ export async function writeClaudeDesktopHooks(workspaceRoot, proxyUrl, workspace
|
|
|
238
222
|
const existingHooks = existingConfig.hooks ?? {};
|
|
239
223
|
const existingPreToolUse = existingHooks.PreToolUse ?? [];
|
|
240
224
|
// De-duplicate: remove any existing Intutic entry for Bash then re-add
|
|
241
|
-
|
|
225
|
+
// M3: 'mcp__.*' routes Claude Desktop's mcp__<server>__<tool> calls into
|
|
226
|
+
// this same gate script — see the identical note in claudeCodeHooks.ts,
|
|
227
|
+
// which shares this harness's PreToolUse contract exactly.
|
|
228
|
+
const HOOK_MATCHERS = ['Bash', 'Edit', 'Write', 'MultiEdit', 'mcp__.*'];
|
|
242
229
|
const intuticCmd = `node ${hookScriptPath}`;
|
|
243
230
|
const filteredPTU = existingPreToolUse.filter((entry) => {
|
|
244
231
|
if (typeof entry !== 'object' || entry === null)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claudeDesktopHooks.js","sourceRoot":"","sources":["../../src/harness/claudeDesktopHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"claudeDesktopHooks.js","sourceRoot":"","sources":["../../src/harness/claudeDesktopHooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clineHooks.d.ts","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"clineHooks.d.ts","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAsBH;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAkJf"}
|
|
@@ -17,6 +17,7 @@ import * as fs from 'node:fs/promises';
|
|
|
17
17
|
import * as path from 'node:path';
|
|
18
18
|
import { createLogger } from '@intutic/logger';
|
|
19
19
|
import { newIso } from '@intutic/id';
|
|
20
|
+
import { emitJsGate, emitJsFailClosedPrelude, REVIEW_REQUESTS_BASENAME } from './gateBody.js';
|
|
20
21
|
const log = createLogger('sync-cline-hooks');
|
|
21
22
|
/** Tools Cline can invoke that require governance interception. */
|
|
22
23
|
const GOVERNED_TOOLS = [
|
|
@@ -29,20 +30,6 @@ const GOVERNED_TOOLS = [
|
|
|
29
30
|
'rename_file',
|
|
30
31
|
'read_file', // watch for exfiltration patterns
|
|
31
32
|
];
|
|
32
|
-
/**
|
|
33
|
-
* Governance-sensitive paths that must never be agent-modified.
|
|
34
|
-
* Mirrors the protected path list in claudeCodeHooks.ts.
|
|
35
|
-
*/
|
|
36
|
-
const PROTECTED_PATH_FRAGMENTS = [
|
|
37
|
-
'.cline/hooks',
|
|
38
|
-
'.intutic/hooks',
|
|
39
|
-
'.intutic/integrity.json',
|
|
40
|
-
'.claude/settings.json',
|
|
41
|
-
'.cursor/hooks.json',
|
|
42
|
-
'.codeium/windsurf/hooks.json',
|
|
43
|
-
'.agents/plugins/intutic-governance',
|
|
44
|
-
'.openhands/hooks.json',
|
|
45
|
-
];
|
|
46
33
|
/**
|
|
47
34
|
* Write the Intutic governance hook into .cline/hooks/.
|
|
48
35
|
*
|
|
@@ -79,6 +66,7 @@ export async function writeClineHooks(workspaceRoot, proxyUrl, workspaceId = '')
|
|
|
79
66
|
* Proxy: ${proxyUrl}
|
|
80
67
|
* Generated: ${newIso()}
|
|
81
68
|
*/
|
|
69
|
+
${emitJsFailClosedPrelude({ harness: 'cline', contract: 'stdout-cancel' })}
|
|
82
70
|
const path = require('path');
|
|
83
71
|
const os = require('os');
|
|
84
72
|
const fs = require('fs');
|
|
@@ -100,12 +88,25 @@ try {
|
|
|
100
88
|
|
|
101
89
|
const HOOK_EVENTS_LOG = path.join(os.homedir(), '.intutic', 'events', 'cline-hook-events.jsonl');
|
|
102
90
|
|
|
91
|
+
let _intuticSessionId = '';
|
|
103
92
|
function logEvent(verdict, toolName, reason) {
|
|
104
93
|
try {
|
|
105
94
|
const ts = new Date().toISOString();
|
|
106
95
|
const incidentId = crypto.createHash('sha1').update(ts + toolName + _intuticWsId).digest('hex').slice(0, 16);
|
|
107
|
-
const entry = JSON.stringify({
|
|
108
|
-
|
|
96
|
+
const entry = JSON.stringify({ // Passed through, not collapsed to two values: the advisory tier emits
|
|
97
|
+
// 'tool_flagged', and a ternary here silently recorded it as an allow.
|
|
98
|
+
event: verdict, toolName, reason: reason || '', workspaceId: _intuticWsId, harnessType: 'cline', timestamp: ts, incidentId, ...(_intuticSessionId ? { sessionId: _intuticSessionId } : {}) }) + '\\n';
|
|
99
|
+
// mkdir first. This was a bare appendFileSync inside a swallowing catch, so
|
|
100
|
+
// on any machine where nothing else had created ~/.intutic/events the append
|
|
101
|
+
// threw ENOENT and was discarded — every Cline audit line, including blocks,
|
|
102
|
+
// silently dropped. The other writers create this directory from the
|
|
103
|
+
// TypeScript side; Cline's writer only creates .cline/hooks, so its log
|
|
104
|
+
// existed only by luck.
|
|
105
|
+
try {
|
|
106
|
+
const _dir = path.dirname(HOOK_EVENTS_LOG);
|
|
107
|
+
if (!fs.existsSync(_dir)) fs.mkdirSync(_dir, { recursive: true });
|
|
108
|
+
fs.appendFileSync(HOOK_EVENTS_LOG, entry, { flag: 'a' });
|
|
109
|
+
} catch {}
|
|
109
110
|
if (_intuticKey) {
|
|
110
111
|
try {
|
|
111
112
|
const body = JSON.stringify({ events: [JSON.parse(entry)] });
|
|
@@ -119,39 +120,46 @@ function logEvent(verdict, toolName, reason) {
|
|
|
119
120
|
} catch {}
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
${emitJsGate({ harness: 'cline', contract: 'stdout-cancel', reviewRequestFile: path.join(workspaceRoot, '.intutic', 'events', REVIEW_REQUESTS_BASENAME) })}
|
|
123
124
|
|
|
124
125
|
let raw = '';
|
|
125
126
|
process.stdin.on('data', (c) => { raw += c; });
|
|
126
127
|
process.stdin.on('end', () => {
|
|
127
128
|
try {
|
|
128
129
|
const ctx = JSON.parse(raw);
|
|
129
|
-
|
|
130
|
+
_intuticSessionId = ctx.session_id || ctx.sessionId || ctx.conversation_id || ctx.conversationId || ctx.task_id || ctx.taskId || '';
|
|
131
|
+
// An envelope carrying none of the tool fields extracts to empty strings,
|
|
132
|
+
// which match no rule — an allow. Refused instead, and the refusal CANCELS
|
|
133
|
+
// (this harness ignores exit codes); see intuticGuardEnvelope.
|
|
134
|
+
intuticGuardEnvelope(ctx, ['tool_name', 'toolName', 'tool_input', 'toolInput'], logEvent);
|
|
135
|
+
const tool = (ctx.tool_name || '').toLowerCase()
|
|
136
|
+
// Case preserved for the gate. A BLOCK: SOP compiles to a tool-name
|
|
137
|
+
// pattern the operator wrote as they see it (Bash, Write) and this
|
|
138
|
+
// harness lowercases the tool for its own matching. Handing the gate the
|
|
139
|
+
// lowercased form means every SOP tool rule silently matches nothing here
|
|
140
|
+
// while appearing active everywhere else.
|
|
141
|
+
const rawToolName = ctx.tool_name || '';
|
|
130
142
|
const input = ctx.tool_input || {};
|
|
131
143
|
const inputStr = JSON.stringify(input);
|
|
132
144
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
process.exit(0);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
145
|
+
// This gate had two defects that hid each other. Its path list was
|
|
146
|
+
// hand-rolled as PROTECTED_PATH_FRAGMENTS with four of the twelve universal
|
|
147
|
+
// entries missing, and harnessProtectedPaths.test.ts keyed its coverage on
|
|
148
|
+
// finding a constant named PROTECTED_PATHS — so not having one read as "not
|
|
149
|
+
// a harness" rather than "unguarded". It also had no command guard at all,
|
|
150
|
+
// and ran only for six named file tools.
|
|
151
|
+
const targetPath = input.path || input.file_path || input.filePath ||
|
|
152
|
+
input.target || input.source || input.notebook_path || '';
|
|
153
|
+
const command = input.command || input.cmd || input.script || '';
|
|
154
|
+
intuticGate(rawToolName, targetPath, command, logEvent, _intuticWsId, input);
|
|
147
155
|
|
|
148
156
|
// Allow all other tool calls
|
|
149
|
-
logEvent('
|
|
157
|
+
logEvent('tool_allowed', tool || 'unknown', '');
|
|
150
158
|
process.stdout.write(JSON.stringify({ cancel: false }));
|
|
151
159
|
process.exit(0);
|
|
152
160
|
} catch (err) {
|
|
153
161
|
// Fail CLOSED — hook parse error blocks the tool call
|
|
154
|
-
logEvent('
|
|
162
|
+
logEvent('tool_blocked', 'unknown', String(err));
|
|
155
163
|
process.stdout.write(JSON.stringify({
|
|
156
164
|
cancel: true,
|
|
157
165
|
errorMessage: '[Intutic Governance] Hook error (fail-closed): ' + String(err),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clineHooks.js","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"clineHooks.js","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codexHooks.ts — Codex CLI PreToolUse hook injection.
|
|
3
|
+
*
|
|
4
|
+
* Codex CLI executes PreToolUse hooks registered in `hooks.json`:
|
|
5
|
+
* - User-level: ~/.codex/hooks.json
|
|
6
|
+
* - Project-level: <workspaceRoot>/.codex/hooks.json
|
|
7
|
+
*
|
|
8
|
+
* Each PreToolUse entry carries a `matcher` regex over tool names; the hook
|
|
9
|
+
* command receives JSON on stdin — {tool_name, tool_use_id, tool_input}, with
|
|
10
|
+
* a shell command at tool_input.command — and refuses with **exit code 2**
|
|
11
|
+
* (stderr becomes the reason shown to the agent). That is the same stdin shape
|
|
12
|
+
* and exit-2 contract the shared JS gate already speaks, so the gate body is a
|
|
13
|
+
* drop-in; this writer's whole job is emitting the gate file and registering
|
|
14
|
+
* it in both hooks.json files without destroying anything the user put there.
|
|
15
|
+
*
|
|
16
|
+
* (Codex also accepts a stdout `{"hookSpecificOutput":{"permissionDecision":
|
|
17
|
+
* "deny"}}` object; exit 2 is used here because it is the contract every other
|
|
18
|
+
* exit-2 harness shares and the one `generatedGateBehaviour.test.ts` drives.)
|
|
19
|
+
*
|
|
20
|
+
* Historically codex was governed by `.env.intutic` proxy routing only — an
|
|
21
|
+
* egress control, not a tool gate. This closes that gap.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Write the Codex gate script and register it at user and project level.
|
|
27
|
+
*
|
|
28
|
+
* Safe to call repeatedly — uses atomic rename (`.intutic-tmp` → final path)
|
|
29
|
+
* and command-keyed de-duplication in both hooks.json files.
|
|
30
|
+
*
|
|
31
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
32
|
+
* @param proxyUrl - Intutic proxy URL written into the gate header.
|
|
33
|
+
* @param workspaceId - Workspace ID embedded in every hook event payload.
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeCodexHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
|
|
36
|
+
//# sourceMappingURL=codexHooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codexHooks.d.ts","sourceRoot":"","sources":["../../src/harness/codexHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAwLH;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAwBf"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codexHooks.ts — Codex CLI PreToolUse hook injection.
|
|
3
|
+
*
|
|
4
|
+
* Codex CLI executes PreToolUse hooks registered in `hooks.json`:
|
|
5
|
+
* - User-level: ~/.codex/hooks.json
|
|
6
|
+
* - Project-level: <workspaceRoot>/.codex/hooks.json
|
|
7
|
+
*
|
|
8
|
+
* Each PreToolUse entry carries a `matcher` regex over tool names; the hook
|
|
9
|
+
* command receives JSON on stdin — {tool_name, tool_use_id, tool_input}, with
|
|
10
|
+
* a shell command at tool_input.command — and refuses with **exit code 2**
|
|
11
|
+
* (stderr becomes the reason shown to the agent). That is the same stdin shape
|
|
12
|
+
* and exit-2 contract the shared JS gate already speaks, so the gate body is a
|
|
13
|
+
* drop-in; this writer's whole job is emitting the gate file and registering
|
|
14
|
+
* it in both hooks.json files without destroying anything the user put there.
|
|
15
|
+
*
|
|
16
|
+
* (Codex also accepts a stdout `{"hookSpecificOutput":{"permissionDecision":
|
|
17
|
+
* "deny"}}` object; exit 2 is used here because it is the contract every other
|
|
18
|
+
* exit-2 harness shares and the one `generatedGateBehaviour.test.ts` drives.)
|
|
19
|
+
*
|
|
20
|
+
* Historically codex was governed by `.env.intutic` proxy routing only — an
|
|
21
|
+
* egress control, not a tool gate. This closes that gap.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import * as fs from 'node:fs/promises';
|
|
26
|
+
import * as path from 'node:path';
|
|
27
|
+
import * as os from 'node:os';
|
|
28
|
+
import { createLogger } from '@intutic/logger';
|
|
29
|
+
import { newIso } from '@intutic/id';
|
|
30
|
+
import { emitJsGate, emitJsFailClosedPrelude } from './gateBody.js';
|
|
31
|
+
const log = createLogger('sync-codex-hooks');
|
|
32
|
+
/**
|
|
33
|
+
* The pre-tool gate script — receives Codex's PreToolUse JSON on stdin,
|
|
34
|
+
* exits 0 to allow or 2 to block.
|
|
35
|
+
*/
|
|
36
|
+
function buildHookScript(proxyUrl, workspaceRoot, workspaceId) {
|
|
37
|
+
const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'hook-events.jsonl');
|
|
38
|
+
return `#!/usr/bin/env node
|
|
39
|
+
/**
|
|
40
|
+
* Intutic Codex CLI governance gate.
|
|
41
|
+
* Auto-generated by intutic sync-daemon. DO NOT EDIT.
|
|
42
|
+
* Proxy: ${proxyUrl}
|
|
43
|
+
* Generated: ${newIso()}
|
|
44
|
+
*/
|
|
45
|
+
${emitJsFailClosedPrelude({ harness: 'codex', contract: 'exit2' })}
|
|
46
|
+
const path = require('path');
|
|
47
|
+
const os = require('os');
|
|
48
|
+
const fs = require('fs');
|
|
49
|
+
const crypto = require('crypto');
|
|
50
|
+
const https = require('https');
|
|
51
|
+
|
|
52
|
+
// Runtime credentials
|
|
53
|
+
const _runtimeEnvPath = path.join(os.homedir(), '.intutic', 'env', 'runtime.env');
|
|
54
|
+
let _intuticHost = 'https://api.intutic.ai', _intuticKey = '', _intuticWsId = ${JSON.stringify(workspaceId)};
|
|
55
|
+
try {
|
|
56
|
+
fs.readFileSync(_runtimeEnvPath, 'utf-8').split('\\n').forEach(line => {
|
|
57
|
+
const eq = line.indexOf('='); if (eq < 0) return;
|
|
58
|
+
const k = line.slice(0, eq).trim(), v = line.slice(eq + 1).trim();
|
|
59
|
+
if (k === 'INTUTIC_HOST' && v) _intuticHost = v;
|
|
60
|
+
if (k === 'INTUTIC_API_KEY' && v) _intuticKey = v;
|
|
61
|
+
if (k === 'INTUTIC_WORKSPACE_ID' && v) _intuticWsId = v;
|
|
62
|
+
});
|
|
63
|
+
} catch {}
|
|
64
|
+
|
|
65
|
+
${emitJsGate({ harness: 'codex', contract: 'exit2' })}
|
|
66
|
+
|
|
67
|
+
let _intuticSessionId = '';
|
|
68
|
+
function logEvent(verdict, toolName, reason) {
|
|
69
|
+
try {
|
|
70
|
+
const ts = new Date().toISOString();
|
|
71
|
+
const incidentId = crypto.createHash('sha1').update(ts + toolName + _intuticWsId).digest('hex').slice(0, 16);
|
|
72
|
+
const entry = JSON.stringify({
|
|
73
|
+
// Passed through, not collapsed to two values: the advisory tier emits
|
|
74
|
+
// 'tool_flagged', and a ternary here silently recorded it as an allow.
|
|
75
|
+
event: verdict,
|
|
76
|
+
toolName, reason: reason || '',
|
|
77
|
+
workspaceId: _intuticWsId,
|
|
78
|
+
harnessType: 'codex',
|
|
79
|
+
timestamp: ts,
|
|
80
|
+
incidentId,
|
|
81
|
+
...(_intuticSessionId ? { sessionId: _intuticSessionId } : {}),
|
|
82
|
+
}) + '\\n';
|
|
83
|
+
fs.appendFileSync(${JSON.stringify(hookEventsLog)}, entry, { flag: 'a' });
|
|
84
|
+
if (_intuticKey) {
|
|
85
|
+
try {
|
|
86
|
+
const body = JSON.stringify({ events: [JSON.parse(entry)] });
|
|
87
|
+
const urlObj = new URL('/api/v1/hook-events', _intuticHost);
|
|
88
|
+
const mod = urlObj.protocol === 'https:' ? https : require('http');
|
|
89
|
+
const req = mod.request({ hostname: urlObj.hostname, port: urlObj.port || 443, path: urlObj.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), 'Authorization': 'Bearer ' + _intuticKey } });
|
|
90
|
+
req.on('error', () => { /* fire-and-forget */ });
|
|
91
|
+
req.write(body); req.end();
|
|
92
|
+
} catch { /* never crash the hook */ }
|
|
93
|
+
}
|
|
94
|
+
} catch { /* never crash the hook */ }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let raw = '';
|
|
98
|
+
process.stdin.on('data', (c) => { raw += c; });
|
|
99
|
+
process.stdin.on('end', () => {
|
|
100
|
+
try {
|
|
101
|
+
const ctx = JSON.parse(raw);
|
|
102
|
+
_intuticSessionId = ctx.session_id || ctx.sessionId || '';
|
|
103
|
+
// Verified during the v5 audit: this writer did NOT have the envelope
|
|
104
|
+
// guard its registry note implied it inherited — a payload with neither a
|
|
105
|
+
// tool name nor a tool_input defaulted to toolName 'tool' with empty
|
|
106
|
+
// fields, matched no rule, and was allowed. Refused now, uniformly.
|
|
107
|
+
intuticGuardEnvelope(ctx, ['tool_name', 'toolName', 'tool_input', 'toolInput'], logEvent);
|
|
108
|
+
const input = ctx.tool_input || ctx.toolInput || {};
|
|
109
|
+
const toolName = ctx.tool_name || ctx.toolName || 'tool';
|
|
110
|
+
|
|
111
|
+
// Every spelling any harness uses, matching the bash extractor. A field
|
|
112
|
+
// name this list misses is a path guard that silently does not run — and
|
|
113
|
+
// the caller controls which spelling it sends.
|
|
114
|
+
const targetPath = input.path || input.file_path || input.filePath || input.file ||
|
|
115
|
+
input.target || input.notebook_path || '';
|
|
116
|
+
const command = input.command || input.cmd || input.script || '';
|
|
117
|
+
|
|
118
|
+
intuticGate(toolName, targetPath, command, logEvent, _intuticWsId, input);
|
|
119
|
+
|
|
120
|
+
// Allow
|
|
121
|
+
logEvent('tool_allowed', toolName, '');
|
|
122
|
+
process.exit(0);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
// Fail CLOSED
|
|
125
|
+
const errMsg = String(err);
|
|
126
|
+
process.stderr.write('[Intutic Governance] Hook error (fail-closed): ' + errMsg + '\\n');
|
|
127
|
+
logEvent('tool_blocked', 'unknown', errMsg);
|
|
128
|
+
process.exit(2);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Merge the Intutic PreToolUse entry into a Codex hooks.json, preserving
|
|
135
|
+
* everything the user registered themselves.
|
|
136
|
+
*
|
|
137
|
+
* De-duplication is by command (any entry whose hooks all invoke this gate
|
|
138
|
+
* script is ours and is replaced), the same convention claudeDesktopHooks
|
|
139
|
+
* uses — so repeated syncs are idempotent and never stack duplicate entries.
|
|
140
|
+
*/
|
|
141
|
+
async function mergeCodexHooksJson(configPath, hookScriptPath) {
|
|
142
|
+
let existing = {};
|
|
143
|
+
try {
|
|
144
|
+
existing = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// File doesn't exist or is malformed — start fresh
|
|
148
|
+
}
|
|
149
|
+
const existingHooks = existing.hooks ?? {};
|
|
150
|
+
const existingPreToolUse = Array.isArray(existingHooks.PreToolUse)
|
|
151
|
+
? existingHooks.PreToolUse
|
|
152
|
+
: [];
|
|
153
|
+
const intuticCmd = `node "${hookScriptPath}"`;
|
|
154
|
+
const filtered = existingPreToolUse.filter((entry) => {
|
|
155
|
+
if (typeof entry !== 'object' || entry === null)
|
|
156
|
+
return true;
|
|
157
|
+
const inner = entry.hooks ?? [];
|
|
158
|
+
return !(inner.length > 0 &&
|
|
159
|
+
inner.every((h) => typeof h === 'object' && h !== null && h.command === intuticCmd));
|
|
160
|
+
});
|
|
161
|
+
// Provenance lives in `description` and NOWHERE else: Codex parses
|
|
162
|
+
// hooks.json with a strict schema ("expected `description` or `hooks`") and
|
|
163
|
+
// REJECTS the entire file over an unknown top-level key. The first version
|
|
164
|
+
// of this writer stamped `_intutic_comment`/`_intutic_last_sync` here, and a
|
|
165
|
+
// live smoke against codex-cli 0.147.0 showed the gate silently never
|
|
166
|
+
// loading — "warning: failed to parse hooks config … unknown field
|
|
167
|
+
// `_intutic_comment`" — while every emitted-script test stayed green. Only
|
|
168
|
+
// fields the consumer's schema names may appear at this level, and the two
|
|
169
|
+
// legacy keys are actively deleted so a file the old writer touched heals
|
|
170
|
+
// on the next sync instead of staying rejected forever.
|
|
171
|
+
delete existing['_intutic_comment'];
|
|
172
|
+
delete existing['_intutic_last_sync'];
|
|
173
|
+
const merged = {
|
|
174
|
+
...existing,
|
|
175
|
+
description: 'Intutic governance PreToolUse hook (auto-managed; your own hooks are preserved). ' +
|
|
176
|
+
`Last sync: ${newIso()}`,
|
|
177
|
+
hooks: {
|
|
178
|
+
...existingHooks,
|
|
179
|
+
PreToolUse: [
|
|
180
|
+
...filtered,
|
|
181
|
+
{
|
|
182
|
+
// One catch-all matcher rather than a tool list: the gate itself
|
|
183
|
+
// decides from the arguments, so a tool name nobody anticipated is
|
|
184
|
+
// still evaluated rather than silently unmatched.
|
|
185
|
+
matcher: '.*',
|
|
186
|
+
hooks: [{ type: 'command', command: intuticCmd }],
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
const tmp = configPath + '.intutic-tmp';
|
|
192
|
+
await fs.writeFile(tmp, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
193
|
+
await fs.rename(tmp, configPath);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Write the Codex gate script and register it at user and project level.
|
|
197
|
+
*
|
|
198
|
+
* Safe to call repeatedly — uses atomic rename (`.intutic-tmp` → final path)
|
|
199
|
+
* and command-keyed de-duplication in both hooks.json files.
|
|
200
|
+
*
|
|
201
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
202
|
+
* @param proxyUrl - Intutic proxy URL written into the gate header.
|
|
203
|
+
* @param workspaceId - Workspace ID embedded in every hook event payload.
|
|
204
|
+
*/
|
|
205
|
+
export async function writeCodexHooks(workspaceRoot, proxyUrl, workspaceId = '') {
|
|
206
|
+
const hookScriptDir = path.join(workspaceRoot, '.intutic', 'hooks');
|
|
207
|
+
await fs.mkdir(hookScriptDir, { recursive: true });
|
|
208
|
+
await fs.mkdir(path.join(workspaceRoot, '.intutic', 'events'), { recursive: true });
|
|
209
|
+
// Named after its writer — see the note in claudeCodeHooks.ts about two
|
|
210
|
+
// writers overwriting one `pre-tool-check.js`.
|
|
211
|
+
const hookScriptPath = path.join(hookScriptDir, 'codex-check.js');
|
|
212
|
+
const tmpScript = hookScriptPath + '.intutic-tmp';
|
|
213
|
+
await fs.writeFile(tmpScript, buildHookScript(proxyUrl, workspaceRoot, workspaceId), 'utf-8');
|
|
214
|
+
await fs.rename(tmpScript, hookScriptPath);
|
|
215
|
+
await fs.chmod(hookScriptPath, 0o755);
|
|
216
|
+
// 1. Project-level: <root>/.codex/hooks.json
|
|
217
|
+
const projectCodexDir = path.join(workspaceRoot, '.codex');
|
|
218
|
+
await fs.mkdir(projectCodexDir, { recursive: true });
|
|
219
|
+
await mergeCodexHooksJson(path.join(projectCodexDir, 'hooks.json'), hookScriptPath);
|
|
220
|
+
log.info({ action: 'codex_hooks_written', level: 'project', path: projectCodexDir }, 'Codex project-level hooks written');
|
|
221
|
+
// 2. User-level: ~/.codex/hooks.json
|
|
222
|
+
const userCodexDir = path.join(os.homedir(), '.codex');
|
|
223
|
+
await fs.mkdir(userCodexDir, { recursive: true });
|
|
224
|
+
await mergeCodexHooksJson(path.join(userCodexDir, 'hooks.json'), hookScriptPath);
|
|
225
|
+
log.info({ action: 'codex_hooks_written', level: 'user', path: userCodexDir }, 'Codex user-level hooks written');
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=codexHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codexHooks.js","sourceRoot":"","sources":["../../src/harness/codexHooks.ts"],"names":[],"mappings":"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"}
|