@hybridlabor-api/aos 4.7.2 โ†’ 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/.agents/agents.md +1 -1
  2. package/.agents/nodes.json +1 -1
  3. package/.agents/vendor-manifest.json +23 -1
  4. package/.claude/agents/godmode-media-eventtech.md +1 -1
  5. package/.claude/hooks/conventional-commits.mjs +125 -0
  6. package/.claude/hooks/env-file-protection.mjs +105 -0
  7. package/.claude/hooks/go-gate.mjs +101 -81
  8. package/.claude/settings.json +13 -0
  9. package/.opencode/agents/godmode-media-eventtech.md +1 -1
  10. package/.opencode/plugins/bdb-aos.js +31 -4
  11. package/README.de.md +1 -1
  12. package/README.md +1 -1
  13. package/README.pt.md +1 -1
  14. package/THIRD_PARTY_NOTICES.md +88 -0
  15. package/docs/skills_table.md +1 -1
  16. package/installer.js +309 -101
  17. package/package.json +6 -2
  18. package/packages/aos-cli/README.md +80 -0
  19. package/packages/aos-cli/bin/aos-cli.mjs +134 -0
  20. package/packages/aos-cli/core-skills.json +12 -0
  21. package/packages/aos-cli/extensions/aos.ts +321 -0
  22. package/packages/aos-cli/package-lock.json +1923 -0
  23. package/packages/aos-cli/package.json +29 -0
  24. package/packages/aos-cli/scripts/check-theme.mjs +63 -0
  25. package/packages/aos-cli/themes/aos.json +97 -0
  26. package/scripts/build-plugin-manifest.mjs +131 -0
  27. package/scripts/validate-skills.mjs +81 -6
  28. package/skills/basic/ao-orchestrator/SKILL.md +116 -0
  29. package/skills/global_config/agenttrail/SKILL.md +6 -1
  30. package/skills/global_config/ask-tim/SKILL.md +4 -4
  31. package/skills/global_config/bash-script-generator/SKILL.md +201 -0
  32. package/skills/global_config/bash-script-generator/assets/templates/standard-template.sh +96 -0
  33. package/skills/global_config/bash-script-generator/docs/bash-scripting-guide.md +729 -0
  34. package/skills/global_config/bash-script-generator/docs/generation-best-practices.md +193 -0
  35. package/skills/global_config/bash-script-generator/docs/script-patterns.md +566 -0
  36. package/skills/global_config/bash-script-generator/docs/text-processing-guide.md +437 -0
  37. package/skills/global_config/bash-script-generator/examples/log-analyzer.sh +92 -0
  38. package/skills/global_config/bash-script-generator/scripts/generate_script_template.sh +123 -0
  39. package/skills/global_config/bash-script-generator/scripts/run_ci_checks.sh +172 -0
  40. package/skills/global_config/bash-script-generator/scripts/test_generator.sh +413 -0
  41. package/skills/global_config/bash-script-validator/SKILL.md +249 -0
  42. package/skills/global_config/bash-script-validator/docs/awk-reference.md +449 -0
  43. package/skills/global_config/bash-script-validator/docs/bash-reference.md +468 -0
  44. package/skills/global_config/bash-script-validator/docs/common-mistakes.md +623 -0
  45. package/skills/global_config/bash-script-validator/docs/grep-reference.md +395 -0
  46. package/skills/global_config/bash-script-validator/docs/regex-reference.md +391 -0
  47. package/skills/global_config/bash-script-validator/docs/sed-reference.md +454 -0
  48. package/skills/global_config/bash-script-validator/docs/shell-reference.md +463 -0
  49. package/skills/global_config/bash-script-validator/docs/shellcheck-reference.md +399 -0
  50. package/skills/global_config/bash-script-validator/examples/bad-bash.sh +55 -0
  51. package/skills/global_config/bash-script-validator/examples/bad-shell.sh +54 -0
  52. package/skills/global_config/bash-script-validator/examples/good-bash.sh +71 -0
  53. package/skills/global_config/bash-script-validator/examples/good-shell.sh +69 -0
  54. package/skills/global_config/bash-script-validator/scripts/run_ci_checks.sh +23 -0
  55. package/skills/global_config/bash-script-validator/scripts/shellcheck_wrapper.sh +174 -0
  56. package/skills/global_config/bash-script-validator/scripts/test_validate.sh +446 -0
  57. package/skills/global_config/bash-script-validator/scripts/validate.sh +512 -0
  58. package/skills/global_config/ci-pipeline/SKILL.md +135 -0
  59. package/skills/global_config/dispatching-parallel-agents/SKILL.md +170 -0
  60. package/skills/global_config/dockerfile-generator/SKILL.md +1038 -0
  61. package/skills/global_config/dockerfile-generator/examples/example.dockerignore +95 -0
  62. package/skills/global_config/dockerfile-generator/examples/golang-distroless.Dockerfile +34 -0
  63. package/skills/global_config/dockerfile-generator/examples/java-springboot.Dockerfile +45 -0
  64. package/skills/global_config/dockerfile-generator/examples/nextjs-production.Dockerfile +49 -0
  65. package/skills/global_config/dockerfile-generator/examples/nodejs-multistage.Dockerfile +55 -0
  66. package/skills/global_config/dockerfile-generator/examples/python-fastapi.Dockerfile +48 -0
  67. package/skills/global_config/dockerfile-generator/references/language_specific_guides.md +510 -0
  68. package/skills/global_config/dockerfile-generator/references/multistage_builds.md +570 -0
  69. package/skills/global_config/dockerfile-generator/references/optimization_patterns.md +492 -0
  70. package/skills/global_config/dockerfile-generator/references/security_best_practices.md +375 -0
  71. package/skills/global_config/dockerfile-generator/scripts/generate_dockerignore.sh +199 -0
  72. package/skills/global_config/dockerfile-generator/scripts/generate_golang.sh +172 -0
  73. package/skills/global_config/dockerfile-generator/scripts/generate_java.sh +185 -0
  74. package/skills/global_config/dockerfile-generator/scripts/generate_nodejs.sh +279 -0
  75. package/skills/global_config/dockerfile-generator/scripts/generate_python.sh +218 -0
  76. package/skills/global_config/dockerfile-generator/scripts/test_generator.sh +210 -0
  77. package/skills/global_config/dockerfile-validator/SKILL.md +300 -0
  78. package/skills/global_config/dockerfile-validator/examples/.dockerignore.example +34 -0
  79. package/skills/global_config/dockerfile-validator/examples/bad-example.Dockerfile +37 -0
  80. package/skills/global_config/dockerfile-validator/examples/golang-distroless.Dockerfile +50 -0
  81. package/skills/global_config/dockerfile-validator/examples/good-example.Dockerfile +52 -0
  82. package/skills/global_config/dockerfile-validator/examples/python-optimized.Dockerfile +53 -0
  83. package/skills/global_config/dockerfile-validator/examples/security-issues.Dockerfile +42 -0
  84. package/skills/global_config/dockerfile-validator/references/docker_best_practices.md +348 -0
  85. package/skills/global_config/dockerfile-validator/references/optimization_guide.md +473 -0
  86. package/skills/global_config/dockerfile-validator/references/security_checklist.md +208 -0
  87. package/skills/global_config/dockerfile-validator/scripts/dockerfile-validate.sh +699 -0
  88. package/skills/global_config/dockerfile-validator/scripts/test_validate.sh +35 -0
  89. package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn-lock-read.Dockerfile +6 -0
  90. package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn.Dockerfile +6 -0
  91. package/skills/global_config/dockerfile-validator/tests/fixtures/from-platform-nonroot.Dockerfile +7 -0
  92. package/skills/global_config/dockerfile-validator/tests/test_regression.sh +164 -0
  93. package/skills/global_config/finishing-a-development-branch/SKILL.md +228 -0
  94. package/skills/global_config/github-actions-generator/SKILL.md +353 -0
  95. package/skills/global_config/github-actions-generator/assets/templates/action/composite/action.yml +82 -0
  96. package/skills/global_config/github-actions-generator/assets/templates/action/docker/Dockerfile +25 -0
  97. package/skills/global_config/github-actions-generator/assets/templates/action/docker/action.yml +42 -0
  98. package/skills/global_config/github-actions-generator/assets/templates/action/docker/entrypoint.sh +27 -0
  99. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/action.yml +33 -0
  100. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/index.js +50 -0
  101. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/package.json +27 -0
  102. package/skills/global_config/github-actions-generator/assets/templates/workflow/basic_workflow.yml +242 -0
  103. package/skills/global_config/github-actions-generator/assets/templates/workflow/reusable_workflow.yml +106 -0
  104. package/skills/global_config/github-actions-generator/examples/README.md +147 -0
  105. package/skills/global_config/github-actions-generator/examples/actions/setup-node-cached/action.yml +93 -0
  106. package/skills/global_config/github-actions-generator/examples/caching/docker-buildkit.yml +256 -0
  107. package/skills/global_config/github-actions-generator/examples/security/dependency-review.yml +62 -0
  108. package/skills/global_config/github-actions-generator/examples/security/sbom-attestation.yml +119 -0
  109. package/skills/global_config/github-actions-generator/examples/triggers/chatops-commands.yml +475 -0
  110. package/skills/global_config/github-actions-generator/examples/triggers/repository-dispatch.yml +418 -0
  111. package/skills/global_config/github-actions-generator/examples/triggers/workflow-orchestration.yml +404 -0
  112. package/skills/global_config/github-actions-generator/examples/workflows/docker-build-push.yml +68 -0
  113. package/skills/global_config/github-actions-generator/examples/workflows/go-ci.yml +161 -0
  114. package/skills/global_config/github-actions-generator/examples/workflows/monorepo-ci.yml +340 -0
  115. package/skills/global_config/github-actions-generator/examples/workflows/multi-environment-deploy.yml +406 -0
  116. package/skills/global_config/github-actions-generator/examples/workflows/nodejs-ci.yml +122 -0
  117. package/skills/global_config/github-actions-generator/examples/workflows/python-ci.yml +157 -0
  118. package/skills/global_config/github-actions-generator/examples/workflows/scheduled-tasks.yml +376 -0
  119. package/skills/global_config/github-actions-generator/references/advanced-triggers.md +917 -0
  120. package/skills/global_config/github-actions-generator/references/best-practices.md +755 -0
  121. package/skills/global_config/github-actions-generator/references/common-actions.md +715 -0
  122. package/skills/global_config/github-actions-generator/references/custom-actions.md +320 -0
  123. package/skills/global_config/github-actions-generator/references/expressions-and-contexts.md +688 -0
  124. package/skills/global_config/github-actions-generator/references/modern-features.md +421 -0
  125. package/skills/global_config/github-actions-generator/scripts/test_generator.sh +344 -0
  126. package/skills/global_config/github-actions-templates/SKILL.md +7 -0
  127. package/skills/global_config/github-actions-validator/SKILL.md +576 -0
  128. package/skills/global_config/github-actions-validator/examples/README.md +88 -0
  129. package/skills/global_config/github-actions-validator/examples/outdated-versions.yml +76 -0
  130. package/skills/global_config/github-actions-validator/examples/valid-ci.yml +79 -0
  131. package/skills/global_config/github-actions-validator/examples/with-errors.yml +47 -0
  132. package/skills/global_config/github-actions-validator/references/act_usage.md +233 -0
  133. package/skills/global_config/github-actions-validator/references/action_versions.md +122 -0
  134. package/skills/global_config/github-actions-validator/references/actionlint_usage.md +343 -0
  135. package/skills/global_config/github-actions-validator/references/common_errors.md +512 -0
  136. package/skills/global_config/github-actions-validator/references/modern_features.md +384 -0
  137. package/skills/global_config/github-actions-validator/references/runners.md +317 -0
  138. package/skills/global_config/github-actions-validator/scripts/install_tools.sh +113 -0
  139. package/skills/global_config/github-actions-validator/scripts/validate_workflow.sh +910 -0
  140. package/skills/global_config/github-actions-validator/tests/test_validate_workflow.sh +237 -0
  141. package/skills/global_config/makefile-generator/SKILL.md +614 -0
  142. package/skills/global_config/makefile-generator/assets/templates/.gitkeep +1 -0
  143. package/skills/global_config/makefile-generator/docs/makefile-structure.md +530 -0
  144. package/skills/global_config/makefile-generator/docs/optimization-guide.md +784 -0
  145. package/skills/global_config/makefile-generator/docs/patterns-guide.md +642 -0
  146. package/skills/global_config/makefile-generator/docs/security-guide.md +361 -0
  147. package/skills/global_config/makefile-generator/docs/targets-guide.md +642 -0
  148. package/skills/global_config/makefile-generator/docs/variables-guide.md +596 -0
  149. package/skills/global_config/makefile-generator/scripts/add_standard_targets.sh +539 -0
  150. package/skills/global_config/makefile-generator/scripts/generate_makefile_template.sh +690 -0
  151. package/skills/global_config/makefile-generator/test/test_helper_scripts.sh +190 -0
  152. package/skills/global_config/makefile-validator/SKILL.md +244 -0
  153. package/skills/global_config/makefile-validator/docs/bake-tool.md +1000 -0
  154. package/skills/global_config/makefile-validator/docs/best-practices.md +858 -0
  155. package/skills/global_config/makefile-validator/docs/common-mistakes.md +944 -0
  156. package/skills/global_config/makefile-validator/examples/bad-makefile.mk +77 -0
  157. package/skills/global_config/makefile-validator/examples/good-makefile.mk +103 -0
  158. package/skills/global_config/makefile-validator/scripts/test_validate.sh +382 -0
  159. package/skills/global_config/makefile-validator/scripts/validate_makefile.sh +712 -0
  160. package/skills/global_config/{MCP_Manage โ†’ mcp-manage}/SKILL.md +3 -3
  161. package/skills/global_config/requesting-code-review/SKILL.md +98 -0
  162. package/skills/global_config/requesting-code-review/code-reviewer.md +198 -0
  163. package/skills/global_config/using-git-worktrees/SKILL.md +170 -0
  164. package/skills/global_config/verification-before-completion/SKILL.md +123 -0
  165. package/skills/global_config/writing-plans/SKILL.md +126 -46
  166. package/skills/global_config/writing-plans-legacy/SKILL.md +139 -0
