orbital-command 0.3.0 → 1.0.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 (160) hide show
  1. package/README.md +67 -42
  2. package/bin/commands/config.js +19 -0
  3. package/bin/commands/events.js +40 -0
  4. package/bin/commands/launch.js +126 -0
  5. package/bin/commands/manifest.js +283 -0
  6. package/bin/commands/registry.js +104 -0
  7. package/bin/commands/update.js +24 -0
  8. package/bin/lib/helpers.js +229 -0
  9. package/bin/orbital.js +95 -870
  10. package/dist/assets/Landing-CfQdHR0N.js +11 -0
  11. package/dist/assets/PrimitivesConfig-DThSipFy.js +32 -0
  12. package/dist/assets/QualityGates-B4kxM5UU.js +26 -0
  13. package/dist/assets/SessionTimeline-Bz1iZnmg.js +1 -0
  14. package/dist/assets/Settings-DLcZwbCT.js +12 -0
  15. package/dist/assets/SourceControl-BMNIz7Lt.js +36 -0
  16. package/dist/assets/WorkflowVisualizer-CxuSBOYu.js +69 -0
  17. package/dist/assets/{arrow-down-CPy85_J6.js → arrow-down-DVPp6_qp.js} +1 -1
  18. package/dist/assets/bot-NFaJBDn_.js +6 -0
  19. package/dist/assets/{charts-DbDg0Psc.js → charts-LGLb8hyU.js} +1 -1
  20. package/dist/assets/{circle-x-Cwz6ZQDV.js → circle-x-IsFCkBZu.js} +1 -1
  21. package/dist/assets/{file-text-C46Xr65c.js → file-text-J1cebZXF.js} +1 -1
  22. package/dist/assets/{globe-Cn2yNZUD.js → globe-WzeyHsUc.js} +1 -1
  23. package/dist/assets/index-BdJ57EhC.css +1 -0
  24. package/dist/assets/index-o4ScMAuR.js +349 -0
  25. package/dist/assets/{key-OPaNTWJ5.js → key-CKR8JJSj.js} +1 -1
  26. package/dist/assets/{minus-GMsbpKym.js → minus-CHBsJyjp.js} +1 -1
  27. package/dist/assets/radio-xqZaR-Uk.js +6 -0
  28. package/dist/assets/rocket-D_xvvNG6.js +6 -0
  29. package/dist/assets/{shield-DwAFkDYI.js → shield-TdB1yv_a.js} +1 -1
  30. package/dist/assets/useSocketListener-0L5yiN5i.js +1 -0
  31. package/dist/assets/useWorkflowEditor-CqeRWVQX.js +11 -0
  32. package/dist/assets/workflow-constants-Rw-GmgHZ.js +6 -0
  33. package/dist/assets/zap-C9wqYMpl.js +6 -0
  34. package/dist/index.html +3 -3
  35. package/dist/server/server/__tests__/data-routes.test.js +2 -0
  36. package/dist/server/server/__tests__/scope-routes.test.js +1 -0
  37. package/dist/server/server/config-migrator.js +0 -3
  38. package/dist/server/server/config.js +35 -6
  39. package/dist/server/server/database.js +0 -22
  40. package/dist/server/server/index.js +26 -814
  41. package/dist/server/server/init.js +32 -399
  42. package/dist/server/server/launch.js +1 -1
  43. package/dist/server/server/parsers/event-parser.js +4 -1
  44. package/dist/server/server/project-context.js +19 -9
  45. package/dist/server/server/project-manager.js +6 -6
  46. package/dist/server/server/routes/aggregate-routes.js +871 -0
  47. package/dist/server/server/routes/config-routes.js +41 -88
  48. package/dist/server/server/routes/data-routes.js +5 -15
  49. package/dist/server/server/routes/dispatch-routes.js +24 -8
  50. package/dist/server/server/routes/manifest-routes.js +1 -1
  51. package/dist/server/server/routes/scope-routes.js +10 -7
  52. package/dist/server/server/schema.js +1 -0
  53. package/dist/server/server/services/batch-orchestrator.js +17 -3
  54. package/dist/server/server/services/config-service.js +10 -1
  55. package/dist/server/server/services/scope-service.js +7 -7
  56. package/dist/server/server/services/sprint-orchestrator.js +24 -11
  57. package/dist/server/server/services/sprint-service.js +2 -2
  58. package/dist/server/server/uninstall.js +195 -0
  59. package/dist/server/server/update.js +212 -0
  60. package/dist/server/server/utils/dispatch-utils.js +8 -6
  61. package/dist/server/server/utils/flag-builder.js +54 -0
  62. package/dist/server/server/utils/json-fields.js +14 -0
  63. package/dist/server/server/utils/json-fields.test.js +73 -0
  64. package/dist/server/server/utils/route-helpers.js +37 -0
  65. package/dist/server/server/utils/route-helpers.test.js +115 -0
  66. package/dist/server/server/watchers/event-watcher.js +28 -13
  67. package/dist/server/server/wizard/config-editor.js +4 -4
  68. package/dist/server/server/wizard/doctor.js +2 -2
  69. package/dist/server/server/wizard/index.js +224 -39
  70. package/dist/server/server/wizard/phases/welcome.js +1 -4
  71. package/dist/server/server/wizard/ui.js +6 -7
  72. package/dist/server/shared/api-types.js +80 -1
  73. package/dist/server/shared/workflow-engine.js +1 -1
  74. package/package.json +20 -20
  75. package/schemas/orbital.config.schema.json +1 -19
  76. package/scripts/postinstall.js +6 -42
  77. package/scripts/release.sh +53 -0
  78. package/server/__tests__/data-routes.test.ts +2 -0
  79. package/server/__tests__/scope-routes.test.ts +1 -0
  80. package/server/config-migrator.ts +0 -3
  81. package/server/config.ts +39 -11
  82. package/server/database.ts +0 -26
  83. package/server/global-config.ts +4 -0
  84. package/server/index.ts +29 -894
  85. package/server/init.ts +32 -443
  86. package/server/launch.ts +1 -1
  87. package/server/parsers/event-parser.ts +4 -1
  88. package/server/project-context.ts +26 -10
  89. package/server/project-manager.ts +5 -6
  90. package/server/routes/aggregate-routes.ts +968 -0
  91. package/server/routes/config-routes.ts +41 -81
  92. package/server/routes/data-routes.ts +7 -16
  93. package/server/routes/dispatch-routes.ts +29 -8
  94. package/server/routes/manifest-routes.ts +1 -1
  95. package/server/routes/scope-routes.ts +12 -7
  96. package/server/schema.ts +1 -0
  97. package/server/services/batch-orchestrator.ts +18 -2
  98. package/server/services/config-service.ts +10 -1
  99. package/server/services/scope-service.ts +6 -6
  100. package/server/services/sprint-orchestrator.ts +24 -9
  101. package/server/services/sprint-service.ts +2 -2
  102. package/server/uninstall.ts +214 -0
  103. package/server/update.ts +263 -0
  104. package/server/utils/dispatch-utils.ts +8 -6
  105. package/server/utils/flag-builder.ts +56 -0
  106. package/server/utils/json-fields.test.ts +83 -0
  107. package/server/utils/json-fields.ts +14 -0
  108. package/server/utils/route-helpers.test.ts +144 -0
  109. package/server/utils/route-helpers.ts +38 -0
  110. package/server/watchers/event-watcher.ts +24 -12
  111. package/server/wizard/config-editor.ts +4 -4
  112. package/server/wizard/doctor.ts +2 -2
  113. package/server/wizard/index.ts +291 -40
  114. package/server/wizard/phases/welcome.ts +1 -5
  115. package/server/wizard/ui.ts +6 -7
  116. package/shared/api-types.ts +106 -0
  117. package/shared/workflow-engine.ts +1 -1
  118. package/templates/agents/QUICK-REFERENCE.md +1 -0
  119. package/templates/agents/README.md +1 -0
  120. package/templates/agents/SKILL-TRIGGERS.md +11 -0
  121. package/templates/agents/green-team/deep-dive.md +361 -0
  122. package/templates/hooks/end-session.sh +1 -0
  123. package/templates/hooks/init-session.sh +1 -0
  124. package/templates/hooks/scope-commit-logger.sh +2 -2
  125. package/templates/hooks/scope-create-gate.sh +2 -4
  126. package/templates/hooks/scope-gate.sh +4 -6
  127. package/templates/hooks/scope-helpers.sh +10 -1
  128. package/templates/hooks/scope-lifecycle-gate.sh +14 -5
  129. package/templates/hooks/scope-prepare.sh +1 -1
  130. package/templates/hooks/scope-transition.sh +14 -6
  131. package/templates/hooks/time-tracker.sh +2 -5
  132. package/templates/orbital.config.json +1 -4
  133. package/templates/presets/development.json +4 -4
  134. package/templates/presets/gitflow.json +7 -0
  135. package/templates/prompts/README.md +23 -0
  136. package/templates/prompts/deep-dive-audit.md +94 -0
  137. package/templates/quick/rules.md +56 -5
  138. package/templates/skills/git-commit/SKILL.md +21 -6
  139. package/templates/skills/git-dev/SKILL.md +8 -4
  140. package/templates/skills/git-main/SKILL.md +8 -4
  141. package/templates/skills/git-production/SKILL.md +6 -3
  142. package/templates/skills/git-staging/SKILL.md +6 -3
  143. package/templates/skills/scope-fix-review/SKILL.md +8 -4
  144. package/templates/skills/scope-implement/SKILL.md +13 -5
  145. package/templates/skills/scope-post-review/SKILL.md +16 -4
  146. package/templates/skills/scope-pre-review/SKILL.md +6 -2
  147. package/dist/assets/PrimitivesConfig-CrmQXYh4.js +0 -32
  148. package/dist/assets/QualityGates-BbasOsF3.js +0 -21
  149. package/dist/assets/SessionTimeline-CGeJsVvy.js +0 -1
  150. package/dist/assets/Settings-oiM496mc.js +0 -12
  151. package/dist/assets/SourceControl-B1fP2nJL.js +0 -41
  152. package/dist/assets/WorkflowVisualizer-CWLYf-f0.js +0 -74
  153. package/dist/assets/formatDistanceToNow-BMqsSP44.js +0 -1
  154. package/dist/assets/index-Aj4sV8Al.css +0 -1
  155. package/dist/assets/index-Bc9dK3MW.js +0 -354
  156. package/dist/assets/useWorkflowEditor-BJkTX_NR.js +0 -16
  157. package/dist/assets/zap-DfbUoOty.js +0 -11
  158. package/dist/server/server/services/telemetry-service.js +0 -143
  159. package/server/services/telemetry-service.ts +0 -195
  160. /package/{shared/default-workflow.json → templates/presets/default.json} +0 -0
