aether-colony 5.1.0 → 5.3.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 (185) hide show
  1. package/.aether/aether-utils.sh +157 -42
  2. package/.aether/agents/aether-ambassador.md +140 -0
  3. package/.aether/agents/aether-archaeologist.md +108 -0
  4. package/.aether/agents/aether-architect.md +133 -0
  5. package/.aether/agents/aether-auditor.md +144 -0
  6. package/.aether/agents/aether-builder.md +184 -0
  7. package/.aether/agents/aether-chaos.md +115 -0
  8. package/.aether/agents/aether-chronicler.md +122 -0
  9. package/.aether/agents/aether-gatekeeper.md +116 -0
  10. package/.aether/agents/aether-includer.md +117 -0
  11. package/.aether/agents/aether-keeper.md +177 -0
  12. package/.aether/agents/aether-measurer.md +128 -0
  13. package/.aether/agents/aether-oracle.md +137 -0
  14. package/.aether/agents/aether-probe.md +133 -0
  15. package/.aether/agents/aether-queen.md +286 -0
  16. package/.aether/agents/aether-route-setter.md +130 -0
  17. package/.aether/agents/aether-sage.md +106 -0
  18. package/.aether/agents/aether-scout.md +101 -0
  19. package/.aether/agents/aether-surveyor-disciplines.md +391 -0
  20. package/.aether/agents/aether-surveyor-nest.md +329 -0
  21. package/.aether/agents/aether-surveyor-pathogens.md +264 -0
  22. package/.aether/agents/aether-surveyor-provisions.md +334 -0
  23. package/.aether/agents/aether-tracker.md +137 -0
  24. package/.aether/agents/aether-watcher.md +174 -0
  25. package/.aether/agents/aether-weaver.md +130 -0
  26. package/.aether/commands/claude/archaeology.md +334 -0
  27. package/.aether/commands/claude/build.md +65 -0
  28. package/.aether/commands/claude/chaos.md +336 -0
  29. package/.aether/commands/claude/colonize.md +259 -0
  30. package/.aether/commands/claude/continue.md +60 -0
  31. package/.aether/commands/claude/council.md +507 -0
  32. package/.aether/commands/claude/data-clean.md +81 -0
  33. package/.aether/commands/claude/dream.md +268 -0
  34. package/.aether/commands/claude/entomb.md +498 -0
  35. package/.aether/commands/claude/export-signals.md +57 -0
  36. package/.aether/commands/claude/feedback.md +96 -0
  37. package/.aether/commands/claude/flag.md +151 -0
  38. package/.aether/commands/claude/flags.md +169 -0
  39. package/.aether/commands/claude/focus.md +76 -0
  40. package/.aether/commands/claude/help.md +154 -0
  41. package/.aether/commands/claude/history.md +140 -0
  42. package/.aether/commands/claude/import-signals.md +71 -0
  43. package/.aether/commands/claude/init.md +505 -0
  44. package/.aether/commands/claude/insert-phase.md +105 -0
  45. package/.aether/commands/claude/interpret.md +278 -0
  46. package/.aether/commands/claude/lay-eggs.md +210 -0
  47. package/.aether/commands/claude/maturity.md +113 -0
  48. package/.aether/commands/claude/memory-details.md +77 -0
  49. package/.aether/commands/claude/migrate-state.md +171 -0
  50. package/.aether/commands/claude/oracle.md +642 -0
  51. package/.aether/commands/claude/organize.md +232 -0
  52. package/.aether/commands/claude/patrol.md +620 -0
  53. package/.aether/commands/claude/pause-colony.md +233 -0
  54. package/.aether/commands/claude/phase.md +115 -0
  55. package/.aether/commands/claude/pheromones.md +156 -0
  56. package/.aether/commands/claude/plan.md +693 -0
  57. package/.aether/commands/claude/preferences.md +65 -0
  58. package/.aether/commands/claude/quick.md +100 -0
  59. package/.aether/commands/claude/redirect.md +76 -0
  60. package/.aether/commands/claude/resume-colony.md +197 -0
  61. package/.aether/commands/claude/resume.md +388 -0
  62. package/.aether/commands/claude/run.md +231 -0
  63. package/.aether/commands/claude/seal.md +774 -0
  64. package/.aether/commands/claude/skill-create.md +286 -0
  65. package/.aether/commands/claude/status.md +410 -0
  66. package/.aether/commands/claude/swarm.md +349 -0
  67. package/.aether/commands/claude/tunnels.md +426 -0
  68. package/.aether/commands/claude/update.md +132 -0
  69. package/.aether/commands/claude/verify-castes.md +143 -0
  70. package/.aether/commands/claude/watch.md +239 -0
  71. package/.aether/commands/colonize.yaml +4 -0
  72. package/.aether/commands/council.yaml +205 -0
  73. package/.aether/commands/init.yaml +46 -13
  74. package/.aether/commands/insert-phase.yaml +4 -0
  75. package/.aether/commands/opencode/archaeology.md +331 -0
  76. package/.aether/commands/opencode/build.md +1168 -0
  77. package/.aether/commands/opencode/chaos.md +329 -0
  78. package/.aether/commands/opencode/colonize.md +195 -0
  79. package/.aether/commands/opencode/continue.md +1436 -0
  80. package/.aether/commands/opencode/council.md +437 -0
  81. package/.aether/commands/opencode/data-clean.md +77 -0
  82. package/.aether/commands/opencode/dream.md +260 -0
  83. package/.aether/commands/opencode/entomb.md +377 -0
  84. package/.aether/commands/opencode/export-signals.md +54 -0
  85. package/.aether/commands/opencode/feedback.md +99 -0
  86. package/.aether/commands/opencode/flag.md +149 -0
  87. package/.aether/commands/opencode/flags.md +167 -0
  88. package/.aether/commands/opencode/focus.md +73 -0
  89. package/.aether/commands/opencode/help.md +157 -0
  90. package/.aether/commands/opencode/history.md +136 -0
  91. package/.aether/commands/opencode/import-signals.md +68 -0
  92. package/.aether/commands/opencode/init.md +518 -0
  93. package/.aether/commands/opencode/insert-phase.md +111 -0
  94. package/.aether/commands/opencode/interpret.md +272 -0
  95. package/.aether/commands/opencode/lay-eggs.md +213 -0
  96. package/.aether/commands/opencode/maturity.md +108 -0
  97. package/.aether/commands/opencode/memory-details.md +83 -0
  98. package/.aether/commands/opencode/migrate-state.md +165 -0
  99. package/.aether/commands/opencode/oracle.md +593 -0
  100. package/.aether/commands/opencode/organize.md +226 -0
  101. package/.aether/commands/opencode/patrol.md +626 -0
  102. package/.aether/commands/opencode/pause-colony.md +203 -0
  103. package/.aether/commands/opencode/phase.md +113 -0
  104. package/.aether/commands/opencode/pheromones.md +162 -0
  105. package/.aether/commands/opencode/plan.md +684 -0
  106. package/.aether/commands/opencode/preferences.md +71 -0
  107. package/.aether/commands/opencode/quick.md +91 -0
  108. package/.aether/commands/opencode/redirect.md +84 -0
  109. package/.aether/commands/opencode/resume-colony.md +190 -0
  110. package/.aether/commands/opencode/resume.md +394 -0
  111. package/.aether/commands/opencode/run.md +237 -0
  112. package/.aether/commands/opencode/seal.md +452 -0
  113. package/.aether/commands/opencode/skill-create.md +63 -0
  114. package/.aether/commands/opencode/status.md +307 -0
  115. package/.aether/commands/opencode/swarm.md +15 -0
  116. package/.aether/commands/opencode/tunnels.md +400 -0
  117. package/.aether/commands/opencode/update.md +127 -0
  118. package/.aether/commands/opencode/verify-castes.md +139 -0
  119. package/.aether/commands/opencode/watch.md +227 -0
  120. package/.aether/commands/plan.yaml +53 -2
  121. package/.aether/commands/quick.yaml +104 -0
  122. package/.aether/commands/resume-colony.yaml +6 -4
  123. package/.aether/commands/resume.yaml +9 -0
  124. package/.aether/commands/run.yaml +37 -1
  125. package/.aether/commands/seal.yaml +9 -0
  126. package/.aether/commands/status.yaml +45 -1
  127. package/.aether/docs/command-playbooks/build-full.md +3 -2
  128. package/.aether/docs/command-playbooks/build-prep.md +12 -4
  129. package/.aether/docs/command-playbooks/build-verify.md +51 -0
  130. package/.aether/docs/command-playbooks/continue-advance.md +115 -6
  131. package/.aether/docs/command-playbooks/continue-full.md +1 -0
  132. package/.aether/docs/command-playbooks/continue-verify.md +33 -0
  133. package/.aether/utils/clash-detect.sh +239 -0
  134. package/.aether/utils/council.sh +425 -0
  135. package/.aether/utils/error-handler.sh +3 -3
  136. package/.aether/utils/flag.sh +23 -12
  137. package/.aether/utils/hive.sh +2 -2
  138. package/.aether/utils/hooks/clash-pre-tool-use.js +99 -0
  139. package/.aether/utils/immune.sh +508 -0
  140. package/.aether/utils/learning.sh +2 -2
  141. package/.aether/utils/merge-driver-lockfile.sh +35 -0
  142. package/.aether/utils/midden.sh +712 -0
  143. package/.aether/utils/pheromone.sh +1376 -108
  144. package/.aether/utils/queen.sh +31 -21
  145. package/.aether/utils/session.sh +264 -0
  146. package/.aether/utils/spawn-tree.sh +7 -7
  147. package/.aether/utils/spawn.sh +2 -2
  148. package/.aether/utils/state-api.sh +216 -5
  149. package/.aether/utils/swarm.sh +1 -1
  150. package/.aether/utils/worktree.sh +189 -0
  151. package/.claude/commands/ant/colonize.md +2 -0
  152. package/.claude/commands/ant/council.md +205 -0
  153. package/.claude/commands/ant/init.md +53 -14
  154. package/.claude/commands/ant/insert-phase.md +4 -0
  155. package/.claude/commands/ant/plan.md +27 -1
  156. package/.claude/commands/ant/quick.md +100 -0
  157. package/.claude/commands/ant/resume-colony.md +3 -2
  158. package/.claude/commands/ant/resume.md +9 -0
  159. package/.claude/commands/ant/run.md +37 -1
  160. package/.claude/commands/ant/seal.md +9 -0
  161. package/.claude/commands/ant/status.md +45 -1
  162. package/.opencode/commands/ant/colonize.md +2 -0
  163. package/.opencode/commands/ant/council.md +143 -0
  164. package/.opencode/commands/ant/init.md +53 -13
  165. package/.opencode/commands/ant/insert-phase.md +4 -0
  166. package/.opencode/commands/ant/plan.md +26 -1
  167. package/.opencode/commands/ant/quick.md +91 -0
  168. package/.opencode/commands/ant/resume-colony.md +3 -2
  169. package/.opencode/commands/ant/resume.md +9 -0
  170. package/.opencode/commands/ant/run.md +37 -1
  171. package/.opencode/commands/ant/status.md +2 -0
  172. package/CHANGELOG.md +116 -0
  173. package/README.md +34 -8
  174. package/bin/cli.js +103 -61
  175. package/bin/lib/banner.js +14 -0
  176. package/bin/lib/init.js +8 -7
  177. package/bin/lib/interactive-setup.js +251 -0
  178. package/bin/npx-entry.js +21 -0
  179. package/bin/npx-install.js +9 -167
  180. package/bin/validate-package.sh +23 -0
  181. package/package.json +11 -3
  182. package/.aether/docs/plans/pheromone-display-plan.md +0 -257
  183. package/.aether/schemas/example-prompt-builder.xml +0 -234
  184. package/.aether/scripts/incident-test-add.sh +0 -47
  185. package/.aether/scripts/weekly-audit.sh +0 -79
