@mudlab/create-workflow 1.0.0

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.
@@ -0,0 +1,49 @@
1
+ # My Mudlab Workflows
2
+
3
+ Automated workflows powered by [Mudlab](https://mudlab.io).
4
+
5
+ ## Setup
6
+
7
+ 1. Copy `.env.example` to `.env` and add your API keys:
8
+ ```bash
9
+ cp .env.example .env
10
+ ```
11
+
12
+ 2. Edit `.env` with your Mudlab API key and any integration keys you need.
13
+
14
+ ## Creating Workflows
15
+
16
+ 1. **Add a tool** in `execution/tools/my_tool.js`
17
+ 2. **Add a workflow** in `execution/workflows/my_workflow.json`
18
+ 3. **Add a directive** in `directives/my_workflow.md`
19
+
20
+ See `CLAUDE.md` for detailed documentation on the 3-layer architecture.
21
+
22
+ ## Push to Mudlab
23
+
24
+ ```bash
25
+ # Single workflow
26
+ node execution/push_workflow.js execution/workflows/my_workflow.json
27
+
28
+ # All workflows
29
+ npm run push:all
30
+ ```
31
+
32
+ ## Directory Structure
33
+
34
+ ```
35
+ ├── CLAUDE.md # AI orchestrator instructions
36
+ ├── directives/ # Workflow documentation (SOPs)
37
+ ├── execution/
38
+ │ ├── push_workflow.js # Push script
39
+ │ ├── tools/ # Node.js tool functions
40
+ │ ├── tools_directives/ # AI usage instructions
41
+ │ └── workflows/ # Workflow packages (JSON)
42
+ ├── assets/ # Static assets
43
+ └── .tmp/ # Temp files (gitignored)
44
+ ```
45
+
46
+ ## Learn More
47
+
48
+ - [Mudlab Documentation](https://mudlab.io/docs)
49
+ - See `CLAUDE.md` for the complete 3-layer architecture guide
@@ -0,0 +1,3 @@
1
+ # Assets
2
+
3
+ Place static assets here (logos, templates, etc.)
@@ -0,0 +1,12 @@
1
+ # Workflow Directives
2
+
3
+ Place your workflow directive markdown files here.
4
+
5
+ Each workflow needs a directive file that documents:
6
+ - What the workflow does
7
+ - Workflow scope (what it IS and IS NOT for)
8
+ - Trigger format (webhook URL and payload)
9
+ - Input/output specifications
10
+ - Example usage
11
+
12
+ See CLAUDE.md for the full directive structure template.
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Push Workflow to Mudlab
5
+ *
6
+ * Takes one or more workflow packages (tools + workflow definition) and pushes
7
+ * everything to the Mudlab API. Automatically handles create vs update
8
+ * based on existing IDs, and writes returned IDs back to the JSON files.
9
+ *
10
+ * Usage:
11
+ * Single: node execution/push_workflow.js execution/workflows/my_workflow.json
12
+ * Bulk: node execution/push_workflow.js execution/workflows/*.json
13
+ *
14
+ * When multiple files are provided, they are pushed in a single bulk API call
15
+ * for better performance.
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ // Load environment variables from .env
22
+ function loadEnv() {
23
+ const envPath = path.join(process.cwd(), '.env');
24
+ if (fs.existsSync(envPath)) {
25
+ const envContent = fs.readFileSync(envPath, 'utf-8');
26
+ for (const line of envContent.split('\n')) {
27
+ const trimmed = line.trim();
28
+ if (trimmed && !trimmed.startsWith('#')) {
29
+ const [key, ...valueParts] = trimmed.split('=');
30
+ const value = valueParts.join('=').replace(/^["']|["']$/g, '');
31
+ process.env[key] = value;
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ loadEnv();
38
+
39
+ const MUDLAB_API_URL = process.env.MUDLAB_API_URL || 'http://localhost:3000';
40
+ const MUDLAB_API_KEY = process.env.MUDLAB_API_KEY;
41
+
42
+ function getHeaders() {
43
+ return {
44
+ 'Content-Type': 'application/json',
45
+ ...(MUDLAB_API_KEY && { 'X-API-Key': MUDLAB_API_KEY })
46
+ };
47
+ }
48
+
49
+
50
+ /**
51
+ * Write IDs back to the workflow JSON file
52
+ */
53
+ function updateWorkflowFile(workflowPath, workflowId, toolIds) {
54
+ const workflowPackage = JSON.parse(fs.readFileSync(workflowPath, 'utf-8'));
55
+
56
+ // Update workflow ID
57
+ workflowPackage.id = workflowId;
58
+
59
+ // Update tool IDs
60
+ for (const tool of workflowPackage.tools) {
61
+ if (toolIds[tool.tool_id]) {
62
+ tool.id = toolIds[tool.tool_id];
63
+ }
64
+ }
65
+
66
+ fs.writeFileSync(workflowPath, JSON.stringify(workflowPackage, null, 2) + '\n');
67
+ }
68
+
69
+ function savePushLog(workflowResult, toolResults) {
70
+ const tmpDir = path.join(process.cwd(), '.tmp');
71
+ if (!fs.existsSync(tmpDir)) {
72
+ fs.mkdirSync(tmpDir, { recursive: true });
73
+ }
74
+
75
+ const logPath = path.join(tmpDir, 'pushed_workflows.json');
76
+ let log = [];
77
+ if (fs.existsSync(logPath)) {
78
+ log = JSON.parse(fs.readFileSync(logPath, 'utf-8'));
79
+ }
80
+
81
+ log.push({
82
+ pushed_at: new Date().toISOString(),
83
+ workflow: workflowResult,
84
+ tools: toolResults
85
+ });
86
+
87
+ fs.writeFileSync(logPath, JSON.stringify(log, null, 2));
88
+ }
89
+
90
+ /**
91
+ * Prepare a single workflow payload from package and directive
92
+ */
93
+ function prepareWorkflowPayload(workflowPackage, directivePath) {
94
+ // Read directive
95
+ const directive = fs.readFileSync(directivePath, 'utf-8');
96
+
97
+ // Prepare tools with code and directives loaded from files
98
+ const tools = workflowPackage.tools.map(tool => {
99
+ let code = tool.code;
100
+ if (tool.code_file) {
101
+ const codePath = path.join(process.cwd(), tool.code_file);
102
+ if (!fs.existsSync(codePath)) {
103
+ throw new Error(`Tool code file not found: ${tool.code_file}`);
104
+ }
105
+ code = fs.readFileSync(codePath, 'utf-8');
106
+ }
107
+
108
+ // Read tool directive from file if specified
109
+ let toolDirective = tool.directive || null;
110
+ if (tool.directive_file) {
111
+ const toolDirectivePath = path.join(process.cwd(), tool.directive_file);
112
+ if (fs.existsSync(toolDirectivePath)) {
113
+ toolDirective = fs.readFileSync(toolDirectivePath, 'utf-8');
114
+ console.log(` 📝 Loaded directive for ${tool.tool_id} (${toolDirective.length} chars)`);
115
+ } else {
116
+ console.log(` ⚠️ Directive file not found: ${tool.directive_file}`);
117
+ }
118
+ }
119
+
120
+ return {
121
+ id: tool.id || null,
122
+ tool_id: tool.tool_id,
123
+ name: tool.name,
124
+ description: tool.description,
125
+ directive: toolDirective,
126
+ running_message: tool.running_message,
127
+ code: code,
128
+ inputs: tool.inputs,
129
+ outputs: tool.outputs
130
+ };
131
+ });
132
+
133
+ // Debug: show tools with directives
134
+ for (const tool of tools) {
135
+ if (tool.directive) {
136
+ console.log(` 📤 Sending directive for ${tool.tool_id}: "${tool.directive.substring(0, 50)}..."`);
137
+ }
138
+ }
139
+
140
+ return {
141
+ id: workflowPackage.id || null,
142
+ name: workflowPackage.name,
143
+ description: workflowPackage.description,
144
+ directive: directive,
145
+ running_message: workflowPackage.running_message,
146
+ success_message: workflowPackage.success_message,
147
+ client_id: workflowPackage.client_id,
148
+ steps: workflowPackage.steps,
149
+ tools: tools
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Push workflow(s) to the API
155
+ * @param {Array|Object} payloads - Single payload or array of payloads
156
+ * @returns {Object} API response (normalized)
157
+ *
158
+ * Bulk response format:
159
+ * {
160
+ * success: boolean, // true only if ALL succeeded
161
+ * results: [...], // all results (success and failure)
162
+ * summary: { total, succeeded, failed },
163
+ * workflows: [...], // convenience: only successes
164
+ * errors: [{ workflow_name, error, details }, ...] // convenience: only failures
165
+ * }
166
+ */
167
+ async function pushToApi(payloads) {
168
+ const isBulk = Array.isArray(payloads) && payloads.length > 1;
169
+ const body = isBulk ? payloads : (Array.isArray(payloads) ? payloads[0] : payloads);
170
+
171
+ const response = await fetch(`${MUDLAB_API_URL}/api/push-workflow`, {
172
+ method: 'POST',
173
+ headers: getHeaders(),
174
+ body: JSON.stringify(body)
175
+ });
176
+
177
+ // Handle different status codes
178
+ // 200 = all succeeded, 207 = partial success, 400/500 = error
179
+ if (!response.ok && response.status !== 207) {
180
+ const error = await response.text();
181
+ throw new Error(`Push failed (${response.status}): ${error}`);
182
+ }
183
+
184
+ const result = await response.json();
185
+
186
+ // Normalize response format
187
+ // Single: { success, workflow, tools }
188
+ // Bulk: { success, results, summary, workflows, errors }
189
+ if (isBulk) {
190
+ return { isBulk: true, ...result };
191
+ } else {
192
+ // Normalize single response to match bulk format
193
+ return {
194
+ isBulk: false,
195
+ results: [result],
196
+ summary: { total: 1, succeeded: result.success ? 1 : 0, failed: result.success ? 0 : 1 },
197
+ workflows: result.success ? [result] : [],
198
+ errors: result.success ? [] : [{ workflow_name: result.workflow?.name || 'Unknown', error: result.error, details: result.details }]
199
+ };
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Process a single workflow result and update its JSON file
205
+ */
206
+ function processWorkflowResult(result, workflowPath, workflowPackage) {
207
+ const workflowData = result.workflow;
208
+ // Tools are now nested inside workflow object
209
+ const toolResults = workflowData.tools || [];
210
+ const toolIdMap = {};
211
+
212
+ // Log tool results
213
+ console.log('🔧 Tools:\n');
214
+ for (const tool of toolResults) {
215
+ toolIdMap[tool.tool_id] = tool.id;
216
+ const action = tool.created ? 'Created' : 'Updated';
217
+ const toolDef = workflowPackage.tools.find(t => t.tool_id === tool.tool_id);
218
+ const toolName = tool.name || toolDef?.name || tool.tool_id;
219
+ const hasDirective = toolDef?.directive_file ? ' [has directive]' : '';
220
+ console.log(` ✓ ${toolName} (${action})${hasDirective}`);
221
+ console.log(` ID: ${tool.id}`);
222
+ console.log(` Version: ${tool.version}\n`);
223
+ }
224
+
225
+ // Log workflow result
226
+ const workflowAction = workflowData.created ? 'Created' : 'Updated';
227
+ console.log(`📋 Workflow: ${workflowPackage.name} (${workflowAction})`);
228
+ console.log(` ID: ${workflowData.id}`);
229
+ console.log(` Status: ${workflowData.status}`);
230
+ console.log(` Steps: ${workflowData.steps_count || workflowPackage.steps.length}\n`);
231
+
232
+ // Write IDs back to the JSON file
233
+ console.log('💾 Updating workflow file with IDs...');
234
+ updateWorkflowFile(workflowPath, workflowData.id, toolIdMap);
235
+ console.log(` ✓ ${workflowPath}\n`);
236
+
237
+ // Save push log
238
+ savePushLog(workflowData, toolResults);
239
+
240
+ return { workflowData, toolIdMap };
241
+ }
242
+
243
+ async function main() {
244
+ const args = process.argv.slice(2);
245
+
246
+ if (args.length === 0) {
247
+ console.error('Usage: node push_workflow.js <workflow-package.json> [workflow2.json ...]');
248
+ console.error(' node push_workflow.js execution/workflows/*.json');
249
+ process.exit(1);
250
+ }
251
+
252
+ // Validate all files exist first
253
+ const workflowPaths = [];
254
+ for (const arg of args) {
255
+ if (!fs.existsSync(arg)) {
256
+ console.error(`Workflow package not found: ${arg}`);
257
+ process.exit(1);
258
+ }
259
+ workflowPaths.push(arg);
260
+ }
261
+
262
+ if (!MUDLAB_API_KEY) {
263
+ console.warn('Warning: MUDLAB_API_KEY not set. API calls may fail.');
264
+ }
265
+
266
+ const isBulk = workflowPaths.length > 1;
267
+
268
+ if (isBulk) {
269
+ console.log(`\n📦 Pushing ${workflowPaths.length} workflow packages (bulk mode)`);
270
+ } else {
271
+ console.log(`\n📦 Pushing workflow package: ${workflowPaths[0]}`);
272
+ }
273
+ console.log(` API: ${MUDLAB_API_URL}\n`);
274
+
275
+ // Prepare all payloads
276
+ const payloads = [];
277
+ const workflowPackages = [];
278
+
279
+ for (const workflowPath of workflowPaths) {
280
+ console.log(`📄 Preparing: ${workflowPath}`);
281
+ const workflowPackage = JSON.parse(fs.readFileSync(workflowPath, 'utf-8'));
282
+ workflowPackages.push(workflowPackage);
283
+
284
+ // Validate directive file exists
285
+ if (!workflowPackage.directive_file) {
286
+ console.error(` Missing required field: directive_file in ${workflowPath}`);
287
+ process.exit(1);
288
+ }
289
+
290
+ const directivePath = path.join(process.cwd(), workflowPackage.directive_file);
291
+ if (!fs.existsSync(directivePath)) {
292
+ console.error(` Directive file not found: ${workflowPackage.directive_file}`);
293
+ process.exit(1);
294
+ }
295
+
296
+ console.log(` Directive: ${workflowPackage.directive_file}`);
297
+ const payload = prepareWorkflowPayload(workflowPackage, directivePath);
298
+ payloads.push(payload);
299
+ }
300
+
301
+ console.log('\n🔗 Pushing workflow(s) and tools...\n');
302
+
303
+ try {
304
+ const response = await pushToApi(payloads);
305
+ const { results, summary, errors } = response;
306
+
307
+ // Process each result
308
+ const allToolIds = {};
309
+ const allWorkflowIds = [];
310
+
311
+ for (let i = 0; i < results.length; i++) {
312
+ const result = results[i];
313
+ const workflowPath = workflowPaths[i];
314
+ const workflowPackage = workflowPackages[i];
315
+
316
+ if (isBulk) {
317
+ console.log('─'.repeat(50));
318
+ console.log(`\n[${i + 1}/${results.length}] ${workflowPackage.name}\n`);
319
+ }
320
+
321
+ if (!result.success) {
322
+ console.error(` ✗ Failed: ${result.error || 'Unknown error'}`);
323
+ if (result.details) {
324
+ console.error(` Details: ${result.details}`);
325
+ }
326
+ continue;
327
+ }
328
+
329
+ const { workflowData, toolIdMap } = processWorkflowResult(result, workflowPath, workflowPackage);
330
+ Object.assign(allToolIds, toolIdMap);
331
+ allWorkflowIds.push({ name: workflowPackage.name, id: workflowData.id });
332
+ }
333
+
334
+ // Summary
335
+ console.log('─'.repeat(50));
336
+
337
+ if (isBulk) {
338
+ console.log(`\n📊 Bulk Push Summary:`);
339
+ console.log(` Total: ${summary.total}`);
340
+ console.log(` Succeeded: ${summary.succeeded}`);
341
+ console.log(` Failed: ${summary.failed}`);
342
+
343
+ if (summary.failed > 0 && errors && errors.length > 0) {
344
+ console.log(`\n❌ Failed Workflows:\n`);
345
+ for (const err of errors) {
346
+ console.log(` ${err.workflow_name}:`);
347
+ console.log(` Error: ${err.error}`);
348
+ if (err.details) {
349
+ console.log(` Details: ${err.details}`);
350
+ }
351
+ }
352
+ console.log('');
353
+ } else if (summary.failed === 0) {
354
+ console.log(`\n✅ All workflows pushed successfully!\n`);
355
+ }
356
+
357
+ if (allWorkflowIds.length > 0) {
358
+ console.log('Workflow UUIDs:');
359
+ for (const { name, id } of allWorkflowIds) {
360
+ console.log(` ${name}: ${id}`);
361
+ }
362
+ }
363
+ } else {
364
+ if (summary.failed > 0) {
365
+ console.log('\n❌ Workflow push failed.\n');
366
+ } else {
367
+ console.log('\n✅ Workflow pushed successfully!\n');
368
+ console.log('Tool UUIDs:');
369
+ for (const [toolId, uuid] of Object.entries(allToolIds)) {
370
+ console.log(` ${toolId}: ${uuid}`);
371
+ }
372
+ const workflowId = allWorkflowIds[0]?.id;
373
+ console.log(`\nWorkflow UUID: ${workflowId}`);
374
+ console.log(`\nTo run this workflow:`);
375
+ console.log(` POST ${MUDLAB_API_URL}/api/run`);
376
+ console.log(` Body: { "workflow_id": "${workflowId}", "input": { ... } }`);
377
+ }
378
+ }
379
+
380
+ console.log(`\nPush log saved to: .tmp/pushed_workflows.json\n`);
381
+
382
+ // Exit with error if any failed
383
+ if (summary.failed > 0) {
384
+ process.exit(1);
385
+ }
386
+
387
+ } catch (error) {
388
+ console.error(` ✗ Push failed: ${error.message}`);
389
+ process.exit(1);
390
+ }
391
+ }
392
+
393
+ main().catch(error => {
394
+ console.error('Fatal error:', error.message);
395
+ process.exit(1);
396
+ });
@@ -0,0 +1,18 @@
1
+ # Tools
2
+
3
+ Place your Node.js tool files here.
4
+
5
+ Each tool is a module that exports a `run` function:
6
+
7
+ ```javascript
8
+ async function run({ input, env, context }) {
9
+ // Your logic here
10
+ return {
11
+ action: "completed",
12
+ message: "Human-readable success message",
13
+ // ... other output fields
14
+ };
15
+ }
16
+
17
+ module.exports = { run };
18
+ ```
@@ -0,0 +1,12 @@
1
+ # Tool Directives
2
+
3
+ Place your tool directive markdown files here.
4
+
5
+ Each tool directive provides AI usage instructions:
6
+ - Input constraints and validation rules
7
+ - Output details
8
+ - Formatting requirements
9
+ - Best practices
10
+ - Common mistakes to avoid
11
+
12
+ These are injected into the AI's context when the tool is used.
@@ -0,0 +1,10 @@
1
+ # Workflow Packages
2
+
3
+ Place your workflow JSON package files here.
4
+
5
+ Each workflow package defines:
6
+ - Workflow metadata (name, description, messages)
7
+ - Tools used by the workflow
8
+ - Steps that execute the tools in order
9
+
10
+ Run `node execution/push_workflow.js execution/workflows/your_workflow.json` to push.
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "my-mudlab-workflows",
3
+ "version": "1.0.0",
4
+ "description": "Mudlab workflow automation project",
5
+ "scripts": {
6
+ "push": "node execution/push_workflow.js",
7
+ "push:all": "node execution/push_workflow.js execution/workflows/*.json"
8
+ },
9
+ "keywords": ["mudlab", "workflow", "automation"],
10
+ "license": "MIT",
11
+ "dependencies": {}
12
+ }