orbital-command 1.0.1 → 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/README.md +1 -1
- package/bin/commands/launch.js +0 -1
- package/bin/lib/helpers.js +1 -31
- package/bin/orbital.js +34 -32
- package/dist/assets/Landing-B6q9U0Vd.js +11 -0
- package/dist/assets/{PrimitivesConfig-DThSipFy.js → PrimitivesConfig-TlFvypvg.js} +9 -14
- package/dist/assets/{QualityGates-B4kxM5UU.js → QualityGates-D8uvclW4.js} +1 -1
- package/dist/assets/SessionTimeline-QUaJw6Aa.js +1 -0
- package/dist/assets/Settings-CAEnAZAk.js +12 -0
- package/dist/assets/{SourceControl-BMNIz7Lt.js → SourceControl-DPeSBaMV.js} +7 -12
- package/dist/assets/{WorkflowVisualizer-CxuSBOYu.js → WorkflowVisualizer-DHrIjx6W.js} +1 -1
- package/dist/assets/{arrow-down-DVPp6_qp.js → arrow-down-DnfKgF33.js} +1 -1
- package/dist/assets/{bot-NFaJBDn_.js → bot-DUPnHZfM.js} +1 -1
- package/dist/assets/{circle-x-IsFCkBZu.js → circle-x-B4nA79je.js} +1 -1
- package/dist/assets/{file-text-J1cebZXF.js → file-text-PnzRO4pO.js} +1 -1
- package/dist/assets/{globe-WzeyHsUc.js → globe-CIX_GBr0.js} +1 -1
- package/dist/assets/index-B-B-tTjw.css +1 -0
- package/dist/assets/index-DQVAzHMu.js +354 -0
- package/dist/assets/{key-CKR8JJSj.js → key-DpAZM-3d.js} +1 -1
- package/dist/assets/{minus-CHBsJyjp.js → minus-CJlpKNUB.js} +1 -1
- package/dist/assets/{radio-xqZaR-Uk.js → radio-DhynLcSx.js} +1 -1
- package/dist/assets/{rocket-D_xvvNG6.js → rocket-DnuQh3sF.js} +1 -1
- package/dist/assets/{shield-TdB1yv_a.js → shield-DLMQmvFQ.js} +1 -1
- package/dist/assets/{ui-BmsSg9jU.js → ui-QhrRH5wk.js} +6 -6
- package/dist/assets/{useSocketListener-0L5yiN5i.js → useSocketListener-DPrExNDV.js} +1 -1
- package/dist/assets/{useWorkflowEditor-CqeRWVQX.js → useWorkflowEditor-BxN7phfr.js} +2 -2
- package/dist/assets/{workflow-constants-Rw-GmgHZ.js → workflow-constants-ZcCN11vm.js} +1 -1
- package/dist/assets/{zap-C9wqYMpl.js → zap-eJ3z8HRI.js} +1 -1
- package/dist/index.html +3 -3
- package/dist/server/server/index.js +9 -10
- package/dist/server/server/launch.js +0 -3
- package/dist/server/server/routes/sync-routes.js +175 -0
- package/dist/server/server/services/telemetry-service.js +143 -0
- package/dist/server/server/wizard/detect.js +0 -79
- package/dist/server/server/wizard/index.js +43 -113
- package/dist/server/server/wizard/phases/setup-wizard.js +4 -34
- package/dist/server/server/wizard/types.js +3 -25
- package/dist/server/shared/workflow-presets.js +22 -0
- package/package.json +1 -1
- package/server/index.ts +7 -14
- package/server/launch.ts +0 -3
- package/server/routes/sync-routes.ts +205 -0
- package/server/wizard/detect.ts +1 -81
- package/server/wizard/index.ts +45 -146
- package/server/wizard/phases/setup-wizard.ts +7 -38
- package/server/wizard/types.ts +4 -45
- package/shared/workflow-presets.ts +28 -0
- package/templates/hooks/block-push.sh +16 -2
- package/templates/hooks/git-commit-guard.sh +3 -2
- package/dist/assets/Landing-CfQdHR0N.js +0 -11
- package/dist/assets/SessionTimeline-Bz1iZnmg.js +0 -1
- package/dist/assets/Settings-DLcZwbCT.js +0 -12
- package/dist/assets/index-BdJ57EhC.css +0 -1
- package/dist/assets/index-o4ScMAuR.js +0 -349
- package/dist/server/server/wizard/phases/confirm.js +0 -39
- package/dist/server/server/wizard/phases/project-setup.js +0 -90
- package/dist/server/server/wizard/phases/welcome.js +0 -32
- package/dist/server/server/wizard/phases/workflow-setup.js +0 -22
- package/server/wizard/phases/confirm.ts +0 -45
- package/server/wizard/phases/project-setup.ts +0 -106
- package/server/wizard/phases/welcome.ts +0 -39
- package/server/wizard/phases/workflow-setup.ts +0 -28
package/server/wizard/detect.ts
CHANGED
|
@@ -4,16 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import type { SetupState
|
|
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
|
-
}
|
package/server/wizard/index.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Interactive CLI wizard — main orchestrator.
|
|
3
3
|
*
|
|
4
4
|
* Entry points:
|
|
5
|
-
* runSetupWizard() —
|
|
6
|
-
*
|
|
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,25 +13,19 @@ 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
|
|
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
|
-
import { NOTES } from './ui.js';
|
|
23
18
|
import { runConfigEditor } from './config-editor.js';
|
|
24
19
|
import { runDoctor } from './doctor.js';
|
|
25
20
|
import { isITerm2Available } from '../adapters/iterm2-adapter.js';
|
|
26
|
-
import { registerProject } from '../global-config.js';
|
|
27
21
|
|
|
28
22
|
export { runConfigEditor, runDoctor };
|
|
29
23
|
|
|
30
24
|
// ─── Phase 1: Setup Wizard ─────────────────────────────────────
|
|
31
25
|
|
|
32
26
|
/**
|
|
33
|
-
* First-time setup. Creates ~/.orbital
|
|
34
|
-
*
|
|
27
|
+
* First-time setup. Creates ~/.orbital/ and seeds primitives.
|
|
28
|
+
* Project setup is now handled by the frontend Add Project modal.
|
|
35
29
|
*/
|
|
36
30
|
export async function runSetupWizard(packageVersion: string): Promise<void> {
|
|
37
31
|
const state = buildSetupState(packageVersion);
|
|
@@ -40,94 +34,7 @@ export async function runSetupWizard(packageVersion: string): Promise<void> {
|
|
|
40
34
|
|
|
41
35
|
await phaseSetupWizard(state);
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
for (const projectRoot of state.linkedProjects) {
|
|
45
|
-
p.log.step(`Setting up ${pc.cyan(path.basename(projectRoot))}...`);
|
|
46
|
-
await runProjectSetupInline(projectRoot, packageVersion);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (state.linkedProjects.length === 0) {
|
|
50
|
-
p.note(NOTES.setupComplete, 'Done');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
p.outro(
|
|
54
|
-
state.linkedProjects.length > 0
|
|
55
|
-
? `Run ${pc.cyan('orbital')} to launch the dashboard.`
|
|
56
|
-
: `Run ${pc.cyan('orbital')} in a project directory to get started.`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ─── Phase 2: Project Setup ────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Per-project setup. Walks through name, commands, workflow, then
|
|
64
|
-
* calls runInit() to scaffold files into .claude/.
|
|
65
|
-
*/
|
|
66
|
-
export async function runProjectSetup(projectRoot: string, packageVersion: string, args: string[]): Promise<void> {
|
|
67
|
-
const state = buildProjectState(projectRoot, packageVersion);
|
|
68
|
-
const force = args.includes('--force');
|
|
69
|
-
|
|
70
|
-
p.intro(`${pc.bgCyan(pc.black(' Orbital Command '))} ${pc.dim(`v${packageVersion}`)}`);
|
|
71
|
-
|
|
72
|
-
// Welcome gate: detect re-init / reconfigure
|
|
73
|
-
const forceFromWelcome = await phaseWelcome(state);
|
|
74
|
-
const useForce = force || forceFromWelcome;
|
|
75
|
-
|
|
76
|
-
await runProjectPhases(state, useForce);
|
|
77
|
-
|
|
78
|
-
p.outro(`Run ${pc.cyan('orbital')} to launch the dashboard.`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ─── Shared project phases (used by both flows) ────────────────
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Run the project setup phases and install. Used by both
|
|
85
|
-
* standalone runProjectSetup() and inline from runSetupWizard().
|
|
86
|
-
*/
|
|
87
|
-
async function runProjectPhases(state: ReturnType<typeof buildProjectState>, useForce: boolean): Promise<void> {
|
|
88
|
-
await phaseProjectSetup(state);
|
|
89
|
-
await phaseWorkflowSetup(state);
|
|
90
|
-
await phaseConfirm(state);
|
|
91
|
-
|
|
92
|
-
// Install
|
|
93
|
-
const s = p.spinner();
|
|
94
|
-
s.start('Installing into project...');
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const { runInit } = await import('../init.js');
|
|
98
|
-
|
|
99
|
-
runInit(state.projectRoot, {
|
|
100
|
-
force: useForce,
|
|
101
|
-
quiet: true,
|
|
102
|
-
preset: state.workflowPreset,
|
|
103
|
-
projectName: state.projectName,
|
|
104
|
-
serverPort: state.serverPort,
|
|
105
|
-
clientPort: state.clientPort,
|
|
106
|
-
commands: state.selectedCommands,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
registerProject(state.projectRoot, { name: state.projectName });
|
|
110
|
-
stampTemplateVersion(state.projectRoot, state.packageVersion);
|
|
111
|
-
|
|
112
|
-
s.stop('Project ready.');
|
|
113
|
-
} catch (err) {
|
|
114
|
-
s.stop('Installation failed.');
|
|
115
|
-
p.log.error(err instanceof Error ? err.message : String(err));
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
showPostInstall(state);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Inline project setup — called from Phase 1 when user links a project.
|
|
124
|
-
* Skips intro/outro since the setup wizard already has those.
|
|
125
|
-
*/
|
|
126
|
-
async function runProjectSetupInline(projectRoot: string, packageVersion: string): Promise<void> {
|
|
127
|
-
const state = buildProjectState(projectRoot, packageVersion);
|
|
128
|
-
|
|
129
|
-
// Skip welcome gate for inline — this is a fresh project being linked
|
|
130
|
-
await runProjectPhases(state, false);
|
|
37
|
+
p.outro('Launching dashboard...');
|
|
131
38
|
}
|
|
132
39
|
|
|
133
40
|
// ─── Update Check ─────────────────────────────────────────────
|
|
@@ -138,6 +45,18 @@ interface UpdateInfo {
|
|
|
138
45
|
isOutdated: boolean;
|
|
139
46
|
}
|
|
140
47
|
|
|
48
|
+
/** Returns true if `a` is older than `b` (semver comparison). */
|
|
49
|
+
function isOlderThan(a: string, b: string): boolean {
|
|
50
|
+
const pa = a.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
51
|
+
const pb = b.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
52
|
+
if (!pa || !pb) return false;
|
|
53
|
+
for (let i = 1; i <= 3; i++) {
|
|
54
|
+
if (parseInt(pa[i]) < parseInt(pb[i])) return true;
|
|
55
|
+
if (parseInt(pa[i]) > parseInt(pb[i])) return false;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
141
60
|
async function checkForUpdate(
|
|
142
61
|
currentVersion: string,
|
|
143
62
|
cache: { lastUpdateCheck?: string; latestVersion?: string },
|
|
@@ -146,7 +65,7 @@ async function checkForUpdate(
|
|
|
146
65
|
if (cache.lastUpdateCheck && cache.latestVersion) {
|
|
147
66
|
const age = Date.now() - new Date(cache.lastUpdateCheck).getTime();
|
|
148
67
|
if (age < 24 * 60 * 60 * 1000) {
|
|
149
|
-
const isOutdated = cache.latestVersion
|
|
68
|
+
const isOutdated = isOlderThan(currentVersion, cache.latestVersion);
|
|
150
69
|
return {
|
|
151
70
|
info: { current: currentVersion, latest: cache.latestVersion, isOutdated },
|
|
152
71
|
cacheChanged: false,
|
|
@@ -162,7 +81,7 @@ async function checkForUpdate(
|
|
|
162
81
|
const data = await res.json() as { version: string };
|
|
163
82
|
const latest = data.version;
|
|
164
83
|
return {
|
|
165
|
-
info: { current: currentVersion, latest, isOutdated: latest
|
|
84
|
+
info: { current: currentVersion, latest, isOutdated: isOlderThan(currentVersion, latest) },
|
|
166
85
|
cacheChanged: true,
|
|
167
86
|
};
|
|
168
87
|
} catch {
|
|
@@ -172,7 +91,7 @@ async function checkForUpdate(
|
|
|
172
91
|
|
|
173
92
|
// ─── Hub Menu ─────────────────────────────────────────────────
|
|
174
93
|
|
|
175
|
-
export type HubAction = 'launch' | '
|
|
94
|
+
export type HubAction = 'launch' | 'config' | 'doctor' | 'update' | 'status' | 'reset';
|
|
176
95
|
|
|
177
96
|
export interface HubResult {
|
|
178
97
|
action: HubAction;
|
|
@@ -186,7 +105,6 @@ export interface HubResult {
|
|
|
186
105
|
*/
|
|
187
106
|
export async function runHub(opts: {
|
|
188
107
|
packageVersion: string;
|
|
189
|
-
isProjectInitialized: boolean;
|
|
190
108
|
projectNames: string[];
|
|
191
109
|
itermPromptShown: boolean;
|
|
192
110
|
isMac: boolean;
|
|
@@ -357,28 +275,28 @@ export async function runHub(opts: {
|
|
|
357
275
|
}
|
|
358
276
|
}
|
|
359
277
|
|
|
360
|
-
// ──
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
278
|
+
// ── Show menu and pick action ──
|
|
279
|
+
result.action = await promptHubAction(opts.projectNames);
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
364
282
|
|
|
365
|
-
|
|
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(', ')})`)
|
|
290
|
+
: '';
|
|
366
291
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
);
|
|
376
|
-
} else {
|
|
377
|
-
options.push(
|
|
378
|
-
{ value: 'init', label: 'Initialize this project' },
|
|
379
|
-
{ value: 'launch', label: `Launch dashboard${projectHint}` },
|
|
380
|
-
);
|
|
381
|
-
}
|
|
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
|
+
];
|
|
382
300
|
|
|
383
301
|
const action = await p.select({
|
|
384
302
|
message: 'What would you like to do?',
|
|
@@ -390,7 +308,7 @@ export async function runHub(opts: {
|
|
|
390
308
|
process.exit(0);
|
|
391
309
|
}
|
|
392
310
|
|
|
393
|
-
//
|
|
311
|
+
// Double-confirm for destructive reset
|
|
394
312
|
if (action === 'reset') {
|
|
395
313
|
p.note(
|
|
396
314
|
'This will overwrite ALL hooks, skills, agents, and workflow config\n' +
|
|
@@ -403,36 +321,17 @@ export async function runHub(opts: {
|
|
|
403
321
|
initialValue: false,
|
|
404
322
|
});
|
|
405
323
|
if (p.isCancel(confirmReset) || !confirmReset) {
|
|
406
|
-
|
|
407
|
-
process.exit(0);
|
|
324
|
+
return promptHubAction(projectNames);
|
|
408
325
|
}
|
|
409
326
|
const doubleConfirm = await p.confirm({
|
|
410
327
|
message: 'This cannot be undone. Continue?',
|
|
411
328
|
initialValue: false,
|
|
412
329
|
});
|
|
413
330
|
if (p.isCancel(doubleConfirm) || !doubleConfirm) {
|
|
414
|
-
|
|
415
|
-
process.exit(0);
|
|
331
|
+
return promptHubAction(projectNames);
|
|
416
332
|
}
|
|
417
333
|
}
|
|
418
334
|
|
|
419
|
-
|
|
420
|
-
return result;
|
|
335
|
+
return action;
|
|
421
336
|
}
|
|
422
337
|
|
|
423
|
-
// ─── Template Version Stamping ─────────────────────────────────
|
|
424
|
-
|
|
425
|
-
function stampTemplateVersion(projectRoot: string, packageVersion: string): void {
|
|
426
|
-
const configPath = path.join(projectRoot, '.claude', 'orbital.config.json');
|
|
427
|
-
if (!fs.existsSync(configPath)) return;
|
|
428
|
-
|
|
429
|
-
try {
|
|
430
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
431
|
-
if (config.templateVersion !== packageVersion) {
|
|
432
|
-
config.templateVersion = packageVersion;
|
|
433
|
-
const tmp = configPath + `.tmp.${process.pid}`;
|
|
434
|
-
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
435
|
-
fs.renameSync(tmp, configPath);
|
|
436
|
-
}
|
|
437
|
-
} catch { /* ignore malformed config */ }
|
|
438
|
-
}
|
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import fs from 'fs';
|
|
9
9
|
import * as p from '@clack/prompts';
|
|
10
|
-
import pc from 'picocolors';
|
|
11
10
|
import type { SetupState } from '../types.js';
|
|
12
11
|
import { NOTES } from '../ui.js';
|
|
13
|
-
import {
|
|
12
|
+
import { ORBITAL_HOME } from '../detect.js';
|
|
14
13
|
|
|
15
|
-
export async function phaseSetupWizard(
|
|
14
|
+
export async function phaseSetupWizard(_state: SetupState): Promise<void> {
|
|
16
15
|
// Welcome and core concepts
|
|
17
16
|
p.note(NOTES.setupWelcome, 'Welcome');
|
|
18
17
|
|
|
@@ -40,39 +39,9 @@ export async function phaseSetupWizard(state: SetupState): Promise<void> {
|
|
|
40
39
|
process.exit(1);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
//
|
|
44
|
-
p.note(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const wantsProject = await p.confirm({
|
|
49
|
-
message: state.linkedProjects.length === 0
|
|
50
|
-
? 'Add a project now?'
|
|
51
|
-
: 'Add another project?',
|
|
52
|
-
initialValue: state.linkedProjects.length === 0,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
if (p.isCancel(wantsProject) || !wantsProject) {
|
|
56
|
-
addMore = false;
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const projectPath = await p.text({
|
|
61
|
-
message: 'Project path',
|
|
62
|
-
placeholder: '~/Code/my-project',
|
|
63
|
-
validate: (val) => {
|
|
64
|
-
if (!val || !val.trim()) return 'Path is required';
|
|
65
|
-
return isValidProjectPath(val.trim());
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
if (p.isCancel(projectPath)) {
|
|
70
|
-
addMore = false;
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const resolved = resolveProjectPath(projectPath.trim());
|
|
75
|
-
state.linkedProjects.push(resolved);
|
|
76
|
-
p.log.success(`Added: ${pc.cyan(resolved)}`);
|
|
77
|
-
}
|
|
42
|
+
// Direct user to the dashboard for project setup
|
|
43
|
+
p.note(
|
|
44
|
+
'Launch the dashboard to add your first project.\nThe setup wizard will guide you through it.',
|
|
45
|
+
'Next Steps',
|
|
46
|
+
);
|
|
78
47
|
}
|
package/server/wizard/types.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the interactive CLI wizard.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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,45 +11,5 @@ export interface SetupState {
|
|
|
12
11
|
linkedProjects: string[]; // project paths added during setup
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
export
|
|
16
|
-
|
|
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
|
-
export interface PresetInfo {
|
|
30
|
-
value: string;
|
|
31
|
-
label: string;
|
|
32
|
-
hint: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const WORKFLOW_PRESETS: PresetInfo[] = [
|
|
36
|
-
{
|
|
37
|
-
value: 'default',
|
|
38
|
-
label: 'Default',
|
|
39
|
-
hint: '7 lists, trunk-based — Icebox → Planning → Backlog → Implementing → Review → Completed → Main',
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
value: 'minimal',
|
|
43
|
-
label: 'Minimal',
|
|
44
|
-
hint: '3 lists — To Do → In Progress → Done',
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
value: 'development',
|
|
48
|
-
label: 'Development',
|
|
49
|
-
hint: '5 lists, dev branch — Backlog → Implementing → Review → Completed → Dev',
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
value: 'gitflow',
|
|
53
|
-
label: 'Gitflow',
|
|
54
|
-
hint: '9 lists, multi-branch — Full pipeline with Dev, Staging, and Production',
|
|
55
|
-
},
|
|
56
|
-
];
|
|
14
|
+
export type { PresetInfo } from '../../shared/workflow-presets.js';
|
|
15
|
+
export { WORKFLOW_PRESETS } from '../../shared/workflow-presets.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface PresetInfo {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
hint: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const WORKFLOW_PRESETS: PresetInfo[] = [
|
|
8
|
+
{
|
|
9
|
+
value: 'default',
|
|
10
|
+
label: 'Default',
|
|
11
|
+
hint: '7 lists, trunk-based — Icebox → Planning → Backlog → Implementing → Review → Completed → Main',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
value: 'minimal',
|
|
15
|
+
label: 'Minimal',
|
|
16
|
+
hint: '3 lists — To Do → In Progress → Done',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: 'development',
|
|
20
|
+
label: 'Development',
|
|
21
|
+
hint: '5 lists, dev branch — Backlog → Implementing → Review → Completed → Dev',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
value: 'gitflow',
|
|
25
|
+
label: 'Gitflow',
|
|
26
|
+
hint: '9 lists, multi-branch — Full pipeline with Dev, Staging, and Production',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
echo "$PPID" > "$IMPL_FLAG"
|
|
30
31
|
else
|
|
31
32
|
rm -f "$IMPL_FLAG"
|
|
32
33
|
fi
|