orbital-command 1.1.0 → 1.1.3
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/bin/commands/launch.js +0 -1
- package/bin/commands/update.js +18 -2
- package/bin/lib/helpers.js +1 -31
- package/bin/orbital.js +32 -30
- package/dist/assets/{Landing-B6q9U0Vd.js → Landing-D41fKTK0.js} +1 -1
- package/dist/assets/{PrimitivesConfig-TlFvypvg.js → PrimitivesConfig-BDKrqwMI.js} +1 -1
- package/dist/assets/{QualityGates-D8uvclW4.js → QualityGates-CcrwWI45.js} +1 -1
- package/dist/assets/{SessionTimeline-QUaJw6Aa.js → SessionTimeline-D2IN9T3a.js} +1 -1
- package/dist/assets/{Settings-CAEnAZAk.js → Settings-gAsObGj2.js} +1 -1
- package/dist/assets/{SourceControl-DPeSBaMV.js → SourceControl-CmrDWqSs.js} +1 -1
- package/dist/assets/{WorkflowVisualizer-DHrIjx6W.js → WorkflowVisualizer-SxM8rp11.js} +1 -1
- package/dist/assets/{arrow-down-DnfKgF33.js → arrow-down-CPRn0RJb.js} +1 -1
- package/dist/assets/{bot-DUPnHZfM.js → bot-D2wELs5q.js} +1 -1
- package/dist/assets/{circle-x-B4nA79je.js → circle-x-CXIlaZex.js} +1 -1
- package/dist/assets/{file-text-PnzRO4pO.js → file-text-b1U7rP0E.js} +1 -1
- package/dist/assets/{globe-CIX_GBr0.js → globe-Dxpor8Dk.js} +1 -1
- package/dist/assets/{index-DQVAzHMu.js → index-xFU6WWfl.js} +36 -36
- package/dist/assets/{key-DpAZM-3d.js → key-CBLFsYFp.js} +1 -1
- package/dist/assets/{minus-CJlpKNUB.js → minus-pmCQmtIS.js} +1 -1
- package/dist/assets/{radio-DhynLcSx.js → radio-Cf5n4Vr7.js} +1 -1
- package/dist/assets/{rocket-DnuQh3sF.js → rocket-grx18Wv8.js} +1 -1
- package/dist/assets/{shield-DLMQmvFQ.js → shield-Cgjh9h5t.js} +1 -1
- package/dist/assets/{useSocketListener-DPrExNDV.js → useSocketListener-BW3fKan5.js} +1 -1
- package/dist/assets/{useWorkflowEditor-BxN7phfr.js → useWorkflowEditor-BRepFz22.js} +1 -1
- package/dist/assets/{workflow-constants-ZcCN11vm.js → workflow-constants-YePmoWgU.js} +1 -1
- package/dist/assets/{zap-eJ3z8HRI.js → zap-rWDVJHlW.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/server/server/index.js +1 -9
- package/dist/server/server/launch.js +0 -3
- package/dist/server/server/routes/sync-routes.js +2 -2
- 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 +27 -91
- package/dist/server/server/wizard/types.js +2 -3
- package/package.json +1 -1
- package/server/index.ts +0 -13
- package/server/launch.ts +0 -3
- package/server/routes/sync-routes.ts +2 -2
- package/server/wizard/detect.ts +1 -81
- package/server/wizard/index.ts +29 -116
- package/server/wizard/types.ts +2 -17
- package/templates/hooks/block-push.sh +16 -2
- package/templates/hooks/git-commit-guard.sh +3 -2
- 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
|
@@ -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(
|
|
325
|
+
const presetsDir = path.join(TEMPLATES_DIR, 'presets');
|
|
326
326
|
let planningDir = 'planning'; // default fallback
|
|
327
327
|
|
|
328
328
|
try {
|
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,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
|
|
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(
|
|
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' | '
|
|
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
|
-
// ──
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
401
|
-
process.exit(0);
|
|
331
|
+
return promptHubAction(projectNames);
|
|
402
332
|
}
|
|
403
333
|
}
|
|
404
334
|
|
|
405
|
-
|
|
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
|
-
}
|
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,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
|
|
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
|
|
@@ -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
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 3: Confirm choices, run installation, show next steps.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import * as p from '@clack/prompts';
|
|
8
|
-
import type { ProjectSetupState } from '../types.js';
|
|
9
|
-
import { NOTES, formatSummary } from '../ui.js';
|
|
10
|
-
|
|
11
|
-
export async function phaseConfirm(state: ProjectSetupState): Promise<void> {
|
|
12
|
-
p.note(formatSummary(state), 'Ready to Initialize');
|
|
13
|
-
|
|
14
|
-
const proceed = await p.confirm({
|
|
15
|
-
message: 'Proceed with installation?',
|
|
16
|
-
initialValue: true,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
if (p.isCancel(proceed) || !proceed) {
|
|
20
|
-
p.cancel('Setup cancelled.');
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function showPostInstall(state: ProjectSetupState): void {
|
|
26
|
-
// Count installed artifacts
|
|
27
|
-
const claudeDir = path.join(state.projectRoot, '.claude');
|
|
28
|
-
const counts = {
|
|
29
|
-
hooks: countDir(path.join(claudeDir, 'hooks')),
|
|
30
|
-
skills: countDir(path.join(claudeDir, 'skills')),
|
|
31
|
-
agents: countDir(path.join(claudeDir, 'agents')),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
p.note(NOTES.postInstall(counts), 'Installation Complete');
|
|
35
|
-
p.note(NOTES.nextSteps, 'Getting Started');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function countDir(dir: string): number {
|
|
39
|
-
if (!fs.existsSync(dir)) return 0;
|
|
40
|
-
try {
|
|
41
|
-
return fs.readdirSync(dir).filter(f => !f.startsWith('.')).length;
|
|
42
|
-
} catch {
|
|
43
|
-
return 0;
|
|
44
|
-
}
|
|
45
|
-
}
|