@conductor-oss/conductor-skills 1.4.2

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 (46) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/LICENSE.txt +176 -0
  4. package/README.md +352 -0
  5. package/VERSION +1 -0
  6. package/bin/conductor-skills.js +135 -0
  7. package/commands/conductor-optimize.md +18 -0
  8. package/commands/conductor-scaffold-worker.md +19 -0
  9. package/commands/conductor-setup.md +15 -0
  10. package/commands/conductor.md +15 -0
  11. package/install.ps1 +677 -0
  12. package/install.sh +855 -0
  13. package/package.json +50 -0
  14. package/skills/conductor/SKILL.md +151 -0
  15. package/skills/conductor/examples/ai-agent-loop.md +119 -0
  16. package/skills/conductor/examples/ai-agent-mcp.md +129 -0
  17. package/skills/conductor/examples/create-and-run-workflow.md +50 -0
  18. package/skills/conductor/examples/do-while-loop.md +72 -0
  19. package/skills/conductor/examples/fork-join.md +52 -0
  20. package/skills/conductor/examples/llm-chat.md +61 -0
  21. package/skills/conductor/examples/llm-rag.md +115 -0
  22. package/skills/conductor/examples/monitor-and-retry.md +54 -0
  23. package/skills/conductor/examples/review-workflow.md +67 -0
  24. package/skills/conductor/examples/signal-wait-task.md +36 -0
  25. package/skills/conductor/examples/sub-workflow.md +52 -0
  26. package/skills/conductor/examples/workflows/ai-agent-loop.json +88 -0
  27. package/skills/conductor/examples/workflows/ai-agent-mcp.json +69 -0
  28. package/skills/conductor/examples/workflows/child-normalize.json +21 -0
  29. package/skills/conductor/examples/workflows/do-while-loop.json +35 -0
  30. package/skills/conductor/examples/workflows/fork-join.json +61 -0
  31. package/skills/conductor/examples/workflows/llm-chat.json +28 -0
  32. package/skills/conductor/examples/workflows/llm-rag.json +49 -0
  33. package/skills/conductor/examples/workflows/parent-pipeline.json +35 -0
  34. package/skills/conductor/examples/workflows/weather-notification.json +42 -0
  35. package/skills/conductor/references/api-reference.md +111 -0
  36. package/skills/conductor/references/cli-index.md +92 -0
  37. package/skills/conductor/references/fallback-cli.md +36 -0
  38. package/skills/conductor/references/optimization.md +148 -0
  39. package/skills/conductor/references/orkes.md +57 -0
  40. package/skills/conductor/references/schedules.md +81 -0
  41. package/skills/conductor/references/setup.md +126 -0
  42. package/skills/conductor/references/troubleshooting.md +35 -0
  43. package/skills/conductor/references/visualization.md +49 -0
  44. package/skills/conductor/references/workers.md +227 -0
  45. package/skills/conductor/references/workflow-definition.md +672 -0
  46. package/skills/conductor/scripts/conductor_api.py +396 -0
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env python3
2
+ """Conductor REST API fallback — stdlib only, no third-party packages.
3
+
4
+ Use when the `conductor` CLI is not installed.
5
+ Requires CONDUCTOR_SERVER_URL env var. CONDUCTOR_AUTH_TOKEN is optional.
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import os
11
+ import sys
12
+ import time
13
+ import urllib.error
14
+ import urllib.parse
15
+ import urllib.request
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Helpers
19
+ # ---------------------------------------------------------------------------
20
+
21
+ def get_config():
22
+ base = os.environ.get("CONDUCTOR_SERVER_URL", "").rstrip("/")
23
+ if not base:
24
+ print("Error: CONDUCTOR_SERVER_URL is not set.", file=sys.stderr)
25
+ sys.exit(1)
26
+ token = os.environ.get("CONDUCTOR_AUTH_TOKEN", "")
27
+ return base, token
28
+
29
+
30
+ def build_url(base, path, params=None):
31
+ url = f"{base}{path}"
32
+ if params:
33
+ qs = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
34
+ if qs:
35
+ url = f"{url}?{qs}"
36
+ return url
37
+
38
+
39
+ def request_json(url, token, method="GET", body=None, expect_json=True):
40
+ headers = {"Content-Type": "application/json", "Accept": "application/json"}
41
+ if token:
42
+ headers["X-Authorization"] = token
43
+
44
+ data = None
45
+ if body is not None:
46
+ data = json.dumps(body).encode() if not isinstance(body, bytes) else body
47
+
48
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
49
+
50
+ retries = 3
51
+ for attempt in range(retries):
52
+ try:
53
+ with urllib.request.urlopen(req, timeout=30) as resp:
54
+ raw = resp.read().decode()
55
+ if not raw:
56
+ return None
57
+ if expect_json:
58
+ return json.loads(raw)
59
+ return raw
60
+ except urllib.error.HTTPError as e:
61
+ if e.code in (429, 500, 502, 503, 504) and attempt < retries - 1:
62
+ time.sleep(2 ** attempt)
63
+ continue
64
+ body_text = ""
65
+ try:
66
+ body_text = e.read().decode()
67
+ except Exception:
68
+ pass
69
+ print(f"HTTP {e.code}: {e.reason}\n{body_text}", file=sys.stderr)
70
+ sys.exit(1)
71
+ except urllib.error.URLError as e:
72
+ if attempt < retries - 1:
73
+ time.sleep(2 ** attempt)
74
+ continue
75
+ print(f"Connection error: {e.reason}", file=sys.stderr)
76
+ sys.exit(1)
77
+
78
+
79
+ def output(data):
80
+ print(json.dumps(data, indent=2))
81
+
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Workflow metadata handlers
85
+ # ---------------------------------------------------------------------------
86
+
87
+ def handle_list_workflows(args):
88
+ base, token = get_config()
89
+ url = build_url(base, "/metadata/workflow")
90
+ result = request_json(url, token)
91
+ output(result)
92
+
93
+
94
+ def handle_get_workflow(args):
95
+ base, token = get_config()
96
+ params = {}
97
+ if args.version:
98
+ params["version"] = args.version
99
+ url = build_url(base, f"/metadata/workflow/{urllib.parse.quote(args.name)}", params)
100
+ result = request_json(url, token)
101
+ output(result)
102
+
103
+
104
+ def handle_create_workflow(args):
105
+ base, token = get_config()
106
+ with open(args.file) as f:
107
+ body = json.load(f)
108
+ url = build_url(base, "/metadata/workflow")
109
+ result = request_json(url, token, method="POST", body=body)
110
+ if result:
111
+ output(result)
112
+ else:
113
+ print("Workflow created successfully.")
114
+
115
+
116
+ def handle_update_workflow(args):
117
+ base, token = get_config()
118
+ with open(args.file) as f:
119
+ body = json.load(f)
120
+ # Update expects an array
121
+ if isinstance(body, dict):
122
+ body = [body]
123
+ url = build_url(base, "/metadata/workflow")
124
+ result = request_json(url, token, method="PUT", body=body)
125
+ if result:
126
+ output(result)
127
+ else:
128
+ print("Workflow updated successfully.")
129
+
130
+
131
+ def handle_delete_workflow(args):
132
+ base, token = get_config()
133
+ url = build_url(base, f"/metadata/workflow/{urllib.parse.quote(args.name)}/{args.version}")
134
+ request_json(url, token, method="DELETE", expect_json=False)
135
+ print(f"Workflow {args.name} v{args.version} deleted.")
136
+
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # Workflow execution handlers
140
+ # ---------------------------------------------------------------------------
141
+
142
+ def handle_start_workflow(args):
143
+ base, token = get_config()
144
+ body = {"name": args.name}
145
+ if args.version:
146
+ body["version"] = int(args.version)
147
+ if args.correlation_id:
148
+ body["correlationId"] = args.correlation_id
149
+ if args.input:
150
+ body["input"] = json.loads(args.input)
151
+ elif args.input_file:
152
+ with open(args.input_file) as f:
153
+ body["input"] = json.load(f)
154
+
155
+ url = build_url(base, "/workflow")
156
+ result = request_json(url, token, method="POST", body=body, expect_json=False)
157
+ # Start returns the workflow ID as plain text
158
+ wf_id = result.strip().strip('"') if result else ""
159
+ print(json.dumps({"workflowId": wf_id}, indent=2))
160
+
161
+
162
+ def handle_get_execution(args):
163
+ base, token = get_config()
164
+ params = {}
165
+ if args.include_tasks:
166
+ params["includeTasks"] = "true"
167
+ url = build_url(base, f"/workflow/{urllib.parse.quote(args.id)}", params)
168
+ result = request_json(url, token)
169
+ output(result)
170
+
171
+
172
+ def handle_search_workflows(args):
173
+ base, token = get_config()
174
+ params = {"size": str(args.size or 10)}
175
+ if args.status:
176
+ params["query"] = f"status={args.status}"
177
+ if args.query:
178
+ params["query"] = args.query
179
+ if args.sort:
180
+ params["sort"] = args.sort
181
+ url = build_url(base, "/workflow/search", params)
182
+ result = request_json(url, token)
183
+ output(result)
184
+
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # Workflow management handlers
188
+ # ---------------------------------------------------------------------------
189
+
190
+ def handle_pause_workflow(args):
191
+ base, token = get_config()
192
+ url = build_url(base, f"/workflow/{urllib.parse.quote(args.id)}/pause")
193
+ request_json(url, token, method="PUT", expect_json=False)
194
+ print(f"Workflow {args.id} paused.")
195
+
196
+
197
+ def handle_resume_workflow(args):
198
+ base, token = get_config()
199
+ url = build_url(base, f"/workflow/{urllib.parse.quote(args.id)}/resume")
200
+ request_json(url, token, method="PUT", expect_json=False)
201
+ print(f"Workflow {args.id} resumed.")
202
+
203
+
204
+ def handle_terminate_workflow(args):
205
+ base, token = get_config()
206
+ params = {}
207
+ if args.reason:
208
+ params["reason"] = args.reason
209
+ url = build_url(base, f"/workflow/{urllib.parse.quote(args.id)}", params)
210
+ request_json(url, token, method="DELETE", expect_json=False)
211
+ print(f"Workflow {args.id} terminated.")
212
+
213
+
214
+ def handle_restart_workflow(args):
215
+ base, token = get_config()
216
+ url = build_url(base, f"/workflow/{urllib.parse.quote(args.id)}/restart")
217
+ request_json(url, token, method="POST", expect_json=False)
218
+ print(f"Workflow {args.id} restarted.")
219
+
220
+
221
+ def handle_retry_workflow(args):
222
+ base, token = get_config()
223
+ url = build_url(base, f"/workflow/{urllib.parse.quote(args.id)}/retry")
224
+ request_json(url, token, method="POST", expect_json=False)
225
+ print(f"Workflow {args.id} retried.")
226
+
227
+
228
+ # ---------------------------------------------------------------------------
229
+ # Task handlers
230
+ # ---------------------------------------------------------------------------
231
+
232
+ def handle_signal_task(args):
233
+ base, token = get_config()
234
+ status = urllib.parse.quote(args.status)
235
+ url = build_url(
236
+ base,
237
+ f"/tasks/{urllib.parse.quote(args.workflow_id)}/{urllib.parse.quote(args.task_ref)}/{status}",
238
+ )
239
+ body = {}
240
+ if args.output:
241
+ body = json.loads(args.output)
242
+ result = request_json(url, token, method="POST", body=body, expect_json=False)
243
+ print(f"Task {args.task_ref} signaled with status {args.status}.")
244
+ if result:
245
+ print(result)
246
+
247
+
248
+ def handle_signal_task_sync(args):
249
+ base, token = get_config()
250
+ status = urllib.parse.quote(args.status)
251
+ url = build_url(
252
+ base,
253
+ f"/tasks/{urllib.parse.quote(args.workflow_id)}/{urllib.parse.quote(args.task_ref)}/{status}/sync",
254
+ )
255
+ body = {}
256
+ if args.output:
257
+ body = json.loads(args.output)
258
+ result = request_json(url, token, method="POST", body=body)
259
+ if result:
260
+ output(result)
261
+ else:
262
+ print(f"Task {args.task_ref} signaled synchronously with status {args.status}.")
263
+
264
+
265
+ def handle_poll_task(args):
266
+ base, token = get_config()
267
+ params = {"count": str(args.count or 1)}
268
+ url = build_url(base, f"/tasks/poll/batch/{urllib.parse.quote(args.task_type)}", params)
269
+ result = request_json(url, token)
270
+ output(result)
271
+
272
+
273
+ def handle_queue_size(args):
274
+ base, token = get_config()
275
+ params = {}
276
+ if args.task_type:
277
+ params["taskType"] = args.task_type
278
+ url = build_url(base, "/tasks/queue/size", params)
279
+ result = request_json(url, token)
280
+ output(result)
281
+
282
+
283
+ # ---------------------------------------------------------------------------
284
+ # CLI definition
285
+ # ---------------------------------------------------------------------------
286
+
287
+ def main():
288
+ parser = argparse.ArgumentParser(
289
+ description="Conductor REST API fallback (stdlib only)"
290
+ )
291
+ sub = parser.add_subparsers(dest="command", required=True)
292
+
293
+ # -- Workflow metadata --
294
+ sub.add_parser("list-workflows", help="List all workflow definitions")
295
+
296
+ p = sub.add_parser("get-workflow", help="Get a workflow definition")
297
+ p.add_argument("--name", required=True)
298
+ p.add_argument("--version", default=None)
299
+
300
+ p = sub.add_parser("create-workflow", help="Create a workflow definition from JSON file")
301
+ p.add_argument("--file", required=True)
302
+
303
+ p = sub.add_parser("update-workflow", help="Update a workflow definition from JSON file")
304
+ p.add_argument("--file", required=True)
305
+
306
+ p = sub.add_parser("delete-workflow", help="Delete a workflow definition")
307
+ p.add_argument("--name", required=True)
308
+ p.add_argument("--version", required=True)
309
+
310
+ # -- Workflow execution --
311
+ p = sub.add_parser("start-workflow", help="Start a workflow execution")
312
+ p.add_argument("--name", required=True)
313
+ p.add_argument("--version", default=None)
314
+ p.add_argument("--correlation-id", default=None)
315
+ p.add_argument("--input", default=None, help="Inline JSON input")
316
+ p.add_argument("--input-file", default=None, help="Path to JSON input file")
317
+
318
+ p = sub.add_parser("get-execution", help="Get workflow execution status")
319
+ p.add_argument("--id", required=True)
320
+ p.add_argument("--include-tasks", action="store_true")
321
+
322
+ p = sub.add_parser("search-workflows", help="Search workflow executions")
323
+ p.add_argument("--status", default=None)
324
+ p.add_argument("--query", default=None)
325
+ p.add_argument("--size", type=int, default=10)
326
+ p.add_argument("--sort", default=None)
327
+
328
+ # -- Workflow management --
329
+ p = sub.add_parser("pause-workflow", help="Pause a running workflow")
330
+ p.add_argument("--id", required=True)
331
+
332
+ p = sub.add_parser("resume-workflow", help="Resume a paused workflow")
333
+ p.add_argument("--id", required=True)
334
+
335
+ p = sub.add_parser("terminate-workflow", help="Terminate a workflow")
336
+ p.add_argument("--id", required=True)
337
+ p.add_argument("--reason", default=None)
338
+
339
+ p = sub.add_parser("restart-workflow", help="Restart a completed workflow")
340
+ p.add_argument("--id", required=True)
341
+
342
+ p = sub.add_parser("retry-workflow", help="Retry the last failed task")
343
+ p.add_argument("--id", required=True)
344
+
345
+ # -- Task operations --
346
+ p = sub.add_parser("signal-task", help="Signal a task (async)")
347
+ p.add_argument("--workflow-id", required=True)
348
+ p.add_argument("--task-ref", required=True)
349
+ p.add_argument("--status", required=True)
350
+ p.add_argument("--output", default=None, help="JSON output to pass to the task")
351
+
352
+ p = sub.add_parser("signal-task-sync", help="Signal a task (sync, returns workflow)")
353
+ p.add_argument("--workflow-id", required=True)
354
+ p.add_argument("--task-ref", required=True)
355
+ p.add_argument("--status", required=True)
356
+ p.add_argument("--output", default=None, help="JSON output to pass to the task")
357
+
358
+ p = sub.add_parser("poll-task", help="Poll for tasks of a given type")
359
+ p.add_argument("--task-type", required=True)
360
+ p.add_argument("--count", type=int, default=1)
361
+
362
+ p = sub.add_parser("queue-size", help="Get task queue size")
363
+ p.add_argument("--task-type", default=None)
364
+
365
+ args = parser.parse_args()
366
+
367
+ handlers = {
368
+ "list-workflows": handle_list_workflows,
369
+ "get-workflow": handle_get_workflow,
370
+ "create-workflow": handle_create_workflow,
371
+ "update-workflow": handle_update_workflow,
372
+ "delete-workflow": handle_delete_workflow,
373
+ "start-workflow": handle_start_workflow,
374
+ "get-execution": handle_get_execution,
375
+ "search-workflows": handle_search_workflows,
376
+ "pause-workflow": handle_pause_workflow,
377
+ "resume-workflow": handle_resume_workflow,
378
+ "terminate-workflow": handle_terminate_workflow,
379
+ "restart-workflow": handle_restart_workflow,
380
+ "retry-workflow": handle_retry_workflow,
381
+ "signal-task": handle_signal_task,
382
+ "signal-task-sync": handle_signal_task_sync,
383
+ "poll-task": handle_poll_task,
384
+ "queue-size": handle_queue_size,
385
+ }
386
+
387
+ handler = handlers.get(args.command)
388
+ if handler:
389
+ handler(args)
390
+ else:
391
+ parser.print_help()
392
+ sys.exit(1)
393
+
394
+
395
+ if __name__ == "__main__":
396
+ main()