@aicopycoders/exodus 2026.7.200-beta.11 → 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/assets/docs/CLAUDE.md +1 -0
- package/assets/docs/PIPELINES.md +26 -0
- package/assets/skills/exodus/SKILL.md +4 -1
- package/assets/skills/exodus-workflow/SKILL.md +235 -61
- package/dist/bin/exodus.js +2 -0
- package/dist/commands/bank.d.ts +88 -0
- package/dist/commands/bank.js +346 -0
- package/dist/commands/keys.js +1 -0
- package/dist/commands/session.d.ts +55 -0
- package/dist/commands/session.js +202 -0
- package/dist/commands/workflow.d.ts +192 -4
- package/dist/commands/workflow.js +1462 -69
- package/dist/lib/client.d.ts +8 -1
- package/dist/lib/client.js +30 -6
- package/dist/lib/format.d.ts +5 -0
- package/dist/lib/format.js +31 -0
- package/dist/lib/keys-sync.js +1 -0
- package/dist/lib/route-support.d.ts +4 -0
- package/dist/lib/route-support.js +22 -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,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
|
@@ -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,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.7.200-beta.
|
|
3
|
+
"version": "2026.7.200-beta.13",
|
|
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",
|