package/bin/orbital.js CHANGED
@@ -2,876 +2,118 @@
2
2
 
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
- import { spawn, execFileSync } from 'child_process';
6
- import crypto from 'crypto';
7
- import { fileURLToPath } from 'url';
5
+ import {
6
+ detectProjectRoot,
7
+ getPackageVersion,
8
+ isGitRepo,
9
+ requireGitRepo,
10
+ loadRegistry,
11
+ writeRegistryAtomic,
12
+ loadSharedModule,
13
+ loadWizardModule,
14
+ orbitalSetupDone,
15
+ stampTemplateVersion,
16
+ printHelp,
17
+ } from './lib/helpers.js';
18
+
19
+ import { cmdLaunchOrDev, cmdBuild } from './commands/launch.js';
20
+ import { cmdStatus, cmdValidate, cmdPin, cmdUnpin, cmdPins, cmdDiff, cmdReset } from './commands/manifest.js';
21
+ import { cmdRegister, cmdUnregister, cmdProjects } from './commands/registry.js';
22
+ import { cmdConfig, cmdDoctor } from './commands/config.js';
23
+ import { cmdEmit } from './commands/events.js';
24
+ import { cmdUpdate, cmdUninstall } from './commands/update.js';
8
25
 
9
26
  // ---------------------------------------------------------------------------
10
- // Paths
27
+ // Hub Flow — the primary entry point
11
28
  // ---------------------------------------------------------------------------
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = path.dirname(__filename);
14
- const PACKAGE_ROOT = path.resolve(__dirname, '..');
15
29
 