package/installer.js CHANGED
@@ -115,8 +115,8 @@ if (mcpsArgRaw && mcpsArg === null) {
115
115
  // not express that in a script or CI, and got Antigravity as their primary
116
116
  // target instead. Values match the menu: 0 universal, 1 Antigravity,
117
117
  // 2 Claude Desktop/Code, 3 Cursor, 4 custom, 5 Codex, 6 Windsurf, 7 Roo/Cline,
118
- // 8 Aider (9 = project harness has its own --project-harness flag).
119
- const VALID_PLATFORMS = ['0', '1', '2', '3', '4', '5', '6', '7', '8'];
118
+ // 8 Aider, 10 AOS CLI (9 = project harness has its own --project-harness flag).
119
+ const VALID_PLATFORMS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '10'];
120
120
  const platformsArgRaw = process.argv.find(a => a === '--platforms' || a.startsWith('--platforms='));
121
121
  let PLATFORMS_ARG = null;
122
122
  if (platformsArgRaw) {
@@ -1257,6 +1257,7 @@ function syncSkillsToGlobalHarnesses(excludeSkills = []) {
1257
1257
  { dir: path.join(homeDir, '.codex', 'skills'), key: 'codex' },
1258
1258
  { dir: path.join(homeDir, '.cursor', 'skills'), key: 'cursor' },
1259
1259
  { dir: path.join(homeDir, '.roo', 'skills'), key: 'vscode' },
1260
+ { dir: process.platform === 'win32' ? path.join(process.env.APPDATA || homeDir, 'opencode', 'skills') : path.join(homeDir, '.config', 'opencode', 'skills'), key: 'opencode' },
1260
1261
  ].filter((d) => d.key === null || detectedKeys.has(d.key));
1261
1262
 
1262
1263
  for (const { dir: dest } of extraSkillDestinations) {
@@ -1709,7 +1710,12 @@ function checkModuleUpdate(pkgName, targetDir) {
1709
1710
  let remoteVer = null;
1710
1711
  let checkFailed = false;
1711
1712
  try {
1712
- remoteVer = execSync(`npm view ${pkgName} version`, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim();
1713
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
1714
+ if (process.platform === 'win32') {
1715
+ remoteVer = execSync(`"${npmCmd}" view ${pkgName} version`, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim();
1716
+ } else {
1717
+ remoteVer = execFileSync(npmCmd, ['view', pkgName, 'version'], { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim();
1718
+ }
1713
1719
  } catch (e) {
1714
1720
  // Offline, a proxy, or a misconfigured registry. Swallowing this
1715
1721
  // made every module report "up to date" forever on such a machine,
@@ -1804,15 +1810,40 @@ function downloadOrUpdateModule(pkgName, targetDir, displayName) {
1804
1810
  }
1805
1811
  }
1806
1812
 
1813
+ // Phase 1: retire the live tree. On Windows a running daemon holds open
1814
+ // file handles into targetDir โ€” retry with brief backoff before failing.
1815
+ // ponytail: 3 retries ร— 600ms; escalate to process kill if this ceiling matters
1816
+ let phase1Done = false;
1817
+ for (let attempt = 0; attempt < 3 && !phase1Done; attempt++) {
1818
+ try {
1819
+ fs.renameSync(targetDir, retiredDir);
1820
+ phase1Done = true;
1821
+ } catch (retireError) {
1822
+ const retriable = retireError.code === 'EPERM' || retireError.code === 'EBUSY';
1823
+ if (retriable && attempt < 2) {
1824
+ const delayMs = 600 * (attempt + 1);
1825
+ try {
1826
+ if (process.platform === 'win32') {
1827
+ execSync(`powershell -NoProfile -Command "Start-Sleep -Milliseconds ${delayMs}"`, { stdio: 'ignore' });
1828
+ } else {
1829
+ execSync(`sleep ${delayMs / 1000}`, { stdio: 'ignore' });
1830
+ }
1831
+ } catch (_) {}
1832
+ continue;
1833
+ }
1834
+ safeRmDirSync(stagingDir);
1835
+ throw retireError;
1836
+ }
1837
+ }
1838
+
1839
+ // Phase 2: promote the staging tree. stagingDir was just unpacked and
1840
+ // has no open handles, so a single attempt is sufficient.
1807
1841
  try {
1808
- fs.renameSync(targetDir, retiredDir);
1809
1842
  fs.renameSync(stagingDir, targetDir);
1810
- } catch (swapError) {
1811
- if (fs.existsSync(retiredDir) && !fs.existsSync(targetDir)) {
1812
- try { fs.renameSync(retiredDir, targetDir); } catch (_) {}
1813
- }
1843
+ } catch (promoteError) {
1844
+ try { fs.renameSync(retiredDir, targetDir); } catch (_) {}
1814
1845
  safeRmDirSync(stagingDir);
1815
- throw swapError;
1846
+ throw promoteError;
1816
1847
  }
1817
1848
 
1818
1849
  // On Windows especially, deleting a directory that was just renamed or contains
@@ -1885,6 +1916,21 @@ async function installMemB(interactive) {
1885
1916
  }));
1886
1917
  }
1887
1918
  const membDir = path.join(moduleBasePath(), 'memB');
1919
+ // On Windows the memB WebUI process holds open file handles inside membDir.
1920
+ // The atomic rename inside downloadOrUpdateModule fails with EPERM as long as
1921
+ // those handles are live โ€” retry backoff cannot help. Kill the process on
1922
+ // port 8088 before attempting the download so the rename succeeds.
1923
+ if (process.platform === 'win32') {
1924
+ try {
1925
+ execSync(
1926
+ 'powershell -NoProfile -Command "' +
1927
+ 'Get-NetTCPConnection -LocalPort 8088 -ErrorAction SilentlyContinue |' +
1928
+ ' Select-Object -ExpandProperty OwningProcess |' +
1929
+ ' ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }"',
1930
+ { stdio: 'ignore' }
1931
+ );
1932
+ } catch (_) {}
1933
+ }
1888
1934
  // A failed download used to fall through into venv setup and daemon
1889
1935
  // registration, each of which then failed on its own terms or, worse,
1890
1936
  // "succeeded" against a stale tree -- and the run still ended in a success
@@ -2050,13 +2096,23 @@ async function installSynapse() {
2050
2096
  }
2051
2097
 
2052
2098
  const isWin = process.platform === 'win32';
2053
- const binaryName = isWin ? 'synapse.js' : 'synapse';
2054
- let binaryPath = path.join(synapseDir, 'bin', binaryName);
2055
- if (!fs.existsSync(binaryPath)) {
2056
- if (process.platform === 'darwin' && fs.existsSync(path.join(synapseDir, 'bin', 'synapse-darwin-arm64'))) {
2057
- binaryPath = path.join(synapseDir, 'bin', 'synapse-darwin-arm64');
2058
- } else if (process.platform === 'linux' && fs.existsSync(path.join(synapseDir, 'bin', 'synapse-linux-amd64'))) {
2059
- binaryPath = path.join(synapseDir, 'bin', 'synapse-linux-amd64');
2099
+ let binaryPath;
2100
+ if (isWin) {
2101
+ if (fs.existsSync(path.join(synapseDir, 'bin', 'synapse.exe'))) {
2102
+ binaryPath = path.join(synapseDir, 'bin', 'synapse.exe');
2103
+ } else if (fs.existsSync(path.join(synapseDir, 'bin', 'synapse-windows-amd64.exe'))) {
2104
+ binaryPath = path.join(synapseDir, 'bin', 'synapse-windows-amd64.exe');
2105
+ } else {
2106
+ binaryPath = path.join(synapseDir, 'bin', 'synapse.js');
2107
+ }
2108
+ } else {
2109
+ binaryPath = path.join(synapseDir, 'bin', 'synapse');
2110
+ if (!fs.existsSync(binaryPath)) {
2111
+ if (process.platform === 'darwin' && fs.existsSync(path.join(synapseDir, 'bin', 'synapse-darwin-arm64'))) {
2112
+ binaryPath = path.join(synapseDir, 'bin', 'synapse-darwin-arm64');
2113
+ } else if (process.platform === 'linux' && fs.existsSync(path.join(synapseDir, 'bin', 'synapse-linux-amd64'))) {
2114
+ binaryPath = path.join(synapseDir, 'bin', 'synapse-linux-amd64');
2115
+ }
2060
2116
  }
2061
2117
  }
2062
2118
 
@@ -2406,85 +2462,159 @@ async function installDevToolInstaller() {
2406
2462
  }
2407
2463
  }
2408
2464
 
2409
- // AO ships one prebuilt binary: backend/ao-daemon, Mach-O arm64. The package
2410
- // carries Windows and Linux sources but nothing built for them, and its CLI
2411
- // shim picks the first candidate path that EXISTS without checking whether it
2412
- // can run โ€” so on any other platform it hands spawn a Mach-O file and fails
2413
- // with an exec-format error instead of the "go build" hint it means to give.
2414
- // Installing it there would be installing something known not to work.
2465
+ // AO is fully supported across macOS (arm64, amd64), Linux (amd64, arm64), and Windows (amd64).
2415
2466
  function aoSupportedHere() {
2416
- return process.platform === 'darwin' && process.arch === 'arm64';
2467
+ return ['darwin', 'linux', 'win32'].includes(process.platform);
2417
2468
  }
2418
2469
 
2419
2470
  async function installOSAgentWorkspace() {
2420
2471
  if (!aoSupportedHere()) {
2421
- log.warn(`AO ships only a macOS arm64 binary โ€” skipping on ${process.platform}/${process.arch}.`);
2422
- log.warn('Build it from source instead: github.com/hybridlabor-api/bdb-agent-orchestrator');
2472
+ log.warn(`AO is not supported on ${process.platform}/${process.arch}.`);
2423
2473
  return;
2424
2474
  }
2425
2475
 
2426
- const osAgentDir = path.join(moduleBasePath(), 'bdb-agent-orchestrator');
2427
- if (!downloadOrUpdateModule('@hybridlabor-api/bdb-agent-orchestrator', osAgentDir, 'BDB Agent Orchestrator')) {
2428
- log.warn('Skipping AO setup: the module could not be downloaded.');
2429
- return;
2476
+ const isWin = process.platform === 'win32';
2477
+ const isMac = process.platform === 'darwin';
2478
+ const isLinux = process.platform === 'linux';
2479
+
2480
+ // Discover AO directory: check local development checkouts first, then moduleBasePath
2481
+ const candidateDirs = [
2482
+ path.join(homeDir, 'dev', 'agents', 'bdb-agent-orchestrator'),
2483
+ path.join(homeDir, 'dev', 'bdb-dev', 'bdb-agent-orchestrator'),
2484
+ path.join(moduleBasePath(), 'bdb-agent-orchestrator')
2485
+ ];
2486
+ const osAgentDir = candidateDirs.find(d => fs.existsSync(d)) || candidateDirs[candidateDirs.length - 1];
2487
+ const isLocalGitRepo = fs.existsSync(path.join(osAgentDir, '.git'));
2488
+
2489
+ if (isLocalGitRepo) {
2490
+ log.step(`Using local development workspace for AO: ${osAgentDir}`);
2491
+ } else {
2492
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-agent-orchestrator', osAgentDir, 'BDB Agent Orchestrator')) {
2493
+ log.warn('Skipping AO setup: the module could not be downloaded.');
2494
+ return;
2495
+ }
2430
2496
  }
2431
2497
  if (DRY_RUN) {
2432
- log.step('[dry-run] would link ~/.local/bin/ao and run `ao service install`');
2498
+ log.step('[dry-run] would link ao binary and run `ao service install`');
2433
2499
  return;
2434
2500
  }
2435
2501
 
2436
- const localBinDir = path.join(homeDir, '.local', 'bin');
2437
- const binTarget = path.join(localBinDir, 'ao');
2438
- const daemonBin = path.join(osAgentDir, 'backend', 'ao-daemon');
2502
+ const localBinDir = isWin
2503
+ ? path.join(process.env.LOCALAPPDATA || homeDir, 'Programs', 'ao')
2504
+ : path.join(homeDir, '.local', 'bin');
2505
+ const binTarget = path.join(localBinDir, isWin ? 'ao.exe' : 'ao');
2506
+
2507
+ // Platform-specific binary lookup
2508
+ const candidateBinaries = [];
2509
+ if (isWin) {
2510
+ candidateBinaries.push(
2511
+ path.join(osAgentDir, 'backend', 'bin', 'ao-windows-amd64.exe'),
2512
+ path.join(osAgentDir, 'backend', 'bin', 'ao.exe'),
2513
+ path.join(osAgentDir, 'backend', 'ao.exe')
2514
+ );
2515
+ } else if (isMac) {
2516
+ if (process.arch === 'arm64') {
2517
+ candidateBinaries.push(path.join(osAgentDir, 'backend', 'bin', 'ao-darwin-arm64'));
2518
+ } else {
2519
+ candidateBinaries.push(path.join(osAgentDir, 'backend', 'bin', 'ao-darwin-amd64'));
2520
+ }
2521
+ candidateBinaries.push(
2522
+ path.join(osAgentDir, 'backend', 'ao-daemon'),
2523
+ path.join(osAgentDir, 'backend', 'bin', 'ao'),
2524
+ path.join(osAgentDir, 'backend', 'ao')
2525
+ );
2526
+ } else if (isLinux) {
2527
+ if (process.arch === 'arm64') {
2528
+ candidateBinaries.push(path.join(osAgentDir, 'backend', 'bin', 'ao-linux-arm64'));
2529
+ } else {
2530
+ candidateBinaries.push(path.join(osAgentDir, 'backend', 'bin', 'ao-linux-amd64'));
2531
+ }
2532
+ candidateBinaries.push(
2533
+ path.join(osAgentDir, 'backend', 'ao-daemon'),
2534
+ path.join(osAgentDir, 'backend', 'bin', 'ao'),
2535
+ path.join(osAgentDir, 'backend', 'ao')
2536
+ );
2537
+ }
2538
+ let daemonBin = candidateBinaries.find(p => fs.existsSync(p));
2539
+
2540
+ // Fallback: compile from Go source if Go is installed and binary is not pre-built
2541
+ if (!daemonBin && hasExecutable('go') && fs.existsSync(path.join(osAgentDir, 'backend', 'cmd', 'ao'))) {
2542
+ const s = spinner();
2543
+ s.start('Compiling AO daemon binary from Go source...');
2544
+ try {
2545
+ const buildTarget = path.join(osAgentDir, 'backend', 'bin', isWin ? 'ao.exe' : 'ao');
2546
+ fs.mkdirSync(path.dirname(buildTarget), { recursive: true });
2547
+ execFileSync('go', ['build', '-ldflags=-s -w', '-o', buildTarget, './cmd/ao'], {
2548
+ cwd: path.join(osAgentDir, 'backend'),
2549
+ stdio: 'ignore'
2550
+ });
2551
+ daemonBin = buildTarget;
2552
+ s.stop('Compiled AO daemon binary successfully.');
2553
+ } catch (e) {
2554
+ s.stop(`Failed to compile AO from source: ${e.message}`);
2555
+ }
2556
+ }
2439
2557
 
2440
- if (!fs.existsSync(daemonBin)) {
2441
- log.warn(`AO binary missing at ${daemonBin} โ€” package layout changed?`);
2558
+ // Fallback: if already installed and functional, keep it
2559
+ if (!daemonBin && fs.existsSync(binTarget)) {
2560
+ try {
2561
+ execFileSync(binTarget, ['--version'], { stdio: 'ignore' });
2562
+ log.step(`Keeping existing working AO binary at ${binTarget}`);
2563
+ daemonBin = binTarget;
2564
+ } catch (_) {}
2565
+ }
2566
+
2567
+ if (!daemonBin) {
2568
+ log.warn(`AO binary missing in ${osAgentDir} โ€” package layout changed or Go build needed.`);
2442
2569
  return;
2443
2570
  }
2444
2571
 
2445
- installStep('place the ao binary', () => {
2446
- fs.mkdirSync(localBinDir, { recursive: true });
2447
- // copyFileSync preserves bytes, so the package's ad-hoc signature
2448
- // survives. That matters: an unsigned binary at this path is SIGKILLed
2449
- // by AMFI on launch (exit 137) and reads as a daemon that simply will
2450
- // not start, with nothing in the log to say why.
2451
- fs.copyFileSync(daemonBin, binTarget);
2452
- fs.chmodSync(binTarget, 0o755);
2453
- log.step(`Installed ao to ${binTarget}`);
2454
- }, 'AO cannot be started without its binary.');
2572
+ if (daemonBin !== binTarget) {
2573
+ installStep('place the ao binary', () => {
2574
+ fs.mkdirSync(localBinDir, { recursive: true });
2575
+ fs.copyFileSync(daemonBin, binTarget);
2576
+ if (!isWin) {
2577
+ fs.chmodSync(binTarget, 0o755);
2578
+ }
2579
+ log.step(`Installed ao to ${binTarget}`);
2580
+ }, 'AO cannot be started without its binary.');
2581
+ }
2455
2582
 
2456
- try {
2457
- execFileSync('codesign', ['-v', binTarget], { stdio: 'ignore' });
2458
- } catch {
2459
- log.warn('The ao binary is not code-signed โ€” AMFI will kill it at launch.');
2583
+ // macOS only: code-sign ad-hoc to prevent AMFI SIGKILL
2584
+ if (isMac) {
2460
2585
  try {
2461
- execFileSync('codesign', ['-s', '-', '-f', binTarget], { stdio: 'ignore' });
2462
- log.step('Signed it ad-hoc.');
2463
- } catch (e) { log.warn(`Could not sign it: ${e.message}`); }
2586
+ execFileSync('codesign', ['-v', binTarget], { stdio: 'ignore' });
2587
+ } catch {
2588
+ log.warn('The ao binary is not code-signed โ€” AMFI will kill it at launch.');
2589
+ try {
2590
+ execFileSync('codesign', ['-s', '-', '-f', binTarget], { stdio: 'ignore' });
2591
+ log.step('Signed it ad-hoc.');
2592
+ } catch (e) { log.warn(`Could not sign it: ${e.message}`); }
2593
+ }
2464
2594
  }
2465
2595
 
2466
2596
  // AO installs its own service, and does it for macOS, Windows and Linux.
2467
- // AOS used to hand-write a LaunchAgent instead, labelled
2468
- // com.bdb.agent-workspace, whose ProgramArguments were just the binary with
2469
- // NO subcommand -- so launchd started `ao`, which prints help and exits,
2470
- // and with KeepAlive kept restarting it. The daemon never ran, on any
2471
- // machine that installed AO through AOS. Delegating to the tool removes
2472
- // both the wrong label and the wrong arguments, and keeps working when the
2473
- // service definition changes upstream.
2474
- const legacyPlist = path.join(homeDir, 'Library', 'LaunchAgents', 'com.bdb.agent-workspace.plist');
2475
- if (fs.existsSync(legacyPlist)) {
2476
- try { execFileSync('launchctl', ['unload', legacyPlist], { stdio: 'ignore' }); } catch (e) { logDebug(e, 'unload legacy ao agent'); }
2477
- try { fs.unlinkSync(legacyPlist); log.step('Removed the old com.bdb.agent-workspace service.'); } catch (e) { logDebug(e, 'remove legacy plist'); }
2597
+ if (isMac) {
2598
+ const legacyPlist = path.join(homeDir, 'Library', 'LaunchAgents', 'com.bdb.agent-workspace.plist');
2599
+ if (fs.existsSync(legacyPlist)) {
2600
+ try { execFileSync('launchctl', ['unload', legacyPlist], { stdio: 'ignore' }); } catch (e) { logDebug(e, 'unload legacy ao agent'); }
2601
+ try { fs.unlinkSync(legacyPlist); log.step('Removed the old com.bdb.agent-workspace service.'); } catch (e) { logDebug(e, 'remove legacy plist'); }
2602
+ }
2478
2603
  }
2479
2604
 
2480
2605
  installStep('register the AO service', () => {
2481
2606
  execFileSync(binTarget, ['service', 'install'], { stdio: 'ignore' });
2482
2607
  }, 'Start it by hand with: ao service install');
2483
2608
 
2484
- if (await verifyDaemonListening(3101, 'AO Orchestrator', 8000)) {
2609
+ // Windows startup entries fire asynchronously โ€” give the service more time
2610
+ const aoTimeout = process.platform === 'win32' ? 20000 : 8000;
2611
+ if (await verifyDaemonListening(3101, 'AO Orchestrator', aoTimeout)) {
2485
2612
  log.success('AO Orchestrator running on http://localhost:3101');
2486
2613
  } else {
2487
- log.warn('AO did not answer on :3101 โ€” check `ao service status` and `ao service logs`.');
2614
+ const hint = process.platform === 'win32'
2615
+ ? 'Run `ao service install` again or log out and back in, then check `ao service status`.'
2616
+ : 'Check `ao service status` and `ao service logs`.';
2617
+ log.warn(`AO did not answer on :3101 โ€” ${hint}`);
2488
2618
  }
2489
2619
  }
2490
2620
  async function promptMemBIngestion(mcpCodeTarget) {
@@ -2534,7 +2664,7 @@ function verifyEcosystemInstallation() {
2534
2664
  { name: '1. bdb-synapse', pkg: '@hybridlabor-api/bdb-synapse', paths: [path.join(moduleBasePath(), 'bdb-synapse')] },
2535
2665
  { name: '2. memB', pkg: '@hybridlabor-api/memb', paths: [path.join(moduleBasePath(), 'memB'), path.join(geminiDir, 'config', 'mcps', 'memb-mcp')] },
2536
2666
  { name: '3. heimdall-token-saver', pkg: '@hybridlabor-api/heimdall-token-saver', paths: [path.join(moduleBasePath(), 'heimdall-token-saver'), path.join(srcDir, 'vendor', 'token-saver')] },
2537
- { name: '4. AO Agent Orchestrator', pkg: '@hybridlabor-api/bdb-agent-orchestrator', paths: [path.join(moduleBasePath(), 'bdb-agent-orchestrator')] },
2667
+ { name: '4. AO Agent Orchestrator', pkg: '@hybridlabor-api/bdb-agent-orchestrator', paths: [path.join(homeDir, 'dev', 'agents', 'bdb-agent-orchestrator'), path.join(moduleBasePath(), 'bdb-agent-orchestrator')] },
2538
2668
  { name: '5. bdb-dev-creator-extension', pkg: '@hybridlabor-api/bdb-dev-creator-extension', paths: [path.join(moduleBasePath(), 'bdb-dev-creator-extension')] },
2539
2669
  { name: '6. bdb-hardware-pcb', pkg: '@hybridlabor-api/bdb-hardware-pcb', paths: [path.join(moduleBasePath(), 'bdb-hardware-pcb')] },
2540
2670
  { name: '7. bdb-os-remote', pkg: '@hybridlabor-api/bdb-os-remote', paths: [path.join(moduleBasePath(), 'bdb-os-remote')] },
@@ -2558,7 +2688,10 @@ function verifyEcosystemInstallation() {
2558
2688
  let newerVersion = null;
2559
2689
  let newerTag = null;
2560
2690
  try {
2561
- const distTagsJson = execSync(`npm view ${mod.pkg} dist-tags --json`, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim();
2691
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
2692
+ const distTagsJson = process.platform === 'win32'
2693
+ ? execSync(`"${npmCmd}" view ${mod.pkg} dist-tags --json`, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim()
2694
+ : execFileSync(npmCmd, ['view', mod.pkg, 'dist-tags', '--json'], { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim();
2562
2695
  const distTags = JSON.parse(distTagsJson);
2563
2696
  for (const [tag, ver] of Object.entries(distTags)) {
2564
2697
  if (isNewerVersion(localVer, ver)) {
@@ -2604,7 +2737,7 @@ function verifyEcosystemInstallation() {
2604
2737
 
2605
2738
  console.log('');
2606
2739
  console.log(` ${colors.bold}โ”โ”โ” BDB Agent OS Dashboard โ”โ”โ”${colors.reset}`);
2607
- console.log(` Interactive Control Center: npx aos-dashboard`);
2740
+ console.log(` Interactive Control Center: aos-dashboard`);
2608
2741
  console.log(` ${colors.dim}(Live status & service control at http://127.0.0.1:7900)${colors.reset}`);
2609
2742
  }
2610
2743
 
@@ -3544,23 +3677,23 @@ function injectHarnessRules() {
3544
3677
  // that adds or changes a hook silently never received it -- which is exactly
3545
3678
  // what happened to memb-inject.mjs in v4.4.0, on the machines that needed it
3546
3679
  // most (a fresh install got it; every machine that already had AOS did not).
3547
- function installGlobalHooks() {
3680
+ function installGlobalHooks({ targetHome = homeDir, targetGemini = geminiDir } = {}) {
3548
3681
  const hooksSrc = path.join(srcDir, '.claude', 'hooks');
3549
3682
  const workflowsSrc = path.join(srcDir, '.claude', 'workflows');
3550
3683
 
3551
3684
  // 1. Claude Code
3552
3685
  if (fs.existsSync(hooksSrc)) {
3553
- copyDirRecursiveSync(hooksSrc, path.join(homeDir, '.claude', 'hooks'));
3554
- log.step(`Installed hooks to ${path.join(homeDir, '.claude', 'hooks')}`);
3686
+ copyDirRecursiveSync(hooksSrc, path.join(targetHome, '.claude', 'hooks'));
3687
+ log.step(`Installed hooks to ${path.join(targetHome, '.claude', 'hooks')}`);
3555
3688
  }
3556
3689
  if (fs.existsSync(workflowsSrc)) {
3557
- copyDirRecursiveSync(workflowsSrc, path.join(homeDir, '.claude', 'workflows'));
3690
+ copyDirRecursiveSync(workflowsSrc, path.join(targetHome, '.claude', 'workflows'));
3558
3691
  }
3559
- mergeBdbSettingsHooks(path.join(homeDir, '.claude', 'settings.json'));
3692
+ mergeBdbSettingsHooks(path.join(targetHome, '.claude', 'settings.json'));
3560
3693
 
3561
3694
  // 2. Google Antigravity
3562
- const agyHooksDir = path.join(geminiDir, 'config', 'hooks');
3563
- const agyWorkflowsDir = path.join(geminiDir, 'config', 'workflows');
3695
+ const agyHooksDir = path.join(targetGemini, 'config', 'hooks');
3696
+ const agyWorkflowsDir = path.join(targetGemini, 'config', 'workflows');
3564
3697
  if (fs.existsSync(hooksSrc)) {
3565
3698
  copyDirRecursiveSync(hooksSrc, agyHooksDir);
3566
3699
  log.step(`Installed hooks to ${agyHooksDir}`);
@@ -3568,14 +3701,24 @@ function installGlobalHooks() {
3568
3701
  if (fs.existsSync(workflowsSrc)) {
3569
3702
  copyDirRecursiveSync(workflowsSrc, agyWorkflowsDir);
3570
3703
  }
3571
- mergeAntigravityHooks(path.join(geminiDir, 'config', 'hooks.json'));
3572
- if (fs.existsSync(path.join(geminiDir, 'antigravity-cli'))) {
3573
- mergeAntigravityHooks(path.join(geminiDir, 'antigravity-cli', 'hooks.json'));
3704
+ mergeAntigravityHooks(path.join(targetGemini, 'config', 'hooks.json'));
3705
+ const agyCliDir = path.join(targetGemini, 'antigravity-cli');
3706
+ if (fs.existsSync(agyCliDir)) {
3707
+ const agyCliHooksDir = path.join(agyCliDir, 'hooks');
3708
+ const agyCliWorkflowsDir = path.join(agyCliDir, 'workflows');
3709
+ if (fs.existsSync(hooksSrc)) {
3710
+ copyDirRecursiveSync(hooksSrc, agyCliHooksDir);
3711
+ log.step(`Installed hooks to ${agyCliHooksDir}`);
3712
+ }
3713
+ if (fs.existsSync(workflowsSrc)) {
3714
+ copyDirRecursiveSync(workflowsSrc, agyCliWorkflowsDir);
3715
+ }
3716
+ mergeAntigravityHooks(path.join(agyCliDir, 'hooks.json'));
3574
3717
  }
3575
3718
 
3576
3719
  // 3. OpenAI Codex CLI
3577
- const codexHooksDir = path.join(homeDir, '.codex', 'hooks');
3578
- const codexWorkflowsDir = path.join(homeDir, '.codex', 'workflows');
3720
+ const codexHooksDir = path.join(targetHome, '.codex', 'hooks');
3721
+ const codexWorkflowsDir = path.join(targetHome, '.codex', 'workflows');
3579
3722
  if (fs.existsSync(hooksSrc)) {
3580
3723
  copyDirRecursiveSync(hooksSrc, codexHooksDir);
3581
3724
  log.step(`Installed hooks to ${codexHooksDir}`);
@@ -3583,12 +3726,12 @@ function installGlobalHooks() {
3583
3726
  if (fs.existsSync(workflowsSrc)) {
3584
3727
  copyDirRecursiveSync(workflowsSrc, codexWorkflowsDir);
3585
3728
  }
3586
- mergeCodexTomlHooks(path.join(homeDir, '.codex', 'config.toml'));
3729
+ mergeCodexTomlHooks(path.join(targetHome, '.codex', 'config.toml'));
3587
3730
 
3588
3731
  // 4. OpenCode CLI
3589
3732
  const opencodeDir = process.platform === 'win32'
3590
- ? path.join(process.env.APPDATA || homeDir, 'opencode')
3591
- : path.join(homeDir, '.config', 'opencode');
3733
+ ? path.join(process.env.APPDATA || targetHome, 'opencode')
3734
+ : path.join(targetHome, '.config', 'opencode');
3592
3735
  const opencodePluginSrc = path.join(srcDir, '.opencode', 'plugins', 'bdb-aos.js');
3593
3736
  if (fs.existsSync(opencodePluginSrc)) {
3594
3737
  const opencodePluginsDir = path.join(opencodeDir, 'plugins');
@@ -3664,7 +3807,7 @@ function installGlobalBinaries() {
3664
3807
  // merged result goes to a .bdb-new.json sidecar -- the same recovery pattern
3665
3808
  // the MCP config merge in installMcpsForTarget uses.
3666
3809
  function mergeBdbSettingsHooks(settingsPath, { projectLocal = false } = {}) {
3667
- const bdbHookScripts = ['go-gate.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'trail-relay.mjs'];
3810
+ const bdbHookScripts = ['go-gate.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'trail-relay.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs'];
3668
3811
  // memb-inject reads the machine-global memB store under $HOME and is
3669
3812
  // installed once per machine, so it stays $HOME-anchored even inside a
3670
3813
  // project harness -- unlike the two gate hooks, which are per-checkout by
@@ -3736,7 +3879,7 @@ function mergeBdbSettingsHooks(settingsPath, { projectLocal = false } = {}) {
3736
3879
  // Merge the BDB hooks into Google Antigravity's hooks.json (.agents/hooks.json or
3737
3880
  // ~/.gemini/config/hooks.json), preserving user-defined foreign hooks.
3738
3881
  function mergeAntigravityHooks(hooksPath, { projectLocal = false } = {}) {
3739
- const bdbHookScripts = ['go-gate.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'startcycle-dispatch.mjs', 'trail-relay.mjs'];
3882
+ const bdbHookScripts = ['go-gate.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'startcycle-dispatch.mjs', 'trail-relay.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs'];
3740
3883
  const isBdbEntry = (entry) => {
3741
3884
  const cmds = (entry && Array.isArray(entry.hooks) ? entry.hooks : [entry])
3742
3885
  .map((h) => (h && typeof h.command === 'string' ? h.command : (typeof h === 'string' ? h : '')))
@@ -3753,7 +3896,14 @@ function mergeAntigravityHooks(hooksPath, { projectLocal = false } = {}) {
3753
3896
  PreToolUse: [
3754
3897
  {
3755
3898
  matcher: "run_command|Bash",
3756
- hooks: [{ type: "command", command: `node "${path.join(hooksDir, 'go-gate.mjs')}"`, timeout: 10000 }]
3899
+ hooks: [
3900
+ { type: "command", command: `node "${path.join(hooksDir, 'go-gate.mjs')}"`, timeout: 10000 },
3901
+ { type: "command", command: `node "${path.join(hooksDir, 'conventional-commits.mjs')}"`, timeout: 10000 }
3902
+ ]
3903
+ },
3904
+ {
3905
+ matcher: "write_file|edit_file|replace|Write|Edit|MultiEdit",
3906
+ hooks: [{ type: "command", command: `node "${path.join(hooksDir, 'env-file-protection.mjs')}"`, timeout: 10000 }]
3757
3907
  },
3758
3908
  {
3759
3909
  hooks: [{ type: "command", command: `node "${path.join(globalHooksDir, 'trail-relay.mjs')}" --agent agy --event PreToolUse`, timeout: 2000 }]
@@ -4775,18 +4925,24 @@ async function runQuickUpdate(installState) {
4775
4925
  injectHarnessRules();
4776
4926
  }, 'Harness files keep whatever version this machine already had.');
4777
4927
 
4778
- // OpenWiki setup only ever ran on a brand-new install (main()'s fresh-install
4779
- // branch below) -- an existing install running Quick Update never got offered
4780
- // it and never had its daemon schedule refreshed. promptCredentials() already
4781
- // detects an existing key and collapses to a single "keep existing" prompt in
4782
- // that case, so this is a no-op confirm for anyone already configured and a
4783
- // real one-time offer for anyone who isn't (including installs from before
4784
- // this feature existed).
4928
+ // OpenWiki: if already configured, refresh the daemon silently (no prompt).
4929
+ // Only ask when there is no key yet โ€” i.e. a first-time offer or a machine
4930
+ // that predates this feature. This avoids the "keep existing?" confirmation
4931
+ // on every Quick Update for users who have already set up OpenWiki.
4785
4932
  if (!isAutoYes) {
4786
- const creds = await promptCredentials(paths.targetMcpDir);
4787
- if (creds !== BACK) {
4788
- await installOpenWikiDaemon(creds.gemini, paths.targetSkillDir, { provider: creds.openwikiProvider, model: creds.openwikiModel, baseUrl: creds.openwikiBaseUrl });
4933
+ const _owEnv = loadExistingEnv(paths.targetMcpDir);
4934
+ const _owProvider = _owEnv['OPENWIKI_PROVIDER'] || 'google';
4935
+ const _owKeyName = PROVIDER_KEY_ENV_NAMES[_owProvider] || 'GEMINI_API_KEY';
4936
+ const _owKey = _owEnv[_owKeyName] || _owEnv['GEMINI_API_KEY'] || _owEnv['GOOGLE_API_KEY'] || _owEnv['OPENWIKI_API_KEY'] || '';
4937
+ if (_owKey || _owProvider === 'ollama') {
4938
+ await installOpenWikiDaemon(_owKey, paths.targetSkillDir, { provider: _owProvider, model: _owEnv['OPENWIKI_MODEL'] || '', baseUrl: _owEnv['OPENWIKI_BASE_URL'] || '' });
4789
4939
  await installOpenWikiVisualizer();
4940
+ } else {
4941
+ const creds = await promptCredentials(paths.targetMcpDir);
4942
+ if (creds !== BACK) {
4943
+ await installOpenWikiDaemon(creds.gemini, paths.targetSkillDir, { provider: creds.openwikiProvider, model: creds.openwikiModel, baseUrl: creds.openwikiBaseUrl });
4944
+ await installOpenWikiVisualizer();
4945
+ }
4790
4946
  }
4791
4947
  }
4792
4948
 
@@ -4795,8 +4951,7 @@ async function runQuickUpdate(installState) {
4795
4951
  if (subId === 'synapse') await installSynapse();
4796
4952
  else if (subId === 'memb') await installMemB(false);
4797
4953
  else if (subId === 'remote') await installOSRemoteGateway();
4798
- // 'ao' intentionally skipped here even for
4799
- // existing installs -- see promptOptionalModules() for why.
4954
+ else if (subId === 'ao' && aoSupportedHere()) await installOSAgentWorkspace();
4800
4955
  else if (subId === 'creator') await installCreatorExtension();
4801
4956
  else if (subId === 'hardware') await installHardwarePcb();
4802
4957
  else if (subId === 'installer') await installDevToolInstaller();
@@ -4840,6 +4995,40 @@ function buildAoAnnouncementBanner() {
4840
4995
  ].join('\n');
4841
4996
  }
4842
4997
 
4998
+ // AOS CLI ships as a separate npm package under packages/ for one reason: pi
4999
+ // updates on its own cadence, and folding it into this release cycle would turn
5000
+ // every pi bump into a full AOS reinstall. So it installs as an ordinary global
5001
+ // package and pulls pi in as its own dependency -- and it writes nothing to
5002
+ // ~/.agents, because it reads that directory rather than owning it.
5003
+ function installAosCli() {
5004
+ const pkgDir = path.join(srcDir, 'packages', 'aos-cli');
5005
+ const manual = 'cd packages/aos-cli && npm install -g .';
5006
+
5007
+ if (!fs.existsSync(path.join(pkgDir, 'package.json'))) {
5008
+ log.warn(`AOS CLI package missing at ${pkgDir} -- skipped. Install it later: ${manual}`);
5009
+ return;
5010
+ }
5011
+ if (hasExecutable('aos-cli')) {
5012
+ log.step('aos-cli is already on PATH.');
5013
+ return;
5014
+ }
5015
+ if (DRY_RUN) {
5016
+ log.step('[dry-run] would run: npm install -g <packages/aos-cli>');
5017
+ return;
5018
+ }
5019
+
5020
+ const res = installStep('install AOS CLI', () => {
5021
+ log.step('Installing AOS CLI globally (pi is installed as its dependency)...');
5022
+ execSync(`npm install -g ${JSON.stringify(pkgDir)}`, { stdio: 'ignore' });
5023
+ }, `The rest of AOS is installed. Install AOS CLI later: ${manual}`);
5024
+
5025
+ if (res.ok && hasExecutable('aos-cli')) {
5026
+ log.success('AOS CLI installed -- run `aos-cli`.');
5027
+ } else if (res.ok) {
5028
+ log.warn('AOS CLI is installed but `aos-cli` is not on PATH. launchd and some Windows shells start with a PATH that omits npm\'s global bin.');
5029
+ }
5030
+ }
5031
+
4843
5032
  async function main() {
4844
5033
  if (process.argv[2] === 'store') {
4845
5034
  const storeScript = path.join(srcDir, 'bin', 'aos-store.mjs');
@@ -4981,6 +5170,7 @@ async function main() {
4981
5170
  { value: '7', label: 'Roo Code / Cline / VS Code', hint: '~/.roo' },
4982
5171
  { value: '8', label: 'Aider CLI', hint: '~/.aider' },
4983
5172
  { value: '9', label: '๐Ÿ“ Local Project Harness', hint: 'copy dispatcher contract to current project' },
5173
+ { value: '10', label: '๐Ÿ–ฅ๏ธ AOS CLI', hint: 'pi harness -- reads ~/.agents, no own skill copy' },
4984
5174
  { value: '4', label: 'โš™๏ธ Custom Installation', hint: 'specify paths manually' }
4985
5175
  ];
4986
5176
 
@@ -5010,7 +5200,7 @@ async function main() {
5010
5200
  selectedMcps = await promptMcpSelection(tier);
5011
5201
  } else {
5012
5202
  const ctx = { tier: '1', selectedPlatforms: ['0'], mode: 'merge', customPaths: null, creds: null, selectedMcps: null };
5013
- const platformNames = { '0': '๐ŸŒ Universal Harness', '1': 'Google Antigravity', '2': 'Claude Desktop/Code', '3': 'Cursor/Generic IDE', '4': 'Custom Paths', '5': 'ChatGPT Codex CLI', '6': 'Windsurf', '7': 'Roo Code / Cline / VS Code', '8': 'Aider CLI', '9': 'Local Project Harness' };
5203
+ const platformNames = { '0': '๐ŸŒ Universal Harness', '1': 'Google Antigravity', '2': 'Claude Desktop/Code', '3': 'Cursor/Generic IDE', '4': 'Custom Paths', '5': 'ChatGPT Codex CLI', '6': 'Windsurf', '7': 'Roo Code / Cline / VS Code', '8': 'Aider CLI', '9': 'Local Project Harness', '10': 'AOS CLI' };
5014
5204
 
5015
5205
  const stepTier = async () => {
5016
5206
  const t = await selectWithBack({
@@ -5176,7 +5366,22 @@ async function main() {
5176
5366
  fs.mkdirSync(backupDir, { recursive: true, mode: 0o700 });
5177
5367
  }, 'The installation continues, but existing files are not backed up.');
5178
5368
 
5179
- const targets = specificPlatforms.map(p => ({
5369
+ // '10' is an action, not a directory target. AOS CLI reads
5370
+ // ~/.agents/skills, which syncSkillsToGlobalHarnesses() already writes --
5371
+ // left in this list it would fall through resolveTargetPaths()'s universal
5372
+ // defaults and install a second full copy of every skill somewhere nobody
5373
+ // reads them from.
5374
+ const aosCliRequested = specificPlatforms.includes('10');
5375
+ const dirPlatforms = specificPlatforms.filter(p => p !== '10');
5376
+
5377
+ if (aosCliRequested && dirPlatforms.length === 0) {
5378
+ // AOS CLI alone would have no skills to load, and an empty target list
5379
+ // leaves primaryTarget undefined for everything downstream of it.
5380
+ log.warn('AOS CLI was the only target -- adding the universal skill target so it has something to load.');
5381
+ dirPlatforms.push('1');
5382
+ }
5383
+
5384
+ const targets = dirPlatforms.map(p => ({
5180
5385
  value: p,
5181
5386
  ...resolveTargetPaths(p, customPaths)
5182
5387
  }));
@@ -5263,10 +5468,13 @@ async function main() {
5263
5468
  // Flush file-level install manifest after all writes are done.
5264
5469
  flushSessionManifest();
5265
5470
 
5471
+ // After the skill sync, because AOS CLI is useless without ~/.agents.
5472
+ if (aosCliRequested) installAosCli();
5473
+
5266
5474
  console.log('');
5267
5475
  verifyEcosystemInstallation();
5268
5476
 
5269
- outro(`๐ŸŽ‰ Installation complete! Targets: ${targets.map(t => t.value).join(', ')} ยท Tier: ${tier === '1' ? 'Pro MEDIA' : 'Basic'}${DRY_RUN ? ' ยท DRY-RUN (nothing was modified)' : ''}`);
5477
+ outro(`๐ŸŽ‰ Installation complete! Targets: ${targets.map(t => t.value).join(', ')}${aosCliRequested ? ' + AOS CLI' : ''} ยท Tier: ${tier === '1' ? 'Pro MEDIA' : 'Basic'}${DRY_RUN ? ' ยท DRY-RUN (nothing was modified)' : ''}`);
5270
5478
  }
5271
5479
 
5272
5480
  if (require.main === module) {