spectoflow 0.18.0 → 0.19.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/README.md +11 -1
- package/bin/spectoflow.js +74 -2
- package/package.json +1 -1
- package/templates/README.md +3 -1
- package/templates/lib/customize-prompts.js +34 -0
package/README.md
CHANGED
|
@@ -167,7 +167,17 @@ uses, so a generated dashboard automatically matches whatever design is active,
|
|
|
167
167
|
keeps matching if you switch designs later. Blocks can bind live to project stats (`bind:
|
|
168
168
|
"phases.0.pct"`) or hold a static value. Generated skills and agents follow the same gold-standard
|
|
169
169
|
shape as the shipped ones, cite real domain standards (OWASP, WCAG, C4/ADR, …) instead of generic
|
|
170
|
-
advice, and are marked `origin: user-generated` so they're easy to tell apart in the UI.
|
|
170
|
+
advice, and are marked `origin: user-generated` so they're easy to tell apart in the UI. The same
|
|
171
|
+
generators are available from the terminal:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
spectoflow skill create "reviews PRs for accessibility" # or: --auto to propose candidates
|
|
175
|
+
spectoflow agent create "owns accessibility review" # or: --auto
|
|
176
|
+
spectoflow dashboard create "a KPI overview for support" # or: --auto
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Each streams the agent's run live and exits with its status — the same pipeline the dashboard's
|
|
180
|
+
Generate/Auto buttons use, just from a shell.
|
|
171
181
|
|
|
172
182
|
## Agents vs skills
|
|
173
183
|
|
package/bin/spectoflow.js
CHANGED
|
@@ -10,6 +10,8 @@ const detect = require('../lib/detect');
|
|
|
10
10
|
const ownership = require('../lib/ownership');
|
|
11
11
|
const manifest = require('../lib/manifest');
|
|
12
12
|
const mcp = require('../lib/mcp');
|
|
13
|
+
const { startRun } = require('../templates/dashboard/runner');
|
|
14
|
+
const { buildCustomizePrompt } = require('../templates/lib/customize-prompts');
|
|
13
15
|
|
|
14
16
|
const KIT = path.resolve(__dirname, '..');
|
|
15
17
|
const TPL = path.join(KIT, 'templates');
|
|
@@ -257,9 +259,62 @@ async function dashboard() {
|
|
|
257
259
|
if (sub === 'stop') return stopDashboard();
|
|
258
260
|
if (sub === 'status') return dashboardStatus();
|
|
259
261
|
if (sub === 'restart') return restartDashboard();
|
|
262
|
+
if (sub === 'create') return runCustomize('dashboard');
|
|
260
263
|
return startDashboard();
|
|
261
264
|
}
|
|
262
265
|
|
|
266
|
+
// ---- Customize: `spectoflow skill/agent/dashboard create` — the CLI mirror of the dashboard's
|
|
267
|
+
// Settings → Customize UI. Both surfaces build the same natural-language prompt (customize-prompts.js)
|
|
268
|
+
// and post it through the same pipeline (runner.js's startRun — the function /api/run itself calls),
|
|
269
|
+
// so a generation triggered from the terminal behaves identically to one triggered from a click.
|
|
270
|
+
function requireProjectRoot() {
|
|
271
|
+
const root = process.cwd();
|
|
272
|
+
if (!fs.existsSync(path.join(root, '.spectoflow'))) {
|
|
273
|
+
console.log('No spectoflow project here. Run: spectoflow init');
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return root;
|
|
277
|
+
}
|
|
278
|
+
// "create <description words…> [--auto] [--agent=name]" → { description, auto, agentOverride }.
|
|
279
|
+
// Words are re-joined with spaces so an unquoted multi-word description works the same as a quoted one.
|
|
280
|
+
function parseCreateArgs(args) {
|
|
281
|
+
return {
|
|
282
|
+
auto: args.includes('--auto'),
|
|
283
|
+
agentOverride: (args.find((a) => a.startsWith('--agent=')) || '').split('=')[1] || undefined,
|
|
284
|
+
description: args.filter((a) => !a.startsWith('--')).join(' ').trim(),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function printCreateUsage(kind) {
|
|
288
|
+
console.log(`Usage: spectoflow ${kind} create "<description>" ${c.dim('[--agent=name]')}`);
|
|
289
|
+
console.log(` or: spectoflow ${kind} create --auto ${c.dim('[--agent=name]')}`);
|
|
290
|
+
}
|
|
291
|
+
// Streams the same events the dashboard's SSE feed would show: raw output lines as-is, and
|
|
292
|
+
// structured ::spectoflow sentinel messages as "[role] text" (skip the echoed user prompt — printed
|
|
293
|
+
// separately, up front, so it isn't shown twice).
|
|
294
|
+
function cliEmit(evt) {
|
|
295
|
+
if (evt.type === 'run-line') process.stdout.write(evt.chunk);
|
|
296
|
+
else if (evt.type === 'message' && evt.message && evt.message.role !== 'user') {
|
|
297
|
+
console.log(`${c.cy('[' + evt.message.role + ']')} ${evt.message.text}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function runCustomize(kind) {
|
|
301
|
+
const root = requireProjectRoot();
|
|
302
|
+
if (!root) return;
|
|
303
|
+
if (argv[1] !== 'create') return printCreateUsage(kind);
|
|
304
|
+
const { auto, agentOverride, description } = parseCreateArgs(argv.slice(2));
|
|
305
|
+
let prompt;
|
|
306
|
+
try { prompt = buildCustomizePrompt(kind, { auto, description }); }
|
|
307
|
+
catch (e) { console.log(c.y(e.message)); console.log(''); return printCreateUsage(kind); }
|
|
308
|
+
console.log(c.dim(`→ ${prompt}`));
|
|
309
|
+
const code = await new Promise((resolve) => {
|
|
310
|
+
const r = startRun(root, { prompt, agent: agentOverride }, cliEmit);
|
|
311
|
+
if (r.error) { console.log(c.y(r.error)); return resolve(1); }
|
|
312
|
+
if (!r.child) return resolve(1); // spawn failed — cliEmit already printed the error
|
|
313
|
+
r.child.on('close', (exitCode) => resolve(exitCode == null ? 1 : exitCode));
|
|
314
|
+
});
|
|
315
|
+
process.exitCode = code;
|
|
316
|
+
}
|
|
317
|
+
|
|
263
318
|
// Start in the background and return control. Probes first so a second start just reports the running
|
|
264
319
|
// one instead of spawning a duplicate (and never crashes on EADDRINUSE).
|
|
265
320
|
async function startDashboard() {
|
|
@@ -396,6 +451,11 @@ ${c.bold('Dashboard')}
|
|
|
396
451
|
${c.g('dashboard stop')} stop it ${c.dim('(alias: stop)')}
|
|
397
452
|
${c.g('dashboard restart')} stop then start
|
|
398
453
|
|
|
454
|
+
${c.bold('Customize')} ${c.dim('— same as Settings → Customize, from the terminal')}
|
|
455
|
+
${c.g('skill create')} ${c.dim('"<description>" | --auto')} generate a project skill
|
|
456
|
+
${c.g('agent create')} ${c.dim('"<description>" | --auto')} generate a project agent
|
|
457
|
+
${c.g('dashboard create')} ${c.dim('"<description>" | --auto')} generate a custom dashboard
|
|
458
|
+
|
|
399
459
|
${c.bold('Explore')}
|
|
400
460
|
${c.g('list')} agents, skills and the workflow at a glance
|
|
401
461
|
${c.g('agents')} list the team personas
|
|
@@ -421,12 +481,22 @@ const HELP = {
|
|
|
421
481
|
to this CLI's version, ${c.bold('preserving your work')}: config.json, workflow.md, specs/, plans/
|
|
422
482
|
and any agent/skill you edited are never overwritten (an edited file's new version lands as
|
|
423
483
|
${c.dim('*.new')} for you to merge). ${c.g('--dry-run')} previews without writing.`,
|
|
424
|
-
dashboard: `${c.bold('spectoflow dashboard')} ${c.dim('[--port=NNNN] [status|stop|restart]')}\n
|
|
484
|
+
dashboard: `${c.bold('spectoflow dashboard')} ${c.dim('[--port=NNNN] [status|stop|restart|create]')}\n
|
|
425
485
|
Start the local control plane in the ${c.bold('background')} (default ${c.dim('4319')} or
|
|
426
486
|
${c.dim('$SPECTOFLOW_PORT')}) and hand the prompt back. Subcommands:
|
|
427
487
|
${c.g('status')} is it running? (url + pid)
|
|
428
488
|
${c.g('stop')} stop it ${c.dim('(alias: spectoflow stop)')}
|
|
429
|
-
${c.g('restart')} stop then start
|
|
489
|
+
${c.g('restart')} stop then start
|
|
490
|
+
${c.g('create')} generate a custom dashboard, e.g. ${c.dim('spectoflow dashboard create "..." --auto')}`,
|
|
491
|
+
skill: `${c.bold('spectoflow skill create')} ${c.dim('"<description>" [--agent=name]')}\n${c.bold('spectoflow skill create')} ${c.dim('--auto [--agent=name]')}\n
|
|
492
|
+
Generate a project-specific skill — the CLI mirror of Settings → Customize → ${c.bold('Skills')} →
|
|
493
|
+
${c.bold('Add skill')} in the dashboard. Describe what it should do, or pass ${c.g('--auto')} to have
|
|
494
|
+
the agent survey the project and propose candidates instead. Runs the configured agent headless
|
|
495
|
+
(${c.dim('config.json → agent')}, or override with ${c.g('--agent=')}), streaming its output live;
|
|
496
|
+
it clarifies first if the ask is ambiguous, and marks what it writes ${c.dim('origin: user-generated')}.`,
|
|
497
|
+
agent: `${c.bold('spectoflow agent create')} ${c.dim('"<description>" [--agent=name]')}\n${c.bold('spectoflow agent create')} ${c.dim('--auto [--agent=name]')}\n
|
|
498
|
+
Generate a project-specific agent — the CLI mirror of Settings → Customize → ${c.bold('Agents')} →
|
|
499
|
+
${c.bold('Add agent')}. Same behaviour as ${c.g('spectoflow skill create')}, for an agent persona instead.`,
|
|
430
500
|
status: `${c.bold('spectoflow status')}\n
|
|
431
501
|
Print project progress from ${c.dim('plans/*.md')} (tasks done, specs, agents, skills, in-progress
|
|
432
502
|
items) and whether the dashboard is currently running.`,
|
|
@@ -446,6 +516,8 @@ const fns = {
|
|
|
446
516
|
agents: () => { console.log(wordmark()); printAgents(false); },
|
|
447
517
|
skills: () => { console.log(wordmark()); printSkills(false); },
|
|
448
518
|
workflow: () => { console.log(wordmark()); printWorkflow(false); },
|
|
519
|
+
skill: () => runCustomize('skill'),
|
|
520
|
+
agent: () => runCustomize('agent'),
|
|
449
521
|
};
|
|
450
522
|
const wantsHelp = argv.slice(1).some((a) => a === '-h' || a === '--help');
|
|
451
523
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spectoflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Agent-agnostic spec-driven development framework + real-time local control plane. Markdown artifacts, intent router, workflow-by-scope.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spec-driven-development",
|
package/templates/README.md
CHANGED
|
@@ -33,7 +33,9 @@ sit at the project root and just point back here.
|
|
|
33
33
|
- **Extend spectoflow itself** from Settings → **Customize**: describe a project-specific dashboard,
|
|
34
34
|
skill, or agent (or hit **Auto** to have it propose candidates from your project), and it's generated
|
|
35
35
|
for you — a dashboard appears in the nav immediately, a skill/agent follows the same gold-standard
|
|
36
|
-
shape as the shipped ones and is marked `origin: user-generated`.
|
|
36
|
+
shape as the shipped ones and is marked `origin: user-generated`. Same thing from the terminal:
|
|
37
|
+
`spectoflow skill create "<description>"` / `agent create` / `dashboard create` (each also takes
|
|
38
|
+
`--auto`).
|
|
37
39
|
- **Update the framework** to a newer kit: `spectoflow update` (preserves your edits; a file you
|
|
38
40
|
changed is kept and its new version is written next to it as `*.new`).
|
|
39
41
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Builds the exact natural-language prompts the dashboard's Settings → Customize UI posts to
|
|
3
|
+
// /api/run (see templates/dashboard/public/app.js's CZ_KINDS) — the single source of truth so the
|
|
4
|
+
// CLI (`spectoflow skill/agent/dashboard create`) and the dashboard button never drift apart. The
|
|
5
|
+
// browser side can't require this Node module (no build step), so its literal strings are mirrored
|
|
6
|
+
// there by hand; test/customize-prompts.test.js guards against the two falling out of sync.
|
|
7
|
+
const PROMPTS = {
|
|
8
|
+
dashboard: {
|
|
9
|
+
add: (d) => `Add a custom dashboard: ${d}`,
|
|
10
|
+
auto: 'Propose dashboard candidates for this project (Auto customize)',
|
|
11
|
+
},
|
|
12
|
+
skill: {
|
|
13
|
+
add: (d) => `Create a new skill: ${d}`,
|
|
14
|
+
auto: 'Propose skill candidates for this project (Auto customize)',
|
|
15
|
+
},
|
|
16
|
+
agent: {
|
|
17
|
+
add: (d) => `Create a new agent: ${d}`,
|
|
18
|
+
auto: 'Propose agent candidates for this project (Auto customize)',
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// buildCustomizePrompt('skill', { description: 'reviews PRs for accessibility' })
|
|
23
|
+
// buildCustomizePrompt('skill', { auto: true })
|
|
24
|
+
function buildCustomizePrompt(kind, opts) {
|
|
25
|
+
const p = PROMPTS[kind];
|
|
26
|
+
if (!p) throw new Error(`Unknown customize kind "${kind}" (expected dashboard, skill or agent).`);
|
|
27
|
+
const o = opts || {};
|
|
28
|
+
if (o.auto) return p.auto;
|
|
29
|
+
const d = o.description && String(o.description).trim();
|
|
30
|
+
if (!d) throw new Error('A description is required unless --auto is passed.');
|
|
31
|
+
return p.add(d);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { PROMPTS, buildCustomizePrompt };
|