orbital-command 0.3.0 → 1.0.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 +67 -42
- package/bin/commands/config.js +19 -0
- package/bin/commands/events.js +40 -0
- package/bin/commands/launch.js +126 -0
- package/bin/commands/manifest.js +283 -0
- package/bin/commands/registry.js +104 -0
- package/bin/commands/update.js +24 -0
- package/bin/lib/helpers.js +229 -0
- package/bin/orbital.js +90 -873
- package/dist/assets/Landing-CfQdHR0N.js +11 -0
- package/dist/assets/PrimitivesConfig-DThSipFy.js +32 -0
- package/dist/assets/QualityGates-B4kxM5UU.js +26 -0
- package/dist/assets/SessionTimeline-Bz1iZnmg.js +1 -0
- package/dist/assets/Settings-DLcZwbCT.js +12 -0
- package/dist/assets/SourceControl-BMNIz7Lt.js +36 -0
- package/dist/assets/WorkflowVisualizer-CxuSBOYu.js +69 -0
- package/dist/assets/{arrow-down-CPy85_J6.js → arrow-down-DVPp6_qp.js} +1 -1
- package/dist/assets/bot-NFaJBDn_.js +6 -0
- package/dist/assets/{charts-DbDg0Psc.js → charts-LGLb8hyU.js} +1 -1
- package/dist/assets/{circle-x-Cwz6ZQDV.js → circle-x-IsFCkBZu.js} +1 -1
- package/dist/assets/{file-text-C46Xr65c.js → file-text-J1cebZXF.js} +1 -1
- package/dist/assets/{globe-Cn2yNZUD.js → globe-WzeyHsUc.js} +1 -1
- package/dist/assets/index-BdJ57EhC.css +1 -0
- package/dist/assets/index-o4ScMAuR.js +349 -0
- package/dist/assets/{key-OPaNTWJ5.js → key-CKR8JJSj.js} +1 -1
- package/dist/assets/{minus-GMsbpKym.js → minus-CHBsJyjp.js} +1 -1
- package/dist/assets/radio-xqZaR-Uk.js +6 -0
- package/dist/assets/rocket-D_xvvNG6.js +6 -0
- package/dist/assets/{shield-DwAFkDYI.js → shield-TdB1yv_a.js} +1 -1
- package/dist/assets/useSocketListener-0L5yiN5i.js +1 -0
- package/dist/assets/useWorkflowEditor-CqeRWVQX.js +11 -0
- package/dist/assets/workflow-constants-Rw-GmgHZ.js +6 -0
- package/dist/assets/zap-C9wqYMpl.js +6 -0
- package/dist/index.html +3 -3
- package/dist/server/server/__tests__/data-routes.test.js +2 -0
- package/dist/server/server/__tests__/scope-routes.test.js +1 -0
- package/dist/server/server/config-migrator.js +0 -3
- package/dist/server/server/config.js +35 -6
- package/dist/server/server/database.js +0 -22
- package/dist/server/server/index.js +28 -816
- package/dist/server/server/init.js +32 -399
- package/dist/server/server/launch.js +1 -1
- package/dist/server/server/parsers/event-parser.js +4 -1
- package/dist/server/server/project-context.js +19 -9
- package/dist/server/server/project-manager.js +6 -6
- package/dist/server/server/routes/aggregate-routes.js +871 -0
- package/dist/server/server/routes/config-routes.js +41 -88
- package/dist/server/server/routes/data-routes.js +5 -15
- package/dist/server/server/routes/dispatch-routes.js +24 -8
- package/dist/server/server/routes/manifest-routes.js +1 -1
- package/dist/server/server/routes/scope-routes.js +10 -7
- package/dist/server/server/schema.js +1 -0
- package/dist/server/server/services/batch-orchestrator.js +17 -3
- package/dist/server/server/services/config-service.js +10 -1
- package/dist/server/server/services/scope-service.js +7 -7
- package/dist/server/server/services/sprint-orchestrator.js +24 -11
- package/dist/server/server/services/sprint-service.js +2 -2
- package/dist/server/server/uninstall.js +195 -0
- package/dist/server/server/update.js +212 -0
- package/dist/server/server/utils/dispatch-utils.js +8 -6
- package/dist/server/server/utils/flag-builder.js +54 -0
- package/dist/server/server/utils/json-fields.js +14 -0
- package/dist/server/server/utils/json-fields.test.js +73 -0
- package/dist/server/server/utils/route-helpers.js +37 -0
- package/dist/server/server/utils/route-helpers.test.js +115 -0
- package/dist/server/server/watchers/event-watcher.js +28 -13
- package/dist/server/server/wizard/config-editor.js +4 -4
- package/dist/server/server/wizard/doctor.js +2 -2
- package/dist/server/server/wizard/index.js +224 -39
- package/dist/server/server/wizard/phases/welcome.js +1 -4
- package/dist/server/server/wizard/ui.js +6 -7
- package/dist/server/shared/api-types.js +80 -1
- package/dist/server/shared/workflow-engine.js +1 -1
- package/package.json +20 -20
- package/schemas/orbital.config.schema.json +1 -19
- package/scripts/postinstall.js +6 -42
- package/scripts/release.sh +53 -0
- package/server/__tests__/data-routes.test.ts +2 -0
- package/server/__tests__/scope-routes.test.ts +1 -0
- package/server/config-migrator.ts +0 -3
- package/server/config.ts +39 -11
- package/server/database.ts +0 -26
- package/server/global-config.ts +4 -0
- package/server/index.ts +31 -896
- package/server/init.ts +32 -443
- package/server/launch.ts +1 -1
- package/server/parsers/event-parser.ts +4 -1
- package/server/project-context.ts +26 -10
- package/server/project-manager.ts +5 -6
- package/server/routes/aggregate-routes.ts +968 -0
- package/server/routes/config-routes.ts +41 -81
- package/server/routes/data-routes.ts +7 -16
- package/server/routes/dispatch-routes.ts +29 -8
- package/server/routes/manifest-routes.ts +1 -1
- package/server/routes/scope-routes.ts +12 -7
- package/server/schema.ts +1 -0
- package/server/services/batch-orchestrator.ts +18 -2
- package/server/services/config-service.ts +10 -1
- package/server/services/scope-service.ts +6 -6
- package/server/services/sprint-orchestrator.ts +24 -9
- package/server/services/sprint-service.ts +2 -2
- package/server/uninstall.ts +214 -0
- package/server/update.ts +263 -0
- package/server/utils/dispatch-utils.ts +8 -6
- package/server/utils/flag-builder.ts +56 -0
- package/server/utils/json-fields.test.ts +83 -0
- package/server/utils/json-fields.ts +14 -0
- package/server/utils/route-helpers.test.ts +144 -0
- package/server/utils/route-helpers.ts +38 -0
- package/server/watchers/event-watcher.ts +24 -12
- package/server/wizard/config-editor.ts +4 -4
- package/server/wizard/doctor.ts +2 -2
- package/server/wizard/index.ts +291 -40
- package/server/wizard/phases/welcome.ts +1 -5
- package/server/wizard/ui.ts +6 -7
- package/shared/api-types.ts +106 -0
- package/shared/workflow-engine.ts +1 -1
- package/templates/agents/QUICK-REFERENCE.md +1 -0
- package/templates/agents/README.md +1 -0
- package/templates/agents/SKILL-TRIGGERS.md +11 -0
- package/templates/agents/green-team/deep-dive.md +361 -0
- package/templates/hooks/end-session.sh +1 -0
- package/templates/hooks/init-session.sh +1 -0
- package/templates/hooks/scope-commit-logger.sh +2 -2
- package/templates/hooks/scope-create-gate.sh +2 -4
- package/templates/hooks/scope-gate.sh +4 -6
- package/templates/hooks/scope-helpers.sh +10 -1
- package/templates/hooks/scope-lifecycle-gate.sh +14 -5
- package/templates/hooks/scope-prepare.sh +1 -1
- package/templates/hooks/scope-transition.sh +14 -6
- package/templates/hooks/time-tracker.sh +2 -5
- package/templates/orbital.config.json +1 -4
- package/templates/presets/development.json +4 -4
- package/templates/presets/gitflow.json +7 -0
- package/templates/prompts/README.md +23 -0
- package/templates/prompts/deep-dive-audit.md +94 -0
- package/templates/quick/rules.md +56 -5
- package/templates/skills/git-commit/SKILL.md +21 -6
- package/templates/skills/git-dev/SKILL.md +8 -4
- package/templates/skills/git-main/SKILL.md +8 -4
- package/templates/skills/git-production/SKILL.md +6 -3
- package/templates/skills/git-staging/SKILL.md +6 -3
- package/templates/skills/scope-fix-review/SKILL.md +8 -4
- package/templates/skills/scope-implement/SKILL.md +13 -5
- package/templates/skills/scope-post-review/SKILL.md +16 -4
- package/templates/skills/scope-pre-review/SKILL.md +6 -2
- package/dist/assets/PrimitivesConfig-CrmQXYh4.js +0 -32
- package/dist/assets/QualityGates-BbasOsF3.js +0 -21
- package/dist/assets/SessionTimeline-CGeJsVvy.js +0 -1
- package/dist/assets/Settings-oiM496mc.js +0 -12
- package/dist/assets/SourceControl-B1fP2nJL.js +0 -41
- package/dist/assets/WorkflowVisualizer-CWLYf-f0.js +0 -74
- package/dist/assets/formatDistanceToNow-BMqsSP44.js +0 -1
- package/dist/assets/index-Aj4sV8Al.css +0 -1
- package/dist/assets/index-Bc9dK3MW.js +0 -354
- package/dist/assets/useWorkflowEditor-BJkTX_NR.js +0 -16
- package/dist/assets/zap-DfbUoOty.js +0 -11
- package/dist/server/server/services/telemetry-service.js +0 -143
- package/server/services/telemetry-service.ts +0 -195
- /package/{shared/default-workflow.json → templates/presets/default.json} +0 -0
package/server/wizard/index.ts
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
import fs from 'fs';
|
|
12
12
|
import path from 'path';
|
|
13
|
-
import
|
|
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
|
|
16
|
+
import { buildSetupState, buildProjectState } from './detect.js';
|
|
17
17
|
import { phaseSetupWizard } from './phases/setup-wizard.js';
|
|
18
18
|
import { phaseWelcome } from './phases/welcome.js';
|
|
19
19
|
import { phaseProjectSetup } from './phases/project-setup.js';
|
|
@@ -22,6 +22,8 @@ import { phaseConfirm, showPostInstall } from './phases/confirm.js';
|
|
|
22
22
|
import { NOTES } from './ui.js';
|
|
23
23
|
import { runConfigEditor } from './config-editor.js';
|
|
24
24
|
import { runDoctor } from './doctor.js';
|
|
25
|
+
import { isITerm2Available } from '../adapters/iterm2-adapter.js';
|
|
26
|
+
import { registerProject } from '../global-config.js';
|
|
25
27
|
|
|
26
28
|
export { runConfigEditor, runDoctor };
|
|
27
29
|
|
|
@@ -50,8 +52,8 @@ export async function runSetupWizard(packageVersion: string): Promise<void> {
|
|
|
50
52
|
|
|
51
53
|
p.outro(
|
|
52
54
|
state.linkedProjects.length > 0
|
|
53
|
-
? `Run ${pc.cyan('orbital
|
|
54
|
-
: `Run ${pc.cyan('orbital
|
|
55
|
+
? `Run ${pc.cyan('orbital')} to launch the dashboard.`
|
|
56
|
+
: `Run ${pc.cyan('orbital')} in a project directory to get started.`
|
|
55
57
|
);
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -73,7 +75,7 @@ export async function runProjectSetup(projectRoot: string, packageVersion: strin
|
|
|
73
75
|
|
|
74
76
|
await runProjectPhases(state, useForce);
|
|
75
77
|
|
|
76
|
-
p.outro(`Run ${pc.cyan('orbital
|
|
78
|
+
p.outro(`Run ${pc.cyan('orbital')} to launch the dashboard.`);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// ─── Shared project phases (used by both flows) ────────────────
|
|
@@ -104,7 +106,7 @@ async function runProjectPhases(state: ReturnType<typeof buildProjectState>, use
|
|
|
104
106
|
commands: state.selectedCommands,
|
|
105
107
|
});
|
|
106
108
|
|
|
107
|
-
registerProject(state.projectRoot, state.projectName);
|
|
109
|
+
registerProject(state.projectRoot, { name: state.projectName });
|
|
108
110
|
stampTemplateVersion(state.projectRoot, state.packageVersion);
|
|
109
111
|
|
|
110
112
|
s.stop('Project ready.');
|
|
@@ -128,47 +130,294 @@ async function runProjectSetupInline(projectRoot: string, packageVersion: string
|
|
|
128
130
|
await runProjectPhases(state, false);
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
// ───
|
|
133
|
+
// ─── Update Check ─────────────────────────────────────────────
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
interface UpdateInfo {
|
|
136
|
+
current: string;
|
|
137
|
+
latest: string;
|
|
138
|
+
isOutdated: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function checkForUpdate(
|
|
142
|
+
currentVersion: string,
|
|
143
|
+
cache: { lastUpdateCheck?: string; latestVersion?: string },
|
|
144
|
+
): Promise<{ info: UpdateInfo | null; cacheChanged: boolean }> {
|
|
145
|
+
// Use cache if checked within 24 hours
|
|
146
|
+
if (cache.lastUpdateCheck && cache.latestVersion) {
|
|
147
|
+
const age = Date.now() - new Date(cache.lastUpdateCheck).getTime();
|
|
148
|
+
if (age < 24 * 60 * 60 * 1000) {
|
|
149
|
+
const isOutdated = cache.latestVersion !== currentVersion;
|
|
150
|
+
return {
|
|
151
|
+
info: { current: currentVersion, latest: cache.latestVersion, isOutdated },
|
|
152
|
+
cacheChanged: false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
136
156
|
|
|
157
|
+
// Fetch from npm registry
|
|
137
158
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
159
|
+
const res = await fetch('https://registry.npmjs.org/orbital-command/latest', {
|
|
160
|
+
signal: AbortSignal.timeout(3000),
|
|
161
|
+
});
|
|
162
|
+
const data = await res.json() as { version: string };
|
|
163
|
+
const latest = data.version;
|
|
164
|
+
return {
|
|
165
|
+
info: { current: currentVersion, latest, isOutdated: latest !== currentVersion },
|
|
166
|
+
cacheChanged: true,
|
|
167
|
+
};
|
|
141
168
|
} catch {
|
|
142
|
-
|
|
169
|
+
return { info: null, cacheChanged: false };
|
|
143
170
|
}
|
|
171
|
+
}
|
|
144
172
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
// ─── Hub Menu ─────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
export type HubAction = 'launch' | 'init' | 'config' | 'doctor' | 'update' | 'status' | 'reset';
|
|
176
|
+
|
|
177
|
+
export interface HubResult {
|
|
178
|
+
action: HubAction;
|
|
179
|
+
setItermPromptShown?: boolean;
|
|
180
|
+
updateCache?: { lastUpdateCheck: string; latestVersion?: string };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Context-aware hub menu — the main entry point for `orbital` (no args).
|
|
185
|
+
* Checks for updates, offers template sync, shows iTerm2 recommendation, then menu.
|
|
186
|
+
*/
|
|
187
|
+
export async function runHub(opts: {
|
|
188
|
+
packageVersion: string;
|
|
189
|
+
isProjectInitialized: boolean;
|
|
190
|
+
projectNames: string[];
|
|
191
|
+
itermPromptShown: boolean;
|
|
192
|
+
isMac: boolean;
|
|
193
|
+
lastUpdateCheck?: string;
|
|
194
|
+
latestVersion?: string;
|
|
195
|
+
projectPaths: Array<{ name: string; path: string }>;
|
|
196
|
+
}): Promise<HubResult> {
|
|
197
|
+
const result: HubResult = { action: 'launch' };
|
|
198
|
+
|
|
199
|
+
p.intro(`${pc.bgCyan(pc.black(' Orbital Command '))} ${pc.dim(`v${opts.packageVersion}`)}`);
|
|
200
|
+
|
|
201
|
+
// ── Update check ──
|
|
202
|
+
const updateCheck = await checkForUpdate(opts.packageVersion, {
|
|
203
|
+
lastUpdateCheck: opts.lastUpdateCheck,
|
|
204
|
+
latestVersion: opts.latestVersion,
|
|
169
205
|
});
|
|
170
206
|
|
|
171
|
-
|
|
207
|
+
if (updateCheck.cacheChanged) {
|
|
208
|
+
result.updateCache = {
|
|
209
|
+
lastUpdateCheck: new Date().toISOString(),
|
|
210
|
+
latestVersion: updateCheck.info?.latest,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (updateCheck.info?.isOutdated) {
|
|
215
|
+
p.log.info(
|
|
216
|
+
`Update available: ${pc.dim(`v${updateCheck.info.current}`)} → ${pc.cyan(`v${updateCheck.info.latest}`)}`
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const updateChoice = await p.select({
|
|
220
|
+
message: 'Update Orbital Command now?',
|
|
221
|
+
options: [
|
|
222
|
+
{ value: 'update', label: 'Yes, update' },
|
|
223
|
+
{ value: 'skip', label: 'Skip for now' },
|
|
224
|
+
],
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (!p.isCancel(updateChoice) && updateChoice === 'update') {
|
|
228
|
+
const s = p.spinner();
|
|
229
|
+
s.start('Updating Orbital Command...');
|
|
230
|
+
try {
|
|
231
|
+
execFileSync('npm', ['update', '-g', 'orbital-command'], { stdio: 'pipe', timeout: 60000 });
|
|
232
|
+
s.stop(`Updated to v${updateCheck.info.latest}!`);
|
|
233
|
+
p.outro(`Run ${pc.cyan('orbital')} again to use the new version.`);
|
|
234
|
+
process.exit(0);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
s.stop('Update failed.');
|
|
237
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
238
|
+
if (msg.includes('EACCES') || msg.includes('permission denied')) {
|
|
239
|
+
p.log.error('Permission denied. Try running with sudo or ensure npm is installed via nvm.');
|
|
240
|
+
} else if (msg.includes('ETIMEDOUT') || msg.includes('timeout')) {
|
|
241
|
+
p.log.error('Update timed out. Check your network connection and try again.');
|
|
242
|
+
} else {
|
|
243
|
+
p.log.error(msg);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── Template staleness check ──
|
|
250
|
+
if (opts.projectPaths.length > 0) {
|
|
251
|
+
const mod = await import('../manifest.js');
|
|
252
|
+
const initMod = await import('../init.js');
|
|
253
|
+
const outdatedProjects: Array<{
|
|
254
|
+
name: string;
|
|
255
|
+
path: string;
|
|
256
|
+
details: string[];
|
|
257
|
+
}> = [];
|
|
258
|
+
|
|
259
|
+
for (const proj of opts.projectPaths) {
|
|
260
|
+
if (!fs.existsSync(proj.path)) {
|
|
261
|
+
p.log.warn(`${proj.name}: project path not found (${proj.path})`);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const manifest = mod.loadManifest(proj.path);
|
|
265
|
+
if (!manifest) continue;
|
|
266
|
+
const claudeDir = path.join(proj.path, '.claude');
|
|
267
|
+
mod.refreshFileStatuses(manifest, claudeDir);
|
|
268
|
+
const summary = mod.summarizeManifest(manifest);
|
|
269
|
+
const parts = Object.entries(summary.byType)
|
|
270
|
+
.filter(([, counts]) => counts.outdated > 0)
|
|
271
|
+
.map(([type, counts]) => `${counts.outdated} ${type}`);
|
|
272
|
+
if (parts.length > 0) {
|
|
273
|
+
outdatedProjects.push({ name: proj.name, path: proj.path, details: parts });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (outdatedProjects.length > 0) {
|
|
278
|
+
const lines = outdatedProjects.map(proj =>
|
|
279
|
+
` ${pc.cyan(proj.name.padEnd(16))} ${proj.details.join(', ')} outdated`
|
|
280
|
+
);
|
|
281
|
+
const count = outdatedProjects.length;
|
|
282
|
+
p.note(lines.join('\n'), `${count} project${count > 1 ? 's have' : ' has'} outdated templates`);
|
|
283
|
+
|
|
284
|
+
const syncChoice = await p.select({
|
|
285
|
+
message: 'Update project templates now?',
|
|
286
|
+
options: [
|
|
287
|
+
{ value: 'update', label: 'Yes, update all safe files', hint: 'skips modified and pinned' },
|
|
288
|
+
{ value: 'skip', label: 'Skip for now' },
|
|
289
|
+
],
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (!p.isCancel(syncChoice) && syncChoice === 'update') {
|
|
293
|
+
for (const proj of outdatedProjects) {
|
|
294
|
+
const s = p.spinner();
|
|
295
|
+
s.start(`Updating ${proj.name}...`);
|
|
296
|
+
try {
|
|
297
|
+
initMod.runUpdate(proj.path, { dryRun: false });
|
|
298
|
+
s.stop(`${proj.name} updated.`);
|
|
299
|
+
} catch (err) {
|
|
300
|
+
s.stop(`${proj.name} failed.`);
|
|
301
|
+
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── iTerm2 recommendation (macOS only, one-time) ──
|
|
309
|
+
if (opts.isMac && !opts.itermPromptShown && !isITerm2Available()) {
|
|
310
|
+
p.note(
|
|
311
|
+
`Sprint dispatch, batch orchestration, and session management\n` +
|
|
312
|
+
`use iTerm2 tabs to run parallel Claude Code sessions.\n` +
|
|
313
|
+
`Without it, sessions fall back to basic subprocess mode.`,
|
|
314
|
+
'iTerm2 Recommended',
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const itermChoice = await p.select({
|
|
318
|
+
message: 'Install iTerm2?',
|
|
319
|
+
options: [
|
|
320
|
+
{ value: 'install', label: 'Open download page', hint: 'https://iterm2.com' },
|
|
321
|
+
{ value: 'skip', label: 'Skip for now' },
|
|
322
|
+
],
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
result.setItermPromptShown = true;
|
|
326
|
+
|
|
327
|
+
if (!p.isCancel(itermChoice) && itermChoice === 'install') {
|
|
328
|
+
spawn('open', ['https://iterm2.com'], { detached: true, stdio: 'ignore' }).unref();
|
|
329
|
+
p.log.info('Waiting for iTerm2 to install... (press any key to skip)');
|
|
330
|
+
|
|
331
|
+
await new Promise<void>((resolve) => {
|
|
332
|
+
let done = false;
|
|
333
|
+
const cleanup = (): void => {
|
|
334
|
+
if (done) return;
|
|
335
|
+
done = true;
|
|
336
|
+
process.stdin.setRawMode?.(false);
|
|
337
|
+
process.stdin.removeListener('data', onKey);
|
|
338
|
+
process.stdin.pause();
|
|
339
|
+
clearInterval(timer);
|
|
340
|
+
resolve();
|
|
341
|
+
};
|
|
342
|
+
const onKey = (): void => { cleanup(); };
|
|
343
|
+
const startTime = Date.now();
|
|
344
|
+
const MAX_WAIT = 10 * 60 * 1000; // 10 minutes
|
|
345
|
+
const timer = setInterval(() => {
|
|
346
|
+
if (isITerm2Available()) {
|
|
347
|
+
p.log.success('iTerm2 detected!');
|
|
348
|
+
cleanup();
|
|
349
|
+
} else if (Date.now() - startTime > MAX_WAIT) {
|
|
350
|
+
cleanup();
|
|
351
|
+
}
|
|
352
|
+
}, 3000);
|
|
353
|
+
process.stdin.setRawMode?.(true);
|
|
354
|
+
process.stdin.resume();
|
|
355
|
+
process.stdin.on('data', onKey);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ── Build menu options based on project state ──
|
|
361
|
+
const projectHint = opts.projectNames.length > 0
|
|
362
|
+
? pc.dim(` (${opts.projectNames.join(', ')})`)
|
|
363
|
+
: '';
|
|
364
|
+
|
|
365
|
+
const options: Array<{ value: HubAction; label: string; hint?: string }> = [];
|
|
366
|
+
|
|
367
|
+
if (opts.isProjectInitialized) {
|
|
368
|
+
options.push(
|
|
369
|
+
{ value: 'launch', label: `Launch dashboard${projectHint}` },
|
|
370
|
+
{ value: 'config', label: 'Config', hint: 'modify project settings' },
|
|
371
|
+
{ value: 'doctor', label: 'Doctor', hint: 'health check & diagnostics' },
|
|
372
|
+
{ value: 'update', label: 'Update templates', hint: 'sync to latest' },
|
|
373
|
+
{ value: 'status', label: 'Status', hint: 'template sync status' },
|
|
374
|
+
{ value: 'reset', label: 'Reset to defaults', hint: 'force-reset all templates' },
|
|
375
|
+
);
|
|
376
|
+
} else {
|
|
377
|
+
options.push(
|
|
378
|
+
{ value: 'init', label: 'Initialize this project' },
|
|
379
|
+
{ value: 'launch', label: `Launch dashboard${projectHint}` },
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const action = await p.select({
|
|
384
|
+
message: 'What would you like to do?',
|
|
385
|
+
options,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
if (p.isCancel(action)) {
|
|
389
|
+
p.cancel('Cancelled.');
|
|
390
|
+
process.exit(0);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ── Double-confirm for destructive reset ──
|
|
394
|
+
if (action === 'reset') {
|
|
395
|
+
p.note(
|
|
396
|
+
'This will overwrite ALL hooks, skills, agents, and workflow config\n' +
|
|
397
|
+
'with the default templates. Modified and pinned files will be replaced.\n' +
|
|
398
|
+
'Your scopes, database, and orbital.config.json are preserved.',
|
|
399
|
+
'Warning',
|
|
400
|
+
);
|
|
401
|
+
const confirmReset = await p.confirm({
|
|
402
|
+
message: 'Are you sure you want to reset all templates?',
|
|
403
|
+
initialValue: false,
|
|
404
|
+
});
|
|
405
|
+
if (p.isCancel(confirmReset) || !confirmReset) {
|
|
406
|
+
p.cancel('Reset cancelled.');
|
|
407
|
+
process.exit(0);
|
|
408
|
+
}
|
|
409
|
+
const doubleConfirm = await p.confirm({
|
|
410
|
+
message: 'This cannot be undone. Continue?',
|
|
411
|
+
initialValue: false,
|
|
412
|
+
});
|
|
413
|
+
if (p.isCancel(doubleConfirm) || !doubleConfirm) {
|
|
414
|
+
p.cancel('Reset cancelled.');
|
|
415
|
+
process.exit(0);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
result.action = action;
|
|
420
|
+
return result;
|
|
172
421
|
}
|
|
173
422
|
|
|
174
423
|
// ─── Template Version Stamping ─────────────────────────────────
|
|
@@ -181,7 +430,9 @@ function stampTemplateVersion(projectRoot: string, packageVersion: string): void
|
|
|
181
430
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
182
431
|
if (config.templateVersion !== packageVersion) {
|
|
183
432
|
config.templateVersion = packageVersion;
|
|
184
|
-
|
|
433
|
+
const tmp = configPath + `.tmp.${process.pid}`;
|
|
434
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
435
|
+
fs.renameSync(tmp, configPath);
|
|
185
436
|
}
|
|
186
437
|
} catch { /* ignore malformed config */ }
|
|
187
438
|
}
|
|
@@ -18,8 +18,7 @@ export async function phaseWelcome(state: ProjectSetupState): Promise<boolean> {
|
|
|
18
18
|
const action = await p.select({
|
|
19
19
|
message: 'What would you like to do?',
|
|
20
20
|
options: [
|
|
21
|
-
{ value: '
|
|
22
|
-
{ value: 'configure', label: 'Open config editor', hint: 'modify settings without resetting' },
|
|
21
|
+
{ value: 'configure', label: 'Open config editor', hint: 'modify settings' },
|
|
23
22
|
{ value: 'cancel', label: 'Cancel' },
|
|
24
23
|
],
|
|
25
24
|
});
|
|
@@ -33,9 +32,6 @@ export async function phaseWelcome(state: ProjectSetupState): Promise<boolean> {
|
|
|
33
32
|
await runConfigEditor(state.projectRoot, state.packageVersion, []);
|
|
34
33
|
process.exit(0);
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
// Re-init — continue through the full project setup with force
|
|
38
|
-
return true;
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
// Not initialized — continue normally
|
package/server/wizard/ui.ts
CHANGED
|
@@ -19,16 +19,15 @@ ${pc.cyan('.claude/')} directory — no database or external service required.`,
|
|
|
19
19
|
|
|
20
20
|
setupComplete: `${pc.bold('Setup complete.')}
|
|
21
21
|
|
|
22
|
-
${pc.cyan('orbital
|
|
23
|
-
${pc.cyan('orbital launch --open')} Start the dashboard
|
|
22
|
+
${pc.cyan('orbital')} Add a project or launch the dashboard
|
|
24
23
|
${pc.cyan('orbital doctor')} Health check & version info`,
|
|
25
24
|
|
|
26
|
-
addProject: `You can add projects now or later
|
|
25
|
+
addProject: `You can add projects now or later by running ${pc.cyan('orbital')} in a project directory.
|
|
27
26
|
Each project gets its own workflow, scopes, and quality gates.`,
|
|
28
27
|
|
|
29
28
|
// Phase 2: Project setup (runs per-project)
|
|
30
29
|
reconfigure: `This project is already initialized with Orbital Command.
|
|
31
|
-
You can reconfigure settings or
|
|
30
|
+
You can reconfigure settings or select ${pc.cyan('Reset to defaults')} from the hub menu.`,
|
|
32
31
|
|
|
33
32
|
projectConfig: `${pc.bold('Project Config')} ${pc.dim('(.claude/orbital.config.json)')}
|
|
34
33
|
|
|
@@ -56,16 +55,16 @@ ${pc.cyan('Quality Gates')} Automated checks (lint, typecheck, tests) before tr
|
|
|
56
55
|
|
|
57
56
|
nextSteps: `${pc.bold('Next Steps')}
|
|
58
57
|
|
|
59
|
-
1. ${pc.cyan('orbital
|
|
58
|
+
1. Run ${pc.cyan('orbital')} and select ${pc.bold('Launch dashboard')}
|
|
60
59
|
2. Create a scope from the board or use ${pc.cyan('/scope-create')}
|
|
61
60
|
3. Use ${pc.cyan('/scope-implement')} to start working on a scope
|
|
62
61
|
|
|
63
62
|
${pc.bold('Useful Commands')}
|
|
64
63
|
|
|
64
|
+
${pc.cyan('orbital')} Hub menu — launch, config, doctor, etc.
|
|
65
65
|
${pc.cyan('orbital status')} See template sync status
|
|
66
66
|
${pc.cyan('orbital config')} Modify project settings
|
|
67
|
-
${pc.cyan('orbital update')} Sync to latest templates
|
|
68
|
-
${pc.cyan('orbital doctor')} Health check & version info`,
|
|
67
|
+
${pc.cyan('orbital update')} Sync to latest templates`,
|
|
69
68
|
};
|
|
70
69
|
|
|
71
70
|
// ─── Formatting Helpers ─────────────────────────────────────────
|
package/shared/api-types.ts
CHANGED
|
@@ -18,3 +18,109 @@ export interface AgentConfig {
|
|
|
18
18
|
emoji: string;
|
|
19
19
|
color: string;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
// ─── Dispatch Flags (CLI flags passed to `claude`) ──────────
|
|
23
|
+
|
|
24
|
+
export interface DispatchFlags {
|
|
25
|
+
permissionMode: 'bypass' | 'default' | 'plan' | 'acceptEdits';
|
|
26
|
+
verbose: boolean;
|
|
27
|
+
allowedTools: string[];
|
|
28
|
+
disallowedTools: string[];
|
|
29
|
+
appendSystemPrompt: string;
|
|
30
|
+
outputFormat: '' | 'text' | 'json' | 'stream-json';
|
|
31
|
+
noMarkdown: boolean;
|
|
32
|
+
printMode: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_DISPATCH_FLAGS: DispatchFlags = {
|
|
36
|
+
permissionMode: 'bypass',
|
|
37
|
+
verbose: true,
|
|
38
|
+
allowedTools: [],
|
|
39
|
+
disallowedTools: [],
|
|
40
|
+
appendSystemPrompt: '',
|
|
41
|
+
outputFormat: '',
|
|
42
|
+
noMarkdown: false,
|
|
43
|
+
printMode: false,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ─── Dispatch Config (Orbital operational settings) ─────────
|
|
47
|
+
|
|
48
|
+
export interface DispatchConfig {
|
|
49
|
+
staleTimeoutMinutes: number;
|
|
50
|
+
maxBatchSize: number;
|
|
51
|
+
maxConcurrent: number;
|
|
52
|
+
envVars: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const DEFAULT_DISPATCH_CONFIG: DispatchConfig = {
|
|
56
|
+
staleTimeoutMinutes: 10,
|
|
57
|
+
maxBatchSize: 20,
|
|
58
|
+
maxConcurrent: 0,
|
|
59
|
+
envVars: {},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ─── Validation ─────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
export const VALID_PERMISSION_MODES = ['bypass', 'default', 'plan', 'acceptEdits'] as const;
|
|
65
|
+
export const VALID_OUTPUT_FORMATS = ['', 'text', 'json', 'stream-json'];
|
|
66
|
+
export const VALID_TERMINAL_ADAPTERS = ['auto', 'iterm2', 'subprocess', 'none'];
|
|
67
|
+
|
|
68
|
+
const SAFE_TOOL_NAME = /^[a-zA-Z0-9_:.-]+$/;
|
|
69
|
+
const SAFE_ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
70
|
+
|
|
71
|
+
export function validateToolName(name: string): boolean {
|
|
72
|
+
return SAFE_TOOL_NAME.test(name);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function validateEnvKey(key: string): boolean {
|
|
76
|
+
return SAFE_ENV_KEY.test(key);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function validateDispatchFlags(flags: Partial<DispatchFlags>): string | null {
|
|
80
|
+
if (flags.permissionMode !== undefined && !VALID_PERMISSION_MODES.includes(flags.permissionMode as typeof VALID_PERMISSION_MODES[number])) {
|
|
81
|
+
return `Invalid permissionMode: ${flags.permissionMode}`;
|
|
82
|
+
}
|
|
83
|
+
if (flags.outputFormat !== undefined && !VALID_OUTPUT_FORMATS.includes(flags.outputFormat as string)) {
|
|
84
|
+
return `Invalid outputFormat: ${flags.outputFormat}`;
|
|
85
|
+
}
|
|
86
|
+
if (flags.allowedTools !== undefined) {
|
|
87
|
+
if (!Array.isArray(flags.allowedTools)) return 'allowedTools must be an array';
|
|
88
|
+
for (const t of flags.allowedTools) {
|
|
89
|
+
if (typeof t !== 'string' || !validateToolName(t)) return `Invalid tool name: ${t}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (flags.disallowedTools !== undefined) {
|
|
93
|
+
if (!Array.isArray(flags.disallowedTools)) return 'disallowedTools must be an array';
|
|
94
|
+
for (const t of flags.disallowedTools) {
|
|
95
|
+
if (typeof t !== 'string' || !validateToolName(t)) return `Invalid tool name: ${t}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (flags.appendSystemPrompt !== undefined && typeof flags.appendSystemPrompt !== 'string') {
|
|
99
|
+
return 'appendSystemPrompt must be a string';
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function validateDispatchConfig(config: Partial<DispatchConfig> & { terminalAdapter?: string }): string | null {
|
|
105
|
+
if (config.terminalAdapter !== undefined && !VALID_TERMINAL_ADAPTERS.includes(config.terminalAdapter)) {
|
|
106
|
+
return `Invalid terminalAdapter: ${config.terminalAdapter}`;
|
|
107
|
+
}
|
|
108
|
+
if (config.staleTimeoutMinutes !== undefined && (typeof config.staleTimeoutMinutes !== 'number' || config.staleTimeoutMinutes < 1)) {
|
|
109
|
+
return 'staleTimeoutMinutes must be a positive number';
|
|
110
|
+
}
|
|
111
|
+
if (config.maxBatchSize !== undefined && (typeof config.maxBatchSize !== 'number' || config.maxBatchSize < 1)) {
|
|
112
|
+
return 'maxBatchSize must be a positive number';
|
|
113
|
+
}
|
|
114
|
+
if (config.maxConcurrent !== undefined && (typeof config.maxConcurrent !== 'number' || config.maxConcurrent < 0)) {
|
|
115
|
+
return 'maxConcurrent must be a non-negative number';
|
|
116
|
+
}
|
|
117
|
+
if (config.envVars !== undefined) {
|
|
118
|
+
if (typeof config.envVars !== 'object' || config.envVars === null || Array.isArray(config.envVars)) {
|
|
119
|
+
return 'envVars must be an object';
|
|
120
|
+
}
|
|
121
|
+
for (const key of Object.keys(config.envVars)) {
|
|
122
|
+
if (!validateEnvKey(key)) return `Invalid env var key: ${key}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
@@ -385,7 +385,7 @@ export class WorkflowEngine {
|
|
|
385
385
|
if (!targetList) continue;
|
|
386
386
|
// Generate aliases for deployment-group targets (dev, staging, production)
|
|
387
387
|
const group = targetList.group;
|
|
388
|
-
if (group
|
|
388
|
+
if (group?.startsWith('deploy')) {
|
|
389
389
|
const sessionKey = targetList.sessionKey ?? '';
|
|
390
390
|
lines.push(` "to-${edge.to}:${edge.from}:${edge.to}:${sessionKey}"`);
|
|
391
391
|
}
|
|
@@ -90,6 +90,7 @@ Before committing:
|
|
|
90
90
|
| Chaos | "What breaks when things go wrong?" |
|
|
91
91
|
| Frontend Designer | "What does the user see/experience?" |
|
|
92
92
|
| Architect | "Does this fit our patterns?" |
|
|
93
|
+
| Deep Dive | "What's the full picture before we change anything?" |
|
|
93
94
|
| Rules Enforcer | "Do all project rules pass?" |
|
|
94
95
|
|
|
95
96
|
---
|
|
@@ -86,6 +86,7 @@ Every enforcement rule exists because a past mistake taught us it matters. Bypas
|
|
|
86
86
|
│ "Protect quality and standards" │
|
|
87
87
|
│ │
|
|
88
88
|
│ 🏗️ Architect - Patterns, structure, module boundaries │
|
|
89
|
+
│ 🔬 Deep Dive - Codebase audits, refactors, health reviews │
|
|
89
90
|
│ 📋 Rules Enforcer - Non-negotiable project rules (automated) │
|
|
90
91
|
│ │
|
|
91
92
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
@@ -85,6 +85,17 @@ These are **automatic** suggestions based on session state, not user phrases.
|
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
+
## Agent Auto-Triggers by Phrase
|
|
89
|
+
|
|
90
|
+
| User Says | Agent | Why |
|
|
91
|
+
|-----------|-------|-----|
|
|
92
|
+
| "Refactor", "clean up", "simplify" | 🔬 Deep Dive | Codebase health review needed |
|
|
93
|
+
| "Tech debt", "health check", "audit" | 🔬 Deep Dive | Structural assessment |
|
|
94
|
+
| "Pre-launch review", "before we release" | 🔬 Deep Dive | Comprehensive pre-release audit |
|
|
95
|
+
| "Deep dive", "thorough review" | 🔬 Deep Dive | Explicit invocation |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
88
99
|
## Priority Order
|
|
89
100
|
|
|
90
101
|
When multiple skills could apply, prioritize:
|