@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 @@
|
|
|
1
|
+
{"version":3,"file":"gooseHooks.d.ts","sourceRoot":"","sources":["../../src/harness/gooseHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAwBH;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,SAAe,EAAE,WAAW,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAqHrH"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gooseHooks.ts — Goose PreToolUse governance plugin injection.
|
|
3
|
+
*
|
|
4
|
+
* Writes the Intutic governance plugin to the Goose plugin directory:
|
|
5
|
+
* ~/.agents/plugins/intutic-governance/hooks/hooks.json
|
|
6
|
+
* ~/.agents/plugins/intutic-governance/scripts/intutic-check.sh
|
|
7
|
+
*
|
|
8
|
+
* Also merges the Intutic proxy URL into ~/.config/goose/config.yaml.
|
|
9
|
+
* After writing, calls gooseHardener to apply chmod 444 + immutable flags.
|
|
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 * as os from 'node:os';
|
|
19
|
+
import { createLogger } from '@intutic/logger';
|
|
20
|
+
import { newIso } from '@intutic/id';
|
|
21
|
+
import { hardenGoosePlugin, unharden } from './gooseHardener.js';
|
|
22
|
+
const log = createLogger('sync-goose-hooks');
|
|
23
|
+
const PLUGIN_DIR = path.join(os.homedir(), '.agents', 'plugins', 'intutic-governance');
|
|
24
|
+
const GOOSE_CONFIG = path.join(os.homedir(), '.config', 'goose', 'config.yaml');
|
|
25
|
+
const PROTECTED_PATHS = [
|
|
26
|
+
'.agents/plugins/intutic-governance',
|
|
27
|
+
'.intutic/hooks',
|
|
28
|
+
'.intutic/integrity.json',
|
|
29
|
+
'.claude/settings.json',
|
|
30
|
+
'.cursor/hooks.json',
|
|
31
|
+
'.cline/hooks',
|
|
32
|
+
'.openhands/hooks.json',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Write Goose governance plugin and harden it.
|
|
36
|
+
*
|
|
37
|
+
* @param proxyUrl - Intutic proxy URL written into the plugin script.
|
|
38
|
+
* @param workspaceRoot - Absolute workspace root (for hook events log path).
|
|
39
|
+
* @param workspaceId - Workspace ID embedded in hook event payloads.
|
|
40
|
+
*/
|
|
41
|
+
export async function writeGooseHooks(proxyUrl, workspaceRoot = os.homedir(), workspaceId = '') {
|
|
42
|
+
const hooksDir = path.join(PLUGIN_DIR, 'hooks');
|
|
43
|
+
const scriptsDir = path.join(PLUGIN_DIR, 'scripts');
|
|
44
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
45
|
+
await fs.mkdir(scriptsDir, { recursive: true });
|
|
46
|
+
// ── hooks.json ──────────────────────────────────────────────────────
|
|
47
|
+
const hooksConfig = {
|
|
48
|
+
_comment: 'Intutic governance plugin — auto-generated. DO NOT EDIT.',
|
|
49
|
+
_lastSync: newIso(),
|
|
50
|
+
name: 'intutic-governance',
|
|
51
|
+
version: '1.0.0',
|
|
52
|
+
hooks: {
|
|
53
|
+
PreToolUse: {
|
|
54
|
+
command: path.join(scriptsDir, 'intutic-check.sh'),
|
|
55
|
+
failClosed: true,
|
|
56
|
+
},
|
|
57
|
+
PostToolUse: {
|
|
58
|
+
command: path.join(scriptsDir, 'intutic-check.sh --event post'),
|
|
59
|
+
failClosed: false,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const hooksJsonPath = path.join(hooksDir, 'hooks.json');
|
|
64
|
+
// Unharden before writing (in case we're refreshing an existing install)
|
|
65
|
+
await unharden(hooksJsonPath);
|
|
66
|
+
const tmpHooks = hooksJsonPath + '.intutic-tmp';
|
|
67
|
+
await fs.writeFile(tmpHooks, JSON.stringify(hooksConfig, null, 2) + '\n', 'utf-8');
|
|
68
|
+
await fs.rename(tmpHooks, hooksJsonPath);
|
|
69
|
+
// ── intutic-check.sh ─────────────────────────────────────────────────
|
|
70
|
+
const checkScriptPath = path.join(scriptsDir, 'intutic-check.sh');
|
|
71
|
+
await unharden(checkScriptPath);
|
|
72
|
+
const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'hook-events.jsonl');
|
|
73
|
+
// Ensure events dir exists
|
|
74
|
+
await fs.mkdir(path.join(workspaceRoot, '.intutic', 'events'), { recursive: true });
|
|
75
|
+
const checkScript = `#!/usr/bin/env bash
|
|
76
|
+
# Intutic Goose PreToolUse governance gate.
|
|
77
|
+
# Auto-generated by intutic sync-daemon. DO NOT EDIT.
|
|
78
|
+
# Proxy: ${proxyUrl}
|
|
79
|
+
# Generated: ${newIso()}
|
|
80
|
+
set -euo pipefail
|
|
81
|
+
|
|
82
|
+
EVENT="\${1:-pre}"
|
|
83
|
+
INPUT="\$(cat)"
|
|
84
|
+
TOOL="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null || true)"
|
|
85
|
+
TARGET="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); i=d.get('tool_input',{}); print(i.get('path',i.get('file_path','')))" 2>/dev/null || true)"
|
|
86
|
+
|
|
87
|
+
HOOK_EVENTS_LOG=${JSON.stringify(hookEventsLog)}
|
|
88
|
+
|
|
89
|
+
# ── Runtime credentials (sourced from ~/.intutic/env/runtime.env) ────────────────────────
|
|
90
|
+
RUNTIME_ENV="$HOME/.intutic/env/runtime.env"
|
|
91
|
+
INTUITIC_HOST="https://api.intutic.ai"
|
|
92
|
+
INTUITIC_API_KEY=""
|
|
93
|
+
INTUITIC_WORKSPACE_ID=${JSON.stringify(workspaceId)}
|
|
94
|
+
[ -f "$RUNTIME_ENV" ] && source "$RUNTIME_ENV" 2>/dev/null || true
|
|
95
|
+
|
|
96
|
+
log_event() {
|
|
97
|
+
local verdict="\$1" tool="\$2" reason="\$3"
|
|
98
|
+
local ts; ts="\$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
99
|
+
local incident_id; incident_id="\$(printf '%s' "\${ts}\${tool}\${INTUTIC_WORKSPACE_ID}" | sha1sum 2>/dev/null | cut -c1-16 || echo \"\$(date +%s)\")"
|
|
100
|
+
local entry="{\"event\":\"\${verdict}\",\"toolName\":\"\${tool}\",\"reason\":\"\${reason}\",\"workspaceId\":\"\${INTUTIC_WORKSPACE_ID}\",\"harnessType\":\"goose\",\"timestamp\":\"\${ts}\",\"incidentId\":\"\${incident_id}\"}"
|
|
101
|
+
# Path B: reliable file append
|
|
102
|
+
printf '%s\\n' "\$entry" >> "\$HOOK_EVENTS_LOG" 2>/dev/null || true
|
|
103
|
+
# Path A: fire-and-forget HTTP (non-blocking, near-real-time)
|
|
104
|
+
if [ -n "\$INTUTIC_API_KEY" ]; then
|
|
105
|
+
curl -s -o /dev/null --max-time 3 -X POST \\
|
|
106
|
+
-H "Content-Type: application/json" \\
|
|
107
|
+
-H "Authorization: Bearer \$INTUTIC_API_KEY" \\
|
|
108
|
+
-d "{\"events\":[\$entry]}" \\
|
|
109
|
+
"\${INTUTIC_HOST}/api/v1/hook-events" & disown 2>/dev/null || true
|
|
110
|
+
fi
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
PROTECTED_PATHS=(
|
|
114
|
+
".agents/plugins/intutic-governance"
|
|
115
|
+
".intutic/hooks"
|
|
116
|
+
".intutic/integrity.json"
|
|
117
|
+
".claude/settings.json"
|
|
118
|
+
".cursor/hooks.json"
|
|
119
|
+
".cline/hooks"
|
|
120
|
+
".openhands/hooks.json"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
for p in "\${PROTECTED_PATHS[@]}"; do
|
|
124
|
+
if echo "$TARGET" | grep -qF "$p"; then
|
|
125
|
+
REASON="Attempt to modify governance-protected path \\"$TARGET\\""
|
|
126
|
+
echo "[Intutic Governance] BLOCKED: $REASON" >&2
|
|
127
|
+
log_event "tool_blocked" "\$TOOL" "\$REASON"
|
|
128
|
+
exit 2
|
|
129
|
+
fi
|
|
130
|
+
done
|
|
131
|
+
|
|
132
|
+
log_event "tool_allowed" "\$TOOL" ""
|
|
133
|
+
exit 0
|
|
134
|
+
`;
|
|
135
|
+
const tmpScript = checkScriptPath + '.intutic-tmp';
|
|
136
|
+
await fs.writeFile(tmpScript, checkScript, 'utf-8');
|
|
137
|
+
await fs.rename(tmpScript, checkScriptPath);
|
|
138
|
+
await fs.chmod(checkScriptPath, 0o755);
|
|
139
|
+
// Apply immutable flags after writing
|
|
140
|
+
await hardenGoosePlugin(PLUGIN_DIR);
|
|
141
|
+
log.info({ action: 'goose_plugin_written', pluginDir: PLUGIN_DIR }, 'Goose governance plugin written and hardened');
|
|
142
|
+
// ── Merge proxy URL into goose config.yaml ───────────────────────────
|
|
143
|
+
await mergeGooseConfig(proxyUrl);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Merge provider host URL into ~/.config/goose/config.yaml.
|
|
147
|
+
*/
|
|
148
|
+
async function mergeGooseConfig(proxyUrl) {
|
|
149
|
+
let existing = '';
|
|
150
|
+
try {
|
|
151
|
+
existing = await fs.readFile(GOOSE_CONFIG, 'utf-8');
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
existing = '';
|
|
155
|
+
}
|
|
156
|
+
// Inject or update provider.host
|
|
157
|
+
// Goose config.yaml format: provider.host or OPENAI_HOST env var
|
|
158
|
+
if (existing.includes('provider:')) {
|
|
159
|
+
if (existing.includes(' host:')) {
|
|
160
|
+
existing = existing.replace(/^\s*host:\s*.*/m, ` host: "${proxyUrl}"`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
existing = existing.replace(/^provider:/m, `provider:\n host: "${proxyUrl}"`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
existing += `\nprovider:\n host: "${proxyUrl}"\n`;
|
|
168
|
+
}
|
|
169
|
+
const tmpConfig = GOOSE_CONFIG + '.intutic-tmp';
|
|
170
|
+
await fs.mkdir(path.dirname(GOOSE_CONFIG), { recursive: true });
|
|
171
|
+
await fs.writeFile(tmpConfig, existing, 'utf-8');
|
|
172
|
+
await fs.rename(tmpConfig, GOOSE_CONFIG);
|
|
173
|
+
log.info({ action: 'goose_config_written', path: GOOSE_CONFIG }, 'Goose config.yaml updated with proxy host');
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=gooseHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gooseHooks.js","sourceRoot":"","sources":["../../src/harness/gooseHooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gooseHooksWriter.ts — Goose pre_tool_use native hook writer.
|
|
3
|
+
*
|
|
4
|
+
* Writes two artefacts:
|
|
5
|
+
* 1. `~/.intutic/hooks/goose-pre-tool-use.sh` — bash gate script that
|
|
6
|
+
* sources runtime credentials, POSTs to the Intutic hook-gate endpoint,
|
|
7
|
+
* and exits 2 (block) or 0 (allow).
|
|
8
|
+
* 2. `~/.config/goose/config.yaml` — adds `hooks.pre_tool_use` key if it
|
|
9
|
+
* is not already present (string operations only; no yaml dep needed).
|
|
10
|
+
*
|
|
11
|
+
* Fail-open semantics: if curl errors the script exits 0 so Goose continues.
|
|
12
|
+
* On block, a JSONL event line is also appended to
|
|
13
|
+
* `~/.intutic/events/hook-events.jsonl` for local audit.
|
|
14
|
+
*
|
|
15
|
+
* LLD #14 — Phase 4 WS-4C native hook script writers
|
|
16
|
+
* HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Write the Goose pre_tool_use hook script and register it in config.yaml.
|
|
22
|
+
*
|
|
23
|
+
* @param workspaceRoot - Absolute path to the workspace root (unused directly;
|
|
24
|
+
* included for API symmetry with other hook writers).
|
|
25
|
+
*/
|
|
26
|
+
export declare function writeGooseHooks(workspaceRoot: string): Promise<void>;
|
|
27
|
+
//# sourceMappingURL=gooseHooksWriter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gooseHooksWriter.d.ts","sourceRoot":"","sources":["../../src/harness/gooseHooksWriter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAwHH;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB1E"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gooseHooksWriter.ts — Goose pre_tool_use native hook writer.
|
|
3
|
+
*
|
|
4
|
+
* Writes two artefacts:
|
|
5
|
+
* 1. `~/.intutic/hooks/goose-pre-tool-use.sh` — bash gate script that
|
|
6
|
+
* sources runtime credentials, POSTs to the Intutic hook-gate endpoint,
|
|
7
|
+
* and exits 2 (block) or 0 (allow).
|
|
8
|
+
* 2. `~/.config/goose/config.yaml` — adds `hooks.pre_tool_use` key if it
|
|
9
|
+
* is not already present (string operations only; no yaml dep needed).
|
|
10
|
+
*
|
|
11
|
+
* Fail-open semantics: if curl errors the script exits 0 so Goose continues.
|
|
12
|
+
* On block, a JSONL event line is also appended to
|
|
13
|
+
* `~/.intutic/events/hook-events.jsonl` for local audit.
|
|
14
|
+
*
|
|
15
|
+
* LLD #14 — Phase 4 WS-4C native hook script writers
|
|
16
|
+
* HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import * as node_fs from 'node:fs/promises';
|
|
21
|
+
import * as node_path from 'node:path';
|
|
22
|
+
import * as node_os from 'node:os';
|
|
23
|
+
import { createLogger } from '@intutic/logger';
|
|
24
|
+
const log = createLogger('sync-goose-hooks-writer');
|
|
25
|
+
const HOOKS_DIR = node_path.join(node_os.homedir(), '.intutic', 'hooks');
|
|
26
|
+
const EVENTS_DIR = node_path.join(node_os.homedir(), '.intutic', 'events');
|
|
27
|
+
const HOOK_SCRIPT = node_path.join(HOOKS_DIR, 'goose-pre-tool-use.sh');
|
|
28
|
+
const HOOK_EVENTS_LOG = node_path.join(EVENTS_DIR, 'hook-events.jsonl');
|
|
29
|
+
const GOOSE_CONFIG = node_path.join(node_os.homedir(), '.config', 'goose', 'config.yaml');
|
|
30
|
+
/**
|
|
31
|
+
* Build the bash gate script content.
|
|
32
|
+
*
|
|
33
|
+
* Logic:
|
|
34
|
+
* 1. Source ~/.intutic/env/runtime.env for credentials.
|
|
35
|
+
* 2. Read full stdin JSON payload.
|
|
36
|
+
* 3. POST to $INTUTIC_CONTROL_PLANE_URL/api/v1/hook-gate (10 s timeout).
|
|
37
|
+
* 4. If curl fails → fail-open (exit 0).
|
|
38
|
+
* 5. If response body contains `"allowed":false` → append JSONL event, exit 2.
|
|
39
|
+
* 6. Otherwise → exit 0.
|
|
40
|
+
*/
|
|
41
|
+
function buildGooseHookScript() {
|
|
42
|
+
return `#!/usr/bin/env bash
|
|
43
|
+
# Intutic Goose pre_tool_use governance gate.
|
|
44
|
+
# Auto-generated by intutic sync-daemon. DO NOT EDIT.
|
|
45
|
+
set -euo pipefail
|
|
46
|
+
|
|
47
|
+
RUNTIME_ENV="$HOME/.intutic/env/runtime.env"
|
|
48
|
+
HOOK_EVENTS_LOG=${JSON.stringify(HOOK_EVENTS_LOG)}
|
|
49
|
+
INTUTIC_CONTROL_PLANE_URL="https://api.intutic.ai"
|
|
50
|
+
INTUTIC_API_KEY=""
|
|
51
|
+
INTUTIC_WORKSPACE_ID=""
|
|
52
|
+
|
|
53
|
+
# Source runtime credentials (key rotation is automatic on next tool call)
|
|
54
|
+
[ -f "$RUNTIME_ENV" ] && source "$RUNTIME_ENV" 2>/dev/null || true
|
|
55
|
+
|
|
56
|
+
INPUT="$(cat)"
|
|
57
|
+
TOOL_NAME="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null || true)"
|
|
58
|
+
|
|
59
|
+
# ── POST to hook-gate ─────────────────────────────────────────────────
|
|
60
|
+
GATE_URL="\${INTUTIC_CONTROL_PLANE_URL}/api/v1/hook-gate"
|
|
61
|
+
RESPONSE=""
|
|
62
|
+
if [ -n "$INTUTIC_API_KEY" ]; then
|
|
63
|
+
RESPONSE="$(curl -s --max-time 10 -X POST \\
|
|
64
|
+
-H "Content-Type: application/json" \\
|
|
65
|
+
-H "Authorization: Bearer $INTUTIC_API_KEY" \\
|
|
66
|
+
-d "$INPUT" \\
|
|
67
|
+
"$GATE_URL" 2>/dev/null)" || true
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Fail-open: empty response (curl error / no key) → allow
|
|
71
|
+
if [ -z "$RESPONSE" ]; then
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Check decision
|
|
76
|
+
ALLOWED="$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(str(d.get('allowed', True)).lower())" 2>/dev/null || echo "true")"
|
|
77
|
+
|
|
78
|
+
if [ "$ALLOWED" = "false" ]; then
|
|
79
|
+
REASON="$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('reason',''))" 2>/dev/null || true)"
|
|
80
|
+
TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
81
|
+
INCIDENT_ID="$(printf '%s' "\${TS}\${TOOL_NAME}\${INTUTIC_WORKSPACE_ID}" | sha1sum 2>/dev/null | cut -c1-16 || echo "$(date +%s)")"
|
|
82
|
+
ENTRY="{\\"event\\":\\"tool_blocked\\",\\"toolName\\":\\"\${TOOL_NAME}\\",\\"reason\\":\\"\${REASON}\\",\\"workspaceId\\":\\"\${INTUTIC_WORKSPACE_ID}\\",\\"harnessType\\":\\"goose\\",\\"timestamp\\":\\"\${TS}\\",\\"incidentId\\":\\"\${INCIDENT_ID}\\"}"
|
|
83
|
+
mkdir -p "$(dirname "$HOOK_EVENTS_LOG")"
|
|
84
|
+
printf '%s\\n' "$ENTRY" >> "$HOOK_EVENTS_LOG" 2>/dev/null || true
|
|
85
|
+
echo "[Intutic Governance] BLOCKED: $REASON" >&2
|
|
86
|
+
exit 2
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
exit 0
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Merge `hooks.pre_tool_use` into Goose config.yaml using string operations.
|
|
94
|
+
*
|
|
95
|
+
* Reads the existing file (if any), checks if `pre_tool_use:` is already
|
|
96
|
+
* present under a `hooks:` section, and adds it only if absent.
|
|
97
|
+
* Creates the file (with a minimal `hooks:` block) if it does not exist.
|
|
98
|
+
*/
|
|
99
|
+
async function mergeGooseConfig() {
|
|
100
|
+
let existing = '';
|
|
101
|
+
try {
|
|
102
|
+
existing = await node_fs.readFile(GOOSE_CONFIG, 'utf-8');
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
existing = '';
|
|
106
|
+
}
|
|
107
|
+
const hookLine = ` pre_tool_use: ${HOOK_SCRIPT}`;
|
|
108
|
+
// Already registered — nothing to do
|
|
109
|
+
if (existing.includes('pre_tool_use:') && existing.includes(HOOK_SCRIPT)) {
|
|
110
|
+
log.debug({ action: 'goose_config_noop', path: GOOSE_CONFIG }, 'Goose config already contains pre_tool_use hook');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (existing.includes('hooks:')) {
|
|
114
|
+
// hooks: block exists — insert our line after the `hooks:` line
|
|
115
|
+
existing = existing.replace(/^hooks:/m, `hooks:\n${hookLine}`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// No hooks: block — append
|
|
119
|
+
existing = existing.trimEnd() + `\nhooks:\n${hookLine}\n`;
|
|
120
|
+
}
|
|
121
|
+
await node_fs.mkdir(node_path.dirname(GOOSE_CONFIG), { recursive: true });
|
|
122
|
+
const tmp = GOOSE_CONFIG + '.intutic-tmp';
|
|
123
|
+
await node_fs.writeFile(tmp, existing, 'utf-8');
|
|
124
|
+
await node_fs.rename(tmp, GOOSE_CONFIG);
|
|
125
|
+
log.info({ action: 'goose_config_written', path: GOOSE_CONFIG }, 'Goose config.yaml updated with pre_tool_use hook');
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Write the Goose pre_tool_use hook script and register it in config.yaml.
|
|
129
|
+
*
|
|
130
|
+
* @param workspaceRoot - Absolute path to the workspace root (unused directly;
|
|
131
|
+
* included for API symmetry with other hook writers).
|
|
132
|
+
*/
|
|
133
|
+
export async function writeGooseHooks(workspaceRoot) {
|
|
134
|
+
// Ensure target directories exist
|
|
135
|
+
await node_fs.mkdir(HOOKS_DIR, { recursive: true });
|
|
136
|
+
await node_fs.mkdir(EVENTS_DIR, { recursive: true });
|
|
137
|
+
// Write hook script atomically
|
|
138
|
+
const tmp = HOOK_SCRIPT + '.intutic-tmp';
|
|
139
|
+
await node_fs.writeFile(tmp, buildGooseHookScript(), 'utf-8');
|
|
140
|
+
await node_fs.rename(tmp, HOOK_SCRIPT);
|
|
141
|
+
await node_fs.chmod(HOOK_SCRIPT, 0o755);
|
|
142
|
+
log.info({ action: 'goose_hook_script_written', path: HOOK_SCRIPT }, 'Goose pre_tool_use hook script written');
|
|
143
|
+
// Register in config.yaml
|
|
144
|
+
await mergeGooseConfig();
|
|
145
|
+
log.info({ action: 'goose_hooks_done', workspaceRoot }, 'Goose native hooks written successfully');
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=gooseHooksWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gooseHooksWriter.js","sourceRoot":"","sources":["../../src/harness/gooseHooksWriter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hermesHooks.ts — Hermes AI agent governance hook injection.
|
|
3
|
+
*
|
|
4
|
+
* Hermes is a skills-based AI agent that reads `~/.hermes/config.yaml`
|
|
5
|
+
* for configuration. Hook commands are registered under `hooks.preToolUse`.
|
|
6
|
+
* This module:
|
|
7
|
+
*
|
|
8
|
+
* 1. YAML-safe-merges `~/.hermes/config.yaml` to register the hook command.
|
|
9
|
+
* 2. Writes `~/.intutic/hooks/hermes-check.sh` — dual-path bash governance hook.
|
|
10
|
+
* 3. Writes `~/.hermes/skills/intutic-governance/SKILL.md` — governance skill.
|
|
11
|
+
* 4. Writes `.intutic/env/hermes.env` — env snippet for IDE/shell integration.
|
|
12
|
+
*
|
|
13
|
+
* WS-C1 — Hermes harness (Phase 3 cross-harness defence)
|
|
14
|
+
* LLD #14 — Phase 3 cross-harness defence (Gap 3)
|
|
15
|
+
* HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Write the Hermes governance hook script, merge config, and write the
|
|
21
|
+
* governance skill for the Hermes AI agent.
|
|
22
|
+
*
|
|
23
|
+
* Safe to call repeatedly — uses atomic rename so partial writes never
|
|
24
|
+
* leave a corrupt state.
|
|
25
|
+
*
|
|
26
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
27
|
+
* @param proxyUrl - Intutic proxy URL written into hook headers / skill.
|
|
28
|
+
* @param workspaceId - Workspace ID embedded in every hook event payload.
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeHermesHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=hermesHooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hermesHooks.d.ts","sourceRoot":"","sources":["../../src/harness/hermesHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiMH;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAiEf"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hermesHooks.ts — Hermes AI agent governance hook injection.
|
|
3
|
+
*
|
|
4
|
+
* Hermes is a skills-based AI agent that reads `~/.hermes/config.yaml`
|
|
5
|
+
* for configuration. Hook commands are registered under `hooks.preToolUse`.
|
|
6
|
+
* This module:
|
|
7
|
+
*
|
|
8
|
+
* 1. YAML-safe-merges `~/.hermes/config.yaml` to register the hook command.
|
|
9
|
+
* 2. Writes `~/.intutic/hooks/hermes-check.sh` — dual-path bash governance hook.
|
|
10
|
+
* 3. Writes `~/.hermes/skills/intutic-governance/SKILL.md` — governance skill.
|
|
11
|
+
* 4. Writes `.intutic/env/hermes.env` — env snippet for IDE/shell integration.
|
|
12
|
+
*
|
|
13
|
+
* WS-C1 — Hermes harness (Phase 3 cross-harness defence)
|
|
14
|
+
* LLD #14 — Phase 3 cross-harness defence (Gap 3)
|
|
15
|
+
* HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'node:fs/promises';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import * as os from 'node:os';
|
|
22
|
+
import { createLogger } from '@intutic/logger';
|
|
23
|
+
import { newIso } from '@intutic/id';
|
|
24
|
+
const log = createLogger('sync-hermes-hooks');
|
|
25
|
+
/** Path to the Hermes global config file. */
|
|
26
|
+
const HERMES_CONFIG = path.join(os.homedir(), '.hermes', 'config.yaml');
|
|
27
|
+
/** Governance-sensitive paths that the hook gate protects. */
|
|
28
|
+
const PROTECTED_PATHS = [
|
|
29
|
+
'.gemini/settings.json',
|
|
30
|
+
'.intutic/hooks',
|
|
31
|
+
'.intutic/integrity.json',
|
|
32
|
+
'.intutic/events',
|
|
33
|
+
'.claude/settings.json',
|
|
34
|
+
'.cursor/hooks.json',
|
|
35
|
+
'.codeium/windsurf/hooks.json',
|
|
36
|
+
'.cline/hooks',
|
|
37
|
+
'.agents/plugins/intutic-governance',
|
|
38
|
+
'.openhands/hooks.json',
|
|
39
|
+
'.hermes/config.yaml',
|
|
40
|
+
'.hermes/skills/intutic-governance',
|
|
41
|
+
];
|
|
42
|
+
// ─── Bash hook script template ────────────────────────────────────────────────
|
|
43
|
+
function buildHermesCheckScript(proxyUrl, workspaceRoot, workspaceId) {
|
|
44
|
+
const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'hook-events.jsonl');
|
|
45
|
+
const runtimeEnv = path.join(os.homedir(), '.intutic', 'env', 'runtime.env');
|
|
46
|
+
return `#!/usr/bin/env bash
|
|
47
|
+
# Intutic Hermes PreToolUse governance gate.
|
|
48
|
+
# Auto-generated by intutic sync-daemon. DO NOT EDIT.
|
|
49
|
+
# Proxy: ${proxyUrl}
|
|
50
|
+
# Generated: ${newIso()}
|
|
51
|
+
# Workspace: ${workspaceId}
|
|
52
|
+
set -euo pipefail
|
|
53
|
+
|
|
54
|
+
# Source runtime credentials (never embedded in this file)
|
|
55
|
+
if [ -f "${runtimeEnv}" ]; then
|
|
56
|
+
# shellcheck disable=SC1090
|
|
57
|
+
source "${runtimeEnv}"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
INTUTIC_HOST="\${INTUTIC_HOST:-https://api.intutic.ai}"
|
|
61
|
+
INTUTIC_API_KEY="\${INTUTIC_API_KEY:-}"
|
|
62
|
+
INTUTIC_WORKSPACE_ID="\${INTUTIC_WORKSPACE_ID:-${workspaceId}}"
|
|
63
|
+
|
|
64
|
+
INPUT="$(cat)"
|
|
65
|
+
TOOL_NAME="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null || true)"
|
|
66
|
+
TARGET_PATH="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); i=d.get('tool_input',{}); print(i.get('path',i.get('file_path','')))" 2>/dev/null || true)"
|
|
67
|
+
COMMAND="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); i=d.get('tool_input',{}); print(i.get('command',i.get('cmd','')))" 2>/dev/null || true)"
|
|
68
|
+
|
|
69
|
+
HOOK_EVENTS_LOG=${JSON.stringify(hookEventsLog)}
|
|
70
|
+
|
|
71
|
+
log_event() {
|
|
72
|
+
local verdict="\$1" tool="\$2" reason="\$3"
|
|
73
|
+
local ts; ts="\$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
74
|
+
# incidentId = sha1(timestamp + toolName + workspaceId).slice(0,16)
|
|
75
|
+
local incident_id; incident_id="\$(echo -n \"\${ts}\${tool}\${INTUTIC_WORKSPACE_ID}\" | sha1sum | cut -c1-16)"
|
|
76
|
+
local entry="{\\\"event\\\":\\\"toolGovernance\\\",\\\"verdict\\\":\\\"\${verdict}\\\",\\\"toolName\\\":\\\"\${tool}\\\",\\\"reason\\\":\\\"\${reason}\\\",\\\"workspaceId\\\":\\\"\${INTUTIC_WORKSPACE_ID}\\\",\\\"harnessType\\\":\\\"hermes\\\",\\\"incidentId\\\":\\\"\${incident_id}\\\",\\\"timestamp\\\":\\\"\${ts}\\\"}"
|
|
77
|
+
# Path B: reliable file append (sync-daemon drains on FSEvents change)
|
|
78
|
+
printf '%s\\n' "\$entry" >> "\$HOOK_EVENTS_LOG" 2>/dev/null || true
|
|
79
|
+
# Path A: fire-and-forget HTTP POST (near-real-time dashboard, non-blocking)
|
|
80
|
+
if [ -n "\$INTUTIC_API_KEY" ]; then
|
|
81
|
+
curl -sf -X POST "\${INTUTIC_HOST}/api/v1/hook-events" \\
|
|
82
|
+
-H "Content-Type: application/json" \\
|
|
83
|
+
-H "Authorization: Bearer \${INTUTIC_API_KEY}" \\
|
|
84
|
+
-d "{\\\"events\\\":[\$entry]}" \\
|
|
85
|
+
--max-time 5 >/dev/null 2>&1 &
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
PROTECTED_PATHS=(
|
|
90
|
+
${PROTECTED_PATHS.map((p) => ` "${p}"`).join('\n')}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# 1. Protected-path guard
|
|
94
|
+
for p in "\${PROTECTED_PATHS[@]}"; do
|
|
95
|
+
if echo "$TARGET_PATH" | grep -qF "$p"; then
|
|
96
|
+
REASON="Attempt to modify governance-protected path \\"$TARGET_PATH\\""
|
|
97
|
+
echo "[Intutic Governance] BLOCKED: $REASON" >&2
|
|
98
|
+
log_event "tool_blocked" "$TOOL_NAME" "$REASON"
|
|
99
|
+
exit 2
|
|
100
|
+
fi
|
|
101
|
+
done
|
|
102
|
+
|
|
103
|
+
# 2. Bash/shell command guard — block governance bypass patterns
|
|
104
|
+
if [ -n "$COMMAND" ]; then
|
|
105
|
+
BYPASS_PATTERNS=(
|
|
106
|
+
"chflags nouchg"
|
|
107
|
+
"chattr -i"
|
|
108
|
+
"rm -rf .intutic"
|
|
109
|
+
"rm -rf .hermes/config"
|
|
110
|
+
"HERMES_PRE_TOOL_HOOK="
|
|
111
|
+
"rm -rf .gemini/settings"
|
|
112
|
+
)
|
|
113
|
+
for pattern in "\${BYPASS_PATTERNS[@]}"; do
|
|
114
|
+
if echo "$COMMAND" | grep -qF "$pattern"; then
|
|
115
|
+
REASON="Shell command matches governance bypass pattern: \\"$pattern\\""
|
|
116
|
+
echo "[Intutic Governance] BLOCKED: $REASON" >&2
|
|
117
|
+
log_event "tool_blocked" "$TOOL_NAME" "$REASON"
|
|
118
|
+
exit 2
|
|
119
|
+
fi
|
|
120
|
+
done
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
log_event "tool_allowed" "$TOOL_NAME" ""
|
|
124
|
+
exit 0
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
// ─── YAML merge helpers ───────────────────────────────────────────────────────
|
|
128
|
+
/**
|
|
129
|
+
* YAML-safe merge of the Hermes config.
|
|
130
|
+
*
|
|
131
|
+
* Strategy (no YAML library — pure string manipulation):
|
|
132
|
+
* - If file doesn't exist: write fresh with just the hooks block.
|
|
133
|
+
* - If file exists and already contains `preToolUse:`: replace the command line.
|
|
134
|
+
* - If file exists but has no `preToolUse:`: append the hooks block.
|
|
135
|
+
* - If file has `hooks:` but no `preToolUse:`: insert inside existing hooks section.
|
|
136
|
+
*/
|
|
137
|
+
async function mergeHermesConfig(hookScriptPath) {
|
|
138
|
+
const hooksBlock = [
|
|
139
|
+
'hooks:',
|
|
140
|
+
' preToolUse:',
|
|
141
|
+
` command: ${hookScriptPath}`,
|
|
142
|
+
].join('\n');
|
|
143
|
+
let existing = null;
|
|
144
|
+
try {
|
|
145
|
+
existing = await fs.readFile(HERMES_CONFIG, 'utf-8');
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// File doesn't exist — write fresh
|
|
149
|
+
}
|
|
150
|
+
let merged;
|
|
151
|
+
if (existing === null) {
|
|
152
|
+
// Fresh write
|
|
153
|
+
merged = hooksBlock + '\n';
|
|
154
|
+
}
|
|
155
|
+
else if (/^\s*preToolUse:/m.test(existing)) {
|
|
156
|
+
// Replace the existing command line under preToolUse:
|
|
157
|
+
merged = existing.replace(/^(\s*command:\s*).*$/m, `$1${hookScriptPath}`);
|
|
158
|
+
}
|
|
159
|
+
else if (/^hooks:/m.test(existing)) {
|
|
160
|
+
// hooks: block exists but no preToolUse: — insert inside hooks section
|
|
161
|
+
merged = existing.replace(/^(hooks:\s*)$/m, `$1\n preToolUse:\n command: ${hookScriptPath}`);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// No hooks: block at all — append
|
|
165
|
+
merged = existing.trimEnd() + '\n\n' + hooksBlock + '\n';
|
|
166
|
+
}
|
|
167
|
+
const tmpPath = HERMES_CONFIG + '.intutic-tmp';
|
|
168
|
+
await fs.writeFile(tmpPath, merged, 'utf-8');
|
|
169
|
+
await fs.rename(tmpPath, HERMES_CONFIG);
|
|
170
|
+
}
|
|
171
|
+
// ─── Governance skill template ────────────────────────────────────────────────
|
|
172
|
+
function buildGovernanceSkill(proxyUrl) {
|
|
173
|
+
return `---
|
|
174
|
+
name: intutic-governance
|
|
175
|
+
description: Intutic governance awareness. Always check compliance before tool calls.
|
|
176
|
+
triggers:
|
|
177
|
+
- before any tool call
|
|
178
|
+
- before any file write
|
|
179
|
+
- before any shell command
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
# Intutic Governance Policy
|
|
183
|
+
|
|
184
|
+
Before every tool call, you MUST verify compliance with workspace SOPs.
|
|
185
|
+
A pre-tool-use hook (\`~/.intutic/hooks/hermes-check.sh\`) enforces these rules automatically.
|
|
186
|
+
|
|
187
|
+
If a tool call is blocked, do NOT attempt to circumvent the governance gate.
|
|
188
|
+
To update policy, use the Intutic control plane at \`${proxyUrl}\`.
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
192
|
+
/**
|
|
193
|
+
* Write the Hermes governance hook script, merge config, and write the
|
|
194
|
+
* governance skill for the Hermes AI agent.
|
|
195
|
+
*
|
|
196
|
+
* Safe to call repeatedly — uses atomic rename so partial writes never
|
|
197
|
+
* leave a corrupt state.
|
|
198
|
+
*
|
|
199
|
+
* @param workspaceRoot - Absolute workspace root path.
|
|
200
|
+
* @param proxyUrl - Intutic proxy URL written into hook headers / skill.
|
|
201
|
+
* @param workspaceId - Workspace ID embedded in every hook event payload.
|
|
202
|
+
*/
|
|
203
|
+
export async function writeHermesHooks(workspaceRoot, proxyUrl, workspaceId = '') {
|
|
204
|
+
// ── 1. Ensure directories ──────────────────────────────────────────────────
|
|
205
|
+
const globalHookDir = path.join(os.homedir(), '.intutic', 'hooks');
|
|
206
|
+
const hookEventsDir = path.join(workspaceRoot, '.intutic', 'events');
|
|
207
|
+
const envDir = path.join(workspaceRoot, '.intutic', 'env');
|
|
208
|
+
const hermesDir = path.join(os.homedir(), '.hermes');
|
|
209
|
+
const skillsDir = path.join(hermesDir, 'skills', 'intutic-governance');
|
|
210
|
+
await Promise.all([
|
|
211
|
+
fs.mkdir(globalHookDir, { recursive: true }),
|
|
212
|
+
fs.mkdir(hookEventsDir, { recursive: true }),
|
|
213
|
+
fs.mkdir(envDir, { recursive: true }),
|
|
214
|
+
fs.mkdir(hermesDir, { recursive: true }),
|
|
215
|
+
fs.mkdir(skillsDir, { recursive: true }),
|
|
216
|
+
]);
|
|
217
|
+
// ── 2. Write bash hook script ──────────────────────────────────────────────
|
|
218
|
+
const hookScriptPath = path.join(globalHookDir, 'hermes-check.sh');
|
|
219
|
+
const tmpScript = hookScriptPath + '.intutic-tmp';
|
|
220
|
+
await fs.writeFile(tmpScript, buildHermesCheckScript(proxyUrl, workspaceRoot, workspaceId), 'utf-8');
|
|
221
|
+
await fs.rename(tmpScript, hookScriptPath);
|
|
222
|
+
await fs.chmod(hookScriptPath, 0o755);
|
|
223
|
+
log.info({ action: 'hermes_hook_script_written', path: hookScriptPath }, 'Hermes hook script written');
|
|
224
|
+
// ── 3. YAML-safe merge into ~/.hermes/config.yaml ─────────────────────────
|
|
225
|
+
await mergeHermesConfig(hookScriptPath);
|
|
226
|
+
log.info({ action: 'hermes_config_merged', path: HERMES_CONFIG }, 'Merged Intutic hook into ~/.hermes/config.yaml');
|
|
227
|
+
// ── 4. Write governance skill ──────────────────────────────────────────────
|
|
228
|
+
const skillPath = path.join(skillsDir, 'SKILL.md');
|
|
229
|
+
const tmpSkill = skillPath + '.intutic-tmp';
|
|
230
|
+
await fs.writeFile(tmpSkill, buildGovernanceSkill(proxyUrl), 'utf-8');
|
|
231
|
+
await fs.rename(tmpSkill, skillPath);
|
|
232
|
+
log.info({ action: 'hermes_skill_written', path: skillPath }, 'Hermes governance skill written');
|
|
233
|
+
// ── 5. Write env snippet ───────────────────────────────────────────────────
|
|
234
|
+
const envSnippet = [
|
|
235
|
+
`# Intutic Hermes governance env`,
|
|
236
|
+
`INTUITIC_HERMES_HOOK=~/.intutic/hooks/hermes-check.sh`,
|
|
237
|
+
`INTUITIC_HERMES_CONFIG=~/.hermes/config.yaml`,
|
|
238
|
+
].join('\n') + '\n';
|
|
239
|
+
const envFilePath = path.join(envDir, 'hermes.env');
|
|
240
|
+
const tmpEnv = envFilePath + '.intutic-tmp';
|
|
241
|
+
await fs.writeFile(tmpEnv, envSnippet, 'utf-8');
|
|
242
|
+
await fs.rename(tmpEnv, envFilePath);
|
|
243
|
+
log.info({ action: 'hermes_env_written', path: envFilePath }, 'Hermes env snippet written');
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=hermesHooks.js.map
|