@synergenius/flow-weaver 0.23.1 → 0.23.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -442
- package/dist/agent/device-connection.d.ts +7 -0
- package/dist/agent/device-connection.js +7 -0
- package/dist/cli/commands/auth.js +33 -23
- package/dist/cli/commands/connect.d.ts +2 -0
- package/dist/cli/commands/connect.js +95 -1
- package/dist/cli/flow-weaver.mjs +146 -25
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/marketplace/index.d.ts +1 -1
- package/dist/marketplace/index.js +1 -1
- package/dist/marketplace/registry.d.ts +16 -0
- package/dist/marketplace/registry.js +21 -0
- package/dist/marketplace/types.d.ts +2 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -8,461 +8,112 @@
|
|
|
8
8
|
[](./LICENSE)
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Design agent workflows in conversation. The compiled output is yours.**
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*Got an AGENTS.md or runbook? [Convert it to a typed workflow →](https://flowweaver.ai/services/md-converter)*
|
|
16
|
-
|
|
17
|
-
[**flowweaver.ai**](https://flowweaver.ai) · [**Open the Studio**](https://flowweaver.ai/studio) · [**Docs**](https://flowweaver.ai/docs) · [**Discord**](https://discord.gg/6Byh3ur2bk) · [**npm**](https://www.npmjs.com/package/@synergenius/flow-weaver)
|
|
13
|
+
[**flowweaver.ai**](https://flowweaver.ai) · [**Studio**](https://flowweaver.ai/studio) · [**Docs**](https://flowweaver.ai/docs) · [**Discord**](https://discord.gg/6Byh3ur2bk) · [**npm**](https://www.npmjs.com/package/@synergenius/flow-weaver)
|
|
18
14
|
|
|
19
15
|
---
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
17
|
+
> **You:** Build a support agent that classifies messages and either auto-replies or escalates.
|
|
18
|
+
|
|
19
|
+
> **Weaver:** Created `support-agent.ts`. Four nodes, one workflow.
|
|
20
|
+
>
|
|
21
|
+
> ```
|
|
22
|
+
> Workflow: supportAgent
|
|
23
|
+
>
|
|
24
|
+
> Nodes (4):
|
|
25
|
+
> classify [classifyIntent] IN: message OUT: context
|
|
26
|
+
> route [routeAction] IN: context, execute OUT: context, onSuccess, onFailure
|
|
27
|
+
> reply [llmReply] IN: context OUT: reply
|
|
28
|
+
> esc [escalate] IN: context OUT: ticket, assignee
|
|
29
|
+
>
|
|
30
|
+
> Paths (2):
|
|
31
|
+
> Start -> classify -> route -> reply -> Exit
|
|
32
|
+
> Start -> classify -> route -> esc -> Exit
|
|
33
|
+
> ```
|
|
34
|
+
|
|
35
|
+
> **You:** Show me the graph.
|
|
36
|
+
|
|
37
|
+
> **Weaver:**
|
|
38
|
+
>
|
|
39
|
+
> ```
|
|
40
|
+
> ┌───────┐ ┌─────────────────┐ ┌──────────────┐ ┌───────────┐ ┌──────┐
|
|
41
|
+
> │ Start │━━━▶│ Classify Intent │━━━▶│ Route Action │━━━▶│ LLM Reply │━━━▶│ Exit │
|
|
42
|
+
> └───────┘ └─────────────────┘ └──────────────┘ └───────────┘ └──────┘
|
|
43
|
+
>
|
|
44
|
+
> Parallel: Escalate
|
|
45
|
+
> ```
|
|
46
|
+
|
|
47
|
+
> **You:** Compile it.
|
|
48
|
+
|
|
49
|
+
> **Weaver:** Done. Zero runtime deps. The output is a standalone TypeScript function you own.
|
|
50
|
+
|
|
51
|
+
> **You:** Validate.
|
|
52
|
+
|
|
53
|
+
> **Weaver:**
|
|
54
|
+
>
|
|
55
|
+
> ```
|
|
56
|
+
> ✓ 1 valid, 2 warnings in 312ms
|
|
57
|
+
> ```
|
|
58
|
+
>
|
|
59
|
+
> Structural, agent safety, and CI rules. All checked.
|
|
60
|
+
|
|
61
|
+
> **You:** Run it with "I need an urgent refund".
|
|
62
|
+
|
|
63
|
+
> **Weaver:**
|
|
64
|
+
>
|
|
65
|
+
> ```
|
|
66
|
+
> classify: → SUCCEEDED (0ms)
|
|
67
|
+
> route: → SUCCEEDED (0ms)
|
|
68
|
+
> reply: → CANCELLED
|
|
69
|
+
> esc: → SUCCEEDED (0ms)
|
|
70
|
+
> ✓ Workflow "supportAgent" completed in 2ms
|
|
71
|
+
>
|
|
72
|
+
> Result: { ticket: "ESC-1773950738529", assignee: "support-team" }
|
|
73
|
+
> ```
|
|
74
|
+
|
|
75
|
+
> **You:** Commit it.
|
|
76
|
+
|
|
77
|
+
> **Weaver:** Committed to `main`. It's a `.ts` file in your repo. Version-controlled like everything else.
|
|
78
|
+
|
|
79
|
+
> **You:** Now add sentiment analysis before routing.
|
|
80
|
+
|
|
81
|
+
> **Weaver:** Done. Here's what changed:
|
|
82
|
+
>
|
|
83
|
+
> ```
|
|
84
|
+
> [BREAKING] +1 types, ~1 types, +1 nodes, +5 conns, -2 conns
|
|
85
|
+
>
|
|
86
|
+
> + analyzeSentiment
|
|
87
|
+
> ~ routeAction (+inputs: sentiment)
|
|
88
|
+
> + sentiment (instance)
|
|
89
|
+
> + classify.context → sentiment.context
|
|
90
|
+
> + sentiment.sentiment → route.sentiment
|
|
91
|
+
> ```
|
|
92
|
+
|
|
93
|
+
> **You:** Where does this deploy?
|
|
94
|
+
|
|
95
|
+
> **Weaver:** Anywhere TypeScript runs. Or generate boilerplate:
|
|
96
|
+
>
|
|
97
|
+
> ```
|
|
98
|
+
> fw export support-agent.ts --target vercel
|
|
99
|
+
> fw export support-agent.ts --target lambda
|
|
100
|
+
> fw export support-agent.ts --target cloudflare
|
|
101
|
+
> fw export support-agent.ts --target inngest
|
|
102
|
+
> ```
|
|
55
103
|
|
|
56
104
|
---
|
|
57
105
|
|
|
58
|
-
##
|
|
59
|
-
|
|
60
|
-
**In the browser.** Open the [Cloud Studio](https://flowweaver.ai/studio), sign up, and start building. No install required.
|
|
61
|
-
|
|
62
|
-
**From the terminal.**
|
|
106
|
+
## Install
|
|
63
107
|
|
|
64
108
|
```bash
|
|
65
109
|
npm install @synergenius/flow-weaver
|
|
66
|
-
npx
|
|
67
|
-
cd my-project
|
|
68
|
-
npx fw run workflows/example.ts --params '{"input": "value"}'
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
`fw` is the CLI command. `flow-weaver` also works as an alias.
|
|
72
|
-
|
|
73
|
-
## How It Works
|
|
74
|
-
|
|
75
|
-
**Generate.** Let AI build it for you, design visually in the Studio, or write annotated TypeScript by hand. All three stay in sync.
|
|
76
|
-
|
|
77
|
-
**Validate.** The compiler catches wiring errors, type mismatches, missing handlers, unreachable paths, and agent-specific mistakes before anything runs. Not at runtime, not in production.
|
|
78
|
-
|
|
79
|
-
**Own.** Compiled output is plain TypeScript with zero imports from Flow Weaver. Clean git diffs, standard testing, no license obligations on the output. If Flow Weaver disappeared tomorrow, your agents would still run.
|
|
80
|
-
|
|
81
|
-
## Three Ways to Build
|
|
82
|
-
|
|
83
|
-
**Studio.** A visual IDE that combines a full code editor with a graph builder. Write and refactor workflows in code or compose them on the canvas. Both representations stay synchronized: editing either one updates the other instantly.
|
|
84
|
-
|
|
85
|
-
**TypeScript.** Define workflows in plain TypeScript by annotating functions with JSDoc. The compiler derives an executable workflow graph with static typing and compile-time validation. No YAML, no JSON configuration, no runtime layer. Remove the annotations and you keep a clean TypeScript file with zero dependencies.
|
|
86
|
-
|
|
87
|
-
**AI Agents.** Connect Claude Code, Cursor, or OpenClaw and let agents scaffold, compile, fix validation errors, and iterate until the workflow works. Agents can also test with mock AI responses and generate diagrams for review.
|
|
88
|
-
|
|
89
|
-
## Quick Start
|
|
90
|
-
|
|
91
|
-
Workflows are plain TypeScript. Annotations declare the graph structure:
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// data-pipeline.ts
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @flowWeaver nodeType
|
|
98
|
-
* @input rawData - string
|
|
99
|
-
* @output cleaned - string
|
|
100
|
-
* @output wordCount - number
|
|
101
|
-
*/
|
|
102
|
-
function processText(execute: boolean, rawData: string) {
|
|
103
|
-
if (!execute) return { onSuccess: false, onFailure: false, cleaned: '', wordCount: 0 };
|
|
104
|
-
const cleaned = rawData.trim().toLowerCase();
|
|
105
|
-
return { onSuccess: true, onFailure: false, cleaned, wordCount: cleaned.split(/\s+/).length };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* @flowWeaver workflow
|
|
110
|
-
* @node processor processText
|
|
111
|
-
* @connect Start.rawData -> processor.rawData
|
|
112
|
-
* @connect processor.cleaned -> Exit.cleaned
|
|
113
|
-
* @connect processor.wordCount -> Exit.wordCount
|
|
114
|
-
*/
|
|
115
|
-
export async function dataPipeline(
|
|
116
|
-
execute: boolean,
|
|
117
|
-
params: { rawData: string }
|
|
118
|
-
): Promise<{ onSuccess: boolean; onFailure: boolean; cleaned: string; wordCount: number }> {
|
|
119
|
-
throw new Error('Not compiled');
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
npx fw compile data-pipeline.ts # generates executable code in-place
|
|
125
|
-
npx fw run data-pipeline.ts --params '{"rawData": "Hello World"}'
|
|
110
|
+
npx flow-weaver init
|
|
126
111
|
```
|
|
127
112
|
|
|
128
|
-
The
|
|
129
|
-
|
|
130
|
-
## AI-Native Development with MCP
|
|
131
|
-
|
|
132
|
-
Flow Weaver includes an MCP server with 48 tools for Claude Code, Cursor, OpenClaw, or any MCP-compatible agent:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
npx fw mcp-server # auto-registers with Claude Code
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
| Capability | MCP Tools |
|
|
139
|
-
|-----------|-----------|
|
|
140
|
-
| **Build** | `fw_scaffold`, `fw_modify`, `fw_modify_batch`, `fw_add_node`, `fw_connect` |
|
|
141
|
-
| **Model** | `fw_create_model`, `fw_workflow_status`, `fw_implement_node` |
|
|
142
|
-
| **Validate** | `fw_validate` (with friendly error hints), `fw_doctor` |
|
|
143
|
-
| **Understand** | `fw_describe` (json/text/mermaid), `fw_query` (10 query types), `fw_diff` |
|
|
144
|
-
| **Test** | `fw_execute_workflow` (with trace), `fw_compile` |
|
|
145
|
-
| **Visualize** | `fw_diagram` (SVG/HTML), `fw_get_state`, `fw_focus_node` |
|
|
146
|
-
| **Deploy** | `fw_export` (Lambda, Vercel, Cloudflare, Inngest, GitHub Actions, GitLab CI — via packs), `fw_compile --target inngest` |
|
|
147
|
-
| **Reuse** | `fw_list_patterns`, `fw_apply_pattern`, `fw_extract_pattern` |
|
|
148
|
-
| **Extend** | `fw_market_search`, `fw_market_install` |
|
|
149
|
-
|
|
150
|
-
## Model-Driven Workflows
|
|
151
|
-
|
|
152
|
-
Design first, implement later. Describe the workflow shape (nodes, ports, execution path) and the compiler generates a valid skeleton with `declare function` stubs:
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
# Via MCP: fw_create_model with nodes, inputs/outputs, and execution path
|
|
156
|
-
# Via CLI:
|
|
157
|
-
npx fw status my-workflow.ts # shows stub vs implemented progress
|
|
158
|
-
npx fw implement my-workflow.ts processData # scaffolds a node body
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
The graph is valid before any node has a real implementation. Fill in node bodies incrementally, check `status` to track progress. The architect defines the shape, developers fill in the logic.
|
|
162
|
-
|
|
163
|
-
## Agent Workflow Templates
|
|
164
|
-
|
|
165
|
-
Built-in templates for AI agent workflows:
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
npx fw create workflow ai-agent my-agent.ts --provider openai --model gpt-4o
|
|
169
|
-
npx fw create workflow ai-react react-agent.ts
|
|
170
|
-
npx fw create workflow ai-rag rag-pipeline.ts
|
|
171
|
-
npx fw create workflow ai-agent-durable durable-agent.ts
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
**12 workflow templates:**
|
|
175
|
-
|
|
176
|
-
| Template | What it builds |
|
|
177
|
-
|----------|---------------|
|
|
178
|
-
| `ai-agent` | Tool-calling agent with explicit loop and termination semantics |
|
|
179
|
-
| `ai-react` | ReAct agent (Thought -> Action -> Observation) |
|
|
180
|
-
| `ai-rag` | Retrieval-Augmented Generation pipeline |
|
|
181
|
-
| `ai-chat` | Stateful conversational AI with memory |
|
|
182
|
-
| `ai-agent-durable` | Durable agent pipeline with Inngest step-level retries |
|
|
183
|
-
| `ai-pipeline-durable` | Multi-step AI pipeline with durability |
|
|
184
|
-
| `sequential` | Linear data pipeline |
|
|
185
|
-
| `foreach` | Iteration over collections |
|
|
186
|
-
| `conditional` | Branching logic |
|
|
187
|
-
| `aggregator` | Multi-source aggregation |
|
|
188
|
-
| `webhook` | HTTP event handler |
|
|
189
|
-
| `error-handler` | Error recovery pattern |
|
|
190
|
-
|
|
191
|
-
Plus **12 node templates** for common building blocks: `llm-call`, `tool-executor`, `conversation-memory`, `prompt-template`, `json-extractor`, `human-approval`, `agent-router`, `rag-retriever`, `validator`, `transformer`, `http`, `aggregator`.
|
|
192
|
-
|
|
193
|
-
## Compile-Time Validation
|
|
194
|
-
|
|
195
|
-
The validator understands AI agent patterns and enforces safety rules:
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
AGENT_LLM_MISSING_ERROR_HANDLER LLM node's onFailure is unconnected, add error handling
|
|
199
|
-
AGENT_UNGUARDED_TOOL_EXECUTOR Tool executor has no human-approval upstream, add a gate
|
|
200
|
-
AGENT_MISSING_MEMORY_IN_LOOP Agent loop has LLM but no memory, conversations will be stateless
|
|
201
|
-
AGENT_LLM_NO_FALLBACK LLM failure goes directly to Exit, add retry or fallback logic
|
|
202
|
-
AGENT_TOOL_NO_OUTPUT_HANDLING Tool executor outputs are all unconnected, results are discarded
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
These are not generic lint rules. The validator identifies LLM, tool-executor, human-approval, and memory nodes by port signatures, annotations, and naming patterns, then applies agent-specific checks.
|
|
206
|
-
|
|
207
|
-
## Deterministic Agent Testing
|
|
208
|
-
|
|
209
|
-
Test LLM workflows without real API calls:
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
import { createMockLlmProvider, createRecordingProvider, loadRecording } from '@synergenius/flow-weaver/testing';
|
|
213
|
-
|
|
214
|
-
// Mock: deterministic responses for CI
|
|
215
|
-
const mock = createMockLlmProvider([
|
|
216
|
-
{ content: 'I need to search for that.', toolCalls: [{ name: 'search', args: { q: 'test' } }] },
|
|
217
|
-
{ content: 'Based on the results, the answer is 42.' },
|
|
218
|
-
]);
|
|
219
|
-
|
|
220
|
-
// Record: capture real LLM calls, replay later
|
|
221
|
-
const recorder = createRecordingProvider(realProvider);
|
|
222
|
-
// ... run workflow ...
|
|
223
|
-
saveRecording(recorder.getRecording(), 'fixtures/agent-session.json');
|
|
224
|
-
|
|
225
|
-
// Replay: reproducible tests from recorded sessions
|
|
226
|
-
const replay = loadRecording('fixtures/agent-session.json');
|
|
227
|
-
```
|
|
113
|
+
The CLI handles the rest.
|
|
228
114
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
## Scoped Ports: Agent Loops Without Graph Cycles
|
|
232
|
-
|
|
233
|
-
Most workflow engines either ban loops entirely (DAG-only) or allow arbitrary cycles that are hard to reason about. Flow Weaver takes a third path: **scoped ports** express iteration while keeping the graph acyclic and statically analyzable.
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
/**
|
|
237
|
-
* @flowWeaver workflow
|
|
238
|
-
* @node agent llmCall
|
|
239
|
-
* @node tools toolExecutor
|
|
240
|
-
* @node memory conversationMemory
|
|
241
|
-
* @scope agent, tools, memory // these nodes iterate together
|
|
242
|
-
* @connect agent.toolCalls -> tools.calls
|
|
243
|
-
* @connect tools.results -> memory.input
|
|
244
|
-
* @connect memory.history -> agent.context
|
|
245
|
-
*/
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
The scope's output ports become callback parameters, and input ports become return values. Agent reasoning loops, ForEach over collections, map/reduce patterns, and nested sub-workflows all work without introducing graph cycles.
|
|
249
|
-
|
|
250
|
-
## Deploy Anywhere
|
|
251
|
-
|
|
252
|
-
Same workflow source, multiple deployment targets. Export targets are provided by marketplace packs — install the ones you need:
|
|
253
|
-
|
|
254
|
-
```bash
|
|
255
|
-
npm install @synergenius/flow-weaver-pack-lambda @synergenius/flow-weaver-pack-vercel \
|
|
256
|
-
@synergenius/flow-weaver-pack-cloudflare @synergenius/flow-weaver-pack-inngest \
|
|
257
|
-
@synergenius/flow-weaver-pack-github-actions @synergenius/flow-weaver-pack-gitlab-ci
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
```bash
|
|
261
|
-
# Plain TypeScript (default)
|
|
262
|
-
fw compile workflow.ts
|
|
263
|
-
|
|
264
|
-
# Inngest durable functions (per-node step.run, retries, cron)
|
|
265
|
-
fw compile workflow.ts --target inngest --retries 3 --cron "0 9 * * *"
|
|
266
|
-
|
|
267
|
-
# Serverless exports
|
|
268
|
-
fw export workflow.ts --target lambda --output deploy/
|
|
269
|
-
fw export workflow.ts --target vercel --output deploy/
|
|
270
|
-
fw export workflow.ts --target cloudflare --output deploy/
|
|
271
|
-
|
|
272
|
-
# CI/CD pipelines
|
|
273
|
-
fw export workflow.ts --target github-actions --output .github/workflows/
|
|
274
|
-
fw export workflow.ts --target gitlab-ci --output .
|
|
275
|
-
|
|
276
|
-
# HTTP server with OpenAPI docs
|
|
277
|
-
fw serve ./workflows --port 3000 --swagger
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
Both the default TypeScript target and Inngest target parallelize independent nodes with `Promise.all()`. Inngest additionally wraps each node in `step.run()` for individual durability and generates typed Zod event schemas.
|
|
281
|
-
|
|
282
|
-
## Diagram Generation
|
|
283
|
-
|
|
284
|
-
Generate SVG or interactive HTML diagrams from any workflow:
|
|
285
|
-
|
|
286
|
-
```bash
|
|
287
|
-
fw diagram workflow.ts -o workflow.svg --theme dark
|
|
288
|
-
fw diagram workflow.ts -o workflow.html --format html
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
Customize node appearance with annotations:
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
294
|
-
/**
|
|
295
|
-
* @flowWeaver nodeType
|
|
296
|
-
* @color blue
|
|
297
|
-
* @icon database
|
|
298
|
-
*/
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Named colors: `blue`, `purple`, `green`, `cyan`, `orange`, `pink`, `red`, `yellow`. Icons include `api`, `database`, `shield`, `brain`, `cloud`, `search`, `code`, and 60+ more from Material Design 3. The interactive HTML viewer supports zoom/pan, click-to-inspect nodes, and port-level hover with connection tracing.
|
|
302
|
-
|
|
303
|
-
## API
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
import {
|
|
307
|
-
parseWorkflow, // Parse workflow file to AST
|
|
308
|
-
compileWorkflow, // Parse + validate + generate in one step
|
|
309
|
-
validateWorkflow, // Validate AST (returns errors and warnings)
|
|
310
|
-
generateCode, // Generate code from AST
|
|
311
|
-
generateInPlace, // Regenerate only the compiled markers in-place
|
|
312
|
-
} from '@synergenius/flow-weaver';
|
|
313
|
-
|
|
314
|
-
// Full compilation
|
|
315
|
-
const { code, ast, errors } = await compileWorkflow('workflow.ts');
|
|
316
|
-
|
|
317
|
-
// Step by step
|
|
318
|
-
const { ast } = await parseWorkflow('workflow.ts');
|
|
319
|
-
const { errors, warnings } = validateWorkflow(ast);
|
|
320
|
-
const code = generateCode(ast);
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Package Exports
|
|
324
|
-
|
|
325
|
-
| Import Path | Purpose |
|
|
326
|
-
|-------------|---------|
|
|
327
|
-
| `@synergenius/flow-weaver` | Parse, validate, compile, generate, AST types, builders, diff, patterns |
|
|
328
|
-
| `@synergenius/flow-weaver/runtime` | Execution context, errors, function registry for generated code |
|
|
329
|
-
| `@synergenius/flow-weaver/testing` | Mock LLM/approval providers, recording/replay, assertions, token tracking |
|
|
330
|
-
| `@synergenius/flow-weaver/built-in-nodes` | delay, waitForEvent, waitForAgent, invokeWorkflow |
|
|
331
|
-
| `@synergenius/flow-weaver/diagram` | SVG/HTML diagram layout and rendering |
|
|
332
|
-
| `@synergenius/flow-weaver/ast` | AST types and utilities |
|
|
333
|
-
| `@synergenius/flow-weaver/api` | Programmatic workflow manipulation API |
|
|
334
|
-
| `@synergenius/flow-weaver/diff` | Semantic workflow diffing |
|
|
335
|
-
| `@synergenius/flow-weaver/deployment` | Deployment base classes, registry, and CI/CD utilities |
|
|
336
|
-
| `@synergenius/flow-weaver/marketplace` | Marketplace package utilities |
|
|
337
|
-
| `@synergenius/flow-weaver/editor` | Editor completions and suggestions |
|
|
338
|
-
| `@synergenius/flow-weaver/browser` | JSDoc port sync for browser environments |
|
|
339
|
-
| `@synergenius/flow-weaver/describe` | Programmatic workflow description |
|
|
340
|
-
| `@synergenius/flow-weaver/doc-metadata` | Documentation metadata extractors |
|
|
341
|
-
|
|
342
|
-
## CLI Reference
|
|
343
|
-
|
|
344
|
-
```bash
|
|
345
|
-
# Core
|
|
346
|
-
fw compile <file> # Compile to TypeScript or Inngest
|
|
347
|
-
fw validate <file> # Validate without compiling
|
|
348
|
-
fw run <file> # Execute a workflow
|
|
349
|
-
fw dev <file> # Watch + compile + run
|
|
350
|
-
fw strip <file> # Remove generated code sections
|
|
351
|
-
fw describe <file> # Structure output (json/text/mermaid)
|
|
352
|
-
fw diagram <file> # Generate SVG or interactive HTML diagram
|
|
353
|
-
fw diff <f1> <f2> # Semantic workflow comparison
|
|
354
|
-
|
|
355
|
-
# Model-driven
|
|
356
|
-
fw status <file> # Show stub vs implemented progress
|
|
357
|
-
fw implement <file> <node> # Scaffold a node body from its stub
|
|
358
|
-
|
|
359
|
-
# Scaffolding
|
|
360
|
-
fw init [directory] # Create new project
|
|
361
|
-
fw create workflow <t> <f> # Scaffold from template
|
|
362
|
-
fw create node <name> <f> # Scaffold node type
|
|
363
|
-
fw templates # List available templates
|
|
364
|
-
fw doctor # Check project compatibility
|
|
365
|
-
fw grammar # Output annotation grammar (EBNF/railroad)
|
|
366
|
-
|
|
367
|
-
# Deploy
|
|
368
|
-
fw serve [directory] # HTTP server with Swagger UI
|
|
369
|
-
fw export <file> # Export to Lambda/Vercel/Cloudflare/Inngest/GitHub Actions/GitLab CI (via packs)
|
|
370
|
-
fw openapi <directory> # Generate OpenAPI spec
|
|
371
|
-
|
|
372
|
-
# Patterns
|
|
373
|
-
fw pattern list <path> # List reusable patterns
|
|
374
|
-
fw pattern apply <p> <t> # Apply pattern to workflow
|
|
375
|
-
fw pattern extract <src> # Extract pattern from nodes
|
|
376
|
-
|
|
377
|
-
# Docs
|
|
378
|
-
fw docs # List documentation topics
|
|
379
|
-
fw docs read <topic> # Read a topic
|
|
380
|
-
fw docs search <query> # Search documentation
|
|
381
|
-
|
|
382
|
-
# Marketplace
|
|
383
|
-
fw market search [query] # Search npm for packages
|
|
384
|
-
fw market install <pkg> # Install a package
|
|
385
|
-
fw market list # List installed packages
|
|
386
|
-
fw market init <name> # Scaffold a marketplace package
|
|
387
|
-
fw market pack # Validate and generate manifest
|
|
388
|
-
fw market publish # Publish to npm
|
|
389
|
-
|
|
390
|
-
# Editor / IDE
|
|
391
|
-
fw mcp-server # Start MCP server for Claude Code
|
|
392
|
-
fw listen # Stream editor events
|
|
393
|
-
fw changelog # Generate changelog from git history
|
|
394
|
-
fw migrate <glob> # Run migration transforms on workflow files
|
|
395
|
-
fw plugin init <name> # Scaffold an external plugin
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
## Built-in Nodes
|
|
399
|
-
|
|
400
|
-
| Node | Purpose |
|
|
401
|
-
|------|---------|
|
|
402
|
-
| `delay` | Sleep for a duration (ms, s, m, h, d). Mockable for fast testing. |
|
|
403
|
-
| `waitForEvent` | Wait for an external event with optional field matching and timeout. Maps to Inngest `step.waitForEvent()` for zero-cost durable pauses. |
|
|
404
|
-
| `waitForAgent` | Pause execution and wait for an external agent to provide a result. Supports multi-step human-in-the-loop and agent delegation patterns. |
|
|
405
|
-
| `invokeWorkflow` | Invoke another workflow by ID with payload and timeout. Maps to Inngest `step.invoke()`. |
|
|
406
|
-
|
|
407
|
-
## STEP Port Architecture
|
|
408
|
-
|
|
409
|
-
Every node follows a consistent contract:
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
function nodeName(
|
|
413
|
-
execute: boolean, // Control: should this node run?
|
|
414
|
-
...inputs // Data inputs (typed)
|
|
415
|
-
): {
|
|
416
|
-
onSuccess: boolean; // Success control output
|
|
417
|
-
onFailure: boolean; // Failure control output
|
|
418
|
-
...outputs // Data outputs (typed)
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
Expression nodes (`@expression`) skip the control flow boilerplate. Inputs and outputs map directly to the TypeScript signature.
|
|
423
|
-
|
|
424
|
-
## Flow Weaver Cloud
|
|
425
|
-
|
|
426
|
-
[flowweaver.ai](https://flowweaver.ai) is the hosted platform that adds collaboration, managed deployments, and team features on top of everything the CLI provides.
|
|
427
|
-
|
|
428
|
-
What the cloud adds:
|
|
429
|
-
|
|
430
|
-
- **Browser-based Studio** with real-time collaborative editing
|
|
431
|
-
- **One-click deployment** of any workflow as an HTTP endpoint
|
|
432
|
-
- **Execution logs** for every workflow run, with input/output history
|
|
433
|
-
- **Visual debugger** for step-through node inspection (Pro)
|
|
434
|
-
- **Version history** and workflow snapshots (Pro)
|
|
435
|
-
- **AI Chat assistant** for building and debugging workflows (Pro)
|
|
436
|
-
- **Organization workspaces** with team management (Business)
|
|
437
|
-
|
|
438
|
-
Three tiers: **Free** (3 workflows, 1 deployment, 100 executions/month), **Pro** at 9 EUR/month (25 workflows, 10k executions), and **Business** at 29 EUR/month (unlimited workflows, 100k executions). See [flowweaver.ai/pricing](https://flowweaver.ai/pricing) for details.
|
|
439
|
-
|
|
440
|
-
The CLI remains fully functional for local development. The cloud adds the parts that are hard to do alone: team collaboration, always-on endpoints, and operational visibility.
|
|
441
|
-
|
|
442
|
-
## Community
|
|
443
|
-
|
|
444
|
-
- [Discord](https://discord.gg/6Byh3ur2bk)
|
|
445
|
-
- [GitHub Discussions](https://github.com/synergenius-fw/flow-weaver/discussions)
|
|
446
|
-
- [X / Twitter](https://x.com/flowweaver_ai)
|
|
447
|
-
- [Website](https://flowweaver.ai)
|
|
448
|
-
|
|
449
|
-
## Development
|
|
450
|
-
|
|
451
|
-
```bash
|
|
452
|
-
npm run build # Build
|
|
453
|
-
npm run watch # Watch mode
|
|
454
|
-
npm test # Run all tests
|
|
455
|
-
npm run test:watch # Watch mode
|
|
456
|
-
npm run typecheck # Type check
|
|
457
|
-
npm run docs # Generate API docs
|
|
458
|
-
```
|
|
115
|
+
---
|
|
459
116
|
|
|
460
117
|
## License
|
|
461
118
|
|
|
462
|
-
Licensed under the Flow Weaver
|
|
463
|
-
|
|
464
|
-
- **Free to use**: install, run, and compile workflows in any organization
|
|
465
|
-
- **Free to host internally** for organizations with 15 or fewer people
|
|
466
|
-
- **Commercial license required** to host internally for 16+ people (contact support@synergenius.pt)
|
|
467
|
-
- **External hosting prohibited** without a commercial license
|
|
468
|
-
- **Output is unrestricted**: compiled workflows, generated code, and deployment artifacts are yours
|
|
119
|
+
Licensed under the [Flow Weaver License](https://flowweaver.ai/license). See [LICENSE.md](./LICENSE.md).
|
|
@@ -14,6 +14,8 @@ export interface DeviceInfo {
|
|
|
14
14
|
projectDir: string;
|
|
15
15
|
platform: string;
|
|
16
16
|
capabilities: string[];
|
|
17
|
+
/** Pack names that contributed device handlers (for auto-install on platform) */
|
|
18
|
+
packs?: string[];
|
|
17
19
|
}
|
|
18
20
|
export interface DeviceConnectionOptions {
|
|
19
21
|
platformUrl: string;
|
|
@@ -46,6 +48,11 @@ export declare class DeviceConnection {
|
|
|
46
48
|
* Add a capability to advertise to the platform.
|
|
47
49
|
*/
|
|
48
50
|
addCapability(capability: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Set the list of packs that contributed device handlers.
|
|
53
|
+
* The platform uses this to auto-install packs in the user's workspace.
|
|
54
|
+
*/
|
|
55
|
+
setPacks(packs: string[]): void;
|
|
49
56
|
/**
|
|
50
57
|
* Register a handler for incoming requests from the platform.
|
|
51
58
|
*/
|
|
@@ -38,6 +38,13 @@ export class DeviceConnection {
|
|
|
38
38
|
this.deviceInfo.capabilities.push(capability);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Set the list of packs that contributed device handlers.
|
|
43
|
+
* The platform uses this to auto-install packs in the user's workspace.
|
|
44
|
+
*/
|
|
45
|
+
setPacks(packs) {
|
|
46
|
+
this.deviceInfo.packs = packs;
|
|
47
|
+
}
|
|
41
48
|
/**
|
|
42
49
|
* Register a handler for incoming requests from the platform.
|
|
43
50
|
*/
|
|
@@ -3,8 +3,9 @@ import { loadCredentials, saveCredentials, clearCredentials, getPlatformUrl } fr
|
|
|
3
3
|
import { PlatformClient } from '../config/platform-client.js';
|
|
4
4
|
export async function loginCommand(options) {
|
|
5
5
|
const platformUrl = options.platformUrl ?? getPlatformUrl();
|
|
6
|
+
const displayUrl = platformUrl.replace(/^https?:\/\//, '');
|
|
6
7
|
console.log('');
|
|
7
|
-
console.log(
|
|
8
|
+
console.log(` \x1b[1mFlow Weaver\x1b[0m \x1b[2m(${displayUrl})\x1b[0m`);
|
|
8
9
|
console.log('');
|
|
9
10
|
// API key mode (for CI/headless)
|
|
10
11
|
if (options.apiKey) {
|
|
@@ -42,8 +43,8 @@ async function loginWithBrowser(platformUrl) {
|
|
|
42
43
|
interval = data.interval ?? 5;
|
|
43
44
|
}
|
|
44
45
|
catch {
|
|
45
|
-
console.error(
|
|
46
|
-
console.error(' Check
|
|
46
|
+
console.error(` \x1b[31m✗\x1b[0m Cannot connect to ${platformUrl}`);
|
|
47
|
+
console.error(' Check the URL or set FW_PLATFORM_URL');
|
|
47
48
|
process.exit(1);
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
@@ -65,20 +66,24 @@ async function loginWithBrowser(platformUrl) {
|
|
|
65
66
|
}
|
|
66
67
|
console.log('');
|
|
67
68
|
// Step 3: Poll for completion
|
|
68
|
-
process.
|
|
69
|
+
process.stderr.write(' Waiting for approval');
|
|
69
70
|
let cancelled = false;
|
|
70
71
|
const sigHandler = () => { cancelled = true; };
|
|
71
72
|
process.on('SIGINT', sigHandler);
|
|
72
73
|
const maxAttempts = 120; // 10 minutes at 5s intervals
|
|
74
|
+
let networkErrors = 0;
|
|
73
75
|
for (let i = 0; i < maxAttempts && !cancelled; i++) {
|
|
74
76
|
await new Promise(r => setTimeout(r, interval * 1000));
|
|
75
77
|
try {
|
|
76
78
|
const resp = await fetch(`${platformUrl}/auth/device/poll?deviceCode=${deviceCode}`);
|
|
77
|
-
|
|
79
|
+
networkErrors = 0; // reset on successful connection
|
|
80
|
+
if (!resp.ok) {
|
|
81
|
+
process.stderr.write('.');
|
|
78
82
|
continue;
|
|
83
|
+
}
|
|
79
84
|
const data = await resp.json();
|
|
80
85
|
if (data.status === 'approved' && data.token && data.user) {
|
|
81
|
-
process.
|
|
86
|
+
process.stderr.write(' \x1b[32m✓\x1b[0m\n\n');
|
|
82
87
|
saveCredentials({
|
|
83
88
|
token: data.token,
|
|
84
89
|
email: data.user.email,
|
|
@@ -95,7 +100,7 @@ async function loginWithBrowser(platformUrl) {
|
|
|
95
100
|
return;
|
|
96
101
|
}
|
|
97
102
|
if (data.status === 'expired') {
|
|
98
|
-
process.
|
|
103
|
+
process.stderr.write(' \x1b[31mtimed out\x1b[0m\n\n');
|
|
99
104
|
console.log(' Code expired. Run \x1b[36mfw login\x1b[0m again.');
|
|
100
105
|
console.log('');
|
|
101
106
|
process.removeListener('SIGINT', sigHandler);
|
|
@@ -103,27 +108,30 @@ async function loginWithBrowser(platformUrl) {
|
|
|
103
108
|
return;
|
|
104
109
|
}
|
|
105
110
|
if (data.status === 'denied') {
|
|
106
|
-
process.
|
|
111
|
+
process.stderr.write(' \x1b[31mdenied\x1b[0m\n\n');
|
|
107
112
|
console.log(' Access denied.');
|
|
108
113
|
console.log('');
|
|
109
114
|
process.removeListener('SIGINT', sigHandler);
|
|
110
115
|
process.exit(1);
|
|
111
116
|
return;
|
|
112
117
|
}
|
|
113
|
-
// Still pending — show a dot
|
|
114
|
-
|
|
115
|
-
process.stdout.write('.');
|
|
118
|
+
// Still pending — show a dot every poll
|
|
119
|
+
process.stderr.write('.');
|
|
116
120
|
}
|
|
117
121
|
catch {
|
|
118
|
-
|
|
122
|
+
networkErrors++;
|
|
123
|
+
if (networkErrors >= 3) {
|
|
124
|
+
process.stderr.write(' \x1b[33m!\x1b[0m');
|
|
125
|
+
networkErrors = 0;
|
|
126
|
+
}
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
process.removeListener('SIGINT', sigHandler);
|
|
122
130
|
if (cancelled) {
|
|
123
|
-
process.
|
|
131
|
+
process.stderr.write(' \x1b[33mcancelled\x1b[0m\n\n');
|
|
124
132
|
}
|
|
125
133
|
else {
|
|
126
|
-
process.
|
|
134
|
+
process.stderr.write(' \x1b[31mtimed out\x1b[0m\n\n');
|
|
127
135
|
console.log(' Authentication timed out. Run \x1b[36mfw login\x1b[0m again.');
|
|
128
136
|
console.log('');
|
|
129
137
|
}
|
|
@@ -209,23 +217,24 @@ export async function authStatusCommand() {
|
|
|
209
217
|
console.log('');
|
|
210
218
|
}
|
|
211
219
|
function prompt(message, hidden = false) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Hide password input
|
|
220
|
+
if (hidden && process.stdin.isTTY) {
|
|
221
|
+
// Raw-mode password input — no readline (it competes for stdin)
|
|
222
|
+
return new Promise((resolve) => {
|
|
216
223
|
process.stderr.write(message);
|
|
217
224
|
process.stdin.setRawMode(true);
|
|
225
|
+
process.stdin.resume();
|
|
218
226
|
let input = '';
|
|
219
227
|
const handler = (key) => {
|
|
220
228
|
const ch = key.toString();
|
|
221
229
|
if (ch === '\r' || ch === '\n') {
|
|
222
230
|
process.stdin.setRawMode(false);
|
|
231
|
+
process.stdin.pause();
|
|
223
232
|
process.stdin.removeListener('data', handler);
|
|
224
233
|
process.stderr.write('\n');
|
|
225
|
-
rl.close();
|
|
226
234
|
resolve(input);
|
|
227
235
|
}
|
|
228
236
|
else if (ch === '\x03') { // Ctrl+C
|
|
237
|
+
process.stdin.setRawMode(false);
|
|
229
238
|
process.exit(1);
|
|
230
239
|
}
|
|
231
240
|
else if (ch === '\x7f') { // Backspace
|
|
@@ -236,10 +245,11 @@ function prompt(message, hidden = false) {
|
|
|
236
245
|
}
|
|
237
246
|
};
|
|
238
247
|
process.stdin.on('data', handler);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return new Promise((resolve) => {
|
|
251
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
252
|
+
rl.question(message, (answer) => { rl.close(); resolve(answer); });
|
|
243
253
|
});
|
|
244
254
|
}
|
|
245
255
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import { DeviceConnection } from '../../agent/device-connection.js';
|
|
2
|
+
export declare function loadPackDeviceHandlers(conn: DeviceConnection, projectDir: string): Promise<string[]>;
|
|
1
3
|
export declare function handleConnect(projectDir: string): Promise<void>;
|
|
2
4
|
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import * as os from 'node:os';
|
|
4
|
+
import * as readline from 'node:readline';
|
|
4
5
|
import { DeviceConnection } from '../../agent/device-connection.js';
|
|
6
|
+
import { discoverDeviceHandlers } from '../../marketplace/registry.js';
|
|
7
|
+
function promptYesNo(message) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
10
|
+
rl.question(message, (answer) => {
|
|
11
|
+
rl.close();
|
|
12
|
+
const normalized = answer.trim().toLowerCase();
|
|
13
|
+
resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function loadPackDeviceHandlers(conn, projectDir) {
|
|
18
|
+
const loadedPacks = [];
|
|
19
|
+
try {
|
|
20
|
+
const handlers = await discoverDeviceHandlers(projectDir);
|
|
21
|
+
for (const handler of handlers) {
|
|
22
|
+
try {
|
|
23
|
+
const mod = await import(handler.entrypoint);
|
|
24
|
+
if (typeof mod.register === 'function') {
|
|
25
|
+
await mod.register(conn, { projectDir });
|
|
26
|
+
loadedPacks.push(handler.packageName);
|
|
27
|
+
process.stderr.write(` \x1b[2m+ ${handler.packageName} handlers\x1b[0m\n`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
process.stderr.write(` \x1b[33m⚠\x1b[0m Failed to load handlers from ${handler.packageName}: ${err instanceof Error ? err.message : err}\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Discovery failed — non-fatal
|
|
37
|
+
}
|
|
38
|
+
return loadedPacks;
|
|
39
|
+
}
|
|
5
40
|
export async function handleConnect(projectDir) {
|
|
6
41
|
// Load credentials
|
|
7
42
|
const credPath = path.join(os.homedir(), '.fw', 'credentials.json');
|
|
@@ -46,13 +81,72 @@ export async function handleConnect(projectDir) {
|
|
|
46
81
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
47
82
|
return entries
|
|
48
83
|
.filter(e => !e.name.startsWith('.') && e.name !== 'node_modules' && e.name !== 'dist')
|
|
49
|
-
.map(e => ({ name: e.name, type: e.isDirectory() ? 'directory' : 'file', path: path.relative(projectDir, path.join(dirPath, e.name)) }));
|
|
84
|
+
.map(e => ({ name: e.name, type: e.isDirectory() ? 'directory' : 'file', path: path.relative(projectDir, path.join(dirPath, e.name)), hasUnfetchedChildren: e.isDirectory() }));
|
|
50
85
|
});
|
|
86
|
+
// Load pack device handlers (if any installed packs provide them)
|
|
87
|
+
const loadedPacks = await loadPackDeviceHandlers(conn, projectDir);
|
|
88
|
+
// Tell the platform which packs contributed handlers (for auto-install)
|
|
89
|
+
if (loadedPacks.length > 0) {
|
|
90
|
+
conn.setPacks(loadedPacks);
|
|
91
|
+
}
|
|
51
92
|
console.log('');
|
|
52
93
|
console.log(' \x1b[1mflow-weaver connect\x1b[0m');
|
|
53
94
|
console.log(` \x1b[2mProject: ${path.basename(projectDir)}\x1b[0m`);
|
|
54
95
|
console.log(` \x1b[2mPlatform: ${creds.platformUrl}\x1b[0m`);
|
|
55
96
|
console.log('');
|
|
97
|
+
// Check if packs need to be installed in the Studio workspace
|
|
98
|
+
if (loadedPacks.length > 0) {
|
|
99
|
+
try {
|
|
100
|
+
const checkRes = await fetch(`${creds.platformUrl}/devices/check-packs`, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${creds.token}` },
|
|
103
|
+
body: JSON.stringify({ packs: loadedPacks }),
|
|
104
|
+
});
|
|
105
|
+
if (checkRes.ok) {
|
|
106
|
+
const { missing } = await checkRes.json();
|
|
107
|
+
if (missing.length > 0) {
|
|
108
|
+
console.log(' The following packs need to be installed in Studio:');
|
|
109
|
+
for (const p of missing) {
|
|
110
|
+
console.log(` \x1b[36m${p}\x1b[0m`);
|
|
111
|
+
}
|
|
112
|
+
console.log('');
|
|
113
|
+
const answer = await promptYesNo(' Install now? (Y/n) ');
|
|
114
|
+
if (answer) {
|
|
115
|
+
process.stderr.write(' Installing...');
|
|
116
|
+
const installRes = await fetch(`${creds.platformUrl}/devices/install-packs`, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${creds.token}` },
|
|
119
|
+
body: JSON.stringify({ packs: missing }),
|
|
120
|
+
});
|
|
121
|
+
if (installRes.ok) {
|
|
122
|
+
const { results } = await installRes.json();
|
|
123
|
+
const allOk = results.every(r => r.ok);
|
|
124
|
+
if (allOk) {
|
|
125
|
+
process.stderr.write(' \x1b[32m✓\x1b[0m\n\n');
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
process.stderr.write(' \x1b[33mpartial\x1b[0m\n');
|
|
129
|
+
for (const r of results) {
|
|
130
|
+
if (!r.ok)
|
|
131
|
+
console.log(` \x1b[31m✗\x1b[0m ${r.pack}: ${r.error}`);
|
|
132
|
+
}
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
process.stderr.write(' \x1b[31mfailed\x1b[0m\n\n');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.log(' \x1b[2mSkipped. Install manually via Studio marketplace.\x1b[0m\n');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Check failed — non-fatal, continue connecting
|
|
148
|
+
}
|
|
149
|
+
}
|
|
56
150
|
try {
|
|
57
151
|
await conn.connect();
|
|
58
152
|
console.log(' \x1b[2mPress Ctrl+C to disconnect.\x1b[0m\n');
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -9886,7 +9886,7 @@ var VERSION;
|
|
|
9886
9886
|
var init_generated_version = __esm({
|
|
9887
9887
|
"src/generated-version.ts"() {
|
|
9888
9888
|
"use strict";
|
|
9889
|
-
VERSION = "0.23.
|
|
9889
|
+
VERSION = "0.23.3";
|
|
9890
9890
|
}
|
|
9891
9891
|
});
|
|
9892
9892
|
|
|
@@ -35517,6 +35517,7 @@ var init_tag_registry = __esm({
|
|
|
35517
35517
|
// src/marketplace/registry.ts
|
|
35518
35518
|
var registry_exports = {};
|
|
35519
35519
|
__export(registry_exports, {
|
|
35520
|
+
discoverDeviceHandlers: () => discoverDeviceHandlers,
|
|
35520
35521
|
discoverDocTopics: () => discoverDocTopics,
|
|
35521
35522
|
discoverInitContributions: () => discoverInitContributions,
|
|
35522
35523
|
discoverTagHandlers: () => discoverTagHandlers,
|
|
@@ -35655,6 +35656,20 @@ async function discoverInitContributions(projectDir) {
|
|
|
35655
35656
|
}
|
|
35656
35657
|
return contributions;
|
|
35657
35658
|
}
|
|
35659
|
+
async function discoverDeviceHandlers(projectDir) {
|
|
35660
|
+
const packages = await listInstalledPackages(projectDir);
|
|
35661
|
+
const handlers = [];
|
|
35662
|
+
for (const pkg of packages) {
|
|
35663
|
+
const manifest = pkg.manifest;
|
|
35664
|
+
if (!manifest.deviceHandlers) continue;
|
|
35665
|
+
handlers.push({
|
|
35666
|
+
packageName: pkg.name,
|
|
35667
|
+
packagePath: pkg.path,
|
|
35668
|
+
entrypoint: path5.join(pkg.path, manifest.deviceHandlers)
|
|
35669
|
+
});
|
|
35670
|
+
}
|
|
35671
|
+
return handlers;
|
|
35672
|
+
}
|
|
35658
35673
|
var MARKETPLACE_KEYWORD, NPM_SEARCH_URL, PACK_NAME_RE;
|
|
35659
35674
|
var init_registry = __esm({
|
|
35660
35675
|
"src/marketplace/registry.ts"() {
|
|
@@ -95201,8 +95216,9 @@ __export(auth_exports, {
|
|
|
95201
95216
|
import * as readline9 from "node:readline";
|
|
95202
95217
|
async function loginCommand(options) {
|
|
95203
95218
|
const platformUrl = options.platformUrl ?? getPlatformUrl();
|
|
95219
|
+
const displayUrl = platformUrl.replace(/^https?:\/\//, "");
|
|
95204
95220
|
console.log("");
|
|
95205
|
-
console.log(
|
|
95221
|
+
console.log(` \x1B[1mFlow Weaver\x1B[0m \x1B[2m(${displayUrl})\x1B[0m`);
|
|
95206
95222
|
console.log("");
|
|
95207
95223
|
if (options.apiKey) {
|
|
95208
95224
|
await loginWithApiKey(options.apiKey, platformUrl);
|
|
@@ -95234,8 +95250,8 @@ async function loginWithBrowser(platformUrl) {
|
|
|
95234
95250
|
verificationUrl = data.verificationUrl;
|
|
95235
95251
|
interval = data.interval ?? 5;
|
|
95236
95252
|
} catch {
|
|
95237
|
-
console.error(
|
|
95238
|
-
console.error(" Check
|
|
95253
|
+
console.error(` \x1B[31m\u2717\x1B[0m Cannot connect to ${platformUrl}`);
|
|
95254
|
+
console.error(" Check the URL or set FW_PLATFORM_URL");
|
|
95239
95255
|
process.exit(1);
|
|
95240
95256
|
return;
|
|
95241
95257
|
}
|
|
@@ -95252,21 +95268,26 @@ async function loginWithBrowser(platformUrl) {
|
|
|
95252
95268
|
console.log(` \x1B[36m${authUrl}\x1B[0m`);
|
|
95253
95269
|
}
|
|
95254
95270
|
console.log("");
|
|
95255
|
-
process.
|
|
95271
|
+
process.stderr.write(" Waiting for approval");
|
|
95256
95272
|
let cancelled = false;
|
|
95257
95273
|
const sigHandler = () => {
|
|
95258
95274
|
cancelled = true;
|
|
95259
95275
|
};
|
|
95260
95276
|
process.on("SIGINT", sigHandler);
|
|
95261
95277
|
const maxAttempts = 120;
|
|
95278
|
+
let networkErrors = 0;
|
|
95262
95279
|
for (let i = 0; i < maxAttempts && !cancelled; i++) {
|
|
95263
95280
|
await new Promise((r) => setTimeout(r, interval * 1e3));
|
|
95264
95281
|
try {
|
|
95265
95282
|
const resp = await fetch(`${platformUrl}/auth/device/poll?deviceCode=${deviceCode}`);
|
|
95266
|
-
|
|
95283
|
+
networkErrors = 0;
|
|
95284
|
+
if (!resp.ok) {
|
|
95285
|
+
process.stderr.write(".");
|
|
95286
|
+
continue;
|
|
95287
|
+
}
|
|
95267
95288
|
const data = await resp.json();
|
|
95268
95289
|
if (data.status === "approved" && data.token && data.user) {
|
|
95269
|
-
process.
|
|
95290
|
+
process.stderr.write(" \x1B[32m\u2713\x1B[0m\n\n");
|
|
95270
95291
|
saveCredentials({
|
|
95271
95292
|
token: data.token,
|
|
95272
95293
|
email: data.user.email,
|
|
@@ -95283,7 +95304,7 @@ async function loginWithBrowser(platformUrl) {
|
|
|
95283
95304
|
return;
|
|
95284
95305
|
}
|
|
95285
95306
|
if (data.status === "expired") {
|
|
95286
|
-
process.
|
|
95307
|
+
process.stderr.write(" \x1B[31mtimed out\x1B[0m\n\n");
|
|
95287
95308
|
console.log(" Code expired. Run \x1B[36mfw login\x1B[0m again.");
|
|
95288
95309
|
console.log("");
|
|
95289
95310
|
process.removeListener("SIGINT", sigHandler);
|
|
@@ -95291,22 +95312,27 @@ async function loginWithBrowser(platformUrl) {
|
|
|
95291
95312
|
return;
|
|
95292
95313
|
}
|
|
95293
95314
|
if (data.status === "denied") {
|
|
95294
|
-
process.
|
|
95315
|
+
process.stderr.write(" \x1B[31mdenied\x1B[0m\n\n");
|
|
95295
95316
|
console.log(" Access denied.");
|
|
95296
95317
|
console.log("");
|
|
95297
95318
|
process.removeListener("SIGINT", sigHandler);
|
|
95298
95319
|
process.exit(1);
|
|
95299
95320
|
return;
|
|
95300
95321
|
}
|
|
95301
|
-
|
|
95322
|
+
process.stderr.write(".");
|
|
95302
95323
|
} catch {
|
|
95324
|
+
networkErrors++;
|
|
95325
|
+
if (networkErrors >= 3) {
|
|
95326
|
+
process.stderr.write(" \x1B[33m!\x1B[0m");
|
|
95327
|
+
networkErrors = 0;
|
|
95328
|
+
}
|
|
95303
95329
|
}
|
|
95304
95330
|
}
|
|
95305
95331
|
process.removeListener("SIGINT", sigHandler);
|
|
95306
95332
|
if (cancelled) {
|
|
95307
|
-
process.
|
|
95333
|
+
process.stderr.write(" \x1B[33mcancelled\x1B[0m\n\n");
|
|
95308
95334
|
} else {
|
|
95309
|
-
process.
|
|
95335
|
+
process.stderr.write(" \x1B[31mtimed out\x1B[0m\n\n");
|
|
95310
95336
|
console.log(" Authentication timed out. Run \x1B[36mfw login\x1B[0m again.");
|
|
95311
95337
|
console.log("");
|
|
95312
95338
|
}
|
|
@@ -95392,21 +95418,22 @@ async function authStatusCommand() {
|
|
|
95392
95418
|
console.log("");
|
|
95393
95419
|
}
|
|
95394
95420
|
function prompt(message, hidden = false) {
|
|
95395
|
-
|
|
95396
|
-
|
|
95397
|
-
if (hidden && process.stdin.isTTY) {
|
|
95421
|
+
if (hidden && process.stdin.isTTY) {
|
|
95422
|
+
return new Promise((resolve38) => {
|
|
95398
95423
|
process.stderr.write(message);
|
|
95399
95424
|
process.stdin.setRawMode(true);
|
|
95425
|
+
process.stdin.resume();
|
|
95400
95426
|
let input = "";
|
|
95401
95427
|
const handler = (key) => {
|
|
95402
95428
|
const ch = key.toString();
|
|
95403
95429
|
if (ch === "\r" || ch === "\n") {
|
|
95404
95430
|
process.stdin.setRawMode(false);
|
|
95431
|
+
process.stdin.pause();
|
|
95405
95432
|
process.stdin.removeListener("data", handler);
|
|
95406
95433
|
process.stderr.write("\n");
|
|
95407
|
-
rl.close();
|
|
95408
95434
|
resolve38(input);
|
|
95409
95435
|
} else if (ch === "") {
|
|
95436
|
+
process.stdin.setRawMode(false);
|
|
95410
95437
|
process.exit(1);
|
|
95411
95438
|
} else if (ch === "\x7F") {
|
|
95412
95439
|
input = input.slice(0, -1);
|
|
@@ -95415,12 +95442,14 @@ function prompt(message, hidden = false) {
|
|
|
95415
95442
|
}
|
|
95416
95443
|
};
|
|
95417
95444
|
process.stdin.on("data", handler);
|
|
95418
|
-
}
|
|
95419
|
-
|
|
95420
|
-
|
|
95421
|
-
|
|
95422
|
-
|
|
95423
|
-
|
|
95445
|
+
});
|
|
95446
|
+
}
|
|
95447
|
+
return new Promise((resolve38) => {
|
|
95448
|
+
const rl = readline9.createInterface({ input: process.stdin, output: process.stderr });
|
|
95449
|
+
rl.question(message, (answer) => {
|
|
95450
|
+
rl.close();
|
|
95451
|
+
resolve38(answer);
|
|
95452
|
+
});
|
|
95424
95453
|
});
|
|
95425
95454
|
}
|
|
95426
95455
|
var init_auth = __esm({
|
|
@@ -95572,6 +95601,13 @@ var init_device_connection = __esm({
|
|
|
95572
95601
|
this.deviceInfo.capabilities.push(capability);
|
|
95573
95602
|
}
|
|
95574
95603
|
}
|
|
95604
|
+
/**
|
|
95605
|
+
* Set the list of packs that contributed device handlers.
|
|
95606
|
+
* The platform uses this to auto-install packs in the user's workspace.
|
|
95607
|
+
*/
|
|
95608
|
+
setPacks(packs) {
|
|
95609
|
+
this.deviceInfo.packs = packs;
|
|
95610
|
+
}
|
|
95575
95611
|
/**
|
|
95576
95612
|
* Register a handler for incoming requests from the platform.
|
|
95577
95613
|
*/
|
|
@@ -95700,11 +95736,45 @@ var init_device_connection = __esm({
|
|
|
95700
95736
|
// src/cli/commands/connect.ts
|
|
95701
95737
|
var connect_exports = {};
|
|
95702
95738
|
__export(connect_exports, {
|
|
95703
|
-
handleConnect: () => handleConnect
|
|
95739
|
+
handleConnect: () => handleConnect,
|
|
95740
|
+
loadPackDeviceHandlers: () => loadPackDeviceHandlers
|
|
95704
95741
|
});
|
|
95705
95742
|
import * as fs49 from "node:fs";
|
|
95706
95743
|
import * as path52 from "node:path";
|
|
95707
95744
|
import * as os4 from "node:os";
|
|
95745
|
+
import * as readline10 from "node:readline";
|
|
95746
|
+
function promptYesNo(message) {
|
|
95747
|
+
return new Promise((resolve38) => {
|
|
95748
|
+
const rl = readline10.createInterface({ input: process.stdin, output: process.stderr });
|
|
95749
|
+
rl.question(message, (answer) => {
|
|
95750
|
+
rl.close();
|
|
95751
|
+
const normalized = answer.trim().toLowerCase();
|
|
95752
|
+
resolve38(normalized === "" || normalized === "y" || normalized === "yes");
|
|
95753
|
+
});
|
|
95754
|
+
});
|
|
95755
|
+
}
|
|
95756
|
+
async function loadPackDeviceHandlers(conn, projectDir) {
|
|
95757
|
+
const loadedPacks = [];
|
|
95758
|
+
try {
|
|
95759
|
+
const handlers = await discoverDeviceHandlers(projectDir);
|
|
95760
|
+
for (const handler of handlers) {
|
|
95761
|
+
try {
|
|
95762
|
+
const mod = await import(handler.entrypoint);
|
|
95763
|
+
if (typeof mod.register === "function") {
|
|
95764
|
+
await mod.register(conn, { projectDir });
|
|
95765
|
+
loadedPacks.push(handler.packageName);
|
|
95766
|
+
process.stderr.write(` \x1B[2m+ ${handler.packageName} handlers\x1B[0m
|
|
95767
|
+
`);
|
|
95768
|
+
}
|
|
95769
|
+
} catch (err) {
|
|
95770
|
+
process.stderr.write(` \x1B[33m\u26A0\x1B[0m Failed to load handlers from ${handler.packageName}: ${err instanceof Error ? err.message : err}
|
|
95771
|
+
`);
|
|
95772
|
+
}
|
|
95773
|
+
}
|
|
95774
|
+
} catch {
|
|
95775
|
+
}
|
|
95776
|
+
return loadedPacks;
|
|
95777
|
+
}
|
|
95708
95778
|
async function handleConnect(projectDir) {
|
|
95709
95779
|
const credPath = path52.join(os4.homedir(), ".fw", "credentials.json");
|
|
95710
95780
|
if (!fs49.existsSync(credPath)) {
|
|
@@ -95740,13 +95810,63 @@ async function handleConnect(projectDir) {
|
|
|
95740
95810
|
if (!dirPath.startsWith(projectDir)) throw new Error("Path outside project directory");
|
|
95741
95811
|
if (!fs49.existsSync(dirPath)) throw new Error("Directory not found");
|
|
95742
95812
|
const entries = fs49.readdirSync(dirPath, { withFileTypes: true });
|
|
95743
|
-
return entries.filter((e) => !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist").map((e) => ({ name: e.name, type: e.isDirectory() ? "directory" : "file", path: path52.relative(projectDir, path52.join(dirPath, e.name)) }));
|
|
95813
|
+
return entries.filter((e) => !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist").map((e) => ({ name: e.name, type: e.isDirectory() ? "directory" : "file", path: path52.relative(projectDir, path52.join(dirPath, e.name)), hasUnfetchedChildren: e.isDirectory() }));
|
|
95744
95814
|
});
|
|
95815
|
+
const loadedPacks = await loadPackDeviceHandlers(conn, projectDir);
|
|
95816
|
+
if (loadedPacks.length > 0) {
|
|
95817
|
+
conn.setPacks(loadedPacks);
|
|
95818
|
+
}
|
|
95745
95819
|
console.log("");
|
|
95746
95820
|
console.log(" \x1B[1mflow-weaver connect\x1B[0m");
|
|
95747
95821
|
console.log(` \x1B[2mProject: ${path52.basename(projectDir)}\x1B[0m`);
|
|
95748
95822
|
console.log(` \x1B[2mPlatform: ${creds.platformUrl}\x1B[0m`);
|
|
95749
95823
|
console.log("");
|
|
95824
|
+
if (loadedPacks.length > 0) {
|
|
95825
|
+
try {
|
|
95826
|
+
const checkRes = await fetch(`${creds.platformUrl}/devices/check-packs`, {
|
|
95827
|
+
method: "POST",
|
|
95828
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${creds.token}` },
|
|
95829
|
+
body: JSON.stringify({ packs: loadedPacks })
|
|
95830
|
+
});
|
|
95831
|
+
if (checkRes.ok) {
|
|
95832
|
+
const { missing } = await checkRes.json();
|
|
95833
|
+
if (missing.length > 0) {
|
|
95834
|
+
console.log(" The following packs need to be installed in Studio:");
|
|
95835
|
+
for (const p of missing) {
|
|
95836
|
+
console.log(` \x1B[36m${p}\x1B[0m`);
|
|
95837
|
+
}
|
|
95838
|
+
console.log("");
|
|
95839
|
+
const answer = await promptYesNo(" Install now? (Y/n) ");
|
|
95840
|
+
if (answer) {
|
|
95841
|
+
process.stderr.write(" Installing...");
|
|
95842
|
+
const installRes = await fetch(`${creds.platformUrl}/devices/install-packs`, {
|
|
95843
|
+
method: "POST",
|
|
95844
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${creds.token}` },
|
|
95845
|
+
body: JSON.stringify({ packs: missing })
|
|
95846
|
+
});
|
|
95847
|
+
if (installRes.ok) {
|
|
95848
|
+
const { results } = await installRes.json();
|
|
95849
|
+
const allOk = results.every((r) => r.ok);
|
|
95850
|
+
if (allOk) {
|
|
95851
|
+
process.stderr.write(" \x1B[32m\u2713\x1B[0m\n\n");
|
|
95852
|
+
} else {
|
|
95853
|
+
process.stderr.write(" \x1B[33mpartial\x1B[0m\n");
|
|
95854
|
+
for (const r of results) {
|
|
95855
|
+
if (!r.ok) console.log(` \x1B[31m\u2717\x1B[0m ${r.pack}: ${r.error}`);
|
|
95856
|
+
}
|
|
95857
|
+
console.log("");
|
|
95858
|
+
}
|
|
95859
|
+
} else {
|
|
95860
|
+
process.stderr.write(" \x1B[31mfailed\x1B[0m\n\n");
|
|
95861
|
+
}
|
|
95862
|
+
} else {
|
|
95863
|
+
console.log(" \x1B[2mSkipped. Install manually via Studio marketplace.\x1B[0m\n");
|
|
95864
|
+
}
|
|
95865
|
+
}
|
|
95866
|
+
}
|
|
95867
|
+
} catch {
|
|
95868
|
+
}
|
|
95869
|
+
}
|
|
95750
95870
|
try {
|
|
95751
95871
|
await conn.connect();
|
|
95752
95872
|
console.log(" \x1B[2mPress Ctrl+C to disconnect.\x1B[0m\n");
|
|
@@ -95770,6 +95890,7 @@ var init_connect = __esm({
|
|
|
95770
95890
|
"src/cli/commands/connect.ts"() {
|
|
95771
95891
|
"use strict";
|
|
95772
95892
|
init_device_connection();
|
|
95893
|
+
init_registry();
|
|
95773
95894
|
}
|
|
95774
95895
|
});
|
|
95775
95896
|
|
|
@@ -95802,7 +95923,7 @@ var {
|
|
|
95802
95923
|
// src/cli/index.ts
|
|
95803
95924
|
init_logger();
|
|
95804
95925
|
init_error_utils();
|
|
95805
|
-
var version2 = true ? "0.23.
|
|
95926
|
+
var version2 = true ? "0.23.3" : "0.0.0-dev";
|
|
95806
95927
|
var program2 = new Command();
|
|
95807
95928
|
program2.name("fw").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
|
|
95808
95929
|
logger.banner(version2);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.23.
|
|
1
|
+
export declare const VERSION = "0.23.3";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
export type { TMarketplaceManifest, TManifestNodeType, TManifestWorkflow, TManifestPattern, TManifestExportTarget, TManifestPort, TValidationIssue, TValidationSeverity, TPackageValidationResult, TMarketplacePackageInfo, TInstalledPackage, TMarketInitConfig, TManifestTagHandler, TManifestValidationRuleSet, TManifestDocTopic, TManifestInitContribution, TManifestCliCommand, TManifestMcpTool, } from './types.js';
|
|
8
8
|
export { generateManifest, writeManifest, readManifest, type GenerateManifestOptions, type GenerateManifestResult, } from './manifest.js';
|
|
9
9
|
export { validatePackage } from './validator.js';
|
|
10
|
-
export { searchPackages, listInstalledPackages, getInstalledPackageManifest, discoverTagHandlers, discoverValidationRuleSets, discoverDocTopics, discoverInitContributions, type SearchOptions, type TDiscoveredTagHandler, type TDiscoveredValidationRuleSet, type TDiscoveredDocTopic, type TDiscoveredInitContribution, } from './registry.js';
|
|
10
|
+
export { searchPackages, listInstalledPackages, getInstalledPackageManifest, discoverTagHandlers, discoverValidationRuleSets, discoverDocTopics, discoverInitContributions, discoverDeviceHandlers, type SearchOptions, type TDiscoveredTagHandler, type TDiscoveredValidationRuleSet, type TDiscoveredDocTopic, type TDiscoveredInitContribution, type TDiscoveredDeviceHandler, } from './registry.js';
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { generateManifest, writeManifest, readManifest, } from './manifest.js';
|
|
8
8
|
export { validatePackage } from './validator.js';
|
|
9
|
-
export { searchPackages, listInstalledPackages, getInstalledPackageManifest, discoverTagHandlers, discoverValidationRuleSets, discoverDocTopics, discoverInitContributions, } from './registry.js';
|
|
9
|
+
export { searchPackages, listInstalledPackages, getInstalledPackageManifest, discoverTagHandlers, discoverValidationRuleSets, discoverDocTopics, discoverInitContributions, discoverDeviceHandlers, } from './registry.js';
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -49,6 +49,15 @@ export type TDiscoveredInitContribution = TManifestInitContribution & {
|
|
|
49
49
|
/** Package name this contribution belongs to */
|
|
50
50
|
packageName: string;
|
|
51
51
|
};
|
|
52
|
+
/** A device handler entry point discovered from an installed pack manifest. */
|
|
53
|
+
export type TDiscoveredDeviceHandler = {
|
|
54
|
+
/** npm package name */
|
|
55
|
+
packageName: string;
|
|
56
|
+
/** Absolute path to the installed package */
|
|
57
|
+
packagePath: string;
|
|
58
|
+
/** Absolute path to the device handler entrypoint module */
|
|
59
|
+
entrypoint: string;
|
|
60
|
+
};
|
|
52
61
|
/**
|
|
53
62
|
* Discover all tag handlers from installed pack manifests.
|
|
54
63
|
*/
|
|
@@ -65,4 +74,11 @@ export declare function discoverDocTopics(projectDir: string): Promise<TDiscover
|
|
|
65
74
|
* Discover all init contributions from installed pack manifests.
|
|
66
75
|
*/
|
|
67
76
|
export declare function discoverInitContributions(projectDir: string): Promise<TDiscoveredInitContribution[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Discover all device handler entry points from installed pack manifests.
|
|
79
|
+
*
|
|
80
|
+
* Returns packs that declare a `deviceHandlers` field in their manifest,
|
|
81
|
+
* resolved to an absolute entrypoint path.
|
|
82
|
+
*/
|
|
83
|
+
export declare function discoverDeviceHandlers(projectDir: string): Promise<TDiscoveredDeviceHandler[]>;
|
|
68
84
|
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -174,4 +174,25 @@ export async function discoverInitContributions(projectDir) {
|
|
|
174
174
|
}
|
|
175
175
|
return contributions;
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Discover all device handler entry points from installed pack manifests.
|
|
179
|
+
*
|
|
180
|
+
* Returns packs that declare a `deviceHandlers` field in their manifest,
|
|
181
|
+
* resolved to an absolute entrypoint path.
|
|
182
|
+
*/
|
|
183
|
+
export async function discoverDeviceHandlers(projectDir) {
|
|
184
|
+
const packages = await listInstalledPackages(projectDir);
|
|
185
|
+
const handlers = [];
|
|
186
|
+
for (const pkg of packages) {
|
|
187
|
+
const manifest = pkg.manifest;
|
|
188
|
+
if (!manifest.deviceHandlers)
|
|
189
|
+
continue;
|
|
190
|
+
handlers.push({
|
|
191
|
+
packageName: pkg.name,
|
|
192
|
+
packagePath: pkg.path,
|
|
193
|
+
entrypoint: path.join(pkg.path, manifest.deviceHandlers),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return handlers;
|
|
197
|
+
}
|
|
177
198
|
//# sourceMappingURL=registry.js.map
|
|
@@ -44,6 +44,8 @@ export type TMarketplaceManifest = {
|
|
|
44
44
|
mcpEntrypoint?: string;
|
|
45
45
|
/** MCP tools contributed by this pack */
|
|
46
46
|
mcpTools?: TManifestMcpTool[];
|
|
47
|
+
/** Entry point for device connection handler registration */
|
|
48
|
+
deviceHandlers?: string;
|
|
47
49
|
/** External dependency information */
|
|
48
50
|
dependencies?: {
|
|
49
51
|
/** Flow Weaver peer dependency constraints */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synergenius/flow-weaver",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.3",
|
|
4
4
|
"description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"typecheck": "tsc --noEmit -p tsconfig.build.json",
|
|
130
130
|
"docs": "typedoc && tsx scripts/generate-grammar-docs.ts",
|
|
131
131
|
"docs:serve": "npm run docs && npx http-server docs/api -c-1 -o",
|
|
132
|
-
"prepare": "npm run build",
|
|
132
|
+
"prepare": "npm run build && husky || true",
|
|
133
133
|
"cli": "tsx src/cli/index.ts",
|
|
134
134
|
"diagram": "tsx scripts/generate-diagram.ts"
|
|
135
135
|
},
|
|
@@ -179,6 +179,7 @@
|
|
|
179
179
|
"@types/react": "^19.0.0",
|
|
180
180
|
"@types/ws": "^8.18.1",
|
|
181
181
|
"@vitest/coverage-v8": "^4.0.18",
|
|
182
|
+
"husky": "^9.1.7",
|
|
182
183
|
"prettier": "^3.1.1",
|
|
183
184
|
"rimraf": "6.1.2",
|
|
184
185
|
"ts-node": "^10.9.2",
|