@hybridlabor-api/aos 4.5.0 → 4.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.claude/hooks/memb-inject.mjs +135 -40
  2. package/.claude/workflows/startcycle-dispatch.mjs +318 -3
  3. package/.opencode/plugins/bdb-aos.js +171 -0
  4. package/bin/aos-dashboard.mjs +8 -2
  5. package/installer.js +307 -12
  6. package/package.json +1 -1
  7. package/skills/global_config/aos-setup/SKILL.md +7 -9
  8. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +43 -1
  9. package/skills/global_config/openwiki-skill/scripts/install_daemon.ps1 +107 -18
  10. package/.agents/skills/firecrawl/SKILL.md +0 -149
  11. package/.agents/skills/firecrawl/rules/install.md +0 -82
  12. package/.agents/skills/firecrawl/rules/security.md +0 -26
  13. package/.agents/skills/firecrawl-agent/SKILL.md +0 -58
  14. package/.agents/skills/firecrawl-build/SKILL.md +0 -39
  15. package/.agents/skills/firecrawl-build-interact/SKILL.md +0 -68
  16. package/.agents/skills/firecrawl-build-onboarding/SKILL.md +0 -103
  17. package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +0 -39
  18. package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +0 -20
  19. package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +0 -17
  20. package/.agents/skills/firecrawl-build-scrape/SKILL.md +0 -69
  21. package/.agents/skills/firecrawl-build-search/SKILL.md +0 -69
  22. package/.agents/skills/firecrawl-crawl/SKILL.md +0 -59
  23. package/.agents/skills/firecrawl-download/SKILL.md +0 -70
  24. package/.agents/skills/firecrawl-interact/SKILL.md +0 -84
  25. package/.agents/skills/firecrawl-map/SKILL.md +0 -51
  26. package/.agents/skills/firecrawl-scrape/SKILL.md +0 -69
  27. package/.agents/skills/firecrawl-search/SKILL.md +0 -60
  28. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +0 -10
  29. package/mcps/after-effects-mcp/build/index.js +0 -840
  30. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +0 -153
  31. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +0 -218
  32. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +0 -71
  33. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +0 -147
  34. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +0 -114
  35. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +0 -115
  36. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +0 -192
  37. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +0 -90
  38. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +0 -50
  39. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +0 -1773
  40. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +0 -160
  41. package/mcps/computer-use-mcp/dist/client.d.ts +0 -150
  42. package/mcps/computer-use-mcp/dist/client.js +0 -136
  43. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +0 -16
  44. package/mcps/computer-use-mcp/dist/entrypoint.js +0 -26
  45. package/mcps/computer-use-mcp/dist/native.d.ts +0 -212
  46. package/mcps/computer-use-mcp/dist/native.js +0 -50
  47. package/mcps/computer-use-mcp/dist/server.d.ts +0 -32
  48. package/mcps/computer-use-mcp/dist/server.js +0 -342
  49. package/mcps/computer-use-mcp/dist/session.d.ts +0 -101
  50. package/mcps/computer-use-mcp/dist/session.js +0 -2372
