@zhuk/spego 0.8.0 → 0.9.0
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/CHANGELOG.md +16 -0
- package/README.md +6 -2
- package/dist/cli/body-input.d.ts +12 -0
- package/dist/cli/body-input.js +27 -0
- package/dist/cli/body-input.js.map +1 -0
- package/dist/cli/commands/artifact.d.ts +8 -0
- package/dist/cli/commands/artifact.js +171 -0
- package/dist/cli/commands/artifact.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts +7 -0
- package/dist/cli/commands/delivery.js +99 -0
- package/dist/cli/commands/delivery.js.map +1 -0
- package/dist/cli/commands/index-rebuild.d.ts +5 -0
- package/dist/cli/commands/index-rebuild.js +31 -0
- package/dist/cli/commands/index-rebuild.js.map +1 -0
- package/dist/cli/commands/meta.d.ts +6 -0
- package/dist/cli/commands/meta.js +60 -0
- package/dist/cli/commands/meta.js.map +1 -0
- package/dist/cli/commands/skills.d.ts +5 -0
- package/dist/cli/commands/skills.js +47 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/view.d.ts +8 -0
- package/dist/cli/commands/view.js +59 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/commands/workspace.d.ts +5 -0
- package/dist/cli/commands/workspace.js +61 -0
- package/dist/cli/commands/workspace.js.map +1 -0
- package/dist/cli/errors.d.ts +20 -0
- package/dist/cli/errors.js +55 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/output.d.ts +12 -0
- package/dist/cli/output.js +28 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/render.d.ts +7 -0
- package/dist/cli/render.js +22 -0
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/runtime.d.ts +26 -0
- package/dist/cli/runtime.js +48 -0
- package/dist/cli/runtime.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +64 -582
- package/dist/cli.js.map +1 -1
- package/dist/workspace/config.d.ts +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,121 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import fs from 'node:fs/promises';
|
|
4
|
-
import { initWorkspace, workspaceStatus } from './workspace/init.js';
|
|
5
|
-
import { ArtifactEngine } from './artifacts/engine.js';
|
|
6
|
-
import { viewArtifacts } from './export/view.js';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import { renderArtifactFile } from './artifacts/storage.js';
|
|
9
|
-
import { SpegoError } from './errors.js';
|
|
10
|
-
import { COMMAND_REGISTRY } from './commands/registry.js';
|
|
11
|
-
import { WORKFLOW_REGISTRY } from './workflows/registry.js';
|
|
12
|
-
import { generateAll } from './generator/index.js';
|
|
13
|
-
import { resolveWorkspacePaths } from './workspace/paths.js';
|
|
14
|
-
import { readConfig } from './workspace/config.js';
|
|
15
|
-
import { resolveAdapter } from './delivery/index.js';
|
|
16
|
-
import { renderBox, renderTable, renderHeader, renderDivider } from './cli/render.js';
|
|
17
|
-
import pkg from '../package.json' with { type: 'json' };
|
|
18
|
-
// ---------- output helpers ----------
|
|
19
|
-
function emitJson(payload) {
|
|
20
|
-
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
21
|
-
}
|
|
22
|
-
function emitHuman(text) {
|
|
23
|
-
process.stdout.write(text.endsWith('\n') ? text : `${text}\n`);
|
|
24
|
-
}
|
|
25
|
-
function output(json, payload, human) {
|
|
26
|
-
if (json) {
|
|
27
|
-
emitJson(payload);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
emitHuman(human());
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function deprecate(json, message) {
|
|
34
|
-
if (!json) {
|
|
35
|
-
process.stderr.write(`deprecated: ${message}\n`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function readBodyOption(opts) {
|
|
39
|
-
if (opts.bodyFile)
|
|
40
|
-
return fs.readFile(opts.bodyFile, 'utf8');
|
|
41
|
-
return opts.body;
|
|
42
|
-
}
|
|
43
|
-
// ---------- per-domain renderers ----------
|
|
44
|
-
function renderEpicRow(epic) {
|
|
45
|
-
const progress = epic.taskCount != null ? `${epic.tasksDone ?? 0}/${epic.taskCount}` : '—';
|
|
46
|
-
return [epic.externalId, epic.status, progress, epic.title];
|
|
47
|
-
}
|
|
48
|
-
function renderTaskRow(task) {
|
|
49
|
-
return [task.status === 'done' ? '[x]' : '[ ]', task.title];
|
|
50
|
-
}
|
|
51
|
-
function renderArtifactHeader(fm) {
|
|
52
|
-
return `📄 ${fm.type}/${fm.slug} rev ${fm.revision} (${fm.id})`;
|
|
53
|
-
}
|
|
54
2
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
3
|
+
* spego CLI orchestrator.
|
|
4
|
+
*
|
|
5
|
+
* `buildProgram` wires the global `--json` flag, the program-level help and
|
|
6
|
+
* version handling, and one registrar per command group. Every command body
|
|
7
|
+
* lives under `src/cli/commands/` and routes through the shared runtime helper
|
|
8
|
+
* in `src/cli/runtime.ts`. Error formatting is centralized in `src/cli/errors.ts`.
|
|
59
9
|
*/
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
seenFirstSection = true;
|
|
71
|
-
}
|
|
72
|
-
out.push(line);
|
|
73
|
-
}
|
|
74
|
-
return out.join('\n');
|
|
75
|
-
}
|
|
76
|
-
function renderCommandsHuman() {
|
|
77
|
-
const order = ['workspace', 'artifact', 'view', 'planning'];
|
|
78
|
-
const titles = {
|
|
79
|
-
workspace: 'Workspace',
|
|
80
|
-
artifact: 'Artifacts',
|
|
81
|
-
view: 'View',
|
|
82
|
-
planning: 'Planning',
|
|
83
|
-
};
|
|
84
|
-
const sections = order
|
|
85
|
-
.map((cat) => {
|
|
86
|
-
const items = COMMAND_REGISTRY.filter((c) => c.category === cat);
|
|
87
|
-
if (items.length === 0)
|
|
88
|
-
return '';
|
|
89
|
-
const table = renderTable(['name', 'description'], items.map((c) => [c.name, c.description]));
|
|
90
|
-
return `${titles[cat]}\n${table}`;
|
|
91
|
-
})
|
|
92
|
-
.filter((s) => s.length > 0);
|
|
93
|
-
return [renderHeader('🛠️', 'Commands'), '', sections.join('\n\n')].join('\n');
|
|
94
|
-
}
|
|
95
|
-
function renderWorkflowsHuman() {
|
|
96
|
-
const rows = WORKFLOW_REGISTRY.map((w) => [w.name, String(w.phases.length), w.description]);
|
|
97
|
-
const table = renderTable(['name', 'phases', 'description'], rows, { maxWidth: 80 });
|
|
98
|
-
return [renderHeader('🧭', 'Workflows'), '', table].join('\n');
|
|
99
|
-
}
|
|
100
|
-
// ---------- error rendering ----------
|
|
101
|
-
function fail(err, json) {
|
|
102
|
-
if (err instanceof SpegoError) {
|
|
103
|
-
if (json) {
|
|
104
|
-
process.stderr.write(`${JSON.stringify({ error: err.toJSON() }, null, 2)}\n`);
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
process.stderr.write(`⚠️ [${err.code}] ${err.message}\n`);
|
|
108
|
-
}
|
|
109
|
-
process.exit(2);
|
|
110
|
-
}
|
|
111
|
-
if (json) {
|
|
112
|
-
process.stderr.write(`${JSON.stringify({ error: { code: 'INTERNAL', message: err.message } }, null, 2)}\n`);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
process.stderr.write(`⚠️ [INTERNAL] ${err.message}\n`);
|
|
116
|
-
}
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
12
|
+
import { fail, failValidation } from './cli/errors.js';
|
|
13
|
+
import { registerMeta } from './cli/commands/meta.js';
|
|
14
|
+
import { registerWorkspace } from './cli/commands/workspace.js';
|
|
15
|
+
import { registerArtifact } from './cli/commands/artifact.js';
|
|
16
|
+
import { registerView } from './cli/commands/view.js';
|
|
17
|
+
import { registerIndex } from './cli/commands/index-rebuild.js';
|
|
18
|
+
import { registerSkills } from './cli/commands/skills.js';
|
|
19
|
+
import { registerDelivery } from './cli/commands/delivery.js';
|
|
119
20
|
export function buildProgram() {
|
|
120
21
|
const program = new Command();
|
|
121
22
|
program
|
|
@@ -132,474 +33,55 @@ export function buildProgram() {
|
|
|
132
33
|
},
|
|
133
34
|
});
|
|
134
35
|
program.exitOverride((err) => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
program
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
force: opts.force,
|
|
184
|
-
});
|
|
185
|
-
output(json, summary, () => {
|
|
186
|
-
const headerLabel = summary.alreadyInitialized
|
|
187
|
-
? 'Workspace already initialized'
|
|
188
|
-
: 'Initialized spego workspace';
|
|
189
|
-
const box = renderBox('Workspace', [
|
|
190
|
-
['root', summary.workspaceRoot],
|
|
191
|
-
['config', summary.configPath],
|
|
192
|
-
['artifacts', summary.artifactsRoot],
|
|
193
|
-
['index', summary.indexPath],
|
|
194
|
-
['agents', summary.config.agents.join(', ')],
|
|
195
|
-
]);
|
|
196
|
-
return `${renderHeader('📦', headerLabel)}\n${box}`;
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
catch (err) {
|
|
200
|
-
fail(err, json);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
// ---------- status ----------
|
|
204
|
-
program
|
|
205
|
-
.command('status')
|
|
206
|
-
.description('Report workspace status')
|
|
207
|
-
.option('--cwd <dir>', 'project root')
|
|
208
|
-
.action(async (opts) => {
|
|
209
|
-
const json = program.opts().json;
|
|
210
|
-
try {
|
|
211
|
-
const status = await workspaceStatus(opts.cwd);
|
|
212
|
-
output(json, status, () => {
|
|
213
|
-
if (!status.initialized) {
|
|
214
|
-
return `No spego workspace; run: spego init`;
|
|
215
|
-
}
|
|
216
|
-
const box = renderBox('Workspace status', [
|
|
217
|
-
['root', status.workspaceRoot ?? ''],
|
|
218
|
-
['agents', status.agents?.join(', ') ?? '(none)'],
|
|
219
|
-
['artifacts', status.artifactsRoot ?? ''],
|
|
220
|
-
['index', status.indexPath ?? ''],
|
|
221
|
-
]);
|
|
222
|
-
return `${renderHeader('📦', 'Workspace ready')}\n${box}`;
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
catch (err) {
|
|
226
|
-
fail(err, json);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
// ---------- create ----------
|
|
230
|
-
program
|
|
231
|
-
.command('create')
|
|
232
|
-
.description('Create an artifact')
|
|
233
|
-
.requiredOption('--type <type>', 'artifact type, e.g. prd|api|architecture')
|
|
234
|
-
.requiredOption('--title <title>', 'artifact title')
|
|
235
|
-
.option('--slug <slug>', 'optional explicit slug')
|
|
236
|
-
.option('--body <text>', 'inline markdown body')
|
|
237
|
-
.option('--body-file <path>', 'read markdown body from a file (use - for stdin)')
|
|
238
|
-
.option('--meta <json>', 'JSON object of metadata', '{}')
|
|
239
|
-
.option('--cwd <dir>', 'project root')
|
|
240
|
-
.action(async (opts) => {
|
|
241
|
-
const json = program.opts().json;
|
|
242
|
-
const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
|
|
243
|
-
try {
|
|
244
|
-
const body = opts.bodyFile === '-'
|
|
245
|
-
? await new Promise((resolve, reject) => {
|
|
246
|
-
let buf = '';
|
|
247
|
-
process.stdin.setEncoding('utf8');
|
|
248
|
-
process.stdin.on('data', (chunk) => (buf += chunk));
|
|
249
|
-
process.stdin.on('end', () => resolve(buf));
|
|
250
|
-
process.stdin.on('error', reject);
|
|
251
|
-
})
|
|
252
|
-
: (await readBodyOption(opts)) ?? '';
|
|
253
|
-
const meta = JSON.parse(opts.meta);
|
|
254
|
-
const record = await engine.create({
|
|
255
|
-
type: opts.type,
|
|
256
|
-
title: opts.title,
|
|
257
|
-
slug: opts.slug,
|
|
258
|
-
body,
|
|
259
|
-
meta,
|
|
260
|
-
});
|
|
261
|
-
output(json, {
|
|
262
|
-
id: record.frontmatter.id,
|
|
263
|
-
type: record.frontmatter.type,
|
|
264
|
-
slug: record.frontmatter.slug,
|
|
265
|
-
revision: record.frontmatter.revision,
|
|
266
|
-
path: record.path,
|
|
267
|
-
markdown: renderArtifactFile(record.frontmatter, record.body),
|
|
268
|
-
}, () => `✨ Created ${record.frontmatter.type}/${record.frontmatter.slug} rev ${record.frontmatter.revision}\n path: ${record.path}`);
|
|
269
|
-
}
|
|
270
|
-
catch (err) {
|
|
271
|
-
fail(err, json);
|
|
272
|
-
}
|
|
273
|
-
finally {
|
|
274
|
-
engine.close();
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
// ---------- read ----------
|
|
278
|
-
program
|
|
279
|
-
.command('read')
|
|
280
|
-
.description('Read an artifact by id or type+slug')
|
|
281
|
-
.option('--id <id>', 'artifact id')
|
|
282
|
-
.option('--type <type>', 'artifact type (with --slug)')
|
|
283
|
-
.option('--slug <slug>', 'artifact slug (with --type)')
|
|
284
|
-
.option('--path <path>', 'read artifact by absolute file path')
|
|
285
|
-
.option('--revision <n>', 'specific revision number')
|
|
286
|
-
.option('--cwd <dir>', 'project root')
|
|
287
|
-
.action(async (opts) => {
|
|
288
|
-
const json = program.opts().json;
|
|
289
|
-
const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
|
|
290
|
-
try {
|
|
291
|
-
let record;
|
|
292
|
-
if (opts.id) {
|
|
293
|
-
record =
|
|
294
|
-
opts.revision !== undefined
|
|
295
|
-
? await engine.readRevision(opts.id, Number(opts.revision))
|
|
296
|
-
: await engine.readById(opts.id);
|
|
297
|
-
}
|
|
298
|
-
else if (opts.type && opts.slug) {
|
|
299
|
-
record = await engine.readByTypeSlug(opts.type, opts.slug);
|
|
300
|
-
}
|
|
301
|
-
else if (opts.path) {
|
|
302
|
-
record = await engine.readByPath(path.resolve(opts.path));
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
throw new SpegoError('VALIDATION_FAILED', 'Provide --id, --type and --slug, or --path');
|
|
306
|
-
}
|
|
307
|
-
output(json, { frontmatter: record.frontmatter, body: record.body, path: record.path }, () => `${renderArtifactHeader(record.frontmatter)}\n${renderDivider()}\n${record.body}`);
|
|
308
|
-
}
|
|
309
|
-
catch (err) {
|
|
310
|
-
fail(err, json);
|
|
311
|
-
}
|
|
312
|
-
finally {
|
|
313
|
-
engine.close();
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
// ---------- update ----------
|
|
317
|
-
program
|
|
318
|
-
.command('update')
|
|
319
|
-
.description('Update an artifact (requires id)')
|
|
320
|
-
.requiredOption('--id <id>', 'artifact id')
|
|
321
|
-
.option('--title <title>', 'new title')
|
|
322
|
-
.option('--body <text>', 'new markdown body')
|
|
323
|
-
.option('--body-file <path>', 'read body from file')
|
|
324
|
-
.option('--meta <json>', 'replace metadata (JSON object)')
|
|
325
|
-
.option('--expected-revision <n>', 'expected current revision (optimistic concurrency)')
|
|
326
|
-
.option('--cwd <dir>', 'project root')
|
|
327
|
-
.action(async (opts) => {
|
|
328
|
-
const json = program.opts().json;
|
|
329
|
-
const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
|
|
330
|
-
try {
|
|
331
|
-
const body = await readBodyOption(opts);
|
|
332
|
-
const patch = {};
|
|
333
|
-
if (opts.title !== undefined)
|
|
334
|
-
patch.title = opts.title;
|
|
335
|
-
if (body !== undefined)
|
|
336
|
-
patch.body = body;
|
|
337
|
-
if (opts.meta !== undefined)
|
|
338
|
-
patch.meta = JSON.parse(opts.meta);
|
|
339
|
-
if (opts.expectedRevision !== undefined)
|
|
340
|
-
patch.expectedRevision = Number(opts.expectedRevision);
|
|
341
|
-
const record = await engine.update(opts.id, patch);
|
|
342
|
-
output(json, { id: record.frontmatter.id, revision: record.frontmatter.revision, path: record.path }, () => `${renderArtifactHeader(record.frontmatter)}\n${renderDivider()}\n${record.body}`);
|
|
343
|
-
}
|
|
344
|
-
catch (err) {
|
|
345
|
-
fail(err, json);
|
|
346
|
-
}
|
|
347
|
-
finally {
|
|
348
|
-
engine.close();
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
// ---------- delete ----------
|
|
352
|
-
program
|
|
353
|
-
.command('delete')
|
|
354
|
-
.description('Soft-delete an artifact')
|
|
355
|
-
.requiredOption('--id <id>', 'artifact id')
|
|
356
|
-
.option('--cwd <dir>', 'project root')
|
|
357
|
-
.action(async (opts) => {
|
|
358
|
-
const json = program.opts().json;
|
|
359
|
-
const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
|
|
360
|
-
try {
|
|
361
|
-
const record = await engine.softDelete(opts.id);
|
|
362
|
-
output(json, {
|
|
363
|
-
id: record.frontmatter.id,
|
|
364
|
-
deletedAt: record.frontmatter.deletedAt,
|
|
365
|
-
revision: record.frontmatter.revision,
|
|
366
|
-
}, () => `🗑 Deleted ${record.frontmatter.id} at ${record.frontmatter.deletedAt}`);
|
|
367
|
-
}
|
|
368
|
-
catch (err) {
|
|
369
|
-
fail(err, json);
|
|
370
|
-
}
|
|
371
|
-
finally {
|
|
372
|
-
engine.close();
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
// ---------- list ----------
|
|
376
|
-
program
|
|
377
|
-
.command('list')
|
|
378
|
-
.description('List artifacts')
|
|
379
|
-
.option('--type <type>', 'filter by type')
|
|
380
|
-
.option('--include-deleted', 'include soft-deleted artifacts', false)
|
|
381
|
-
.option('--cwd <dir>', 'project root')
|
|
382
|
-
.action(async (opts) => {
|
|
383
|
-
const json = program.opts().json;
|
|
384
|
-
const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
|
|
385
|
-
try {
|
|
386
|
-
const items = engine.list({ type: opts.type, includeDeleted: opts.includeDeleted });
|
|
387
|
-
output(json, items, () => {
|
|
388
|
-
if (items.length === 0)
|
|
389
|
-
return 'No artifacts.';
|
|
390
|
-
const rows = items.map((a) => [
|
|
391
|
-
`${a.type}/${a.slug}`,
|
|
392
|
-
String(a.revision),
|
|
393
|
-
a.deletedAt ? 'deleted' : 'active',
|
|
394
|
-
a.title,
|
|
395
|
-
a.id,
|
|
396
|
-
]);
|
|
397
|
-
const table = renderTable(['type/slug', 'rev', 'status', 'title', 'id'], rows);
|
|
398
|
-
return `${renderHeader('📦', 'Artifacts')}\n${table}`;
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
catch (err) {
|
|
402
|
-
fail(err, json);
|
|
403
|
-
}
|
|
404
|
-
finally {
|
|
405
|
-
engine.close();
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
// ---------- view ----------
|
|
409
|
-
program
|
|
410
|
-
.command('view')
|
|
411
|
-
.description('Export artifact bundle as markdown (default) or JSON via --json')
|
|
412
|
-
.option('--type <type>', 'filter by type')
|
|
413
|
-
.option('--id <id>', 'limit to a single artifact')
|
|
414
|
-
.option('--revision <n>', 'specific revision (requires --id)')
|
|
415
|
-
.option('--include-deleted', 'include soft-deleted artifacts', false)
|
|
416
|
-
.option('--format <fmt>', '[deprecated] markdown | json — use the global --json flag instead')
|
|
417
|
-
.option('--cwd <dir>', 'project root')
|
|
418
|
-
.action(async (opts) => {
|
|
419
|
-
const globalJson = program.opts().json;
|
|
420
|
-
// Deprecation: --format is still honored but warns in human mode.
|
|
421
|
-
if (opts.format !== undefined) {
|
|
422
|
-
deprecate(globalJson, "--format is deprecated; use the global --json flag");
|
|
423
|
-
}
|
|
424
|
-
const wantJson = globalJson || opts.format === 'json';
|
|
425
|
-
const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, wantJson));
|
|
426
|
-
try {
|
|
427
|
-
const view = await viewArtifacts(engine, {
|
|
428
|
-
type: opts.type,
|
|
429
|
-
id: opts.id,
|
|
430
|
-
revision: opts.revision !== undefined ? Number(opts.revision) : undefined,
|
|
431
|
-
includeDeleted: opts.includeDeleted,
|
|
432
|
-
});
|
|
433
|
-
if (wantJson) {
|
|
434
|
-
emitJson(view.json);
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
const body = intersperseBundleDividers(view.markdown.content);
|
|
438
|
-
process.stdout.write(`${renderHeader('📦', 'Artifact bundle')}\n${body}`);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
catch (err) {
|
|
442
|
-
fail(err, wantJson);
|
|
443
|
-
}
|
|
444
|
-
finally {
|
|
445
|
-
engine.close();
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
// ---------- index rebuild ----------
|
|
449
|
-
const indexCmd = program.command('index').description('Index maintenance');
|
|
450
|
-
// ---------- skills ----------
|
|
451
|
-
async function runSkillsCommand(json, cwd) {
|
|
452
|
-
const projectRoot = path.resolve(cwd ?? process.cwd());
|
|
453
|
-
const wsPaths = resolveWorkspacePaths(projectRoot);
|
|
454
|
-
const config = await readConfig(wsPaths.configPath);
|
|
455
|
-
const reports = await generateAll(projectRoot, config.agents, { emitWarnings: !json });
|
|
456
|
-
output(json, reports, () => {
|
|
457
|
-
const lines = [renderHeader('🛠️', 'Skill regeneration'), ''];
|
|
458
|
-
for (const r of reports) {
|
|
459
|
-
lines.push(`[${r.target}]`);
|
|
460
|
-
for (const f of r.files) {
|
|
461
|
-
lines.push(` ${f.action} ${f.path}`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return lines.join('\n');
|
|
36
|
+
if (err.code === 'commander.help' ||
|
|
37
|
+
err.code === 'commander.helpDisplayed' ||
|
|
38
|
+
err.code === 'commander.version') {
|
|
39
|
+
process.exit(err.exitCode ?? 0);
|
|
40
|
+
}
|
|
41
|
+
failValidation(err, program.opts().json);
|
|
42
|
+
});
|
|
43
|
+
registerMeta(program);
|
|
44
|
+
registerWorkspace(program);
|
|
45
|
+
registerArtifact(program);
|
|
46
|
+
registerView(program);
|
|
47
|
+
registerIndex(program);
|
|
48
|
+
registerSkills(program);
|
|
49
|
+
registerDelivery(program);
|
|
50
|
+
return program;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Detect whether this module is the process entry point. Without this guard,
|
|
54
|
+
* importing `buildProgram` from a test would execute `parseAsync` against the
|
|
55
|
+
* test runner's own argv and either error out or terminate the process.
|
|
56
|
+
*/
|
|
57
|
+
function isEntryPoint() {
|
|
58
|
+
if (typeof process === 'undefined')
|
|
59
|
+
return false;
|
|
60
|
+
const argv1 = process.argv[1];
|
|
61
|
+
if (!argv1)
|
|
62
|
+
return false;
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(import.meta.url);
|
|
65
|
+
if (url.protocol !== 'file:')
|
|
66
|
+
return false;
|
|
67
|
+
return url.pathname.endsWith(argv1) || argv1.endsWith(url.pathname);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (isEntryPoint()) {
|
|
74
|
+
const program = buildProgram();
|
|
75
|
+
const args = process.argv;
|
|
76
|
+
if (args.length <= 2) {
|
|
77
|
+
program.parseAsync([...args, '--help']).catch((err) => {
|
|
78
|
+
fail(err, program.opts().json);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
program.parseAsync(args).catch((err) => {
|
|
83
|
+
fail(err, program.opts().json);
|
|
465
84
|
});
|
|
466
85
|
}
|
|
467
|
-
program
|
|
468
|
-
.command('skills')
|
|
469
|
-
.description('Regenerate agent skill and command files')
|
|
470
|
-
.option('--cwd <dir>', 'project root')
|
|
471
|
-
.action(async (opts) => {
|
|
472
|
-
const json = program.opts().json;
|
|
473
|
-
try {
|
|
474
|
-
await runSkillsCommand(json, opts.cwd);
|
|
475
|
-
}
|
|
476
|
-
catch (err) {
|
|
477
|
-
fail(err, json);
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
program
|
|
481
|
-
.command('regenerate', { hidden: true })
|
|
482
|
-
.description('Regenerate agent skill and command files')
|
|
483
|
-
.option('--cwd <dir>', 'project root')
|
|
484
|
-
.action(async (opts) => {
|
|
485
|
-
const json = program.opts().json;
|
|
486
|
-
deprecate(json, "'spego regenerate' is now 'spego skills'");
|
|
487
|
-
try {
|
|
488
|
-
await runSkillsCommand(json, opts.cwd);
|
|
489
|
-
}
|
|
490
|
-
catch (err) {
|
|
491
|
-
fail(err, json);
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
// ---------- epics ----------
|
|
495
|
-
program
|
|
496
|
-
.command('epics')
|
|
497
|
-
.description('List epics or get a single epic')
|
|
498
|
-
.argument('[action]', 'list (default) or get')
|
|
499
|
-
.option('--change <name>', 'get a single epic by name')
|
|
500
|
-
.option('--cwd <dir>', 'project root')
|
|
501
|
-
.action(async (action, opts) => {
|
|
502
|
-
const json = program.opts().json;
|
|
503
|
-
try {
|
|
504
|
-
if (action && !['list', 'get'].includes(action)) {
|
|
505
|
-
throw new SpegoError('DELIVERY_READ_ONLY', `Cannot '${action}' delivery epics through spego. Use the OpenSpec CLI for delivery mutations.`, { attemptedAction: action, suggestedTool: 'openspec' });
|
|
506
|
-
}
|
|
507
|
-
const projectRoot = path.resolve(opts.cwd ?? process.cwd());
|
|
508
|
-
const wsPaths = resolveWorkspacePaths(projectRoot);
|
|
509
|
-
const config = await readConfig(wsPaths.configPath);
|
|
510
|
-
const adapter = resolveAdapter(projectRoot, config);
|
|
511
|
-
if (opts.change) {
|
|
512
|
-
const epic = await adapter.getEpic(opts.change);
|
|
513
|
-
output(json, epic, () => {
|
|
514
|
-
const table = renderTable(['id', 'status', 'progress', 'title'], [renderEpicRow(epic)]);
|
|
515
|
-
return `${renderHeader('📚', `Epic (${adapter.name})`)}\n${table}`;
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
const epics = await adapter.listEpics();
|
|
520
|
-
output(json, epics, () => {
|
|
521
|
-
if (epics.length === 0)
|
|
522
|
-
return 'No epics.';
|
|
523
|
-
const table = renderTable(['id', 'status', 'progress', 'title'], epics.map(renderEpicRow));
|
|
524
|
-
return `${renderHeader('📚', `Epics (${adapter.name})`)}\n${table}`;
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
catch (err) {
|
|
529
|
-
fail(err, json);
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
// ---------- tasks ----------
|
|
533
|
-
program
|
|
534
|
-
.command('tasks')
|
|
535
|
-
.description('List tasks for a change or get a single task')
|
|
536
|
-
.argument('[action]', 'list (default) or get')
|
|
537
|
-
.requiredOption('--change <name>', 'the epic/change name')
|
|
538
|
-
.option('--task <id>', 'get a single task by id')
|
|
539
|
-
.option('--cwd <dir>', 'project root')
|
|
540
|
-
.action(async (action, opts) => {
|
|
541
|
-
const json = program.opts().json;
|
|
542
|
-
try {
|
|
543
|
-
if (action && !['list', 'get'].includes(action)) {
|
|
544
|
-
throw new SpegoError('DELIVERY_READ_ONLY', `Cannot '${action}' delivery tasks through spego. Use the OpenSpec CLI for delivery mutations.`, { attemptedAction: action, suggestedTool: 'openspec' });
|
|
545
|
-
}
|
|
546
|
-
const projectRoot = path.resolve(opts.cwd ?? process.cwd());
|
|
547
|
-
const wsPaths = resolveWorkspacePaths(projectRoot);
|
|
548
|
-
const config = await readConfig(wsPaths.configPath);
|
|
549
|
-
const adapter = resolveAdapter(projectRoot, config);
|
|
550
|
-
if (opts.task) {
|
|
551
|
-
const task = await adapter.getTask(opts.change, opts.task);
|
|
552
|
-
output(json, task, () => {
|
|
553
|
-
const table = renderTable(['done', 'title'], [renderTaskRow(task)]);
|
|
554
|
-
return `${renderHeader('✅', `Task: ${opts.change} (${adapter.name})`)}\n${table}`;
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
const tasks = await adapter.listTasks(opts.change);
|
|
559
|
-
output(json, tasks, () => {
|
|
560
|
-
if (tasks.length === 0)
|
|
561
|
-
return `No tasks for ${opts.change}.`;
|
|
562
|
-
const table = renderTable(['done', 'title'], tasks.map(renderTaskRow));
|
|
563
|
-
return `${renderHeader('✅', `Tasks: ${opts.change} (${adapter.name})`)}\n${table}`;
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
catch (err) {
|
|
568
|
-
fail(err, json);
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
indexCmd
|
|
572
|
-
.command('rebuild')
|
|
573
|
-
.description('Rebuild the SQLite index from markdown files')
|
|
574
|
-
.option('--cwd <dir>', 'project root')
|
|
575
|
-
.action(async (opts) => {
|
|
576
|
-
const json = program.opts().json;
|
|
577
|
-
const engine = await ArtifactEngine.open({ projectRoot: opts.cwd }).catch((err) => fail(err, json));
|
|
578
|
-
try {
|
|
579
|
-
const report = await engine.rebuildIndex();
|
|
580
|
-
output(json, report, () => {
|
|
581
|
-
const summary = renderBox('Index rebuild', [
|
|
582
|
-
['scanned', String(report.scannedFiles)],
|
|
583
|
-
['indexed', String(report.indexedArtifacts)],
|
|
584
|
-
['invalid', String(report.invalidFiles.length)],
|
|
585
|
-
]);
|
|
586
|
-
if (report.invalidFiles.length === 0)
|
|
587
|
-
return summary;
|
|
588
|
-
const table = renderTable(['path', 'error'], report.invalidFiles.map((i) => [i.path, i.error]), { maxWidth: 80 });
|
|
589
|
-
return `${summary}\n\nInvalid files:\n${table}`;
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
catch (err) {
|
|
593
|
-
fail(err, json);
|
|
594
|
-
}
|
|
595
|
-
finally {
|
|
596
|
-
engine.close();
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
return program;
|
|
600
86
|
}
|
|
601
|
-
const program = buildProgram();
|
|
602
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
603
|
-
fail(err, program.opts().json);
|
|
604
|
-
});
|
|
605
87
|
//# sourceMappingURL=cli.js.map
|