@tuongaz/seeflow 0.1.61 → 0.1.64

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.
Files changed (44) hide show
  1. package/README.md +3 -3
  2. package/dist/web/assets/{index-BXYHeBKM.js → index-BAEA18IR.js} +354 -354
  3. package/dist/web/assets/{index.es-BzG6d4Ro.js → index.es-DZEdTXNJ.js} +1 -1
  4. package/dist/web/assets/{jspdf.es.min-CcOxqEhi.js → jspdf.es.min-DT1Li8zz.js} +3 -3
  5. package/dist/web/index.html +1 -1
  6. package/examples/ecommerce-platform/{.seeflow/flow.json → flow.json} +3 -25
  7. package/examples/ecommerce-platform/{.seeflow/scripts → scripts}/play.ts +1 -1
  8. package/examples/order-pipeline/{.seeflow/flow.json → flow.json} +1 -10
  9. package/package.json +1 -1
  10. package/src/api.ts +65 -55
  11. package/src/cli-helpers.ts +6 -5
  12. package/src/cli-manifest.ts +103 -15
  13. package/src/cli.ts +85 -13
  14. package/src/diagram.ts +0 -1
  15. package/src/file-ref.ts +16 -15
  16. package/src/mcp.ts +58 -16
  17. package/src/merge.ts +0 -1
  18. package/src/node-files.ts +5 -5
  19. package/src/operations.ts +40 -101
  20. package/src/paths.ts +16 -0
  21. package/src/proxy.ts +13 -13
  22. package/src/schema-catalog.ts +3 -9
  23. package/src/schema.ts +36 -96
  24. package/src/server.ts +0 -4
  25. package/src/short-id.ts +24 -0
  26. package/src/status-runner.ts +3 -3
  27. package/src/watcher.ts +15 -27
  28. package/src/sdk-template.ts +0 -37
  29. package/src/sdk-writer.ts +0 -37
  30. /package/examples/ecommerce-platform/{.seeflow/nodes → nodes}/node-3zFtHg6ENc/detail.md +0 -0
  31. /package/examples/ecommerce-platform/{.seeflow/nodes → nodes}/node-5F424NWbEu/detail.md +0 -0
  32. /package/examples/ecommerce-platform/{.seeflow/nodes → nodes}/node-CbwYqb7NfB/detail.md +0 -0
  33. /package/examples/ecommerce-platform/{.seeflow/nodes → nodes}/node-XwygzfKPZ5/view.html +0 -0
  34. /package/examples/ecommerce-platform/{.seeflow/nodes → nodes}/node-fkptXw7uvs/detail.md +0 -0
  35. /package/examples/ecommerce-platform/{.seeflow/nodes → nodes}/node-kwBY8YPmYM/detail.md +0 -0
  36. /package/examples/ecommerce-platform/{.seeflow/nodes → nodes}/node-mPqan8rFYN/detail.md +0 -0
  37. /package/examples/ecommerce-platform/{.seeflow/nodes → nodes}/node-yKrg9DV5fJ/detail.md +0 -0
  38. /package/examples/ecommerce-platform/{.seeflow/style.json → style.json} +0 -0
  39. /package/examples/order-pipeline/{.seeflow/nodes → nodes}/node-GXTKUcE3ye/detail.md +0 -0
  40. /package/examples/order-pipeline/{.seeflow/nodes → nodes}/node-XKIyds0TDg/detail.md +0 -0
  41. /package/examples/order-pipeline/{.seeflow/nodes → nodes}/node-YOYiHJpY0i/detail.md +0 -0
  42. /package/examples/order-pipeline/{.seeflow/nodes → nodes}/node-zUIH7WFnhK/detail.md +0 -0
  43. /package/examples/order-pipeline/{.seeflow/scripts → scripts}/play.ts +0 -0
  44. /package/examples/order-pipeline/{.seeflow/style.json → style.json} +0 -0
