@aicopycoders/exodus 2026.7.200-beta.9 → 2026.8.1100
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/assets/docs/CLAUDE.md +2 -0
- package/assets/docs/PIPELINES.md +26 -0
- package/assets/skills/exodus/SKILL.md +5 -2
- package/assets/skills/exodus-meme/SKILL.md +44 -28
- package/assets/skills/exodus-winners/SKILL.md +29 -20
- package/assets/skills/exodus-workflow/SKILL.md +464 -63
- package/dist/bin/exodus.js +3 -0
- package/dist/commands/bank.d.ts +88 -0
- package/dist/commands/bank.js +346 -0
- package/dist/commands/browse.js +2 -1
- package/dist/commands/creative.js +2 -2
- package/dist/commands/genesis.js +16 -4
- package/dist/commands/keys.js +1 -0
- package/dist/commands/meme.js +9 -6
- package/dist/commands/primer.js +2 -1
- package/dist/commands/session.d.ts +55 -0
- package/dist/commands/session.js +202 -0
- package/dist/commands/status.js +4 -4
- package/dist/commands/swipe.js +4 -4
- package/dist/commands/template.js +2 -2
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +20 -0
- package/dist/commands/winners.js +13 -3
- package/dist/commands/workflow.d.ts +293 -8
- package/dist/commands/workflow.js +2396 -90
- package/dist/lib/client.d.ts +8 -1
- package/dist/lib/client.js +30 -6
- package/dist/lib/format.d.ts +7 -0
- package/dist/lib/format.js +52 -5
- package/dist/lib/intel-format.js +2 -1
- package/dist/lib/keys-sync.js +1 -0
- package/dist/lib/poll.js +7 -4
- package/dist/lib/route-support.d.ts +4 -0
- package/dist/lib/route-support.js +22 -0
- package/dist/lib/runStatus.d.ts +22 -0
- package/dist/lib/runStatus.js +139 -0
- package/dist/lib/workflowText.d.ts +3 -0
- package/dist/lib/workflowText.js +113 -0
- package/package.json +5 -2
package/dist/lib/client.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export declare function apiGet<T>(path: string, opts?: {
|
|
|
8
8
|
skipActiveBrand?: boolean;
|
|
9
9
|
activeBrandOverride?: string;
|
|
10
10
|
}): Promise<ApiResponse<T>>;
|
|
11
|
+
export declare function apiGetText(path: string, opts?: {
|
|
12
|
+
skipActiveBrand?: boolean;
|
|
13
|
+
activeBrandOverride?: string;
|
|
14
|
+
}): Promise<ApiResponse<string>>;
|
|
11
15
|
export declare function apiPost<T>(path: string, body: unknown, opts?: {
|
|
12
16
|
ccCommand?: string;
|
|
13
17
|
}): Promise<ApiResponse<T>>;
|
|
@@ -20,4 +24,7 @@ export declare function apiGetDashboard<T>(path: string, opts?: {
|
|
|
20
24
|
timeoutMs?: number;
|
|
21
25
|
activeBrandOverride?: string;
|
|
22
26
|
}): Promise<ApiResponse<T>>;
|
|
23
|
-
export declare function apiPostDashboard<T>(path: string, body: unknown
|
|
27
|
+
export declare function apiPostDashboard<T>(path: string, body: unknown, opts?: {
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
activeBrandOverride?: string;
|
|
30
|
+
}): Promise<ApiResponse<T>>;
|
package/dist/lib/client.js
CHANGED
|
@@ -108,6 +108,16 @@ export async function apiGet(path, opts) {
|
|
|
108
108
|
}
|
|
109
109
|
return { ok: res.ok, status: res.status, data: data };
|
|
110
110
|
}
|
|
111
|
+
export async function apiGetText(path, opts) {
|
|
112
|
+
const { apiUrl, apiKey } = getConfig();
|
|
113
|
+
const url = `${apiUrl}${path}`;
|
|
114
|
+
const res = await fetch(url, {
|
|
115
|
+
method: "GET",
|
|
116
|
+
headers: buildHeaders(apiKey, undefined, opts),
|
|
117
|
+
});
|
|
118
|
+
const text = await res.text();
|
|
119
|
+
return { ok: res.ok, status: res.status, data: text };
|
|
120
|
+
}
|
|
111
121
|
export async function apiPost(path, body, opts) {
|
|
112
122
|
const { apiUrl, apiKey } = getConfig();
|
|
113
123
|
const url = `${apiUrl}${path}`;
|
|
@@ -187,15 +197,29 @@ export async function apiGetDashboard(path, opts) {
|
|
|
187
197
|
}
|
|
188
198
|
return { ok: res.ok, status: res.status, data: data };
|
|
189
199
|
}
|
|
190
|
-
export async function apiPostDashboard(path, body) {
|
|
200
|
+
export async function apiPostDashboard(path, body, opts) {
|
|
191
201
|
const { apiKey } = getConfig();
|
|
192
202
|
const url = `${getDashboardUrl()}${path}`;
|
|
193
|
-
const headers = buildHeaders(apiKey
|
|
194
|
-
|
|
195
|
-
method: "POST",
|
|
196
|
-
headers,
|
|
197
|
-
body: JSON.stringify(body),
|
|
203
|
+
const headers = buildHeaders(apiKey, undefined, {
|
|
204
|
+
activeBrandOverride: opts?.activeBrandOverride,
|
|
198
205
|
});
|
|
206
|
+
const controller = opts?.timeoutMs ? new AbortController() : undefined;
|
|
207
|
+
const timer = controller
|
|
208
|
+
? setTimeout(() => controller.abort(), opts.timeoutMs)
|
|
209
|
+
: undefined;
|
|
210
|
+
let res;
|
|
211
|
+
try {
|
|
212
|
+
res = await fetch(url, {
|
|
213
|
+
method: "POST",
|
|
214
|
+
headers,
|
|
215
|
+
body: JSON.stringify(body),
|
|
216
|
+
signal: controller?.signal,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
if (timer)
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
}
|
|
199
223
|
const text = await res.text();
|
|
200
224
|
let data;
|
|
201
225
|
try {
|
package/dist/lib/format.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
export declare function displayRunStatus(raw: unknown, missing?: string): string;
|
|
2
|
+
export declare function tickerRunStatus(raw: unknown): string;
|
|
1
3
|
export declare function formatGeneration(data: Record<string, unknown>): string;
|
|
2
4
|
export declare function formatGenesisRun(data: Record<string, unknown>): string;
|
|
3
5
|
export declare function formatBrowse(generations: unknown[]): string;
|
|
6
|
+
export declare function formatApiError(res: {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
status: number;
|
|
9
|
+
data: unknown;
|
|
10
|
+
}): string;
|
|
4
11
|
export declare function formatError(res: {
|
|
5
12
|
ok: boolean;
|
|
6
13
|
status: number;
|
package/dist/lib/format.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import { normalizeRunStatus, runStatusLabel } from "./runStatus.js";
|
|
2
|
+
export function displayRunStatus(raw, missing = "unknown") {
|
|
3
|
+
return typeof raw === "string" && raw.trim() ? runStatusLabel(raw) : missing;
|
|
4
|
+
}
|
|
5
|
+
const GENERIC_ACTIVE_STATUSES = new Set([
|
|
6
|
+
"queued",
|
|
7
|
+
"pending",
|
|
8
|
+
"running",
|
|
9
|
+
"processing",
|
|
10
|
+
"in-progress",
|
|
11
|
+
]);
|
|
12
|
+
export function tickerRunStatus(raw) {
|
|
13
|
+
if (typeof raw !== "string" || !raw.trim())
|
|
14
|
+
return "";
|
|
15
|
+
const canonical = normalizeRunStatus(raw);
|
|
16
|
+
const isPhaseWord = (canonical === "running" || canonical === "queued") && !GENERIC_ACTIVE_STATUSES.has(raw);
|
|
17
|
+
return isPhaseWord ? raw : runStatusLabel(raw);
|
|
18
|
+
}
|
|
1
19
|
function stepIcon(status) {
|
|
2
20
|
if (status === "completed" || status === "done")
|
|
3
21
|
return "✓";
|
|
@@ -29,9 +47,8 @@ function formatSteps(steps) {
|
|
|
29
47
|
}
|
|
30
48
|
export function formatGeneration(data) {
|
|
31
49
|
const lines = [];
|
|
32
|
-
const status = data["status"] ?? "unknown";
|
|
33
50
|
lines.push(`## Generation Result`);
|
|
34
|
-
lines.push(`**Status:** ${status}`);
|
|
51
|
+
lines.push(`**Status:** ${displayRunStatus(data["status"])}`);
|
|
35
52
|
if (data["agentName"] || data["agent"]) {
|
|
36
53
|
lines.push(`**Agent:** ${data["agentName"] ?? data["agent"]}`);
|
|
37
54
|
}
|
|
@@ -69,9 +86,8 @@ export function formatGeneration(data) {
|
|
|
69
86
|
}
|
|
70
87
|
export function formatGenesisRun(data) {
|
|
71
88
|
const lines = [];
|
|
72
|
-
const status = data["status"] ?? "unknown";
|
|
73
89
|
lines.push(`## Genesis Run`);
|
|
74
|
-
lines.push(`**Status:** ${status}`);
|
|
90
|
+
lines.push(`**Status:** ${displayRunStatus(data["status"])}`);
|
|
75
91
|
if (data["awarenessLevel"] || data["awareness"]) {
|
|
76
92
|
lines.push(`**Awareness Level:** ${data["awarenessLevel"] ?? data["awareness"]}`);
|
|
77
93
|
}
|
|
@@ -134,12 +150,43 @@ export function formatBrowse(generations) {
|
|
|
134
150
|
date = isNaN(parsed.getTime()) ? "—" : parsed.toISOString().slice(0, 10);
|
|
135
151
|
}
|
|
136
152
|
const agent = gen["agentName"] ?? gen["agentId"] ?? gen["agent"] ?? gen["pipeline"] ?? "—";
|
|
137
|
-
const status = gen["status"]
|
|
153
|
+
const status = displayRunStatus(gen["status"], "—");
|
|
138
154
|
const doc = gen["googleDocUrl"] ?? gen["docUrl"] ?? "—";
|
|
139
155
|
lines.push(`${date} | ${agent} | ${status} | ${doc}`);
|
|
140
156
|
}
|
|
141
157
|
return lines.join("\n");
|
|
142
158
|
}
|
|
159
|
+
export function formatApiError(res) {
|
|
160
|
+
let message;
|
|
161
|
+
let remedy;
|
|
162
|
+
const d = res.data;
|
|
163
|
+
if (d && typeof d === "object" && !Array.isArray(d)) {
|
|
164
|
+
const rec = d;
|
|
165
|
+
const err = rec["error"];
|
|
166
|
+
if (typeof err === "string" && err) {
|
|
167
|
+
message = err;
|
|
168
|
+
}
|
|
169
|
+
else if (err && typeof err === "object") {
|
|
170
|
+
const e = err;
|
|
171
|
+
if (typeof e["message"] === "string" && e["message"])
|
|
172
|
+
message = e["message"];
|
|
173
|
+
if (typeof e["remedy"] === "string" && e["remedy"])
|
|
174
|
+
remedy = e["remedy"];
|
|
175
|
+
}
|
|
176
|
+
if (!message && typeof rec["message"] === "string" && rec["message"]) {
|
|
177
|
+
message = rec["message"];
|
|
178
|
+
}
|
|
179
|
+
if (!remedy && typeof rec["remedy"] === "string" && rec["remedy"]) {
|
|
180
|
+
remedy = rec["remedy"];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (typeof d === "string" && d.trim()) {
|
|
184
|
+
message = d.trim();
|
|
185
|
+
}
|
|
186
|
+
if (!message)
|
|
187
|
+
message = `HTTP ${res.status}`;
|
|
188
|
+
return remedy ? `${message}\nfix: ${remedy}` : message;
|
|
189
|
+
}
|
|
143
190
|
export function formatError(res) {
|
|
144
191
|
const lines = [];
|
|
145
192
|
lines.push(`## Error`);
|
package/dist/lib/intel-format.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { displayRunStatus } from "./format.js";
|
|
1
2
|
export function formatIntelResult(data) {
|
|
2
3
|
const lines = [];
|
|
3
4
|
lines.push("## Intel Analysis Result");
|
|
4
|
-
lines.push(`**Status:** ${data["status"]
|
|
5
|
+
lines.push(`**Status:** ${displayRunStatus(data["status"])}`);
|
|
5
6
|
if (data["mode"])
|
|
6
7
|
lines.push(`**Mode:** ${data["mode"]}`);
|
|
7
8
|
if (data["phase"] !== undefined)
|
package/dist/lib/keys-sync.js
CHANGED
package/dist/lib/poll.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { apiGet } from "./client.js";
|
|
2
|
-
|
|
2
|
+
import { isTerminalRunStatus, normalizeRunStatus } from "./runStatus.js";
|
|
3
3
|
function delay(ms) {
|
|
4
4
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
5
|
}
|
|
6
6
|
export async function pollUntilDone(opts) {
|
|
7
7
|
const { path, intervalMs = 3000, timeoutMs = 600_000, terminalStatuses, onProgress, isDone, } = opts;
|
|
8
|
-
const
|
|
8
|
+
const extraTerminal = new Set(terminalStatuses ?? []);
|
|
9
9
|
const deadline = Date.now() + timeoutMs;
|
|
10
10
|
while (true) {
|
|
11
11
|
const res = await apiGet(path);
|
|
@@ -16,10 +16,13 @@ export async function pollUntilDone(opts) {
|
|
|
16
16
|
if (!res.ok) {
|
|
17
17
|
return { ok: false, data: res.data, timedOut: false };
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
const canonical = status ? normalizeRunStatus(status) : undefined;
|
|
20
|
+
if (canonical === "failed" || canonical === "cancelled") {
|
|
20
21
|
return { ok: false, data: res.data, timedOut: false };
|
|
21
22
|
}
|
|
22
|
-
const statusTerminal = status
|
|
23
|
+
const statusTerminal = status
|
|
24
|
+
? isTerminalRunStatus(status) || extraTerminal.has(status)
|
|
25
|
+
: false;
|
|
23
26
|
const customDone = isDone ? isDone(res.data) : true;
|
|
24
27
|
if (statusTerminal && customDone) {
|
|
25
28
|
return { ok: true, data: res.data, timedOut: false };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApiResponse } from "./client.js";
|
|
2
|
+
import { type Channel } from "./channel.js";
|
|
3
|
+
export declare function isSemanticApiError(data: unknown): boolean;
|
|
4
|
+
export declare function missingRouteLine(res: ApiResponse<unknown>, verb: string, channel?: Channel): string | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getChannel } from "./channel.js";
|
|
2
|
+
export function isSemanticApiError(data) {
|
|
3
|
+
if (typeof data !== "object" || data === null)
|
|
4
|
+
return false;
|
|
5
|
+
const record = data;
|
|
6
|
+
const topCode = record.code;
|
|
7
|
+
if (typeof topCode === "string" && topCode.length > 0)
|
|
8
|
+
return true;
|
|
9
|
+
const err = record.error;
|
|
10
|
+
if (typeof err !== "object" || err === null)
|
|
11
|
+
return false;
|
|
12
|
+
const nestedCode = err.code;
|
|
13
|
+
return typeof nestedCode === "string" && nestedCode.length > 0;
|
|
14
|
+
}
|
|
15
|
+
export function missingRouteLine(res, verb, channel = getChannel()) {
|
|
16
|
+
if (res.status !== 404)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (isSemanticApiError(res.data))
|
|
19
|
+
return undefined;
|
|
20
|
+
return (`this server does not support ${verb} yet — ` +
|
|
21
|
+
`your backend has not been updated (channel: ${channel})`);
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const RUN_STATUSES: readonly ["queued", "running", "awaiting-approval", "succeeded", "succeeded-with-warnings", "failed", "cancelled"];
|
|
2
|
+
export type RunStatus = (typeof RUN_STATUSES)[number];
|
|
3
|
+
export declare const RUN_STATUS_LABELS: Record<RunStatus, string>;
|
|
4
|
+
export type RunStatusTone = "pending" | "active" | "attention" | "success" | "warning" | "danger" | "muted";
|
|
5
|
+
export declare const RUN_STATUS_TONES: Record<RunStatus, RunStatusTone>;
|
|
6
|
+
export declare const LEGACY_RUN_STATUS_ALIASES: Record<string, RunStatus>;
|
|
7
|
+
export declare function normalizeRunStatus(raw: string | null | undefined, fallback?: RunStatus): RunStatus;
|
|
8
|
+
export declare function runStatusLabel(raw: string | null | undefined): string;
|
|
9
|
+
export declare function runStatusTone(raw: string | null | undefined): RunStatusTone;
|
|
10
|
+
export declare const TERMINAL_RUN_STATUSES: readonly ["succeeded", "succeeded-with-warnings", "failed", "cancelled"];
|
|
11
|
+
export declare const ACTIVE_RUN_STATUSES: readonly ["queued", "running"];
|
|
12
|
+
export declare function isTerminalRunStatus(raw: string | null | undefined): boolean;
|
|
13
|
+
export declare function isActiveRunStatus(raw: string | null | undefined): boolean;
|
|
14
|
+
export declare const WORKFLOW_RUN_STATUS_VALUES: readonly ["queued", "running", "awaiting-approval", "succeeded", "succeeded-with-warnings", "failed", "cancelled"];
|
|
15
|
+
export declare const LEGACY_WORKFLOW_RUN_STATUS_VALUES: readonly ["awaiting-review", "completed", "partial", "canceled"];
|
|
16
|
+
export declare function storedWorkflowStatusForms(status: RunStatus): string[];
|
|
17
|
+
export declare function workflowRunPresentation(rawStatus: string | null | undefined, pauseReason?: string | null): {
|
|
18
|
+
status: RunStatus;
|
|
19
|
+
label: string;
|
|
20
|
+
tone: RunStatusTone;
|
|
21
|
+
detail?: string;
|
|
22
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export const RUN_STATUSES = [
|
|
2
|
+
"queued",
|
|
3
|
+
"running",
|
|
4
|
+
"awaiting-approval",
|
|
5
|
+
"succeeded",
|
|
6
|
+
"succeeded-with-warnings",
|
|
7
|
+
"failed",
|
|
8
|
+
"cancelled",
|
|
9
|
+
];
|
|
10
|
+
export const RUN_STATUS_LABELS = {
|
|
11
|
+
queued: "Queued",
|
|
12
|
+
running: "Running",
|
|
13
|
+
"awaiting-approval": "Awaiting approval",
|
|
14
|
+
succeeded: "Succeeded",
|
|
15
|
+
"succeeded-with-warnings": "Succeeded with warnings",
|
|
16
|
+
failed: "Failed",
|
|
17
|
+
cancelled: "Cancelled",
|
|
18
|
+
};
|
|
19
|
+
export const RUN_STATUS_TONES = {
|
|
20
|
+
queued: "pending",
|
|
21
|
+
running: "active",
|
|
22
|
+
"awaiting-approval": "attention",
|
|
23
|
+
succeeded: "success",
|
|
24
|
+
"succeeded-with-warnings": "warning",
|
|
25
|
+
failed: "danger",
|
|
26
|
+
cancelled: "muted",
|
|
27
|
+
};
|
|
28
|
+
export const LEGACY_RUN_STATUS_ALIASES = {
|
|
29
|
+
pending: "queued",
|
|
30
|
+
processing: "running",
|
|
31
|
+
"in-progress": "running",
|
|
32
|
+
walking: "running",
|
|
33
|
+
mining: "running",
|
|
34
|
+
scoring: "running",
|
|
35
|
+
selecting: "running",
|
|
36
|
+
writing: "running",
|
|
37
|
+
assembling: "running",
|
|
38
|
+
discovery: "running",
|
|
39
|
+
analyzing: "running",
|
|
40
|
+
combining: "running",
|
|
41
|
+
phase1_complete: "running",
|
|
42
|
+
phase2_complete: "running",
|
|
43
|
+
"phase1-running": "running",
|
|
44
|
+
"phase1-completed": "running",
|
|
45
|
+
"phase2-running": "running",
|
|
46
|
+
"script-prep-running": "running",
|
|
47
|
+
"parser-running": "running",
|
|
48
|
+
"storyboard-running": "running",
|
|
49
|
+
"cast-sheet-running": "running",
|
|
50
|
+
"storyboard-images-running": "running",
|
|
51
|
+
"voice-and-clips-running": "running",
|
|
52
|
+
stitching: "running",
|
|
53
|
+
extracting: "running",
|
|
54
|
+
ranking: "running",
|
|
55
|
+
conceptualizing: "running",
|
|
56
|
+
rendering: "running",
|
|
57
|
+
"awaiting-review": "awaiting-approval",
|
|
58
|
+
awaiting_hook_selection: "awaiting-approval",
|
|
59
|
+
paused: "awaiting-approval",
|
|
60
|
+
"script-review": "awaiting-approval",
|
|
61
|
+
"storyboard-review": "awaiting-approval",
|
|
62
|
+
"cast-sheet-review": "awaiting-approval",
|
|
63
|
+
completed: "succeeded",
|
|
64
|
+
complete: "succeeded",
|
|
65
|
+
done: "succeeded",
|
|
66
|
+
partial: "succeeded-with-warnings",
|
|
67
|
+
"partial-success": "succeeded-with-warnings",
|
|
68
|
+
"partial-error": "succeeded-with-warnings",
|
|
69
|
+
error: "failed",
|
|
70
|
+
stalled: "failed",
|
|
71
|
+
"verification-failed": "failed",
|
|
72
|
+
canceled: "cancelled",
|
|
73
|
+
superseded: "cancelled",
|
|
74
|
+
};
|
|
75
|
+
export function normalizeRunStatus(raw, fallback = "running") {
|
|
76
|
+
if (!raw)
|
|
77
|
+
return fallback;
|
|
78
|
+
if (RUN_STATUSES.includes(raw))
|
|
79
|
+
return raw;
|
|
80
|
+
return LEGACY_RUN_STATUS_ALIASES[raw] ?? fallback;
|
|
81
|
+
}
|
|
82
|
+
export function runStatusLabel(raw) {
|
|
83
|
+
return RUN_STATUS_LABELS[normalizeRunStatus(raw)];
|
|
84
|
+
}
|
|
85
|
+
export function runStatusTone(raw) {
|
|
86
|
+
return RUN_STATUS_TONES[normalizeRunStatus(raw)];
|
|
87
|
+
}
|
|
88
|
+
export const TERMINAL_RUN_STATUSES = [
|
|
89
|
+
"succeeded",
|
|
90
|
+
"succeeded-with-warnings",
|
|
91
|
+
"failed",
|
|
92
|
+
"cancelled",
|
|
93
|
+
];
|
|
94
|
+
export const ACTIVE_RUN_STATUSES = ["queued", "running"];
|
|
95
|
+
export function isTerminalRunStatus(raw) {
|
|
96
|
+
return TERMINAL_RUN_STATUSES.includes(normalizeRunStatus(raw));
|
|
97
|
+
}
|
|
98
|
+
export function isActiveRunStatus(raw) {
|
|
99
|
+
return ACTIVE_RUN_STATUSES.includes(normalizeRunStatus(raw));
|
|
100
|
+
}
|
|
101
|
+
export const WORKFLOW_RUN_STATUS_VALUES = RUN_STATUSES;
|
|
102
|
+
export const LEGACY_WORKFLOW_RUN_STATUS_VALUES = [
|
|
103
|
+
"awaiting-review",
|
|
104
|
+
"completed",
|
|
105
|
+
"partial",
|
|
106
|
+
"canceled",
|
|
107
|
+
];
|
|
108
|
+
const LEGACY_WORKFLOW_TWIN = {
|
|
109
|
+
"awaiting-approval": "awaiting-review",
|
|
110
|
+
succeeded: "completed",
|
|
111
|
+
"succeeded-with-warnings": "partial",
|
|
112
|
+
cancelled: "canceled",
|
|
113
|
+
};
|
|
114
|
+
export function storedWorkflowStatusForms(status) {
|
|
115
|
+
const twin = LEGACY_WORKFLOW_TWIN[status];
|
|
116
|
+
return twin ? [status, twin] : [status];
|
|
117
|
+
}
|
|
118
|
+
export function workflowRunPresentation(rawStatus, pauseReason) {
|
|
119
|
+
let status = normalizeRunStatus(rawStatus);
|
|
120
|
+
let detail;
|
|
121
|
+
if (status === "awaiting-approval") {
|
|
122
|
+
if (pauseReason === "call") {
|
|
123
|
+
status = "running";
|
|
124
|
+
detail = "sub-workflow";
|
|
125
|
+
}
|
|
126
|
+
else if (pauseReason === "slots") {
|
|
127
|
+
detail = "needs inputs";
|
|
128
|
+
}
|
|
129
|
+
else if (pauseReason === "repair") {
|
|
130
|
+
detail = "needs repair";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
status,
|
|
135
|
+
label: RUN_STATUS_LABELS[status],
|
|
136
|
+
tone: RUN_STATUS_TONES[status],
|
|
137
|
+
detail,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import yaml from "js-yaml";
|
|
2
|
+
function canonicalizeValue(value) {
|
|
3
|
+
if (Array.isArray(value))
|
|
4
|
+
return value.map(canonicalizeValue);
|
|
5
|
+
if (value !== null && typeof value === "object") {
|
|
6
|
+
const src = value;
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const key of Object.keys(src).sort()) {
|
|
9
|
+
out[key] = canonicalizeValue(src[key]);
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function orderKeys(record, fixedOrder) {
|
|
16
|
+
const out = {};
|
|
17
|
+
for (const key of fixedOrder) {
|
|
18
|
+
if (record[key] !== undefined)
|
|
19
|
+
out[key] = record[key];
|
|
20
|
+
}
|
|
21
|
+
const fixed = new Set(fixedOrder);
|
|
22
|
+
for (const key of Object.keys(record).sort()) {
|
|
23
|
+
if (!fixed.has(key) && record[key] !== undefined)
|
|
24
|
+
out[key] = record[key];
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function canonicalizeContract(contract) {
|
|
29
|
+
const root = contract;
|
|
30
|
+
const nodes = Array.isArray(root.nodes) ? root.nodes : [];
|
|
31
|
+
const edges = Array.isArray(root.edges) ? root.edges : [];
|
|
32
|
+
const canonicalNodes = nodes.map((n) => {
|
|
33
|
+
const rec = { ...n };
|
|
34
|
+
if (rec.position !== null && typeof rec.position === "object") {
|
|
35
|
+
rec.position = orderKeys(rec.position, ["x", "y"]);
|
|
36
|
+
}
|
|
37
|
+
if (rec.config !== null && typeof rec.config === "object") {
|
|
38
|
+
rec.config = canonicalizeValue(rec.config);
|
|
39
|
+
}
|
|
40
|
+
return orderKeys(rec, ["id", "kind", "position", "config"]);
|
|
41
|
+
});
|
|
42
|
+
const canonicalEdges = edges.map((e) => orderKeys({ ...e }, [
|
|
43
|
+
"id",
|
|
44
|
+
"source",
|
|
45
|
+
"sourceHandle",
|
|
46
|
+
"target",
|
|
47
|
+
"targetHandle",
|
|
48
|
+
]));
|
|
49
|
+
const rootRecord = {
|
|
50
|
+
...root,
|
|
51
|
+
nodes: canonicalNodes,
|
|
52
|
+
edges: canonicalEdges,
|
|
53
|
+
};
|
|
54
|
+
if (Array.isArray(root.slots)) {
|
|
55
|
+
rootRecord.slots = root.slots.map((s) => orderKeys({ ...s }, [
|
|
56
|
+
"id",
|
|
57
|
+
"label",
|
|
58
|
+
"state",
|
|
59
|
+
"nodeId",
|
|
60
|
+
"configKey",
|
|
61
|
+
"value",
|
|
62
|
+
"hint",
|
|
63
|
+
]));
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(root.triggers)) {
|
|
66
|
+
rootRecord.triggers = root.triggers.map((t) => orderKeys({ ...t }, [
|
|
67
|
+
"type",
|
|
68
|
+
"event",
|
|
69
|
+
"cron",
|
|
70
|
+
"enabled",
|
|
71
|
+
]));
|
|
72
|
+
}
|
|
73
|
+
return orderKeys(rootRecord, [
|
|
74
|
+
"contract",
|
|
75
|
+
"version",
|
|
76
|
+
"workflowId",
|
|
77
|
+
"updatedAt",
|
|
78
|
+
"name",
|
|
79
|
+
"description",
|
|
80
|
+
"slots",
|
|
81
|
+
"triggers",
|
|
82
|
+
"nodes",
|
|
83
|
+
"edges",
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
export function workflowToYaml(contract) {
|
|
87
|
+
return yaml.dump(canonicalizeContract(contract), {
|
|
88
|
+
schema: yaml.CORE_SCHEMA,
|
|
89
|
+
lineWidth: -1,
|
|
90
|
+
noRefs: true,
|
|
91
|
+
sortKeys: false,
|
|
92
|
+
indent: 2,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export function parseWorkflowText(text) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(text);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
}
|
|
101
|
+
let loaded;
|
|
102
|
+
try {
|
|
103
|
+
loaded = yaml.load(text, { schema: yaml.CORE_SCHEMA });
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
107
|
+
throw new Error(`workflow file is not valid YAML or JSON: ${detail}`);
|
|
108
|
+
}
|
|
109
|
+
if (typeof loaded !== "object" || loaded === null || Array.isArray(loaded)) {
|
|
110
|
+
throw new Error("workflow file is not valid YAML or JSON");
|
|
111
|
+
}
|
|
112
|
+
return loaded;
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aicopycoders/exodus",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.8.1100",
|
|
4
4
|
"description": "CLI + Claude Code skills for the Copycoders Exodus ad pipeline",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,8 +28,11 @@
|
|
|
28
28
|
"prepack": "npm run build && npm run bundle-assets",
|
|
29
29
|
"dev": "tsx bin/exodus.ts"
|
|
30
30
|
},
|
|
31
|
-
"dependencies": {
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"js-yaml": "^4.1.1"
|
|
33
|
+
},
|
|
32
34
|
"devDependencies": {
|
|
35
|
+
"@types/js-yaml": "^4.0.9",
|
|
33
36
|
"@types/node": "^20.0.0",
|
|
34
37
|
"esbuild": "^0.25.0",
|
|
35
38
|
"tsx": "^4.7.0",
|