@tuongaz/seeflow 0.1.71 → 0.1.75
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/arc-DtxbFlka.js +1 -0
- package/dist/web/assets/architectureDiagram-3BPJPVTR-BE013nUd.js +36 -0
- package/dist/web/assets/band-BEGRCkg7.js +1 -0
- package/dist/web/assets/blockDiagram-GPEHLZMM-Dp6be3Ms.js +132 -0
- package/dist/web/assets/c4Diagram-AAUBKEIU-DaXH8gyS.js +10 -0
- package/dist/web/assets/channel-BXI2S8ym.js +1 -0
- package/dist/web/assets/chart-CTfwAwIW.js +73 -0
- package/dist/web/assets/chunk-2J33WTMH-DBOxtwXe.js +1 -0
- package/dist/web/assets/chunk-4BX2VUAB-BkqIaZzX.js +1 -0
- package/dist/web/assets/chunk-55IACEB6-Bf2GGBjw.js +1 -0
- package/dist/web/assets/chunk-727SXJPM-DT7H_Ach.js +206 -0
- package/dist/web/assets/chunk-AQP2D5EJ-VrM-sHKP.js +231 -0
- package/dist/web/assets/chunk-FMBD7UC4-Cl6A8JUa.js +15 -0
- package/dist/web/assets/chunk-ND2GUHAM-LRV96SE3.js +1 -0
- package/dist/web/assets/chunk-QZHKN3VN-Dj5_UVo9.js +1 -0
- package/dist/web/assets/classDiagram-4FO5ZUOK-Dm1vuLfb.js +1 -0
- package/dist/web/assets/classDiagram-v2-Q7XG4LA2-Dm1vuLfb.js +1 -0
- package/dist/web/assets/{code-block-CagXzbCC.js → code-block-Be3Lawg0.js} +1 -1
- package/dist/web/assets/cose-bilkent-S5V4N54A-4_XIQLKW.js +1 -0
- package/dist/web/assets/cytoscape.esm-BHYC38rz.js +331 -0
- package/dist/web/assets/dagre-BM42HDAG-Csr5BSLw.js +4 -0
- package/dist/web/assets/defaultLocale-CrowFXzY.js +1 -0
- package/dist/web/assets/diagram-2AECGRRQ-D9cawnSD.js +43 -0
- package/dist/web/assets/diagram-5GNKFQAL-Ba9mTl4r.js +10 -0
- package/dist/web/assets/diagram-KO2AKTUF-D_gNaP7M.js +3 -0
- package/dist/web/assets/diagram-LMA3HP47-BwPi3QWV.js +24 -0
- package/dist/web/assets/diagram-OG6HWLK6-C2iDWN55.js +24 -0
- package/dist/web/assets/erDiagram-TEJ5UH35-MBy10Qfr.js +85 -0
- package/dist/web/assets/flowDiagram-I6XJVG4X-CgVoK-B7.js +162 -0
- package/dist/web/assets/ganttDiagram-6RSMTGT7-DZZ43-Du.js +292 -0
- package/dist/web/assets/gitGraphDiagram-PVQCEYII-BwYPxvng.js +106 -0
- package/dist/web/assets/graph-Dqkl27Ch.js +1 -0
- package/dist/web/assets/index-DljfurDC.css +1 -0
- package/dist/web/assets/{index-NG9m5oBj.js → index-DrmDilPO.js} +1738 -1737
- package/dist/web/assets/{index.es-xel3SIo8.js → index.es-DpqYSimZ.js} +1 -1
- package/dist/web/assets/infoDiagram-5YYISTIA-j7zbBRny.js +2 -0
- package/dist/web/assets/init-BFKUnIhM.js +1 -0
- package/dist/web/assets/ishikawaDiagram-YF4QCWOH-DAp6W4Gj.js +70 -0
- package/dist/web/assets/journeyDiagram-JHISSGLW-Dde2JSR2.js +139 -0
- package/dist/web/assets/{jspdf.es.min-CIMMqiVE.js → jspdf.es.min-tC6T-F7r.js} +3 -3
- package/dist/web/assets/kanban-definition-UN3LZRKU-os60zjrz.js +89 -0
- package/dist/web/assets/katex-C5jXJg4s.js +257 -0
- package/dist/web/assets/layout-DO9UvE1P.js +1 -0
- package/dist/web/assets/linear-f3DePitM.js +1 -0
- package/dist/web/assets/markdown-Feo1eGLy.js +1 -0
- package/dist/web/assets/mermaid.core-DoXF1I4r.js +307 -0
- package/dist/web/assets/mindmap-definition-RKZ34NQL-D6K4uyrn.js +96 -0
- package/dist/web/assets/ordinal-BQ3KlzSu.js +1 -0
- package/dist/web/assets/pieDiagram-4H26LBE5-C4UX0DPX.js +30 -0
- package/dist/web/assets/quadrantDiagram-W4KKPZXB-iSE2IF5i.js +7 -0
- package/dist/web/assets/requirementDiagram-4Y6WPE33-D_lHCX3z.js +84 -0
- package/dist/web/assets/sankeyDiagram-5OEKKPKP-B0-DYq8M.js +40 -0
- package/dist/web/assets/sequenceDiagram-3UESZ5HK-BQM0i8wy.js +162 -0
- package/dist/web/assets/stateDiagram-AJRCARHV-DD0djwwD.js +1 -0
- package/dist/web/assets/stateDiagram-v2-BHNVJYJU-C-OF3f9a.js +1 -0
- package/dist/web/assets/step-CWvwoXpJ.js +1 -0
- package/dist/web/assets/time-BczydnL9.js +1 -0
- package/dist/web/assets/timeline-definition-PNZ67QCA-BjCNA-ke.js +120 -0
- package/dist/web/assets/vennDiagram-CIIHVFJN-G2L4S-_F.js +34 -0
- package/dist/web/assets/wardley-L42UT6IY-cmE8YucU.js +173 -0
- package/dist/web/assets/wardleyDiagram-YWT4CUSO-BM_S_KMT.js +78 -0
- package/dist/web/assets/xychartDiagram-2RQKCTM6-B_aQbaAw.js +7 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/cli-manifest.ts +105 -37
- package/src/mcp.ts +8 -4
- package/src/schema-catalog.ts +29 -2
- package/src/schema.ts +91 -26
- package/src/watcher.ts +61 -13
- package/dist/web/assets/chart-CpHY42Vq.js +0 -73
- package/dist/web/assets/index-DJdVTUx5.css +0 -1
- package/dist/web/assets/markdown-DAvRjvM0.js +0 -1
package/src/schema.ts
CHANGED
|
@@ -58,12 +58,30 @@ const NodeVisualBaseShape = {
|
|
|
58
58
|
// every visual works without a label. `icon` is decorative-by-default; the
|
|
59
59
|
// `type:'icon'` variant overrides it to required.
|
|
60
60
|
const NodeSemanticBaseShape = {
|
|
61
|
-
name: z
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
name: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe(
|
|
65
|
+
"Short human-readable label rendered in the node header. Omit on decorative nodes (sticky, type:'text') where the body content IS the label.",
|
|
66
|
+
),
|
|
67
|
+
description: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe(
|
|
71
|
+
'One-sentence summary surfaced in the detail sidebar and tooltips. Set whenever a reader would benefit from more context than the name alone.',
|
|
72
|
+
),
|
|
73
|
+
detail: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe(
|
|
77
|
+
'Long-form markdown shown in the detail sidebar. Supports headings, lists, code, and ```mermaid``` blocks (the canvas renders mermaid inline). Use for runbooks, schemas, sequence diagrams.',
|
|
78
|
+
),
|
|
79
|
+
icon: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe(
|
|
83
|
+
"Decorative header glyph (Lucide icon name in kebab-case, e.g. 'database', 'cloud-upload'). Falls back to a placeholder when unknown. On type:'icon' nodes the icon IS the visual and is required.",
|
|
84
|
+
),
|
|
67
85
|
};
|
|
68
86
|
|
|
69
87
|
// Relative-path safety refine (textual). Mirrors the same rule used for
|
|
@@ -124,10 +142,22 @@ export const StatusReportSchema = z.object({
|
|
|
124
142
|
ts: z.number().int().positive().optional(),
|
|
125
143
|
});
|
|
126
144
|
|
|
127
|
-
export const StateSourceSchema = z
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
145
|
+
export const StateSourceSchema = z
|
|
146
|
+
.discriminatedUnion('kind', [
|
|
147
|
+
z
|
|
148
|
+
.object({ kind: z.literal('request') })
|
|
149
|
+
.describe(
|
|
150
|
+
'Poll-based state: `statusAction` samples an endpoint on an interval (REST GET, healthcheck, DB query). Use for services you can probe.',
|
|
151
|
+
),
|
|
152
|
+
z
|
|
153
|
+
.object({ kind: z.literal('event') })
|
|
154
|
+
.describe(
|
|
155
|
+
'Push-based state: `statusAction` subscribes to a stream (SSE, webhook, queue topic). Use for message buses, async pipelines, anything that announces state changes.',
|
|
156
|
+
),
|
|
157
|
+
])
|
|
158
|
+
.describe(
|
|
159
|
+
"Declares how this node's live state is sourced. Pair with `statusAction` so observers can tell at a glance whether the node's status is polled or pushed.",
|
|
160
|
+
);
|
|
131
161
|
|
|
132
162
|
// Capabilities — any subset of these makes a node Playable / Stateful. All
|
|
133
163
|
// optional, valid on every node type. A node is Playable iff `playAction` is
|
|
@@ -135,16 +165,27 @@ export const StateSourceSchema = z.discriminatedUnion('kind', [
|
|
|
135
165
|
// informational metadata that pairs with statusAction. `handlerModule` is
|
|
136
166
|
// reserved for a future skills runtime and is schema-only at v1.
|
|
137
167
|
const NodeCapabilitiesShape = {
|
|
138
|
-
playAction: PlayActionSchema.optional()
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
168
|
+
playAction: PlayActionSchema.optional().describe(
|
|
169
|
+
'One-shot script the user invokes by clicking the node (a "Play" affordance). Studio spawns `<interpreter> [...args] <scriptPath>` from the project root with the optional `input` JSON-serialized to stdin. Use for HTTP calls, CLI invocations, anything triggered on demand.',
|
|
170
|
+
),
|
|
171
|
+
statusAction: StatusActionSchema.optional().describe(
|
|
172
|
+
"Long-running status probe. Same spawn shape as `playAction` but the script ticks continuously and writes one JSON `StatusReport` per line to stdout; the canvas renders the most recent state badge. Pair with `stateSource` so observers know whether it's poll- or push-based.",
|
|
173
|
+
),
|
|
174
|
+
stateSource: StateSourceSchema.optional().describe(
|
|
175
|
+
'Set this on any node that has a `statusAction`. Choose `request` for poll-based sources, `event` for push-based sources. Omit on decorative nodes (sticky, label-only text) and on action nodes whose only behavior is `playAction`.',
|
|
176
|
+
),
|
|
177
|
+
handlerModule: z
|
|
178
|
+
.string()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe(
|
|
181
|
+
'Reserved for the v2 skills runtime. Schema-only at v1 — set by tooling; leave undefined when authoring flows by hand.',
|
|
182
|
+
),
|
|
142
183
|
};
|
|
143
184
|
|
|
144
|
-
//
|
|
145
|
-
// GeometricNodeData. `image`, `html`, `icon` carry per-type
|
|
146
|
-
// The renderer picks the SVG / chrome by `type`; the schema treats
|
|
147
|
-
// (apart from the per-type fields below) as identical.
|
|
185
|
+
// 13 flat node types. The first 9 are geometric/illustrative and share
|
|
186
|
+
// GeometricNodeData. `image`, `html`, `icon`, `component` carry per-type
|
|
187
|
+
// fields. The renderer picks the SVG / chrome by `type`; the schema treats
|
|
188
|
+
// them (apart from the per-type fields below) as identical.
|
|
148
189
|
export const GEOMETRIC_NODE_TYPES = [
|
|
149
190
|
'rectangle',
|
|
150
191
|
'ellipse',
|
|
@@ -454,10 +495,19 @@ const FlowImageNodeData = z
|
|
|
454
495
|
.object({
|
|
455
496
|
...NodeSemanticBaseShape,
|
|
456
497
|
...NodeCapabilitiesShape,
|
|
457
|
-
path: z
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
498
|
+
path: z
|
|
499
|
+
.string()
|
|
500
|
+
.min(1)
|
|
501
|
+
.refine(isCleanRelativePath, {
|
|
502
|
+
message: 'path must be a relative path under the project root (no absolute / traversal)',
|
|
503
|
+
})
|
|
504
|
+
.describe(
|
|
505
|
+
"Project-root-relative path to the image file. MUST start with 'nodes/<id>/' so the delete_node cascade owns cleanup. Supported formats: PNG, JPEG, SVG, GIF, WebP.",
|
|
506
|
+
),
|
|
507
|
+
alt: z
|
|
508
|
+
.string()
|
|
509
|
+
.optional()
|
|
510
|
+
.describe('Accessibility alt text. Set on every non-decorative image.'),
|
|
461
511
|
})
|
|
462
512
|
.strict();
|
|
463
513
|
|
|
@@ -465,7 +515,12 @@ const FlowHtmlNodeData = z
|
|
|
465
515
|
.object({
|
|
466
516
|
...NodeSemanticBaseShape,
|
|
467
517
|
...NodeCapabilitiesShape,
|
|
468
|
-
html: z
|
|
518
|
+
html: z
|
|
519
|
+
.string()
|
|
520
|
+
.optional()
|
|
521
|
+
.describe(
|
|
522
|
+
"Inline HTML rendered inside the node. Studio externalizes this to `<project>/nodes/<id>/view.html` on write and inlines it back on read, so authors always see the actual string. Sanitized before injection. Escape-hatch for content the geometric/icon/component visuals don't cover.",
|
|
523
|
+
),
|
|
469
524
|
})
|
|
470
525
|
.strict();
|
|
471
526
|
|
|
@@ -473,8 +528,13 @@ const FlowIconNodeData = z
|
|
|
473
528
|
.object({
|
|
474
529
|
...NodeSemanticBaseShape,
|
|
475
530
|
...NodeCapabilitiesShape,
|
|
476
|
-
icon: z
|
|
477
|
-
|
|
531
|
+
icon: z
|
|
532
|
+
.string()
|
|
533
|
+
.min(1)
|
|
534
|
+
.describe(
|
|
535
|
+
"Required Lucide icon name (kebab-case, e.g. 'cloud-upload', 'database'). On type:'icon' nodes the icon IS the visual — overrides the optional decorative `icon` from the semantic base.",
|
|
536
|
+
),
|
|
537
|
+
alt: z.string().optional().describe('Accessibility alt text for the icon glyph.'),
|
|
478
538
|
})
|
|
479
539
|
.strict();
|
|
480
540
|
|
|
@@ -486,7 +546,12 @@ const FlowComponentNodeData = z
|
|
|
486
546
|
.object({
|
|
487
547
|
...NodeSemanticBaseShape,
|
|
488
548
|
...NodeCapabilitiesShape,
|
|
489
|
-
autoSize: z
|
|
549
|
+
autoSize: z
|
|
550
|
+
.boolean()
|
|
551
|
+
.optional()
|
|
552
|
+
.describe(
|
|
553
|
+
'When true the renderer measures its content and React Flow sizes the wrapper around it. Default (undefined / false) uses the persisted width/height path.',
|
|
554
|
+
),
|
|
490
555
|
})
|
|
491
556
|
.strict();
|
|
492
557
|
|
package/src/watcher.ts
CHANGED
|
@@ -20,6 +20,15 @@ import {
|
|
|
20
20
|
|
|
21
21
|
const DEFAULT_DEBOUNCE_MS = 100;
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Polling backup interval. fs.watch on macOS occasionally drops events
|
|
25
|
+
* (notably right after a fresh watcher is registered), so a low-frequency
|
|
26
|
+
* stat poll over every watched handle guarantees external writes still
|
|
27
|
+
* trigger reparse+broadcast. Own-write echoes are still suppressed by
|
|
28
|
+
* the writtenHashes ring, so this doesn't double-fire on server writes.
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_POLL_INTERVAL_MS = 500;
|
|
31
|
+
|
|
23
32
|
/** Max recent self-write hashes retained per flow for own-echo suppression. */
|
|
24
33
|
const WRITTEN_HASH_RING_SIZE = 4;
|
|
25
34
|
|
|
@@ -52,6 +61,12 @@ export interface WatcherDeps {
|
|
|
52
61
|
events: EventBus;
|
|
53
62
|
/** Override for tests. */
|
|
54
63
|
debounceMs?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Polling backup interval for missed fs.watch events. Defaults to 500ms.
|
|
66
|
+
* Pass `0` to disable (tests that rely on deterministic fs.watch-only
|
|
67
|
+
* behaviour).
|
|
68
|
+
*/
|
|
69
|
+
pollIntervalMs?: number;
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
export interface FlowWatcher {
|
|
@@ -276,6 +291,7 @@ const closeFileWatchers = (handle: WatchHandle): void => {
|
|
|
276
291
|
export function createWatcher(deps: WatcherDeps): FlowWatcher {
|
|
277
292
|
const { registry, events } = deps;
|
|
278
293
|
const debounceMs = deps.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
294
|
+
const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
279
295
|
|
|
280
296
|
const handles = new Map<string, WatchHandle>();
|
|
281
297
|
const snapshots = new Map<string, FlowSnapshot>();
|
|
@@ -455,6 +471,49 @@ export function createWatcher(deps: WatcherDeps): FlowWatcher {
|
|
|
455
471
|
});
|
|
456
472
|
};
|
|
457
473
|
|
|
474
|
+
/**
|
|
475
|
+
* Common path for "something on disk may have changed" — used by both
|
|
476
|
+
* fs.watch callbacks and the polling backup. Debounces, drops own-write
|
|
477
|
+
* echoes, then reparses + broadcasts. Idempotent under repeated calls
|
|
478
|
+
* within the debounce window.
|
|
479
|
+
*/
|
|
480
|
+
const scheduleChange = (flowId: string): void => {
|
|
481
|
+
const handle = handles.get(flowId);
|
|
482
|
+
if (!handle) return;
|
|
483
|
+
if (handle.debounceTimer) clearTimeout(handle.debounceTimer);
|
|
484
|
+
handle.debounceTimer = setTimeout(() => {
|
|
485
|
+
handle.debounceTimer = null;
|
|
486
|
+
// Own-write dedupe: if the on-disk bytes match what the server just
|
|
487
|
+
// wrote (recent hash in the ring), this is our own echo — drop it.
|
|
488
|
+
// notifyWritten already broadcast and seeded the snapshot.
|
|
489
|
+
const combined = readCombinedFromDisk(handle.filePath);
|
|
490
|
+
if (combined !== null && isOwnWriteEcho(flowId, sha256Hex(combined))) return;
|
|
491
|
+
const snap = reparse(flowId);
|
|
492
|
+
if (snap) broadcastReload(flowId, snap);
|
|
493
|
+
}, debounceMs);
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// Single shared interval that stat-polls every watched handle. Cheap (one
|
|
497
|
+
// statSync per watched flow per tick) and bounded — flows that registry
|
|
498
|
+
// doesn't know about aren't watched, so the loop scales with active
|
|
499
|
+
// projects, not historic ones. Backs up macOS fs.watch's occasional
|
|
500
|
+
// dropped events; the debounce + own-write dedupe in scheduleChange
|
|
501
|
+
// keeps it from double-firing alongside a fs.watch hit.
|
|
502
|
+
const pollTimer: ReturnType<typeof setInterval> | null =
|
|
503
|
+
pollIntervalMs > 0
|
|
504
|
+
? setInterval(() => {
|
|
505
|
+
for (const [flowId, handle] of handles) {
|
|
506
|
+
const seen = lastSeenMtimes.get(flowId);
|
|
507
|
+
if (seen === undefined) continue;
|
|
508
|
+
const now = combinedMtimeMs(handle.filePath);
|
|
509
|
+
if (now > seen) scheduleChange(flowId);
|
|
510
|
+
}
|
|
511
|
+
}, pollIntervalMs)
|
|
512
|
+
: null;
|
|
513
|
+
// Don't keep the event loop alive on this timer alone — the studio process
|
|
514
|
+
// should exit cleanly when nothing else is pending.
|
|
515
|
+
pollTimer?.unref?.();
|
|
516
|
+
|
|
458
517
|
const startWatch = (flowId: string) => {
|
|
459
518
|
const existing = handles.get(flowId);
|
|
460
519
|
if (existing) {
|
|
@@ -484,19 +543,7 @@ export function createWatcher(deps: WatcherDeps): FlowWatcher {
|
|
|
484
543
|
// React to flow.json, style.json, or rename-on-save events
|
|
485
544
|
// (some platforms emit those with no filename).
|
|
486
545
|
if (changed && changed !== base && changed !== 'style.json') return;
|
|
487
|
-
|
|
488
|
-
if (!handle) return;
|
|
489
|
-
if (handle.debounceTimer) clearTimeout(handle.debounceTimer);
|
|
490
|
-
handle.debounceTimer = setTimeout(() => {
|
|
491
|
-
handle.debounceTimer = null;
|
|
492
|
-
// Own-write dedupe: if the on-disk bytes match what the server just
|
|
493
|
-
// wrote (recent hash in the ring), this is our own echo — drop it.
|
|
494
|
-
// notifyWritten already broadcast and seeded the snapshot.
|
|
495
|
-
const combined = readCombinedFromDisk(filePath);
|
|
496
|
-
if (combined !== null && isOwnWriteEcho(flowId, sha256Hex(combined))) return;
|
|
497
|
-
const snap = reparse(flowId);
|
|
498
|
-
if (snap) broadcastReload(flowId, snap);
|
|
499
|
-
}, debounceMs);
|
|
546
|
+
scheduleChange(flowId);
|
|
500
547
|
});
|
|
501
548
|
} catch (err) {
|
|
502
549
|
console.error(`[watcher] failed to watch ${dir} for flow ${flowId}:`, err);
|
|
@@ -551,6 +598,7 @@ export function createWatcher(deps: WatcherDeps): FlowWatcher {
|
|
|
551
598
|
for (const entry of registry.list()) startWatch(entry.id);
|
|
552
599
|
},
|
|
553
600
|
closeAll() {
|
|
601
|
+
if (pollTimer) clearInterval(pollTimer);
|
|
554
602
|
for (const [, h] of handles) {
|
|
555
603
|
h.fsWatcher.close();
|
|
556
604
|
if (h.debounceTimer) clearTimeout(h.debounceTimer);
|