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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
import { errMsg, isValidRelativePath, inferErrorStatus, catchRoute } from './route-helpers.js';
|
|
5
|
+
|
|
6
|
+
// ─── errMsg ─────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
describe('errMsg', () => {
|
|
9
|
+
it('extracts message from Error instances', () => {
|
|
10
|
+
expect(errMsg(new Error('something broke'))).toBe('something broke');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('converts non-Error values to strings', () => {
|
|
14
|
+
expect(errMsg('raw string')).toBe('raw string');
|
|
15
|
+
expect(errMsg(42)).toBe('42');
|
|
16
|
+
expect(errMsg(null)).toBe('null');
|
|
17
|
+
expect(errMsg(undefined)).toBe('undefined');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// ─── isValidRelativePath ────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
describe('isValidRelativePath', () => {
|
|
24
|
+
it('accepts normal relative paths', () => {
|
|
25
|
+
expect(isValidRelativePath('hooks/init.sh')).toBe(true);
|
|
26
|
+
expect(isValidRelativePath('agents/attacker/AGENT.md')).toBe(true);
|
|
27
|
+
expect(isValidRelativePath('file.txt')).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('rejects directory traversal', () => {
|
|
31
|
+
expect(isValidRelativePath('../etc/passwd')).toBe(false);
|
|
32
|
+
expect(isValidRelativePath('hooks/../../secret')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('rejects absolute paths', () => {
|
|
36
|
+
expect(isValidRelativePath('/etc/passwd')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('rejects null bytes', () => {
|
|
40
|
+
expect(isValidRelativePath('file\0.txt')).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ─── inferErrorStatus ───────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
describe('inferErrorStatus', () => {
|
|
47
|
+
it('returns 403 for traversal errors', () => {
|
|
48
|
+
expect(inferErrorStatus('Path traversal detected')).toBe(403);
|
|
49
|
+
expect(inferErrorStatus('directory traversal not allowed')).toBe(403);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('returns 404 for not-found errors', () => {
|
|
53
|
+
expect(inferErrorStatus('File not found')).toBe(404);
|
|
54
|
+
expect(inferErrorStatus('ENOENT: no such file')).toBe(404);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns 409 for already-exists errors', () => {
|
|
58
|
+
expect(inferErrorStatus('File already exists at path')).toBe(409);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('returns 400 for directory errors', () => {
|
|
62
|
+
expect(inferErrorStatus('Cannot delete directory')).toBe(400);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns 500 for unrecognized errors', () => {
|
|
66
|
+
expect(inferErrorStatus('something unexpected')).toBe(500);
|
|
67
|
+
expect(inferErrorStatus('')).toBe(500);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('matches the first keyword when multiple are present', () => {
|
|
71
|
+
// "traversal" comes first in the chain, should win
|
|
72
|
+
expect(inferErrorStatus('traversal not found')).toBe(403);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ─── catchRoute ─────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
describe('catchRoute', () => {
|
|
79
|
+
function createApp(handler: express.RequestHandler) {
|
|
80
|
+
const app = express();
|
|
81
|
+
app.get('/test', handler);
|
|
82
|
+
return app;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
it('returns normal response when handler succeeds', async () => {
|
|
86
|
+
const app = createApp(catchRoute((_req, res) => {
|
|
87
|
+
res.json({ ok: true });
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
const res = await request(app).get('/test');
|
|
91
|
+
expect(res.status).toBe(200);
|
|
92
|
+
expect(res.body).toEqual({ ok: true });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('catches sync throws and returns 500', async () => {
|
|
96
|
+
const app = createApp(catchRoute(() => {
|
|
97
|
+
throw new Error('sync failure');
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
const res = await request(app).get('/test');
|
|
101
|
+
expect(res.status).toBe(500);
|
|
102
|
+
expect(res.body).toEqual({ success: false, error: 'sync failure' });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('catches async throws and returns 500', async () => {
|
|
106
|
+
const app = createApp(catchRoute(async () => {
|
|
107
|
+
throw new Error('async failure');
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
const res = await request(app).get('/test');
|
|
111
|
+
expect(res.status).toBe(500);
|
|
112
|
+
expect(res.body).toEqual({ success: false, error: 'async failure' });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('uses custom statusFn for error status codes', async () => {
|
|
116
|
+
const app = createApp(catchRoute(() => {
|
|
117
|
+
throw new Error('File not found at path');
|
|
118
|
+
}, inferErrorStatus));
|
|
119
|
+
|
|
120
|
+
const res = await request(app).get('/test');
|
|
121
|
+
expect(res.status).toBe(404);
|
|
122
|
+
expect(res.body).toEqual({ success: false, error: 'File not found at path' });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('uses custom statusFn for traversal errors', async () => {
|
|
126
|
+
const app = createApp(catchRoute(() => {
|
|
127
|
+
throw new Error('Path traversal detected');
|
|
128
|
+
}, inferErrorStatus));
|
|
129
|
+
|
|
130
|
+
const res = await request(app).get('/test');
|
|
131
|
+
expect(res.status).toBe(403);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('handles non-Error thrown values', async () => {
|
|
135
|
+
const app = createApp(catchRoute(() => {
|
|
136
|
+
// eslint-disable-next-line no-throw-literal
|
|
137
|
+
throw 'raw string error';
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
const res = await request(app).get('/test');
|
|
141
|
+
expect(res.status).toBe(500);
|
|
142
|
+
expect(res.body).toEqual({ success: false, error: 'raw string error' });
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import type { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
2
3
|
|
|
3
4
|
/** Extract a human-readable message from an unknown error value. */
|
|
4
5
|
export function errMsg(err: unknown): string {
|
|
@@ -10,3 +11,40 @@ export function isValidRelativePath(p: string): boolean {
|
|
|
10
11
|
const normalized = path.normalize(p);
|
|
11
12
|
return !normalized.startsWith('..') && !path.isAbsolute(normalized) && !normalized.includes('\0');
|
|
12
13
|
}
|
|
14
|
+
|
|
15
|
+
/** Infer an HTTP status code from an error message. */
|
|
16
|
+
export function inferErrorStatus(msg: string): number {
|
|
17
|
+
if (msg.includes('traversal')) return 403;
|
|
18
|
+
if (msg.includes('ENOENT') || msg.includes('not found')) return 404;
|
|
19
|
+
if (msg.includes('already exists')) return 409;
|
|
20
|
+
if (msg.includes('directory')) return 400;
|
|
21
|
+
return 500;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Wrap an Express route handler to catch thrown errors and send a JSON error response.
|
|
26
|
+
* Works with both sync and async handlers.
|
|
27
|
+
*
|
|
28
|
+
* @param fn — route handler that may throw
|
|
29
|
+
* @param statusFn — optional function to infer status from error message (defaults to 500)
|
|
30
|
+
*/
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
export function catchRoute<Req extends Request = any, Res extends Response = any>(
|
|
33
|
+
fn: (req: Req, res: Res, next: NextFunction) => void | Promise<void>,
|
|
34
|
+
statusFn?: (msg: string) => number,
|
|
35
|
+
): RequestHandler {
|
|
36
|
+
return ((req: Req, res: Res, next: NextFunction) => {
|
|
37
|
+
try {
|
|
38
|
+
const result = fn(req, res, next);
|
|
39
|
+
if (result instanceof Promise) {
|
|
40
|
+
result.catch((err: unknown) => {
|
|
41
|
+
const msg = errMsg(err);
|
|
42
|
+
res.status(statusFn ? statusFn(msg) : 500).json({ success: false, error: msg });
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const msg = errMsg(err);
|
|
47
|
+
res.status(statusFn ? statusFn(msg) : 500).json({ success: false, error: msg });
|
|
48
|
+
}
|
|
49
|
+
}) as unknown as RequestHandler;
|
|
50
|
+
}
|
|
@@ -8,6 +8,7 @@ import { createLogger } from '../utils/logger.js';
|
|
|
8
8
|
const log = createLogger('event');
|
|
9
9
|
|
|
10
10
|
const ARCHIVE_DIR_NAME = 'processed';
|
|
11
|
+
const processingFiles = new Set<string>();
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Watch .claude/orbital-events/ for new JSON event files.
|
|
@@ -81,20 +82,31 @@ function processEventFile(
|
|
|
81
82
|
eventService: EventService,
|
|
82
83
|
archiveDir: string
|
|
83
84
|
): void {
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
if (processingFiles.has(filePath)) return;
|
|
86
|
+
processingFiles.add(filePath);
|
|
87
|
+
try {
|
|
88
|
+
const event = parseEventFile(filePath);
|
|
89
|
+
if (!event) return;
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
eventService.ingest(event);
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
// Move to archive
|
|
94
|
+
const fileName = path.basename(filePath);
|
|
95
|
+
try {
|
|
96
|
+
fs.renameSync(filePath, path.join(archiveDir, fileName));
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
99
|
+
if (code === 'ENOENT') return; // Already archived by concurrent handler
|
|
100
|
+
log.warn('Failed to archive event file', { file: filePath, error: (err as Error).message });
|
|
101
|
+
// If rename fails (cross-device), just delete the source
|
|
102
|
+
try { fs.unlinkSync(filePath); } catch (unlinkErr) {
|
|
103
|
+
const unlinkCode = (unlinkErr as NodeJS.ErrnoException).code;
|
|
104
|
+
if (unlinkCode !== 'ENOENT') {
|
|
105
|
+
log.warn('Failed to delete event file', { file: filePath, error: (unlinkErr as Error).message });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
98
108
|
}
|
|
109
|
+
} finally {
|
|
110
|
+
processingFiles.delete(filePath);
|
|
99
111
|
}
|
|
100
112
|
}
|
|
@@ -15,7 +15,7 @@ export async function runConfigEditor(projectRoot: string, packageVersion: strin
|
|
|
15
15
|
if (subcommand === 'show') {
|
|
16
16
|
const config = loadProjectConfig(projectRoot);
|
|
17
17
|
if (!config) {
|
|
18
|
-
console.error('No config found. Run `orbital
|
|
18
|
+
console.error('No config found. Run `orbital` first.');
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
21
|
console.log(JSON.stringify(config, null, 2));
|
|
@@ -37,7 +37,7 @@ export async function runConfigEditor(projectRoot: string, packageVersion: strin
|
|
|
37
37
|
// Interactive mode
|
|
38
38
|
const config = loadProjectConfig(projectRoot);
|
|
39
39
|
if (!config) {
|
|
40
|
-
p.log.error('No config found. Run `orbital
|
|
40
|
+
p.log.error('No config found. Run `orbital` first.');
|
|
41
41
|
process.exit(1);
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -166,7 +166,7 @@ async function editGlobalSection(): Promise<void> {
|
|
|
166
166
|
const registryPath = path.join(homedir, '.orbital', 'config.json');
|
|
167
167
|
|
|
168
168
|
if (!fs.existsSync(registryPath)) {
|
|
169
|
-
p.log.info('No global registry found. Run `orbital
|
|
169
|
+
p.log.info('No global registry found. Run `orbital` in a project first.');
|
|
170
170
|
return;
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -210,7 +210,7 @@ function saveProjectConfig(projectRoot: string, config: Record<string, unknown>)
|
|
|
210
210
|
function setConfigValue(projectRoot: string, key: string, value: string): void {
|
|
211
211
|
const config = loadProjectConfig(projectRoot);
|
|
212
212
|
if (!config) {
|
|
213
|
-
console.error('No config found. Run `orbital
|
|
213
|
+
console.error('No config found. Run `orbital` first.');
|
|
214
214
|
process.exit(1);
|
|
215
215
|
}
|
|
216
216
|
|
package/server/wizard/doctor.ts
CHANGED
|
@@ -49,7 +49,7 @@ export async function runDoctor(projectRoot: string, packageVersion: string): Pr
|
|
|
49
49
|
checks.push({ label: 'Global', status: pc.yellow('~/.orbital/ exists (registry unreadable)') });
|
|
50
50
|
}
|
|
51
51
|
} else {
|
|
52
|
-
checks.push({ label: 'Global', status: pc.dim('~/.orbital/ not found (run `orbital
|
|
52
|
+
checks.push({ label: 'Global', status: pc.dim('~/.orbital/ not found (run `orbital` to create)') });
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// 4. Project initialization
|
|
@@ -63,7 +63,7 @@ export async function runDoctor(projectRoot: string, packageVersion: string): Pr
|
|
|
63
63
|
checks.push({ label: 'Project', status: pc.yellow('config exists but unreadable') });
|
|
64
64
|
}
|
|
65
65
|
} else {
|
|
66
|
-
checks.push({ label: 'Project', status: pc.dim('not initialized (run `orbital
|
|
66
|
+
checks.push({ label: 'Project', status: pc.dim('not initialized (run `orbital`)') });
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// 5. Workflow
|