@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.
- package/.claude/hooks/memb-inject.mjs +135 -40
- package/.claude/workflows/startcycle-dispatch.mjs +318 -3
- package/.opencode/plugins/bdb-aos.js +171 -0
- package/bin/aos-dashboard.mjs +8 -2
- package/installer.js +307 -12
- package/package.json +1 -1
- package/skills/global_config/aos-setup/SKILL.md +7 -9
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +43 -1
- package/skills/global_config/openwiki-skill/scripts/install_daemon.ps1 +107 -18
- package/.agents/skills/firecrawl/SKILL.md +0 -149
- package/.agents/skills/firecrawl/rules/install.md +0 -82
- package/.agents/skills/firecrawl/rules/security.md +0 -26
- package/.agents/skills/firecrawl-agent/SKILL.md +0 -58
- package/.agents/skills/firecrawl-build/SKILL.md +0 -39
- package/.agents/skills/firecrawl-build-interact/SKILL.md +0 -68
- package/.agents/skills/firecrawl-build-onboarding/SKILL.md +0 -103
- package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +0 -39
- package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +0 -20
- package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +0 -17
- package/.agents/skills/firecrawl-build-scrape/SKILL.md +0 -69
- package/.agents/skills/firecrawl-build-search/SKILL.md +0 -69
- package/.agents/skills/firecrawl-crawl/SKILL.md +0 -59
- package/.agents/skills/firecrawl-download/SKILL.md +0 -70
- package/.agents/skills/firecrawl-interact/SKILL.md +0 -84
- package/.agents/skills/firecrawl-map/SKILL.md +0 -51
- package/.agents/skills/firecrawl-scrape/SKILL.md +0 -69
- package/.agents/skills/firecrawl-search/SKILL.md +0 -60
- package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +0 -10
- package/mcps/after-effects-mcp/build/index.js +0 -840
- package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +0 -153
- package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +0 -218
- package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +0 -71
- package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +0 -147
- package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +0 -114
- package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +0 -115
- package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +0 -192
- package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +0 -90
- package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +0 -50
- package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +0 -1773
- package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +0 -160
- package/mcps/computer-use-mcp/dist/client.d.ts +0 -150
- package/mcps/computer-use-mcp/dist/client.js +0 -136
- package/mcps/computer-use-mcp/dist/entrypoint.d.ts +0 -16
- package/mcps/computer-use-mcp/dist/entrypoint.js +0 -26
- package/mcps/computer-use-mcp/dist/native.d.ts +0 -212
- package/mcps/computer-use-mcp/dist/native.js +0 -50
- package/mcps/computer-use-mcp/dist/server.d.ts +0 -32
- package/mcps/computer-use-mcp/dist/server.js +0 -342
- package/mcps/computer-use-mcp/dist/session.d.ts +0 -101
- 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
|
+
}
|
package/bin/aos-dashboard.mjs
CHANGED
|
@@ -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
|
-
|
|
607
|
-
|
|
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
|
-
|
|
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,
|
|
3287
|
-
log.step(`Copied .agents/ contract to ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3523
|
+
installStep('wire project harnesses to local hooks', () => {
|
|
3315
3524
|
mergeBdbSettingsHooks(path.join(projectClaudeDir, 'settings.json'), { projectLocal: true });
|
|
3316
|
-
|
|
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
|
|
3834
|
-
const
|
|
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:
|
|
3838
|
-
enabled:
|
|
3839
|
-
...(
|
|
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.
|
|
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` |
|
|
40
|
-
| Codex | `~/.codex/skills` | `config.toml` |
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|