@intutic/sync-daemon 1.6.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/brainIndexer.d.ts +51 -0
- package/dist/brainIndexer.d.ts.map +1 -0
- package/dist/brainIndexer.js +148 -0
- package/dist/brainIndexer.js.map +1 -0
- package/dist/configReader.d.ts +46 -0
- package/dist/configReader.d.ts.map +1 -0
- package/dist/configReader.js +133 -0
- package/dist/configReader.js.map +1 -0
- package/dist/configWriter.d.ts +53 -0
- package/dist/configWriter.d.ts.map +1 -0
- package/dist/configWriter.js +503 -0
- package/dist/configWriter.js.map +1 -0
- package/dist/harness/aiderConfigMerger.d.ts +27 -0
- package/dist/harness/aiderConfigMerger.d.ts.map +1 -0
- package/dist/harness/aiderConfigMerger.js +139 -0
- package/dist/harness/aiderConfigMerger.js.map +1 -0
- package/dist/harness/antigravityHooks.d.ts +43 -0
- package/dist/harness/antigravityHooks.d.ts.map +1 -0
- package/dist/harness/antigravityHooks.js +193 -0
- package/dist/harness/antigravityHooks.js.map +1 -0
- package/dist/harness/claudeCodeHooks.d.ts +59 -0
- package/dist/harness/claudeCodeHooks.d.ts.map +1 -0
- package/dist/harness/claudeCodeHooks.js +414 -0
- package/dist/harness/claudeCodeHooks.js.map +1 -0
- package/dist/harness/claudeDesktopHooks.d.ts +30 -0
- package/dist/harness/claudeDesktopHooks.d.ts.map +1 -0
- package/dist/harness/claudeDesktopHooks.js +267 -0
- package/dist/harness/claudeDesktopHooks.js.map +1 -0
- package/dist/harness/clineHooks.d.ts +23 -0
- package/dist/harness/clineHooks.d.ts.map +1 -0
- package/dist/harness/clineHooks.js +169 -0
- package/dist/harness/clineHooks.js.map +1 -0
- package/dist/harness/continueHooks.d.ts +33 -0
- package/dist/harness/continueHooks.d.ts.map +1 -0
- package/dist/harness/continueHooks.js +124 -0
- package/dist/harness/continueHooks.js.map +1 -0
- package/dist/harness/cursorHooks.d.ts +27 -0
- package/dist/harness/cursorHooks.d.ts.map +1 -0
- package/dist/harness/cursorHooks.js +221 -0
- package/dist/harness/cursorHooks.js.map +1 -0
- package/dist/harness/cursorHooksJson.d.ts +29 -0
- package/dist/harness/cursorHooksJson.d.ts.map +1 -0
- package/dist/harness/cursorHooksJson.js +130 -0
- package/dist/harness/cursorHooksJson.js.map +1 -0
- package/dist/harness/gooseHardener.d.ts +39 -0
- package/dist/harness/gooseHardener.d.ts.map +1 -0
- package/dist/harness/gooseHardener.js +152 -0
- package/dist/harness/gooseHardener.js.map +1 -0
- package/dist/harness/gooseHooks.d.ts +24 -0
- package/dist/harness/gooseHooks.d.ts.map +1 -0
- package/dist/harness/gooseHooks.js +175 -0
- package/dist/harness/gooseHooks.js.map +1 -0
- package/dist/harness/gooseHooksWriter.d.ts +27 -0
- package/dist/harness/gooseHooksWriter.d.ts.map +1 -0
- package/dist/harness/gooseHooksWriter.js +147 -0
- package/dist/harness/gooseHooksWriter.js.map +1 -0
- package/dist/harness/hermesHooks.d.ts +31 -0
- package/dist/harness/hermesHooks.d.ts.map +1 -0
- package/dist/harness/hermesHooks.js +245 -0
- package/dist/harness/hermesHooks.js.map +1 -0
- package/dist/harness/mcpAutoWrite.d.ts +35 -0
- package/dist/harness/mcpAutoWrite.d.ts.map +1 -0
- package/dist/harness/mcpAutoWrite.js +334 -0
- package/dist/harness/mcpAutoWrite.js.map +1 -0
- package/dist/harness/n8nHooks.d.ts +28 -0
- package/dist/harness/n8nHooks.d.ts.map +1 -0
- package/dist/harness/n8nHooks.js +205 -0
- package/dist/harness/n8nHooks.js.map +1 -0
- package/dist/harness/openWebuiHooks.d.ts +31 -0
- package/dist/harness/openWebuiHooks.d.ts.map +1 -0
- package/dist/harness/openWebuiHooks.js +166 -0
- package/dist/harness/openWebuiHooks.js.map +1 -0
- package/dist/harness/openclawHooks.d.ts +34 -0
- package/dist/harness/openclawHooks.d.ts.map +1 -0
- package/dist/harness/openclawHooks.js +296 -0
- package/dist/harness/openclawHooks.js.map +1 -0
- package/dist/harness/openhandsHooks.d.ts +24 -0
- package/dist/harness/openhandsHooks.d.ts.map +1 -0
- package/dist/harness/openhandsHooks.js +178 -0
- package/dist/harness/openhandsHooks.js.map +1 -0
- package/dist/harness/openhandsHooksWriter.d.ts +23 -0
- package/dist/harness/openhandsHooksWriter.d.ts.map +1 -0
- package/dist/harness/openhandsHooksWriter.js +146 -0
- package/dist/harness/openhandsHooksWriter.js.map +1 -0
- package/dist/harness/piHooks.d.ts +32 -0
- package/dist/harness/piHooks.d.ts.map +1 -0
- package/dist/harness/piHooks.js +257 -0
- package/dist/harness/piHooks.js.map +1 -0
- package/dist/harness/rooCodeHooks.d.ts +31 -0
- package/dist/harness/rooCodeHooks.d.ts.map +1 -0
- package/dist/harness/rooCodeHooks.js +300 -0
- package/dist/harness/rooCodeHooks.js.map +1 -0
- package/dist/harness/windsurfHooks.d.ts +27 -0
- package/dist/harness/windsurfHooks.d.ts.map +1 -0
- package/dist/harness/windsurfHooks.js +202 -0
- package/dist/harness/windsurfHooks.js.map +1 -0
- package/dist/hashReporter.d.ts +33 -0
- package/dist/hashReporter.d.ts.map +1 -0
- package/dist/hashReporter.js +69 -0
- package/dist/hashReporter.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/integrityStore.d.ts +43 -0
- package/dist/integrityStore.d.ts.map +1 -0
- package/dist/integrityStore.js +94 -0
- package/dist/integrityStore.js.map +1 -0
- package/dist/lib/complianceProbes.d.ts +39 -0
- package/dist/lib/complianceProbes.d.ts.map +1 -0
- package/dist/lib/complianceProbes.js +115 -0
- package/dist/lib/complianceProbes.js.map +1 -0
- package/dist/lib/processPoller.d.ts +2 -0
- package/dist/lib/processPoller.d.ts.map +1 -0
- package/dist/lib/processPoller.js +87 -0
- package/dist/lib/processPoller.js.map +1 -0
- package/dist/lib/runtimeEnv.d.ts +49 -0
- package/dist/lib/runtimeEnv.d.ts.map +1 -0
- package/dist/lib/runtimeEnv.js +70 -0
- package/dist/lib/runtimeEnv.js.map +1 -0
- package/dist/skillWriter.d.ts +24 -0
- package/dist/skillWriter.d.ts.map +1 -0
- package/dist/skillWriter.js +112 -0
- package/dist/skillWriter.js.map +1 -0
- package/dist/syncLoop.d.ts +70 -0
- package/dist/syncLoop.d.ts.map +1 -0
- package/dist/syncLoop.js +546 -0
- package/dist/syncLoop.js.map +1 -0
- package/dist/trajectoryMonitor.d.ts +126 -0
- package/dist/trajectoryMonitor.d.ts.map +1 -0
- package/dist/trajectoryMonitor.js +291 -0
- package/dist/trajectoryMonitor.js.map +1 -0
- package/dist/watcher/driftWatcher.d.ts +25 -0
- package/dist/watcher/driftWatcher.d.ts.map +1 -0
- package/dist/watcher/driftWatcher.js +72 -0
- package/dist/watcher/driftWatcher.js.map +1 -0
- package/dist/watcher/settingsGuard.d.ts +32 -0
- package/dist/watcher/settingsGuard.d.ts.map +1 -0
- package/dist/watcher/settingsGuard.js +250 -0
- package/dist/watcher/settingsGuard.js.map +1 -0
- package/dist/wsClient.d.ts +49 -0
- package/dist/wsClient.d.ts.map +1 -0
- package/dist/wsClient.js +149 -0
- package/dist/wsClient.js.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claudeDesktopHooks.ts — Claude Desktop PreToolUse hook generator.
|
|
3
|
+
*
|
|
4
|
+
* Claude Desktop shares the same hook format as Claude Code (PreToolUse,
|
|
5
|
+
* exit 2 on stdin JSON) but reads its config from an OS-specific location:
|
|
6
|
+
* macOS : ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
7
|
+
* Linux : ~/.config/Claude/claude_desktop_config.json
|
|
8
|
+
*
|
|
9
|
+
* Hook events are written via dual-path logEvent:
|
|
10
|
+
* Path B (reliable) — synchronous append to .intutic/events/claude-desktop-hook-events.jsonl
|
|
11
|
+
* Path A (telemetry) — fire-and-forget https.request to the control plane
|
|
12
|
+
*
|
|
13
|
+
* LLD #14 — Phase 3 cross-harness defence (Gap 3, WS-B)
|
|
14
|
+
* HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'node:fs/promises';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
import * as os from 'node:os';
|
|
21
|
+
import { createLogger } from '@intutic/logger';
|
|
22
|
+
import { newIso } from '@intutic/id';
|
|
23
|
+
const log = createLogger('sync-claude-desktop-hooks');
|
|
24
|
+
/** 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
|
+
/** Resolve the OS-specific claude_desktop_config.json path. */
|
|
35
|
+
function resolveClaudeDesktopConfigPath() {
|
|
36
|
+
switch (process.platform) {
|
|
37
|
+
case 'darwin':
|
|
38
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
39
|
+
case 'linux':
|
|
40
|
+
return path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
41
|
+
default:
|
|
42
|
+
// Fallback to macOS path — Windows is out of scope for now
|
|
43
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ─── Node.js hook script template ─────────────────────────────────────────────
|
|
47
|
+
function buildClaudeDesktopCheckScript(proxyUrl, workspaceRoot, workspaceId) {
|
|
48
|
+
const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'claude-desktop-hook-events.jsonl');
|
|
49
|
+
const runtimeEnvPath = path.join(os.homedir(), '.intutic', 'env', 'runtime.env');
|
|
50
|
+
return `/**
|
|
51
|
+
* Intutic Claude Desktop PreToolUse execution gate.
|
|
52
|
+
* Auto-generated by intutic sync-daemon. DO NOT EDIT.
|
|
53
|
+
*
|
|
54
|
+
* Dual-path logEvent:
|
|
55
|
+
* Path B — synchronous file append to hook-events.jsonl
|
|
56
|
+
* Path A — fire-and-forget https.request to the control plane
|
|
57
|
+
*
|
|
58
|
+
* Proxy: ${proxyUrl}
|
|
59
|
+
* Generated: ${newIso()}
|
|
60
|
+
* Workspace: ${workspaceId}
|
|
61
|
+
*/
|
|
62
|
+
'use strict';
|
|
63
|
+
const path = require('path');
|
|
64
|
+
const os = require('os');
|
|
65
|
+
const fs = require('fs');
|
|
66
|
+
const https = require('https');
|
|
67
|
+
const crypto = require('crypto');
|
|
68
|
+
const url = require('url');
|
|
69
|
+
|
|
70
|
+
// ── Runtime env loader (Path A credentials) ──────────────────────────────────
|
|
71
|
+
const RUNTIME_ENV_PATH = ${JSON.stringify(runtimeEnvPath)};
|
|
72
|
+
|
|
73
|
+
function loadRuntimeEnv() {
|
|
74
|
+
const env = {
|
|
75
|
+
INTUTIC_HOST: 'https://api.intutic.ai',
|
|
76
|
+
INTUTIC_API_KEY: '',
|
|
77
|
+
INTUTIC_WORKSPACE_ID: ${JSON.stringify(workspaceId)},
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
const raw = fs.readFileSync(RUNTIME_ENV_PATH, 'utf-8');
|
|
81
|
+
for (const line of raw.split('\\n')) {
|
|
82
|
+
const stripped = line.trim();
|
|
83
|
+
if (!stripped || stripped.startsWith('#') || !stripped.includes('=')) continue;
|
|
84
|
+
const eq = stripped.indexOf('=');
|
|
85
|
+
const k = stripped.slice(0, eq).trim();
|
|
86
|
+
const v = stripped.slice(eq + 1).trim();
|
|
87
|
+
env[k] = v;
|
|
88
|
+
}
|
|
89
|
+
} catch { /* file may not exist yet */ }
|
|
90
|
+
return env;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Governance-sensitive paths ────────────────────────────────────────────────
|
|
94
|
+
const PROTECTED_PATHS = ${JSON.stringify(PROTECTED_PATHS)};
|
|
95
|
+
|
|
96
|
+
// ── Dual-path logEvent ────────────────────────────────────────────────────────
|
|
97
|
+
const HOOK_EVENTS_LOG = ${JSON.stringify(hookEventsLog)};
|
|
98
|
+
|
|
99
|
+
function logEvent(env, verdict, toolName, reason) {
|
|
100
|
+
const ts = new Date().toISOString();
|
|
101
|
+
const incidentId = crypto.createHash('sha1')
|
|
102
|
+
.update(ts + toolName + env.INTUTIC_WORKSPACE_ID)
|
|
103
|
+
.digest('hex').slice(0, 16);
|
|
104
|
+
|
|
105
|
+
const entry = {
|
|
106
|
+
event: verdict === 'blocked' ? 'tool_blocked' : 'tool_allowed',
|
|
107
|
+
toolName,
|
|
108
|
+
reason: reason || '',
|
|
109
|
+
workspaceId: env.INTUTIC_WORKSPACE_ID,
|
|
110
|
+
harnessType: 'claude-desktop',
|
|
111
|
+
timestamp: ts,
|
|
112
|
+
incidentId,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Path B — reliable file append
|
|
116
|
+
try {
|
|
117
|
+
const dir = path.dirname(HOOK_EVENTS_LOG);
|
|
118
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
119
|
+
fs.appendFileSync(HOOK_EVENTS_LOG, JSON.stringify(entry) + '\\n', { flag: 'a' });
|
|
120
|
+
} catch { /* never crash the hook */ }
|
|
121
|
+
|
|
122
|
+
// Path A — fire-and-forget https.request
|
|
123
|
+
if (env.INTUTIC_API_KEY) {
|
|
124
|
+
try {
|
|
125
|
+
const body = Buffer.from(JSON.stringify({ events: [entry] }));
|
|
126
|
+
const parsedUrl = url.parse(env.INTUTIC_HOST + '/api/v1/hook-events');
|
|
127
|
+
const req = https.request({
|
|
128
|
+
hostname: parsedUrl.hostname,
|
|
129
|
+
port: parsedUrl.port || 443,
|
|
130
|
+
path: parsedUrl.path,
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
'Content-Length': body.length,
|
|
135
|
+
'Authorization': 'Bearer ' + env.INTUTIC_API_KEY,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
req.on('error', () => {}); // fire-and-forget
|
|
139
|
+
req.write(body);
|
|
140
|
+
req.end();
|
|
141
|
+
} catch { /* never crash the hook */ }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
146
|
+
const runtimeEnv = loadRuntimeEnv();
|
|
147
|
+
let inputData = '';
|
|
148
|
+
process.stdin.on('data', (chunk) => { inputData += chunk; });
|
|
149
|
+
process.stdin.on('end', () => {
|
|
150
|
+
try {
|
|
151
|
+
const ctx = JSON.parse(inputData);
|
|
152
|
+
const toolName = (ctx.tool_name || ctx.toolName || '').toLowerCase();
|
|
153
|
+
const toolInput = ctx.tool_input || ctx.toolInput || {};
|
|
154
|
+
const toolInputStr = JSON.stringify(toolInput);
|
|
155
|
+
|
|
156
|
+
// 1. Protected-path guard
|
|
157
|
+
if (['edit', 'write', 'multiedit'].includes(toolName)) {
|
|
158
|
+
const targetPath = toolInput.path || toolInput.file_path ||
|
|
159
|
+
toolInput.new_path || toolInput.target || '';
|
|
160
|
+
for (const p of PROTECTED_PATHS) {
|
|
161
|
+
if (targetPath.includes(p) || String(targetPath).startsWith(p)) {
|
|
162
|
+
const reason = 'Attempt to modify governance-protected path: "' + targetPath + '". ' +
|
|
163
|
+
'To change governance policy, update your SOP via the Intutic control plane.';
|
|
164
|
+
console.error('[Intutic Guardrail] BLOCKED: ' + reason);
|
|
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
|
+
}
|
|
189
|
+
|
|
190
|
+
logEvent(runtimeEnv, 'allowed', toolName, '');
|
|
191
|
+
process.exit(0);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error('[Intutic Guardrail] Hook error (blocking for safety):', err);
|
|
194
|
+
logEvent(runtimeEnv, 'blocked', 'unknown', String(err));
|
|
195
|
+
process.exit(2);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
`.trim();
|
|
199
|
+
}
|
|
200
|
+
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
201
|
+
/**
|
|
202
|
+
* Write the Claude Desktop governance hook script and merge hook configuration
|
|
203
|
+
* into the OS-specific claude_desktop_config.json.
|
|
204
|
+
*
|
|
205
|
+
* Safe to call repeatedly — uses atomic rename (`.intutic-tmp` → final path).
|
|
206
|
+
* Does NOT touch the `mcpServers` section of the config.
|
|
207
|
+
*
|
|
208
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
209
|
+
* @param proxyUrl - Intutic proxy URL written into the hook script header.
|
|
210
|
+
* @param workspaceId - Workspace ID embedded in every hook event payload.
|
|
211
|
+
*/
|
|
212
|
+
export async function writeClaudeDesktopHooks(workspaceRoot, proxyUrl, workspaceId = '') {
|
|
213
|
+
// ── 1. Ensure directories ──────────────────────────────────────────────────
|
|
214
|
+
const hookScriptDir = path.join(workspaceRoot, '.intutic', 'hooks');
|
|
215
|
+
const hookEventsDir = path.join(workspaceRoot, '.intutic', 'events');
|
|
216
|
+
const desktopConfigPath = resolveClaudeDesktopConfigPath();
|
|
217
|
+
const desktopConfigDir = path.dirname(desktopConfigPath);
|
|
218
|
+
await Promise.all([
|
|
219
|
+
fs.mkdir(hookScriptDir, { recursive: true }),
|
|
220
|
+
fs.mkdir(hookEventsDir, { recursive: true }),
|
|
221
|
+
fs.mkdir(desktopConfigDir, { recursive: true }),
|
|
222
|
+
]);
|
|
223
|
+
// ── 2. Write hook script (atomic) ─────────────────────────────────────────
|
|
224
|
+
const hookScriptPath = path.join(hookScriptDir, 'claude-desktop-check.js');
|
|
225
|
+
const tmpScript = hookScriptPath + '.intutic-tmp';
|
|
226
|
+
await fs.writeFile(tmpScript, buildClaudeDesktopCheckScript(proxyUrl, workspaceRoot, workspaceId), 'utf-8');
|
|
227
|
+
await fs.rename(tmpScript, hookScriptPath);
|
|
228
|
+
log.info({ action: 'claude_desktop_hook_script_written', path: hookScriptPath }, 'Claude Desktop hook script written');
|
|
229
|
+
// ── 3. Merge into claude_desktop_config.json (safe merge, preserve mcpServers) ──
|
|
230
|
+
let existingConfig = {};
|
|
231
|
+
try {
|
|
232
|
+
const raw = await fs.readFile(desktopConfigPath, 'utf-8');
|
|
233
|
+
existingConfig = JSON.parse(raw);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// File doesn't exist yet — start fresh
|
|
237
|
+
}
|
|
238
|
+
const existingHooks = existingConfig.hooks ?? {};
|
|
239
|
+
const existingPreToolUse = existingHooks.PreToolUse ?? [];
|
|
240
|
+
// De-duplicate: remove any existing Intutic entry for Bash then re-add
|
|
241
|
+
const HOOK_MATCHERS = ['Bash', 'Edit', 'Write', 'MultiEdit'];
|
|
242
|
+
const intuticCmd = `node ${hookScriptPath}`;
|
|
243
|
+
const filteredPTU = existingPreToolUse.filter((entry) => {
|
|
244
|
+
if (typeof entry !== 'object' || entry === null)
|
|
245
|
+
return true;
|
|
246
|
+
const e = entry;
|
|
247
|
+
// Remove entries where all inner hooks use the intutic command
|
|
248
|
+
const innerHooks = e.hooks ?? [];
|
|
249
|
+
return !innerHooks.every((h) => typeof h === 'object' && h !== null && h.command === intuticCmd);
|
|
250
|
+
});
|
|
251
|
+
const newPreToolUseEntries = HOOK_MATCHERS.map((matcher) => ({
|
|
252
|
+
matcher,
|
|
253
|
+
hooks: [{ type: 'command', command: intuticCmd }],
|
|
254
|
+
}));
|
|
255
|
+
const mergedConfig = {
|
|
256
|
+
...existingConfig,
|
|
257
|
+
hooks: {
|
|
258
|
+
...existingHooks,
|
|
259
|
+
PreToolUse: [...filteredPTU, ...newPreToolUseEntries],
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
const tmpConfig = desktopConfigPath + '.intutic-tmp';
|
|
263
|
+
await fs.writeFile(tmpConfig, JSON.stringify(mergedConfig, null, 2) + '\n', 'utf-8');
|
|
264
|
+
await fs.rename(tmpConfig, desktopConfigPath);
|
|
265
|
+
log.info({ action: 'claude_desktop_config_written', path: desktopConfigPath }, 'Merged Intutic hooks into claude_desktop_config.json');
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=claudeDesktopHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeDesktopHooks.js","sourceRoot":"","sources":["../../src/harness/claudeDesktopHooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* clineHooks.ts — Cline PreToolUse governance hook injection.
|
|
3
|
+
*
|
|
4
|
+
* Writes .cline/hooks/hooks.json and the Intutic pre-tool-check
|
|
5
|
+
* script into the workspace. Cline's hook system accepts JSON on stdin
|
|
6
|
+
* and blocks execution when the script returns {"cancel": true}.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: Hooks are placed in .cline/hooks/ (NOT .clinerules/hooks/) to avoid
|
|
9
|
+
* ENOTDIR conflict — .clinerules is written as a flat file by the CLI adapter.
|
|
10
|
+
*
|
|
11
|
+
* LLD #14 — Phase 3 cross-harness defence
|
|
12
|
+
* HLD §3.14 — Three-Tier Defense Cascade
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Write the Intutic governance hook into .cline/hooks/.
|
|
18
|
+
*
|
|
19
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
20
|
+
* @param proxyUrl - Intutic proxy URL for inclusion in block messages.
|
|
21
|
+
*/
|
|
22
|
+
export declare function writeClineHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=clineHooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clineHooks.d.ts","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAoCH;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CA6Hf"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* clineHooks.ts — Cline PreToolUse governance hook injection.
|
|
3
|
+
*
|
|
4
|
+
* Writes .cline/hooks/hooks.json and the Intutic pre-tool-check
|
|
5
|
+
* script into the workspace. Cline's hook system accepts JSON on stdin
|
|
6
|
+
* and blocks execution when the script returns {"cancel": true}.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: Hooks are placed in .cline/hooks/ (NOT .clinerules/hooks/) to avoid
|
|
9
|
+
* ENOTDIR conflict — .clinerules is written as a flat file by the CLI adapter.
|
|
10
|
+
*
|
|
11
|
+
* LLD #14 — Phase 3 cross-harness defence
|
|
12
|
+
* HLD §3.14 — Three-Tier Defense Cascade
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'node:fs/promises';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import { createLogger } from '@intutic/logger';
|
|
19
|
+
import { newIso } from '@intutic/id';
|
|
20
|
+
const log = createLogger('sync-cline-hooks');
|
|
21
|
+
/** Tools Cline can invoke that require governance interception. */
|
|
22
|
+
const GOVERNED_TOOLS = [
|
|
23
|
+
'execute_command',
|
|
24
|
+
'write_to_file',
|
|
25
|
+
'str_replace_based_edit_tool',
|
|
26
|
+
'replace_in_file',
|
|
27
|
+
'create_file',
|
|
28
|
+
'delete_file',
|
|
29
|
+
'rename_file',
|
|
30
|
+
'read_file', // watch for exfiltration patterns
|
|
31
|
+
];
|
|
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
|
+
/**
|
|
47
|
+
* Write the Intutic governance hook into .cline/hooks/.
|
|
48
|
+
*
|
|
49
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
50
|
+
* @param proxyUrl - Intutic proxy URL for inclusion in block messages.
|
|
51
|
+
*/
|
|
52
|
+
export async function writeClineHooks(workspaceRoot, proxyUrl, workspaceId = '') {
|
|
53
|
+
const hooksDir = path.join(workspaceRoot, '.cline', 'hooks');
|
|
54
|
+
const hooksJsonPath = path.join(hooksDir, 'hooks.json');
|
|
55
|
+
const checkScriptPath = path.join(hooksDir, 'intutic-check.js');
|
|
56
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
57
|
+
// ── hooks.json ──────────────────────────────────────────────────────
|
|
58
|
+
// Cline reads this file and runs matched hook scripts before tool execution.
|
|
59
|
+
// The matcher regex is matched against the tool name.
|
|
60
|
+
const hooksConfig = {
|
|
61
|
+
_comment: 'Intutic governance hooks — auto-generated. DO NOT EDIT.',
|
|
62
|
+
_lastSync: newIso(),
|
|
63
|
+
hooks: GOVERNED_TOOLS.map((tool) => ({
|
|
64
|
+
type: 'PreToolUse',
|
|
65
|
+
matcher: `^${tool}$`,
|
|
66
|
+
command: `node ${checkScriptPath}`,
|
|
67
|
+
})),
|
|
68
|
+
};
|
|
69
|
+
const tmpJson = hooksJsonPath + '.intutic-tmp';
|
|
70
|
+
await fs.writeFile(tmpJson, JSON.stringify(hooksConfig, null, 2) + '\n', 'utf-8');
|
|
71
|
+
await fs.rename(tmpJson, hooksJsonPath);
|
|
72
|
+
// ── pre-tool-check script ────────────────────────────────────────────
|
|
73
|
+
// Cline passes tool_name + tool_input as JSON on stdin.
|
|
74
|
+
// Return {"cancel": false} to allow, {"cancel": true, "errorMessage": "..."} to block.
|
|
75
|
+
const checkScript = `#!/usr/bin/env node
|
|
76
|
+
/**
|
|
77
|
+
* Intutic Cline PreToolUse governance gate.
|
|
78
|
+
* Auto-generated by intutic sync-daemon. DO NOT EDIT.
|
|
79
|
+
* Proxy: ${proxyUrl}
|
|
80
|
+
* Generated: ${newIso()}
|
|
81
|
+
*/
|
|
82
|
+
const path = require('path');
|
|
83
|
+
const os = require('os');
|
|
84
|
+
const fs = require('fs');
|
|
85
|
+
const crypto = require('crypto');
|
|
86
|
+
const https = require('https');
|
|
87
|
+
|
|
88
|
+
// Runtime credentials
|
|
89
|
+
const _runtimeEnvPath = path.join(os.homedir(), '.intutic', 'env', 'runtime.env');
|
|
90
|
+
let _intuticHost = 'https://api.intutic.ai', _intuticKey = '', _intuticWsId = ${JSON.stringify(workspaceId)};
|
|
91
|
+
try {
|
|
92
|
+
fs.readFileSync(_runtimeEnvPath, 'utf-8').split('\\n').forEach(line => {
|
|
93
|
+
const eq = line.indexOf('='); if (eq < 0) return;
|
|
94
|
+
const k = line.slice(0, eq).trim(), v = line.slice(eq + 1).trim();
|
|
95
|
+
if (k === 'INTUTIC_HOST' && v) _intuticHost = v;
|
|
96
|
+
if (k === 'INTUTIC_API_KEY' && v) _intuticKey = v;
|
|
97
|
+
if (k === 'INTUTIC_WORKSPACE_ID' && v) _intuticWsId = v;
|
|
98
|
+
});
|
|
99
|
+
} catch {}
|
|
100
|
+
|
|
101
|
+
const HOOK_EVENTS_LOG = path.join(os.homedir(), '.intutic', 'events', 'cline-hook-events.jsonl');
|
|
102
|
+
|
|
103
|
+
function logEvent(verdict, toolName, reason) {
|
|
104
|
+
try {
|
|
105
|
+
const ts = new Date().toISOString();
|
|
106
|
+
const incidentId = crypto.createHash('sha1').update(ts + toolName + _intuticWsId).digest('hex').slice(0, 16);
|
|
107
|
+
const entry = JSON.stringify({ event: verdict === 'blocked' ? 'tool_blocked' : 'tool_allowed', toolName, reason: reason || '', workspaceId: _intuticWsId, harnessType: 'cline', timestamp: ts, incidentId }) + '\\n';
|
|
108
|
+
try { fs.appendFileSync(HOOK_EVENTS_LOG, entry, { flag: 'a' }); } catch {}
|
|
109
|
+
if (_intuticKey) {
|
|
110
|
+
try {
|
|
111
|
+
const body = JSON.stringify({ events: [JSON.parse(entry)] });
|
|
112
|
+
const urlObj = new URL('/api/v1/hook-events', _intuticHost);
|
|
113
|
+
const mod = urlObj.protocol === 'https:' ? https : require('http');
|
|
114
|
+
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 } });
|
|
115
|
+
req.on('error', () => {});
|
|
116
|
+
req.write(body); req.end();
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const PROTECTED_PATHS = ${JSON.stringify(PROTECTED_PATH_FRAGMENTS)};
|
|
123
|
+
|
|
124
|
+
let raw = '';
|
|
125
|
+
process.stdin.on('data', (c) => { raw += c; });
|
|
126
|
+
process.stdin.on('end', () => {
|
|
127
|
+
try {
|
|
128
|
+
const ctx = JSON.parse(raw);
|
|
129
|
+
const tool = (ctx.tool_name || '').toLowerCase();
|
|
130
|
+
const input = ctx.tool_input || {};
|
|
131
|
+
const inputStr = JSON.stringify(input);
|
|
132
|
+
|
|
133
|
+
// Protected-path guard: block any write/edit/delete targeting governance files
|
|
134
|
+
const targetPath = input.path || input.file_path || input.target || input.source || '';
|
|
135
|
+
if (['write_to_file','str_replace_based_edit_tool','replace_in_file','create_file','delete_file','rename_file'].includes(tool)) {
|
|
136
|
+
for (const p of PROTECTED_PATHS) {
|
|
137
|
+
if (String(targetPath).includes(p)) {
|
|
138
|
+
logEvent('blocked', tool, 'Blocked: attempt to modify governance-protected path "' + targetPath + '"');
|
|
139
|
+
process.stdout.write(JSON.stringify({
|
|
140
|
+
cancel: true,
|
|
141
|
+
errorMessage: '[Intutic Governance] Blocked: attempt to modify governance-protected path "' + targetPath + '". Update policy via the Intutic control plane.',
|
|
142
|
+
}));
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Allow all other tool calls
|
|
149
|
+
logEvent('allowed', tool || 'unknown', '');
|
|
150
|
+
process.stdout.write(JSON.stringify({ cancel: false }));
|
|
151
|
+
process.exit(0);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
// Fail CLOSED — hook parse error blocks the tool call
|
|
154
|
+
logEvent('blocked', 'unknown', String(err));
|
|
155
|
+
process.stdout.write(JSON.stringify({
|
|
156
|
+
cancel: true,
|
|
157
|
+
errorMessage: '[Intutic Governance] Hook error (fail-closed): ' + String(err),
|
|
158
|
+
}));
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
`;
|
|
163
|
+
const tmpScript = checkScriptPath + '.intutic-tmp';
|
|
164
|
+
await fs.writeFile(tmpScript, checkScript, 'utf-8');
|
|
165
|
+
await fs.rename(tmpScript, checkScriptPath);
|
|
166
|
+
await fs.chmod(checkScriptPath, 0o755);
|
|
167
|
+
log.info({ action: 'cline_hooks_written', hooksDir, toolCount: GOVERNED_TOOLS.length }, 'Cline governance hooks written');
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=clineHooks.js.map
|
|
@@ -0,0 +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;AAEpC,MAAM,GAAG,GAAG,YAAY,CAAC,kBAAkB,CAAC,CAAA;AAE5C,mEAAmE;AACnE,MAAM,cAAc,GAAG;IACrB,iBAAiB;IACjB,eAAe;IACf,6BAA6B;IAC7B,iBAAiB;IACjB,aAAa;IACb,aAAa;IACb,aAAa;IACb,WAAW,EAAG,kCAAkC;CACjD,CAAA;AAED;;;GAGG;AACH,MAAM,wBAAwB,GAAG;IAC/B,cAAc;IACd,gBAAgB;IAChB,yBAAyB;IACzB,uBAAuB;IACvB,oBAAoB;IACpB,8BAA8B;IAC9B,oCAAoC;IACpC,uBAAuB;CACxB,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,aAAqB,EACrB,QAAgB,EAChB,WAAW,GAAG,EAAE;IAEhB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC5D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAA;IACvD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAA;IAE/D,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAE7C,uEAAuE;IACvE,6EAA6E;IAC7E,sDAAsD;IACtD,MAAM,WAAW,GAAG;QAClB,QAAQ,EAAE,yDAAyD;QACnE,SAAS,EAAE,MAAM,EAAE;QACnB,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,IAAI,IAAI,GAAG;YACpB,OAAO,EAAE,QAAQ,eAAe,EAAE;SACnC,CAAC,CAAC;KACJ,CAAA;IAED,MAAM,OAAO,GAAG,aAAa,GAAG,cAAc,CAAA;IAC9C,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAA;IACjF,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAEvC,wEAAwE;IACxE,wDAAwD;IACxD,uFAAuF;IACvF,MAAM,WAAW,GAAG;;;;YAIV,QAAQ;gBACJ,MAAM,EAAE;;;;;;;;;;gFAUwD,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAgCjF,IAAI,CAAC,SAAS,CAAC,wBAAwB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCjE,CAAA;IAEC,MAAM,SAAS,GAAG,eAAe,GAAG,cAAc,CAAA;IAClD,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;IACnD,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,CAAA;IAC3C,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAA;IAEtC,GAAG,CAAC,IAAI,CACN,EAAE,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,CAAC,MAAM,EAAE,EAC7E,gCAAgC,CACjC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* continueHooks.ts — Continue.dev governance via proxy routing.
|
|
3
|
+
*
|
|
4
|
+
* Continue.dev has no native hook system. Governance is applied by routing
|
|
5
|
+
* all model API calls through the Intutic proxy: each model's `apiBase`
|
|
6
|
+
* field is set to `proxyUrl` in `~/.continue/config.json`.
|
|
7
|
+
*
|
|
8
|
+
* JSONC handling: ~/.continue/config.json allows `// comments`. We strip
|
|
9
|
+
* single-line comments with a regex before parsing, then write back as
|
|
10
|
+
* standard JSON with a managed-file header comment.
|
|
11
|
+
*
|
|
12
|
+
* LLD #14 — Phase 3 cross-harness defence (Gap 3, WS-B)
|
|
13
|
+
* HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Configure Continue.dev to route all model API calls through the Intutic proxy.
|
|
19
|
+
*
|
|
20
|
+
* 1. Reads `~/.continue/config.json` (JSONC — strips // comments before parsing).
|
|
21
|
+
* 2. Sets `apiBase = proxyUrl` on each model that doesn't already use it.
|
|
22
|
+
* 3. Adds a `customInstructions` governance notice.
|
|
23
|
+
* 4. Writes the merged result back atomically.
|
|
24
|
+
* 5. Writes `.intutic/env/continue.env` with the proxy URL.
|
|
25
|
+
*
|
|
26
|
+
* Safe to call repeatedly — uses atomic rename (`.intutic-tmp` → final path).
|
|
27
|
+
*
|
|
28
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
29
|
+
* @param proxyUrl - Intutic proxy URL to set as model apiBase.
|
|
30
|
+
* @param workspaceId - Workspace ID stored in the env snippet.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeContinueHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=continueHooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continueHooks.d.ts","sourceRoot":"","sources":["../../src/harness/continueHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAmCH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAqFf"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* continueHooks.ts — Continue.dev governance via proxy routing.
|
|
3
|
+
*
|
|
4
|
+
* Continue.dev has no native hook system. Governance is applied by routing
|
|
5
|
+
* all model API calls through the Intutic proxy: each model's `apiBase`
|
|
6
|
+
* field is set to `proxyUrl` in `~/.continue/config.json`.
|
|
7
|
+
*
|
|
8
|
+
* JSONC handling: ~/.continue/config.json allows `// comments`. We strip
|
|
9
|
+
* single-line comments with a regex before parsing, then write back as
|
|
10
|
+
* standard JSON with a managed-file header comment.
|
|
11
|
+
*
|
|
12
|
+
* LLD #14 — Phase 3 cross-harness defence (Gap 3, WS-B)
|
|
13
|
+
* HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from 'node:fs/promises';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import * as os from 'node:os';
|
|
20
|
+
import { createLogger } from '@intutic/logger';
|
|
21
|
+
import { newIso } from '@intutic/id';
|
|
22
|
+
const log = createLogger('sync-continue-hooks');
|
|
23
|
+
/** Default path to the Continue.dev global config file. */
|
|
24
|
+
const CONTINUE_CONFIG_PATH = path.join(os.homedir(), '.continue', 'config.json');
|
|
25
|
+
/** Governance notice injected into customInstructions. */
|
|
26
|
+
const GOVERNANCE_NOTICE = [
|
|
27
|
+
'This workspace is governed by Intutic SOP policies.',
|
|
28
|
+
'All LLM API requests are routed through the Intutic governance proxy.',
|
|
29
|
+
'Do not remove or override the apiBase field in this configuration.',
|
|
30
|
+
].join(' ');
|
|
31
|
+
// ─── JSONC comment stripper ───────────────────────────────────────────────────
|
|
32
|
+
/**
|
|
33
|
+
* Strip single-line `// ...` comments from a JSONC string so it can be
|
|
34
|
+
* parsed by JSON.parse. Block comments (`/* ... *\/`) are not stripped
|
|
35
|
+
* because they are uncommon in Continue config files.
|
|
36
|
+
*/
|
|
37
|
+
function stripJsoncComments(raw) {
|
|
38
|
+
// Remove // comments that are not inside strings.
|
|
39
|
+
// Simple approach: replace // up to end-of-line (acceptable for Continue config).
|
|
40
|
+
return raw.replace(/\/\/[^\n]*/g, '');
|
|
41
|
+
}
|
|
42
|
+
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
43
|
+
/**
|
|
44
|
+
* Configure Continue.dev to route all model API calls through the Intutic proxy.
|
|
45
|
+
*
|
|
46
|
+
* 1. Reads `~/.continue/config.json` (JSONC — strips // comments before parsing).
|
|
47
|
+
* 2. Sets `apiBase = proxyUrl` on each model that doesn't already use it.
|
|
48
|
+
* 3. Adds a `customInstructions` governance notice.
|
|
49
|
+
* 4. Writes the merged result back atomically.
|
|
50
|
+
* 5. Writes `.intutic/env/continue.env` with the proxy URL.
|
|
51
|
+
*
|
|
52
|
+
* Safe to call repeatedly — uses atomic rename (`.intutic-tmp` → final path).
|
|
53
|
+
*
|
|
54
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
55
|
+
* @param proxyUrl - Intutic proxy URL to set as model apiBase.
|
|
56
|
+
* @param workspaceId - Workspace ID stored in the env snippet.
|
|
57
|
+
*/
|
|
58
|
+
export async function writeContinueHooks(workspaceRoot, proxyUrl, workspaceId = '') {
|
|
59
|
+
// ── 1. Ensure directories ──────────────────────────────────────────────────
|
|
60
|
+
const continueDir = path.join(os.homedir(), '.continue');
|
|
61
|
+
const envDir = path.join(workspaceRoot, '.intutic', 'env');
|
|
62
|
+
await Promise.all([
|
|
63
|
+
fs.mkdir(continueDir, { recursive: true }),
|
|
64
|
+
fs.mkdir(envDir, { recursive: true }),
|
|
65
|
+
]);
|
|
66
|
+
// ── 2. Read and parse ~/.continue/config.json ─────────────────────────────
|
|
67
|
+
let configObj = {};
|
|
68
|
+
try {
|
|
69
|
+
const raw = await fs.readFile(CONTINUE_CONFIG_PATH, 'utf-8');
|
|
70
|
+
const stripped = stripJsoncComments(raw);
|
|
71
|
+
configObj = JSON.parse(stripped);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// File doesn't exist or is malformed — start with empty config
|
|
75
|
+
log.warn({ action: 'continue_config_read_failed', path: CONTINUE_CONFIG_PATH }, 'Could not read ~/.continue/config.json — creating fresh');
|
|
76
|
+
}
|
|
77
|
+
// ── 3. Update model apiBase fields ────────────────────────────────────────
|
|
78
|
+
const models = Array.isArray(configObj.models) ? configObj.models : [];
|
|
79
|
+
let modifiedCount = 0;
|
|
80
|
+
const updatedModels = models.map((model) => {
|
|
81
|
+
if (typeof model !== 'object' || model === null)
|
|
82
|
+
return model;
|
|
83
|
+
const m = model;
|
|
84
|
+
if (m.apiBase === proxyUrl)
|
|
85
|
+
return m; // Already set — no-op
|
|
86
|
+
modifiedCount++;
|
|
87
|
+
return { ...m, apiBase: proxyUrl };
|
|
88
|
+
});
|
|
89
|
+
// ── 4. Build merged config ────────────────────────────────────────────────
|
|
90
|
+
const mergedConfig = {
|
|
91
|
+
...configObj,
|
|
92
|
+
// Re-attach updated models array (or keep original if empty)
|
|
93
|
+
...(models.length > 0 ? { models: updatedModels } : {}),
|
|
94
|
+
// Add/overwrite customInstructions with governance notice
|
|
95
|
+
customInstructions: GOVERNANCE_NOTICE,
|
|
96
|
+
// Metadata fields
|
|
97
|
+
_intutic_managed: true,
|
|
98
|
+
_intutic_last_sync: newIso(),
|
|
99
|
+
_intutic_workspace_id: workspaceId,
|
|
100
|
+
};
|
|
101
|
+
// ── 5. Write back atomically ──────────────────────────────────────────────
|
|
102
|
+
// Prepend a managed-file warning as a comment (will be lost on next read —
|
|
103
|
+
// that's acceptable; we re-add it on each sync).
|
|
104
|
+
const managedHeader = `// AUTO-MANAGED by Intutic sync-daemon. Last sync: ${newIso()}\n// Do not edit apiBase fields — they are governance proxy routes.\n`;
|
|
105
|
+
const serialized = managedHeader + JSON.stringify(mergedConfig, null, 2) + '\n';
|
|
106
|
+
const tmpConfig = CONTINUE_CONFIG_PATH + '.intutic-tmp';
|
|
107
|
+
await fs.writeFile(tmpConfig, serialized, 'utf-8');
|
|
108
|
+
await fs.rename(tmpConfig, CONTINUE_CONFIG_PATH);
|
|
109
|
+
log.info({ action: 'continue_config_written', path: CONTINUE_CONFIG_PATH, modifiedModels: modifiedCount }, 'Continue.dev config patched with Intutic proxy apiBase');
|
|
110
|
+
// ── 6. Write .intutic/env/continue.env ────────────────────────────────────
|
|
111
|
+
const envContent = [
|
|
112
|
+
`# Intutic Continue.dev governance env — auto-generated ${newIso()}`,
|
|
113
|
+
`# This file is managed by the Intutic sync-daemon. DO NOT EDIT.`,
|
|
114
|
+
`CONTINUE_PROXY_URL=${proxyUrl}`,
|
|
115
|
+
workspaceId ? `INTUTIC_WORKSPACE_ID=${workspaceId}` : '',
|
|
116
|
+
`# All Continue.dev model requests are routed through CONTINUE_PROXY_URL.`,
|
|
117
|
+
].filter((l) => l !== '').join('\n') + '\n';
|
|
118
|
+
const envFilePath = path.join(envDir, 'continue.env');
|
|
119
|
+
const tmpEnv = envFilePath + '.intutic-tmp';
|
|
120
|
+
await fs.writeFile(tmpEnv, envContent, 'utf-8');
|
|
121
|
+
await fs.rename(tmpEnv, envFilePath);
|
|
122
|
+
log.info({ action: 'continue_env_written', path: envFilePath }, 'Continue.dev env snippet written');
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=continueHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continueHooks.js","sourceRoot":"","sources":["../../src/harness/continueHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;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;AAEpC,MAAM,GAAG,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAA;AAE/C,2DAA2D;AAC3D,MAAM,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,aAAa,CAAC,CAAA;AAEhF,0DAA0D;AAC1D,MAAM,iBAAiB,GAAG;IACxB,qDAAqD;IACrD,uEAAuE;IACvE,oEAAoE;CACrE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEX,iFAAiF;AAEjF;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,GAAW;IACrC,kDAAkD;IAClD,kFAAkF;IAClF,OAAO,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;AACvC,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,aAAqB,EACrB,QAAgB,EAChB,WAAW,GAAG,EAAE;IAEhB,8EAA8E;IAE9E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAA;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,EAAE,KAAK,CAAC,CAAA;IAE1D,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC1C,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;KACtC,CAAC,CAAA;IAEF,6EAA6E;IAE7E,IAAI,SAAS,GAA4B,EAAE,CAAA;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAA;QAC5D,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAA;QACxC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;QAC/D,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,6BAA6B,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,yDAAyD,CAAC,CAAA;IAC5I,CAAC;IAED,6EAA6E;IAE7E,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAA;IACtE,IAAI,aAAa,GAAG,CAAC,CAAA;IAErB,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAc,EAAE,EAAE;QAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAA;QAC7D,MAAM,CAAC,GAAG,KAAgC,CAAA;QAC1C,IAAI,CAAC,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAA,CAAC,sBAAsB;QAC3D,aAAa,EAAE,CAAA;QACf,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,6EAA6E;IAE7E,MAAM,YAAY,GAA4B;QAC5C,GAAG,SAAS;QACZ,6DAA6D;QAC7D,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,0DAA0D;QAC1D,kBAAkB,EAAE,iBAAiB;QACrC,kBAAkB;QAClB,gBAAgB,EAAE,IAAI;QACtB,kBAAkB,EAAE,MAAM,EAAE;QAC5B,qBAAqB,EAAE,WAAW;KACnC,CAAA;IAED,6EAA6E;IAE7E,2EAA2E;IAC3E,iDAAiD;IACjD,MAAM,aAAa,GAAG,sDAAsD,MAAM,EAAE,uEAAuE,CAAA;IAC3J,MAAM,UAAU,GAAG,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;IAE/E,MAAM,SAAS,GAAG,oBAAoB,GAAG,cAAc,CAAA;IACvD,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IAClD,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAA;IAEhD,GAAG,CAAC,IAAI,CACN,EAAE,MAAM,EAAE,yBAAyB,EAAE,IAAI,EAAE,oBAAoB,EAAE,cAAc,EAAE,aAAa,EAAE,EAChG,wDAAwD,CACzD,CAAA;IAED,6EAA6E;IAE7E,MAAM,UAAU,GAAG;QACjB,0DAA0D,MAAM,EAAE,EAAE;QACpE,iEAAiE;QACjE,sBAAsB,QAAQ,EAAE;QAChC,WAAW,CAAC,CAAC,CAAC,wBAAwB,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE;QACxD,0EAA0E;KAC3E,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAE3C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG,WAAW,GAAG,cAAc,CAAA;IAC3C,MAAM,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IAC/C,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IAEpC,GAAG,CAAC,IAAI,CACN,EAAE,MAAM,EAAE,sBAAsB,EAAE,IAAI,EAAE,WAAW,EAAE,EACrD,kCAAkC,CACnC,CAAA;AACH,CAAC"}
|