@hybridlabor-api/aos 4.7.0 → 4.7.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/.agents/agents.md +8 -3
- package/.agents/nodes.json +2 -1
- package/.agents/state.schema.json +24 -0
- package/.agents/vendor-manifest.json +6 -0
- package/.agents/workflows/startcycle-graph.md +2 -2
- package/.claude/agents/architect.md +5 -4
- package/.claude/agents/techlead.md +2 -0
- package/.claude/workflows/startcycle-dispatch.mjs +175 -17
- package/.opencode/agents/architect.md +5 -4
- package/.opencode/agents/techlead.md +2 -0
- package/THIRD_PARTY_NOTICES.md +9 -1
- package/bin/aos-doctor.mjs +386 -0
- package/bin/aos-store.mjs +171 -0
- package/docs/AUDIT_BRIEF_ECC_STORE.md +122 -0
- package/docs/PLAN_AOS_STORE_PHASE1.md +190 -0
- package/installer.js +68 -15
- package/lib/aos-archify-contract.mjs +277 -0
- package/lib/ecc-store-index.json +1 -0
- package/mcp_config.json +4 -0
- package/mcps/mcsc/packages/core/src/adapters/codex.js +160 -37
- package/package.json +7 -2
- package/scripts/build-ecc-store-index.mjs +147 -0
- package/skills/basic/startcycle-graph/SKILL.md +13 -0
- package/skills/global_config/agenttrail/bin/agenttrail.mjs +153 -42
- package/skills/global_config/agenttrail/public/index.html +5 -4
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +8 -2
- package/skills/global_config/archify/examples/dataflow-product-analytics.html +15048 -0
- package/skills/global_config/archify/examples/lifecycle-agent-run.html +14983 -0
- package/skills/global_config/archify/examples/sequence-cache-miss-request.html +15063 -0
- package/skills/global_config/archify/examples/web-app-rendered.html +15012 -0
- package/skills/global_config/archify/examples/workflow-agent-tool-call-rendered.html +15054 -0
- package/skills/global_config/ask-tim/SKILL.md +10 -0
- package/skills/global_config/mcsc/SKILL.md +60 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +31 -6
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +4 -2
- package/docs/handover/agenttrail-for-ao.md +0 -58
- package/docs/handover/deja-for-ao.md +0 -51
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// AOS System Checkup & Doctor: Verifies system dependencies, file placement,
|
|
3
|
+
// cross-platform harness synchronization, daemons and hooks across macOS and Windows.
|
|
4
|
+
// Usage:
|
|
5
|
+
// aos doctor
|
|
6
|
+
// node bin/aos-doctor.mjs [--json] [--net]
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { connect } from 'node:net';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
|
|
14
|
+
const HOME = os.homedir();
|
|
15
|
+
const JSON_OUT = process.argv.includes('--json');
|
|
16
|
+
const NET = process.argv.includes('--net');
|
|
17
|
+
const IS_MAC = process.platform === 'darwin';
|
|
18
|
+
const IS_WIN = process.platform === 'win32';
|
|
19
|
+
const IS_ARM64 = process.arch === 'arm64';
|
|
20
|
+
|
|
21
|
+
const h = (...p) => path.join(HOME, ...p);
|
|
22
|
+
const tilde = (p) => (p || '').replace(HOME, '~');
|
|
23
|
+
const readJson = (p) => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; } };
|
|
24
|
+
|
|
25
|
+
const firstExisting = (candidates) => candidates.find((c) => existsSync(c)) || null;
|
|
26
|
+
|
|
27
|
+
const MODULE_BASES = [
|
|
28
|
+
h('.agents'),
|
|
29
|
+
h('.claude'),
|
|
30
|
+
h('dev', 'bdb-dev'),
|
|
31
|
+
...(process.env.npm_config_prefix ? [path.join(process.env.npm_config_prefix, 'lib', 'node_modules')] : []),
|
|
32
|
+
'/usr/local/lib/node_modules',
|
|
33
|
+
'/opt/homebrew/lib/node_modules',
|
|
34
|
+
];
|
|
35
|
+
const findModule = (name) => firstExisting(MODULE_BASES.map((b) => path.join(b, name)));
|
|
36
|
+
|
|
37
|
+
const MCP_DIRS = [
|
|
38
|
+
h('.gemini', 'config', 'mcps'),
|
|
39
|
+
h('Library', 'Application Support', 'Claude', 'mcps'),
|
|
40
|
+
...(process.env.APPDATA ? [path.join(process.env.APPDATA, 'Claude', 'mcps')] : []),
|
|
41
|
+
h('.cursor', 'mcps'),
|
|
42
|
+
h('.codex', 'mcps'),
|
|
43
|
+
h('.windsurf', 'mcps'),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const results = [];
|
|
47
|
+
const add = (area, name, ok, detail, fix, warningOnly = false) => {
|
|
48
|
+
results.push({ area, name, ok, detail, fix, warningOnly });
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const which = (bin) => {
|
|
52
|
+
const probe = IS_WIN ? 'where.exe' : 'which';
|
|
53
|
+
try {
|
|
54
|
+
return tilde(execFileSync(probe, [bin], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim().split(/\r?\n/)[0]);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const dirCount = (p) => {
|
|
61
|
+
try {
|
|
62
|
+
return readdirSync(p, { withFileTypes: true }).filter(d => d.isDirectory()).length;
|
|
63
|
+
} catch {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function portOpen(port, timeout = 1200) {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
const s = connect({ host: '127.0.0.1', port });
|
|
71
|
+
const done = (v) => { s.destroy(); resolve(v); };
|
|
72
|
+
s.setTimeout(timeout);
|
|
73
|
+
s.once('connect', () => done(true));
|
|
74
|
+
s.once('timeout', () => done(false));
|
|
75
|
+
s.once('error', () => done(false));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------- 1. System Prereqs
|
|
80
|
+
function checkPrereqs() {
|
|
81
|
+
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
82
|
+
add('prereq', 'Node.js runtime', nodeMajor >= 20, `v${process.versions.node} (${nodeMajor >= 22 ? 'optimal for OpenWiki' : '>= 20 required'})`,
|
|
83
|
+
'Update Node.js to v20 or v22+ via nvm or nodejs.org.');
|
|
84
|
+
|
|
85
|
+
const pyProbe = which('python3') || which('python') || (IS_WIN ? which('py') : null);
|
|
86
|
+
add('prereq', 'Python 3', !!pyProbe, pyProbe || 'missing — needed for memB venv & OpenWiki',
|
|
87
|
+
IS_WIN ? 'Install Python from python.org or winget install Python.Python.3.12' : 'brew install python3');
|
|
88
|
+
|
|
89
|
+
const gitProbe = which('git');
|
|
90
|
+
add('prereq', 'Git CLI', !!gitProbe, gitProbe || 'missing — required for AOS workflows and worktrees',
|
|
91
|
+
IS_WIN ? 'Install Git via git-scm.com or winget install Git.Git' : 'brew install git / xcode-select --install');
|
|
92
|
+
|
|
93
|
+
const uvProbe = which('uv');
|
|
94
|
+
add('prereq', 'uv package manager', !!uvProbe, uvProbe ? `${uvProbe} (fast venv seeding)` : 'optional — pip fallback will be used',
|
|
95
|
+
'curl -LsSf https://astral.sh/uv/install.sh | sh (or pip install uv)', true);
|
|
96
|
+
|
|
97
|
+
const ghProbe = which('gh');
|
|
98
|
+
add('prereq', 'GitHub CLI (gh)', !!ghProbe, ghProbe ? `${ghProbe}` : 'optional — needed for repo automation & release PRs',
|
|
99
|
+
'Install GitHub CLI: brew install gh / winget install GitHub.cli', true);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------- 2. AOS Core Placement & Store
|
|
103
|
+
function checkAosCore() {
|
|
104
|
+
const manifestPath = h('.agents', '.bdb-manifest.json');
|
|
105
|
+
const manifest = readJson(manifestPath);
|
|
106
|
+
add('aos-core', 'Install Manifest', !!manifest,
|
|
107
|
+
manifest ? `v${manifest.version || '4.x'} · tier ${manifest.tier || 'full'} · modules: ${(manifest.installedModules || []).join(', ') || 'default'}` : `${manifestPath} missing or unreadable`,
|
|
108
|
+
'Run the AOS installer: npx @hybridlabor-api/aos@latest');
|
|
109
|
+
|
|
110
|
+
// Network version check gated by --net
|
|
111
|
+
if (NET) {
|
|
112
|
+
try {
|
|
113
|
+
const npmBin = IS_WIN ? 'npm.cmd' : 'npm';
|
|
114
|
+
const latest = execFileSync(npmBin, ['view', '@hybridlabor-api/aos', 'version'], {
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
timeout: 8000,
|
|
117
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
118
|
+
shell: IS_WIN
|
|
119
|
+
}).trim();
|
|
120
|
+
const currentVer = manifest?.version || '4.7.1';
|
|
121
|
+
add('aos-core', 'Version vs npm', currentVer === latest, `local v${currentVer} · npm v${latest}`,
|
|
122
|
+
'Run: npx @hybridlabor-api/aos@latest');
|
|
123
|
+
} catch {
|
|
124
|
+
add('aos-core', 'Version vs npm', false, 'npm view failed (offline or network timeout)', 'Re-run with network or omit --net', true);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
add('aos-core', 'Version vs npm', true, 'Skipped (offline mode — pass --net to check npm)', '', true);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check store index
|
|
131
|
+
const storeIndexCandidates = [
|
|
132
|
+
path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'lib', 'ecc-store-index.json'),
|
|
133
|
+
h('.agents', 'lib', 'ecc-store-index.json'),
|
|
134
|
+
h('dev', 'bdb-dev', 'bdb-dev-optimized-agent-skills', 'lib', 'ecc-store-index.json')
|
|
135
|
+
];
|
|
136
|
+
const storeIndexPath = firstExisting(storeIndexCandidates);
|
|
137
|
+
let storeSkillsCount = 0;
|
|
138
|
+
if (storeIndexPath) {
|
|
139
|
+
try {
|
|
140
|
+
const idx = readJson(storeIndexPath);
|
|
141
|
+
storeSkillsCount = (idx?.skills ? Object.keys(idx.skills).length : 0);
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
add('aos-core', 'Offline Store Index', !!storeIndexPath && storeSkillsCount > 0,
|
|
145
|
+
storeIndexPath ? `${tilde(storeIndexPath)} (${storeSkillsCount} catalog skills indexed)` : 'Store index missing',
|
|
146
|
+
'Run scripts/build-ecc-store-index.mjs or re-run the AOS installer.');
|
|
147
|
+
|
|
148
|
+
// Check MCSC registration
|
|
149
|
+
const mcpConfigCandidates = [
|
|
150
|
+
path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'mcp_config.json'),
|
|
151
|
+
h('.agents', 'mcp_config.json'),
|
|
152
|
+
h('dev', 'bdb-dev', 'bdb-dev-optimized-agent-skills', 'mcp_config.json')
|
|
153
|
+
];
|
|
154
|
+
const mcpConfigPath = firstExisting(mcpConfigCandidates);
|
|
155
|
+
const mcpConfig = mcpConfigPath ? readJson(mcpConfigPath) : null;
|
|
156
|
+
const hasMcsc = !!mcpConfig?.mcpServers?.mcsc;
|
|
157
|
+
add('aos-core', 'MCSC Telemetry Registration', hasMcsc,
|
|
158
|
+
hasMcsc ? `Registered in ${tilde(mcpConfigPath)}` : 'mcsc missing from mcp_config.json',
|
|
159
|
+
'Re-run installer to register mcsc cross-harness adapter.');
|
|
160
|
+
|
|
161
|
+
// Retired module check (CDC bug prevention)
|
|
162
|
+
const retired = findModule('bdb-os-agent-workspace');
|
|
163
|
+
if (retired) {
|
|
164
|
+
add('aos-core', 'Retired AO module', false,
|
|
165
|
+
`${tilde(retired)} — archived predecessor with known CDC loop bug`,
|
|
166
|
+
`Remove directory: rm -rf ${tilde(retired)} — AO is now bdb-agent-orchestrator.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------- 3. Harness Placement & Skills Sync
|
|
171
|
+
function checkHarnesses() {
|
|
172
|
+
const harnesses = [
|
|
173
|
+
{ name: 'Claude Code', path: h('.claude', 'skills') },
|
|
174
|
+
{ name: 'Gemini / Antigravity', path: firstExisting([h('.gemini', 'config', 'skills'), h('.gemini', 'antigravity-cli', 'skills')]) || h('.gemini', 'config', 'skills') },
|
|
175
|
+
{ name: 'Codex', path: h('.codex', 'skills') },
|
|
176
|
+
{ name: 'Cursor', path: h('.cursor', 'skills') },
|
|
177
|
+
{ name: 'Roo Code', path: h('.roo', 'skills') },
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const SENTINEL = 'startcycle';
|
|
181
|
+
for (const hr of harnesses) {
|
|
182
|
+
const exists = existsSync(hr.path);
|
|
183
|
+
const count = dirCount(hr.path);
|
|
184
|
+
const hasSentinel = existsSync(path.join(hr.path, SENTINEL, 'SKILL.md'));
|
|
185
|
+
const ok = exists && count > 0 && hasSentinel;
|
|
186
|
+
add('harnesses', `${hr.name} Skills`, ok,
|
|
187
|
+
ok ? `${tilde(hr.path)} (${count} skills, sentinel verified)` : (exists ? `${tilde(hr.path)} (${count} skills, sentinel missing)` : `${tilde(hr.path)} not synced`),
|
|
188
|
+
`Run 'npx @hybridlabor-api/aos@latest' and select ${hr.name} to sync skills.`, !exists);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// OpenCode Plugin check
|
|
192
|
+
const opencodePlugin = firstExisting([
|
|
193
|
+
h('.opencode', 'plugins', 'bdb-aos.js'),
|
|
194
|
+
h('.config', 'opencode', 'plugins', 'bdb-aos.js')
|
|
195
|
+
]);
|
|
196
|
+
add('harnesses', 'OpenCode Plugin', !!opencodePlugin,
|
|
197
|
+
opencodePlugin ? tilde(opencodePlugin) : 'bdb-aos.js not installed in OpenCode plugins',
|
|
198
|
+
'Run the AOS installer to wire OpenCode telemetry plugin.', true);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------- 4. Hooks & Security Gates
|
|
202
|
+
function checkHooks() {
|
|
203
|
+
const claudeHooksDir = h('.claude', 'hooks');
|
|
204
|
+
const EXPECTED_VERSION = { 'memb-inject.mjs': 5 };
|
|
205
|
+
const versionOf = (text) => {
|
|
206
|
+
const m = /^\/\/\s*aos-hook-version:\s*(\d+)/m.exec(text);
|
|
207
|
+
return m ? Number(m[1]) : null;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
for (const file of ['go-gate.mjs', 'graph-gate.mjs', 'memb-inject.mjs']) {
|
|
211
|
+
const p = path.join(claudeHooksDir, file);
|
|
212
|
+
if (!existsSync(p)) {
|
|
213
|
+
add('hooks', file, false, 'missing from ~/.claude/hooks',
|
|
214
|
+
'Run the installer to wire Claude Code safety hooks.');
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const want = EXPECTED_VERSION[file];
|
|
218
|
+
if (!want) {
|
|
219
|
+
add('hooks', file, true, tilde(p), '');
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const got = versionOf(readFileSync(p, 'utf8'));
|
|
223
|
+
add('hooks', file, got === want,
|
|
224
|
+
got === want ? `${tilde(p)} (v${got})` : `${tilde(p)} (v${got || 'legacy'}, expected v${want})`,
|
|
225
|
+
'Run the AOS installer to update to latest hook version.');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Claude settings.json wiring
|
|
229
|
+
const claudeSettings = readJson(h('.claude', 'settings.json'));
|
|
230
|
+
const wired = JSON.stringify(claudeSettings?.hooks || {});
|
|
231
|
+
const claudeWiredOk = wired.includes('go-gate.mjs') && wired.includes('memb-inject.mjs');
|
|
232
|
+
add('hooks', 'Claude settings.json wired', claudeWiredOk,
|
|
233
|
+
claudeWiredOk ? 'PreToolUse & UserPromptSubmit registered' : 'Hooks not fully registered in ~/.claude/settings.json',
|
|
234
|
+
'Run the AOS installer to wire hooks in settings.json.');
|
|
235
|
+
|
|
236
|
+
// Antigravity hooks
|
|
237
|
+
const agHooksFile = firstExisting([
|
|
238
|
+
h('.gemini', 'antigravity-cli', 'hooks.json'),
|
|
239
|
+
h('.gemini', 'config', 'hooks.json'),
|
|
240
|
+
h('.agents', 'hooks.json')
|
|
241
|
+
]);
|
|
242
|
+
const agHooks = agHooksFile ? readJson(agHooksFile) : null;
|
|
243
|
+
const agWired = JSON.stringify(agHooks?.hooks || {});
|
|
244
|
+
add('hooks', 'Antigravity hooks', agWired.includes('memb-inject.mjs'),
|
|
245
|
+
agHooksFile ? `${tilde(agHooksFile)} (${agWired.includes('memb-inject.mjs') ? 'memb-inject wired' : 'unwired'})` : 'hooks.json not found',
|
|
246
|
+
'Run the AOS installer to configure Antigravity hooks.', true);
|
|
247
|
+
|
|
248
|
+
// Codex hooks
|
|
249
|
+
const codexConf = h('.codex', 'config.toml');
|
|
250
|
+
let codexToml = '';
|
|
251
|
+
try { codexToml = readFileSync(codexConf, 'utf8'); } catch {}
|
|
252
|
+
add('hooks', 'Codex config.toml hooks', codexToml.includes('AOS:HOOKS'),
|
|
253
|
+
existsSync(codexConf) ? `${tilde(codexConf)} (${codexToml.includes('AOS:HOOKS') ? 'AOS:HOOKS block wired' : 'no AOS:HOOKS'})` : `${tilde(codexConf)} missing`,
|
|
254
|
+
'Run the AOS installer to wire Codex hooks.', true);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------- 5. Daemons & Ecosystem Modules (Including AO)
|
|
258
|
+
async function checkDaemonsAndModules() {
|
|
259
|
+
// memB
|
|
260
|
+
const membDir = findModule('memB');
|
|
261
|
+
const membListening = await portOpen(8088);
|
|
262
|
+
add('daemons', 'memB WebUI (:8088)', membListening,
|
|
263
|
+
membListening ? 'Active & listening on http://127.0.0.1:8088' : 'Not listening on port 8088',
|
|
264
|
+
IS_MAC ? 'launchctl load -w ~/Library/LaunchAgents/com.bdb.memb.webui.plist' : 'Start src/backend/server.py in memB module.');
|
|
265
|
+
|
|
266
|
+
// Synapse 3D
|
|
267
|
+
const synapseListening = await portOpen(7781);
|
|
268
|
+
add('daemons', 'Synapse 3D (:7781)', synapseListening,
|
|
269
|
+
synapseListening ? 'Active & listening on http://127.0.0.1:7781' : 'Not listening on port 7781',
|
|
270
|
+
IS_MAC ? 'launchctl load -w ~/Library/LaunchAgents/com.bdb.synapse.plist' : 'synapse serve --port 7781', true);
|
|
271
|
+
|
|
272
|
+
// BDB OS Remote Gateway
|
|
273
|
+
const remoteListening = await portOpen(9080);
|
|
274
|
+
add('daemons', 'Remote Gateway (:9080)', remoteListening,
|
|
275
|
+
remoteListening ? 'Active & listening on port 9080' : 'Not listening on port 9080',
|
|
276
|
+
'Start gateway via ~/dev/bdb-dev/bdb-os-remote/start-gateway.sh', true);
|
|
277
|
+
|
|
278
|
+
// BDB Agent Orchestrator (AO)
|
|
279
|
+
const aoBin = which('ao') || (existsSync(h('.local', 'bin', 'ao')) ? h('.local', 'bin', 'ao') : null);
|
|
280
|
+
let aoVer = null;
|
|
281
|
+
if (aoBin) {
|
|
282
|
+
try {
|
|
283
|
+
aoVer = execFileSync(aoBin, ['--version'], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
284
|
+
} catch {}
|
|
285
|
+
}
|
|
286
|
+
const aoListening = await portOpen(3101);
|
|
287
|
+
let aoServiceInfo = 'Not running';
|
|
288
|
+
if (aoBin) {
|
|
289
|
+
try {
|
|
290
|
+
const statusOut = execFileSync(aoBin, ['service', 'status'], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
291
|
+
const runningLine = statusOut.split('\n').find(l => l.toLowerCase().includes('running'));
|
|
292
|
+
if (runningLine) aoServiceInfo = runningLine.trim();
|
|
293
|
+
} catch {}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const aoOk = !!aoBin;
|
|
297
|
+
add('daemons', 'AO Agent Orchestrator', aoOk,
|
|
298
|
+
aoBin ? `${tilde(aoBin)} (${aoVer || 'installed'}) · Daemon :3101: ${aoListening ? 'ONLINE' : 'STOPPED'} (${aoServiceInfo})` : 'ao binary not found',
|
|
299
|
+
IS_MAC && IS_ARM64
|
|
300
|
+
? 'Enable AO in the installer or run: ao service install'
|
|
301
|
+
: 'AO binary is arm64 macOS native; build from source on other platforms: github.com/hybridlabor-api/bdb-agent-orchestrator', false);
|
|
302
|
+
|
|
303
|
+
// Code-signing check for AO on macOS
|
|
304
|
+
if (IS_MAC && aoBin) {
|
|
305
|
+
let signed = false;
|
|
306
|
+
try {
|
|
307
|
+
execFileSync('codesign', ['-v', aoBin], { stdio: 'ignore' });
|
|
308
|
+
signed = true;
|
|
309
|
+
} catch {}
|
|
310
|
+
add('security', 'AO Binary Signature (AMFI)', signed,
|
|
311
|
+
signed ? 'Binary code-signed (AMFI safe)' : 'Unsigned binary — AMFI might terminate on launch',
|
|
312
|
+
`Run: codesign -s - -f "${aoBin}"`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------- 6. Output & Announcement
|
|
317
|
+
function printAoAnnouncement() {
|
|
318
|
+
console.log(`
|
|
319
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
320
|
+
│ │
|
|
321
|
+
│ 🚀 BDB AGENT ORCHESTRATOR APP — FINALE BETA JETZT VERFÜGBAR! │
|
|
322
|
+
│ │
|
|
323
|
+
│ Die nächste Generation der Cross-Harness Multi-Agenten-Orchestrierung │
|
|
324
|
+
│ ist jetzt als finale Beta für alle User freigeschaltet. │
|
|
325
|
+
│ │
|
|
326
|
+
│ • Dashboard & WebUI: http://localhost:3101 │
|
|
327
|
+
│ • Service-Befehl: ao service install (Hintergrunddienst aktivieren) │
|
|
328
|
+
│ • Quick Launch: ao open oder ao service status │
|
|
329
|
+
│ • Features: Session-Telemetrie, Live-AgentTrail & Multi-Workspaces│
|
|
330
|
+
│ │
|
|
331
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
332
|
+
`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function report() {
|
|
336
|
+
if (JSON_OUT) {
|
|
337
|
+
const ok = results.filter(r => !r.warningOnly).every(r => r.ok);
|
|
338
|
+
console.log(JSON.stringify({ ok, platform: process.platform, arch: process.arch, results }, null, 2));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
printAoAnnouncement();
|
|
343
|
+
|
|
344
|
+
console.log(`\n======================================================`);
|
|
345
|
+
console.log(` 🩺 AOS SYSTEM CHECKUP & DIAGNOSTIC REPORT`);
|
|
346
|
+
console.log(` Platform: ${process.platform} (${process.arch}) · Node: ${process.version}`);
|
|
347
|
+
console.log(`======================================================`);
|
|
348
|
+
|
|
349
|
+
let currentArea = '';
|
|
350
|
+
for (const r of results) {
|
|
351
|
+
if (r.area !== currentArea) {
|
|
352
|
+
currentArea = r.area;
|
|
353
|
+
console.log(`\n[${currentArea.toUpperCase()}]`);
|
|
354
|
+
}
|
|
355
|
+
const icon = r.ok ? '✅' : (r.warningOnly ? '⚠️ ' : '❌');
|
|
356
|
+
console.log(` ${icon} ${r.name.padEnd(28)} ${r.detail}`);
|
|
357
|
+
if (!r.ok && r.fix) {
|
|
358
|
+
console.log(` ↳ Fix: ${r.fix}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const criticalFails = results.filter(r => !r.ok && !r.warningOnly);
|
|
363
|
+
const warnings = results.filter(r => !r.ok && r.warningOnly);
|
|
364
|
+
|
|
365
|
+
console.log(`\n------------------------------------------------------`);
|
|
366
|
+
if (criticalFails.length === 0) {
|
|
367
|
+
console.log(`🎉 ALL CRITICAL CHECKS PASSED (${results.length - warnings.length}/${results.length})! AOS is properly configured.`);
|
|
368
|
+
if (warnings.length > 0) {
|
|
369
|
+
console.log(` (${warnings.length} optional warning(s) detected — see hints above).`);
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
console.log(`⚠️ ${criticalFails.length} critical check(s) need attention! Follow the fix instructions above.`);
|
|
373
|
+
}
|
|
374
|
+
console.log(`------------------------------------------------------\n`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ---------------------------------------------------------------- Run
|
|
378
|
+
checkPrereqs();
|
|
379
|
+
checkAosCore();
|
|
380
|
+
checkHarnesses();
|
|
381
|
+
checkHooks();
|
|
382
|
+
await checkDaemonsAndModules();
|
|
383
|
+
report();
|
|
384
|
+
|
|
385
|
+
const hasCriticalFails = results.some(r => !r.ok && !r.warningOnly);
|
|
386
|
+
process.exit(hasCriticalFails ? 1 : 0);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const INDEX_PATH = join(ROOT, 'lib', 'ecc-store-index.json');
|
|
11
|
+
const index = JSON.parse(readFileSync(INDEX_PATH, 'utf8'));
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const dryRun = args.includes('--dry-run');
|
|
14
|
+
const project = args.includes('--project');
|
|
15
|
+
const net = args.includes('--net');
|
|
16
|
+
const positional = args.filter((arg) => !arg.startsWith('--'));
|
|
17
|
+
const option = (name) => args.find((arg) => arg === name || arg.startsWith(`${name}=`))?.split('=').slice(1).join('=');
|
|
18
|
+
|
|
19
|
+
function loadIndex() {
|
|
20
|
+
if (!index.pinned_commit || !index.skills || !index.subagents) throw new Error('Invalid AOS store index');
|
|
21
|
+
return index;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function allItems(type) {
|
|
25
|
+
const selected = type === 'agents' || type === 'subagents' ? 'subagents' : 'skills';
|
|
26
|
+
return Object.entries(index[selected] || {}).map(([name, item]) => ({ name, item, type: selected }));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function findItem(name) {
|
|
30
|
+
for (const type of ['skills', 'subagents']) {
|
|
31
|
+
if (index[type]?.[name]) return { name, item: index[type][name], type };
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function localSkillNames() {
|
|
37
|
+
const names = new Set();
|
|
38
|
+
const visit = (dir) => {
|
|
39
|
+
if (!existsSync(dir)) return;
|
|
40
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
41
|
+
const fullPath = join(dir, entry.name);
|
|
42
|
+
if (entry.isDirectory()) visit(fullPath);
|
|
43
|
+
else if (entry.name === 'SKILL.md') names.add(basename(dirname(fullPath)));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
visit(join(ROOT, 'skills'));
|
|
47
|
+
return names;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function localAgentNames() {
|
|
51
|
+
const names = new Set();
|
|
52
|
+
for (const dir of [join(ROOT, '.claude', 'agents'), join(ROOT, '.agents', 'agents'), join(ROOT, 'agents')]) {
|
|
53
|
+
if (!existsSync(dir)) continue;
|
|
54
|
+
for (const entry of readdirSync(dir)) names.add(entry.replace(/\.md$/i, ''));
|
|
55
|
+
}
|
|
56
|
+
return names;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function globalRoots(type) {
|
|
60
|
+
const home = homedir();
|
|
61
|
+
return type === 'skills'
|
|
62
|
+
? [
|
|
63
|
+
join(home, '.agents', 'skills'),
|
|
64
|
+
join(home, '.claude', 'skills'),
|
|
65
|
+
join(home, '.codex', 'skills'),
|
|
66
|
+
join(home, '.cursor', 'skills'),
|
|
67
|
+
join(home, '.roo', 'skills'),
|
|
68
|
+
join(home, '.gemini', 'config', 'skills'),
|
|
69
|
+
]
|
|
70
|
+
: [
|
|
71
|
+
join(home, '.agents', 'agents'),
|
|
72
|
+
join(home, '.claude', 'agents'),
|
|
73
|
+
join(home, '.codex', 'agents'),
|
|
74
|
+
join(home, '.cursor', 'agents'),
|
|
75
|
+
join(home, '.roo', 'agents'),
|
|
76
|
+
join(home, '.gemini', 'config', 'agents'),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function printItems(items) {
|
|
81
|
+
if (items.length === 0) {
|
|
82
|
+
console.log('No store items found.');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const { name, item, type } of items) {
|
|
86
|
+
const kind = type === 'skills' ? 'skill' : 'agent';
|
|
87
|
+
console.log(`${kind}\t${name}\t${item.category}\t${item.description}`);
|
|
88
|
+
}
|
|
89
|
+
console.log(`\n${items.length} item(s).`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function download(upstreamPath) {
|
|
93
|
+
const url = `https://raw.githubusercontent.com/affaan-m/ECC/${index.pinned_commit}/${upstreamPath}`;
|
|
94
|
+
const response = await fetch(url, { headers: { 'user-agent': 'aos-store' } });
|
|
95
|
+
if (!response.ok) throw new Error(`Download failed (${response.status}): ${url}`);
|
|
96
|
+
return Buffer.from(await response.arrayBuffer());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function targetsFor(type, name) {
|
|
100
|
+
if (project) {
|
|
101
|
+
return type === 'skills'
|
|
102
|
+
? [join(process.cwd(), 'skills', name, 'SKILL.md')]
|
|
103
|
+
: [join(process.cwd(), 'agents', `${name}.md`)];
|
|
104
|
+
}
|
|
105
|
+
return globalRoots(type).map((root) => type === 'skills'
|
|
106
|
+
? join(root, name, 'SKILL.md')
|
|
107
|
+
: join(root, `${name}.md`));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function install(name) {
|
|
111
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(`Invalid store name: ${name}`);
|
|
112
|
+
const found = findItem(name);
|
|
113
|
+
if (!found) throw new Error(`Store item not found: ${name}`);
|
|
114
|
+
const { item, type } = found;
|
|
115
|
+
const coreNames = type === 'skills' ? localSkillNames() : localAgentNames();
|
|
116
|
+
if (coreNames.has(name)) {
|
|
117
|
+
console.log(`Skipped ${name}: AOS core already provides it.`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const targets = targetsFor(type, name);
|
|
121
|
+
if (dryRun) {
|
|
122
|
+
for (const target of targets) console.log(`[dry-run] write ${target}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!net) {
|
|
126
|
+
throw new Error(`Downloading '${name}' requires internet access to fetch from upstream GitHub. Pass --net to confirm: aos store install ${name} --net`);
|
|
127
|
+
}
|
|
128
|
+
const content = await download(item.upstream_path);
|
|
129
|
+
const digest = createHash('sha256').update(content).digest('hex');
|
|
130
|
+
if (digest !== item.sha256) throw new Error(`SHA-256 mismatch for ${name}: expected ${item.sha256}, got ${digest}`);
|
|
131
|
+
for (const target of targets) {
|
|
132
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
133
|
+
writeFileSync(target, content);
|
|
134
|
+
console.log(`Installed ${name} -> ${target}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function usage() {
|
|
139
|
+
console.log('Usage:');
|
|
140
|
+
console.log(' aos store list [--type=skills|agents]');
|
|
141
|
+
console.log(' aos store search <query>');
|
|
142
|
+
console.log(' aos store install <name> [--project] [--dry-run] [--net]');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function main() {
|
|
146
|
+
loadIndex();
|
|
147
|
+
const command = positional[0] || 'list';
|
|
148
|
+
if (command === 'list') {
|
|
149
|
+
printItems(allItems(option('--type') || 'skills'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (command === 'search') {
|
|
153
|
+
const query = (positional[1] || '').toLowerCase();
|
|
154
|
+
if (!query) throw new Error('Search requires a query.');
|
|
155
|
+
const items = allItems(option('--type') || '').filter(({ name, item }) => `${name} ${item.description} ${item.category}`.toLowerCase().includes(query));
|
|
156
|
+
printItems(items);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (command === 'install') {
|
|
160
|
+
if (!positional[1]) throw new Error('Install requires a store item name.');
|
|
161
|
+
await install(positional[1]);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
usage();
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
main().catch((error) => {
|
|
169
|
+
console.error(`aos store: ${error.message}`);
|
|
170
|
+
process.exitCode = 1;
|
|
171
|
+
});
|