16
- /**
17
- * Resolve a package binary (e.g. 'tsx', 'vite') to an absolute path.
18
- * Checks PACKAGE_ROOT/node_modules/.bin first (global installs, non-hoisted),
19
- * then the parent node_modules/.bin (hoisted local installs where deps are
20
- * lifted to <project>/node_modules/.bin/). Returns null to fall back to npx.
21
- */
22
- function resolveBin(name) {
23
- const local = path.join(PACKAGE_ROOT, 'node_modules', '.bin', name);
24
- if (fs.existsSync(local)) return local;
25
- const hoisted = path.join(PACKAGE_ROOT, '..', '.bin', name);
26
- if (fs.existsSync(hoisted)) return path.resolve(hoisted);
27
- return null;
28
- }
29
-
30
- // ---------------------------------------------------------------------------
31
- // CLI Helpers
32
- // ---------------------------------------------------------------------------
33
-
34
- function getFlagValue(args, flag) {
35
- const idx = args.indexOf(flag);
36
- return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
37
- }
38
-
39
- function detectProjectRoot() {
40
- try {
41
- return execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim();
42
- } catch {
43
- return process.cwd();
44
- }
45
- }
46
-
47
- function loadConfig(projectRoot) {
48
- const configPath = path.join(projectRoot, '.claude', 'orbital.config.json');
49
- if (fs.existsSync(configPath)) {
50
- try {
51
- return JSON.parse(fs.readFileSync(configPath, 'utf8'));
52
- } catch (err) {
53
- console.warn(`Warning: could not parse ${configPath}: ${err.message}`);
54
- }
55
- }
56
- return { serverPort: 4444, clientPort: 4445 };
57
- }
58
-
59
- function getPackageVersion() {
60
- try {
61
- const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
62
- return pkg.version || '0.0.0';
63
- } catch {
64
- return '0.0.0';
65
- }
66
- }
67
-
68
- function stampTemplateVersion(projectRoot) {
69
- const configPath = path.join(projectRoot, '.claude', 'orbital.config.json');
70
- if (!fs.existsSync(configPath)) return;
71
-
72
- try {
73
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
74
- const version = getPackageVersion();
75
- if (config.templateVersion !== version) {
76
- config.templateVersion = version;
77
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
78
- console.log(` Stamped templateVersion: ${version}`);
79
- }
80
- } catch { /* ignore malformed config */ }
81
- }
82
-
83
- function checkTemplatesStaleness(projectRoot) {
84
- const configPath = path.join(projectRoot, '.claude', 'orbital.config.json');
85
- if (!fs.existsSync(configPath)) return;
86
-
87
- try {
88
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
89
- const projectVersion = config.templateVersion || null;
90
- const packageVersion = getPackageVersion();
91
-
92
- if (projectVersion && projectVersion === packageVersion) return;
93
-
94
- if (projectVersion) {
95
- console.log(`\n ⚠ Templates outdated (project: v${projectVersion}, package: v${packageVersion})`);
96
- } else {
97
- console.log(`\n ⚠ Templates have no version stamp`);
98
- }
99
- console.log(` Run \`orbital update\` to refresh templates.\n`);
100
- } catch { /* ignore malformed config */ }
101
- }
102
-
103
- function openBrowser(url) {
104
- const platform = process.platform;
105
- if (platform === 'darwin') {
106
- spawn('open', [url], { detached: true, stdio: 'ignore' }).unref();
107
- } else if (platform === 'win32') {
108
- spawn('cmd', ['/c', 'start', url], { detached: true, stdio: 'ignore' }).unref();
109
- } else {
110
- spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref();
111
- }
112
- }
113
-
114
- // ---------------------------------------------------------------------------
115
- // Shared Module Loader
116
- // ---------------------------------------------------------------------------
117
-
118
- /**
119
- * Load the shared init module. Tries compiled JS first (production/global
120
- * installs via npm publish), then TypeScript source (dev via tsx).
121
- */
122
- async function loadSharedModule() {
123
- try {
124
- // Production: compiled JS (tsconfig.server.json outputs to dist/server/server/)
125
- return await import('../dist/server/server/init.js');
126
- } catch {
127
- try {
128
- // Dev: TypeScript source loaded via tsx
129
- return await import('../server/init.js');
130
- } catch {
131
- console.error('Error: Orbital Command server module not found.');
132
- console.error('Try reinstalling: npm install -g orbital-command');
133
- console.error('For local development: npm run build:server');
134
- process.exit(1);
135
- }
136
- }
137
- }
138
-
139
- /**
140
- * Load the interactive wizard module.
141
- */
142
- async function loadWizardModule() {
143
- try {
144
- return await import('../dist/server/server/wizard/index.js');
145
- } catch {
146
- try {
147
- return await import('../server/wizard/index.js');
148
- } catch {
149
- console.error('Error: Wizard module not found.');
150
- console.error('Try reinstalling: npm install -g orbital-command');
151
- process.exit(1);
152
- }
153
- }
154
- }
155
-
156
- // ---------------------------------------------------------------------------
157
- // Commands
158
- // ---------------------------------------------------------------------------
159
-
160
- function orbitalSetupDone() {
161
- return fs.existsSync(path.join(ORBITAL_HOME, 'config.json'));
162
- }
163
-
164
- function autoRegisterProject(projectRoot) {
165
- if (!fs.existsSync(ORBITAL_HOME)) fs.mkdirSync(ORBITAL_HOME, { recursive: true });
166
- const registry = loadRegistry();
167
- if (!(registry.projects || []).some(p => p.path === projectRoot)) {
168
- cmdRegister([projectRoot]);
169
- }
170
- }
171
-
172
- async function cmdInit(args) {
173
- const isYes = args.includes('--yes') || args.includes('-y');
174
- const isInteractive = process.stdout.isTTY && !process.env.CI && !isYes;
175
- const projectRoot = detectProjectRoot();
176
-
177
- if (isInteractive) {
178
- const wiz = await loadWizardModule();
179
- const version = getPackageVersion();
180
-
181
- // If Orbital hasn't been set up yet, run Phase 1 first
182
- if (!orbitalSetupDone()) {
183
- await wiz.runSetupWizard(version);
184
- }
185
-
186
- // Phase 2: project setup for current directory
187
- await wiz.runProjectSetup(projectRoot, version, args);
188
- stampTemplateVersion(projectRoot);
30
+ async function runHubFlow() {
31
+ if (!process.stdout.isTTY || process.env.CI) {
32
+ printHelp();
189
33
  return;
190
34
  }
191
35
 
192
- // Non-interactive / --yes fallback (existing behavior preserved)
193
- const force = args.includes('--force');
194
- const globalPrivate = loadRegistry().privateMode === true;
195
- const isPrivate = args.includes('--private') || globalPrivate;
196
- const preset = getFlagValue(args, '--preset');
197
- const projectName = getFlagValue(args, '--project-name');
198
- const serverPort = getFlagValue(args, '--server-port');
199
- const clientPort = getFlagValue(args, '--client-port');
200
-
201
- const { runInit } = await loadSharedModule();
202
- runInit(projectRoot, {
203
- force,
204
- preset: preset || undefined,
205
- projectName: projectName || undefined,
206
- serverPort: serverPort ? Number(serverPort) : undefined,
207
- clientPort: clientPort ? Number(clientPort) : undefined,
208
- });
209
- stampTemplateVersion(projectRoot);
210
-
211
- if (isPrivate) {
212
- const configPath = path.join(projectRoot, '.claude', 'orbital.config.json');
213
- if (fs.existsSync(configPath)) {
214
- try {
215
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
216
- if (!config.telemetry) config.telemetry = {};
217
- config.telemetry.enabled = false;
218
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
219
-
220
- } catch { /* leave config as-is */ }
221
- }
222
- }
223
-
224
- autoRegisterProject(projectRoot);
225
- console.log(`Run \`orbital launch\` to start the dashboard.\n`);
226
- }
227
-
228
- function cmdLaunchOrDev(forceViteFlag) {
229
- const shouldOpen = process.argv.includes('--open');
230
- const forceVite = forceViteFlag || process.argv.includes('--vite');
231
- const projectRoot = detectProjectRoot();
232
- const config = loadConfig(projectRoot);
233
- const serverPort = config.serverPort || 4444;
234
- const clientPort = config.clientPort || 4445;
235
-
236
- autoRegisterProject(projectRoot);
237
-
238
- // Detect packaged mode: dist/index.html exists → serve pre-built frontend
239
- const hasPrebuiltFrontend = fs.existsSync(path.join(PACKAGE_ROOT, 'dist', 'index.html'));
240
- const useVite = forceVite || !hasPrebuiltFrontend;
241
-
242
- console.log(`\nOrbital Command — ${useVite ? 'dev' : 'launch'}`);
243
- console.log(`Project root: ${projectRoot}`);
244
- if (useVite) {
245
- console.log(`Server: http://localhost:${serverPort}`);
246
- console.log(`Client: http://localhost:${clientPort} (Vite dev server)\n`);
247
- } else {
248
- console.log(`Dashboard: http://localhost:${serverPort}\n`);
249
- }
250
-
251
- checkTemplatesStaleness(projectRoot);
252
-
253
- const env = {
254
- ...process.env,
255
- ORBITAL_LAUNCH_MODE: 'central',
256
- ORBITAL_AUTO_REGISTER: projectRoot,
257
- ORBITAL_SERVER_PORT: String(serverPort),
258
- };
259
-
260
- const serverScript = path.join(PACKAGE_ROOT, 'server', 'launch.ts');
261
- const tsxBin = resolveBin('tsx');
262
- const serverProcess = tsxBin
263
- ? spawn(tsxBin, ['watch', serverScript],
264
- { stdio: 'inherit', env, cwd: PACKAGE_ROOT })
265
- : spawn('npx', ['tsx', 'watch', serverScript],
266
- { stdio: 'inherit', env, cwd: PACKAGE_ROOT });
267
-
268
- let viteProcess = null;
269
-
270
- if (useVite) {
271
- const viteBin = resolveBin('vite');
272
- viteProcess = viteBin
273
- ? spawn(viteBin, ['--config', path.join(PACKAGE_ROOT, 'vite.config.ts'), '--port', String(clientPort)],
274
- { stdio: 'inherit', env, cwd: PACKAGE_ROOT })
275
- : spawn('npx', ['vite', '--config', path.join(PACKAGE_ROOT, 'vite.config.ts'), '--port', String(clientPort)],
276
- { stdio: 'inherit', env, cwd: PACKAGE_ROOT });
277
- }
278
-
279
- const dashboardUrl = useVite
280
- ? `http://localhost:${clientPort}`
281
- : `http://localhost:${serverPort}`;
282
-
283
- if (shouldOpen) {
284
- setTimeout(() => openBrowser(dashboardUrl), 2000);
285
- }
286
-
287
- let exiting = false;
288
-
289
- function cleanup() {
290
- if (exiting) return;
291
- exiting = true;
292
- serverProcess.kill();
293
- if (viteProcess) viteProcess.kill();
294
- process.exit(0);
295
- }
296
- process.on('SIGINT', cleanup);
297
- process.on('SIGTERM', cleanup);
298
-
299
- serverProcess.on('exit', (code) => {
300
- if (exiting) return;
301
- exiting = true;
302
- console.log(`Server exited with code ${code}`);
303
- if (viteProcess) viteProcess.kill();
304
- process.exit(code || 0);
305
- });
306
- if (viteProcess) {
307
- viteProcess.on('exit', (code) => {
308
- if (exiting) return;
309
- exiting = true;
310
- console.log(`Vite exited with code ${code}`);
311
- serverProcess.kill();
312
- process.exit(code || 0);
313
- });
314
- }
315
- }
316
-
317
- function cmdBuild() {
318
- console.log(`\nOrbital Command — build\n`);
319
-
320
- const viteBin = resolveBin('vite');
321
- const buildProcess = viteBin
322
- ? spawn(viteBin, ['build', '--config', path.join(PACKAGE_ROOT, 'vite.config.ts')],
323
- { stdio: 'inherit', cwd: PACKAGE_ROOT })
324
- : spawn('npx', ['vite', 'build', '--config', path.join(PACKAGE_ROOT, 'vite.config.ts')],
325
- { stdio: 'inherit', cwd: PACKAGE_ROOT });
326
-
327
- buildProcess.on('exit', (code) => {
328
- process.exit(code || 0);
329
- });
330
- }
331
-
332
- function cmdEmit(args) {
333
- const type = args[0];
334
- const jsonStr = args.slice(1).join(' ');
335
-
336
- if (!type) {
337
- console.error('Usage: orbital emit <TYPE> <JSON>');
338
- process.exit(1);
339
- }
340
-
341
- const projectRoot = detectProjectRoot();
342
- const eventsDir = path.join(projectRoot, '.claude', 'orbital-events');
343
- if (!fs.existsSync(eventsDir)) fs.mkdirSync(eventsDir, { recursive: true });
344
-
345
- let payload;
346
- try {
347
- payload = jsonStr ? JSON.parse(jsonStr) : {};
348
- } catch (err) {
349
- console.error(`Invalid JSON: ${err.message}`);
350
- process.exit(1);
351
- }
352
-
353
- const eventId = crypto.randomUUID();
354
- const event = {
355
- ...payload,
356
- id: eventId,
357
- type,
358
- timestamp: new Date().toISOString(),
359
- };
360
-
361
- const filePath = path.join(eventsDir, `${eventId}.json`);
362
- fs.writeFileSync(filePath, JSON.stringify(event, null, 2) + '\n', 'utf8');
363
-
364
- console.log(`Event emitted: ${type} (${eventId})`);
365
- console.log(` File: ${path.relative(projectRoot, filePath)}`);
366
- }
367
-
368
- async function cmdUpdate(args) {
369
- const projectRoot = detectProjectRoot();
370
- const dryRun = args.includes('--dry-run');
371
-
372
- const { runUpdate } = await loadSharedModule();
373
- runUpdate(projectRoot, { dryRun });
374
-
375
- if (!dryRun) stampTemplateVersion(projectRoot);
376
- }
377
-
378
- async function cmdUninstall(args) {
379
- const projectRoot = detectProjectRoot();
380
- const dryRun = args.includes('--dry-run');
381
- const keepConfig = args.includes('--keep-config');
382
-
383
- const { runUninstall } = await loadSharedModule();
384
- runUninstall(projectRoot, { dryRun, keepConfig });
385
- }
386
-
387
- // ---------------------------------------------------------------------------
388
- // Manifest management commands
389
- // ---------------------------------------------------------------------------
390
-
391
- async function cmdValidate() {
392
- const projectRoot = detectProjectRoot();
393
-
394
- const mod = await loadSharedModule();
395
- const report = mod.validate(projectRoot, getPackageVersion());
396
- console.log(mod.formatValidationReport(report));
397
- process.exit(report.errors > 0 ? 1 : 0);
398
- }
399
-
400
- async function cmdStatus() {
401
- const projectRoot = detectProjectRoot();
402
-
403
- const mod = await loadSharedModule();
404
- const manifest = mod.loadManifest(projectRoot);
405
-
406
- if (!manifest) {
407
- console.log('\nNo manifest found. Run `orbital init` or `orbital update` to create one.\n');
408
- return;
409
- }
410
-
411
- const claudeDir = path.join(projectRoot, '.claude');
412
- mod.refreshFileStatuses(manifest, claudeDir);
413
-
414
- const summary = mod.summarizeManifest(manifest);
415
- const packageVersion = getPackageVersion();
416
- const needsUpdate = manifest.packageVersion !== packageVersion;
417
-
418
- console.log(`\nOrbital Command v${packageVersion}${needsUpdate ? ` (installed: ${manifest.packageVersion} → needs update)` : ''}\n`);
419
-
420
- for (const [type, counts] of Object.entries(summary.byType)) {
421
- const parts = [];
422
- if (counts.synced) parts.push(`${counts.synced} synced`);
423
- if (counts.outdated) parts.push(`${counts.outdated} outdated`);
424
- if (counts.modified) parts.push(`${counts.modified} modified`);
425
- if (counts.pinned) parts.push(`${counts.pinned} pinned`);
426
- if (counts.userOwned) parts.push(`${counts.userOwned} user-owned`);
427
- console.log(` ${type.padEnd(16)} ${parts.join(', ')}`);
428
- }
429
-
430
- // Show outdated files (template moved ahead, user hasn't touched)
431
- const outdated = Object.entries(manifest.files)
432
- .filter(([, r]) => r.status === 'outdated');
433
- if (outdated.length > 0) {
434
- console.log('\n Outdated files (safe to update):');
435
- for (const [file] of outdated) {
436
- console.log(` ${file}`);
437
- }
438
- }
439
-
440
- // Show modified files (user edited)
441
- const modified = Object.entries(manifest.files)
442
- .filter(([, r]) => r.status === 'modified');
443
- if (modified.length > 0) {
444
- console.log('\n Modified files (user edited):');
445
- for (const [file] of modified) {
446
- console.log(` ${file} (run 'orbital diff ${file}')`);
447
- }
448
- }
449
-
450
- // Show pinned files
451
- const pinned = Object.entries(manifest.files)
452
- .filter(([, r]) => r.status === 'pinned');
453
- if (pinned.length > 0) {
454
- console.log('\n Pinned files:');
455
- for (const [file, record] of pinned) {
456
- const reason = record.pinnedReason ? `"${record.pinnedReason}"` : '';
457
- console.log(` ${file} ${reason}`);
458
- }
459
- }
460
-
461
- console.log();
462
- }
463
-
464
- async function cmdPin(args) {
465
- const projectRoot = detectProjectRoot();
466
- const filePath = args.find(a => !a.startsWith('--'));
467
- const reasonIdx = args.indexOf('--reason');
468
- const reason = reasonIdx !== -1 ? args[reasonIdx + 1] : undefined;
469
-
470
- if (!filePath) {
471
- console.error('Usage: orbital pin <relative-path> [--reason "..."]');
472
- process.exit(1);
473
- }
474
-
475
- const mod = await loadSharedModule();
476
- const manifest = mod.loadManifest(projectRoot);
477
- if (!manifest) {
478
- console.error('No manifest found. Run `orbital init` first.');
479
- process.exit(1);
480
- }
481
-
482
- const record = manifest.files[filePath];
483
- if (!record) {
484
- console.error(`File not tracked: ${filePath}`);
485
- process.exit(1);
486
- }
487
- if (record.origin === 'user') {
488
- console.error(`Cannot pin user-owned file: ${filePath}`);
489
- process.exit(1);
490
- }
491
-
492
- record.status = 'pinned';
493
- record.pinnedAt = new Date().toISOString();
494
- if (reason) record.pinnedReason = reason;
495
-
496
- mod.saveManifest(projectRoot, manifest);
497
- console.log(`Pinned: ${filePath}${reason ? ` (${reason})` : ''}`);
498
- }
499
-
500
- async function cmdUnpin(args) {
501
- const projectRoot = detectProjectRoot();
502
- const filePath = args[0];
503
-
504
- if (!filePath) {
505
- console.error('Usage: orbital unpin <relative-path>');
506
- process.exit(1);
507
- }
508
-
509
- const mod = await loadSharedModule();
510
- const manifest = mod.loadManifest(projectRoot);
511
- if (!manifest) {
512
- console.error('No manifest found. Run `orbital init` first.');
513
- process.exit(1);
514
- }
515
-
516
- const record = manifest.files[filePath];
517
- if (!record || record.status !== 'pinned') {
518
- console.error(`File is not pinned: ${filePath}`);
519
- process.exit(1);
520
- }
521
-
522
- // Clear pinned state, then recompute
523
- record.status = 'synced';
524
- delete record.pinnedAt;
525
- delete record.pinnedReason;
526
-
527
- const absPath = path.join(projectRoot, '.claude', filePath);
528
- if (fs.existsSync(absPath)) {
529
- const currentHash = mod.hashFile(absPath);
530
- record.status = mod.computeFileStatus(record, currentHash);
531
- } else {
532
- record.status = 'synced';
533
- }
534
-
535
- mod.saveManifest(projectRoot, manifest);
536
- console.log(`Unpinned: ${filePath} (now ${record.status})`);
537
- }
538
-
539
- async function cmdPins() {
540
- const projectRoot = detectProjectRoot();
36
+ const wiz = await loadWizardModule();
37
+ const hubVersion = getPackageVersion();
541
38
 
542
- const mod = await loadSharedModule();
543
- const manifest = mod.loadManifest(projectRoot);
544
- if (!manifest) {
545
- console.error('No manifest found. Run `orbital init` first.');
546
- process.exit(1);
547
- }
548
-
549
- const pinned = Object.entries(manifest.files)
550
- .filter(([, r]) => r.status === 'pinned');
551
-
552
- if (pinned.length === 0) {
553
- console.log('No pinned files.');
39
+ // First-time global setup — no menu, just run the wizard
40
+ if (!orbitalSetupDone()) {
41
+ await wiz.runSetupWizard(hubVersion);
554
42
  return;
555
43
  }
556
44
 
557
- console.log(`\n Pinned files:\n`);
558
- for (const [file, record] of pinned) {
559
- const reason = record.pinnedReason || '(no reason)';
560
- const date = record.pinnedAt ? new Date(record.pinnedAt).toLocaleDateString() : '';
561
- console.log(` ${file}`);
562
- console.log(` Reason: ${reason} Pinned: ${date}`);
563
- if (record.templateHash !== record.installedHash) {
564
- console.log(` Template has changed since pin — run 'orbital diff ${file}' to compare`);
565
- }
566
- }
567
- console.log();
568
- }
569
-
570
- async function cmdDiff(args) {
571
- const projectRoot = detectProjectRoot();
572
- const filePath = args[0];
573
-
574
- if (!filePath) {
575
- console.error('Usage: orbital diff <relative-path>');
576
- process.exit(1);
577
- }
578
-
579
- const mod = await loadSharedModule();
580
- const manifest = mod.loadManifest(projectRoot);
581
- if (!manifest) {
582
- console.error('No manifest found. Run `orbital init` first.');
583
- process.exit(1);
584
- }
585
-
586
- const record = manifest.files[filePath];
587
- if (!record || record.origin !== 'template') {
588
- console.error(`Not a template file: ${filePath}`);
589
- process.exit(1);
590
- }
591
-
592
- // Resolve template path
593
- let templateRelPath = filePath;
594
- if (filePath.startsWith('config/workflows/')) {
595
- templateRelPath = filePath.replace('config/workflows/', 'presets/');
596
- }
597
-
598
- const templatePath = path.join(PACKAGE_ROOT, 'templates', templateRelPath);
599
- const localPath = path.join(projectRoot, '.claude', filePath);
600
-
601
- if (!fs.existsSync(templatePath)) {
602
- console.error(`Template file not found: ${templateRelPath}`);
603
- process.exit(1);
604
- }
605
- if (!fs.existsSync(localPath)) {
606
- console.log('Local file does not exist. Template content:');
607
- console.log(fs.readFileSync(templatePath, 'utf-8'));
45
+ // Need a git repo for everything else
46
+ if (!isGitRepo()) {
47
+ requireGitRepo(); // exits with error
608
48
  return;
609
49
  }
610
50
 
611
- // Use git diff for nice formatting (safe: no user input in arguments)
612
- const { execFileSync } = await import('child_process');
613
- try {
614
- const output = execFileSync(
615
- 'git', ['diff', '--no-index', '--color', '--', templatePath, localPath],
616
- { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
617
- );
618
- console.log(output);
619
- } catch (e) {
620
- // git diff exits 1 when files differ — that's expected
621
- if (e.stdout) console.log(e.stdout);
622
- else console.log('Files differ but git diff is unavailable.');
623
- }
624
- }
625
-
626
- async function cmdReset(args) {
627
- const projectRoot = detectProjectRoot();
628
- const filePath = args[0];
629
-
630
- if (!filePath) {
631
- console.error('Usage: orbital reset <relative-path>');
632
- process.exit(1);
633
- }
51
+ const hubRoot = detectProjectRoot();
52
+ const isInitialized = fs.existsSync(
53
+ path.join(hubRoot, '.claude', 'orbital.config.json')
54
+ );
55
+ const hubRegistry = loadRegistry();
56
+ const projectNames = (hubRegistry.projects || []).map(p => p.name);
634
57
 
635
- const mod = await loadSharedModule();
636
- const manifest = mod.loadManifest(projectRoot);
637
- if (!manifest) {
638
- console.error('No manifest found. Run `orbital init` first.');
639
- process.exit(1);
640
- }
641
-
642
- const record = manifest.files[filePath];
643
- if (!record || record.origin !== 'template') {
644
- console.error(`Not a template file: ${filePath}`);
645
- process.exit(1);
646
- }
647
-
648
- // Resolve and copy template file
649
- let templateRelPath = filePath;
650
- if (filePath.startsWith('config/workflows/')) {
651
- templateRelPath = filePath.replace('config/workflows/', 'presets/');
652
- }
653
-
654
- const templatePath = path.join(PACKAGE_ROOT, 'templates', templateRelPath);
655
- const localPath = path.join(projectRoot, '.claude', filePath);
656
-
657
- if (!fs.existsSync(templatePath)) {
658
- console.error(`Template file not found: ${templateRelPath}`);
659
- process.exit(1);
660
- }
661
-
662
- fs.copyFileSync(templatePath, localPath);
663
- const newHash = mod.hashFile(localPath);
664
- record.status = 'synced';
665
- record.templateHash = newHash;
666
- record.installedHash = newHash;
667
- delete record.pinnedAt;
668
- delete record.pinnedReason;
669
-
670
- mod.saveManifest(projectRoot, manifest);
671
- console.log(`Reset: ${filePath} → synced with template`);
672
- }
673
-
674
- // ---------------------------------------------------------------------------
675
- // Multi-project commands
676
- // ---------------------------------------------------------------------------
677
-
678
- const ORBITAL_HOME = path.join(process.env.HOME || process.env.USERPROFILE || '~', '.orbital');
679
- const REGISTRY_PATH = path.join(ORBITAL_HOME, 'config.json');
680
-
681
- function loadRegistry() {
682
- if (!fs.existsSync(REGISTRY_PATH)) return { version: 1, projects: [] };
683
- try {
684
- return JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8'));
685
- } catch {
686
- return { version: 1, projects: [] };
687
- }
688
- }
689
-
690
- // cmdLaunch removed — merged into cmdLaunchOrDev() above
691
-
692
- function cmdRegister(args) {
693
- const targetPath = args[0] ? path.resolve(args[0]) : detectProjectRoot();
694
- const nameFlag = args.indexOf('--alias');
695
- const name = nameFlag >= 0 ? args[nameFlag + 1] : path.basename(targetPath);
696
-
697
- // Ensure ~/.orbital/ exists
698
- if (!fs.existsSync(ORBITAL_HOME)) fs.mkdirSync(ORBITAL_HOME, { recursive: true });
699
-
700
- // Check the project has been initialized
701
- if (!fs.existsSync(path.join(targetPath, '.claude'))) {
702
- console.error(`Error: ${targetPath} has not been initialized with Orbital Command.`);
703
- console.error(`Run \`orbital init\` in that directory first.`);
704
- process.exit(1);
705
- }
706
-
707
- const registry = loadRegistry();
708
-
709
- // Check if already registered
710
- if (registry.projects?.some(p => p.path === targetPath)) {
711
- console.log(`Project already registered: ${targetPath}`);
58
+ // Not initialized and no registered projects — just run setup wizard
59
+ if (!isInitialized && projectNames.length === 0) {
60
+ await wiz.runProjectSetup(hubRoot, hubVersion, []);
61
+ stampTemplateVersion(hubRoot);
712
62
  return;
713
63
  }
714
64
 
715
- // Color palette
716
- const COLORS = [
717
- '210 80% 55%', '340 75% 55%', '160 60% 45%', '30 90% 55%',
718
- '270 65% 55%', '50 85% 50%', '180 55% 45%', '0 70% 55%',
719
- '120 50% 42%', '300 60% 50%', '200 70% 50%', '15 80% 55%',
720
- ];
721
- const usedColors = (registry.projects || []).map(p => p.color);
722
- const color = COLORS.find(c => !usedColors.includes(c)) || COLORS[0];
723
-
724
- // Generate slug
725
- const baseSlug = path.basename(targetPath).toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'project';
726
- const existingIds = (registry.projects || []).map(p => p.id);
727
- const slug = existingIds.includes(baseSlug)
728
- ? `${baseSlug}-${crypto.createHash('sha256').update(targetPath).digest('hex').slice(0, 4)}`
729
- : baseSlug;
730
-
731
- const project = {
732
- id: slug,
733
- path: targetPath,
734
- name,
735
- color,
736
- registeredAt: new Date().toISOString(),
737
- enabled: true,
738
- };
739
-
740
- if (!registry.projects) registry.projects = [];
741
- registry.projects.push(project);
742
- fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2), 'utf8');
743
-
744
- console.log(`Registered project: ${name}`);
745
- console.log(` ID: ${slug}`);
746
- console.log(` Path: ${targetPath}`);
747
- console.log(` Color: ${color}`);
748
- }
65
+ // Show hub menu (initialized OR has registered projects)
66
+ const projects = (hubRegistry.projects || [])
67
+ .filter(p => p.enabled !== false)
68
+ .map(p => ({ name: p.name, path: p.path }));
69
+
70
+ const hubResult = await wiz.runHub({
71
+ packageVersion: hubVersion,
72
+ isProjectInitialized: isInitialized,
73
+ projectNames,
74
+ itermPromptShown: hubRegistry.itermPromptShown === true,
75
+ isMac: process.platform === 'darwin',
76
+ lastUpdateCheck: hubRegistry.lastUpdateCheck,
77
+ latestVersion: hubRegistry.latestVersion,
78
+ projectPaths: projects,
79
+ });
749
80
 
750
- function cmdUnregister(args) {
751
- const idOrPath = args[0];
752
- if (!idOrPath) {
753
- console.error('Usage: orbital unregister <id-or-path>');
754
- process.exit(1);
81
+ // Persist registry changes in one write
82
+ let registryChanged = false;
83
+ if (hubResult.setItermPromptShown) {
84
+ hubRegistry.itermPromptShown = true;
85
+ registryChanged = true;
755
86
  }
756
-
757
- const absPath = path.isAbsolute(idOrPath) ? idOrPath : path.resolve(idOrPath);
758
- const registry = loadRegistry();
759
- const idx = (registry.projects || []).findIndex(p => p.id === idOrPath || p.path === absPath);
760
-
761
- if (idx === -1) {
762
- console.error(`Project not found: ${idOrPath}`);
763
- process.exit(1);
87
+ if (hubResult.updateCache) {
88
+ hubRegistry.lastUpdateCheck = hubResult.updateCache.lastUpdateCheck;
89
+ hubRegistry.latestVersion = hubResult.updateCache.latestVersion;
90
+ registryChanged = true;
764
91
  }
765
-
766
- const removed = registry.projects.splice(idx, 1)[0];
767
- fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2), 'utf8');
768
-
769
- console.log(`Unregistered project: ${removed.name} (${removed.id})`);
770
- console.log(` Project files in ${removed.path} are preserved.`);
771
- }
772
-
773
- function cmdProjects() {
774
- const registry = loadRegistry();
775
- const projects = registry.projects || [];
776
-
777
- if (projects.length === 0) {
778
- console.log('\nNo projects registered.');
779
- console.log('Use `orbital register` or `orbital init` to add a project.\n');
780
- return;
92
+ if (registryChanged) {
93
+ writeRegistryAtomic(hubRegistry);
781
94
  }
782
95
 
783
- console.log(`\n ${'ID'.padEnd(22)} ${'NAME'.padEnd(22)} ${'STATUS'.padEnd(10)} PATH`);
784
- console.log(` ${'─'.repeat(22)} ${'─'.repeat(22)} ${'─'.repeat(10)} ${'─'.repeat(30)}`);
785
- for (const p of projects) {
786
- const status = p.enabled ? (fs.existsSync(p.path) ? 'active' : 'offline') : 'disabled';
787
- console.log(` ${p.id.padEnd(22)} ${p.name.padEnd(22)} ${status.padEnd(10)} ${p.path}`);
96
+ // Route the chosen action
97
+ switch (hubResult.action) {
98
+ case 'launch': cmdLaunchOrDev(false); break;
99
+ case 'init':
100
+ await wiz.runProjectSetup(hubRoot, hubVersion, []);
101
+ stampTemplateVersion(hubRoot);
102
+ break;
103
+ case 'config': await cmdConfig([]); break;
104
+ case 'doctor': await cmdDoctor(); break;
105
+ case 'update': await cmdUpdate([]); break;
106
+ case 'status': await cmdStatus(); break;
107
+ case 'reset': {
108
+ const { runInit } = await loadSharedModule();
109
+ runInit(hubRoot, { force: true });
110
+ stampTemplateVersion(hubRoot);
111
+ break;
112
+ }
113
+ default:
114
+ console.error(`Unknown action: ${hubResult.action}`);
115
+ process.exit(1);
788
116
  }
789
- console.log();
790
- }
791
-
792
- async function cmdConfig(args) {
793
- const { runConfigEditor } = await loadWizardModule();
794
- const projectRoot = detectProjectRoot();
795
- const version = getPackageVersion();
796
- await runConfigEditor(projectRoot, version, args);
797
- }
798
-
799
- async function cmdDoctor() {
800
- const { runDoctor } = await loadWizardModule();
801
- const projectRoot = detectProjectRoot();
802
- const version = getPackageVersion();
803
- await runDoctor(projectRoot, version);
804
- }
805
-
806
- function printHelp() {
807
- console.log(`
808
- Orbital Command — CLI for the agentic project management system
809
-
810
- Usage:
811
- orbital <command> [options]
812
-
813
- Commands:
814
- init Set up Orbital Command (interactive wizard)
815
- launch Start the dashboard
816
- config Modify project settings interactively
817
- doctor Health check and version diagnostics
818
- update Sync templates and apply migrations
819
- status Show template sync status
820
-
821
- Aliases:
822
- setup Same as init
823
- dev Same as launch --vite (development with HMR)
824
-
825
- Config Subcommands:
826
- config show Print current config as JSON
827
- config set <k> <v> Set a config value non-interactively
828
-
829
- Template Management:
830
- validate Check cross-references and consistency
831
- pin <path> Lock a file from updates
832
- unpin <path> Unlock a pinned file
833
- pins List all pinned files
834
- diff <path> Show diff between template and local file
835
- reset <path> Restore a file from the current template
836
-
837
- Project Management:
838
- register [path] Register a project with the dashboard
839
- unregister <id> Remove a project from the dashboard
840
- projects List all registered projects
841
-
842
- Other:
843
- build Production build of the dashboard frontend
844
- emit <TYPE> <JSON> Emit an orbital event
845
- uninstall Remove Orbital artifacts from the project
846
-
847
- Init Options:
848
- --force Overwrite existing hooks, skills, and agents
849
- --yes, -y Skip the wizard, use auto-detected defaults
850
- --private Disable telemetry for this project
851
- --preset <name> Workflow preset (default/minimal/development/gitflow)
852
- --project-name <n> Override auto-detected project name
853
- --server-port <n> Override default server port (4444)
854
- --client-port <n> Override default client port (4445)
855
-
856
- Launch Options:
857
- --open Open the dashboard in the browser
858
- --vite Force Vite dev server for HMR
859
-
860
- Update Options:
861
- --dry-run Preview changes without applying them
862
-
863
- Uninstall Options:
864
- --dry-run Preview removal without applying
865
- --keep-config Keep orbital.config.json for re-initialization
866
-
867
- Examples:
868
- orbital init
869
- orbital launch --open
870
- orbital config
871
- orbital doctor
872
- orbital update --dry-run
873
- orbital status
874
- `);
875
117
  }
876
118
 
877
119
  // ---------------------------------------------------------------------------
@@ -882,13 +124,15 @@ const [command, ...args] = process.argv.slice(2);
882
124
 
883
125
  async function main() {
884
126
  switch (command) {
885
- case 'launch':
886
- cmdLaunchOrDev(false);
887
- break;
127
+ // Deprecated commands — silently redirect to hub
888
128
  case 'init':
889
129
  case 'setup':
890
- await cmdInit(args);
130
+ case 'launch':
131
+ case undefined:
132
+ await runHubFlow();
891
133
  break;
134
+
135
+ // Active commands
892
136
  case 'config':
893
137
  await cmdConfig(args);
894
138
  break;
@@ -944,10 +188,8 @@ async function main() {
944
188
  const registry = loadRegistry();
945
189
  const enable = args[0] !== 'off';
946
190
  registry.privateMode = enable;
947
- if (!fs.existsSync(ORBITAL_HOME)) fs.mkdirSync(ORBITAL_HOME, { recursive: true });
948
- fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2), 'utf8');
191
+ writeRegistryAtomic(registry);
949
192
  console.log(`Private mode ${enable ? 'enabled' : 'disabled'} globally.`);
950
-
951
193
  break;
952
194
  }
953
195
  case 'help':
@@ -955,23 +197,6 @@ async function main() {
955
197
  case '-h':
956
198
  printHelp();
957
199
  break;
958
- case undefined:
959
- if (process.stdout.isTTY && !process.env.CI) {
960
- const wiz = await loadWizardModule();
961
- const version = getPackageVersion();
962
- if (!orbitalSetupDone()) {
963
- // First time — run Phase 1 setup
964
- await wiz.runSetupWizard(version);
965
- } else {
966
- // Already set up — run Phase 2 for current directory
967
- const projectRoot = detectProjectRoot();
968
- await wiz.runProjectSetup(projectRoot, version, []);
969
- stampTemplateVersion(projectRoot);
970
- }
971
- } else {
972
- printHelp();
973
- }
974
- break;
975
200
  default:
976
201
  console.error(`Unknown command: ${command}`);
977
202
  printHelp();