@hybridlabor-api/aos 4.0.0 → 4.0.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.
@@ -0,0 +1,37 @@
1
+ # AOS Header Banner — Regeneration Prompt
2
+
3
+ Reference for regenerating `assets/header-v4.jpg` (or its successor) via
4
+ Nano Banana / Gemini image generation on a future version bump. Update the
5
+ version string in the prompt and re-run rather than re-deriving the brand
6
+ rules from scratch.
7
+
8
+ ## Brand constraints (from `~/dev/assets/bdb-brand/bdbagentOS/00_MASTER_BRANDING_SPEC.md`)
9
+
10
+ - Dominant colors: Obsidian Black (`#0a0a0a` / `#141414`) and Base White (`#FFFFFF`).
11
+ - Accent: Vibrant Orchid / Lila (`#9b30c4`) and Deep Plum (`#51116F`) — sparingly (rings, glow, gradient details), never the dominant fill.
12
+ - Strict prohibitions: no mint/cyan, no rainbow/multi-hue gradients, no heartbeat lines, no heavy glassmorphism/blur, no ambient glow halos.
13
+ - No personal names, studio names, or attribution baked into the image — product-level branding only.
14
+ - Target dimensions: 1376x768 (16:9-ish wide banner, matches GitHub README width).
15
+
16
+ ## Working prompt (used for v4.0.0)
17
+
18
+ > A wide banner image, 1376x768, pure black background (#0a0a0a). On the
19
+ > left: the bold white wordmark "AOS" in a large, heavy sans-serif font,
20
+ > with a smaller white subtitle "BDB AGENT OS" beneath it, and below that
21
+ > a small rounded pill/badge containing the text "CORE KERNEL · AOS · v4.0.0"
22
+ > in white on a dark badge. On the right: an abstract network/graph made of
23
+ > glossy hexagonal nodes (mix of black and white hexagons, beveled/glossy
24
+ > material) connected by thin magenta/lila (#9b30c4) lines, with a subtle
25
+ > lila glow ring around some of the outer nodes. No other colors — strictly
26
+ > black, white, and the lila accent. No text other than what's specified.
27
+ > Clean, professional, high-contrast — not glassy/blurred, not a rainbow
28
+ > gradient.
29
+
30
+ ## Notes for next regeneration
31
+
32
+ - Keep the wordmark + hexagon-network composition (echoes the dispatcher-
33
+ graph architecture) unless the product story changes.
34
+ - Only the version string in the badge needs updating for a routine bump.
35
+ - Re-verify the output against the prohibitions above before committing —
36
+ Nano Banana can drift toward its default glow/gradient aesthetic if the
37
+ constraints aren't repeated explicitly.
Binary file
package/installer.js CHANGED
@@ -44,8 +44,14 @@ const {
44
44
  log
45
45
  } = clack;
46
46
 
47
+ const {
48
+ renderKineticIntro,
49
+ buildHeroHeader,
50
+ buildTelemetryCard
51
+ } = require('./lib/startup-ui.js');
52
+
47
53
  const pkgPath = path.join(__dirname, 'package.json');
48
- let pkg = { name: '@hybridlabor-api/bdb-dev-optimized-agent-skills', version: '3.9.6-beta' };
54
+ let pkg = { name: '@hybridlabor-api/aos', version: '4.0.0' };
49
55
  if (fs.existsSync(pkgPath)) {
50
56
  try { pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch (e) { logDebug(e, 'operation'); }
51
57
  }
@@ -56,12 +62,6 @@ const colors = {
56
62
  cyan: "\x1b[36m",
57
63
  purple: "\x1b[38;2;157;78;221m",
58
64
  purpleBold: "\x1b[1;\x1b[38;2;157;78;221m",
59
- // v3.13 "NODEFORGE" release identity. The wordmark itself is drawn with a
60
- // per-column pink -> yellow -> amethyst gradient computed in buildBanner();
61
- // these named constants cover the surrounding chrome (divider, tagline) and
62
- // the animation effects. Deliberately distinct from the purple/cyan beta
63
- // banner so a push to `latest` is visually obvious, not just a
64
- // version-number diff someone has to notice on their own.
65
65
  amethyst: "\x1b[38;2;155;89;182m",
66
66
  beige: "\x1b[38;2;222;202;168m",
67
67
  bannerWhite: "\x1b[38;2;255;255;255m",
@@ -1256,59 +1256,44 @@ async function installOpenWikiDaemon(apiKey, targetSkillDir, openwikiEnv = {}) {
1256
1256
  });
1257
1257
  }
1258
1258
 
1259
- const GLITCH_CHARS = ['!', '@', '#', '$', '%', '^', '&', '*', '█', '▓', '▒', '░', '▄', '▀', '▌', '▐', '▆', '▇'];
1259
+ // Wordmark banner. Rendered after the kinetic intro finishes, before the
1260
+ // hero header -- the user wanted both kept, not one replacing the other.
1261
+ // Built programmatically rather than stored as an escaped string literal:
1262
+ // the wordmark carries a per-column pink -> yellow -> amethyst gradient,
1263
+ // which needs a truecolor escape emitted per character run.
1264
+ const BANNER_WORDMARK = [
1265
+ "█████▄ ████▄ █████▄ ▄████▄ ▄████ ██████ ███ ██ ██████ ▄████▄ ▄█████ ",
1266
+ "██▄▄██ ██ ██ ██▄▄██ ██▄▄██ ██ ▄▄▄ ██▄▄ ██ ▀▄██ ██ ██ ██ ▀▀▀▄▄▄ ",
1267
+ "██▄▄█▀ ████▀ ██▄▄█▀ ██ ██ ▀███▀ ██▄▄▄▄ ██ ██ ██ ▀████▀ █████▀"
1268
+ ];
1260
1269
 
1261
- async function glitchBanner(bannerStr) {
1262
- if (!process.stdout.isTTY) return;
1270
+ function buildWordmarkBanner() {
1271
+ const pink = [236, 72, 153];
1272
+ const yellow = [255, 208, 66];
1273
+ const amethystRgb = [155, 89, 182];
1274
+ const lerp = (a, b, t) => a.map((v, i) => Math.round(v + (b[i] - v) * t));
1275
+ const gradientAt = (t) => (t < 0.5 ? lerp(pink, yellow, t * 2) : lerp(yellow, amethystRgb, (t - 0.5) * 2));
1263
1276
 
1264
- const lines = bannerStr.split('\n');
1265
- const lineCount = lines.length;
1266
-
1267
- console.log(bannerStr);
1268
-
1269
- try {
1270
- process.stdout.write('\x1B[?25l');
1271
- const duration = 800;
1272
- const fps = 15;
1273
- const frameTime = Math.floor(1000 / fps);
1274
- const frames = Math.floor(duration / frameTime);
1275
-
1276
- for (let i = 0; i < frames; i++) {
1277
- readline.moveCursor(process.stdout, 0, -lineCount);
1278
-
1279
- const glitchedLines = lines.map(line => {
1280
- if (line.trim().length === 0) return line;
1281
-
1282
- let out = '';
1283
- let inEscape = false;
1284
- for (let j = 0; j < line.length; j++) {
1285
- const c = line[j];
1286
- if (c === '\x1B') inEscape = true;
1287
-
1288
- if (inEscape) {
1289
- out += c;
1290
- if (c === 'm') inEscape = false;
1291
- } else {
1292
- if (c !== ' ' && Math.random() < 0.05) {
1293
- const gChar = GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)];
1294
- out += `${colors.magenta}${gChar}${colors.reset}`;
1295
- } else {
1296
- out += c;
1297
- }
1298
- }
1299
- }
1300
- return out;
1301
- });
1302
-
1303
- process.stdout.write(glitchedLines.join('\n') + '\n');
1304
- await new Promise(r => setTimeout(r, frameTime));
1277
+ const width = Math.max(...BANNER_WORDMARK.map((l) => l.length));
1278
+ const wordmark = BANNER_WORDMARK.map((line) => {
1279
+ let out = '';
1280
+ let lastCode = null;
1281
+ for (let i = 0; i < line.length; i++) {
1282
+ const ch = line[i];
1283
+ if (ch === ' ') { out += ch; continue; }
1284
+ const [r, g, b] = gradientAt(i / (width - 1));
1285
+ const code = `\x1b[38;2;${r};${g};${b}m`;
1286
+ if (code !== lastCode) { out += code; lastCode = code; }
1287
+ out += ch;
1305
1288
  }
1306
-
1307
- readline.moveCursor(process.stdout, 0, -lineCount);
1308
- readline.clearScreenDown(process.stdout);
1309
- } finally {
1310
- process.stdout.write('\x1B[?25h');
1311
- }
1289
+ return out + colors.reset;
1290
+ }).join('\n');
1291
+
1292
+ const label = ' N O D E F O R G E ';
1293
+ const fill = width - label.length;
1294
+ const divider = '─'.repeat(Math.floor(fill / 2)) + label + '─'.repeat(fill - Math.floor(fill / 2));
1295
+
1296
+ return `${colors.bold}\n${wordmark}\n\n${colors.beige}${divider}${colors.reset}`;
1312
1297
  }
1313
1298
 
1314
1299
  async function runTopologyAnimation(backgroundTask, label) {
@@ -1495,20 +1480,35 @@ async function installMemB(interactive) {
1495
1480
  ? path.join(membDir, '.venv', 'Scripts', 'python.exe')
1496
1481
  : path.join(membDir, '.venv', 'bin', 'python');
1497
1482
 
1483
+ let createdViaUv = false;
1498
1484
  if (!fs.existsSync(venvPython)) {
1499
1485
  try {
1500
1486
  execSync(`uv venv --seed .venv`, { cwd: membDir, stdio: 'ignore' });
1487
+ createdViaUv = true;
1501
1488
  } catch (e1) {
1502
1489
  execSync(`${pythonCmd} -m venv .venv`, { cwd: membDir, stdio: 'ignore' });
1503
1490
  }
1504
1491
  }
1505
1492
 
1506
- const pipViaPython = `"${venvPython}" -m pip`;
1507
- runPipWithRetry(`${pipViaPython} install --upgrade setuptools --timeout 30 --no-input`, { cwd: membDir, stdio: 'ignore' }, 'pip setuptools for memB standalone', 2, 120000);
1508
- runPipWithRetry(`${pipViaPython} install -r requirements.txt --timeout 30 --no-input`, { cwd: membDir, stdio: 'inherit' }, 'pip install for memB standalone', 2, 900000);
1493
+ // `uv venv --seed` is supposed to seed pip/setuptools/wheel into the venv, but
1494
+ // this has been observed to silently under-deliver (venv created, pip missing) --
1495
+ // when that happens every `-m pip install` call below fails with "No module named
1496
+ // pip". `uv pip install --python <venv>` never needs pip present in the venv at
1497
+ // all, so prefer it whenever uv itself is on PATH, regardless of which tool
1498
+ // actually created the venv -- it's a strictly more reliable install path.
1499
+ let hasUv = createdViaUv;
1500
+ if (!hasUv) {
1501
+ try { execSync('uv --version', { stdio: 'ignore' }); hasUv = true; } catch (e) { hasUv = false; }
1502
+ }
1503
+ const uvInstall = (pkgsArg) => `uv pip install --python "${venvPython}" ${pkgsArg}`;
1504
+ const pipInstall = (pkgsArg) => `"${venvPython}" -m pip install ${pkgsArg} --timeout 30 --no-input`;
1505
+ const installCmd = (pkgsArg) => hasUv ? uvInstall(pkgsArg) : pipInstall(pkgsArg);
1506
+
1507
+ runPipWithRetry(installCmd('--upgrade setuptools'), { cwd: membDir, stdio: 'ignore' }, 'pip setuptools for memB standalone', 2, 120000);
1508
+ runPipWithRetry(installCmd('-r requirements.txt'), { cwd: membDir, stdio: 'inherit' }, 'pip install for memB standalone', 2, 900000);
1509
1509
 
1510
1510
  if (installWebUI && fs.existsSync(serverPy)) {
1511
- runPipWithRetry(`${pipViaPython} install fastapi uvicorn --timeout 30 --no-input`, { cwd: membDir, stdio: 'ignore' }, 'pip fastapi+uvicorn for memB WebUI', 2, 120000);
1511
+ runPipWithRetry(installCmd('fastapi uvicorn'), { cwd: membDir, stdio: 'ignore' }, 'pip fastapi+uvicorn for memB WebUI', 2, 120000);
1512
1512
  }
1513
1513
  } catch (e) {
1514
1514
  log.warn(`Failed memB standalone venv setup: ${e.message}`);
@@ -1624,6 +1624,8 @@ async function installSynapse() {
1624
1624
  <array>
1625
1625
  ${binaryPath.endsWith('.js') ? `<string>/usr/local/bin/node</string>\n <string>${binaryPath}</string>` : `<string>${binaryPath}</string>`}
1626
1626
  <string>serve</string>
1627
+ <string>--port</string>
1628
+ <string>7781</string>
1627
1629
  </array>
1628
1630
  <key>WorkingDirectory</key>
1629
1631
  <string>${synapseDir}</string>
@@ -1657,7 +1659,7 @@ async function installSynapse() {
1657
1659
  const stdoutLog = path.join(synapseLogDir, 'daemon.stdout.log');
1658
1660
  const stderrLog = path.join(synapseLogDir, 'daemon.stderr.log');
1659
1661
  const batPath = path.join(synapseLogDir, 'run-synapse.bat');
1660
- const runCmd = binaryPath.endsWith('.js') ? `node "${binaryPath}" serve` : `"${binaryPath}" serve`;
1662
+ const runCmd = binaryPath.endsWith('.js') ? `node "${binaryPath}" serve --port 7781` : `"${binaryPath}" serve --port 7781`;
1661
1663
  // WshShell.Run has no stdout/stderr redirection of its own, so a crash on launch
1662
1664
  // (e.g. a missing dependency the binary shells out to) used to die silently with
1663
1665
  // nothing to diagnose short of reading source — route through a .bat wrapper that
@@ -1678,7 +1680,7 @@ async function installSynapse() {
1678
1680
  if (isListening) {
1679
1681
  log.success('Synapse 3D Windows Background Service registered & started (Port 7781)');
1680
1682
  } else {
1681
- log.warn(`Synapse 3D Windows daemon did not respond on Port 7781 within timeout. Check ${stderrLog} for the actual error (e.g. a missing 'go' on PATH) before assuming it's just slow to start.`);
1683
+ log.warn(`Synapse 3D Windows daemon did not respond on Port 7781 within timeout. Check ${stderrLog} for the actual error before assuming it's just slow to start.`);
1682
1684
  }
1683
1685
  } catch (e) { logDebug(e, 'windows synapse daemon setup'); }
1684
1686
  }
@@ -1904,7 +1906,7 @@ function verifyEcosystemInstallation() {
1904
1906
  { name: '4. bdb-dev-creator-extension', pkg: '@hybridlabor-api/bdb-dev-creator-extension', paths: [path.join(moduleBasePath(), 'bdb-dev-creator-extension')] },
1905
1907
  { name: '5. bdb-os-remote', pkg: '@hybridlabor-api/bdb-os-remote', paths: [path.join(moduleBasePath(), 'bdb-os-remote')] },
1906
1908
  { name: '6. bdb-dev-tool-installer', pkg: '@hybridlabor-api/bdb-dev-tool-installer', paths: [path.join(moduleBasePath(), 'bdb-dev-tool-installer')] },
1907
- { name: '7. bdb-dev-optimized-agent-skills', pkg: '@hybridlabor-api/bdb-dev-optimized-agent-skills', paths: [srcDir] }
1909
+ { name: '7. aos (bdb agent os)', pkg: '@hybridlabor-api/aos', paths: [srcDir] }
1908
1910
  ];
1909
1911
 
1910
1912
  for (const mod of modules) {
@@ -3268,6 +3270,20 @@ async function runQuickUpdate(installState) {
3268
3270
  syncSkillsToGlobalHarnesses(excludeSkills);
3269
3271
  s.stop('Skills refreshed');
3270
3272
 
3273
+ // OpenWiki setup only ever ran on a brand-new install (main()'s fresh-install
3274
+ // branch below) -- an existing install running Quick Update never got offered
3275
+ // it and never had its daemon schedule refreshed. promptCredentials() already
3276
+ // detects an existing key and collapses to a single "keep existing" prompt in
3277
+ // that case, so this is a no-op confirm for anyone already configured and a
3278
+ // real one-time offer for anyone who isn't (including installs from before
3279
+ // this feature existed).
3280
+ if (!isAutoYes) {
3281
+ const creds = await promptCredentials(paths.targetMcpDir);
3282
+ if (creds !== BACK) {
3283
+ await installOpenWikiDaemon(creds.gemini, paths.targetSkillDir, { provider: creds.openwikiProvider, model: creds.openwikiModel, baseUrl: creds.openwikiBaseUrl });
3284
+ }
3285
+ }
3286
+
3271
3287
  const modulesToUpdate = installState.installedModules || [];
3272
3288
  for (const subId of modulesToUpdate) {
3273
3289
  if (subId === 'synapse') await installSynapse();
@@ -3294,61 +3310,24 @@ async function runQuickUpdate(installState) {
3294
3310
  }
3295
3311
 
3296
3312
 
3297
- // v3.13 "NODEFORGE" release banner. Built programmatically rather than stored
3298
- // as one giant escaped string literal: the wordmark carries a per-column
3299
- // pink -> yellow -> amethyst gradient, which needs a truecolor escape emitted
3300
- // per character run. Hand-maintaining that as literal text would be
3301
- // unreadable and near-impossible to edit safely.
3302
- //
3303
- // Width discipline: every rendered line stays <= 76 visible columns (the
3304
- // wordmark's natural width), so the banner never wraps on an 80-column
3305
- // terminal -- the same class of terminal-width bug this release already fixed
3306
- // elsewhere. Changing the font means re-checking that number.
3307
- const BANNER_WORDMARK = [
3308
- "█████▄ ████▄ █████▄ ▄████▄ ▄████ ██████ ███ ██ ██████ ▄████▄ ▄█████ ",
3309
- "██▄▄██ ██ ██ ██▄▄██ ██▄▄██ ██ ▄▄▄ ██▄▄ ██ ▀▄██ ██ ██ ██ ▀▀▀▄▄▄ ",
3310
- "██▄▄█▀ ████▀ ██▄▄█▀ ██ ██ ▀███▀ ██▄▄▄▄ ██ ██ ██ ▀████▀ █████▀"
3311
- ];
3312
-
3313
- function buildBanner() {
3314
- const pink = [236, 72, 153];
3315
- const yellow = [255, 208, 66];
3316
- const amethystRgb = [155, 89, 182];
3317
- const lerp = (a, b, t) => a.map((v, i) => Math.round(v + (b[i] - v) * t));
3318
- const gradientAt = (t) => (t < 0.5 ? lerp(pink, yellow, t * 2) : lerp(yellow, amethystRgb, (t - 0.5) * 2));
3319
-
3320
- const width = Math.max(...BANNER_WORDMARK.map((l) => l.length));
3321
- const wordmark = BANNER_WORDMARK.map((line) => {
3322
- let out = '';
3323
- let lastCode = null;
3324
- for (let i = 0; i < line.length; i++) {
3325
- const ch = line[i];
3326
- if (ch === ' ') { out += ch; continue; }
3327
- const [r, g, b] = gradientAt(i / (width - 1));
3328
- const code = `\x1b[38;2;${r};${g};${b}m`;
3329
- if (code !== lastCode) { out += code; lastCode = code; }
3330
- out += ch;
3331
- }
3332
- return out + colors.reset;
3333
- }).join('\n');
3334
-
3335
- const label = ' N O D E F O R G E ';
3336
- const fill = width - label.length;
3337
- const divider = '─'.repeat(Math.floor(fill / 2)) + label + '─'.repeat(fill - Math.floor(fill / 2));
3338
-
3339
- const tagline = 'BDB AGENT OS · CORE KERNEL · AOS - v4.0.0';
3340
- const taglinePad = ' '.repeat(Math.max(0, Math.floor((width - tagline.length) / 2)));
3313
+ async function main() {
3314
+ const skipIntro = isAutoYes || process.argv.includes('--no-intro') || process.argv.includes('--no-animation');
3315
+ await renderKineticIntro({ rotations: 1, fps: 12.5, skip: skipIntro });
3316
+ console.log(buildWordmarkBanner());
3341
3317
 
3342
- return `${colors.bold}\n${wordmark}\n\n`
3343
- + `${colors.beige}${divider}${colors.reset}\n\n`
3344
- + `${colors.bannerWhite}${colors.bold}${taglinePad}${tagline}${colors.reset}`;
3345
- }
3318
+ const installState = detectInstallState();
3319
+ const detections = detectPlatforms();
3346
3320
 
3347
- async function main() {
3348
- const banner = buildBanner();
3321
+ // Fast local daemon checks (150ms budget)
3322
+ const aoOnline = await verifyDaemonListening(3101, 'AO Daemon', 150);
3323
+ const remoteOsOnline = await verifyDaemonListening(9080, 'RemoteOS', 150);
3324
+ const daemonStatus = [
3325
+ { name: 'AO Orchestrator', port: 3101, online: aoOnline },
3326
+ { name: 'RemoteOS Gateway', port: 9080, online: remoteOsOnline }
3327
+ ];
3349
3328
 
3350
- await glitchBanner(banner);
3351
- intro(banner);
3329
+ intro(buildHeroHeader(pkg.version));
3330
+ console.log(buildTelemetryCard({ installState, detections, daemonStatus }));
3352
3331
 
3353
3332
  if (DRY_RUN) {
3354
3333
  log.warn('DRY-RUN MODE active - no files will be modified, no commands executed.');
@@ -3362,8 +3341,6 @@ async function main() {
3362
3341
  log.warn(`Update available: v${pkg.version} ➔ v${latest} — run: npx ${pkg.name}@latest`);
3363
3342
  }
3364
3343
 
3365
- const installState = detectInstallState();
3366
-
3367
3344
  // Initialize session manifest – all copyDirRecursiveSync calls from here
3368
3345
  // forward will use manifest-aware writes automatically.
3369
3346
  initSessionManifest(installState.installManifest, [
@@ -3387,28 +3364,22 @@ async function main() {
3387
3364
  }
3388
3365
 
3389
3366
  if (installState.isInstalled && !isAutoYes) {
3390
- if (installState.updateAvailable) {
3391
- log.warn(`BDB AGENT OS installation detected: v${installState.localVersion} ➔ v${installState.currentVersion}`);
3392
- } else {
3393
- log.success(`BDB AGENT OS is already up to date (v${installState.currentVersion}).`);
3394
- }
3395
-
3396
3367
  const options = installState.updateAvailable
3397
3368
  ? [
3398
- { value: 'quick', label: `⚡ Quick Update (v${installState.localVersion} ➔ v${installState.currentVersion}) refresh skills & daemons, keep settings` },
3399
- { value: 'project', label: '📁 Drop Local Project Harness (in current directory)' },
3400
- { value: 'reconfigure', label: '🛠️ Full re-installation / re-configuration' },
3369
+ { value: 'quick', label: `⚡ Quick Update (v${installState.localVersion} ➔ v${installState.currentVersion})`, hint: 'refresh skills, templates & daemons' },
3370
+ { value: 'project', label: '📁 Drop Local Project Harness', hint: 'copy dispatcher contract to cwd' },
3371
+ { value: 'reconfigure', label: '🛠️ Reconfigure System', hint: 'change targets, tier or options' },
3401
3372
  { value: 'cancel', label: '❌ Exit' }
3402
3373
  ]
3403
3374
  : [
3404
- { value: 'project', label: '📁 Drop Local Project Harness (in current directory)' },
3405
- { value: 'reconfigure', label: '🛠️ Re-configuration / switch tier (Pro vs Basic)' },
3406
- { value: 'quick', label: '🔄 Repair / force reinstall all skills' },
3375
+ { value: 'project', label: '📁 Drop Local Project Harness', hint: 'copy dispatcher contract to cwd' },
3376
+ { value: 'quick', label: '🔄 Verify & Refresh All Skills', hint: 're-sync and health check' },
3377
+ { value: 'reconfigure', label: '🛠️ Reconfigure System', hint: 'switch tier or targets' },
3407
3378
  { value: 'cancel', label: '❌ Exit' }
3408
3379
  ];
3409
3380
 
3410
3381
  const action = pick(await select({
3411
- message: 'BDB OS maintenance:',
3382
+ message: 'Select an operation:',
3412
3383
  options,
3413
3384
  initialValue: options[0].value
3414
3385
  }));
@@ -3429,26 +3400,18 @@ async function main() {
3429
3400
  }
3430
3401
  }
3431
3402
 
3432
- const detections = detectPlatforms();
3433
- if (detections.length > 0) {
3434
- log.info('Detected Agent Environments on this system:');
3435
- detections.forEach(d => log.message(`${d.name} (${d.path})`));
3436
- } else {
3437
- log.message('No active agent config directories auto-detected in standard locations.');
3438
- }
3439
-
3440
3403
  const detectedNames = detections.map(d => d.name).join(', ');
3441
3404
  const platformOptions = [
3442
- { value: '9', label: 'Local Project Harness', hint: 'copy dispatcher contract to current project' },
3443
3405
  { value: '0', label: '🌐 Universal Agent Harness', hint: detections.length > 0 ? `sync ALL detected: ${detectedNames}` : 'sync across ALL AI platforms' },
3444
3406
  { value: '1', label: 'Google Antigravity', hint: '~/.gemini/config/skills' },
3445
3407
  { value: '2', label: 'Claude Desktop / Claude Code', hint: '~/.claude/skills' },
3446
3408
  { value: '3', label: 'Cursor / Generic IDE (project-local)', hint: '.cursor/' },
3447
- { value: '4', label: 'Custom Installation (specify paths manually)' },
3448
3409
  { value: '5', label: 'ChatGPT Codex CLI', hint: '~/.codex/skills' },
3449
3410
  { value: '6', label: 'Windsurf IDE', hint: '~/.windsurf' },
3450
3411
  { value: '7', label: 'Roo Code / Cline / VS Code', hint: '~/.roo' },
3451
- { value: '8', label: 'Aider CLI', hint: '~/.aider' }
3412
+ { value: '8', label: 'Aider CLI', hint: '~/.aider' },
3413
+ { value: '9', label: '📁 Local Project Harness', hint: 'copy dispatcher contract to current project' },
3414
+ { value: '4', label: '⚙️ Custom Installation', hint: 'specify paths manually' }
3452
3415
  ];
3453
3416
 
3454
3417
  let tier = '1';
@@ -0,0 +1,137 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const framesData = require('../assets/bdb-ascii-frames.js');
5
+
6
+ // BDB Master Branding Design Tokens (00_MASTER_BRANDING_SPEC.md)
7
+ const BRAND = {
8
+ lila: "\x1b[38;2;155;48;196m", // #9b30c4 Vibrant Orchid
9
+ lilaDark: "\x1b[38;2;81;17;111m", // #51116F Deep Plum
10
+ white: "\x1b[38;2;255;255;255m", // #FFFFFF Base White
11
+ dim: "\x1b[38;2;115;115;125m", // #73737D Muted / Telemetry Dim
12
+ emerald: "\x1b[38;2;16;185;129m", // Status Success
13
+ amber: "\x1b[38;2;245;158;11m", // Status Warning
14
+ reset: "\x1b[0m",
15
+ bold: "\x1b[1m"
16
+ };
17
+
18
+ /**
19
+ * Plays the 3D rotating BDB medallion kinetic intro in terminal.
20
+ * Automatically clears frame buffer upon completion for smooth handoff to Clack.
21
+ */
22
+ async function renderKineticIntro({ rotations = 1, fps = 12.5, skip = false } = {}) {
23
+ if (skip || !process.stdout.isTTY) return;
24
+
25
+ const frames = framesData.ansi;
26
+ const tickMs = Math.round(1000 / fps);
27
+ const totalFrames = Math.round(rotations * frames.length);
28
+
29
+ // Clear terminal screen and hide cursor for a clean viewport
30
+ process.stdout.write('\x1b]0;BDB Agent OS · AOS Installer v4.0.0\x07');
31
+ process.stdout.write('\x1b[2J\x1b[H\x1b[?25l');
32
+
33
+ return new Promise((resolve) => {
34
+ let currentFrame = 0;
35
+
36
+ const timer = setInterval(() => {
37
+ const frame = frames[currentFrame % frames.length];
38
+ const lines = frame.split('\n');
39
+
40
+ const buffer = [
41
+ '\x1b[H',
42
+ ...lines.map(line => '\x1b[2K' + line),
43
+ '\x1b[2K',
44
+ `\x1b[2K ${BRAND.lila}${BRAND.bold}━━━ BDB AGENT OS ━━━${BRAND.reset} ${BRAND.white}${BRAND.bold}AOS Kernel v4.0.0${BRAND.reset} ${BRAND.dim}· Initializing runtime...${BRAND.reset}`
45
+ ].join('\n');
46
+
47
+ process.stdout.write(buffer);
48
+ currentFrame++;
49
+
50
+ if (currentFrame >= totalFrames) {
51
+ clearInterval(timer);
52
+
53
+ // Cleanly clear the animation lines so Clack has a fresh, tidy viewport
54
+ process.stdout.write('\x1b[2J\x1b[H\x1b[?25h\x1b[0m');
55
+ resolve();
56
+ }
57
+ }, tickMs);
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Builds the executive, studio-grade hero header for Clack intro.
63
+ */
64
+ function buildHeroHeader(pkgVersion = '4.0.0') {
65
+ const title = `${BRAND.lila}${BRAND.bold}BDB AGENT OS${BRAND.reset} ${BRAND.dim}·${BRAND.reset} ${BRAND.white}${BRAND.bold}CORE KERNEL${BRAND.reset} ${BRAND.dim}·${BRAND.reset} ${BRAND.lila}${BRAND.bold}AOS${BRAND.reset} ${BRAND.dim}- v${pkgVersion}${BRAND.reset}`;
66
+ const subtitle = `${BRAND.dim}Multi-Agent Skill & Power-Up Ecosystem · Claude, Antigravity, Codex, Cursor & more${BRAND.reset}`;
67
+ const sysInfo = `${BRAND.dim}Host: ${os.type()} ${os.arch()} · Node ${process.version} · PID ${process.pid}${BRAND.reset}`;
68
+
69
+ return `${title}\n│ ${subtitle}\n│ ${sysInfo}`;
70
+ }
71
+
72
+ /**
73
+ * Formats a clean pre-flight telemetry card to display before user choices.
74
+ */
75
+ function buildTelemetryCard({ installState, detections, daemonStatus = [] }) {
76
+ const lines = [];
77
+ const G = BRAND.dim + '│' + BRAND.reset;
78
+ lines.push(`${G}`);
79
+ lines.push(`${G} ${BRAND.bold}${BRAND.white}System Pre-Flight Telemetry${BRAND.reset}`);
80
+
81
+ // 1. Agent Platforms
82
+ const SHORT_NAMES = {
83
+ 'Google Antigravity': 'Antigravity',
84
+ 'ChatGPT Codex CLI': 'Codex CLI',
85
+ 'Claude Code CLI': 'Claude Code',
86
+ 'Roo Code / Cline / VS Code': 'Roo / Cline',
87
+ 'Aider CLI': 'Aider',
88
+ 'OpenCode CLI': 'OpenCode',
89
+ 'Cursor / Generic IDE': 'Cursor',
90
+ 'Windsurf IDE': 'Windsurf'
91
+ };
92
+
93
+ if (detections && detections.length > 0) {
94
+ const platformBadges = detections.map(d => `${BRAND.emerald}✔${BRAND.reset} ${BRAND.white}${SHORT_NAMES[d.name] || d.name}${BRAND.reset}`);
95
+ const maxPerRow = 4;
96
+ const rows = [];
97
+ for (let i = 0; i < platformBadges.length; i += maxPerRow) {
98
+ rows.push(platformBadges.slice(i, i + maxPerRow).join(' '));
99
+ }
100
+ lines.push(`${G} ${BRAND.lila}├─${BRAND.reset} ${BRAND.dim}Agent Platforms:${BRAND.reset} ${rows[0]}`);
101
+ for (let i = 1; i < rows.length; i++) {
102
+ lines.push(`${G} ${rows[i]}`);
103
+ }
104
+ } else {
105
+ lines.push(`${G} ${BRAND.lila}├─${BRAND.reset} ${BRAND.dim}Agent Platforms:${BRAND.reset} ${BRAND.dim}None auto-detected (universal harness mode available)${BRAND.reset}`);
106
+ }
107
+
108
+ // 2. Local Fleet Daemons
109
+ if (daemonStatus && daemonStatus.length > 0) {
110
+ const daemonBadges = daemonStatus
111
+ .map(d => `${d.online ? BRAND.emerald + '✔' : BRAND.dim + '○'} ${BRAND.white}${d.name}${BRAND.reset} ${BRAND.dim}(:${d.port})${BRAND.reset}`)
112
+ .join(' ');
113
+ lines.push(`${G} ${BRAND.lila}├─${BRAND.reset} ${BRAND.dim}Fleet Daemons:${BRAND.reset} ${daemonBadges}`);
114
+ }
115
+
116
+ // 3. Installation State
117
+ if (installState) {
118
+ if (installState.isInstalled) {
119
+ const versionStatus = installState.updateAvailable
120
+ ? `${BRAND.amber}v${installState.localVersion}${BRAND.reset} ➔ ${BRAND.emerald}${BRAND.bold}v${installState.currentVersion}${BRAND.reset} ${BRAND.amber}(Update Available)${BRAND.reset}`
121
+ : `${BRAND.emerald}✔${BRAND.reset} ${BRAND.white}v${installState.currentVersion}${BRAND.reset} ${BRAND.dim}(Current & Up-to-date)${BRAND.reset}`;
122
+ lines.push(`${G} ${BRAND.lila}└─${BRAND.reset} ${BRAND.dim}Kernel State:${BRAND.reset} ${versionStatus}`);
123
+ } else {
124
+ lines.push(`${G} ${BRAND.lila}└─${BRAND.reset} ${BRAND.dim}Kernel State:${BRAND.reset} ${BRAND.lila}Fresh Installation${BRAND.reset} ${BRAND.dim}(v${installState.currentVersion || '4.0.0'})${BRAND.reset}`);
125
+ }
126
+ }
127
+ lines.push(`${G}`);
128
+
129
+ return lines.join('\n');
130
+ }
131
+
132
+ module.exports = {
133
+ BRAND,
134
+ renderKineticIntro,
135
+ buildHeroHeader,
136
+ buildTelemetryCard
137
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridlabor-api/aos",
3
- "version": "4.0.0",
3
+ "version": "4.0.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
  "bin": {
@@ -24,6 +24,7 @@
24
24
  "files": [
25
25
  "installer.js",
26
26
  "bin/",
27
+ "lib/",
27
28
  "mcp_config.json",
28
29
  "GEMINI.md",
29
30
  "CODEX.md",