orbital-command 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orbital-command",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Orbital Command — mission control dashboard for Claude Code projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/server/index.ts CHANGED
@@ -20,8 +20,6 @@ import { createSyncRoutes } from './routes/sync-routes.js';
20
20
  import { seedGlobalPrimitives } from './init.js';
21
21
  import {
22
22
  ensureOrbitalHome,
23
- loadGlobalConfig,
24
- registerProject as registerProjectGlobal,
25
23
  GLOBAL_PRIMITIVES_DIR,
26
24
  ORBITAL_HOME,
27
25
  } from './global-config.js';
@@ -29,8 +27,6 @@ import {
29
27
  export interface CentralServerOverrides {
30
28
  port?: number;
31
29
  clientPort?: number;
32
- /** If set, auto-register this project on first launch */
33
- autoRegisterPath?: string;
34
30
  }
35
31
 
36
32
  export interface CentralServerInstance {
@@ -53,13 +49,6 @@ export async function startCentralServer(overrides?: CentralServerOverrides): Pr
53
49
  const port = overrides?.port ?? (Number(process.env.ORBITAL_SERVER_PORT) || 4444);
54
50
  const clientPort = overrides?.clientPort ?? (Number(process.env.ORBITAL_CLIENT_PORT) || 4445);
55
51
 
56
- // Auto-register current project if registry is empty
57
- const globalConfig = loadGlobalConfig();
58
- if (globalConfig.projects.length === 0 && overrides?.autoRegisterPath) {
59
- registerProjectGlobal(overrides.autoRegisterPath);
60
- log.info('Auto-registered current project', { path: overrides.autoRegisterPath });
61
- }
62
-
63
52
  const app = express();
64
53
  const httpServer = createServer(app);
65
54
 
@@ -261,10 +250,8 @@ const isDirectRun = process.argv[1] && (
261
250
  );
262
251
 
263
252
  if (isDirectRun) {
264
- const projectRoot = process.env.ORBITAL_PROJECT_ROOT || process.cwd();
265
253
  startCentralServer({
266
254
  port: Number(process.env.ORBITAL_SERVER_PORT) || 4444,
267
- autoRegisterPath: projectRoot,
268
255
  }).then(({ shutdown }) => {
269
256
  process.on('SIGINT', async () => {
270
257
  await shutdown();
package/server/launch.ts CHANGED
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Reads environment variables set by bin/orbital.js:
5
5
  * ORBITAL_LAUNCH_MODE=central
6
- * ORBITAL_AUTO_REGISTER=<path> (if no projects registered yet)
7
6
  * ORBITAL_SERVER_PORT=<port>
8
7
  */
9
8
  import { startCentralServer } from './index.js';
@@ -12,11 +11,9 @@ import { createLogger } from './utils/logger.js';
12
11
  const log = createLogger('launch');
13
12
 
14
13
  const port = Number(process.env.ORBITAL_SERVER_PORT) || 4444;
15
- const autoRegisterPath = process.env.ORBITAL_AUTO_REGISTER || undefined;
16
14
 
17
15
  startCentralServer({
18
16
  port,
19
- autoRegisterPath: autoRegisterPath || undefined,
20
17
  }).then(({ shutdown }) => {
21
18
  process.on('SIGINT', async () => {
22
19
  await shutdown();
@@ -5,7 +5,7 @@ import { execFile } from 'child_process';
5
5
  import type { SyncService } from '../services/sync-service.js';
6
6
  import type { ProjectManager } from '../project-manager.js';
7
7
  import { isValidRelativePath } from '../utils/route-helpers.js';
8
- import { runInit } from '../init.js';
8
+ import { runInit, TEMPLATES_DIR } from '../init.js';
9
9
  import { loadGlobalConfig } from '../global-config.js';
10
10
  import { getPackageVersion } from '../utils/package-info.js';
11
11
 
@@ -322,7 +322,7 @@ export function createSyncRoutes({ syncService, projectManager }: SyncRouteDeps)
322
322
 
323
323
  function seedWelcomeCard(projectRoot: string, preset: string): void {
324
324
  // Determine the planning directory from the preset
325
- const presetsDir = path.join(path.dirname(path.dirname(new URL(import.meta.url).pathname)), 'templates', 'presets');
325
+ const presetsDir = path.join(TEMPLATES_DIR, 'presets');
326
326
  let planningDir = 'planning'; // default fallback
327
327
 
328
328
  try {
@@ -4,16 +4,12 @@
4
4
 
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
- import type { SetupState, ProjectSetupState } from './types.js';
7
+ import type { SetupState } from './types.js';
8
8
 
9
9
  const ORBITAL_HOME = path.join(process.env.HOME || process.env.USERPROFILE || '~', '.orbital');
10
10
 
11
11
  export { ORBITAL_HOME };
12
12
 
13
- export function isInteractiveTerminal(): boolean {
14
- return !!(process.stdout.isTTY && !process.env.CI);
15
- }
16
-
17
13
  export function isOrbitalSetupDone(): boolean {
18
14
  return fs.existsSync(path.join(ORBITAL_HOME, 'config.json'));
19
15
  }
@@ -26,79 +22,3 @@ export function buildSetupState(packageVersion: string): SetupState {
26
22
  };
27
23
  }
28
24
 
29
- export function buildProjectState(projectRoot: string, packageVersion: string): ProjectSetupState {
30
- const projectConfigExists = fs.existsSync(path.join(projectRoot, '.claude', 'orbital.config.json'));
31
-
32
- return {
33
- projectRoot,
34
- isProjectInitialized: projectConfigExists,
35
- packageVersion,
36
- };
37
- }
38
-
39
- export function detectProjectName(projectRoot: string): string {
40
- return path.basename(projectRoot)
41
- .replace(/[-_]+/g, ' ')
42
- .replace(/\b\w/g, c => c.toUpperCase());
43
- }
44
-
45
- export function detectCommands(projectRoot: string): Record<string, string | null> {
46
- const commands: Record<string, string | null> = {
47
- typeCheck: null,
48
- lint: null,
49
- build: null,
50
- test: null,
51
- };
52
-
53
- const pkgJsonPath = path.join(projectRoot, 'package.json');
54
- if (!fs.existsSync(pkgJsonPath)) return commands;
55
-
56
- try {
57
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
58
- const scripts = pkg.scripts || {};
59
-
60
- if (scripts.typecheck || scripts['type-check']) {
61
- commands.typeCheck = `npm run ${scripts.typecheck ? 'typecheck' : 'type-check'}`;
62
- }
63
- if (scripts.lint) commands.lint = 'npm run lint';
64
- if (scripts.build) commands.build = 'npm run build';
65
- if (scripts.test) commands.test = 'npm run test';
66
- } catch { /* ignore malformed package.json */ }
67
-
68
- return commands;
69
- }
70
-
71
- export function detectPortConflict(serverPort: number): string | null {
72
- const registryPath = path.join(ORBITAL_HOME, 'config.json');
73
- if (!fs.existsSync(registryPath)) return null;
74
-
75
- try {
76
- const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
77
- for (const project of registry.projects || []) {
78
- const configPath = path.join(project.path, '.claude', 'orbital.config.json');
79
- if (!fs.existsSync(configPath)) continue;
80
- try {
81
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
82
- if (config.serverPort === serverPort) return project.name;
83
- } catch { /* skip unreadable configs */ }
84
- }
85
- } catch { /* skip unreadable registry */ }
86
-
87
- return null;
88
- }
89
-
90
- export function isValidProjectPath(p: string): string | undefined {
91
- const resolved = p.startsWith('~')
92
- ? path.join(process.env.HOME || process.env.USERPROFILE || '~', p.slice(1))
93
- : path.resolve(p);
94
- if (!fs.existsSync(resolved)) return 'Directory does not exist';
95
- if (!fs.statSync(resolved).isDirectory()) return 'Not a directory';
96
- return undefined;
97
- }
98
-
99
- export function resolveProjectPath(p: string): string {
100
- if (p.startsWith('~')) {
101
- return path.join(process.env.HOME || process.env.USERPROFILE || '~', p.slice(1));
102
- }
103
- return path.resolve(p);
104
- }
@@ -2,8 +2,8 @@
2
2
  * Interactive CLI wizard — main orchestrator.
3
3
  *
4
4
  * Entry points:
5
- * runSetupWizard() — Phase 1: first-time Orbital setup (~/.orbital/)
6
- * runProjectSetup() Phase 2: per-project scaffolding (.claude/)
5
+ * runSetupWizard() — First-time Orbital setup (~/.orbital/)
6
+ * runHub() Context-aware hub menu (orbital)
7
7
  * runConfigEditor() — interactive config editor (orbital config)
8
8
  * runDoctor() — health diagnostics (orbital doctor)
9
9
  */
@@ -13,16 +13,11 @@ import path from 'path';
13
13
  import { spawn, execFileSync } from 'child_process';
14
14
  import * as p from '@clack/prompts';
15
15
  import pc from 'picocolors';
16
- import { buildSetupState, buildProjectState } from './detect.js';
16
+ import { buildSetupState } from './detect.js';
17
17
  import { phaseSetupWizard } from './phases/setup-wizard.js';
18
- import { phaseWelcome } from './phases/welcome.js';
19
- import { phaseProjectSetup } from './phases/project-setup.js';
20
- import { phaseWorkflowSetup } from './phases/workflow-setup.js';
21
- import { phaseConfirm, showPostInstall } from './phases/confirm.js';
22
18
  import { runConfigEditor } from './config-editor.js';
23
19
  import { runDoctor } from './doctor.js';
24
20
  import { isITerm2Available } from '../adapters/iterm2-adapter.js';
25
- import { registerProject } from '../global-config.js';
26
21
 
27
22
  export { runConfigEditor, runDoctor };
28
23
 
@@ -39,69 +34,7 @@ export async function runSetupWizard(packageVersion: string): Promise<void> {
39
34
 
40
35
  await phaseSetupWizard(state);
41
36
 
42
- p.outro(`Run ${pc.cyan('orbital')} to launch the dashboard and add your first project.`);
43
- }
44
-
45
- // ─── Phase 2: Project Setup ────────────────────────────────────
46
-
47
- /**
48
- * Per-project setup. Walks through name, commands, workflow, then
49
- * calls runInit() to scaffold files into .claude/.
50
- */
51
- export async function runProjectSetup(projectRoot: string, packageVersion: string, args: string[]): Promise<void> {
52
- const state = buildProjectState(projectRoot, packageVersion);
53
- const force = args.includes('--force');
54
-
55
- p.intro(`${pc.bgCyan(pc.black(' Orbital Command '))} ${pc.dim(`v${packageVersion}`)}`);
56
-
57
- // Welcome gate: detect re-init / reconfigure
58
- const forceFromWelcome = await phaseWelcome(state);
59
- const useForce = force || forceFromWelcome;
60
-
61
- await runProjectPhases(state, useForce);
62
-
63
- p.outro(`Run ${pc.cyan('orbital')} to launch the dashboard.`);
64
- }
65
-
66
- // ─── Shared project phases (used by both flows) ────────────────
67
-
68
- /**
69
- * Run the project setup phases and install. Used by both
70
- * standalone runProjectSetup() and inline from runSetupWizard().
71
- */
72
- async function runProjectPhases(state: ReturnType<typeof buildProjectState>, useForce: boolean): Promise<void> {
73
- await phaseProjectSetup(state);
74
- await phaseWorkflowSetup(state);
75
- await phaseConfirm(state);
76
-
77
- // Install
78
- const s = p.spinner();
79
- s.start('Installing into project...');
80
-
81
- try {
82
- const { runInit } = await import('../init.js');
83
-
84
- runInit(state.projectRoot, {
85
- force: useForce,
86
- quiet: true,
87
- preset: state.workflowPreset,
88
- projectName: state.projectName,
89
- serverPort: state.serverPort,
90
- clientPort: state.clientPort,
91
- commands: state.selectedCommands,
92
- });
93
-
94
- registerProject(state.projectRoot, { name: state.projectName });
95
- stampTemplateVersion(state.projectRoot, state.packageVersion);
96
-
97
- s.stop('Project ready.');
98
- } catch (err) {
99
- s.stop('Installation failed.');
100
- p.log.error(err instanceof Error ? err.message : String(err));
101
- process.exit(1);
102
- }
103
-
104
- showPostInstall(state);
37
+ p.outro('Launching dashboard...');
105
38
  }
106
39
 
107
40
  // ─── Update Check ─────────────────────────────────────────────
@@ -158,7 +91,7 @@ async function checkForUpdate(
158
91
 
159
92
  // ─── Hub Menu ─────────────────────────────────────────────────
160
93
 
161
- export type HubAction = 'launch' | 'init' | 'config' | 'doctor' | 'update' | 'status' | 'reset';
94
+ export type HubAction = 'launch' | 'config' | 'doctor' | 'update' | 'status' | 'reset';
162
95
 
163
96
  export interface HubResult {
164
97
  action: HubAction;
@@ -172,7 +105,6 @@ export interface HubResult {
172
105
  */
173
106
  export async function runHub(opts: {
174
107
  packageVersion: string;
175
- isProjectInitialized: boolean;
176
108
  projectNames: string[];
177
109
  itermPromptShown: boolean;
178
110
  isMac: boolean;
@@ -343,28 +275,28 @@ export async function runHub(opts: {
343
275
  }
344
276
  }
345
277
 
346
- // ── Build menu options based on project state ──
347
- const projectHint = opts.projectNames.length > 0
348
- ? pc.dim(` (${opts.projectNames.join(', ')})`)
278
+ // ── Show menu and pick action ──
279
+ result.action = await promptHubAction(opts.projectNames);
280
+ return result;
281
+ }
282
+
283
+ /**
284
+ * Show the hub menu and return the chosen action.
285
+ * Exported separately so the CLI can loop back after executing an action.
286
+ */
287
+ export async function promptHubAction(projectNames: string[]): Promise<HubAction> {
288
+ const projectHint = projectNames.length > 0
289
+ ? pc.dim(` (${projectNames.join(', ')})`)
349
290
  : '';
350
291
 
351
- const options: Array<{ value: HubAction; label: string; hint?: string }> = [];
352
-
353
- if (opts.isProjectInitialized) {
354
- options.push(
355
- { value: 'launch', label: `Launch dashboard${projectHint}` },
356
- { value: 'config', label: 'Config', hint: 'modify project settings' },
357
- { value: 'doctor', label: 'Doctor', hint: 'health check & diagnostics' },
358
- { value: 'update', label: 'Update templates', hint: 'sync to latest' },
359
- { value: 'status', label: 'Status', hint: 'template sync status' },
360
- { value: 'reset', label: 'Reset to defaults', hint: 'force-reset all templates' },
361
- );
362
- } else {
363
- options.push(
364
- { value: 'init', label: 'Initialize this project' },
365
- { value: 'launch', label: `Launch dashboard${projectHint}` },
366
- );
367
- }
292
+ const options: Array<{ value: HubAction; label: string; hint?: string }> = [
293
+ { value: 'launch', label: `Launch dashboard${projectHint}` },
294
+ { value: 'config', label: 'Config', hint: 'modify project settings' },
295
+ { value: 'doctor', label: 'Doctor', hint: 'health check & diagnostics' },
296
+ { value: 'update', label: 'Update templates', hint: 'sync to latest' },
297
+ { value: 'status', label: 'Status', hint: 'template sync status' },
298
+ { value: 'reset', label: 'Reset to defaults', hint: 'force-reset all templates' },
299
+ ];
368
300
 
369
301
  const action = await p.select({
370
302
  message: 'What would you like to do?',
@@ -376,7 +308,7 @@ export async function runHub(opts: {
376
308
  process.exit(0);
377
309
  }
378
310
 
379
- // ── Double-confirm for destructive reset ──
311
+ // Double-confirm for destructive reset
380
312
  if (action === 'reset') {
381
313
  p.note(
382
314
  'This will overwrite ALL hooks, skills, agents, and workflow config\n' +
@@ -389,36 +321,17 @@ export async function runHub(opts: {
389
321
  initialValue: false,
390
322
  });
391
323
  if (p.isCancel(confirmReset) || !confirmReset) {
392
- p.cancel('Reset cancelled.');
393
- process.exit(0);
324
+ return promptHubAction(projectNames);
394
325
  }
395
326
  const doubleConfirm = await p.confirm({
396
327
  message: 'This cannot be undone. Continue?',
397
328
  initialValue: false,
398
329
  });
399
330
  if (p.isCancel(doubleConfirm) || !doubleConfirm) {
400
- p.cancel('Reset cancelled.');
401
- process.exit(0);
331
+ return promptHubAction(projectNames);
402
332
  }
403
333
  }
404
334
 
405
- result.action = action;
406
- return result;
335
+ return action;
407
336
  }
408
337
 
409
- // ─── Template Version Stamping ─────────────────────────────────
410
-
411
- function stampTemplateVersion(projectRoot: string, packageVersion: string): void {
412
- const configPath = path.join(projectRoot, '.claude', 'orbital.config.json');
413
- if (!fs.existsSync(configPath)) return;
414
-
415
- try {
416
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
417
- if (config.templateVersion !== packageVersion) {
418
- config.templateVersion = packageVersion;
419
- const tmp = configPath + `.tmp.${process.pid}`;
420
- fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
421
- fs.renameSync(tmp, configPath);
422
- }
423
- } catch { /* ignore malformed config */ }
424
- }
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Types for the interactive CLI wizard.
3
3
  *
4
- * Two wizard flows:
5
- * Phase 1 (SetupState) — first-time Orbital setup, ~/.orbital/ creation
6
- * Phase 2 (ProjectSetupState) — per-project scaffolding into .claude/
4
+ * Phase 1 (SetupState) — first-time Orbital setup, ~/.orbital/ creation.
5
+ * Project setup is handled by the frontend Add Project modal.
7
6
  */
8
7
 
9
8
  export interface SetupState {
@@ -12,19 +11,5 @@ export interface SetupState {
12
11
  linkedProjects: string[]; // project paths added during setup
13
12
  }
14
13
 
15
- export interface ProjectSetupState {
16
- projectRoot: string;
17
- isProjectInitialized: boolean; // .claude/orbital.config.json exists
18
- packageVersion: string;
19
-
20
- // Collected from phases
21
- projectName?: string;
22
- serverPort?: number;
23
- clientPort?: number;
24
- detectedCommands?: Record<string, string | null>;
25
- selectedCommands?: Record<string, string | null>;
26
- workflowPreset?: string; // 'default' | 'minimal' | 'development' | 'gitflow'
27
- }
28
-
29
14
  export type { PresetInfo } from '../../shared/workflow-presets.js';
30
15
  export { WORKFLOW_PRESETS } from '../../shared/workflow-presets.js';
@@ -37,8 +37,22 @@ _pipeline_after() {
37
37
  echo "$result"
38
38
  }
39
39
 
40
+ # Check if a flag file's session is still alive; remove stale flags
41
+ _flag_alive() {
42
+ local flag="$1"
43
+ [ -f "$flag" ] || return 1
44
+ local pid
45
+ pid=$(cat "$flag" 2>/dev/null)
46
+ # Empty flag (legacy touch-style) or dead process → stale
47
+ if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
48
+ rm -f "$flag"
49
+ return 1
50
+ fi
51
+ return 0
52
+ }
53
+
40
54
  # Block git push during /git-commit
41
- if [ -f "$PUSH_FLAG" ]; then
55
+ if _flag_alive "$PUSH_FLAG"; then
42
56
  if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)git\s+push'; then
43
57
  "$HOOK_DIR/orbital-emit.sh" VIOLATION '{"rule":"block-push","outcome":"blocked"}' 2>/dev/null || true
44
58
  REMAINING=$(_pipeline_after "completed")
@@ -51,7 +65,7 @@ if [ -f "$PUSH_FLAG" ]; then
51
65
  fi
52
66
 
53
67
  # Block git commit/add during /scope-implement
54
- if [ -f "$IMPL_FLAG" ]; then
68
+ if _flag_alive "$IMPL_FLAG"; then
55
69
  if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)git\s+(commit|add)'; then
56
70
  "$HOOK_DIR/orbital-emit.sh" VIOLATION '{"rule":"block-commit-implementing","outcome":"blocked"}' 2>/dev/null || true
57
71
  REMAINING=$(_pipeline_after "implementing")
@@ -18,15 +18,16 @@ PUSH_FLAG="$PROJECT_DIR/.claude/.block-push-active"
18
18
  IMPL_FLAG="$PROJECT_DIR/.claude/.implementing-session"
19
19
 
20
20
  # /git-commit → block pushes during commit skill
21
+ # Store parent PID so block-push.sh can detect stale flags from dead sessions
21
22
  if [ "$SKILL" = "git-commit" ]; then
22
- touch "$PUSH_FLAG"
23
+ echo "$PPID" > "$PUSH_FLAG"
23
24
  else
24
25
  rm -f "$PUSH_FLAG"
25
26
  fi
26
27
 
27
28
  # /scope-implement → block commits during implementing
28
29
  if [ "$SKILL" = "scope-implement" ]; then
29
- touch "$IMPL_FLAG"
30
+ echo "$PPID" > "$IMPL_FLAG"
30
31
  else
31
32
  rm -f "$IMPL_FLAG"
32
33
  fi
@@ -1,39 +0,0 @@
1
- /**
2
- * Phase 3: Confirm choices, run installation, show next steps.
3
- */
4
- import fs from 'fs';
5
- import path from 'path';
6
- import * as p from '@clack/prompts';
7
- import { NOTES, formatSummary } from '../ui.js';
8
- export async function phaseConfirm(state) {
9
- p.note(formatSummary(state), 'Ready to Initialize');
10
- const proceed = await p.confirm({
11
- message: 'Proceed with installation?',
12
- initialValue: true,
13
- });
14
- if (p.isCancel(proceed) || !proceed) {
15
- p.cancel('Setup cancelled.');
16
- process.exit(0);
17
- }
18
- }
19
- export function showPostInstall(state) {
20
- // Count installed artifacts
21
- const claudeDir = path.join(state.projectRoot, '.claude');
22
- const counts = {
23
- hooks: countDir(path.join(claudeDir, 'hooks')),
24
- skills: countDir(path.join(claudeDir, 'skills')),
25
- agents: countDir(path.join(claudeDir, 'agents')),
26
- };
27
- p.note(NOTES.postInstall(counts), 'Installation Complete');
28
- p.note(NOTES.nextSteps, 'Getting Started');
29
- }
30
- function countDir(dir) {
31
- if (!fs.existsSync(dir))
32
- return 0;
33
- try {
34
- return fs.readdirSync(dir).filter(f => !f.startsWith('.')).length;
35
- }
36
- catch {
37
- return 0;
38
- }
39
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * Phase 1: Project configuration — name, commands, ports.
3
- */
4
- import * as p from '@clack/prompts';
5
- import { NOTES, formatDetectedCommands } from '../ui.js';
6
- import { detectProjectName, detectCommands, detectPortConflict } from '../detect.js';
7
- export async function phaseProjectSetup(state) {
8
- p.note(NOTES.projectConfig, 'Project Configuration');
9
- // 1. Project name
10
- const defaultName = detectProjectName(state.projectRoot);
11
- const name = await p.text({
12
- message: 'Project name',
13
- placeholder: defaultName,
14
- defaultValue: defaultName,
15
- });
16
- if (p.isCancel(name)) {
17
- p.cancel('Setup cancelled.');
18
- process.exit(0);
19
- }
20
- state.projectName = name;
21
- // 2. Command detection
22
- const detected = detectCommands(state.projectRoot);
23
- state.detectedCommands = detected;
24
- const detectedCount = Object.values(detected).filter(v => v !== null).length;
25
- if (detectedCount > 0) {
26
- p.note(formatDetectedCommands(detected), `Detected ${detectedCount} command(s) from package.json`);
27
- const useDetected = await p.confirm({
28
- message: 'Use these detected commands for quality gates?',
29
- initialValue: true,
30
- });
31
- if (p.isCancel(useDetected)) {
32
- p.cancel('Setup cancelled.');
33
- process.exit(0);
34
- }
35
- if (useDetected) {
36
- state.selectedCommands = { ...detected };
37
- }
38
- else {
39
- state.selectedCommands = await promptCommands(detected);
40
- }
41
- }
42
- else {
43
- p.log.info('No build commands detected from package.json. You can configure them later with `orbital config`.');
44
- state.selectedCommands = detected;
45
- }
46
- // 3. Port conflict detection
47
- const conflict = detectPortConflict(4444);
48
- if (conflict) {
49
- p.log.warn(`Port 4444 is already used by "${conflict}".`);
50
- const serverPort = await p.text({
51
- message: 'Server port',
52
- placeholder: '4446',
53
- defaultValue: '4446',
54
- validate: (val) => {
55
- const n = Number(val);
56
- if (isNaN(n) || n < 1 || n > 65535)
57
- return 'Must be a valid port (1-65535)';
58
- return undefined;
59
- },
60
- });
61
- if (p.isCancel(serverPort)) {
62
- p.cancel('Setup cancelled.');
63
- process.exit(0);
64
- }
65
- state.serverPort = Number(serverPort);
66
- state.clientPort = state.serverPort + 1;
67
- }
68
- }
69
- async function promptCommands(defaults) {
70
- const commands = {};
71
- const labels = {
72
- typeCheck: 'Type check command',
73
- lint: 'Lint command',
74
- build: 'Build command',
75
- test: 'Test command',
76
- };
77
- for (const [key, defaultVal] of Object.entries(defaults)) {
78
- const val = await p.text({
79
- message: labels[key] || key,
80
- placeholder: defaultVal || 'none',
81
- defaultValue: defaultVal || '',
82
- });
83
- if (p.isCancel(val)) {
84
- p.cancel('Setup cancelled.');
85
- process.exit(0);
86
- }
87
- commands[key] = val || null;
88
- }
89
- return commands;
90
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Phase 2 welcome gate — project-scoped only.
3
- *
4
- * If the project is already initialized, offers re-init or config editor.
5
- * If not, returns false to continue the project setup flow.
6
- */
7
- import * as p from '@clack/prompts';
8
- import pc from 'picocolors';
9
- import { NOTES } from '../ui.js';
10
- import { runConfigEditor } from '../config-editor.js';
11
- export async function phaseWelcome(state) {
12
- if (state.isProjectInitialized) {
13
- p.note(NOTES.reconfigure, pc.yellow('Already Initialized'));
14
- const action = await p.select({
15
- message: 'What would you like to do?',
16
- options: [
17
- { value: 'configure', label: 'Open config editor', hint: 'modify settings' },
18
- { value: 'cancel', label: 'Cancel' },
19
- ],
20
- });
21
- if (p.isCancel(action) || action === 'cancel') {
22
- p.cancel('Cancelled.');
23
- process.exit(0);
24
- }
25
- if (action === 'configure') {
26
- await runConfigEditor(state.projectRoot, state.packageVersion, []);
27
- process.exit(0);
28
- }
29
- }
30
- // Not initialized — continue normally
31
- return false;
32
- }
@@ -1,22 +0,0 @@
1
- /**
2
- * Phase 2: Workflow preset selection.
3
- */
4
- import * as p from '@clack/prompts';
5
- import { WORKFLOW_PRESETS } from '../types.js';
6
- import { NOTES } from '../ui.js';
7
- export async function phaseWorkflowSetup(state) {
8
- p.note(NOTES.workflow, 'Workflow Selection');
9
- const preset = await p.select({
10
- message: 'Choose a workflow preset',
11
- options: WORKFLOW_PRESETS.map(p => ({
12
- value: p.value,
13
- label: p.label,
14
- hint: p.hint,
15
- })),
16
- });
17
- if (p.isCancel(preset)) {
18
- p.cancel('Setup cancelled.');
19
- process.exit(0);
20
- }
21
- state.workflowPreset = preset;
22
- }