package/bin/cli.js CHANGED
@@ -1313,79 +1313,111 @@ program.on('option:quiet', () => {
1313
1313
  globalQuiet = true;
1314
1314
  });
1315
1315
 
1316
- // Install command
1317
- program
1318
- .command('install')
1319
- .description('Install commands and agents to ~/.claude/ and set up distribution hub')
1320
- .action(wrapCommand(async () => {
1321
- log(c.header(`aether-colony v${VERSION} — installing...`));
1322
-
1323
- // Sync commands to ~/.claude/commands/ant/ (with orphan cleanup)
1324
- if (!fs.existsSync(COMMANDS_SRC)) {
1325
- // Running from source repo — commands are in .claude/commands/ant/
1326
- const repoCommands = path.join(PACKAGE_DIR, '.claude', 'commands', 'ant');
1327
- if (fs.existsSync(repoCommands)) {
1328
- const result = syncDirWithCleanup(repoCommands, COMMANDS_DEST);
1329
- log(` Commands: ${result.copied} files -> ${COMMANDS_DEST}`);
1330
- if (result.removed.length > 0) {
1331
- log(` Commands: removed ${result.removed.length} stale files`);
1332
- for (const f of result.removed) log(` - ${f}`);
1333
- }
1334
- } else {
1335
- console.error(' Commands source not found. Skipping.');
1336
- }
1337
- } else {
1338
- const result = syncDirWithCleanup(COMMANDS_SRC, COMMANDS_DEST);
1316
+ /**
1317
+ * Perform a global installation of commands, agents, and hub setup.
1318
+ * Extracted so it can be called from the interactive installer.
1319
+ */
1320
+ async function performGlobalInstall() {
1321
+ log(c.header(`aether-colony v${VERSION} — installing...`));
1322
+
1323
+ // Sync commands to ~/.claude/commands/ant/ (with orphan cleanup)
1324
+ if (!fs.existsSync(COMMANDS_SRC)) {
1325
+ // Running from source repo — commands are in .claude/commands/ant/
1326
+ const repoCommands = path.join(PACKAGE_DIR, '.claude', 'commands', 'ant');
1327
+ if (fs.existsSync(repoCommands)) {
1328
+ const result = syncDirWithCleanup(repoCommands, COMMANDS_DEST);
1339
1329
  log(` Commands: ${result.copied} files -> ${COMMANDS_DEST}`);
1340
1330
  if (result.removed.length > 0) {
1341
1331
  log(` Commands: removed ${result.removed.length} stale files`);
1342
1332
  for (const f of result.removed) log(` - ${f}`);
1343
1333
  }
1334
+ } else {
1335
+ console.error(' Commands source not found. Skipping.');
1336
+ }
1337
+ } else {
1338
+ const result = syncDirWithCleanup(COMMANDS_SRC, COMMANDS_DEST);
1339
+ log(` Commands: ${result.copied} files -> ${COMMANDS_DEST}`);
1340
+ if (result.removed.length > 0) {
1341
+ log(` Commands: removed ${result.removed.length} stale files`);
1342
+ for (const f of result.removed) log(` - ${f}`);
1344
1343
  }
1344
+ }
1345
1345
 
1346
- // Sync agents to ~/.claude/agents/ant/ (with orphan cleanup)
1347
- const repoAgents = path.join(PACKAGE_DIR, '.claude', 'agents', 'ant');
1348
- if (fs.existsSync(repoAgents)) {
1349
- const result = syncDirWithCleanup(repoAgents, AGENTS_DEST);
1350
- log(` Agents (claude): ${result.copied} files -> ${AGENTS_DEST}`);
1351
- if (result.removed.length > 0) {
1352
- log(` Agents (claude): removed ${result.removed.length} stale files`);
1353
- for (const f of result.removed) log(` - ${f}`);
1354
- }
1346
+ // Sync agents to ~/.claude/agents/ant/ (with orphan cleanup)
1347
+ const repoAgents = path.join(PACKAGE_DIR, '.claude', 'agents', 'ant');
1348
+ if (fs.existsSync(repoAgents)) {
1349
+ const result = syncDirWithCleanup(repoAgents, AGENTS_DEST);
1350
+ log(` Agents (claude): ${result.copied} files -> ${AGENTS_DEST}`);
1351
+ if (result.removed.length > 0) {
1352
+ log(` Agents (claude): removed ${result.removed.length} stale files`);
1353
+ for (const f of result.removed) log(` - ${f}`);
1355
1354
  }
1355
+ }
1356
1356
 
1357
- // Sync OpenCode commands to ~/.opencode/command/ (with orphan cleanup)
1358
- const opencodeCmdsSrc = path.join(PACKAGE_DIR, '.opencode', 'commands', 'ant');
1359
- if (fs.existsSync(opencodeCmdsSrc)) {
1360
- const result = syncDirWithCleanup(opencodeCmdsSrc, OPENCODE_COMMANDS_DEST);
1361
- log(` Commands (opencode): ${result.copied} files -> ${OPENCODE_COMMANDS_DEST}`);
1362
- if (result.removed.length > 0) {
1363
- log(` Commands (opencode): removed ${result.removed.length} stale files`);
1364
- for (const f of result.removed) log(` - ${f}`);
1365
- }
1357
+ // Sync OpenCode commands to ~/.opencode/command/ (with orphan cleanup)
1358
+ const opencodeCmdsSrc = path.join(PACKAGE_DIR, '.opencode', 'commands', 'ant');
1359
+ if (fs.existsSync(opencodeCmdsSrc)) {
1360
+ const result = syncDirWithCleanup(opencodeCmdsSrc, OPENCODE_COMMANDS_DEST);
1361
+ log(` Commands (opencode): ${result.copied} files -> ${OPENCODE_COMMANDS_DEST}`);
1362
+ if (result.removed.length > 0) {
1363
+ log(` Commands (opencode): removed ${result.removed.length} stale files`);
1364
+ for (const f of result.removed) log(` - ${f}`);
1366
1365
  }
1366
+ }
1367
1367
 
1368
- // Sync OpenCode agents to ~/.opencode/agent/ (with orphan cleanup)
1369
- const opencodeAgentsSrc = path.join(PACKAGE_DIR, '.opencode', 'agents');
1370
- if (fs.existsSync(opencodeAgentsSrc)) {
1371
- const result = syncDirWithCleanup(opencodeAgentsSrc, OPENCODE_AGENTS_DEST);
1372
- log(` Agents (opencode): ${result.copied} files -> ${OPENCODE_AGENTS_DEST}`);
1373
- if (result.removed.length > 0) {
1374
- log(` Agents (opencode): removed ${result.removed.length} stale files`);
1375
- for (const f of result.removed) log(` - ${f}`);
1376
- }
1368
+ // Sync OpenCode agents to ~/.opencode/agent/ (with orphan cleanup)
1369
+ const opencodeAgentsSrc = path.join(PACKAGE_DIR, '.opencode', 'agents');
1370
+ if (fs.existsSync(opencodeAgentsSrc)) {
1371
+ const result = syncDirWithCleanup(opencodeAgentsSrc, OPENCODE_AGENTS_DEST);
1372
+ log(` Agents (opencode): ${result.copied} files -> ${OPENCODE_AGENTS_DEST}`);
1373
+ if (result.removed.length > 0) {
1374
+ log(` Agents (opencode): removed ${result.removed.length} stale files`);
1375
+ for (const f of result.removed) log(` - ${f}`);
1377
1376
  }
1377
+ }
1378
1378
 
1379
- // Set up distribution hub at ~/.aether/
1380
- log('');
1381
- log(c.colony('Setting up distribution hub...'));
1382
- setupHub();
1379
+ // Set up distribution hub at ~/.aether/
1380
+ log('');
1381
+ log(c.colony('Setting up distribution hub...'));
1382
+ setupHub();
1383
1383
 
1384
- log('');
1385
- log(c.success('Install complete.'));
1386
- log(` ${c.queen('Claude Code:')} run /ant to get started`);
1387
- log(` ${c.colony('OpenCode:')} run /ant to get started`);
1388
- log(` ${c.colony('Hub:')} ${c.dim('~/.aether/')} (for coordinated updates across repos)`);
1384
+ log('');
1385
+ log(c.success('Install complete.'));
1386
+ log(` ${c.queen('Claude Code:')} run /ant to get started`);
1387
+ log(` ${c.colony('OpenCode:')} run /ant to get started`);
1388
+ log(` ${c.colony('Hub:')} ${c.dim('~/.aether/')} (for coordinated updates across repos)`);
1389
+ }
1390
+
1391
+ // Install command
1392
+ program
1393
+ .command('install')
1394
+ .description('Install commands and agents to ~/.claude/ and set up distribution hub')
1395
+ .action(wrapCommand(performGlobalInstall));
1396
+
1397
+ // Setup command — set up Aether in the current directory from hub
1398
+ program
1399
+ .command('setup')
1400
+ .description('Set up Aether in the current directory (copies system files from hub)')
1401
+ .option('-f, --force', 'Overwrite existing setup')
1402
+ .action(wrapCommand(async (options) => {
1403
+ if (!fs.existsSync(HUB_VERSION)) {
1404
+ console.error('Aether hub not installed.');
1405
+ console.error('Run "npx aether-colony" or "aether install" to install the hub first.');
1406
+ process.exit(1);
1407
+ }
1408
+ const repoPath = process.cwd();
1409
+ log(c.header('Setting up Aether in this directory...'));
1410
+ const result = await initializeRepo(repoPath, { setupOnly: true });
1411
+ if (result.success) {
1412
+ log('');
1413
+ log(c.success('Aether is ready.'));
1414
+ log(` ${result.filesCopied} system files synced to .aether/`);
1415
+ log('');
1416
+ log(' Next steps:');
1417
+ log(' In Claude Code: /ant:init "your goal"');
1418
+ log(' Or terminal: aether init --goal "your goal"');
1419
+ log('');
1420
+ }
1389
1421
  }));
1390
1422
 
1391
1423
  // Update command
@@ -2172,10 +2204,20 @@ module.exports = {
2172
2204
  syncDirWithCleanup,
2173
2205
  syncSkillsToHub,
2174
2206
  listFilesRecursive,
2175
- cleanEmptyDirs
2207
+ cleanEmptyDirs,
2208
+ performGlobalInstall,
2209
+ run
2176
2210
  };
2177
2211
 
2212
+ /**
2213
+ * Parse CLI arguments. Called automatically when run directly,
2214
+ * or explicitly by npx-entry.js when delegating a subcommand.
2215
+ */
2216
+ function run() {
2217
+ program.parse();
2218
+ }
2219
+
2178
2220
  // Parse command line arguments only when run directly (not when required as a module)
2179
2221
  if (require.main === module) {
2180
- program.parse();
2222
+ run();
2181
2223
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared ASCII banner for Aether installers.
3
+ */
4
+
5
+ const BANNER = `
6
+ █████╗ ███████╗████████╗██╗ ██╗███████╗██████╗
7
+ ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔════╝██╔══██╗
8
+ ███████║█████╗ ██║ ███████║█████╗ ██████╔╝
9
+ ██╔══██║██╔══╝ ██║ ██╔══██║██╔══╝ ██╔══██╗
10
+ ██║ ██║███████╗ ██║ ██║ ██║███████╗██║ ██║
11
+ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
12
+ `;
13
+
14
+ module.exports = { BANNER };
package/bin/lib/init.js CHANGED
@@ -321,7 +321,7 @@ function validateInitialization(repoPath) {
321
321
  * @returns {object} Result: { success: boolean, stateFile: string|null, message: string }
322
322
  */
323
323
  async function initializeRepo(repoPath, options = {}) {
324
- const { goal, skipIfExists = false, quiet = false } = options;
324
+ const { goal, skipIfExists = false, quiet = false, setupOnly = false } = options;
325
325
 
326
326
  // Check if already initialized
327
327
  if (isInitialized(repoPath) && skipIfExists) {
@@ -411,12 +411,13 @@ locks/
411
411
  `;
412
412
  fs.writeFileSync(gitignorePath, gitignoreContent);
413
413
 
414
- // Create initial state
415
- const state = createInitialState(goal);
416
-
417
- // Write state file
418
- const stateFile = path.join(repoPath, '.aether', 'data', 'COLONY_STATE.json');
419
- fs.writeFileSync(stateFile, JSON.stringify(state, null, 2) + '\n');
414
+ // Create initial colony state (skipped in setupOnly mode)
415
+ let stateFile = null;
416
+ if (!setupOnly) {
417
+ const state = createInitialState(goal);
418
+ stateFile = path.join(repoPath, '.aether', 'data', 'COLONY_STATE.json');
419
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2) + '\n');
420
+ }
420
421
 
421
422
  // Get hub version
422
423
  const hubVersion = readJsonSafe(HUB_VERSION);
@@ -0,0 +1,251 @@
1
+ /**
2
+ * interactive-setup.js — Interactive menu for npx aether-colony
3
+ *
4
+ * Displays an environment-aware menu with three options:
5
+ * [1] Full setup — Install globally + set up this repo
6
+ * [2] Global only — Install hub, commands, and agents (~/.aether/)
7
+ * [3] Repo only — Set up Aether in this directory (.aether/)
8
+ *
9
+ * Zero npm dependencies — uses built-in Node readline.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const readline = require('readline');
16
+
17
+ const { BANNER } = require('./banner');
18
+
19
+ const VERSION = require('../../package.json').version;
20
+ const HOME_DIR = os.homedir();
21
+ const HUB_VERSION_PATH = path.join(HOME_DIR, '.aether', 'version.json');
22
+
23
+ /**
24
+ * Detect the current environment state.
25
+ * @returns {{ hubInstalled: boolean, hasAether: boolean, isProjectDir: boolean }}
26
+ */
27
+ function detectEnvironment() {
28
+ const cwd = process.cwd();
29
+
30
+ const hubInstalled = fs.existsSync(HUB_VERSION_PATH);
31
+
32
+ const hasAether = fs.existsSync(path.join(cwd, '.aether', 'aether-utils.sh'));
33
+
34
+ const isProjectDir =
35
+ fs.existsSync(path.join(cwd, '.git')) ||
36
+ fs.existsSync(path.join(cwd, 'package.json')) ||
37
+ fs.existsSync(path.join(cwd, 'Makefile')) ||
38
+ fs.existsSync(path.join(cwd, 'pyproject.toml')) ||
39
+ fs.existsSync(path.join(cwd, 'Cargo.toml'));
40
+
41
+ return { hubInstalled, hasAether, isProjectDir };
42
+ }
43
+
44
+ /**
45
+ * Choose the context-sensitive default menu option.
46
+ * @param {{ hubInstalled: boolean, hasAether: boolean, isProjectDir: boolean }} env
47
+ * @returns {1|2|3}
48
+ */
49
+ function getDefaultOption(env) {
50
+ if (!env.hubInstalled && env.isProjectDir) return 1;
51
+ if (!env.hubInstalled) return 2;
52
+ if (env.hubInstalled && !env.hasAether) return 3;
53
+ return 1;
54
+ }
55
+
56
+ /**
57
+ * Readline promise helper.
58
+ * @param {readline.Interface} rl
59
+ * @param {string} question
60
+ * @returns {Promise<string>}
61
+ */
62
+ function prompt(rl, question) {
63
+ return new Promise(resolve => rl.question(question, resolve));
64
+ }
65
+
66
+ /**
67
+ * Log a message with 🐜 ant prefix.
68
+ * @param {string} msg
69
+ */
70
+ function log(msg) {
71
+ console.log(`🐜 ${msg}`);
72
+ }
73
+
74
+ /**
75
+ * Main interactive setup entry point.
76
+ * Handles --global, --repo, --yes flags and non-TTY environments.
77
+ */
78
+ async function interactiveSetup() {
79
+ const args = process.argv.slice(2);
80
+ const flagGlobal = args.includes('--global');
81
+ const flagRepo = args.includes('--repo');
82
+ const flagYes = args.includes('--yes');
83
+
84
+ // Import performGlobalInstall lazily to avoid circular require issues
85
+ const { performGlobalInstall } = require('../cli');
86
+ const { initializeRepo } = require('./init');
87
+
88
+ const env = detectEnvironment();
89
+
90
+ // Non-TTY: auto-pick default without prompting
91
+ if (!process.stdin.isTTY && !flagGlobal && !flagRepo && !flagYes) {
92
+ const choice = getDefaultOption(env);
93
+ await executeChoice(choice, env, performGlobalInstall, initializeRepo);
94
+ return;
95
+ }
96
+
97
+ // Flag shortcuts: skip menu entirely
98
+ if (flagGlobal) {
99
+ await executeChoice(2, env, performGlobalInstall, initializeRepo);
100
+ return;
101
+ }
102
+ if (flagRepo) {
103
+ await executeChoice(3, env, performGlobalInstall, initializeRepo);
104
+ return;
105
+ }
106
+
107
+ // Already fully set up: offer refresh
108
+ if (env.hubInstalled && env.hasAether) {
109
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
110
+ try {
111
+ console.log(BANNER);
112
+ console.log(` 🐜 Aether Colony v${VERSION}\n`);
113
+ log('Aether is already set up in this directory.');
114
+
115
+ let answer;
116
+ if (flagYes) {
117
+ answer = 'y';
118
+ } else {
119
+ answer = await prompt(rl, '\n Already set up. Refresh? (y/n) [n]: ');
120
+ }
121
+
122
+ if (answer.trim().toLowerCase() === 'y') {
123
+ await executeChoice(1, env, performGlobalInstall, initializeRepo);
124
+ } else {
125
+ log('Nothing changed. Run /ant:init "your goal" to start a colony.');
126
+ }
127
+ } finally {
128
+ rl.close();
129
+ }
130
+ return;
131
+ }
132
+
133
+ // Interactive menu
134
+ const defaultChoice = flagYes ? getDefaultOption(env) : null;
135
+
136
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
137
+ try {
138
+ console.log(BANNER);
139
+ console.log(` 🐜 Aether Colony v${VERSION}\n`);
140
+
141
+ const defaultOption = getDefaultOption(env);
142
+ const option3Disabled = !env.isProjectDir;
143
+
144
+ console.log(' 🐜 What would you like to do?\n');
145
+ console.log(` [1] Full setup — Install globally + set up this repo${defaultOption === 1 ? ' (recommended)' : ''}`);
146
+ console.log(` [2] Global only — Install hub, commands, and agents (~/.aether/)${defaultOption === 2 ? ' (recommended)' : ''}`);
147
+ if (option3Disabled) {
148
+ console.log(' [3] Repo only — (not available: no project found in current directory)');
149
+ } else {
150
+ console.log(` [3] Repo only — Set up Aether in this directory (.aether/)${defaultOption === 3 ? ' (recommended)' : ''}`);
151
+ }
152
+ console.log('');
153
+
154
+ let choice;
155
+ if (flagYes) {
156
+ choice = defaultOption;
157
+ console.log(` Auto-selected [${choice}] (--yes flag)\n`);
158
+ } else {
159
+ const raw = await prompt(rl, ` Enter choice [${defaultOption}]: `);
160
+ const trimmed = raw.trim();
161
+ choice = trimmed === '' ? defaultOption : parseInt(trimmed, 10);
162
+ }
163
+
164
+ if (isNaN(choice) || choice < 1 || choice > 3) {
165
+ console.error('\n Invalid choice. Please run again and select 1, 2, or 3.\n');
166
+ process.exit(1);
167
+ }
168
+
169
+ if (choice === 3 && option3Disabled) {
170
+ console.error('\n Option 3 is not available outside a project directory.\n');
171
+ process.exit(1);
172
+ }
173
+
174
+ await executeChoice(choice, env, performGlobalInstall, initializeRepo);
175
+ } finally {
176
+ rl.close();
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Execute the selected menu option.
182
+ * @param {1|2|3} choice
183
+ * @param {{ hubInstalled: boolean, hasAether: boolean, isProjectDir: boolean }} env
184
+ * @param {Function} performGlobalInstall
185
+ * @param {Function} initializeRepo
186
+ */
187
+ async function executeChoice(choice, env, performGlobalInstall, initializeRepo) {
188
+ const cwd = process.cwd();
189
+
190
+ if (choice === 1) {
191
+ log('Running full setup...');
192
+ await performGlobalInstall();
193
+ const result = await initializeRepo(cwd, { setupOnly: true });
194
+ printRepoSuccess(result);
195
+ } else if (choice === 2) {
196
+ log('Running global install...');
197
+ await performGlobalInstall();
198
+ printGlobalSuccess();
199
+ } else if (choice === 3) {
200
+ if (!env.hubInstalled) {
201
+ console.error('\n Aether hub not installed. Run without --repo to install globally first.\n');
202
+ process.exit(1);
203
+ }
204
+ log('Setting up this repository...');
205
+ const result = await initializeRepo(cwd, { setupOnly: true });
206
+ printRepoSuccess(result);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Print success message after global install.
212
+ */
213
+ function printGlobalSuccess() {
214
+ console.log('');
215
+ console.log(' 🐜 Global install complete!');
216
+ console.log('');
217
+ console.log(' Next steps:');
218
+ console.log(' cd into a project, then run: npx aether-colony --repo');
219
+ console.log(' Or: aether init --goal "your goal"');
220
+ console.log(' 🐜🐜🐜')
221
+ console.log('');
222
+ }
223
+
224
+ /**
225
+ * Print success message after repo setup.
226
+ * @param {{ success: boolean, filesCopied?: number }} result
227
+ */
228
+ function printRepoSuccess(result) {
229
+ if (!result || !result.success) {
230
+ console.error('\n Repo setup failed. Check that the Aether hub is installed.\n');
231
+ return;
232
+ }
233
+ console.log('');
234
+ console.log(' 🐜 Aether is ready!');
235
+ if (result.filesCopied != null) {
236
+ console.log(` ${result.filesCopied} system files synced to .aether/`);
237
+ }
238
+ console.log('');
239
+ console.log(' Next steps:');
240
+ console.log(' In Claude Code: /ant:init "your goal"');
241
+ console.log(' Or terminal: aether init --goal "your goal"');
242
+ console.log(' 🐜🐜🐜');
243
+ console.log('');
244
+ }
245
+
246
+ module.exports = {
247
+ interactiveSetup,
248
+ detectEnvironment,
249
+ getDefaultOption,
250
+ executeChoice,
251
+ };
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * npx-entry.js — Entry point for `npx aether-colony`
4
+ *
5
+ * With no subcommand: launches interactive setup menu.
6
+ * With a subcommand (e.g. `npx aether-colony install`): delegates to full CLI.
7
+ */
8
+
9
+ const args = process.argv.slice(2);
10
+
11
+ // If a subcommand is provided (not a flag), delegate to the full CLI
12
+ if (args.length > 0 && !args[0].startsWith('-')) {
13
+ const { run } = require('./cli.js');
14
+ run();
15
+ } else {
16
+ const { interactiveSetup } = require('./lib/interactive-setup');
17
+ interactiveSetup().catch(err => {
18
+ console.error('Setup failed:', err.message);
19
+ process.exit(1);
20
+ });
21
+ }