@aicopycoders/exodus 2026.7.200-beta.3 → 2026.7.200-beta.5
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/assets/skills/exodus/SKILL.md +1 -0
- package/assets/skills/exodus-workflow/SKILL.md +173 -0
- package/dist/bin/exodus.js +3 -1
- package/dist/commands/brand.js +159 -0
- package/dist/commands/genesis.js +1 -1
- package/dist/commands/primer.js +4 -2
- package/dist/commands/segment.d.ts +57 -0
- package/dist/commands/segment.js +274 -0
- package/dist/commands/workflow.d.ts +271 -0
- package/dist/commands/workflow.js +744 -0
- package/dist/lib/image-sniff.d.ts +6 -0
- package/dist/lib/image-sniff.js +18 -0
- package/dist/lib/template-constants.d.ts +1 -1
- package/dist/lib/template-constants.js +102 -0
- package/package.json +1 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { apiGet, apiPost } from "../lib/client.js";
|
|
3
|
+
import { formatError } from "../lib/format.js";
|
|
4
|
+
export const helpText = `
|
|
5
|
+
exodus segment — Segment map + personas (WHAT / WHO / WHY), agentic I/O
|
|
6
|
+
|
|
7
|
+
The JSON contract is the edit surface: export the map, edit the file, import it
|
|
8
|
+
back. Import is a FULL REPLACE, slug-stable by name-match — existing outcomes and
|
|
9
|
+
values keep their slugs, new names get new ones, and names absent from the file
|
|
10
|
+
are deleted. Personas the new map orphans are ARCHIVED (never deleted).
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
exodus segment show Human-readable summary of the map
|
|
14
|
+
exodus segment show --json Raw normalized map JSON
|
|
15
|
+
exodus segment export [--out <file>] Contract JSON (map + personas) to stdout or a file
|
|
16
|
+
exodus segment import <file> [--yes] Import a contract JSON file (dry-run first)
|
|
17
|
+
exodus segment personas List saved personas (incl. archived)
|
|
18
|
+
exodus segment personas --json Raw persona list JSON
|
|
19
|
+
|
|
20
|
+
Flags:
|
|
21
|
+
--json Machine-readable JSON output (show / personas)
|
|
22
|
+
--out <file> Write export to a file instead of stdout
|
|
23
|
+
--yes Apply a destructive import (deletes segments / archives personas)
|
|
24
|
+
|
|
25
|
+
Notes:
|
|
26
|
+
• All operations scope to your active brand. Check with: exodus brand current
|
|
27
|
+
• 'import' ALWAYS previews via a server dry-run first and prints what would
|
|
28
|
+
change. If anything would be deleted at ANY layer (outcome, sub-slice,
|
|
29
|
+
demo/facet value) or a persona archived/deleted, it refuses to apply
|
|
30
|
+
without --yes and exits non-zero (never prompts — agents hang on prompts).
|
|
31
|
+
• 'import' on a workspace with no map yet just creates it.
|
|
32
|
+
• 'export' output imports back unchanged — it is the contract document.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
exodus segment show
|
|
36
|
+
exodus segment export --out segments.json
|
|
37
|
+
exodus segment import segments.json
|
|
38
|
+
exodus segment import segments.json --yes
|
|
39
|
+
exodus segment personas --json
|
|
40
|
+
`.trim();
|
|
41
|
+
export function asSummary(data) {
|
|
42
|
+
const d = (typeof data === "object" && data !== null ? data : {});
|
|
43
|
+
const list = (v) => (Array.isArray(v) ? v : []);
|
|
44
|
+
return {
|
|
45
|
+
added: list(d.added),
|
|
46
|
+
keptRenamed: list(d.keptRenamed),
|
|
47
|
+
deleted: list(d.deleted),
|
|
48
|
+
subsDeleted: list(d.subsDeleted),
|
|
49
|
+
demoValuesDeleted: list(d.demoValuesDeleted),
|
|
50
|
+
facetValuesDeleted: list(d.facetValuesDeleted),
|
|
51
|
+
personasArchived: list(d.personasArchived),
|
|
52
|
+
personasDeleted: list(d.personasDeleted),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function isDestructive(summary) {
|
|
56
|
+
return (summary.deleted.length > 0 ||
|
|
57
|
+
summary.subsDeleted.length > 0 ||
|
|
58
|
+
summary.demoValuesDeleted.length > 0 ||
|
|
59
|
+
summary.facetValuesDeleted.length > 0 ||
|
|
60
|
+
summary.personasArchived.length > 0 ||
|
|
61
|
+
summary.personasDeleted.length > 0);
|
|
62
|
+
}
|
|
63
|
+
function line(label, names) {
|
|
64
|
+
const detail = names.length > 0 ? ` (${names.join(", ")})` : "";
|
|
65
|
+
return ` ${label.padEnd(24)} ${String(names.length).padStart(3)}${detail}`;
|
|
66
|
+
}
|
|
67
|
+
export function formatImportSummary(summary) {
|
|
68
|
+
return [
|
|
69
|
+
"Import preview:",
|
|
70
|
+
line("+ outcomes added", summary.added),
|
|
71
|
+
line("~ outcomes kept", summary.keptRenamed),
|
|
72
|
+
line("- outcomes deleted", summary.deleted),
|
|
73
|
+
line("- subs deleted", summary.subsDeleted),
|
|
74
|
+
line("- demo values deleted", summary.demoValuesDeleted),
|
|
75
|
+
line("- facet values deleted", summary.facetValuesDeleted),
|
|
76
|
+
line("personas archived", summary.personasArchived),
|
|
77
|
+
line("personas deleted", summary.personasDeleted),
|
|
78
|
+
].join("\n");
|
|
79
|
+
}
|
|
80
|
+
export function formatMapSummary(map) {
|
|
81
|
+
const lines = [];
|
|
82
|
+
lines.push(`Segment map — ${map.brandLabel} (product: ${map.productWord})`);
|
|
83
|
+
lines.push(`\nOutcomes (${map.outcomes.length}):`);
|
|
84
|
+
for (const o of map.outcomes) {
|
|
85
|
+
const lens = o.lens ? ` [${o.lens}]` : "";
|
|
86
|
+
const subs = o.subs.length > 0 ? ` · subs: ${o.subs.map((s) => s.name).join(", ")}` : "";
|
|
87
|
+
lines.push(` ${o.slug.padEnd(24)} ${o.name}${lens}${subs}`);
|
|
88
|
+
}
|
|
89
|
+
const groupBlock = (title, groups) => {
|
|
90
|
+
lines.push(`\n${title}:`);
|
|
91
|
+
for (const g of groups) {
|
|
92
|
+
const vals = g.values.length > 0 ? g.values.map((val) => val.name).join(", ") : "—";
|
|
93
|
+
lines.push(` ${g.name} (${g.values.length}): ${vals}`);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
groupBlock("Demographics (WHO)", map.demoGroups);
|
|
97
|
+
groupBlock("Facets (WHY)", map.facetFamilies);
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}
|
|
100
|
+
const IMPORT_PATH = "/api/v2/segments/import";
|
|
101
|
+
export async function importFlow(file, opts, deps) {
|
|
102
|
+
const calls = [];
|
|
103
|
+
let text;
|
|
104
|
+
try {
|
|
105
|
+
text = deps.readFile(file);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return { code: 1, lines: [`Error: file not found: ${file}`], calls };
|
|
109
|
+
}
|
|
110
|
+
let contract;
|
|
111
|
+
try {
|
|
112
|
+
contract = JSON.parse(text);
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
116
|
+
return { code: 1, lines: [`Error: ${file} is not valid JSON: ${msg}`], calls };
|
|
117
|
+
}
|
|
118
|
+
if (typeof contract !== "object" || contract === null || Array.isArray(contract)) {
|
|
119
|
+
return { code: 1, lines: [`Error: ${file} is not a segment map (expected a JSON object).`], calls };
|
|
120
|
+
}
|
|
121
|
+
const clean = { ...contract };
|
|
122
|
+
delete clean.dryRun;
|
|
123
|
+
calls.push({ dryRun: true });
|
|
124
|
+
const dry = await deps.post(IMPORT_PATH, { ...clean, dryRun: true });
|
|
125
|
+
if (!dry.ok) {
|
|
126
|
+
const lines = opts.json
|
|
127
|
+
? [JSON.stringify({ ok: false, status: dry.status, data: dry.data })]
|
|
128
|
+
: [formatError({ ok: dry.ok, status: dry.status, data: dry.data })];
|
|
129
|
+
return { code: 1, lines, calls };
|
|
130
|
+
}
|
|
131
|
+
const summary = asSummary(dry.data);
|
|
132
|
+
const destructive = isDestructive(summary);
|
|
133
|
+
if (destructive && !opts.yes) {
|
|
134
|
+
const lines = opts.json
|
|
135
|
+
? [JSON.stringify({ ok: false, applied: false, destructive: true, summary })]
|
|
136
|
+
: [
|
|
137
|
+
formatImportSummary(summary),
|
|
138
|
+
"",
|
|
139
|
+
"This import is destructive (deletes outcomes, sub-slices, or values,",
|
|
140
|
+
"and/or archives or deletes personas). Re-run with --yes to apply it.",
|
|
141
|
+
];
|
|
142
|
+
return { code: 1, lines, calls };
|
|
143
|
+
}
|
|
144
|
+
calls.push({ dryRun: false });
|
|
145
|
+
const real = await deps.post(IMPORT_PATH, clean);
|
|
146
|
+
if (!real.ok) {
|
|
147
|
+
const lines = opts.json
|
|
148
|
+
? [JSON.stringify({ ok: false, status: real.status, data: real.data })]
|
|
149
|
+
: [formatError({ ok: real.ok, status: real.status, data: real.data })];
|
|
150
|
+
return { code: 1, lines, calls };
|
|
151
|
+
}
|
|
152
|
+
const applied = asSummary(real.data);
|
|
153
|
+
const lines = opts.json
|
|
154
|
+
? [JSON.stringify({ ok: true, applied: true, destructive, summary: applied })]
|
|
155
|
+
: [formatImportSummary(applied), "", "Imported."];
|
|
156
|
+
return { code: 0, lines, calls };
|
|
157
|
+
}
|
|
158
|
+
export async function run(flags) {
|
|
159
|
+
const positional = parsePositional();
|
|
160
|
+
const [sub, ...rest] = positional;
|
|
161
|
+
if (!sub || sub === "show")
|
|
162
|
+
return runShow(flags);
|
|
163
|
+
if (sub === "export")
|
|
164
|
+
return runExport(flags);
|
|
165
|
+
if (sub === "import")
|
|
166
|
+
return runImport(rest, flags);
|
|
167
|
+
if (sub === "personas")
|
|
168
|
+
return runPersonas(flags);
|
|
169
|
+
console.error(`Unknown subcommand: "${sub}"\n`);
|
|
170
|
+
console.log(helpText);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
function parsePositional() {
|
|
174
|
+
const args = process.argv.slice(3);
|
|
175
|
+
const out = [];
|
|
176
|
+
let i = 0;
|
|
177
|
+
while (i < args.length) {
|
|
178
|
+
const a = args[i];
|
|
179
|
+
if (a.startsWith("--")) {
|
|
180
|
+
const next = args[i + 1];
|
|
181
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
182
|
+
i += 2;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
i++;
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
out.push(a);
|
|
190
|
+
i++;
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
async function runShow(flags) {
|
|
195
|
+
const json = !!flags["json"];
|
|
196
|
+
const res = await apiGet("/api/v2/segments");
|
|
197
|
+
if (!res.ok) {
|
|
198
|
+
if (json)
|
|
199
|
+
console.log(JSON.stringify({ ok: false, status: res.status, data: res.data }));
|
|
200
|
+
else
|
|
201
|
+
console.log(formatError(res));
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
if (json) {
|
|
205
|
+
console.log(JSON.stringify(res.data.map));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (!res.data.map) {
|
|
209
|
+
console.log("No segment map yet for the active brand.");
|
|
210
|
+
console.log("Build one and import it: exodus segment import <file>");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
console.log(formatMapSummary(res.data.map));
|
|
214
|
+
}
|
|
215
|
+
async function runExport(flags) {
|
|
216
|
+
const out = typeof flags["out"] === "string" ? flags["out"] : undefined;
|
|
217
|
+
const res = await apiGet("/api/v2/segments/export");
|
|
218
|
+
if (!res.ok) {
|
|
219
|
+
console.log(formatError(res));
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
const doc = JSON.stringify(res.data, null, 2);
|
|
223
|
+
if (out) {
|
|
224
|
+
fs.writeFileSync(out, doc + "\n", "utf-8");
|
|
225
|
+
console.log(`Wrote segment contract to ${out}.`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
console.log(doc);
|
|
229
|
+
}
|
|
230
|
+
async function runImport(positional, flags) {
|
|
231
|
+
const file = positional[0];
|
|
232
|
+
if (!file) {
|
|
233
|
+
console.error("Error: a contract JSON file is required");
|
|
234
|
+
console.log("Usage: exodus segment import <file> [--yes]");
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
const result = await importFlow(file, { yes: !!flags["yes"], json: !!flags["json"] }, {
|
|
238
|
+
post: (path, body) => apiPost(path, body),
|
|
239
|
+
readFile: (p) => fs.readFileSync(p, "utf-8"),
|
|
240
|
+
});
|
|
241
|
+
for (const l of result.lines)
|
|
242
|
+
console.log(l);
|
|
243
|
+
if (result.code !== 0)
|
|
244
|
+
process.exit(result.code);
|
|
245
|
+
}
|
|
246
|
+
async function runPersonas(flags) {
|
|
247
|
+
const json = !!flags["json"];
|
|
248
|
+
const res = await apiGet("/api/v2/segments/personas");
|
|
249
|
+
if (!res.ok) {
|
|
250
|
+
if (json)
|
|
251
|
+
console.log(JSON.stringify({ ok: false, status: res.status, data: res.data }));
|
|
252
|
+
else
|
|
253
|
+
console.log(formatError(res));
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
const personas = res.data.personas ?? [];
|
|
257
|
+
if (json) {
|
|
258
|
+
console.log(JSON.stringify({ personas }));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (personas.length === 0) {
|
|
262
|
+
console.log("No personas saved yet for the active brand.");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
console.log(`Personas (${personas.length}):`);
|
|
266
|
+
for (const p of personas) {
|
|
267
|
+
const tag = p.archived ? " [archived]" : "";
|
|
268
|
+
const code = p.code ? `${p.code} ` : "";
|
|
269
|
+
const type = p.type ? ` (${p.type})` : "";
|
|
270
|
+
console.log(` ${code}${p.name}${type}${tag}`);
|
|
271
|
+
if (p.description)
|
|
272
|
+
console.log(` ${p.description}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { type ApiResponse } from "../lib/client.js";
|
|
2
|
+
import { type PollOptions, type PollResult } from "../lib/poll.js";
|
|
3
|
+
export declare const helpText: string;
|
|
4
|
+
export type WorkflowNodeKind = "brief" | "bot" | "primer" | "image" | "output";
|
|
5
|
+
export interface WorkflowNode {
|
|
6
|
+
id: string;
|
|
7
|
+
kind: WorkflowNodeKind;
|
|
8
|
+
position: {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
config: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkflowEdge {
|
|
15
|
+
id: string;
|
|
16
|
+
source: string;
|
|
17
|
+
sourceHandle: string;
|
|
18
|
+
target: string;
|
|
19
|
+
targetHandle: string;
|
|
20
|
+
}
|
|
21
|
+
export interface WorkflowContractJson {
|
|
22
|
+
contract: "exodus-workflow";
|
|
23
|
+
version: number;
|
|
24
|
+
workflowId?: string;
|
|
25
|
+
updatedAt?: string;
|
|
26
|
+
name: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
nodes: WorkflowNode[];
|
|
29
|
+
edges: WorkflowEdge[];
|
|
30
|
+
}
|
|
31
|
+
export interface UnresolvedWorkflowRef {
|
|
32
|
+
nodeId: string;
|
|
33
|
+
ref: "persona" | "primer";
|
|
34
|
+
value: string;
|
|
35
|
+
message: string;
|
|
36
|
+
}
|
|
37
|
+
export interface WorkflowImportResult {
|
|
38
|
+
name: string;
|
|
39
|
+
created: boolean;
|
|
40
|
+
workflowId?: string;
|
|
41
|
+
nodeCount: number;
|
|
42
|
+
edgeCount: number;
|
|
43
|
+
unresolved: UnresolvedWorkflowRef[];
|
|
44
|
+
warnings: string[];
|
|
45
|
+
}
|
|
46
|
+
export type GraphIssueCode = "bad-shape" | "unknown-kind" | "duplicate-node-id" | "dangling-edge" | "unknown-port" | "type-mismatch" | "duplicate-input" | "cycle" | "missing-required-input" | "bad-config";
|
|
47
|
+
export interface GraphIssue {
|
|
48
|
+
code: GraphIssueCode;
|
|
49
|
+
message: string;
|
|
50
|
+
nodeId?: string;
|
|
51
|
+
edgeId?: string;
|
|
52
|
+
portId?: string;
|
|
53
|
+
remedy?: string;
|
|
54
|
+
}
|
|
55
|
+
export type WorkflowImportErrorCode = "invalid-graph" | "conflict" | "forbidden" | "not-found";
|
|
56
|
+
export interface WorkflowImportError {
|
|
57
|
+
code: WorkflowImportErrorCode;
|
|
58
|
+
message: string;
|
|
59
|
+
issues?: GraphIssue[];
|
|
60
|
+
currentUpdatedAt?: string;
|
|
61
|
+
}
|
|
62
|
+
export type WorkflowPortType = "text" | "primer" | "image";
|
|
63
|
+
export type WorkflowPrimerKind = "body" | "hook" | "headline" | "summary";
|
|
64
|
+
export type WorkflowParamKind = "select" | "text" | "textarea" | "toggle" | "number" | "multiselect";
|
|
65
|
+
export interface CatalogInput {
|
|
66
|
+
id: string;
|
|
67
|
+
label: string;
|
|
68
|
+
accepts: WorkflowPortType[];
|
|
69
|
+
required: boolean;
|
|
70
|
+
multi?: boolean;
|
|
71
|
+
primerKinds?: WorkflowPrimerKind[];
|
|
72
|
+
}
|
|
73
|
+
export interface CatalogParam {
|
|
74
|
+
key: string;
|
|
75
|
+
label: string;
|
|
76
|
+
kind: WorkflowParamKind;
|
|
77
|
+
options?: string[];
|
|
78
|
+
min?: number;
|
|
79
|
+
max?: number;
|
|
80
|
+
required?: boolean;
|
|
81
|
+
help?: string;
|
|
82
|
+
default?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface CatalogBot {
|
|
85
|
+
slug: string;
|
|
86
|
+
name: string;
|
|
87
|
+
blurb: string;
|
|
88
|
+
category: string;
|
|
89
|
+
categoryLabel: string;
|
|
90
|
+
inputs: CatalogInput[];
|
|
91
|
+
params: CatalogParam[];
|
|
92
|
+
outputType: string;
|
|
93
|
+
splitsOutput: boolean;
|
|
94
|
+
}
|
|
95
|
+
export interface WorkflowCatalog {
|
|
96
|
+
catalog: "exodus-workflow-catalog";
|
|
97
|
+
version: 1;
|
|
98
|
+
bots: CatalogBot[];
|
|
99
|
+
vocabulary: {
|
|
100
|
+
nodeKinds: string[];
|
|
101
|
+
briefSources: string[];
|
|
102
|
+
primerKinds: string[];
|
|
103
|
+
imageModels: string[];
|
|
104
|
+
aspectRatios: string[];
|
|
105
|
+
imageQuantityModes: string[];
|
|
106
|
+
categories: Array<{
|
|
107
|
+
id: string;
|
|
108
|
+
label: string;
|
|
109
|
+
}>;
|
|
110
|
+
};
|
|
111
|
+
customBot: {
|
|
112
|
+
slug: "custom";
|
|
113
|
+
configKey: "customSlug";
|
|
114
|
+
inputs: CatalogInput[];
|
|
115
|
+
summaryOnlySlugs: string[];
|
|
116
|
+
notes: string;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export type WorkflowBriefSource = "text" | "swipe-ad" | "swipe-bundle" | "organic-url" | "ad-url";
|
|
120
|
+
export interface WorkflowInputDescriptor {
|
|
121
|
+
fieldName: string;
|
|
122
|
+
nodeId: string;
|
|
123
|
+
source: WorkflowBriefSource;
|
|
124
|
+
required: boolean;
|
|
125
|
+
description?: string;
|
|
126
|
+
bundleSize?: number;
|
|
127
|
+
}
|
|
128
|
+
export interface WorkflowPrerequisiteDescriptor {
|
|
129
|
+
primerKind: WorkflowPrimerKind;
|
|
130
|
+
nodeIds: string[];
|
|
131
|
+
}
|
|
132
|
+
export interface WorkflowOutputDescriptor {
|
|
133
|
+
type: "text" | "image";
|
|
134
|
+
label: string;
|
|
135
|
+
nodeId: string;
|
|
136
|
+
botSlug?: string;
|
|
137
|
+
}
|
|
138
|
+
export interface WorkflowDescribeResponse {
|
|
139
|
+
workflowId: string;
|
|
140
|
+
name: string;
|
|
141
|
+
description?: string;
|
|
142
|
+
updatedAt: string;
|
|
143
|
+
inputs: WorkflowInputDescriptor[];
|
|
144
|
+
prerequisites: Array<WorkflowPrerequisiteDescriptor & {
|
|
145
|
+
stored: boolean;
|
|
146
|
+
}>;
|
|
147
|
+
outputs: WorkflowOutputDescriptor[];
|
|
148
|
+
}
|
|
149
|
+
export interface WorkflowListItem {
|
|
150
|
+
_id: string;
|
|
151
|
+
name: string;
|
|
152
|
+
description?: string;
|
|
153
|
+
nodeCount: number;
|
|
154
|
+
edgeCount: number;
|
|
155
|
+
createdAt: string;
|
|
156
|
+
updatedAt: string;
|
|
157
|
+
}
|
|
158
|
+
export interface WorkflowListResponse {
|
|
159
|
+
workflows: WorkflowListItem[];
|
|
160
|
+
}
|
|
161
|
+
export type WorkflowRunStatus = "queued" | "running" | "completed" | "partial" | "failed";
|
|
162
|
+
export type WorkflowNodeRunStatus = "idle" | "running" | "done" | "failed" | "skipped";
|
|
163
|
+
export type WorkflowArtifact = {
|
|
164
|
+
type: "text";
|
|
165
|
+
text: string;
|
|
166
|
+
label?: string;
|
|
167
|
+
} | {
|
|
168
|
+
type: "primer";
|
|
169
|
+
text: string;
|
|
170
|
+
primerKind: string;
|
|
171
|
+
} | {
|
|
172
|
+
type: "image";
|
|
173
|
+
storageId: string;
|
|
174
|
+
imageUrl?: string;
|
|
175
|
+
};
|
|
176
|
+
export interface WorkflowRunOutput {
|
|
177
|
+
nodeId: string;
|
|
178
|
+
botSlug?: string;
|
|
179
|
+
type: "text" | "image";
|
|
180
|
+
label: string;
|
|
181
|
+
text?: string;
|
|
182
|
+
imageUrl?: string;
|
|
183
|
+
imageId?: string;
|
|
184
|
+
}
|
|
185
|
+
export interface WorkflowRunNode {
|
|
186
|
+
nodeId: string;
|
|
187
|
+
kind: string;
|
|
188
|
+
status: WorkflowNodeRunStatus;
|
|
189
|
+
error?: string;
|
|
190
|
+
startedAt?: number;
|
|
191
|
+
completedAt?: number;
|
|
192
|
+
outputs: WorkflowArtifact[];
|
|
193
|
+
}
|
|
194
|
+
export interface WorkflowCounts {
|
|
195
|
+
done: number;
|
|
196
|
+
failed: number;
|
|
197
|
+
skipped: number;
|
|
198
|
+
total: number;
|
|
199
|
+
}
|
|
200
|
+
export interface WorkflowRun {
|
|
201
|
+
_id: string;
|
|
202
|
+
workflowId: string;
|
|
203
|
+
workflowName: string;
|
|
204
|
+
status: WorkflowRunStatus;
|
|
205
|
+
error?: string;
|
|
206
|
+
counts?: WorkflowCounts;
|
|
207
|
+
inputs: Record<string, string>;
|
|
208
|
+
triggerRunId?: string;
|
|
209
|
+
createdAt: number;
|
|
210
|
+
startedAt?: number;
|
|
211
|
+
completedAt?: number;
|
|
212
|
+
isTerminal: boolean;
|
|
213
|
+
nodes: WorkflowRunNode[];
|
|
214
|
+
outputs?: WorkflowRunOutput[];
|
|
215
|
+
}
|
|
216
|
+
export type WorkflowRunProjection = Omit<WorkflowRun, "nodes"> & {
|
|
217
|
+
nodes?: never;
|
|
218
|
+
};
|
|
219
|
+
export interface WorkflowRunDeps {
|
|
220
|
+
get: (path: string) => Promise<ApiResponse<unknown>>;
|
|
221
|
+
post: (path: string, body: unknown) => Promise<ApiResponse<unknown>>;
|
|
222
|
+
readFile: (path: string) => string;
|
|
223
|
+
writeFile: (path: string, text: string) => void;
|
|
224
|
+
poll: (opts: PollOptions) => Promise<PollResult>;
|
|
225
|
+
}
|
|
226
|
+
export interface FlowResult {
|
|
227
|
+
code: number;
|
|
228
|
+
lines: string[];
|
|
229
|
+
}
|
|
230
|
+
interface RunFlowOptions {
|
|
231
|
+
inputs: Record<string, string>;
|
|
232
|
+
wait: boolean;
|
|
233
|
+
json: boolean;
|
|
234
|
+
onProgressLine?: (line: string) => void;
|
|
235
|
+
}
|
|
236
|
+
export declare function parseInputFlags(args: string[], readFile?: (path: string) => string): Record<string, string>;
|
|
237
|
+
export declare function formatWorkflowList(workflows: WorkflowListItem[]): string;
|
|
238
|
+
export declare function formatRecentRuns(runs: WorkflowRunProjection[]): string;
|
|
239
|
+
export declare function formatImportSummary(result: WorkflowImportResult, mode?: {
|
|
240
|
+
dryRun?: boolean;
|
|
241
|
+
update?: boolean;
|
|
242
|
+
}): string;
|
|
243
|
+
export declare function formatWorkflowRun(run: WorkflowRun): string;
|
|
244
|
+
export declare function formatDescribe(res: WorkflowDescribeResponse): string;
|
|
245
|
+
export declare function formatBotsList(catalog: WorkflowCatalog, category?: string): string;
|
|
246
|
+
export declare function formatBotDetail(bot: CatalogBot): string;
|
|
247
|
+
export declare function resolveWorkflowId(ref: string, deps: WorkflowRunDeps): Promise<string>;
|
|
248
|
+
export declare function listFlow(json: boolean, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
249
|
+
export declare function describeFlow(workflowRef: string, opts: {
|
|
250
|
+
json: boolean;
|
|
251
|
+
}, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
252
|
+
export declare function botsFlow(opts: {
|
|
253
|
+
category?: string;
|
|
254
|
+
slug?: string;
|
|
255
|
+
json: boolean;
|
|
256
|
+
}, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
257
|
+
export declare function runFlow(workflowRef: string, opts: RunFlowOptions, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
258
|
+
export declare function statusFlow(opts: {
|
|
259
|
+
id?: string;
|
|
260
|
+
json: boolean;
|
|
261
|
+
}, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
262
|
+
export declare function exportFlow(workflowRef: string, opts: {
|
|
263
|
+
out?: string;
|
|
264
|
+
}, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
265
|
+
export declare function importFlow(file: string, opts: {
|
|
266
|
+
dryRun: boolean;
|
|
267
|
+
json: boolean;
|
|
268
|
+
update?: string;
|
|
269
|
+
}, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
270
|
+
export declare function run(flags: Record<string, string | boolean>): Promise<void>;
|
|
271
|
+
export {};
|