package/src/schema.ts CHANGED
@@ -8,8 +8,8 @@ const PositionSchema = z.object({
8
8
  // US-008: HttpAction was the original shape for both playAction and resetAction
9
9
  // in pre-script-action releases. After US-001 cut playAction to script-only and
10
10
  // US-008 cut resetAction the same way, no schema in this file uses HttpAction
11
- // anymore. Connectors keep the `http` *kind literal* for visual semantics, but
12
- // that's an independent enum — search before re-adding any HttpActionSchema.
11
+ // anymore. `HttpMethodSchema` is still used for the optional `method` field on
12
+ // connectors (documentation metadata).
13
13
  const HttpMethodSchema = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
14
14
 
15
15
  // Curated palette tokens. Stored on disk as readable names; the frontend maps
@@ -62,10 +62,12 @@ const isCleanRelativePath = (s: string): boolean => {
62
62
  };
63
63
 
64
64
  // Script-based action: the studio spawns `<interpreter> [...args] <scriptPath>`
65
- // from the project's repoPath. `scriptPath` is a relative path under
66
- // `<project>/.seeflow/`; `args` (optional) prepend to the interpreter; `input`
67
- // (optional) gets JSON-serialized and written to the child's stdin then closed;
68
- // `timeoutMs` caps execution (default applied at the spawn layer, not here).
65
+ // from the project's repoPath. `scriptPath` is a relative path under the
66
+ // project root (typically `nodes/<id>/scripts/<name>` for play/status, or any
67
+ // project-root-relative path for reset); `args` (optional) prepend to the
68
+ // interpreter; `input` (optional) gets JSON-serialized and written to the
69
+ // child's stdin then closed; `timeoutMs` caps execution (default applied at
70
+ // the spawn layer, not here).
69
71
  const ScriptActionSchema = z.object({
70
72
  kind: z.literal('script'),
71
73
  interpreter: z.string().min(1),
@@ -117,7 +119,6 @@ export const StateSourceSchema = z.discriminatedUnion('kind', [
117
119
 
118
120
  const NodeDataBaseSchema = z.object({
119
121
  name: z.string().min(1),
120
- kind: z.string().min(1),
121
122
  stateSource: StateSourceSchema,
122
123
  // Reserved for v2: a module path resolved by future skills runtime.
123
124
  // Schema-only at v1 — never read at runtime.
@@ -188,14 +189,14 @@ const ShapeNodeSchema = z.object({
188
189
  data: ShapeNodeDataSchema,
189
190
  });
190
191
 
191
- // Decorative image node — references a file under `<project>/.seeflow/` by
192
+ // Decorative image node — references a file under the project root by
192
193
  // relative path (US-004 hard-cut from base64 data URLs to path-backed files).
193
- // `path` is a relative path under `<project>/.seeflow/` for imageNode uploads: rooted
194
- // at `.seeflow/`, no leading slash, no `..` segments. The renderer fetches via
194
+ // `path` is a relative path under the project root for imageNode uploads:
195
+ // no leading slash, no `..` segments. The renderer fetches via
195
196
  // `GET /api/projects/:id/files/:path`.
196
197
  const ImageNodeDataSchema = z.object({
197
198
  path: z.string().min(1).refine(isCleanRelativePath, {
198
- message: 'path must be a relative path under .seeflow/ (no absolute / traversal)',
199
+ message: 'path must be a relative path under the project root (no absolute / traversal)',
199
200
  }),
200
201
  alt: z.string().optional(),
201
202
  ...NodeVisualBaseShape,
@@ -212,8 +213,8 @@ const ImageNodeSchema = z.object({
212
213
  // US-011 (illustrative-shapes-htmlnode): htmlNode is the escape-hatch node type
213
214
  // for content the curated nodes don't cover — carries author-written HTML
214
215
  // inline via `data.html`. The studio externalizes the content to
215
- // `<project>/.seeflow/nodes/<id>/view.html` and stores a `file://` ref in
216
- // flow.json; the resolver inlines the content back on read so consumers see
216
+ // `<project>/nodes/<id>/view.html` and stores a `file://` ref in flow.json;
217
+ // the resolver inlines the content back on read so consumers see
217
218
  // the resolved HTML string. The renderer sanitizes before injection
218
219
  // (US-013/US-014). Spreads NodeVisualBaseShape so authors can theme the
219
220
  // wrapper (border / background / radius / font) with the same fields
@@ -273,17 +274,13 @@ const NodeSchema = z.discriminatedUnion('type', [
273
274
  HtmlNodeSchema,
274
275
  ]);
275
276
 
276
- // Connector is the semantic edge between two nodes describes HOW they are
277
- // connected, not just THAT they are. Discriminated on `kind`:
278
- // http — service-to-service HTTP call (method + url echo of the playAction)
279
- // event — pub/sub event (eventName)
280
- // queue — message-queue handoff (queueName)
281
- // default user-drawn, no semantic payload (UI annotation only)
282
- // The frontend derives a React Flow Edge from each connector at render time
283
- // (id/source/target are reused; `label` becomes the edge label; visual style
284
- // is picked from `kind`, but per-connector `style`/`color` overrides it). v1
285
- // has no separate `edges[]` array — connectors are the sole source of truth
286
- // for inter-node connections.
277
+ // Connector is the edge between two nodes. The frontend derives a React Flow
278
+ // Edge from each connector at render time (id/source/target are reused;
279
+ // `label` becomes the edge label; visual style comes from optional
280
+ // `style`/`color` fields). v1 has no separate `edges[]` array — connectors
281
+ // are the sole source of truth for inter-node connections. Optional
282
+ // metadata fields (`method`/`url`/`eventName`/`queueName`) may be present
283
+ // for documentation purposes; the renderer does not branch on them.
287
284
  const ConnectorStyleSchema = z.enum(['solid', 'dashed', 'dotted']);
288
285
  const ConnectorDirectionSchema = z.enum(['forward', 'backward', 'both', 'none']);
289
286
  // Path geometry — orthogonal to `style` (which means the dash pattern). Absent
@@ -291,7 +288,7 @@ const ConnectorDirectionSchema = z.enum(['forward', 'backward', 'both', 'none'])
291
288
  // (right-angle / zigzag) path. (US-017)
292
289
  const ConnectorPathSchema = z.enum(['curve', 'step']);
293
290
 
294
- // Visual fields shared by every connector kind. All optional — existing
291
+ // Visual fields shared by every connector. All optional — existing
295
292
  // demo files predate them and must continue to parse. `direction` defaults
296
293
  // to 'forward' when absent (the historical behavior).
297
294
  const ConnectorVisualBaseShape = {
@@ -305,7 +302,7 @@ const ConnectorVisualBaseShape = {
305
302
  fontSize: z.number().positive().optional(),
306
303
  };
307
304
 
308
- // Handle ids — every node kind in this codebase uses the same four-handle
305
+ // Handle ids — every node type in this codebase uses the same four-handle
309
306
  // layout: target-only on top + left, source-only on right + bottom (US-013).
310
307
  // `sourceHandle` MUST be a source-side id and `targetHandle` MUST be a
311
308
  // target-side id; sending the wrong role leaves a stranded endpoint at render
@@ -350,37 +347,14 @@ const ConnectorBaseShape = {
350
347
  ...ConnectorVisualBaseShape,
351
348
  };
352
349
 
353
- const HttpConnectorSchema = z.object({
350
+ const ConnectorSchema = z.object({
354
351
  ...ConnectorBaseShape,
355
- kind: z.literal('http'),
356
352
  method: HttpMethodSchema.optional(),
357
353
  url: z.string().min(1).optional(),
354
+ eventName: z.string().min(1).optional(),
355
+ queueName: z.string().min(1).optional(),
358
356
  });
359
357
 
360
- const EventConnectorSchema = z.object({
361
- ...ConnectorBaseShape,
362
- kind: z.literal('event'),
363
- eventName: z.string().min(1),
364
- });
365
-
366
- const QueueConnectorSchema = z.object({
367
- ...ConnectorBaseShape,
368
- kind: z.literal('queue'),
369
- queueName: z.string().min(1),
370
- });
371
-
372
- const DefaultConnectorSchema = z.object({
373
- ...ConnectorBaseShape,
374
- kind: z.literal('default'),
375
- });
376
-
377
- const ConnectorSchema = z.discriminatedUnion('kind', [
378
- HttpConnectorSchema,
379
- EventConnectorSchema,
380
- QueueConnectorSchema,
381
- DefaultConnectorSchema,
382
- ]);
383
-
384
358
  export const ResolvedFlowSchema = z
385
359
  .object({
386
360
  version: z.literal(2),
@@ -439,10 +413,6 @@ export type HtmlNodeData = z.infer<typeof HtmlNodeDataSchema>;
439
413
  export type ShapeKind = z.infer<typeof ShapeKindSchema>;
440
414
  export type ColorToken = z.infer<typeof ColorTokenSchema>;
441
415
  export type Connector = z.infer<typeof ConnectorSchema>;
442
- export type HttpConnector = z.infer<typeof HttpConnectorSchema>;
443
- export type EventConnector = z.infer<typeof EventConnectorSchema>;
444
- export type QueueConnector = z.infer<typeof QueueConnectorSchema>;
445
- export type DefaultConnector = z.infer<typeof DefaultConnectorSchema>;
446
416
  export type ConnectorStyle = z.infer<typeof ConnectorStyleSchema>;
447
417
  export type ConnectorDirection = z.infer<typeof ConnectorDirectionSchema>;
448
418
  export type ConnectorPath = z.infer<typeof ConnectorPathSchema>;
@@ -456,12 +426,11 @@ export type StateSource = z.infer<typeof StateSourceSchema>;
456
426
 
457
427
  // =============================================================================
458
428
  // Flow schema — pure semantic data, every visual/layout field stripped.
459
- // What lives on disk in <project>/.seeflow/flow.json after the split.
429
+ // What lives on disk in <project>/flow.json after the split.
460
430
  // =============================================================================
461
431
 
462
432
  const FlowNodeDataBaseShape = {
463
433
  name: z.string().min(1),
464
- kind: z.string().min(1),
465
434
  stateSource: StateSourceSchema,
466
435
  handlerModule: z.string().optional(),
467
436
  icon: z.string().optional(),
@@ -495,7 +464,7 @@ const FlowShapeNodeDataSchema = z
495
464
  const FlowImageNodeDataSchema = z
496
465
  .object({
497
466
  path: z.string().min(1).refine(isCleanRelativePath, {
498
- message: 'path must be a relative path under .seeflow/ (no absolute / traversal)',
467
+ message: 'path must be a relative path under the project root (no absolute / traversal)',
499
468
  }),
500
469
  alt: z.string().optional(),
501
470
  ...NodeDescriptionBaseShape,
@@ -588,45 +557,16 @@ const FlowConnectorBaseShape = {
588
557
  label: z.string().optional(),
589
558
  };
590
559
 
591
- export const FlowHttpConnectorSchema = z
560
+ export const FlowConnectorSchema = z
592
561
  .object({
593
562
  ...FlowConnectorBaseShape,
594
- kind: z.literal('http'),
595
563
  method: HttpMethodSchema.optional(),
596
564
  url: z.string().min(1).optional(),
565
+ eventName: z.string().min(1).optional(),
566
+ queueName: z.string().min(1).optional(),
597
567
  })
598
568
  .strict();
599
569
 
600
- export const FlowEventConnectorSchema = z
601
- .object({
602
- ...FlowConnectorBaseShape,
603
- kind: z.literal('event'),
604
- eventName: z.string().min(1),
605
- })
606
- .strict();
607
-
608
- export const FlowQueueConnectorSchema = z
609
- .object({
610
- ...FlowConnectorBaseShape,
611
- kind: z.literal('queue'),
612
- queueName: z.string().min(1),
613
- })
614
- .strict();
615
-
616
- export const FlowDefaultConnectorSchema = z
617
- .object({
618
- ...FlowConnectorBaseShape,
619
- kind: z.literal('default'),
620
- })
621
- .strict();
622
-
623
- const FlowConnectorSchema = z.discriminatedUnion('kind', [
624
- FlowHttpConnectorSchema,
625
- FlowEventConnectorSchema,
626
- FlowQueueConnectorSchema,
627
- FlowDefaultConnectorSchema,
628
- ]);
629
-
630
570
  export const FlowSchema = z
631
571
  .object({
632
572
  version: z.literal(2),
@@ -663,10 +603,10 @@ export type FlowConnector = z.infer<typeof FlowConnectorSchema>;
663
603
 
664
604
  // Envelope-only flow shape for the `seeflow schema flow` surface. The full
665
605
  // FlowSchema validates the whole graph; this companion schema describes the
666
- // top-level shape without inlining every node + connector variant, so the
667
- // runtime-introspectable JSON Schema stays compact. Authors drill into
668
- // `seeflow schema node` / `seeflow schema connector` for the per-variant
669
- // shapes. Not used for validation — only the catalog reads it.
606
+ // top-level shape without inlining every node variant or the connector
607
+ // shape, so the runtime-introspectable JSON Schema stays compact. Authors
608
+ // drill into `seeflow schema node` / `seeflow schema connector` for the
609
+ // detailed shapes. Not used for validation — only the catalog reads it.
670
610
  export const FlowEnvelopeSchema = z
671
611
  .object({
672
612
  version: z.literal(2),
@@ -680,7 +620,7 @@ export const FlowEnvelopeSchema = z
680
620
 
681
621
  // =============================================================================
682
622
  // Style schema — keyed map of presentation overrides, side-table by id.
683
- // What lives on disk in <project>/.seeflow/style.json (optional file).
623
+ // What lives on disk in <project>/style.json (optional file).
684
624
  // =============================================================================
685
625
 
686
626
  const NodeStyleSchema = z
package/src/server.ts CHANGED
@@ -54,8 +54,6 @@ export interface CreateAppOptions {
54
54
  /** Inject a ProxyFacade — tests use this to short-circuit runPlay /
55
55
  * runReset / stopAllPlays and assert call order. */
56
56
  proxy?: ProxyFacade;
57
- /** Override base directory for new projects. Defaults to ~/.seeflow. Tests inject a tmp dir. */
58
- projectBaseDir?: string;
59
57
  }
60
58
 
61
59
  const DEFAULT_VITE_DEV_URL = 'http://localhost:5173';
@@ -139,7 +137,6 @@ export function createApp(options: CreateAppOptions = {}): Hono {
139
137
  statusRunner,
140
138
  processSpawner: options.processSpawner,
141
139
  proxy: options.proxy,
142
- projectBaseDir: options.projectBaseDir,
143
140
  }),
144
141
  );
145
142
 
@@ -158,7 +155,6 @@ export function createApp(options: CreateAppOptions = {}): Hono {
158
155
  const mcpServer = createMcpServer({
159
156
  registry,
160
157
  watcher,
161
- projectBaseDir: options.projectBaseDir,
162
158
  });
163
159
  await mcpServer.connect(transport);
164
160
  try {
package/src/short-id.ts CHANGED
@@ -22,3 +22,27 @@ export function shortId(len = 10): string {
22
22
  }
23
23
  return out;
24
24
  }
25
+
26
+ // Public id-type vocabulary shared by the CLI (`seeflow ids`), the REST API
27
+ // (`GET /api/ids/:type/:count`), and the MCP tool (`seeflow_ids`). Internal
28
+ // prefix `conn-` stays canonical (operations.ts mints connectors as
29
+ // `conn-…`); the friendlier word `connector` is what callers pass.
30
+ export const ID_TYPES = ['node', 'connector'] as const;
31
+ export type IdType = (typeof ID_TYPES)[number];
32
+
33
+ export const ID_PREFIX_BY_TYPE: Record<IdType, string> = {
34
+ node: 'node-',
35
+ connector: 'conn-',
36
+ };
37
+
38
+ export const MAX_ID_COUNT = 100;
39
+
40
+ export const isIdType = (v: unknown): v is IdType =>
41
+ typeof v === 'string' && (ID_TYPES as readonly string[]).includes(v);
42
+
43
+ export function generateIds(type: IdType, count: number): string[] {
44
+ const prefix = ID_PREFIX_BY_TYPE[type];
45
+ const out: string[] = new Array(count);
46
+ for (let i = 0; i < count; i++) out[i] = `${prefix}${shortId()}`;
47
+ return out;
48
+ }
@@ -16,8 +16,8 @@
16
16
  * report. A solicited kill (restart / stop / maxLifetimeMs) is silent on exit.
17
17
  *
18
18
  * Defense-in-depth on scriptPath mirrors proxy.ts:`resolveScript` — realpath
19
- * the resolved file against `<repoPath>/.seeflow/nodes/<nodeId>/` so a
20
- * symlink-escape can't spawn arbitrary scripts outside the node folder.
19
+ * the resolved file against `<repoPath>/nodes/<nodeId>/` so a symlink-escape
20
+ * can't spawn arbitrary scripts outside the node folder.
21
21
  */
22
22
 
23
23
  import { existsSync, realpathSync } from 'node:fs';
@@ -63,7 +63,7 @@ interface TrackedHandle {
63
63
  type ResolvedScript = { ok: true; absPath: string } | { ok: false };
64
64
 
65
65
  function resolveScript(repoPath: string, nodeId: string, scriptPath: string): ResolvedScript {
66
- const nodeRoot = join(repoPath, '.seeflow', 'nodes', nodeId);
66
+ const nodeRoot = join(repoPath, 'nodes', nodeId);
67
67
  let realRoot: string;
68
68
  try {
69
69
  realRoot = realpathSync(nodeRoot);
package/src/watcher.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { type FSWatcher, existsSync, readFileSync, statSync, watch } from 'node:fs';
3
3
  import { basename, dirname, isAbsolute, join } from 'node:path';
4
+ // `file://` refs in flow.json resolve against the project root (the dir that
5
+ // contains flow.json). The studio is path-agnostic — whatever path the caller
6
+ // supplied is the project root.
7
+ const projectRootForFlow = (flowPath: string): string => dirname(flowPath);
4
8
  import type { EventBus } from './events.ts';
5
9
  import { resolveFileRefs } from './file-ref.ts';
6
10
  import { mergeFlowAndStyle } from './merge.ts';
@@ -70,17 +74,17 @@ export interface FlowWatcher {
70
74
  styleContent: string,
71
75
  ): void;
72
76
  /**
73
- * Relative paths (under `<project>/.seeflow/`) currently being watched
74
- * because they're referenced by a node's `data.path` (imageNode). htmlNode
75
- * content rides on the file:// resolver via `data.html`, not this list.
76
- * Sorted for stable assertion order. Used by tests.
77
+ * Relative paths (under the project root) currently being watched because
78
+ * they're referenced by a node's `data.path` (imageNode). htmlNode content
79
+ * rides on the file:// resolver via `data.html`, not this list. Sorted for
80
+ * stable assertion order. Used by tests.
77
81
  */
78
82
  referencedPaths(flowId: string): string[];
79
83
  }
80
84
 
81
85
  interface FileWatchEntry {
82
86
  fsWatcher: FSWatcher;
83
- /** basename → relative path (rooted at `<project>/.seeflow/`) */
87
+ /** basename → relative path (rooted at the project root) */
84
88
  files: Map<string, string>;
85
89
  /** basename → pending debounce timer for the next broadcast */
86
90
  timers: Map<string, ReturnType<typeof setTimeout>>;
@@ -101,22 +105,6 @@ interface WatchHandle {
101
105
  const resolveFilePath = (repoPath: string, flowPath: string): string =>
102
106
  isAbsolute(flowPath) ? flowPath : join(repoPath, flowPath);
103
107
 
104
- // `file://` refs in flow.json resolve against `<project>/.seeflow/` per the
105
- // skill spec — not against the flow file's own directory. Walk up from the
106
- // flow's parent looking for an ancestor named `.seeflow`. Fallback to the
107
- // flow's parent for flows registered outside the `.seeflow/` convention.
108
- const computeSeeflowRoot = (flowPath: string): string => {
109
- const flowDir = dirname(flowPath);
110
- let current = flowDir;
111
- while (true) {
112
- if (basename(current) === '.seeflow') return current;
113
- const parent = dirname(current);
114
- if (parent === current) break;
115
- current = parent;
116
- }
117
- return flowDir;
118
- };
119
-
120
108
  const isCleanRelativePath = (p: string): boolean => {
121
109
  if (!p) return false;
122
110
  // Reject data URLs early — the pre-launch hard-cut (US-004) replaces
@@ -162,7 +150,7 @@ export interface ReadMergedFlowResult {
162
150
  flow: ResolvedFlow | null;
163
151
  valid: boolean;
164
152
  error: string | null;
165
- /** Sorted relative paths under `<seeflowRoot>` resolved via file://. */
153
+ /** Sorted relative paths under the project root resolved via file://. */
166
154
  fileRefs: string[];
167
155
  /** Flow file paths referenced via imageNode.path. */
168
156
  staticRefs: string[];
@@ -181,7 +169,7 @@ export function readMergedFlow(flowPath: string): ReadMergedFlowResult {
181
169
  }
182
170
 
183
171
  const flowDir = dirname(flowPath);
184
- const seeflowRoot = computeSeeflowRoot(flowPath);
172
+ const projectRoot = projectRootForFlow(flowPath);
185
173
  const stylePath = join(flowDir, 'style.json');
186
174
 
187
175
  let rawFlow: unknown;
@@ -194,7 +182,7 @@ export function readMergedFlow(flowPath: string): ReadMergedFlowResult {
194
182
  };
195
183
  }
196
184
 
197
- const { resolved, refs } = resolveFileRefs(rawFlow, seeflowRoot);
185
+ const { resolved, refs } = resolveFileRefs(rawFlow, projectRoot);
198
186
  const staticRefs = collectReferencedPaths(rawFlow);
199
187
 
200
188
  const flowParse = FlowSchema.safeParse(resolved);
@@ -306,12 +294,12 @@ export function createWatcher(deps: WatcherDeps): FlowWatcher {
306
294
  const reconcileFileWatchers = (
307
295
  flowId: string,
308
296
  handle: WatchHandle,
309
- seeflowRoot: string,
297
+ projectRoot: string,
310
298
  refs: string[],
311
299
  ): void => {
312
300
  const desired = new Map<string, Map<string, string>>();
313
301
  for (const relPath of refs) {
314
- const abs = join(seeflowRoot, relPath);
302
+ const abs = join(projectRoot, relPath);
315
303
  const dir = dirname(abs);
316
304
  const base = basename(abs);
317
305
  let dirMap = desired.get(dir);
@@ -417,7 +405,7 @@ export function createWatcher(deps: WatcherDeps): FlowWatcher {
417
405
  const handle = handles.get(flowId);
418
406
  if (handle) {
419
407
  const allRefs = [...result.fileRefs, ...result.staticRefs];
420
- reconcileFileWatchers(flowId, handle, computeSeeflowRoot(filePath), allRefs);
408
+ reconcileFileWatchers(flowId, handle, projectRootForFlow(filePath), allRefs);
421
409
  }
422
410
 
423
411
  return next;
@@ -1,37 +0,0 @@
1
- /**
2
- * Self-contained `emit()` helper copied verbatim into a target repo's
3
- * `.seeflow/sdk/emit.ts` by `seeflow register` when the demo declares any
4
- * event-bound state node. Kept dependency-free so the user's app can call it
5
- * without installing `@seeflow/sdk`.
6
- */
7
- export const EMIT_TEMPLATE = `// Auto-generated by seeflow register. Safe to commit; safe to delete.
8
- // Re-running register will not overwrite this file.
9
- export type EmitStatus = 'running' | 'done' | 'error';
10
-
11
- export interface EmitOptions {
12
- runId?: string;
13
- payload?: unknown;
14
- studioUrl?: string;
15
- }
16
-
17
- const readEnv = (key: string): string | undefined => {
18
- const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;
19
- return proc?.env?.[key];
20
- };
21
-
22
- export async function emit(
23
- flowId: string,
24
- nodeId: string,
25
- status: EmitStatus,
26
- opts: EmitOptions = {},
27
- ): Promise<void> {
28
- const base = (
29
- opts.studioUrl ?? readEnv('SEEFLOW_STUDIO_URL') ?? 'http://localhost:4321'
30
- ).replace(/\\/+$/, '');
31
- await fetch(\`\${base}/api/emit\`, {
32
- method: 'POST',
33
- headers: { 'content-type': 'application/json' },
34
- body: JSON.stringify({ flowId, nodeId, status, runId: opts.runId, payload: opts.payload }),
35
- });
36
- }
37
- `;
package/src/sdk-writer.ts DELETED
@@ -1,37 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import type { Flow } from './schema.ts';
4
- import { EMIT_TEMPLATE } from './sdk-template.ts';
5
-
6
- export type SdkWriteOutcome = 'skipped' | 'written' | 'present';
7
-
8
- export interface SdkWriteResult {
9
- outcome: SdkWriteOutcome;
10
- /** Absolute path of the SDK file (written or pre-existing). Null on `skipped`. */
11
- filePath: string | null;
12
- }
13
-
14
- /**
15
- * Writes `.seeflow/sdk/emit.ts` into a target repo iff the demo declares any
16
- * node with `stateSource.kind === 'event'`. Idempotent: existing files are
17
- * never overwritten. The only place M1's CLI mutates a user repo.
18
- */
19
- export function writeSdkEmitIfNeeded(repoPath: string, demo: Flow): SdkWriteResult {
20
- const hasEventState = demo.nodes.some(
21
- (n) =>
22
- n.type !== 'shapeNode' &&
23
- n.type !== 'imageNode' &&
24
- n.type !== 'iconNode' &&
25
- n.type !== 'htmlNode' &&
26
- n.data.stateSource.kind === 'event',
27
- );
28
- if (!hasEventState) return { outcome: 'skipped', filePath: null };
29
-
30
- const sdkDir = join(repoPath, '.seeflow', 'sdk');
31
- const filePath = join(sdkDir, 'emit.ts');
32
- if (existsSync(filePath)) return { outcome: 'present', filePath };
33
-
34
- mkdirSync(sdkDir, { recursive: true });
35
- writeFileSync(filePath, EMIT_TEMPLATE);
36
- return { outcome: 'written', filePath };
37
- }