@tuongaz/seeflow 0.1.83 → 0.1.85
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/dist/web/assets/{architectureDiagram-3BPJPVTR-CHtgIZZ1.js → architectureDiagram-3BPJPVTR-BxjUpYZP.js} +1 -1
- package/dist/web/assets/{blockDiagram-GPEHLZMM-D4zjpGBX.js → blockDiagram-GPEHLZMM-BwW58gmi.js} +1 -1
- package/dist/web/assets/{c4Diagram-AAUBKEIU-zSpjD7ZC.js → c4Diagram-AAUBKEIU-Dzt08ghL.js} +1 -1
- package/dist/web/assets/channel-H6DSsj4q.js +1 -0
- package/dist/web/assets/{chart-D6WVIyqJ.js → chart-DpfirWPs.js} +1 -1
- package/dist/web/assets/{chunk-2J33WTMH-Cr7utMp9.js → chunk-2J33WTMH-Bxngez4X.js} +1 -1
- package/dist/web/assets/{chunk-4BX2VUAB-B6PCB6H4.js → chunk-4BX2VUAB-BOogOMBc.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-D_PUAOtb.js → chunk-55IACEB6-P8EKMZOA.js} +1 -1
- package/dist/web/assets/{chunk-727SXJPM-DTQLQ_nx.js → chunk-727SXJPM-D2YRvYNf.js} +1 -1
- package/dist/web/assets/{chunk-AQP2D5EJ-OBgrFsDS.js → chunk-AQP2D5EJ-DE2NipXE.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-Cqo28fuY.js → chunk-FMBD7UC4-DDhJtfWk.js} +1 -1
- package/dist/web/assets/{chunk-ND2GUHAM-DcquCeO4.js → chunk-ND2GUHAM-D3XJtrli.js} +1 -1
- package/dist/web/assets/{chunk-QZHKN3VN-SKsRSBcC.js → chunk-QZHKN3VN-BdMZCj9e.js} +1 -1
- package/dist/web/assets/classDiagram-4FO5ZUOK-BAWJcCIq.js +1 -0
- package/dist/web/assets/classDiagram-v2-Q7XG4LA2-BAWJcCIq.js +1 -0
- package/dist/web/assets/{code-block-CQcwHam8.js → code-block-sw_8m1aF.js} +1 -1
- package/dist/web/assets/{cose-bilkent-S5V4N54A-Ds8DGArP.js → cose-bilkent-S5V4N54A-aEoPvgjp.js} +1 -1
- package/dist/web/assets/{dagre-BM42HDAG-CqMyTq3c.js → dagre-BM42HDAG-p9EyJsU9.js} +1 -1
- package/dist/web/assets/{diagram-2AECGRRQ-CWEIApTd.js → diagram-2AECGRRQ-D5JkQMH9.js} +1 -1
- package/dist/web/assets/{diagram-5GNKFQAL-8TEzgir4.js → diagram-5GNKFQAL-N-K8VEKH.js} +1 -1
- package/dist/web/assets/{diagram-KO2AKTUF-UNKtOw9m.js → diagram-KO2AKTUF-YYXTKlRp.js} +1 -1
- package/dist/web/assets/{diagram-LMA3HP47-D6QoRbi6.js → diagram-LMA3HP47-DB6IV5pI.js} +1 -1
- package/dist/web/assets/{diagram-OG6HWLK6-CQqkl0nk.js → diagram-OG6HWLK6-CYpsa5S_.js} +1 -1
- package/dist/web/assets/{erDiagram-TEJ5UH35-Clv5A-5B.js → erDiagram-TEJ5UH35-DG_ieSJb.js} +1 -1
- package/dist/web/assets/{flowDiagram-I6XJVG4X-4hg7rn9v.js → flowDiagram-I6XJVG4X-DhkYdddf.js} +1 -1
- package/dist/web/assets/{ganttDiagram-6RSMTGT7-3PAgpAEA.js → ganttDiagram-6RSMTGT7-Dch6LnJg.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PVQCEYII-D5g_5z1o.js → gitGraphDiagram-PVQCEYII-BEdh0-R-.js} +1 -1
- package/dist/web/assets/index-2wkTDsTh.js +8619 -0
- package/dist/web/assets/{index-fl8DS9WO.css → index-DzEkjMbu.css} +1 -1
- package/dist/web/assets/{index.es-Cgv8Vcjx.js → index.es-BPbregt5.js} +1 -1
- package/dist/web/assets/{infoDiagram-5YYISTIA-BuFXAl4D.js → infoDiagram-5YYISTIA-Bg5TB2bn.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-DOE-GUy-.js → ishikawaDiagram-YF4QCWOH-C9wgnFPt.js} +1 -1
- package/dist/web/assets/{journeyDiagram-JHISSGLW-B5BXjJgy.js → journeyDiagram-JHISSGLW-DXoECOxR.js} +1 -1
- package/dist/web/assets/{jspdf.es.min-_GQ7N2xd.js → jspdf.es.min-D0_5WkpV.js} +3 -3
- package/dist/web/assets/{kanban-definition-UN3LZRKU-DTLQ2kuG.js → kanban-definition-UN3LZRKU-Cul7RbDt.js} +1 -1
- package/dist/web/assets/{linear-DCKgJuDe.js → linear-CZ3VwjbT.js} +1 -1
- package/dist/web/assets/{markdown-DQ9_ZAvJ.js → markdown-EWVEPYpm.js} +1 -1
- package/dist/web/assets/{mermaid.core-Cf2CUPpR.js → mermaid.core-Ceyl9DjX.js} +4 -4
- package/dist/web/assets/{mindmap-definition-RKZ34NQL-CI-_BCCZ.js → mindmap-definition-RKZ34NQL-DCBC1TsK.js} +1 -1
- package/dist/web/assets/{pieDiagram-4H26LBE5-BdTv4DaU.js → pieDiagram-4H26LBE5-CqccSO7S.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-W4KKPZXB-pqQ7pKT1.js → quadrantDiagram-W4KKPZXB-e7-sWjUK.js} +1 -1
- package/dist/web/assets/{requirementDiagram-4Y6WPE33-CCP9fYhL.js → requirementDiagram-4Y6WPE33-BBwVJS4I.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-5OEKKPKP-CpMAd0Mj.js → sankeyDiagram-5OEKKPKP-BCNgzA-3.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-3UESZ5HK-BWg19eGL.js → sequenceDiagram-3UESZ5HK-BjjeSbs9.js} +1 -1
- package/dist/web/assets/{stateDiagram-AJRCARHV-BliITsKN.js → stateDiagram-AJRCARHV-tl6fq8NN.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-BHNVJYJU-7VEgV0SV.js +1 -0
- package/dist/web/assets/{time-Dqs-aHL0.js → time-BQz-ZWra.js} +1 -1
- package/dist/web/assets/{timeline-definition-PNZ67QCA-Bk8P84Jg.js → timeline-definition-PNZ67QCA-Cc_C7lIC.js} +1 -1
- package/dist/web/assets/{vennDiagram-CIIHVFJN-CMtXVM_H.js → vennDiagram-CIIHVFJN-BwgTN2w7.js} +1 -1
- package/dist/web/assets/{wardley-L42UT6IY-CHP-BfXr.js → wardley-L42UT6IY-CxmFlIp3.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-YWT4CUSO-DInIu6_2.js → wardleyDiagram-YWT4CUSO-DMEdETxd.js} +1 -1
- package/dist/web/assets/{xychartDiagram-2RQKCTM6-B0gobaUy.js → xychartDiagram-2RQKCTM6-B2xg8rTK.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/api.ts +7 -112
- package/src/cli-helpers.ts +3 -0
- package/src/cli-manifest.ts +61 -15
- package/src/cli-ops.ts +2 -2
- package/src/cli.ts +75 -5
- package/src/events.ts +0 -1
- package/src/mcp.ts +2 -2
- package/src/merge.ts +0 -3
- package/src/operations.ts +77 -4
- package/src/project-scanner.ts +18 -0
- package/src/proxy.ts +1 -193
- package/src/schema-catalog.ts +1 -3
- package/src/schema.ts +0 -10
- package/src/server.ts +1 -2
- package/dist/web/assets/channel-CXa0j4Re.js +0 -1
- package/dist/web/assets/classDiagram-4FO5ZUOK-DGPggFxp.js +0 -1
- package/dist/web/assets/classDiagram-v2-Q7XG4LA2-DGPggFxp.js +0 -1
- package/dist/web/assets/index-CaE1zYAX.js +0 -8619
- package/dist/web/assets/stateDiagram-v2-BHNVJYJU-BCerI8Jf.js +0 -1
package/src/cli-manifest.ts
CHANGED
|
@@ -133,9 +133,10 @@ export const COMMAND_MANIFEST: CommandManifestEntry[] = [
|
|
|
133
133
|
name: 'register',
|
|
134
134
|
synopsis: 'seeflow register [--path <dir>] [--flow <file>]',
|
|
135
135
|
description:
|
|
136
|
-
'Register a demo repo with the studio.
|
|
137
|
-
'
|
|
138
|
-
'
|
|
136
|
+
'Register a demo repo with the studio. Manifest-aware: when <repoPath>/seeflow.json ' +
|
|
137
|
+
'exists, scans every declared flow under flows/<id>/flow.json and upserts one entry ' +
|
|
138
|
+
'per flow. Otherwise reads <repoPath>/<flow> (defaulting to ./flow.json) as a ' +
|
|
139
|
+
'single-flow project. Writes to ~/.seeflow/registry.json. Alias of flows:register.',
|
|
139
140
|
category: 'flows',
|
|
140
141
|
args: [],
|
|
141
142
|
flags: [
|
|
@@ -143,20 +144,25 @@ export const COMMAND_MANIFEST: CommandManifestEntry[] = [
|
|
|
143
144
|
{
|
|
144
145
|
name: 'flow',
|
|
145
146
|
valuePlaceholder: '<file>',
|
|
146
|
-
description:
|
|
147
|
+
description:
|
|
148
|
+
'Path to flow.json relative to repo root (default: flow.json). Ignored when seeflow.json exists.',
|
|
147
149
|
},
|
|
148
150
|
],
|
|
149
151
|
outputs: {
|
|
150
152
|
okExample: { id: 'abc12345', slug: 'checkout' },
|
|
151
|
-
errorKinds: ['fileNotFound', 'badJson', 'badSchema'],
|
|
153
|
+
errorKinds: ['fileNotFound', 'badJson', 'badSchema', 'manifestInvalid'],
|
|
152
154
|
},
|
|
153
155
|
requiresStudio: false,
|
|
154
|
-
examples: [
|
|
156
|
+
examples: [
|
|
157
|
+
'seeflow register',
|
|
158
|
+
'seeflow register --path ./my-app',
|
|
159
|
+
'seeflow register --path ./checkout # manifest-aware re-scan',
|
|
160
|
+
],
|
|
155
161
|
},
|
|
156
162
|
{
|
|
157
163
|
name: 'flows:register',
|
|
158
164
|
synopsis: 'seeflow flows:register [--path <dir>] [--flow <file>]',
|
|
159
|
-
description: 'Register a demo repo. Identical behaviour to `register
|
|
165
|
+
description: 'Register a demo repo. Identical behaviour to `register` (manifest-aware).',
|
|
160
166
|
category: 'flows',
|
|
161
167
|
args: [],
|
|
162
168
|
flags: [
|
|
@@ -164,27 +170,40 @@ export const COMMAND_MANIFEST: CommandManifestEntry[] = [
|
|
|
164
170
|
{
|
|
165
171
|
name: 'flow',
|
|
166
172
|
valuePlaceholder: '<file>',
|
|
167
|
-
description:
|
|
173
|
+
description:
|
|
174
|
+
'Path to flow.json relative to repo root (default: flow.json). Ignored when seeflow.json exists.',
|
|
168
175
|
},
|
|
169
176
|
],
|
|
170
177
|
body: { schemaRef: 'RegisterBody' },
|
|
171
178
|
outputs: {
|
|
172
179
|
okExample: { id: 'abc12345', slug: 'checkout' },
|
|
173
|
-
errorKinds: ['fileNotFound', 'badJson', 'badSchema'],
|
|
180
|
+
errorKinds: ['fileNotFound', 'badJson', 'badSchema', 'manifestInvalid'],
|
|
174
181
|
},
|
|
175
182
|
requiresStudio: false,
|
|
176
183
|
examples: ['seeflow flows:register --path ./my-app'],
|
|
177
184
|
},
|
|
178
185
|
{
|
|
179
186
|
name: 'flows:list',
|
|
180
|
-
synopsis: 'seeflow flows:list',
|
|
181
|
-
description:
|
|
187
|
+
synopsis: 'seeflow flows:list [--project <p>]',
|
|
188
|
+
description:
|
|
189
|
+
'List every registered flow with id, slug, name, repoPath, and valid flag. With ' +
|
|
190
|
+
'--project <p>, filters to one project and returns { projectSlug, flows: [{ flowSlug, ' +
|
|
191
|
+
'name, icon?, isDefault, valid }] }.',
|
|
182
192
|
category: 'flows',
|
|
183
193
|
args: [],
|
|
184
|
-
flags: [
|
|
185
|
-
|
|
194
|
+
flags: [
|
|
195
|
+
{
|
|
196
|
+
name: 'project',
|
|
197
|
+
valuePlaceholder: '<p>',
|
|
198
|
+
description: 'Project slug — filters output to flows under this project',
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
outputs: {
|
|
202
|
+
okExample: { flows: [{ id: 'abc12345', slug: 'checkout', name: 'Checkout' }] },
|
|
203
|
+
errorKinds: ['projectNotFound'],
|
|
204
|
+
},
|
|
186
205
|
requiresStudio: false,
|
|
187
|
-
examples: ['seeflow flows:list'],
|
|
206
|
+
examples: ['seeflow flows:list', 'seeflow flows:list --project order-pipeline'],
|
|
188
207
|
},
|
|
189
208
|
{
|
|
190
209
|
name: 'flows:summary',
|
|
@@ -488,6 +507,33 @@ export const COMMAND_MANIFEST: CommandManifestEntry[] = [
|
|
|
488
507
|
'seeflow projects:create --path ./checkout --name "Checkout" --description "Cart + payments flow"',
|
|
489
508
|
],
|
|
490
509
|
},
|
|
510
|
+
{
|
|
511
|
+
name: 'projects:list',
|
|
512
|
+
synopsis: 'seeflow projects:list',
|
|
513
|
+
description:
|
|
514
|
+
'List every registered project: groups the registry by projectSlug and reads each ' +
|
|
515
|
+
'seeflow.json for the human-readable name + defaultFlow. Falls back to the ' +
|
|
516
|
+
"projectSlug and the registry's isDefault entry when a manifest is missing or " +
|
|
517
|
+
'malformed, so a partially-broken project still surfaces.',
|
|
518
|
+
category: 'project',
|
|
519
|
+
args: [],
|
|
520
|
+
flags: [],
|
|
521
|
+
outputs: {
|
|
522
|
+
okExample: {
|
|
523
|
+
projects: [
|
|
524
|
+
{
|
|
525
|
+
projectSlug: 'checkout',
|
|
526
|
+
name: 'Checkout',
|
|
527
|
+
defaultFlow: 'main',
|
|
528
|
+
flowCount: 2,
|
|
529
|
+
repoPath: '/abs/path/to/.seeflow/checkout',
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
requiresStudio: false,
|
|
535
|
+
examples: ['seeflow projects:list'],
|
|
536
|
+
},
|
|
491
537
|
// ---- nodes -------------------------------------------------------------
|
|
492
538
|
{
|
|
493
539
|
name: 'nodes:add',
|
|
@@ -860,7 +906,7 @@ export const COMMAND_MANIFEST: CommandManifestEntry[] = [
|
|
|
860
906
|
'Optional named schema within the category — e.g. for `node`: ' +
|
|
861
907
|
'rectangle, ellipse, sticky, text, database, server, user, queue, ' +
|
|
862
908
|
'cloud, image, html, icon, component. For `action`: playAction, ' +
|
|
863
|
-
'statusAction,
|
|
909
|
+
'statusAction, statusReport, componentAction. For ' +
|
|
864
910
|
'`componentSpec`: componentSpec, componentSpecElement.',
|
|
865
911
|
},
|
|
866
912
|
],
|
package/src/cli-ops.ts
CHANGED
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
/**
|
|
11
11
|
* Build a single Operations handle for in-process CLI use.
|
|
12
12
|
*
|
|
13
|
-
* The CLI has no watcher and no statusRunner — play
|
|
14
|
-
*
|
|
13
|
+
* The CLI has no watcher and no statusRunner — play is server-only
|
|
14
|
+
* and still goes via HTTP. When a CLI mutates a flow file, the
|
|
15
15
|
* running studio's flow watcher picks up the disk write externally and
|
|
16
16
|
* broadcasts flow:reload to connected SPA clients.
|
|
17
17
|
*
|
package/src/cli.ts
CHANGED
|
@@ -178,6 +178,8 @@ if (argv.includes('--version') || argv.includes('-v')) {
|
|
|
178
178
|
await runRegister();
|
|
179
179
|
} else if (sub === 'projects:create') {
|
|
180
180
|
await runProjectsCreate();
|
|
181
|
+
} else if (sub === 'projects:list') {
|
|
182
|
+
await runProjectsList();
|
|
181
183
|
} else if (sub === 'flows:list') {
|
|
182
184
|
await runFlowsList();
|
|
183
185
|
} else if (sub === 'flows:summary') {
|
|
@@ -243,10 +245,11 @@ Usage:
|
|
|
243
245
|
Commands (work without a running studio):
|
|
244
246
|
start Start the SeeFlow Studio server (default port 4321) — default when no command is given
|
|
245
247
|
stop Stop a background studio instance
|
|
246
|
-
register Register a demo repo,
|
|
247
|
-
flows:register Register a demo repo
|
|
248
|
+
register Register a demo repo. Manifest-aware: when <repoPath>/seeflow.json exists, re-scans every declared flow; otherwise reads <repoPath>/<flow> (defaults to flow.json) as a single-flow project (alias of flows:register)
|
|
249
|
+
flows:register Register a demo repo (manifest-aware — same behaviour as register)
|
|
248
250
|
projects:create Scaffold a new project (writes <path>/seeflow.json + <path>/flows/main/flow.json) — (--path <dir> --name <name> [--description <text>])
|
|
249
|
-
|
|
251
|
+
projects:list List every registered project with projectSlug, name, defaultFlow, flowCount
|
|
252
|
+
flows:list List registered flows. With --project <p>, filters to one project (returns flowSlug, name, icon?, isDefault per flow)
|
|
250
253
|
flows:summary List registered flows (id + name + description only)
|
|
251
254
|
flows:get Get flow details (--project <p> --flow <f>)
|
|
252
255
|
flows:graph List nodes + connectors without inlined file content (--project <p> --flow <f>)
|
|
@@ -594,12 +597,24 @@ function isEsrch(err: unknown): boolean {
|
|
|
594
597
|
|
|
595
598
|
async function runRegister() {
|
|
596
599
|
const repoPath = resolve(flagValue('path') ?? '.');
|
|
597
|
-
const demoPathArg = flagValue('flow') ?? DEFAULT_FLOW_PATH;
|
|
598
600
|
|
|
601
|
+
// Manifest-aware path: if <repoPath>/seeflow.json is on disk, scan and
|
|
602
|
+
// upsert every declared flow in one shot.
|
|
603
|
+
if (existsSync(join(repoPath, 'seeflow.json'))) {
|
|
604
|
+
await runRegisterManifest(repoPath);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Legacy single-flow path: pre-manifest projects (and skill tests that
|
|
609
|
+
// exercise registerFlow directly) still pass a bare flow.json at the
|
|
610
|
+
// root. Read it, schema-validate, upsert one entry.
|
|
611
|
+
const demoPathArg = flagValue('flow') ?? DEFAULT_FLOW_PATH;
|
|
599
612
|
const fullPath = isAbsolute(demoPathArg) ? demoPathArg : join(repoPath, demoPathArg);
|
|
600
613
|
if (!existsSync(fullPath)) {
|
|
601
614
|
console.error(`No demo file at ${fullPath}`);
|
|
602
|
-
console.error(
|
|
615
|
+
console.error(
|
|
616
|
+
`Create ${DEFAULT_FLOW_PATH} in your repo, or pass --flow <path>. For manifest-driven projects, place a seeflow.json at the repo root.`,
|
|
617
|
+
);
|
|
603
618
|
process.exit(1);
|
|
604
619
|
}
|
|
605
620
|
|
|
@@ -643,6 +658,48 @@ async function runRegister() {
|
|
|
643
658
|
}
|
|
644
659
|
}
|
|
645
660
|
|
|
661
|
+
async function runRegisterManifest(repoPath: string) {
|
|
662
|
+
const outcome = registerProject({ repoPath });
|
|
663
|
+
if (outcome.kind !== 'ok') {
|
|
664
|
+
switch (outcome.kind) {
|
|
665
|
+
case 'manifest-invalid':
|
|
666
|
+
console.error(`${join(repoPath, 'seeflow.json')} is invalid: ${outcome.message}`);
|
|
667
|
+
process.exit(2);
|
|
668
|
+
break;
|
|
669
|
+
case 'manifest-missing':
|
|
670
|
+
// Defensive — runRegister gated on existsSync, so this should not fire.
|
|
671
|
+
console.error(`No seeflow.json at ${repoPath}`);
|
|
672
|
+
process.exit(3);
|
|
673
|
+
break;
|
|
674
|
+
case 'legacy-root-flow':
|
|
675
|
+
console.error(
|
|
676
|
+
`${repoPath} has a legacy root flow.json but no seeflow.json. Migrate it into the new flows/<id>/ layout before re-registering.`,
|
|
677
|
+
);
|
|
678
|
+
process.exit(3);
|
|
679
|
+
break;
|
|
680
|
+
case 'flow-json-missing':
|
|
681
|
+
console.error(
|
|
682
|
+
`Manifest declares flow "${outcome.flowId}" but ${outcome.flowPath} is missing.`,
|
|
683
|
+
);
|
|
684
|
+
process.exit(3);
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const pid = readPid();
|
|
691
|
+
const live = pid !== undefined && isPidAlive(pid);
|
|
692
|
+
const overrideUrl = process.env.SEEFLOW_STUDIO_URL?.replace(/\/+$/, '');
|
|
693
|
+
const baseUrl = live ? (overrideUrl ?? studioUrl(readConfig())) : null;
|
|
694
|
+
|
|
695
|
+
for (const entry of outcome.entries) {
|
|
696
|
+
const tail = baseUrl
|
|
697
|
+
? ` → ${baseUrl}/projects/${outcome.projectSlug}/flows/${entry.flowSlug}`
|
|
698
|
+
: ` (slug: ${entry.slug})`;
|
|
699
|
+
console.log(`Registered "${entry.name}"${tail}`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
646
703
|
async function ensureStudioRunning(url: string, port: number, noStart: boolean) {
|
|
647
704
|
if (await healthOk(url)) return;
|
|
648
705
|
|
|
@@ -709,10 +766,23 @@ async function runProjectsCreate() {
|
|
|
709
766
|
|
|
710
767
|
async function runFlowsList() {
|
|
711
768
|
const ops = createCliOperations();
|
|
769
|
+
const project = flagValue('project');
|
|
770
|
+
if (project) {
|
|
771
|
+
const result = ops.listFlowsByProject(project);
|
|
772
|
+
if (result.kind !== 'ok') printOutcome(result);
|
|
773
|
+
printOk({ projectSlug: result.data.projectSlug, flows: result.data.flows });
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
712
776
|
const result = ops.listFlows();
|
|
713
777
|
printOk({ flows: result.data });
|
|
714
778
|
}
|
|
715
779
|
|
|
780
|
+
function runProjectsList() {
|
|
781
|
+
const ops = createCliOperations();
|
|
782
|
+
const result = ops.listProjects();
|
|
783
|
+
printOk({ projects: result.data });
|
|
784
|
+
}
|
|
785
|
+
|
|
716
786
|
async function runFlowsSummary() {
|
|
717
787
|
const ops = createCliOperations();
|
|
718
788
|
const result = ops.listFlowsSummary();
|
package/src/events.ts
CHANGED
package/src/mcp.ts
CHANGED
|
@@ -295,7 +295,7 @@ const buildTools = (ops: Operations, ctx: ToolContext): McpTool[] => [
|
|
|
295
295
|
'spec, or flow envelope looks like before authoring writes. Categories: ' +
|
|
296
296
|
'`flow`, `node` (13 flat variants — rectangle/ellipse/sticky/text/' +
|
|
297
297
|
'database/server/user/queue/cloud/image/html/icon/component), ' +
|
|
298
|
-
'`connector`, `action` (playAction/statusAction/
|
|
298
|
+
'`connector`, `action` (playAction/statusAction/statusReport/' +
|
|
299
299
|
"componentAction), `componentSpec` (sidecar shape for type:'component' " +
|
|
300
300
|
'nodes), `style`.',
|
|
301
301
|
inputSchema: {
|
|
@@ -311,7 +311,7 @@ const buildTools = (ops: Operations, ctx: ToolContext): McpTool[] => [
|
|
|
311
311
|
'Optional named schema within the category (requires `name`). For ' +
|
|
312
312
|
"name='node': rectangle, ellipse, sticky, text, database, server, " +
|
|
313
313
|
'user, queue, cloud, image, html, icon, component. For ' +
|
|
314
|
-
"name='action': playAction, statusAction,
|
|
314
|
+
"name='action': playAction, statusAction, statusReport, " +
|
|
315
315
|
"componentAction. For name='componentSpec': componentSpec, " +
|
|
316
316
|
'componentSpecElement.',
|
|
317
317
|
},
|
package/src/merge.ts
CHANGED
|
@@ -32,7 +32,6 @@ export function mergeFlowAndStyle(flow: Flow, style: Style): ResolvedFlow {
|
|
|
32
32
|
version: flow.version,
|
|
33
33
|
name: flow.name,
|
|
34
34
|
...(flow.description !== undefined ? { description: flow.description } : {}),
|
|
35
|
-
...(flow.resetAction ? { resetAction: flow.resetAction } : {}),
|
|
36
35
|
nodes: mergedNodes,
|
|
37
36
|
connectors: mergedConnectors,
|
|
38
37
|
} as ResolvedFlow;
|
|
@@ -114,7 +113,6 @@ export function splitFlow(resolved: {
|
|
|
114
113
|
version: number;
|
|
115
114
|
name: string;
|
|
116
115
|
description?: string;
|
|
117
|
-
resetAction?: unknown;
|
|
118
116
|
nodes: Array<Record<string, unknown>>;
|
|
119
117
|
connectors: Array<Record<string, unknown>>;
|
|
120
118
|
}): { flow: Record<string, unknown>; style: Record<string, unknown> } {
|
|
@@ -187,7 +185,6 @@ export function splitFlow(resolved: {
|
|
|
187
185
|
connectors: flowConnectors,
|
|
188
186
|
};
|
|
189
187
|
if (resolved.description !== undefined) flow.description = resolved.description;
|
|
190
|
-
if (resolved.resetAction !== undefined) flow.resetAction = resolved.resetAction;
|
|
191
188
|
|
|
192
189
|
const style: Record<string, unknown> = {};
|
|
193
190
|
if (Object.keys(styleNodes).length > 0) style.nodes = styleNodes;
|
package/src/operations.ts
CHANGED
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
removeNodeDir,
|
|
23
23
|
writeNodeFile,
|
|
24
24
|
} from './node-files.ts';
|
|
25
|
-
import { scanProject } from './project-scanner.ts';
|
|
26
|
-
import { type Registry, slugify } from './registry.ts';
|
|
25
|
+
import { readProjectManifest, scanProject } from './project-scanner.ts';
|
|
26
|
+
import { type FlowEntry, type Registry, slugify } from './registry.ts';
|
|
27
27
|
import {
|
|
28
28
|
ColorTokenSchema,
|
|
29
29
|
ComponentSpecSchema,
|
|
@@ -404,6 +404,31 @@ export interface CreateProjectSuccess {
|
|
|
404
404
|
|
|
405
405
|
export type ListFlowsOutcome = { kind: 'ok'; data: FlowListItem[] };
|
|
406
406
|
|
|
407
|
+
export interface ProjectListItem {
|
|
408
|
+
projectSlug: string;
|
|
409
|
+
name: string;
|
|
410
|
+
description?: string;
|
|
411
|
+
defaultFlow: string;
|
|
412
|
+
flowCount: number;
|
|
413
|
+
repoPath: string;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export type ListProjectsOutcome = { kind: 'ok'; data: ProjectListItem[] };
|
|
417
|
+
|
|
418
|
+
export interface ProjectFlowListItem {
|
|
419
|
+
id: string;
|
|
420
|
+
slug: string;
|
|
421
|
+
flowSlug: string;
|
|
422
|
+
name: string;
|
|
423
|
+
icon?: string;
|
|
424
|
+
isDefault: boolean;
|
|
425
|
+
valid: boolean;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export type ListFlowsByProjectOutcome =
|
|
429
|
+
| { kind: 'ok'; data: { projectSlug: string; flows: ProjectFlowListItem[] } }
|
|
430
|
+
| { kind: 'projectNotFound'; projectSlug: string };
|
|
431
|
+
|
|
407
432
|
// Minimal projection for agent/CLI discovery — `description` and `name` come
|
|
408
433
|
// from the live watcher snapshot when available so author edits to flow.json
|
|
409
434
|
// surface immediately; fall back to the registry value at startup before
|
|
@@ -737,7 +762,6 @@ function readRawFlowAndStyle(flowPath: string): ReadRawResult {
|
|
|
737
762
|
type MutateMergedFlowMutator<E> = (flow: {
|
|
738
763
|
version: number;
|
|
739
764
|
name: string;
|
|
740
|
-
resetAction?: unknown;
|
|
741
765
|
nodes: Array<Record<string, unknown>>;
|
|
742
766
|
connectors: Array<Record<string, unknown>>;
|
|
743
767
|
}) => { kind: 'ok' } | E;
|
|
@@ -783,7 +807,6 @@ export async function mutateMergedFlow<E extends { kind: string }>(
|
|
|
783
807
|
const merged = inlinedFlow as unknown as {
|
|
784
808
|
version: number;
|
|
785
809
|
name: string;
|
|
786
|
-
resetAction?: unknown;
|
|
787
810
|
nodes: Array<Record<string, unknown>>;
|
|
788
811
|
connectors: Array<Record<string, unknown>>;
|
|
789
812
|
};
|
|
@@ -951,6 +974,52 @@ export function listDemosImpl(deps: OperationsDeps): ListFlowsOutcome {
|
|
|
951
974
|
return { kind: 'ok', data };
|
|
952
975
|
}
|
|
953
976
|
|
|
977
|
+
export function listProjectsImpl(deps: OperationsDeps): ListProjectsOutcome {
|
|
978
|
+
const grouped = new Map<string, FlowEntry[]>();
|
|
979
|
+
for (const entry of deps.registry.list()) {
|
|
980
|
+
const existing = grouped.get(entry.projectSlug);
|
|
981
|
+
if (existing) existing.push(entry);
|
|
982
|
+
else grouped.set(entry.projectSlug, [entry]);
|
|
983
|
+
}
|
|
984
|
+
const data: ProjectListItem[] = [];
|
|
985
|
+
for (const [projectSlug, entries] of grouped) {
|
|
986
|
+
const head = entries[0];
|
|
987
|
+
if (!head) continue;
|
|
988
|
+
const manifest = readProjectManifest(head.repoPath);
|
|
989
|
+
const defaultEntry = entries.find((e) => e.isDefault) ?? head;
|
|
990
|
+
data.push({
|
|
991
|
+
projectSlug,
|
|
992
|
+
name: manifest?.name ?? projectSlug,
|
|
993
|
+
...(manifest?.description !== undefined ? { description: manifest.description } : {}),
|
|
994
|
+
defaultFlow: manifest?.defaultFlow ?? defaultEntry.flowSlug,
|
|
995
|
+
flowCount: entries.length,
|
|
996
|
+
repoPath: head.repoPath,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
return { kind: 'ok', data };
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export function listFlowsByProjectImpl(
|
|
1003
|
+
deps: OperationsDeps,
|
|
1004
|
+
projectSlug: string,
|
|
1005
|
+
): ListFlowsByProjectOutcome {
|
|
1006
|
+
const entries = deps.registry.list().filter((e) => e.projectSlug === projectSlug);
|
|
1007
|
+
if (entries.length === 0) return { kind: 'projectNotFound', projectSlug };
|
|
1008
|
+
const flows: ProjectFlowListItem[] = entries.map((e) => {
|
|
1009
|
+
const fileExists = existsSync(resolveFilePath(e.repoPath, e.flowPath));
|
|
1010
|
+
return {
|
|
1011
|
+
id: e.id,
|
|
1012
|
+
slug: e.slug,
|
|
1013
|
+
flowSlug: e.flowSlug,
|
|
1014
|
+
name: e.name,
|
|
1015
|
+
...(e.icon !== undefined ? { icon: e.icon } : {}),
|
|
1016
|
+
isDefault: e.isDefault,
|
|
1017
|
+
valid: e.valid && fileExists,
|
|
1018
|
+
};
|
|
1019
|
+
});
|
|
1020
|
+
return { kind: 'ok', data: { projectSlug, flows } };
|
|
1021
|
+
}
|
|
1022
|
+
|
|
954
1023
|
export function listFlowsSummaryImpl(deps: OperationsDeps): ListFlowsSummaryOutcome {
|
|
955
1024
|
const { registry, watcher } = deps;
|
|
956
1025
|
const data = registry.list().map((e) => {
|
|
@@ -1899,6 +1968,8 @@ export async function applyLayoutImpl(
|
|
|
1899
1968
|
export interface Operations {
|
|
1900
1969
|
listFlows(): ReturnType<typeof listDemosImpl>;
|
|
1901
1970
|
listFlowsSummary(): ReturnType<typeof listFlowsSummaryImpl>;
|
|
1971
|
+
listProjects(): ReturnType<typeof listProjectsImpl>;
|
|
1972
|
+
listFlowsByProject(projectSlug: string): ReturnType<typeof listFlowsByProjectImpl>;
|
|
1902
1973
|
getFlow(id: string): ReturnType<typeof getFlowImpl>;
|
|
1903
1974
|
getFlowGraph(id: string): ReturnType<typeof getFlowGraphImpl>;
|
|
1904
1975
|
getNode(flowId: string, nodeId: string): ReturnType<typeof getNodeImpl>;
|
|
@@ -1946,6 +2017,8 @@ export function createOperations(deps: OperationsDeps): Operations {
|
|
|
1946
2017
|
return {
|
|
1947
2018
|
listFlows: () => listDemosImpl(deps),
|
|
1948
2019
|
listFlowsSummary: () => listFlowsSummaryImpl(deps),
|
|
2020
|
+
listProjects: () => listProjectsImpl(deps),
|
|
2021
|
+
listFlowsByProject: (projectSlug) => listFlowsByProjectImpl(deps, projectSlug),
|
|
1949
2022
|
getFlow: (id) => getFlowImpl(deps, id),
|
|
1950
2023
|
getFlowGraph: (id) => getFlowGraphImpl(deps, id),
|
|
1951
2024
|
getNode: (flowId, nodeId) => getNodeImpl(deps, flowId, nodeId),
|
package/src/project-scanner.ts
CHANGED
|
@@ -47,6 +47,24 @@ export type ScanResult =
|
|
|
47
47
|
const MANIFEST_FILENAME = 'seeflow.json';
|
|
48
48
|
const LEGACY_FLOW_FILENAME = 'flow.json';
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Best-effort manifest read for listing routes / CLI listing verbs.
|
|
52
|
+
* Returns `null` when the manifest is missing or malformed — callers fall
|
|
53
|
+
* back to derived defaults (projectSlug, isDefault entry) so one broken
|
|
54
|
+
* project does not collapse the whole listing.
|
|
55
|
+
*/
|
|
56
|
+
export function readProjectManifest(repoPath: string): SeeflowManifest | null {
|
|
57
|
+
const manifestPath = join(repoPath, MANIFEST_FILENAME);
|
|
58
|
+
if (!existsSync(manifestPath)) return null;
|
|
59
|
+
try {
|
|
60
|
+
const raw = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
61
|
+
const parsed = SeeflowManifestSchema.safeParse(raw);
|
|
62
|
+
return parsed.success ? parsed.data : null;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
export function scanProject(repoPath: string): ScanResult {
|
|
51
69
|
const manifestPath = join(repoPath, MANIFEST_FILENAME);
|
|
52
70
|
if (!existsSync(manifestPath)) {
|