@tuongaz/seeflow 0.1.91 → 0.1.93
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-CLQb7I2I.js → architectureDiagram-3BPJPVTR-vgRdQoho.js} +1 -1
- package/dist/web/assets/{blockDiagram-GPEHLZMM-DN0OjtOL.js → blockDiagram-GPEHLZMM-D1bbs0Ix.js} +1 -1
- package/dist/web/assets/{c4Diagram-AAUBKEIU-DMVbVbvl.js → c4Diagram-AAUBKEIU-BHmsWf1o.js} +1 -1
- package/dist/web/assets/channel-C-YVVNMU.js +1 -0
- package/dist/web/assets/{chart-Bx3ReVE3.js → chart-CawZdlOV.js} +1 -1
- package/dist/web/assets/{chunk-2J33WTMH-9vQ1xqy3.js → chunk-2J33WTMH-BLQaRMqq.js} +1 -1
- package/dist/web/assets/{chunk-4BX2VUAB-DRGxmqVG.js → chunk-4BX2VUAB-DTivSBmA.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-DlboNFJr.js → chunk-55IACEB6-DZ5Ond8O.js} +1 -1
- package/dist/web/assets/{chunk-727SXJPM-2Se8RGwW.js → chunk-727SXJPM-CwKBkYmr.js} +1 -1
- package/dist/web/assets/{chunk-AQP2D5EJ-DqZEBh23.js → chunk-AQP2D5EJ-CAiwEMmH.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-BJX_21R2.js → chunk-FMBD7UC4-DCWBhj6w.js} +1 -1
- package/dist/web/assets/{chunk-ND2GUHAM-DFBKXknR.js → chunk-ND2GUHAM-vLte473x.js} +1 -1
- package/dist/web/assets/{chunk-QZHKN3VN-CHnWLNTw.js → chunk-QZHKN3VN-Bcu6ixss.js} +1 -1
- package/dist/web/assets/classDiagram-4FO5ZUOK-BklVRjbL.js +1 -0
- package/dist/web/assets/classDiagram-v2-Q7XG4LA2-BklVRjbL.js +1 -0
- package/dist/web/assets/{code-block-IJZcqBQa.js → code-block-Q3inQ3vw.js} +1 -1
- package/dist/web/assets/{cose-bilkent-S5V4N54A-B0LjjLKu.js → cose-bilkent-S5V4N54A-Cb8kZ6Km.js} +1 -1
- package/dist/web/assets/{dagre-BM42HDAG-mMg_Ia_X.js → dagre-BM42HDAG-Xws4A-Mi.js} +1 -1
- package/dist/web/assets/{diagram-2AECGRRQ-BUy1Taew.js → diagram-2AECGRRQ-S1kziNDg.js} +1 -1
- package/dist/web/assets/{diagram-5GNKFQAL-DH_tg4Cb.js → diagram-5GNKFQAL-eoeXtzaC.js} +1 -1
- package/dist/web/assets/{diagram-KO2AKTUF-CE1rkBey.js → diagram-KO2AKTUF-DktxY0CQ.js} +1 -1
- package/dist/web/assets/{diagram-LMA3HP47-DS7ee5II.js → diagram-LMA3HP47-myBeIjhs.js} +1 -1
- package/dist/web/assets/{diagram-OG6HWLK6-DzCf3KBM.js → diagram-OG6HWLK6-CFj88ujv.js} +1 -1
- package/dist/web/assets/{erDiagram-TEJ5UH35-Spog-6po.js → erDiagram-TEJ5UH35-WaEyv1iP.js} +1 -1
- package/dist/web/assets/{flowDiagram-I6XJVG4X-0VAojPZO.js → flowDiagram-I6XJVG4X-DAZ3T2Zd.js} +1 -1
- package/dist/web/assets/{ganttDiagram-6RSMTGT7-DcjKkuMU.js → ganttDiagram-6RSMTGT7-uY09PoQq.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PVQCEYII-Bq9UIINL.js → gitGraphDiagram-PVQCEYII-BSJHmF4Z.js} +1 -1
- package/dist/web/assets/index-5X7OVal6.js +8624 -0
- package/dist/web/assets/{index-CWGdrwRY.css → index-I8_SAWCr.css} +1 -1
- package/dist/web/assets/{index.es-BUxcZ9iL.js → index.es-BJkNyJb3.js} +1 -1
- package/dist/web/assets/{infoDiagram-5YYISTIA-yxIJiF1X.js → infoDiagram-5YYISTIA-BJIRmQdX.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-BkcKepSB.js → ishikawaDiagram-YF4QCWOH-Dk3i-Xkk.js} +1 -1
- package/dist/web/assets/{journeyDiagram-JHISSGLW-KfXgvDdv.js → journeyDiagram-JHISSGLW-DnJaLB-P.js} +1 -1
- package/dist/web/assets/{jspdf.es.min-BPfxcczM.js → jspdf.es.min-C9Avn9P2.js} +3 -3
- package/dist/web/assets/{kanban-definition-UN3LZRKU-B2m52Unk.js → kanban-definition-UN3LZRKU-CAvihSvB.js} +1 -1
- package/dist/web/assets/{linear-D-pwAWPr.js → linear-QJGLYtiK.js} +1 -1
- package/dist/web/assets/{markdown-lr17R9FO.js → markdown-umDyoRvw.js} +1 -1
- package/dist/web/assets/{mermaid.core-CRo4rzDL.js → mermaid.core-DjNa-8Hv.js} +4 -4
- package/dist/web/assets/{mindmap-definition-RKZ34NQL-DUpVPXgC.js → mindmap-definition-RKZ34NQL-DdGdY0IJ.js} +1 -1
- package/dist/web/assets/{pieDiagram-4H26LBE5-CatVLCYi.js → pieDiagram-4H26LBE5-ByidPHli.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-W4KKPZXB-TJ5_ZxiK.js → quadrantDiagram-W4KKPZXB-3-CAwQni.js} +1 -1
- package/dist/web/assets/{requirementDiagram-4Y6WPE33-B7CTMFGC.js → requirementDiagram-4Y6WPE33-Bc0BnzZd.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-5OEKKPKP-C56xsvrm.js → sankeyDiagram-5OEKKPKP-CnxTmkwV.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-3UESZ5HK-EPKxvTJ9.js → sequenceDiagram-3UESZ5HK-DrDm1r9r.js} +1 -1
- package/dist/web/assets/{stateDiagram-AJRCARHV-Cma2F0T8.js → stateDiagram-AJRCARHV-Dgco9NEU.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-BHNVJYJU-F-kPNI2H.js +1 -0
- package/dist/web/assets/{time-D6UR1Qac.js → time-Yxy9gOXu.js} +1 -1
- package/dist/web/assets/{timeline-definition-PNZ67QCA-iYUvK9JO.js → timeline-definition-PNZ67QCA-CquekUx0.js} +1 -1
- package/dist/web/assets/{vennDiagram-CIIHVFJN-CkLxULtS.js → vennDiagram-CIIHVFJN-BGGVq4Mv.js} +1 -1
- package/dist/web/assets/{wardley-L42UT6IY-DPcFqZu2.js → wardley-L42UT6IY-fkIRiPsZ.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-YWT4CUSO-BKqSvb-r.js → wardleyDiagram-YWT4CUSO-ih5pIO2M.js} +1 -1
- package/dist/web/assets/{xychartDiagram-2RQKCTM6-0L00Xzr6.js → xychartDiagram-2RQKCTM6-Rwg_Ry62.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/api.ts +20 -2
- package/src/cli-manifest.ts +49 -24
- package/src/cli.ts +28 -9
- package/src/mcp.ts +14 -2
- package/src/merge.ts +0 -1
- package/src/node-files.ts +39 -1
- package/src/operations.ts +35 -36
- package/src/schema-catalog.ts +109 -3
- package/src/schema.ts +5 -6
- package/dist/web/assets/channel-CilAQwI4.js +0 -1
- package/dist/web/assets/classDiagram-4FO5ZUOK-fiT0MjXY.js +0 -1
- package/dist/web/assets/classDiagram-v2-Q7XG4LA2-fiT0MjXY.js +0 -1
- package/dist/web/assets/index-DFpY3RpV.js +0 -8624
- package/dist/web/assets/stateDiagram-v2-BHNVJYJU-DRP_iswg.js +0 -1
package/src/cli.ts
CHANGED
|
@@ -268,11 +268,14 @@ Commands (work without a running studio):
|
|
|
268
268
|
connectors:patch <connId> Patch a connector (--project <p> --flow <f>) [--json/--file/--stdin]
|
|
269
269
|
connectors:delete <connId> Delete a connector (--project <p> --flow <f>)
|
|
270
270
|
validate Schema-validate a flow.json (--file <file> [--style <file>])
|
|
271
|
-
schema [<category> [<subname>]]
|
|
272
|
-
Get the flow.json schema.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
271
|
+
schema [<category> [<subname>]] [--jq <path>]
|
|
272
|
+
Get the flow.json schema. Run this before designing /
|
|
273
|
+
authoring nodes. No arg → category index with subnames
|
|
274
|
+
inlined; category arg → full JSON Schema(s) + jqHints;
|
|
275
|
+
subname arg → one variant + jqHints.dataFields listing
|
|
276
|
+
every data.<field> available. Pair with --jq to slice
|
|
277
|
+
(e.g. 'schema node rectangle --jq
|
|
278
|
+
.schemas.rectangle.properties.data.properties.playAction').
|
|
276
279
|
ids <type> <count> Print <count> short ids of the given <type>, one per
|
|
277
280
|
line. <type> is 'node' (-> 'node-...') or 'connector'
|
|
278
281
|
(-> 'conn-...'). <count> is 1..100. Call once per type
|
|
@@ -1100,10 +1103,19 @@ async function runSchema() {
|
|
|
1100
1103
|
const category = argv[1] && !argv[1].startsWith('--') ? argv[1] : undefined;
|
|
1101
1104
|
const subname = argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined;
|
|
1102
1105
|
const jqFilter = flagValue('jq');
|
|
1103
|
-
const {
|
|
1104
|
-
|
|
1106
|
+
const {
|
|
1107
|
+
listSchemaCategories,
|
|
1108
|
+
getSchemaCategory,
|
|
1109
|
+
getCategorySubschema,
|
|
1110
|
+
listCategorySubnames,
|
|
1111
|
+
buildJqHints,
|
|
1112
|
+
SCHEMA_INDEX_USAGE,
|
|
1113
|
+
} = await import('./schema-catalog.ts');
|
|
1105
1114
|
if (!category) {
|
|
1106
|
-
const base = {
|
|
1115
|
+
const base = {
|
|
1116
|
+
categories: listSchemaCategories(),
|
|
1117
|
+
usage: SCHEMA_INDEX_USAGE,
|
|
1118
|
+
};
|
|
1107
1119
|
if (jqFilter !== undefined) {
|
|
1108
1120
|
printOk({ result: applyJqOrDie(base, jqFilter) });
|
|
1109
1121
|
}
|
|
@@ -1141,6 +1153,7 @@ async function runSchema() {
|
|
|
1141
1153
|
subname,
|
|
1142
1154
|
schemas: single.schemas,
|
|
1143
1155
|
notes: single.notes,
|
|
1156
|
+
jqHints: buildJqHints(category as string, subname),
|
|
1144
1157
|
};
|
|
1145
1158
|
if (jqFilter !== undefined) {
|
|
1146
1159
|
printOk({ name: category, subname, result: applyJqOrDie(base, jqFilter) });
|
|
@@ -1154,7 +1167,13 @@ async function runSchema() {
|
|
|
1154
1167
|
process.stderr.write(`${JSON.stringify({ error: message, code: 'notFound', available })}\n`);
|
|
1155
1168
|
process.exit(3);
|
|
1156
1169
|
}
|
|
1157
|
-
const base = {
|
|
1170
|
+
const base = {
|
|
1171
|
+
name: category,
|
|
1172
|
+
schemas: payload.schemas,
|
|
1173
|
+
notes: payload.notes,
|
|
1174
|
+
subnames: listCategorySubnames(category as string) ?? [],
|
|
1175
|
+
jqHints: buildJqHints(category as string),
|
|
1176
|
+
};
|
|
1158
1177
|
if (jqFilter !== undefined) {
|
|
1159
1178
|
printOk({ name: category, result: applyJqOrDie(base, jqFilter) });
|
|
1160
1179
|
}
|
package/src/mcp.ts
CHANGED
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
} from './operations.ts';
|
|
31
31
|
import type { Registry } from './registry.ts';
|
|
32
32
|
import {
|
|
33
|
+
SCHEMA_INDEX_USAGE,
|
|
34
|
+
buildJqHints,
|
|
33
35
|
getCategorySubschema,
|
|
34
36
|
getSchemaCategory,
|
|
35
37
|
listCategorySubnames,
|
|
@@ -329,7 +331,10 @@ const buildTools = (ops: Operations, ctx: ToolContext): McpTool[] => [
|
|
|
329
331
|
if (subname !== undefined && subname !== null && subname !== '') {
|
|
330
332
|
return errorResult('Invalid arguments: `subname` requires `name` to be set');
|
|
331
333
|
}
|
|
332
|
-
return okResult({
|
|
334
|
+
return okResult({
|
|
335
|
+
categories: listSchemaCategories(),
|
|
336
|
+
usage: SCHEMA_INDEX_USAGE,
|
|
337
|
+
});
|
|
333
338
|
}
|
|
334
339
|
if (typeof name !== 'string') {
|
|
335
340
|
return errorResult('Invalid arguments: `name` must be a string when present');
|
|
@@ -345,6 +350,7 @@ const buildTools = (ops: Operations, ctx: ToolContext): McpTool[] => [
|
|
|
345
350
|
subname,
|
|
346
351
|
schemas: single.schemas,
|
|
347
352
|
notes: single.notes,
|
|
353
|
+
jqHints: buildJqHints(name, subname),
|
|
348
354
|
});
|
|
349
355
|
}
|
|
350
356
|
const availableSubs = listCategorySubnames(name);
|
|
@@ -363,7 +369,13 @@ const buildTools = (ops: Operations, ctx: ToolContext): McpTool[] => [
|
|
|
363
369
|
`unknown schema category: ${name} (available: ${schemaCategoryNames().join(', ')})`,
|
|
364
370
|
);
|
|
365
371
|
}
|
|
366
|
-
return okResult({
|
|
372
|
+
return okResult({
|
|
373
|
+
name,
|
|
374
|
+
schemas: payload.schemas,
|
|
375
|
+
notes: payload.notes,
|
|
376
|
+
subnames: listCategorySubnames(name) ?? [],
|
|
377
|
+
jqHints: buildJqHints(name),
|
|
378
|
+
});
|
|
367
379
|
},
|
|
368
380
|
},
|
|
369
381
|
{
|
package/src/merge.ts
CHANGED
package/src/node-files.ts
CHANGED
|
@@ -8,16 +8,54 @@ import { writeFileAtomic } from './atomic-write.ts';
|
|
|
8
8
|
// `flows/<flow-id>/nodes/<id>/`; for legacy single-flow fixtures with
|
|
9
9
|
// `flowPath: 'flow.json'` it collapses to the project root. `nodeTypes` (when
|
|
10
10
|
// present) scopes the spec entry to specific node types; absent means
|
|
11
|
-
// "applies to every node type".
|
|
11
|
+
// "applies to every node type".
|
|
12
|
+
//
|
|
13
|
+
// Two flavors of externalization:
|
|
14
|
+
// - `kind: 'ref'` (default) — write the file, replace `data[field]` with
|
|
15
|
+
// `file://<fileName>`. The ref survives splitFlow (the field is in
|
|
16
|
+
// NODE_DATA_FLOW_KEYS in merge.ts). Used by string fields like `detail`
|
|
17
|
+
// and `html`.
|
|
18
|
+
// - `kind: 'sidecar'` — write the file, leave `data[field]` untouched on
|
|
19
|
+
// the in-memory node so the post-mutation parse still sees the original
|
|
20
|
+
// value. splitFlow drops the field from flow.json on write; the resolver
|
|
21
|
+
// inlines it from disk on read. Used by JSON fields like component `spec`.
|
|
22
|
+
//
|
|
23
|
+
// `serialize` turns the in-memory value into file contents. Returning `null`
|
|
24
|
+
// skips the write entirely — used by `spec` to no-op when the caller didn't
|
|
25
|
+
// supply one, instead of writing an empty file that would fail JSON parse
|
|
26
|
+
// on the next read.
|
|
12
27
|
export interface ExternalizedFieldSpec {
|
|
13
28
|
field: string;
|
|
14
29
|
fileName: string;
|
|
15
30
|
nodeTypes?: readonly string[];
|
|
31
|
+
kind?: 'ref' | 'sidecar';
|
|
32
|
+
serialize?: (value: unknown) => string | null;
|
|
16
33
|
}
|
|
17
34
|
|
|
35
|
+
// Default serializer: strings pass through; non-strings coerce to empty.
|
|
36
|
+
// Keeps the historical detail/html behavior — an absent detail still writes
|
|
37
|
+
// an empty detail.md so the file:// ref points somewhere.
|
|
38
|
+
export const defaultExternalizedSerializer = (value: unknown): string =>
|
|
39
|
+
typeof value === 'string' ? value : '';
|
|
40
|
+
|
|
41
|
+
// JSON serializer for sidecar fields: pretty-print plain objects with a
|
|
42
|
+
// trailing newline. Returns null for anything else so the loop can skip the
|
|
43
|
+
// write rather than emit an invalid sidecar.
|
|
44
|
+
const jsonExternalizedSerializer = (value: unknown): string | null =>
|
|
45
|
+
value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
46
|
+
? `${JSON.stringify(value, null, 2)}\n`
|
|
47
|
+
: null;
|
|
48
|
+
|
|
18
49
|
export const EXTERNALIZED_NODE_FIELDS: readonly ExternalizedFieldSpec[] = [
|
|
19
50
|
{ field: 'detail', fileName: 'detail.md' },
|
|
20
51
|
{ field: 'html', fileName: 'view.html', nodeTypes: ['html'] },
|
|
52
|
+
{
|
|
53
|
+
field: 'spec',
|
|
54
|
+
fileName: 'spec.json',
|
|
55
|
+
nodeTypes: ['component'],
|
|
56
|
+
kind: 'sidecar',
|
|
57
|
+
serialize: jsonExternalizedSerializer,
|
|
58
|
+
},
|
|
21
59
|
];
|
|
22
60
|
|
|
23
61
|
export const externalizedFieldsForNodeType = (
|
package/src/operations.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { type LayoutOptions, computeLayout } from './layout.ts';
|
|
|
16
16
|
import { mergeFlowAndStyle, splitFlow } from './merge.ts';
|
|
17
17
|
import {
|
|
18
18
|
EXTERNALIZED_NODE_FIELDS,
|
|
19
|
+
defaultExternalizedSerializer,
|
|
19
20
|
externalizedFieldsForNodeType,
|
|
20
21
|
nodeFileAbsPath,
|
|
21
22
|
nodeFileRef,
|
|
@@ -100,7 +101,6 @@ export const NodePatchBodySchema = z
|
|
|
100
101
|
borderWidth: z.number().min(0).max(8).optional(),
|
|
101
102
|
borderStyle: z.enum(['solid', 'dashed', 'dotted']).optional(),
|
|
102
103
|
fontSize: z.number().positive().optional(),
|
|
103
|
-
textColor: ColorTokenSchema.optional(),
|
|
104
104
|
textAlign: z.enum(['left', 'center', 'right']).optional(),
|
|
105
105
|
cornerRadius: z.number().min(0).optional(),
|
|
106
106
|
shadow: z.number().int().min(0).max(5).optional(),
|
|
@@ -164,7 +164,6 @@ const NODE_DATA_PATCH_KEYS = [
|
|
|
164
164
|
'borderWidth',
|
|
165
165
|
'borderStyle',
|
|
166
166
|
'fontSize',
|
|
167
|
-
'textColor',
|
|
168
167
|
'textAlign',
|
|
169
168
|
'cornerRadius',
|
|
170
169
|
'shadow',
|
|
@@ -267,7 +266,6 @@ const NODE_VISUAL_KEYS = new Set([
|
|
|
267
266
|
'borderSize',
|
|
268
267
|
'borderStyle',
|
|
269
268
|
'fontSize',
|
|
270
|
-
'textColor',
|
|
271
269
|
'textAlign',
|
|
272
270
|
'cornerRadius',
|
|
273
271
|
'shadow',
|
|
@@ -1347,12 +1345,16 @@ export async function addNodeImpl(
|
|
|
1347
1345
|
? { ...(newNode.data as Record<string, unknown>) }
|
|
1348
1346
|
: {};
|
|
1349
1347
|
const flowDir = dirname(entry.flowPath);
|
|
1350
|
-
for (const
|
|
1351
|
-
const incoming = data[field];
|
|
1352
|
-
const
|
|
1353
|
-
|
|
1348
|
+
for (const spec of externalizedFieldsForNodeType(newNode.type)) {
|
|
1349
|
+
const incoming = data[spec.field];
|
|
1350
|
+
const serializer = spec.serialize ?? defaultExternalizedSerializer;
|
|
1351
|
+
const content = serializer(incoming);
|
|
1352
|
+
if (content === null) continue; // serializer opted out (e.g. spec absent)
|
|
1353
|
+
if ((spec.kind ?? 'ref') === 'ref') {
|
|
1354
|
+
data[spec.field] = nodeFileRef(newId, spec.fileName);
|
|
1355
|
+
}
|
|
1354
1356
|
externalized.push({
|
|
1355
|
-
absPath: nodeFileAbsPath(entry.repoPath, flowDir, newId, fileName),
|
|
1357
|
+
absPath: nodeFileAbsPath(entry.repoPath, flowDir, newId, spec.fileName),
|
|
1356
1358
|
content,
|
|
1357
1359
|
});
|
|
1358
1360
|
}
|
|
@@ -1431,12 +1433,16 @@ export async function addFlowBulkImpl(
|
|
|
1431
1433
|
const data: Record<string, unknown> = dataIsRecord
|
|
1432
1434
|
? { ...(newNode.data as Record<string, unknown>) }
|
|
1433
1435
|
: {};
|
|
1434
|
-
for (const
|
|
1435
|
-
const incoming = data[field];
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1436
|
+
for (const spec of externalizedFieldsForNodeType(newNode.type)) {
|
|
1437
|
+
const incoming = data[spec.field];
|
|
1438
|
+
const serializer = spec.serialize ?? defaultExternalizedSerializer;
|
|
1439
|
+
const content = serializer(incoming);
|
|
1440
|
+
if (content === null) continue; // serializer opted out (e.g. spec absent)
|
|
1441
|
+
if ((spec.kind ?? 'ref') === 'ref') {
|
|
1442
|
+
data[spec.field] = nodeFileRef(newId, spec.fileName);
|
|
1443
|
+
}
|
|
1438
1444
|
externalized.push({
|
|
1439
|
-
absPath: nodeFileAbsPath(entry.repoPath, flowDir, newId, fileName),
|
|
1445
|
+
absPath: nodeFileAbsPath(entry.repoPath, flowDir, newId, spec.fileName),
|
|
1440
1446
|
content,
|
|
1441
1447
|
});
|
|
1442
1448
|
}
|
|
@@ -1637,15 +1643,20 @@ export async function patchNodeImpl(
|
|
|
1637
1643
|
ref: string;
|
|
1638
1644
|
field: string;
|
|
1639
1645
|
content: string;
|
|
1646
|
+
kind: 'ref' | 'sidecar';
|
|
1640
1647
|
}> = [];
|
|
1641
|
-
for (const
|
|
1642
|
-
const incoming = (updates as Record<string, unknown>)[field];
|
|
1648
|
+
for (const spec of externalizedFieldsForNodeType(node.type)) {
|
|
1649
|
+
const incoming = (updates as Record<string, unknown>)[spec.field];
|
|
1643
1650
|
if (incoming === undefined) continue;
|
|
1651
|
+
const serializer = spec.serialize ?? defaultExternalizedSerializer;
|
|
1652
|
+
const content = serializer(incoming);
|
|
1653
|
+
if (content === null) continue; // serializer opted out
|
|
1644
1654
|
externalizedWrites.push({
|
|
1645
|
-
absPath: nodeFileAbsPath(entry.repoPath, flowDir, nodeId, fileName),
|
|
1646
|
-
ref: nodeFileRef(nodeId, fileName),
|
|
1647
|
-
field,
|
|
1648
|
-
content
|
|
1655
|
+
absPath: nodeFileAbsPath(entry.repoPath, flowDir, nodeId, spec.fileName),
|
|
1656
|
+
ref: nodeFileRef(nodeId, spec.fileName),
|
|
1657
|
+
field: spec.field,
|
|
1658
|
+
content,
|
|
1659
|
+
kind: spec.kind ?? 'ref',
|
|
1649
1660
|
});
|
|
1650
1661
|
}
|
|
1651
1662
|
mergeNodeUpdates(node, updates);
|
|
@@ -1664,26 +1675,14 @@ export async function patchNodeImpl(
|
|
|
1664
1675
|
message: err instanceof Error ? err.message : String(err),
|
|
1665
1676
|
};
|
|
1666
1677
|
}
|
|
1667
|
-
data[
|
|
1678
|
+
// 'ref' fields swap data[field] for a file:// pointer; 'sidecar'
|
|
1679
|
+
// fields (e.g. component spec) leave the in-memory value alone so the
|
|
1680
|
+
// post-mutation parse still sees it — splitFlow drops it from flow.json
|
|
1681
|
+
// on write and the resolver inlines it back from disk on read.
|
|
1682
|
+
if (w.kind === 'ref') data[w.field] = w.ref;
|
|
1668
1683
|
}
|
|
1669
1684
|
node.data = data;
|
|
1670
1685
|
}
|
|
1671
|
-
// Component spec sidecar — write the pretty-printed JSON to
|
|
1672
|
-
// `<repoPath>/<flowDir>/nodes/<id>/spec.json` so the on-disk source of
|
|
1673
|
-
// truth stays in sync. mergeNodeUpdates already put data.spec on the
|
|
1674
|
-
// merged tree for the post-mutation ResolvedFlowSchema parse; splitFlow
|
|
1675
|
-
// strips it from flow.json so we don't double-store the spec.
|
|
1676
|
-
if (node.type === 'component' && updates.spec !== undefined) {
|
|
1677
|
-
const specAbs = nodeFileAbsPath(entry.repoPath, flowDir, nodeId, 'spec.json');
|
|
1678
|
-
try {
|
|
1679
|
-
writeNodeFile(specAbs, `${JSON.stringify(updates.spec, null, 2)}\n`);
|
|
1680
|
-
} catch (err) {
|
|
1681
|
-
return {
|
|
1682
|
-
kind: 'writeFailed',
|
|
1683
|
-
message: err instanceof Error ? err.message : String(err),
|
|
1684
|
-
};
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
1686
|
return { kind: 'ok' };
|
|
1688
1687
|
});
|
|
1689
1688
|
}
|
package/src/schema-catalog.ts
CHANGED
|
@@ -37,6 +37,9 @@ import {
|
|
|
37
37
|
export interface SchemaCategory {
|
|
38
38
|
name: string;
|
|
39
39
|
description: string;
|
|
40
|
+
// Every drill target valid for `seeflow schema <name> <subname>`. Lets the
|
|
41
|
+
// agent pick a variant without a second round-trip to listCategorySubnames.
|
|
42
|
+
subnames: string[];
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export interface SchemaPayload {
|
|
@@ -44,13 +47,40 @@ export interface SchemaPayload {
|
|
|
44
47
|
notes: string[];
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
// Hint payload attached to every schema response so the agent can drill in
|
|
51
|
+
// further without round-tripping. `examples` are ready-to-paste jq paths;
|
|
52
|
+
// `dataFields` lists the node-variant `data.<field>` keys (single-variant
|
|
53
|
+
// lookups only — undefined for non-node categories or category-level
|
|
54
|
+
// responses).
|
|
55
|
+
export interface JqHints {
|
|
56
|
+
dataFields?: string[];
|
|
57
|
+
examples: string[];
|
|
58
|
+
tip?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
// Draft-07 pin matches the widest tool support; the same target string is
|
|
48
62
|
// used by the MCP `tools/list` JSON Schemas (default in zod-to-json-schema)
|
|
49
63
|
// so consumers see one consistent dialect across the whole surface.
|
|
50
64
|
const toJsonSchema = (schema: ZodTypeAny): unknown =>
|
|
51
65
|
zodToJsonSchema(schema, { $refStrategy: 'none', target: 'jsonSchema7' });
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
// Recipe block returned on the schema index (CLI / REST / MCP) so the agent
|
|
68
|
+
// sees the drill + filter pattern in the response itself, not just in
|
|
69
|
+
// `seeflow help schema`.
|
|
70
|
+
export const SCHEMA_INDEX_USAGE = {
|
|
71
|
+
drill: 'seeflow schema <category> [<subname>]',
|
|
72
|
+
filter: 'seeflow schema <category> [<subname>] --jq <jq-path>',
|
|
73
|
+
examples: [
|
|
74
|
+
'seeflow schema node',
|
|
75
|
+
'seeflow schema node rectangle',
|
|
76
|
+
'seeflow schema node rectangle --jq .schemas.rectangle.properties.data.properties.playAction',
|
|
77
|
+
'seeflow schema action playAction',
|
|
78
|
+
],
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
81
|
+
// Description metadata. `subnames` are filled in by listSchemaCategories()
|
|
82
|
+
// at call time from PAYLOADS, so the two stay in lockstep automatically.
|
|
83
|
+
const CATEGORY_META: Array<Omit<SchemaCategory, 'subnames'>> = [
|
|
54
84
|
{ name: 'flow', description: 'Top-level flow.json envelope.' },
|
|
55
85
|
{
|
|
56
86
|
name: 'node',
|
|
@@ -141,7 +171,10 @@ const PAYLOADS: Record<string, SchemaPayload> = {
|
|
|
141
171
|
};
|
|
142
172
|
|
|
143
173
|
export function listSchemaCategories(): SchemaCategory[] {
|
|
144
|
-
return
|
|
174
|
+
return CATEGORY_META.map((c) => ({
|
|
175
|
+
...c,
|
|
176
|
+
subnames: Object.keys(PAYLOADS[c.name]?.schemas ?? {}),
|
|
177
|
+
}));
|
|
145
178
|
}
|
|
146
179
|
|
|
147
180
|
export function getSchemaCategory(name: string): SchemaPayload | null {
|
|
@@ -151,7 +184,7 @@ export function getSchemaCategory(name: string): SchemaPayload | null {
|
|
|
151
184
|
}
|
|
152
185
|
|
|
153
186
|
export function schemaCategoryNames(): string[] {
|
|
154
|
-
return
|
|
187
|
+
return CATEGORY_META.map((c) => c.name);
|
|
155
188
|
}
|
|
156
189
|
|
|
157
190
|
// Drill into one named schema inside a category — e.g. ('node', 'rectangle')
|
|
@@ -176,3 +209,76 @@ export function listCategorySubnames(category: string): string[] | null {
|
|
|
176
209
|
if (!payload) return null;
|
|
177
210
|
return Object.keys(payload.schemas);
|
|
178
211
|
}
|
|
212
|
+
|
|
213
|
+
// Top-level keys under `data.properties` for a single node variant — i.e.
|
|
214
|
+
// the per-shape data fields an author actually sets on a flow.json node
|
|
215
|
+
// (`name`, `icon`, `playAction`, etc.). Returns null when the variant has
|
|
216
|
+
// no `data.properties` wrapper (action / connector / style / componentSpec
|
|
217
|
+
// schemas, plus anything malformed). Pure helper consumed by buildJqHints
|
|
218
|
+
// to surface concrete drill-down paths.
|
|
219
|
+
export function getDataFieldNames(category: string, subname: string): string[] | null {
|
|
220
|
+
const sub = PAYLOADS[category]?.schemas[subname] as
|
|
221
|
+
| { properties?: { data?: { properties?: Record<string, unknown> } } }
|
|
222
|
+
| undefined;
|
|
223
|
+
const dataProps = sub?.properties?.data?.properties;
|
|
224
|
+
if (!dataProps) return null;
|
|
225
|
+
return Object.keys(dataProps);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Build ready-to-paste jq path examples for a schema response. When `subname`
|
|
229
|
+
// is provided, the examples drill into that single variant — including one
|
|
230
|
+
// path per `data.<field>` so the agent can `--jq` straight to (say)
|
|
231
|
+
// `.schemas.rectangle.properties.data.properties.playAction` without first
|
|
232
|
+
// reading the whole envelope. When `subname` is omitted, the hints cover the
|
|
233
|
+
// whole category (iteration, one sample variant, notes). `dataFields` only
|
|
234
|
+
// surfaces on single-variant lookups for shapes that actually carry a
|
|
235
|
+
// `data.properties` wrapper.
|
|
236
|
+
export function buildJqHints(category: string, subname?: string): JqHints | null {
|
|
237
|
+
const payload = PAYLOADS[category];
|
|
238
|
+
if (!payload) return null;
|
|
239
|
+
if (subname) {
|
|
240
|
+
if (payload.schemas[subname] === undefined) return null;
|
|
241
|
+
const dataFields = getDataFieldNames(category, subname);
|
|
242
|
+
const examples = [
|
|
243
|
+
`.schemas.${subname}`,
|
|
244
|
+
`.schemas.${subname}.required`,
|
|
245
|
+
...(dataFields && dataFields.length > 0
|
|
246
|
+
? [
|
|
247
|
+
`.schemas.${subname}.properties.data.properties`,
|
|
248
|
+
...dataFields
|
|
249
|
+
.slice(0, 6)
|
|
250
|
+
.map((f) => `.schemas.${subname}.properties.data.properties.${f}`),
|
|
251
|
+
]
|
|
252
|
+
: [`.schemas.${subname}.properties`]),
|
|
253
|
+
'.notes',
|
|
254
|
+
'.notes[]',
|
|
255
|
+
];
|
|
256
|
+
const hint = dataFields
|
|
257
|
+
? `dataFields lists every \`data.<field>\` available on this variant — point \`--jq\` at any of them with \`.schemas.${subname}.properties.data.properties.<field>\`.`
|
|
258
|
+
: `Use \`--jq\` to pluck a single property — e.g. \`.schemas.${subname}.required\`.`;
|
|
259
|
+
return {
|
|
260
|
+
...(dataFields ? { dataFields } : {}),
|
|
261
|
+
examples,
|
|
262
|
+
tip: hint,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const subs = Object.keys(payload.schemas);
|
|
266
|
+
const sample = subs[0];
|
|
267
|
+
if (!sample) return { examples: ['.schemas', '.notes', '.notes[]'] };
|
|
268
|
+
const examples = [
|
|
269
|
+
'.schemas',
|
|
270
|
+
`.schemas.${sample}`,
|
|
271
|
+
`.schemas.${sample}.required`,
|
|
272
|
+
`.schemas.${sample}.properties.data.properties`,
|
|
273
|
+
'.schemas[]',
|
|
274
|
+
'.notes',
|
|
275
|
+
'.notes[]',
|
|
276
|
+
];
|
|
277
|
+
return {
|
|
278
|
+
examples,
|
|
279
|
+
tip:
|
|
280
|
+
subs.length > 1
|
|
281
|
+
? `Pass \`seeflow schema ${category} <subname>\` (one of: ${subs.join(', ')}) to drop the other ${subs.length - 1} variant(s) from the payload before \`--jq\`-ing.`
|
|
282
|
+
: `Single-variant category — \`--jq\` paths drill straight into \`.schemas.${sample}\`.`,
|
|
283
|
+
};
|
|
284
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -14,25 +14,26 @@ const HttpMethodSchema = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
|
14
14
|
// them to actual CSS values (theme-aware, light + dark).
|
|
15
15
|
export const ColorTokenSchema = z.enum([
|
|
16
16
|
// `'none'` renders transparent border / fill on nodes (no stroke, no fill).
|
|
17
|
-
// Hidden from the
|
|
18
|
-
// and
|
|
17
|
+
// Hidden from the connector-color picker — invisible edges aren't useful,
|
|
18
|
+
// and `'default'` already covers "inherit".
|
|
19
19
|
'none',
|
|
20
20
|
'default',
|
|
21
21
|
'white',
|
|
22
22
|
'slate',
|
|
23
23
|
'gray',
|
|
24
24
|
'red',
|
|
25
|
-
'rose',
|
|
26
25
|
'orange',
|
|
27
26
|
'amber',
|
|
27
|
+
'yellow',
|
|
28
28
|
'lime',
|
|
29
29
|
'green',
|
|
30
30
|
'teal',
|
|
31
31
|
'cyan',
|
|
32
|
+
'sky',
|
|
32
33
|
'blue',
|
|
33
34
|
'indigo',
|
|
34
35
|
'violet',
|
|
35
|
-
'
|
|
36
|
+
'fuchsia',
|
|
36
37
|
'pink',
|
|
37
38
|
]);
|
|
38
39
|
|
|
@@ -47,7 +48,6 @@ const NodeVisualBaseShape = {
|
|
|
47
48
|
borderSize: z.number().min(0).optional(),
|
|
48
49
|
borderStyle: z.enum(['solid', 'dashed', 'dotted']).optional(),
|
|
49
50
|
fontSize: z.number().positive().optional(),
|
|
50
|
-
textColor: ColorTokenSchema.optional(),
|
|
51
51
|
// Horizontal alignment for the node's text content. Defaults to 'center'
|
|
52
52
|
// at render time when omitted; explicit picks from the toolbar's Align
|
|
53
53
|
// toggle persist here.
|
|
@@ -711,7 +711,6 @@ const NodeStyleSchema = z
|
|
|
711
711
|
borderSize: z.number().min(0).optional(),
|
|
712
712
|
borderStyle: z.enum(['solid', 'dashed', 'dotted']).optional(),
|
|
713
713
|
fontSize: z.number().positive().optional(),
|
|
714
|
-
textColor: ColorTokenSchema.optional(),
|
|
715
714
|
textAlign: z.enum(['left', 'center', 'right']).optional(),
|
|
716
715
|
cornerRadius: z.number().min(0).optional(),
|
|
717
716
|
shadow: z.number().int().min(0).max(5).optional(),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./mermaid.core-CRo4rzDL.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as e,C as t}from"./chunk-727SXJPM-2Se8RGwW.js";import{a as i}from"./mermaid.core-CRo4rzDL.js";import"./index-DFpY3RpV.js";import"./chunk-FMBD7UC4-BJX_21R2.js";import"./chunk-ND2GUHAM-DFBKXknR.js";import"./chunk-55IACEB6-DlboNFJr.js";import"./chunk-2J33WTMH-9vQ1xqy3.js";import"./purify.es-CLGrRn1w.js";import"./step-CWvwoXpJ.js";var b={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{b as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as e,C as t}from"./chunk-727SXJPM-2Se8RGwW.js";import{a as i}from"./mermaid.core-CRo4rzDL.js";import"./index-DFpY3RpV.js";import"./chunk-FMBD7UC4-BJX_21R2.js";import"./chunk-ND2GUHAM-DFBKXknR.js";import"./chunk-55IACEB6-DlboNFJr.js";import"./chunk-2J33WTMH-9vQ1xqy3.js";import"./purify.es-CLGrRn1w.js";import"./step-CWvwoXpJ.js";var b={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{b as diagram};
|