@aicopycoders/exodus 2026.7.200-beta.12 → 2026.7.200-beta.13
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 +51 -0
- package/dist/commands/bank.d.ts +1 -0
- package/dist/commands/bank.js +3 -3
- package/dist/commands/keys.js +1 -0
- package/dist/commands/workflow.d.ts +6 -0
- package/dist/commands/workflow.js +120 -37
- package/dist/lib/format.d.ts +5 -0
- package/dist/lib/format.js +31 -0
- package/dist/lib/keys-sync.js +1 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @aicopycoders/exodus
|
|
2
|
+
|
|
3
|
+
The Exodus CLI + Claude Code skills for the Copycoders ad pipeline. It drives the
|
|
4
|
+
Genesis creative bots, workflows, banks, and image/meme pipelines against your
|
|
5
|
+
brand — from your terminal and from Claude Code.
|
|
6
|
+
|
|
7
|
+
You don't install it globally. Run it with `npx` from your workspace folder.
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 1. Scaffold (or refresh) the workspace in the current folder
|
|
13
|
+
npx -y @aicopycoders/exodus@beta init
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`init` creates the workspace (the `.claude/` skills, docs, and your brand
|
|
17
|
+
subfolder) and prints the next steps. The `@beta` tag is the dev/preview channel;
|
|
18
|
+
drop it (`@aicopycoders/exodus`) for the stable release channel.
|
|
19
|
+
|
|
20
|
+
### Connect your account
|
|
21
|
+
|
|
22
|
+
1. Open the Exodus dashboard → **Settings → Claude Code**.
|
|
23
|
+
2. Copy the `.env` snippet it shows you (your `EXODUS_API_KEY` + dashboard URL)
|
|
24
|
+
and paste it into a `.env` file in your workspace folder.
|
|
25
|
+
3. Pull your provider keys down so the local tools have them:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx -y @aicopycoders/exodus@beta keys pull
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This writes your dashboard provider + image keys into `.env` (it only upserts
|
|
32
|
+
those keys and preserves the rest of the file). Values are never printed.
|
|
33
|
+
|
|
34
|
+
### Check everything is wired up
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx -y @aicopycoders/exodus@beta doctor
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`doctor` diagnoses local and dashboard-side issues in one shot — keys, workspace
|
|
41
|
+
health, CLI currency — and tells you exactly what to fix.
|
|
42
|
+
|
|
43
|
+
## Where to go next
|
|
44
|
+
|
|
45
|
+
- `npx -y @aicopycoders/exodus@beta --help` — the full command surface.
|
|
46
|
+
- `npx -y @aicopycoders/exodus@beta workflow --help` — run, build, and automate
|
|
47
|
+
saved multi-node workflows; resolve gate/repair parks from the inbox; read
|
|
48
|
+
banks and promote winners.
|
|
49
|
+
|
|
50
|
+
Inside Claude Code, just say **"exodus"** plus what you want
|
|
51
|
+
("exodus, write me some ads") and the skills route the rest.
|
package/dist/commands/bank.d.ts
CHANGED
package/dist/commands/bank.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { apiGet, apiPost } from "../lib/client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { formatApiError } from "../lib/format.js";
|
|
4
4
|
import { getChannel } from "../lib/channel.js";
|
|
5
5
|
import { missingRouteLine } from "../lib/route-support.js";
|
|
6
6
|
export const helpText = `
|
|
@@ -37,7 +37,7 @@ Examples:
|
|
|
37
37
|
exodus bank list
|
|
38
38
|
exodus bank show hooks
|
|
39
39
|
exodus bank promote hooks "Stop scrolling — your knees will thank you"
|
|
40
|
-
exodus bank promote body --file winner.txt --awareness solutionProductAware
|
|
40
|
+
exodus bank promote body:solutionProductAware --file winner.txt --awareness solutionProductAware
|
|
41
41
|
cat winner.txt | exodus bank promote hooks
|
|
42
42
|
exodus bank promote hooks "..." --spend 4200 --roas 3.1 --ctr 1.8 --note "Q3 winner"
|
|
43
43
|
exodus bank promote hooks "..." --run wr_123 --node bot-4
|
|
@@ -66,7 +66,7 @@ function asErrorResult(res, json) {
|
|
|
66
66
|
code: 1,
|
|
67
67
|
lines: json
|
|
68
68
|
? [JSON.stringify({ ok: false, status: res.status, data: res.data })]
|
|
69
|
-
: [
|
|
69
|
+
: [formatApiError(res)],
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
function table(headers, rows) {
|
package/dist/commands/keys.js
CHANGED
|
@@ -63,6 +63,7 @@ export interface WorkflowImportResult {
|
|
|
63
63
|
workflowId?: string;
|
|
64
64
|
nodeCount: number;
|
|
65
65
|
edgeCount: number;
|
|
66
|
+
triggers?: WorkflowTrigger[];
|
|
66
67
|
unresolved: UnresolvedWorkflowRef[];
|
|
67
68
|
warnings: string[];
|
|
68
69
|
}
|
|
@@ -240,6 +241,11 @@ export type WorkflowArtifact = {
|
|
|
240
241
|
type: "image";
|
|
241
242
|
storageId: string;
|
|
242
243
|
imageUrl?: string;
|
|
244
|
+
} | {
|
|
245
|
+
type: "session";
|
|
246
|
+
sessionId: string;
|
|
247
|
+
label?: string;
|
|
248
|
+
port?: string;
|
|
243
249
|
};
|
|
244
250
|
export interface WorkflowRunOutput {
|
|
245
251
|
nodeId: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { apiGet, apiGetText, apiPost, apiPostDashboard, getDashboardUrl, } from "../lib/client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { formatApiError } from "../lib/format.js";
|
|
4
4
|
import { pollUntilDone } from "../lib/poll.js";
|
|
5
5
|
import { workflowToYaml, parseWorkflowText } from "../lib/workflowText.js";
|
|
6
6
|
import { missingRouteLine } from "../lib/route-support.js";
|
|
@@ -207,7 +207,24 @@ function asErrorResult(res, json) {
|
|
|
207
207
|
code: 1,
|
|
208
208
|
lines: json
|
|
209
209
|
? [JSON.stringify({ ok: false, status: res.status, data: res.data })]
|
|
210
|
-
: [
|
|
210
|
+
: [formatApiError(res)],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function resolveIdErrorResult(e, json) {
|
|
214
|
+
if (e instanceof WorkflowResolveError) {
|
|
215
|
+
return {
|
|
216
|
+
code: 1,
|
|
217
|
+
lines: json
|
|
218
|
+
? [JSON.stringify({ ok: false, status: e.status, data: e.data })]
|
|
219
|
+
: [e.message],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
223
|
+
return {
|
|
224
|
+
code: 1,
|
|
225
|
+
lines: json
|
|
226
|
+
? [JSON.stringify({ ok: false, status: 0, data: { error: { message } } })]
|
|
227
|
+
: [message],
|
|
211
228
|
};
|
|
212
229
|
}
|
|
213
230
|
function isRecord(value) {
|
|
@@ -252,12 +269,26 @@ function outputLines(output) {
|
|
|
252
269
|
if (output.type === "primer") {
|
|
253
270
|
return [` primer:${output.primerKind}: ${truncateText(output.text)}`];
|
|
254
271
|
}
|
|
255
|
-
|
|
272
|
+
if (output.type === "image") {
|
|
273
|
+
const url = output.imageUrl ?? output.storageId;
|
|
274
|
+
return url ? [` image: ${url}`] : [];
|
|
275
|
+
}
|
|
276
|
+
return [];
|
|
256
277
|
}
|
|
257
|
-
function progressLine(node) {
|
|
278
|
+
function progressLine(node, parked = false) {
|
|
258
279
|
const err = node.error ? ` — error: ${node.error}` : "";
|
|
280
|
+
if (parked) {
|
|
281
|
+
return ` ⏸ ${node.nodeId} (${node.kind}) awaiting review`;
|
|
282
|
+
}
|
|
259
283
|
return ` ${statusIcon(node.status)} ${node.nodeId} (${node.kind}) ${node.status}${err}`;
|
|
260
284
|
}
|
|
285
|
+
function gateParkedNodeId(status, pauseReason, pausedNodeId) {
|
|
286
|
+
if (status !== "awaiting-review")
|
|
287
|
+
return undefined;
|
|
288
|
+
if (pauseReason === "taste" || pauseReason === undefined)
|
|
289
|
+
return pausedNodeId;
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
261
292
|
export function formatPauseNotice(pauseReason, runId, dashboardUrl) {
|
|
262
293
|
if (!pauseReason) {
|
|
263
294
|
return [
|
|
@@ -398,6 +429,28 @@ export function formatImportSummary(result, mode = {}) {
|
|
|
398
429
|
lines.push(`workflowId: ${result.workflowId}`);
|
|
399
430
|
lines.push(`nodes: ${result.nodeCount}`);
|
|
400
431
|
lines.push(`edges: ${result.edgeCount}`);
|
|
432
|
+
if (result.triggers && result.triggers.length > 0) {
|
|
433
|
+
lines.push("");
|
|
434
|
+
const enabledCount = result.triggers.filter((t) => t.enabled).length;
|
|
435
|
+
lines.push(`Triggers (${result.triggers.length}):`);
|
|
436
|
+
for (const t of result.triggers) {
|
|
437
|
+
const detail = t.type === "event" ? `event (${t.event})` : `cron (${t.cron})`;
|
|
438
|
+
if (t.enabled) {
|
|
439
|
+
const fires = t.type === "event"
|
|
440
|
+
? t.event === "winner-promoted"
|
|
441
|
+
? "fires on every promote"
|
|
442
|
+
: `fires on ${t.event}`
|
|
443
|
+
: `fires on schedule ${t.cron}`;
|
|
444
|
+
lines.push(` ⚠ ${detail} — ENABLED, ${fires}`);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
lines.push(` ${detail} — disabled`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (enabledCount > 0) {
|
|
451
|
+
lines.push(` ⚠ ${enabledCount} enabled trigger${enabledCount === 1 ? "" : "s"} armed — a background run may start on the owner's keys. Disable with: exodus workflow triggers <id> disable <n>`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
401
454
|
if (result.unresolved.length > 0) {
|
|
402
455
|
lines.push("");
|
|
403
456
|
lines.push(`Unresolved references (${result.unresolved.length}):`);
|
|
@@ -430,11 +483,12 @@ export function formatWorkflowRun(run) {
|
|
|
430
483
|
const inputs = Object.entries(run.inputs).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
431
484
|
lines.push(`inputs: ${inputs}`);
|
|
432
485
|
}
|
|
486
|
+
const parkedNodeId = gateParkedNodeId(run.status, run.pauseReason, run.pausedNodeId);
|
|
433
487
|
if (run.nodes.length > 0) {
|
|
434
488
|
lines.push("");
|
|
435
489
|
lines.push(`Nodes (${run.nodes.length}):`);
|
|
436
490
|
for (const node of run.nodes) {
|
|
437
|
-
lines.push(progressLine(node));
|
|
491
|
+
lines.push(progressLine(node, parkedNodeId !== undefined && node.nodeId === parkedNodeId));
|
|
438
492
|
for (const output of node.outputs)
|
|
439
493
|
lines.push(...outputLines(output));
|
|
440
494
|
}
|
|
@@ -442,8 +496,9 @@ export function formatWorkflowRun(run) {
|
|
|
442
496
|
if (run.outputs && run.outputs.length > 0) {
|
|
443
497
|
lines.push("");
|
|
444
498
|
lines.push(`Outputs (${run.outputs.length}):`);
|
|
445
|
-
for (const output of run.outputs)
|
|
446
|
-
lines.push(...runOutputLines(output));
|
|
499
|
+
for (const output of run.outputs) {
|
|
500
|
+
lines.push(...runOutputLines(output, parkedNodeId !== undefined && output.nodeId === parkedNodeId));
|
|
501
|
+
}
|
|
447
502
|
}
|
|
448
503
|
if (run.sessions && run.sessions.length > 0) {
|
|
449
504
|
lines.push("");
|
|
@@ -454,30 +509,31 @@ export function formatWorkflowRun(run) {
|
|
|
454
509
|
}
|
|
455
510
|
return lines.join("\n");
|
|
456
511
|
}
|
|
457
|
-
function runOutputLines(output) {
|
|
512
|
+
function runOutputLines(output, awaitingReview = false) {
|
|
458
513
|
const slug = output.botSlug ? ` (${output.botSlug})` : "";
|
|
514
|
+
const review = awaitingReview ? " — awaiting review, not yet approved" : "";
|
|
459
515
|
if (output.type === "image") {
|
|
460
|
-
return [` ${output.label} [image]${slug}: ${output.imageUrl ?? output.imageId ?? "(no url)"}`];
|
|
516
|
+
return [` ${output.label} [image]${slug}: ${output.imageUrl ?? output.imageId ?? "(no url)"}${review}`];
|
|
461
517
|
}
|
|
462
518
|
if (output.type === "video") {
|
|
463
519
|
const tag = output.final === true ? "final video" : "video";
|
|
464
|
-
return [` ${output.label} [${tag}]${slug}: ${output.videoUrl ?? "(no url)"}`];
|
|
520
|
+
return [` ${output.label} [${tag}]${slug}: ${output.videoUrl ?? "(no url)"}${review}`];
|
|
465
521
|
}
|
|
466
522
|
if (output.type === "audio") {
|
|
467
|
-
return [` ${output.label} [audio]${slug}: ${output.audioUrl ?? "(no url)"}`];
|
|
523
|
+
return [` ${output.label} [audio]${slug}: ${output.audioUrl ?? "(no url)"}${review}`];
|
|
468
524
|
}
|
|
469
525
|
if (output.type === "frames") {
|
|
470
526
|
const n = output.frames?.length ?? 0;
|
|
471
|
-
return [` ${output.label} [frames]${slug}: ${n} scene${n === 1 ? "" : "s"}`];
|
|
527
|
+
return [` ${output.label} [frames]${slug}: ${n} scene${n === 1 ? "" : "s"}${review}`];
|
|
472
528
|
}
|
|
473
529
|
if (output.type === "storyboard") {
|
|
474
|
-
return [` ${output.label} [storyboard]${slug}: use --json for the scene plan`];
|
|
530
|
+
return [` ${output.label} [storyboard]${slug}: use --json for the scene plan${review}`];
|
|
475
531
|
}
|
|
476
532
|
const raw = output.text ?? "";
|
|
477
533
|
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
478
534
|
const body = truncateText(raw, 400);
|
|
479
535
|
const note = normalized.length > 400 ? "\n (truncated — use --json for the full text)" : "";
|
|
480
|
-
return [` ${output.label} [text]${slug}:`, ` ${body}${note}`];
|
|
536
|
+
return [` ${output.label} [text]${slug}${review}:`, ` ${body}${note}`];
|
|
481
537
|
}
|
|
482
538
|
export function formatDescribe(res) {
|
|
483
539
|
const lines = [];
|
|
@@ -812,15 +868,31 @@ function formatImportError(res) {
|
|
|
812
868
|
lines.push(`fix: ${remedy}`);
|
|
813
869
|
return lines.join("\n");
|
|
814
870
|
}
|
|
815
|
-
return
|
|
871
|
+
return formatApiError(res);
|
|
872
|
+
}
|
|
873
|
+
class WorkflowResolveError extends Error {
|
|
874
|
+
status;
|
|
875
|
+
data;
|
|
876
|
+
constructor(status, data, message) {
|
|
877
|
+
super(message);
|
|
878
|
+
this.status = status;
|
|
879
|
+
this.data = data;
|
|
880
|
+
this.name = "WorkflowResolveError";
|
|
881
|
+
}
|
|
816
882
|
}
|
|
817
883
|
export async function resolveWorkflowId(ref, deps) {
|
|
818
884
|
const res = await deps.get(LIST_PATH);
|
|
819
885
|
if (!res.ok)
|
|
820
|
-
throw new
|
|
886
|
+
throw new WorkflowResolveError(res.status, res.data, formatApiError(res));
|
|
821
887
|
const workflows = res.data.workflows ?? [];
|
|
822
|
-
const
|
|
823
|
-
|
|
888
|
+
const byName = workflows.find((w) => w.name.toLowerCase() === ref.toLowerCase());
|
|
889
|
+
if (byName)
|
|
890
|
+
return byName._id;
|
|
891
|
+
const byId = workflows.find((w) => w._id === ref);
|
|
892
|
+
if (byId)
|
|
893
|
+
return byId._id;
|
|
894
|
+
const message = `No workflow named "${ref}" on this brand — run: exodus workflow list`;
|
|
895
|
+
throw new WorkflowResolveError(404, { error: { code: "NOT_FOUND", message } }, message);
|
|
824
896
|
}
|
|
825
897
|
export async function listFlow(json, deps) {
|
|
826
898
|
const res = await deps.get(LIST_PATH);
|
|
@@ -838,7 +910,7 @@ export async function describeFlow(workflowRef, opts, deps) {
|
|
|
838
910
|
workflowId = await resolveWorkflowId(workflowRef, deps);
|
|
839
911
|
}
|
|
840
912
|
catch (e) {
|
|
841
|
-
return
|
|
913
|
+
return resolveIdErrorResult(e, opts.json ?? false);
|
|
842
914
|
}
|
|
843
915
|
const res = await deps.get(`${DESCRIBE_PATH}?id=${encodeURIComponent(workflowId)}`);
|
|
844
916
|
if (!res.ok)
|
|
@@ -872,7 +944,7 @@ export async function runFlow(workflowRef, opts, deps) {
|
|
|
872
944
|
workflowId = await resolveWorkflowId(workflowRef, deps);
|
|
873
945
|
}
|
|
874
946
|
catch (e) {
|
|
875
|
-
return
|
|
947
|
+
return resolveIdErrorResult(e, opts.json ?? false);
|
|
876
948
|
}
|
|
877
949
|
const body = {
|
|
878
950
|
workflowId,
|
|
@@ -927,12 +999,15 @@ async function waitForRun(runId, opts, deps) {
|
|
|
927
999
|
opts.onProgressLine(line);
|
|
928
1000
|
}
|
|
929
1001
|
}
|
|
1002
|
+
const parkedNodeId = gateParkedNodeId(raw["status"], raw["pauseReason"], raw["pausedNodeId"]);
|
|
930
1003
|
const nodes = Array.isArray(raw["nodes"]) ? raw["nodes"] : [];
|
|
931
1004
|
for (const node of nodes) {
|
|
932
|
-
|
|
1005
|
+
const isParked = parkedNodeId !== undefined && node.nodeId === parkedNodeId;
|
|
1006
|
+
const renderKey = `${node.status}:${isParked}`;
|
|
1007
|
+
if (seen.get(node.nodeId) === renderKey)
|
|
933
1008
|
continue;
|
|
934
|
-
seen.set(node.nodeId,
|
|
935
|
-
opts.onProgressLine(progressLine(node));
|
|
1009
|
+
seen.set(node.nodeId, renderKey);
|
|
1010
|
+
opts.onProgressLine(progressLine(node, isParked));
|
|
936
1011
|
}
|
|
937
1012
|
},
|
|
938
1013
|
});
|
|
@@ -977,7 +1052,7 @@ export async function exportFlow(workflowRef, opts, deps) {
|
|
|
977
1052
|
workflowId = await resolveWorkflowId(workflowRef, deps);
|
|
978
1053
|
}
|
|
979
1054
|
catch (e) {
|
|
980
|
-
return
|
|
1055
|
+
return resolveIdErrorResult(e, opts.json ?? false);
|
|
981
1056
|
}
|
|
982
1057
|
const versionParam = opts.version !== undefined ? `&version=${encodeURIComponent(String(opts.version))}` : "";
|
|
983
1058
|
const res = await deps.get(`${EXPORT_PATH}?id=${encodeURIComponent(workflowId)}${versionParam}`);
|
|
@@ -1011,7 +1086,7 @@ export async function versionsFlow(workflowRef, opts, deps, channel = getChannel
|
|
|
1011
1086
|
workflowId = await resolveWorkflowId(workflowRef, deps);
|
|
1012
1087
|
}
|
|
1013
1088
|
catch (e) {
|
|
1014
|
-
return
|
|
1089
|
+
return resolveIdErrorResult(e, opts.json ?? false);
|
|
1015
1090
|
}
|
|
1016
1091
|
const res = await deps.get(`${VERSIONS_PATH}?id=${encodeURIComponent(workflowId)}`);
|
|
1017
1092
|
const unsupported = missingRouteLine(res, "workflow versions", channel);
|
|
@@ -1148,7 +1223,7 @@ export async function triggersListFlow(workflowRef, opts, deps) {
|
|
|
1148
1223
|
workflowId = await resolveWorkflowId(workflowRef, deps);
|
|
1149
1224
|
}
|
|
1150
1225
|
catch (e) {
|
|
1151
|
-
return
|
|
1226
|
+
return resolveIdErrorResult(e, opts.json ?? false);
|
|
1152
1227
|
}
|
|
1153
1228
|
const fetched = await fetchTriggers(workflowId, deps);
|
|
1154
1229
|
if (!fetched.ok)
|
|
@@ -1177,7 +1252,7 @@ export async function triggersSetEnabledFlow(workflowRef, n, enabled, opts, deps
|
|
|
1177
1252
|
workflowId = await resolveWorkflowId(workflowRef, deps);
|
|
1178
1253
|
}
|
|
1179
1254
|
catch (e) {
|
|
1180
|
-
return
|
|
1255
|
+
return resolveIdErrorResult(e, opts.json ?? false);
|
|
1181
1256
|
}
|
|
1182
1257
|
const fetched = await fetchTriggers(workflowId, deps);
|
|
1183
1258
|
if (!fetched.ok)
|
|
@@ -1206,7 +1281,7 @@ export async function triggersFireFlow(workflowRef, opts, deps) {
|
|
|
1206
1281
|
workflowId = await resolveWorkflowId(workflowRef, deps);
|
|
1207
1282
|
}
|
|
1208
1283
|
catch (e) {
|
|
1209
|
-
return
|
|
1284
|
+
return resolveIdErrorResult(e, opts.json ?? false);
|
|
1210
1285
|
}
|
|
1211
1286
|
const fetched = await fetchTriggers(workflowId, deps);
|
|
1212
1287
|
if (!fetched.ok)
|
|
@@ -1335,9 +1410,9 @@ function formatTextError(res) {
|
|
|
1335
1410
|
}
|
|
1336
1411
|
catch {
|
|
1337
1412
|
const snippet = body.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
1338
|
-
return
|
|
1413
|
+
return formatApiError({ ok: false, status: res.status, data: snippet || "(empty response)" });
|
|
1339
1414
|
}
|
|
1340
|
-
return
|
|
1415
|
+
return formatApiError({ ok: false, status: res.status, data: parsed });
|
|
1341
1416
|
}
|
|
1342
1417
|
export async function schemaFlow(opts, deps) {
|
|
1343
1418
|
const res = await deps.get(SCHEMA_PATH);
|
|
@@ -1358,9 +1433,6 @@ export async function schemaFlow(opts, deps) {
|
|
|
1358
1433
|
}
|
|
1359
1434
|
return { code: 0, lines: [formatSchema(payload)] };
|
|
1360
1435
|
}
|
|
1361
|
-
function shortId(id) {
|
|
1362
|
-
return id.length <= 12 ? id : `${id.slice(0, 11)}…`;
|
|
1363
|
-
}
|
|
1364
1436
|
export function formatAge(value, now = Date.now()) {
|
|
1365
1437
|
if (value === undefined || value === null)
|
|
1366
1438
|
return "-";
|
|
@@ -1399,7 +1471,7 @@ export function formatInbox(rows, now = Date.now()) {
|
|
|
1399
1471
|
if (rows.length === 0)
|
|
1400
1472
|
return NO_INBOX;
|
|
1401
1473
|
return table(["run", "workflow", "kind", "node", "via", "age"], rows.map((r) => [
|
|
1402
|
-
|
|
1474
|
+
r._id,
|
|
1403
1475
|
r.workflowName || "(unnamed)",
|
|
1404
1476
|
parkBadge(r.pauseReason),
|
|
1405
1477
|
r.pausedNodeId ?? "-",
|
|
@@ -1607,17 +1679,28 @@ export async function gateEditFlow(runId, n, sources, opts, deps) {
|
|
|
1607
1679
|
return triggerErrorResult(res, "workflow gate edit", opts.json);
|
|
1608
1680
|
return okLine(`Edited candidate ${n} at ${pf.run.pausedNodeId}.`, { ok: true, runId, nodeId: pf.run.pausedNodeId, outputIndex: cand.outputIndex }, opts.json);
|
|
1609
1681
|
}
|
|
1682
|
+
function resolveGateSessionId(run) {
|
|
1683
|
+
const gateNode = (run.nodes ?? []).find((x) => x.nodeId === run.pausedNodeId);
|
|
1684
|
+
const artifact = (gateNode?.outputs ?? []).find((a) => a.type === "session" && a.port === "session" && !!a.sessionId);
|
|
1685
|
+
if (artifact)
|
|
1686
|
+
return artifact.sessionId;
|
|
1687
|
+
const sessions = run.sessions ?? [];
|
|
1688
|
+
const byNode = sessions.find((s) => s.nodeId === run.pausedNodeId);
|
|
1689
|
+
if (byNode)
|
|
1690
|
+
return byNode.sessionId;
|
|
1691
|
+
return undefined;
|
|
1692
|
+
}
|
|
1610
1693
|
export async function gatePushFlow(runId, message, opts, deps) {
|
|
1611
1694
|
const pf = await preflightPark(runId, "taste", "workflow gate push", opts.json, deps);
|
|
1612
1695
|
if (!pf.ok)
|
|
1613
1696
|
return pf.result;
|
|
1614
|
-
const
|
|
1615
|
-
if (!
|
|
1697
|
+
const sessionId = resolveGateSessionId(pf.run);
|
|
1698
|
+
if (!sessionId)
|
|
1616
1699
|
return errLine("This gate has no live session — nothing to push to.", opts.json);
|
|
1617
1700
|
if (!deps.postDashboard) {
|
|
1618
1701
|
return errLine("gate push is unavailable here (no dashboard client).", opts.json);
|
|
1619
1702
|
}
|
|
1620
|
-
const chat = await deps.postDashboard(CHAT_PATH, { sessionId
|
|
1703
|
+
const chat = await deps.postDashboard(CHAT_PATH, { sessionId, text: message }, { timeoutMs: CHAT_TIMEOUT_MS });
|
|
1621
1704
|
if (!chat.ok) {
|
|
1622
1705
|
const err = routeErrorText(chat.data, chat.status);
|
|
1623
1706
|
return {
|
package/dist/lib/format.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export declare function formatGeneration(data: Record<string, unknown>): string;
|
|
2
2
|
export declare function formatGenesisRun(data: Record<string, unknown>): string;
|
|
3
3
|
export declare function formatBrowse(generations: unknown[]): string;
|
|
4
|
+
export declare function formatApiError(res: {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
status: number;
|
|
7
|
+
data: unknown;
|
|
8
|
+
}): string;
|
|
4
9
|
export declare function formatError(res: {
|
|
5
10
|
ok: boolean;
|
|
6
11
|
status: number;
|
package/dist/lib/format.js
CHANGED
|
@@ -140,6 +140,37 @@ export function formatBrowse(generations) {
|
|
|
140
140
|
}
|
|
141
141
|
return lines.join("\n");
|
|
142
142
|
}
|
|
143
|
+
export function formatApiError(res) {
|
|
144
|
+
let message;
|
|
145
|
+
let remedy;
|
|
146
|
+
const d = res.data;
|
|
147
|
+
if (d && typeof d === "object" && !Array.isArray(d)) {
|
|
148
|
+
const rec = d;
|
|
149
|
+
const err = rec["error"];
|
|
150
|
+
if (typeof err === "string" && err) {
|
|
151
|
+
message = err;
|
|
152
|
+
}
|
|
153
|
+
else if (err && typeof err === "object") {
|
|
154
|
+
const e = err;
|
|
155
|
+
if (typeof e["message"] === "string" && e["message"])
|
|
156
|
+
message = e["message"];
|
|
157
|
+
if (typeof e["remedy"] === "string" && e["remedy"])
|
|
158
|
+
remedy = e["remedy"];
|
|
159
|
+
}
|
|
160
|
+
if (!message && typeof rec["message"] === "string" && rec["message"]) {
|
|
161
|
+
message = rec["message"];
|
|
162
|
+
}
|
|
163
|
+
if (!remedy && typeof rec["remedy"] === "string" && rec["remedy"]) {
|
|
164
|
+
remedy = rec["remedy"];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (typeof d === "string" && d.trim()) {
|
|
168
|
+
message = d.trim();
|
|
169
|
+
}
|
|
170
|
+
if (!message)
|
|
171
|
+
message = `HTTP ${res.status}`;
|
|
172
|
+
return remedy ? `${message}\nfix: ${remedy}` : message;
|
|
173
|
+
}
|
|
143
174
|
export function formatError(res) {
|
|
144
175
|
const lines = [];
|
|
145
176
|
lines.push(`## Error`);
|
package/dist/lib/keys-sync.js
CHANGED