@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.
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE.txt +176 -0
- package/README.md +352 -0
- package/VERSION +1 -0
- package/bin/conductor-skills.js +135 -0
- package/commands/conductor-optimize.md +18 -0
- package/commands/conductor-scaffold-worker.md +19 -0
- package/commands/conductor-setup.md +15 -0
- package/commands/conductor.md +15 -0
- package/install.ps1 +677 -0
- package/install.sh +855 -0
- package/package.json +50 -0
- package/skills/conductor/SKILL.md +151 -0
- package/skills/conductor/examples/ai-agent-loop.md +119 -0
- package/skills/conductor/examples/ai-agent-mcp.md +129 -0
- package/skills/conductor/examples/create-and-run-workflow.md +50 -0
- package/skills/conductor/examples/do-while-loop.md +72 -0
- package/skills/conductor/examples/fork-join.md +52 -0
- package/skills/conductor/examples/llm-chat.md +61 -0
- package/skills/conductor/examples/llm-rag.md +115 -0
- package/skills/conductor/examples/monitor-and-retry.md +54 -0
- package/skills/conductor/examples/review-workflow.md +67 -0
- package/skills/conductor/examples/signal-wait-task.md +36 -0
- package/skills/conductor/examples/sub-workflow.md +52 -0
- package/skills/conductor/examples/workflows/ai-agent-loop.json +88 -0
- package/skills/conductor/examples/workflows/ai-agent-mcp.json +69 -0
- package/skills/conductor/examples/workflows/child-normalize.json +21 -0
- package/skills/conductor/examples/workflows/do-while-loop.json +35 -0
- package/skills/conductor/examples/workflows/fork-join.json +61 -0
- package/skills/conductor/examples/workflows/llm-chat.json +28 -0
- package/skills/conductor/examples/workflows/llm-rag.json +49 -0
- package/skills/conductor/examples/workflows/parent-pipeline.json +35 -0
- package/skills/conductor/examples/workflows/weather-notification.json +42 -0
- package/skills/conductor/references/api-reference.md +111 -0
- package/skills/conductor/references/cli-index.md +92 -0
- package/skills/conductor/references/fallback-cli.md +36 -0
- package/skills/conductor/references/optimization.md +148 -0
- package/skills/conductor/references/orkes.md +57 -0
- package/skills/conductor/references/schedules.md +81 -0
- package/skills/conductor/references/setup.md +126 -0
- package/skills/conductor/references/troubleshooting.md +35 -0
- package/skills/conductor/references/visualization.md +49 -0
- package/skills/conductor/references/workers.md +227 -0
- package/skills/conductor/references/workflow-definition.md +672 -0
- 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()
|