@@ -0,0 +1,171 @@
1
+ /**
2
+ * BDB Agent OS (AOS) plugin for OpenCode.
3
+ *
4
+ * Implements:
5
+ * 1. tool.execute.before: Releases gatekeeper (go-gate). Guards outward-facing
6
+ * or hard-to-reverse actions (git push, npm publish, npm version, rm -rf)
7
+ * unless the user explicitly authorized it with the literal word "GO".
8
+ * 2. chat.message: Ambient memory injection (memB). Injects relevant project & user
9
+ * memories as ephemeral context from ~/.MemBDB/memb.db via node:sqlite.
10
+ *
11
+ * Zero external runtime dependencies. Fails open on memory lookup, fails closed
12
+ * on unguarded destructive actions.
13
+ */
14
+
15
+ import { existsSync, readFileSync } from 'node:fs';
16
+ import os from 'node:os';
17
+ import path from 'node:path';
18
+
19
+ const GUARDED_PATTERNS = [
20
+ /^\s*git\s+push\b/i,
21
+ /^\s*npm\s+publish\b/i,
22
+ /^\s*npm\s+version\b/i,
23
+ /^\s*rm\s+(-\w*[rR]\w*|--recursive)\b/i,
24
+ ];
25
+
26
+ let lastHumanPrompt = '';
27
+
28
+ function isGuardedCommand(cmd) {
29
+ if (typeof cmd !== 'string') return false;
30
+ return GUARDED_PATTERNS.some((re) => re.test(cmd));
31
+ }
32
+
33
+ function verifyGoAuthorized(lastPrompt) {
34
+ const trimmed = (lastPrompt || '').trim();
35
+ return trimmed.toUpperCase() === 'GO';
36
+ }
37
+
38
+ // Extract ambient memory from local memB database
39
+ async function getMembContext(prompt, currentDir) {
40
+ const home = os.homedir();
41
+ const dbPath = path.join(home, '.MemBDB', 'memb.db');
42
+ if (!existsSync(dbPath)) return null;
43
+
44
+ try {
45
+ const { DatabaseSync } = await import('node:sqlite');
46
+ const db = new DatabaseSync(dbPath, { readOnly: true });
47
+ db.exec('PRAGMA busy_timeout = 3000;');
48
+
49
+ const contextItems = [];
50
+
51
+ // Standing persona facts
52
+ const personaFile = path.join(home, '.MemBDB', 'ambient-persona.txt');
53
+ if (existsSync(personaFile)) {
54
+ try {
55
+ for (const line of readFileSync(personaFile, 'utf8').split('\n')) {
56
+ const t = line.trim();
57
+ if (t && !t.startsWith('#')) contextItems.push(`- ${t}`);
58
+ }
59
+ } catch {}
60
+ }
61
+
62
+ // Resolve project ID candidate
63
+ let projectName = path.basename(currentDir);
64
+ const pkgPath = path.join(currentDir, 'package.json');
65
+ if (existsSync(pkgPath)) {
66
+ try {
67
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
68
+ if (pkg?.name) projectName = pkg.name;
69
+ } catch {}
70
+ }
71
+
72
+ // Query project memories
73
+ try {
74
+ const sql = `
75
+ SELECT payload FROM memb_vectors
76
+ WHERE collection = 'bdb_agent_memory'
77
+ AND (
78
+ json_extract(payload, '$.project_id') = ?
79
+ OR json_extract(payload, '$.metadata.project_id') = ?
80
+ OR json_extract(payload, '$.project') = ?
81
+ OR json_extract(payload, '$.metadata.project') = ?
82
+ )
83
+ ORDER BY rowid DESC
84
+ LIMIT 3
85
+ `;
86
+ const rows = db.prepare(sql).all(projectName, projectName, projectName, projectName);
87
+ for (const r of rows) {
88
+ try {
89
+ const p = JSON.parse(r.payload);
90
+ const text = (p.memory || p.data || '').trim().replace(/\s+/g, ' ');
91
+ if (text.length > 10) {
92
+ contextItems.push(`- Project [${projectName}]: ${text.slice(0, 180)}`);
93
+ }
94
+ } catch {}
95
+ }
96
+ } catch {}
97
+
98
+ db.close();
99
+
100
+ if (contextItems.length === 0) return null;
101
+ return `[AOS Ambient Memory - memB]\n${contextItems.join('\n')}`;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ export default async function bdbAosPlugin(input) {
108
+ const directory = input.directory || process.cwd();
109
+
110
+ return {
111
+ 'chat.message': async (msgInput, msgOutput) => {
112
+ // Capture the user prompt text for GO-gate verification
113
+ const textParts = (msgOutput.parts || []).filter((p) => p && p.type === 'text' && typeof p.text === 'string');
114
+ const fullText = textParts.map((p) => p.text).join('\n').trim();
115
+ if (fullText) {
116
+ lastHumanPrompt = fullText;
117
+ }
118
+
119
+ // Inject memB ambient memory
120
+ try {
121
+ const membContext = await getMembContext(fullText, directory);
122
+ if (membContext) {
123
+ msgOutput.parts.unshift({
124
+ id: `memb-${Date.now()}`,
125
+ sessionID: msgInput.sessionID,
126
+ messageID: msgInput.messageID || '',
127
+ type: 'text',
128
+ text: membContext,
129
+ synthetic: true,
130
+ });
131
+ }
132
+ } catch {}
133
+ },
134
+
135
+ 'tool.execute.before': async (toolInput, toolOutput) => {
136
+ const toolName = (toolInput.tool || '').toLowerCase();
137
+ // Intercept bash, terminal, or command execution tools
138
+ if (toolName === 'bash' || toolName === 'terminal' || toolName === 'shell' || toolName === 'exec' || toolName === 'run_command') {
139
+ const cmd = toolOutput?.args?.command || toolOutput?.args?.cmd || toolOutput?.args?.script || '';
140
+ if (isGuardedCommand(cmd)) {
141
+ let authorized = verifyGoAuthorized(lastHumanPrompt);
142
+
143
+ // If last cached prompt wasn't GO, query recent session messages via OpenCode client as fallback
144
+ if (!authorized && input.client && toolInput.sessionID) {
145
+ try {
146
+ const res = await input.client.session.messages({ path: { id: toolInput.sessionID } });
147
+ const msgs = res?.data || [];
148
+ for (let i = msgs.length - 1; i >= 0; i--) {
149
+ const m = msgs[i];
150
+ if (m.role === 'user') {
151
+ const parts = m.parts || [];
152
+ const text = parts.filter((p) => p.type === 'text').map((p) => p.text).join('\n').trim();
153
+ if (text) {
154
+ authorized = (text.toUpperCase() === 'GO');
155
+ break;
156
+ }
157
+ }
158
+ }
159
+ } catch {}
160
+ }
161
+
162
+ if (!authorized) {
163
+ throw new Error(
164
+ `Blocked by BDB go-gate: Command "${cmd.slice(0, 80)}" is guarded and requires explicit authorization with the literal word "GO" before proceeding.`
165
+ );
166
+ }
167
+ }
168
+ }
169
+ },
170
+ };
171
+ }
@@ -603,6 +603,12 @@ server.listen(PORT, '127.0.0.1', () => {
603
603
  const url = `http://127.0.0.1:${PORT}`;
604
604
  console.log(`AOS Dashboard v${AOS_VERSION || '?'}: ${url} (Strg-C beendet)`);
605
605
  if (!OPEN) return;
606
- const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
607
- execFile(opener, [url], () => { /* no browser is not an error */ });
606
+ if (process.platform === 'win32') {
607
+ spawn('cmd.exe', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' })
608
+ .on('error', () => { /* no browser or spawn error is not fatal */ })
609
+ .unref();
610
+ } else {
611
+ const opener = process.platform === 'darwin' ? 'open' : 'xdg-open';
612
+ execFile(opener, [url], () => { /* no browser is not an error */ });
613
+ }
608
614
  });
package/installer.js CHANGED
@@ -3175,11 +3175,62 @@ function injectHarnessRules() {
3175
3175
  // most (a fresh install got it; every machine that already had AOS did not).
3176
3176
  function installGlobalHooks() {
3177
3177
  const hooksSrc = path.join(srcDir, '.claude', 'hooks');
3178
+ const workflowsSrc = path.join(srcDir, '.claude', 'workflows');
3179
+
3180
+ // 1. Claude Code
3178
3181
  if (fs.existsSync(hooksSrc)) {
3179
3182
  copyDirRecursiveSync(hooksSrc, path.join(homeDir, '.claude', 'hooks'));
3180
3183
  log.step(`Installed hooks to ${path.join(homeDir, '.claude', 'hooks')}`);
3181
3184
  }
3185
+ if (fs.existsSync(workflowsSrc)) {
3186
+ copyDirRecursiveSync(workflowsSrc, path.join(homeDir, '.claude', 'workflows'));
3187
+ }
3182
3188
  mergeBdbSettingsHooks(path.join(homeDir, '.claude', 'settings.json'));
3189
+
3190
+ // 2. Google Antigravity
3191
+ const agyHooksDir = path.join(geminiDir, 'config', 'hooks');
3192
+ const agyWorkflowsDir = path.join(geminiDir, 'config', 'workflows');
3193
+ if (fs.existsSync(hooksSrc)) {
3194
+ copyDirRecursiveSync(hooksSrc, agyHooksDir);
3195
+ log.step(`Installed hooks to ${agyHooksDir}`);
3196
+ }
3197
+ if (fs.existsSync(workflowsSrc)) {
3198
+ copyDirRecursiveSync(workflowsSrc, agyWorkflowsDir);
3199
+ }
3200
+ mergeAntigravityHooks(path.join(geminiDir, 'config', 'hooks.json'));
3201
+ if (fs.existsSync(path.join(geminiDir, 'antigravity-cli'))) {
3202
+ mergeAntigravityHooks(path.join(geminiDir, 'antigravity-cli', 'hooks.json'));
3203
+ }
3204
+
3205
+ // 3. OpenAI Codex CLI
3206
+ const codexHooksDir = path.join(homeDir, '.codex', 'hooks');
3207
+ const codexWorkflowsDir = path.join(homeDir, '.codex', 'workflows');
3208
+ if (fs.existsSync(hooksSrc)) {
3209
+ copyDirRecursiveSync(hooksSrc, codexHooksDir);
3210
+ log.step(`Installed hooks to ${codexHooksDir}`);
3211
+ }
3212
+ if (fs.existsSync(workflowsSrc)) {
3213
+ copyDirRecursiveSync(workflowsSrc, codexWorkflowsDir);
3214
+ }
3215
+ mergeCodexTomlHooks(path.join(homeDir, '.codex', 'config.toml'));
3216
+
3217
+ // 4. OpenCode CLI
3218
+ const opencodeDir = process.platform === 'win32'
3219
+ ? path.join(process.env.APPDATA || homeDir, 'opencode')
3220
+ : path.join(homeDir, '.config', 'opencode');
3221
+ const opencodePluginSrc = path.join(srcDir, '.opencode', 'plugins', 'bdb-aos.js');
3222
+ if (fs.existsSync(opencodePluginSrc)) {
3223
+ const opencodePluginsDir = path.join(opencodeDir, 'plugins');
3224
+ const opencodePluginDest = path.join(opencodePluginsDir, 'bdb-aos.js');
3225
+ try {
3226
+ fs.mkdirSync(opencodePluginsDir, { recursive: true });
3227
+ fs.copyFileSync(opencodePluginSrc, opencodePluginDest);
3228
+ try { fs.chmodSync(opencodePluginDest, 0o644); } catch (e) { logDebug(e, 'chmod opencode plugin'); }
3229
+ log.step(`Installed OpenCode plugin to ${opencodePluginDest}`);
3230
+ } catch (e) {
3231
+ log.warn(`Could not install OpenCode plugin: ${e.message}`);
3232
+ }
3233
+ }
3183
3234
  }
3184
3235
 
3185
3236
  // Merge the BDB hooks -- the two gates plus the memB ambient-memory hook --
@@ -3258,12 +3309,161 @@ function mergeBdbSettingsHooks(settingsPath, { projectLocal = false } = {}) {
3258
3309
  }
3259
3310
 
3260
3311
  try {
3312
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
3261
3313
  fs.writeFileSync(settingsPath, JSON.stringify(buildMerged(existing), null, 2) + '\n');
3262
3314
  } catch (e) {
3263
3315
  log.warn(`Could not write ${settingsPath}: ${e.message}`);
3264
3316
  }
3265
3317
  }
3266
3318
 
3319
+ // Merge the BDB hooks into Google Antigravity's hooks.json (.agents/hooks.json or
3320
+ // ~/.gemini/config/hooks.json), preserving user-defined foreign hooks.
3321
+ function mergeAntigravityHooks(hooksPath, { projectLocal = false } = {}) {
3322
+ const bdbHookScripts = ['go-gate.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'startcycle-dispatch.mjs'];
3323
+ const isBdbEntry = (entry) => {
3324
+ const cmds = (entry && Array.isArray(entry.hooks) ? entry.hooks : [entry])
3325
+ .map((h) => (h && typeof h.command === 'string' ? h.command : (typeof h === 'string' ? h : '')))
3326
+ .join(' ');
3327
+ return bdbHookScripts.some((name) => cmds.includes(name));
3328
+ };
3329
+
3330
+ const baseDir = path.dirname(hooksPath);
3331
+ const hooksDir = projectLocal ? path.join(currentDir, '.agents', 'hooks') : path.join(baseDir, 'hooks');
3332
+ const workflowsDir = projectLocal ? path.join(currentDir, '.agents', 'workflows') : path.join(baseDir, 'workflows');
3333
+ const globalHooksDir = projectLocal ? path.join(homeDir, '.gemini', 'config', 'hooks') : hooksDir;
3334
+
3335
+ const bdbHooks = {
3336
+ PreToolUse: [
3337
+ {
3338
+ matcher: "run_command|Bash",
3339
+ hooks: [{ type: "command", command: `node "${path.join(hooksDir, 'go-gate.mjs')}"`, timeout: 10000 }]
3340
+ }
3341
+ ],
3342
+ Stop: [
3343
+ {
3344
+ hooks: [{ type: "command", command: `node "${path.join(hooksDir, 'graph-gate.mjs')}"`, timeout: 10000 }]
3345
+ }
3346
+ ],
3347
+ PreInvocation: [
3348
+ {
3349
+ hooks: [
3350
+ { type: "command", command: `node "${path.join(globalHooksDir, 'memb-inject.mjs')}"`, timeout: 8000 },
3351
+ { type: "command", command: `node "${path.join(workflowsDir, 'startcycle-dispatch.mjs')}"`, timeout: 30000 }
3352
+ ]
3353
+ }
3354
+ ]
3355
+ };
3356
+
3357
+ const buildMerged = (existing) => {
3358
+ const merged = existing && typeof existing === 'object' ? existing : {};
3359
+ merged.hooks = merged.hooks && typeof merged.hooks === 'object' ? merged.hooks : {};
3360
+ for (const [event, entries] of Object.entries(bdbHooks)) {
3361
+ const foreignEntries = (Array.isArray(merged.hooks[event]) ? merged.hooks[event] : [])
3362
+ .filter((e) => !isBdbEntry(e));
3363
+ merged.hooks[event] = [...foreignEntries, ...entries];
3364
+ }
3365
+ return merged;
3366
+ };
3367
+
3368
+ let existing = null;
3369
+ if (fs.existsSync(hooksPath)) {
3370
+ existing = readJsonFile(hooksPath);
3371
+ if (!existing) {
3372
+ const backupCopy = `${hooksPath}.corrupt_${timestamp}.bak`;
3373
+ const sideCarPath = `${hooksPath}.bdb-new.json`;
3374
+ let backupWritten = true;
3375
+ try {
3376
+ fs.copyFileSync(hooksPath, backupCopy);
3377
+ } catch (copyError) {
3378
+ backupWritten = false;
3379
+ log.warn(`Could not create the hooks backup: ${copyError.message}`);
3380
+ }
3381
+ try {
3382
+ fs.writeFileSync(sideCarPath, JSON.stringify(buildMerged(null), null, 2) + '\n');
3383
+ } catch (writeError) {
3384
+ log.warn(`Could not write ${path.basename(sideCarPath)}: ${writeError.message}`);
3385
+ }
3386
+ log.warn(`${path.basename(hooksPath)} is not valid JSON - hook merge skipped, nothing overwritten.`);
3387
+ if (backupWritten) log.warn(`Backup copy: ${backupCopy}`);
3388
+ log.warn(`BDB-wired hooks: ${sideCarPath}`);
3389
+ return;
3390
+ }
3391
+ }
3392
+
3393
+ try {
3394
+ fs.mkdirSync(path.dirname(hooksPath), { recursive: true });
3395
+ fs.writeFileSync(hooksPath, JSON.stringify(buildMerged(existing), null, 2) + '\n');
3396
+ } catch (e) {
3397
+ log.warn(`Could not write ${hooksPath}: ${e.message}`);
3398
+ }
3399
+ }
3400
+
3401
+ // Merge the BDB hooks into ChatGPT Codex CLI's config.toml (~/.codex/config.toml or
3402
+ // .codex/config.toml), ensuring [features] hooks = true and preserving existing
3403
+ // non-BDB settings, comments, and MCP servers.
3404
+ function mergeCodexTomlHooks(configTomlPath, { projectLocal = false } = {}) {
3405
+ const baseDir = path.dirname(configTomlPath);
3406
+ const hooksDir = projectLocal ? path.join(currentDir, '.codex', 'hooks') : path.join(baseDir, 'hooks');
3407
+ const workflowsDir = projectLocal ? path.join(currentDir, '.codex', 'workflows') : path.join(baseDir, 'workflows');
3408
+ const globalHooksDir = projectLocal ? path.join(homeDir, '.codex', 'hooks') : hooksDir;
3409
+
3410
+ const tomlSnippet = [
3411
+ '# AOS:HOOKS:START',
3412
+ '[[hooks.PreToolUse]]',
3413
+ 'matcher = "^(Bash|run_command)$"',
3414
+ '[[hooks.PreToolUse.hooks]]',
3415
+ 'type = "command"',
3416
+ `command = ${JSON.stringify(`node "${path.join(hooksDir, 'go-gate.mjs')}"`)}`,
3417
+ 'timeout = 30',
3418
+ '',
3419
+ '[[hooks.Stop]]',
3420
+ '[[hooks.Stop.hooks]]',
3421
+ 'type = "command"',
3422
+ `command = ${JSON.stringify(`node "${path.join(hooksDir, 'graph-gate.mjs')}"`)}`,
3423
+ 'timeout = 30',
3424
+ '',
3425
+ '[[hooks.UserPromptSubmit]]',
3426
+ '[[hooks.UserPromptSubmit.hooks]]',
3427
+ 'type = "command"',
3428
+ `command = ${JSON.stringify(`node "${path.join(globalHooksDir, 'memb-inject.mjs')}"`)}`,
3429
+ 'timeout = 30',
3430
+ '',
3431
+ '[[hooks.UserPromptSubmit]]',
3432
+ '[[hooks.UserPromptSubmit.hooks]]',
3433
+ 'type = "command"',
3434
+ `command = ${JSON.stringify(`node "${path.join(workflowsDir, 'startcycle-dispatch.mjs')}"`)}`,
3435
+ 'timeout = 30',
3436
+ '# AOS:HOOKS:END'
3437
+ ].join('\n');
3438
+
3439
+ let content = fs.existsSync(configTomlPath) ? fs.readFileSync(configTomlPath, 'utf8') : '';
3440
+
3441
+ // Ensure [features] hooks = true
3442
+ const hasHooksFeature = /(?:hooks|codex_hooks)\s*=\s*true/m.test(content);
3443
+ if (!hasHooksFeature) {
3444
+ if (/^\[features\]/m.test(content)) {
3445
+ content = content.replace(/^\[features\]/m, '[features]\nhooks = true');
3446
+ } else {
3447
+ content = `[features]\nhooks = true\n\n${content.trimStart()}`;
3448
+ }
3449
+ }
3450
+
3451
+ // Replace existing AOS:HOOKS block or append
3452
+ const hookBlockRegex = /# AOS:HOOKS:START[\s\S]*?# AOS:HOOKS:END/;
3453
+ if (hookBlockRegex.test(content)) {
3454
+ content = content.replace(hookBlockRegex, tomlSnippet);
3455
+ } else {
3456
+ content = `${content.trimEnd()}\n\n${tomlSnippet}\n`;
3457
+ }
3458
+
3459
+ try {
3460
+ fs.mkdirSync(path.dirname(configTomlPath), { recursive: true });
3461
+ fs.writeFileSync(configTomlPath, content, { mode: 0o600 });
3462
+ } catch (e) {
3463
+ log.warn(`Could not write ${configTomlPath}: ${e.message}`);
3464
+ }
3465
+ }
3466
+
3267
3467
  // Tier 9 (Local Project Harness): drop the dispatcher contract into the
3268
3468
  // CURRENT PROJECT only -- no writes to $HOME, no global skill/MCP sync, and
3269
3469
  // this runs as an early return BEFORE any global install step. A bare
@@ -3276,22 +3476,29 @@ function mergeBdbSettingsHooks(settingsPath, { projectLocal = false } = {}) {
3276
3476
  // non-interactively via `--project-harness` (combine with -y for CI).
3277
3477
  function installProjectHarness() {
3278
3478
  const projectClaudeDir = path.join(currentDir, '.claude');
3279
- installStep(`create ${projectClaudeDir}`, () => {
3479
+ const projectAgentsDir = path.join(currentDir, '.agents');
3480
+ const projectCodexDir = path.join(currentDir, '.codex');
3481
+
3482
+ installStep(`create harness directories`, () => {
3280
3483
  fs.mkdirSync(projectClaudeDir, { recursive: true });
3484
+ fs.mkdirSync(projectAgentsDir, { recursive: true });
3485
+ fs.mkdirSync(projectCodexDir, { recursive: true });
3281
3486
  }, 'The harness copies below will most likely fail as well.');
3282
3487
 
3283
3488
  installStep('copy .agents/ contract into project', () => {
3284
3489
  const agentsSrc = path.join(srcDir, '.agents');
3285
3490
  if (!fs.existsSync(agentsSrc)) throw new Error(`missing payload: ${agentsSrc}`);
3286
- copyDirRecursiveSync(agentsSrc, path.join(currentDir, '.agents'));
3287
- log.step(`Copied .agents/ contract to ${path.join(currentDir, '.agents')}`);
3491
+ copyDirRecursiveSync(agentsSrc, projectAgentsDir);
3492
+ log.step(`Copied .agents/ contract to ${projectAgentsDir}`);
3288
3493
  }, 'graph.md / state.schema.json may be missing in the project.');
3289
3494
 
3290
3495
  installStep('copy dispatcher workflows into project', () => {
3291
3496
  const workflowsSrc = path.join(srcDir, '.claude', 'workflows');
3292
3497
  if (fs.existsSync(workflowsSrc)) {
3293
3498
  copyDirRecursiveSync(workflowsSrc, path.join(projectClaudeDir, 'workflows'));
3294
- log.step(`Copied dispatcher workflows to ${path.join(projectClaudeDir, 'workflows')}`);
3499
+ copyDirRecursiveSync(workflowsSrc, path.join(projectAgentsDir, 'workflows'));
3500
+ copyDirRecursiveSync(workflowsSrc, path.join(projectCodexDir, 'workflows'));
3501
+ log.step(`Copied dispatcher workflows to project harnesses`);
3295
3502
  }
3296
3503
  }, '/startcycle dispatch degrades to graph.md as a manual guide.');
3297
3504
 
@@ -3299,7 +3506,9 @@ function installProjectHarness() {
3299
3506
  const hooksSrc = path.join(srcDir, '.claude', 'hooks');
3300
3507
  if (fs.existsSync(hooksSrc)) {
3301
3508
  copyDirRecursiveSync(hooksSrc, path.join(projectClaudeDir, 'hooks'));
3302
- log.step(`Copied hooks to ${path.join(projectClaudeDir, 'hooks')}`);
3509
+ copyDirRecursiveSync(hooksSrc, path.join(projectAgentsDir, 'hooks'));
3510
+ copyDirRecursiveSync(hooksSrc, path.join(projectCodexDir, 'hooks'));
3511
+ log.step(`Copied hooks to project harnesses`);
3303
3512
  }
3304
3513
  }, 'go-gate / graph-gate enforcement stays inactive in this project.');
3305
3514
 
@@ -3311,9 +3520,11 @@ function installProjectHarness() {
3311
3520
  }
3312
3521
  }, 'The dispatcher runs, but its agent-file pointers resolve to nothing.');
3313
3522
 
3314
- installStep('wire project .claude/settings.json to local hooks', () => {
3523
+ installStep('wire project harnesses to local hooks', () => {
3315
3524
  mergeBdbSettingsHooks(path.join(projectClaudeDir, 'settings.json'), { projectLocal: true });
3316
- log.step(`Wired project hooks in ${path.join(projectClaudeDir, 'settings.json')}`);
3525
+ mergeAntigravityHooks(path.join(projectAgentsDir, 'hooks.json'), { projectLocal: true });
3526
+ mergeCodexTomlHooks(path.join(projectCodexDir, 'config.toml'), { projectLocal: true });
3527
+ log.step(`Wired project hooks in Claude, Antigravity, and Codex configurations`);
3317
3528
  }, 'The gate hooks exist but are not auto-wired for this project.');
3318
3529
 
3319
3530
  log.success(`Project harness installed to ${currentDir}`);
@@ -3829,17 +4040,98 @@ async function universalHarnessSync(primaryMcpConfigPath) {
3829
4040
  const existing = readJsoncFile(targetPath);
3830
4041
  const data = existing && existing.mcp ? existing : Object.assign({}, existing || {}, { mcp: {} });
3831
4042
  if (masterMcpData.mcpServers) {
4043
+ // OpenCode provider APIs (e.g. OpenAI-compatible, Console) enforce tool name length limits
4044
+ // (e.g. max 64 chars) and context limits. Loading 20+ MCPs causes provider errors (e.g. 73-char tool names).
4045
+ // OpenCode uses a slim profile: only core servers (memb_mcp, zavora_computer_use) are enabled by default.
4046
+ const OPENCODE_DEFAULT_SLIM = new Set(['memb_mcp', 'zavora_computer_use']);
4047
+ const existingMcp = existing && existing.mcp ? existing.mcp : {};
4048
+ const hasExistingKeys = Object.keys(existingMcp).length > 0;
4049
+
3832
4050
  for (const [key, val] of Object.entries(masterMcpData.mcpServers)) {
3833
- const cmd = Array.isArray(val.command) ? val.command : [val.command];
3834
- const args = Array.isArray(val.args) ? val.args : [];
4051
+ const rawCmd = Array.isArray(val.command) ? val.command : [val.command];
4052
+ const rawArgs = Array.isArray(val.args) ? val.args : [];
4053
+ const fullCmd = [...rawCmd, ...rawArgs].map(c => {
4054
+ if (c === '__PYTHON_BIN__') {
4055
+ return process.platform === 'win32'
4056
+ ? path.join(homeDir, '.gemini', 'config', 'mcps', 'memb-mcp', '.venv', 'Scripts', 'python.exe')
4057
+ : path.join(homeDir, '.gemini', 'config', 'mcps', 'memb-mcp', '.venv', 'bin', 'python');
4058
+ }
4059
+ return c;
4060
+ });
4061
+
4062
+ const existingEntry = existingMcp[key];
4063
+
4064
+ // If existing config is intentionally slimmed (has keys, but omitted this one), don't resurrect unless in slim set
4065
+ if (hasExistingKeys && !existingEntry && !OPENCODE_DEFAULT_SLIM.has(key)) {
4066
+ continue;
4067
+ }
4068
+
4069
+ // Preserve user's explicit enabled/disabled setting; otherwise default to true only for slim set
4070
+ const isEnabled = existingEntry && typeof existingEntry.enabled === 'boolean'
4071
+ ? existingEntry.enabled
4072
+ : OPENCODE_DEFAULT_SLIM.has(key);
4073
+
4074
+ let envObj = val.environment || val.env;
4075
+ if (envObj) {
4076
+ envObj = Object.assign({}, envObj);
4077
+ for (const [eKey, eVal] of Object.entries(envObj)) {
4078
+ if (eVal === '__GEMINI_API_KEY__') {
4079
+ envObj[eKey] = '${GEMINI_API_KEY}';
4080
+ }
4081
+ }
4082
+ }
4083
+
3835
4084
  data.mcp[key] = {
3836
4085
  type: "local",
3837
- command: [...cmd, ...args],
3838
- enabled: true,
3839
- ...(val.environment || val.env ? { environment: val.environment || val.env } : {})
4086
+ command: fullCmd,
4087
+ enabled: isEnabled,
4088
+ ...(envObj ? { environment: envObj } : {})
3840
4089
  };
3841
4090
  }
3842
4091
  }
4092
+
4093
+ // Wire BDB AOS Plugin for OpenCode
4094
+ const opencodeDir = path.dirname(targetPath);
4095
+ const pluginsDir = path.join(opencodeDir, 'plugins');
4096
+ const pluginFile = path.join(pluginsDir, 'bdb-aos.js');
4097
+ const pluginSrc = path.join(srcDir, '.opencode', 'plugins', 'bdb-aos.js');
4098
+
4099
+ try {
4100
+ if (fs.existsSync(pluginSrc)) {
4101
+ fs.mkdirSync(pluginsDir, { recursive: true });
4102
+ fs.copyFileSync(pluginSrc, pluginFile);
4103
+ try { fs.chmodSync(pluginFile, 0o644); } catch (e) { logDebug(e, 'chmod pluginFile'); }
4104
+ }
4105
+ } catch (pluginErr) {
4106
+ log.warn(`Could not install OpenCode plugin: ${pluginErr.message}`);
4107
+ }
4108
+
4109
+ // Register plugin in opencode.jsonc if plugin file exists
4110
+ if (fs.existsSync(pluginFile)) {
4111
+ if (!Array.isArray(data.plugin)) {
4112
+ data.plugin = [];
4113
+ }
4114
+ const pluginPathNormalized = pluginFile.replace(/\\/g, '/');
4115
+ const alreadyRegistered = data.plugin.some(p => {
4116
+ const str = typeof p === 'string' ? p : (Array.isArray(p) ? p[0] : '');
4117
+ return str.includes('bdb-aos');
4118
+ });
4119
+ if (!alreadyRegistered) {
4120
+ data.plugin.push(pluginPathNormalized);
4121
+ }
4122
+ }
4123
+
4124
+ // Register skills paths for OpenCode
4125
+ if (!data.skills || typeof data.skills !== 'object') {
4126
+ data.skills = { paths: [".agents/skills"] };
4127
+ } else if (Array.isArray(data.skills.paths)) {
4128
+ if (!data.skills.paths.includes(".agents/skills")) {
4129
+ data.skills.paths.push(".agents/skills");
4130
+ }
4131
+ } else {
4132
+ data.skills.paths = [".agents/skills"];
4133
+ }
4134
+
3843
4135
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
3844
4136
  fs.writeFileSync(targetPath, JSON.stringify(data, null, 2), { mode: 0o600 });
3845
4137
  try { fs.chmodSync(targetPath, 0o600); } catch (e) { logDebug(e, 'chmod targetPath'); }
@@ -4406,6 +4698,9 @@ module.exports = {
4406
4698
  detectPlatforms,
4407
4699
  markPlatformsExplicit,
4408
4700
  mergeBdbSettingsHooks,
4701
+ mergeAntigravityHooks,
4702
+ mergeCodexTomlHooks,
4703
+ mergeCodexHooks: mergeCodexTomlHooks,
4409
4704
  installGlobalHooks,
4410
4705
  installProjectHarness,
4411
4706
  mirrorMcpServersTo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridlabor-api/aos",
3
- "version": "4.5.0",
3
+ "version": "4.5.2",
4
4
  "description": "AOS — A Curated AI AGENT OS. Optimized agent skills and add-ons like memB, OpenWiki, Heimdall Token Saver, and Godmode architectures.",
5
5
  "main": "installer.js",
6
6
  "engines": {
@@ -36,15 +36,12 @@ believing memory works everywhere:
36
36
  | | Skills | MCP | Hooks |
37
37
  |---|---|---|---|
38
38
  | Claude Code | `~/.claude/skills` | `~/.claude.json` | **yes** |
39
- | Antigravity | `~/.gemini/config/skills` | `mcp_config.json` | no |
40
- | Codex | `~/.codex/skills` | `config.toml` | no |
39
+ | Antigravity | `~/.gemini/config/skills` | `mcp_config.json` | **yes** (.agents/hooks.json / hooks.json) |
40
+ | Codex | `~/.codex/skills` | `config.toml` | **yes** (config.toml) |
41
41
  | OpenCode | — | `opencode.jsonc` | no |
42
42
  | Cursor / Roo | `bdb-skills` | `mcp.json` | no |
43
43
 
44
- Only Claude Code has hooks, so only there can memB inject per prompt. Elsewhere
45
- the same context arrives through the rule files that harness loads at start —
46
- see section 5 — which means it is as fresh as the last write, not as fresh as
47
- the prompt. Say so plainly rather than letting someone assume parity.
44
+ Claude Code, Google Antigravity, and OpenAI Codex have native hook support, allowing memB to inject ambient memory per prompt across all three harnesses. For hookless harnesses (Cursor, Roo, OpenCode, Windsurf), the same context arrives through the rule files that harness loads at start — see section 5 — which means it is as fresh as the last write, not as fresh as the prompt. Say so plainly rather than letting someone assume parity.
48
45
 
49
46
  ---
50
47
 
@@ -173,7 +170,8 @@ injects the relevant memories as context. It fails open: any error exits `0`
173
170
  and the prompt proceeds untouched.
174
171
 
175
172
  Since v4.4.0 the installer ships `memb-inject.mjs` into `~/.claude/hooks/` and
176
- wires it as a `UserPromptSubmit` hook. **v4.4.0's Quick Update did not** — it
173
+ wires it as an automated prompt hook across Claude Code (`UserPromptSubmit` in `settings.json`),
174
+ Google Antigravity (`PreInvocation` in `hooks.json` / `.agents/hooks.json`), and OpenAI Codex (`UserPromptSubmit` in `config.toml`). **v4.4.0's Quick Update did not** — it
177
175
  refreshes skills and submodules, and hooks are harness plumbing rather than
178
176
  skills, so a machine that already had AOS updated to 4.4.0 without ever
179
177
  receiving the hook. Fixed in v4.4.1; a machine that took that update needs one
@@ -183,7 +181,7 @@ more run:
183
181
  npx -y @hybridlabor-api/aos@latest
184
182
  ```
185
183
 
186
- The wiring is merged into `~/.claude/settings.json`, never written over it:
184
+ The wiring is merged into `~/.claude/settings.json`, Antigravity `hooks.json`, and Codex `config.toml`, never written over them:
187
185
  user keys and foreign hook entries survive, and a re-run replaces the BDB entry
188
186
  rather than adding a second copy.
189
187
 
@@ -199,7 +197,7 @@ harness — the memB store is machine-global, and pointing it at
199
197
  `$CLAUDE_PROJECT_DIR` would make it fail on every prompt in any project the
200
198
  harness was never installed into.
201
199
 
202
- **On a harness without hooks** — everything except Claude Code — the same
200
+ **On hookless harnesses** — Cursor, Roo, OpenCode, Windsurf — native hooks are not supported, so the same
203
201
  context is written into the rule files that harness loads instead:
204
202
 
205
203
  ```bash