@qoren/cli 0.1.0

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.
Files changed (171) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +162 -0
  3. package/dist/args.d.ts +5 -0
  4. package/dist/args.d.ts.map +1 -0
  5. package/dist/args.js +23 -0
  6. package/dist/args.js.map +1 -0
  7. package/dist/commands/account.d.ts +4 -0
  8. package/dist/commands/account.d.ts.map +1 -0
  9. package/dist/commands/account.js +154 -0
  10. package/dist/commands/account.js.map +1 -0
  11. package/dist/commands/agent.d.ts +27 -0
  12. package/dist/commands/agent.d.ts.map +1 -0
  13. package/dist/commands/agent.js +968 -0
  14. package/dist/commands/agent.js.map +1 -0
  15. package/dist/commands/api.d.ts +4 -0
  16. package/dist/commands/api.d.ts.map +1 -0
  17. package/dist/commands/api.js +66 -0
  18. package/dist/commands/api.js.map +1 -0
  19. package/dist/commands/approvals.d.ts +11 -0
  20. package/dist/commands/approvals.d.ts.map +1 -0
  21. package/dist/commands/approvals.js +140 -0
  22. package/dist/commands/approvals.js.map +1 -0
  23. package/dist/commands/auth.d.ts +4 -0
  24. package/dist/commands/auth.d.ts.map +1 -0
  25. package/dist/commands/auth.js +215 -0
  26. package/dist/commands/auth.js.map +1 -0
  27. package/dist/commands/clients.d.ts +62 -0
  28. package/dist/commands/clients.d.ts.map +1 -0
  29. package/dist/commands/clients.js +311 -0
  30. package/dist/commands/clients.js.map +1 -0
  31. package/dist/commands/console.d.ts +4 -0
  32. package/dist/commands/console.d.ts.map +1 -0
  33. package/dist/commands/console.js +26 -0
  34. package/dist/commands/console.js.map +1 -0
  35. package/dist/commands/customAgent.d.ts +42 -0
  36. package/dist/commands/customAgent.d.ts.map +1 -0
  37. package/dist/commands/customAgent.js +449 -0
  38. package/dist/commands/customAgent.js.map +1 -0
  39. package/dist/commands/designer.d.ts +32 -0
  40. package/dist/commands/designer.d.ts.map +1 -0
  41. package/dist/commands/designer.js +682 -0
  42. package/dist/commands/designer.js.map +1 -0
  43. package/dist/commands/env.d.ts +15 -0
  44. package/dist/commands/env.d.ts.map +1 -0
  45. package/dist/commands/env.js +245 -0
  46. package/dist/commands/env.js.map +1 -0
  47. package/dist/commands/interactive.d.ts +4 -0
  48. package/dist/commands/interactive.d.ts.map +1 -0
  49. package/dist/commands/interactive.js +30 -0
  50. package/dist/commands/interactive.js.map +1 -0
  51. package/dist/commands/jobs.d.ts +4 -0
  52. package/dist/commands/jobs.d.ts.map +1 -0
  53. package/dist/commands/jobs.js +59 -0
  54. package/dist/commands/jobs.js.map +1 -0
  55. package/dist/commands/secrets.d.ts +15 -0
  56. package/dist/commands/secrets.d.ts.map +1 -0
  57. package/dist/commands/secrets.js +164 -0
  58. package/dist/commands/secrets.js.map +1 -0
  59. package/dist/commands/webhook.d.ts +4 -0
  60. package/dist/commands/webhook.d.ts.map +1 -0
  61. package/dist/commands/webhook.js +275 -0
  62. package/dist/commands/webhook.js.map +1 -0
  63. package/dist/config.d.ts +38 -0
  64. package/dist/config.d.ts.map +1 -0
  65. package/dist/config.js +117 -0
  66. package/dist/config.js.map +1 -0
  67. package/dist/context.d.ts +52 -0
  68. package/dist/context.d.ts.map +1 -0
  69. package/dist/context.js +163 -0
  70. package/dist/context.js.map +1 -0
  71. package/dist/duration.d.ts +9 -0
  72. package/dist/duration.d.ts.map +1 -0
  73. package/dist/duration.js +30 -0
  74. package/dist/duration.js.map +1 -0
  75. package/dist/index.d.ts +3 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +27 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/issues.d.ts +10 -0
  80. package/dist/issues.d.ts.map +1 -0
  81. package/dist/issues.js +45 -0
  82. package/dist/issues.js.map +1 -0
  83. package/dist/jobProgress.d.ts +12 -0
  84. package/dist/jobProgress.d.ts.map +1 -0
  85. package/dist/jobProgress.js +56 -0
  86. package/dist/jobProgress.js.map +1 -0
  87. package/dist/loopback.d.ts +28 -0
  88. package/dist/loopback.d.ts.map +1 -0
  89. package/dist/loopback.js +110 -0
  90. package/dist/loopback.js.map +1 -0
  91. package/dist/migrate/apply.d.ts +29 -0
  92. package/dist/migrate/apply.d.ts.map +1 -0
  93. package/dist/migrate/apply.js +135 -0
  94. package/dist/migrate/apply.js.map +1 -0
  95. package/dist/migrate/bundle.d.ts +89 -0
  96. package/dist/migrate/bundle.d.ts.map +1 -0
  97. package/dist/migrate/bundle.js +681 -0
  98. package/dist/migrate/bundle.js.map +1 -0
  99. package/dist/migrate/tar.d.ts +9 -0
  100. package/dist/migrate/tar.d.ts.map +1 -0
  101. package/dist/migrate/tar.js +89 -0
  102. package/dist/migrate/tar.js.map +1 -0
  103. package/dist/output.d.ts +46 -0
  104. package/dist/output.d.ts.map +1 -0
  105. package/dist/output.js +152 -0
  106. package/dist/output.js.map +1 -0
  107. package/dist/program.d.ts +17 -0
  108. package/dist/program.d.ts.map +1 -0
  109. package/dist/program.js +89 -0
  110. package/dist/program.js.map +1 -0
  111. package/dist/runtimes.d.ts +11 -0
  112. package/dist/runtimes.d.ts.map +1 -0
  113. package/dist/runtimes.js +20 -0
  114. package/dist/runtimes.js.map +1 -0
  115. package/dist/slug.d.ts +3 -0
  116. package/dist/slug.d.ts.map +1 -0
  117. package/dist/slug.js +15 -0
  118. package/dist/slug.js.map +1 -0
  119. package/dist/taskChain.d.ts +20 -0
  120. package/dist/taskChain.d.ts.map +1 -0
  121. package/dist/taskChain.js +40 -0
  122. package/dist/taskChain.js.map +1 -0
  123. package/dist/taskDelivery.d.ts +33 -0
  124. package/dist/taskDelivery.d.ts.map +1 -0
  125. package/dist/taskDelivery.js +67 -0
  126. package/dist/taskDelivery.js.map +1 -0
  127. package/dist/tui/app.d.ts +5 -0
  128. package/dist/tui/app.d.ts.map +1 -0
  129. package/dist/tui/app.js +130 -0
  130. package/dist/tui/app.js.map +1 -0
  131. package/dist/tui/index.d.ts +12 -0
  132. package/dist/tui/index.d.ts.map +1 -0
  133. package/dist/tui/index.js +34 -0
  134. package/dist/tui/index.js.map +1 -0
  135. package/dist/tui/model.d.ts +110 -0
  136. package/dist/tui/model.d.ts.map +1 -0
  137. package/dist/tui/model.js +257 -0
  138. package/dist/tui/model.js.map +1 -0
  139. package/dist/tui/overlay.d.ts +66 -0
  140. package/dist/tui/overlay.d.ts.map +1 -0
  141. package/dist/tui/overlay.js +216 -0
  142. package/dist/tui/overlay.js.map +1 -0
  143. package/dist/tui/panes/account.d.ts +5 -0
  144. package/dist/tui/panes/account.d.ts.map +1 -0
  145. package/dist/tui/panes/account.js +108 -0
  146. package/dist/tui/panes/account.js.map +1 -0
  147. package/dist/tui/panes/agents.d.ts +9 -0
  148. package/dist/tui/panes/agents.d.ts.map +1 -0
  149. package/dist/tui/panes/agents.js +340 -0
  150. package/dist/tui/panes/agents.js.map +1 -0
  151. package/dist/tui/panes/chat.d.ts +14 -0
  152. package/dist/tui/panes/chat.d.ts.map +1 -0
  153. package/dist/tui/panes/chat.js +169 -0
  154. package/dist/tui/panes/chat.js.map +1 -0
  155. package/dist/tui/panes/environments.d.ts +6 -0
  156. package/dist/tui/panes/environments.d.ts.map +1 -0
  157. package/dist/tui/panes/environments.js +364 -0
  158. package/dist/tui/panes/environments.js.map +1 -0
  159. package/dist/tui/panes/jobs.d.ts +5 -0
  160. package/dist/tui/panes/jobs.d.ts.map +1 -0
  161. package/dist/tui/panes/jobs.js +104 -0
  162. package/dist/tui/panes/jobs.js.map +1 -0
  163. package/dist/tui/store.d.ts +40 -0
  164. package/dist/tui/store.d.ts.map +1 -0
  165. package/dist/tui/store.js +124 -0
  166. package/dist/tui/store.js.map +1 -0
  167. package/dist/tui/ui.d.ts +95 -0
  168. package/dist/tui/ui.d.ts.map +1 -0
  169. package/dist/tui/ui.js +144 -0
  170. package/dist/tui/ui.js.map +1 -0
  171. package/package.json +51 -0
@@ -0,0 +1,682 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import { requireContext, run, } from "../context.js";
3
+ import { hasErrors, issueSummary, printIssues } from "../issues.js";
4
+ import { age, bold, details, dim, emit, note, statusColor, table, warn } from "../output.js";
5
+ import { slugify } from "../slug.js";
6
+ import { parseOnOff, readJsonArg } from "../args.js";
7
+ import { gated, resolveClient } from "./clients.js";
8
+ import { parseWaitTimeout, printRun, reportWaitedRun, waitForRun } from "./customAgent.js";
9
+ // The Agent Designer, from the terminal: designs as code.
10
+ //
11
+ // A design is a JSON spec with one editable draft and immutable published
12
+ // versions. The loop this file is built around is the one a team keeps in git:
13
+ // `export` a design to a file, edit it, `push` it back (validated, optionally
14
+ // published), then `deploy` it as a Custom agent for each client. Operating the
15
+ // agents themselves (runs, chat, channels) lives under `qoren agent`.
16
+ const ms = (value) => value ? new Date(value).toISOString() : "";
17
+ const ago = (value) => value ? age(new Date(value).toISOString()) : "";
18
+ /** Repeatable `--input key=value` flags. */
19
+ export function collectPairs(value, previous = []) {
20
+ return [...previous, value];
21
+ }
22
+ /**
23
+ * Pick one design by id, exact name, or the name's slug. Archived designs are
24
+ * matched only by id. Null when nothing matches; a name that matches more than
25
+ * one design is refused rather than guessed.
26
+ */
27
+ export function findDesign(designs, ref) {
28
+ const byId = designs.find((d) => d.designId === ref);
29
+ if (byId)
30
+ return byId;
31
+ const active = designs.filter((d) => d.archivedAt === null);
32
+ const named = active.filter((d) => d.name === ref);
33
+ const candidates = named.length > 0
34
+ ? named
35
+ : active.filter((d) => slugify(d.name) === slugify(ref) && slugify(ref) !== "");
36
+ if (candidates.length > 1) {
37
+ throw new Error(`More than one design is called "${ref}". Use its id (see qoren designer ls).`);
38
+ }
39
+ return candidates[0] ?? null;
40
+ }
41
+ export function matchDesign(designs, ref) {
42
+ const design = findDesign(designs, ref);
43
+ if (!design) {
44
+ throw new Error(`No design has the id or name "${ref}". Run qoren designer ls to see them.`);
45
+ }
46
+ return design;
47
+ }
48
+ async function resolveDesign(ctx, ref) {
49
+ return matchDesign(await ctx.qoren.designer.designs.list(), ref);
50
+ }
51
+ /** `draft`, `latest` or a version number, off the command line. */
52
+ export function parseVersionRef(input) {
53
+ const value = input.trim().toLowerCase();
54
+ if (value === "draft" || value === "latest")
55
+ return value;
56
+ const n = Number.parseInt(value.replace(/^v/, ""), 10);
57
+ if (Number.isInteger(n) && n > 0 && String(n) === value.replace(/^v/, ""))
58
+ return n;
59
+ throw new Error(`Expected draft, latest or a version number, got "${input}".`);
60
+ }
61
+ export function parseVersion(input) {
62
+ const ref = parseVersionRef(input);
63
+ if (typeof ref !== "number")
64
+ throw new Error(`Expected a version number, got "${input}".`);
65
+ return ref;
66
+ }
67
+ /** Read a design spec file, refusing anything that is not one. */
68
+ export function readSpecFile(path) {
69
+ let parsed;
70
+ try {
71
+ parsed = JSON.parse(readFileSync(path, "utf8"));
72
+ }
73
+ catch (err) {
74
+ throw new Error(`Could not read ${path} as JSON: ${err instanceof Error ? err.message : String(err)}`);
75
+ }
76
+ if (!parsed ||
77
+ typeof parsed !== "object" ||
78
+ parsed.kind !== "qoren.design") {
79
+ throw new Error(`${path} is not a design spec (expected "kind": "qoren.design"). Get one with qoren designer export.`);
80
+ }
81
+ return parsed;
82
+ }
83
+ const RESOURCE_KINDS = new Set([
84
+ "connection",
85
+ "knowledge_base",
86
+ "machine",
87
+ "fleet_agent",
88
+ "mcp_server",
89
+ ]);
90
+ /**
91
+ * Turn `key=value` pairs into the three binding maps, using what the design
92
+ * says each input is. A secret input takes a vault secret NAME (never a value),
93
+ * a resource input takes an id, and a scalar is typed by its kind: `number`
94
+ * and `boolean` are parsed, a multiple-choice input splits on commas, and an
95
+ * empty value clears the input.
96
+ */
97
+ export function bindInputs(inputs, pairs) {
98
+ const out = {
99
+ bindings: {},
100
+ secretBindings: {},
101
+ resourceBindings: {},
102
+ };
103
+ for (const pair of pairs) {
104
+ const eq = pair.indexOf("=");
105
+ if (eq <= 0)
106
+ throw new Error(`Expected key=value, got "${pair}".`);
107
+ const key = pair.slice(0, eq).trim();
108
+ const raw = pair.slice(eq + 1);
109
+ const input = inputs.find((i) => i.key === key);
110
+ if (!input) {
111
+ const known = inputs.map((i) => i.key).join(", ");
112
+ throw new Error(`This design has no input "${key}".${known ? ` Its inputs are: ${known}.` : " It declares no inputs."}`);
113
+ }
114
+ const list = () => raw.split(",").map((v) => v.trim()).filter(Boolean);
115
+ if (input.kind === "secret") {
116
+ out.secretBindings[key] = raw.trim();
117
+ }
118
+ else if (RESOURCE_KINDS.has(input.kind)) {
119
+ out.resourceBindings[key] = input.multiple ? list() : raw.trim();
120
+ }
121
+ else {
122
+ out.bindings[key] = scalar(input, raw);
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+ function scalar(input, raw) {
128
+ if (raw === "")
129
+ return null;
130
+ if (input.kind === "number") {
131
+ const n = Number(raw);
132
+ if (!Number.isFinite(n))
133
+ throw new Error(`Input "${input.key}" takes a number, got "${raw}".`);
134
+ return n;
135
+ }
136
+ if (input.kind === "boolean") {
137
+ const b = parseOnOff(raw);
138
+ if (b === null)
139
+ throw new Error(`Input "${input.key}" takes on or off, got "${raw}".`);
140
+ return b;
141
+ }
142
+ if (input.multiple)
143
+ return raw.split(",").map((v) => v.trim()).filter(Boolean);
144
+ return raw;
145
+ }
146
+ /** Only the binding maps that carry something, so an untouched map is left alone upstream. */
147
+ function nonEmpty(b) {
148
+ const out = {};
149
+ if (b.bindings && Object.keys(b.bindings).length)
150
+ out.bindings = b.bindings;
151
+ if (b.secretBindings && Object.keys(b.secretBindings).length)
152
+ out.secretBindings = b.secretBindings;
153
+ if (b.resourceBindings && Object.keys(b.resourceBindings).length)
154
+ out.resourceBindings = b.resourceBindings;
155
+ return out;
156
+ }
157
+ /** Laid over what an agent already has: the API replaces a map whole. */
158
+ export function mergeBindings(current, changes) {
159
+ const out = {};
160
+ if (changes.bindings)
161
+ out.bindings = { ...current.bindings, ...changes.bindings };
162
+ if (changes.secretBindings)
163
+ out.secretBindings = { ...current.secretBindings, ...changes.secretBindings };
164
+ if (changes.resourceBindings)
165
+ out.resourceBindings = { ...current.resourceBindings, ...changes.resourceBindings };
166
+ return out;
167
+ }
168
+ function parseCredits(value) {
169
+ const n = Number.parseInt(value.replace(/[,_]/g, ""), 10);
170
+ if (!Number.isInteger(n) || n < 1) {
171
+ throw new Error("--budget takes a whole number of credits, 1 or more (1,000 credits is $1).");
172
+ }
173
+ return n;
174
+ }
175
+ function printPublish(name, result) {
176
+ note(`Published ${bold(name)} as version ${bold(String(result.version))}.`);
177
+ if (result.upgraded.length > 0) {
178
+ note(` ${dim("·")} ${result.upgraded.length} agent(s) moved to it`);
179
+ }
180
+ for (const behind of result.needsInputs) {
181
+ warn(`Agent ${behind.agentId} stays on its version until it has: ${behind.missingInputs.join(", ")}. Set them with qoren designer set.`);
182
+ }
183
+ }
184
+ /** A Custom agent's design settings, for a person. Secret inputs show the
185
+ * vault name they point at; no value ever reaches this command. */
186
+ export function printAgentDesigner(v) {
187
+ const version = v.versionPolicy === "pinned"
188
+ ? `pinned to v${v.pinnedVersion}`
189
+ : `follows the latest${v.latestVersion == null ? "" : ` (v${v.latestVersion})`}`;
190
+ details([
191
+ ["Agent", v.agentId],
192
+ ["Design", `${v.designName ?? dim("(archived)")} (${v.designId})`],
193
+ ["Running", v.activeVersion == null ? "" : `v${v.activeVersion}`],
194
+ ["Version", version],
195
+ ["Status", v.status ? statusColor(v.status) : ""],
196
+ [
197
+ "Upgrade",
198
+ v.pendingUpgrade
199
+ ? `v${v.pendingUpgrade.version} waits for: ${v.pendingUpgrade.missingInputs.join(", ")}`
200
+ : "",
201
+ ],
202
+ ["Budget", `${v.runGuardCredits.toLocaleString("en-US")} credits per run`],
203
+ ["Paused", v.paused ? "yes" : "no"],
204
+ ["Client", v.customerId ?? ""],
205
+ ]);
206
+ const rows = [
207
+ ...Object.entries(v.bindings ?? {}).map(([key, value]) => ({
208
+ key,
209
+ kind: "value",
210
+ value: value === null ? "" : Array.isArray(value) ? value.join(", ") : String(value),
211
+ })),
212
+ ...Object.entries(v.secretBindings ?? {}).map(([key, value]) => ({ key, kind: "secret", value })),
213
+ ...Object.entries(v.resourceBindings ?? {}).map(([key, value]) => ({
214
+ key,
215
+ kind: "resource",
216
+ value: Array.isArray(value) ? value.join(", ") : value,
217
+ })),
218
+ ];
219
+ if (rows.length > 0) {
220
+ note(bold("\nInputs"));
221
+ table(rows, [
222
+ { header: "key", value: (r) => r.key },
223
+ { header: "kind", value: (r) => r.kind },
224
+ { header: "value", value: (r) => r.value },
225
+ ]);
226
+ }
227
+ }
228
+ const HELP_INPUT = "an input value as key=value; repeatable. Secret inputs take a vault secret name, resource inputs an id";
229
+ export function designerCommands(program, global) {
230
+ const designer = program
231
+ .command("designer")
232
+ .alias("designs")
233
+ .description("Build Custom agents in the Agent Designer: designs as code");
234
+ designer
235
+ .command("ls")
236
+ .alias("list")
237
+ .description("List your designs")
238
+ .option("--archived", "include archived designs")
239
+ .action((options) => run(async () => {
240
+ const ctx = requireContext(global());
241
+ const designs = await ctx.qoren.designer.designs.list({
242
+ includeArchived: options.archived === true,
243
+ });
244
+ emit(designs, () => table(designs, [
245
+ { header: "id", value: (d) => d.designId },
246
+ { header: "name", value: (d) => d.name },
247
+ {
248
+ header: "version",
249
+ value: (d) => (d.latestVersion == null ? dim("draft only") : `v${d.latestVersion}`),
250
+ },
251
+ { header: "agents", value: (d) => String(d.instanceCount) },
252
+ { header: "updated", value: (d) => ago(d.updatedAt) },
253
+ { header: "archived", value: (d) => (d.archivedAt ? "yes" : "") },
254
+ ]));
255
+ })());
256
+ designer
257
+ .command("get <design>")
258
+ .description("Show one design: its draft, inputs and triggers")
259
+ .action((ref) => run(async () => {
260
+ const ctx = requireContext(global());
261
+ const { designId } = await resolveDesign(ctx, ref);
262
+ const d = await ctx.qoren.designer.designs.get(designId);
263
+ emit(d, () => {
264
+ details([
265
+ ["Id", d.designId],
266
+ ["Name", d.name],
267
+ ["Description", d.description ?? ""],
268
+ ["Latest version", d.latestVersion == null ? dim("not published") : `v${d.latestVersion}`],
269
+ ["Agents", String(d.instanceCount)],
270
+ ["Draft", `revision ${d.draftRevision}, hash ${d.draftHash.slice(0, 12)}`],
271
+ ["Triggers", d.triggers.map((t) => `${t.nodeId} (${t.kind})`).join(", ")],
272
+ ["Updated", ms(d.updatedAt)],
273
+ ]);
274
+ const inputs = d.latestInputs ?? d.inputs;
275
+ if (inputs.length > 0) {
276
+ note(bold(`\nInputs${d.latestInputs ? " (latest version)" : " (draft)"}`));
277
+ table(inputs, [
278
+ { header: "key", value: (i) => i.key },
279
+ { header: "kind", value: (i) => i.kind + (i.multiple ? "[]" : "") },
280
+ { header: "required", value: (i) => (i.required ? "yes" : "") },
281
+ { header: "label", value: (i) => i.label },
282
+ ]);
283
+ }
284
+ });
285
+ })());
286
+ designer
287
+ .command("new <name>")
288
+ .alias("create")
289
+ .description("Create a design: blank, from a starter, or from a spec file")
290
+ .option("--starter <slug>", "start from a starter design (see qoren designer starters)")
291
+ .option("--from <file>", "start from a design spec file")
292
+ .option("--description <text>", "what the design is for")
293
+ .action((name, options) => run(async () => {
294
+ if (options.starter && options.from) {
295
+ throw new Error("Choose --starter or --from, not both.");
296
+ }
297
+ const spec = options.from ? readSpecFile(options.from) : undefined;
298
+ const ctx = requireContext(global());
299
+ const result = await ctx.qoren.designer.designs.create({
300
+ name,
301
+ ...(options.description ? { description: options.description } : {}),
302
+ ...(options.starter ? { starterSlug: options.starter } : {}),
303
+ ...(spec ? { spec } : {}),
304
+ });
305
+ emit(result, () => {
306
+ note(`Created ${bold(result.design.name)} (${result.design.designId}).`);
307
+ if (result.issues.length > 0) {
308
+ note(`The draft has ${issueSummary(result.issues)}:`);
309
+ printIssues(result.issues);
310
+ }
311
+ });
312
+ })());
313
+ designer
314
+ .command("rm <design>")
315
+ .alias("archive")
316
+ .description("Archive a design (refused while agents still run it)")
317
+ .action((ref) => run(async () => {
318
+ const ctx = requireContext(global());
319
+ const design = await resolveDesign(ctx, ref);
320
+ const result = await ctx.qoren.designer.designs.archive(design.designId);
321
+ emit({ ...result, designId: design.designId }, () => note(`Archived ${bold(design.name)}.`));
322
+ })());
323
+ designer
324
+ .command("export <design>")
325
+ // Not --version: the program's own -v/--version would swallow it.
326
+ .description("Print a design's JSON spec (the draft unless --at says otherwise)")
327
+ .option("--at <version>", "draft, latest, or a version number", "draft")
328
+ .option("--out <file>", "write the spec to a file instead of stdout")
329
+ .action((ref, options) => run(async () => {
330
+ const version = parseVersionRef(options.at);
331
+ const ctx = requireContext(global());
332
+ const design = await resolveDesign(ctx, ref);
333
+ const exported = await ctx.qoren.designer.designs.export(design.designId, version);
334
+ const text = `${JSON.stringify(exported.spec, null, 2)}\n`;
335
+ if (options.out) {
336
+ writeFileSync(options.out, text);
337
+ emit({ ...exported, spec: undefined, path: options.out }, () => note(`Wrote ${bold(design.name)} ${exported.version == null ? "(draft)" : `v${exported.version}`} to ${options.out}.`));
338
+ return;
339
+ }
340
+ // The spec IS the data, so it goes to stdout as is; --json adds the
341
+ // version and hash around it.
342
+ emit(exported, () => process.stdout.write(text));
343
+ })());
344
+ designer
345
+ .command("push <file>")
346
+ .description("Import a spec file into a design's draft, creating the design if it is new; exits 1 when the draft has errors")
347
+ .option("--design <design>", "the design to update (defaults to the spec's meta.name)")
348
+ .option("--publish", "publish the draft as a new version when it has no errors")
349
+ .option("--note <text>", "what changed, for the version history (with --publish)")
350
+ .option("--dry-run", "validate and report, save nothing")
351
+ .action((file, options) => run(async () => {
352
+ const spec = readSpecFile(file);
353
+ const ref = options.design ?? spec.meta?.name;
354
+ if (!ref) {
355
+ throw new Error("Pass --design, or give the spec a meta.name.");
356
+ }
357
+ if (options.publish && options.dryRun) {
358
+ throw new Error("Choose --publish or --dry-run, not both.");
359
+ }
360
+ const ctx = requireContext(global());
361
+ const existing = findDesign(await ctx.qoren.designer.designs.list(), ref);
362
+ let designId;
363
+ let name;
364
+ let created = false;
365
+ let applied;
366
+ let issues;
367
+ let draftHash = null;
368
+ if (!existing) {
369
+ if (options.dryRun) {
370
+ emit({ designId: null, name: ref, created: false, applied: false, issues: [], published: null }, () => note(`No design called ${bold(ref)} yet. Push without --dry-run to create it; it is validated then.`));
371
+ return;
372
+ }
373
+ const result = await ctx.qoren.designer.designs.create({
374
+ name: spec.meta?.name ?? ref,
375
+ ...(spec.meta?.description ? { description: spec.meta.description } : {}),
376
+ spec,
377
+ });
378
+ designId = result.design.designId;
379
+ name = result.design.name;
380
+ created = true;
381
+ applied = true;
382
+ issues = result.issues;
383
+ draftHash = result.draftHash ?? null;
384
+ }
385
+ else {
386
+ designId = existing.designId;
387
+ name = existing.name;
388
+ const result = await ctx.qoren.designer.designs.import(designId, {
389
+ spec,
390
+ ...(options.dryRun ? { dryRun: true } : {}),
391
+ });
392
+ applied = result.applied;
393
+ issues = result.issues;
394
+ draftHash = result.draftHash;
395
+ }
396
+ const failed = hasErrors(issues);
397
+ let published = null;
398
+ if (options.publish && !failed) {
399
+ published = await ctx.qoren.designer.designs.publish(designId, {
400
+ ...(options.note ? { note: options.note } : {}),
401
+ ...(draftHash ? { expectedDraftHash: draftHash } : {}),
402
+ });
403
+ }
404
+ emit({ designId, name, created, applied, issues, draftHash, published }, () => {
405
+ const verb = options.dryRun
406
+ ? "Checked"
407
+ : created
408
+ ? "Created"
409
+ : "Updated the draft of";
410
+ note(`${verb} ${bold(name)} (${designId}): ${issueSummary(issues)}.`);
411
+ printIssues(issues);
412
+ if (published)
413
+ printPublish(name, published);
414
+ else if (options.publish)
415
+ warn("Not published: fix the errors above first.");
416
+ });
417
+ if (failed)
418
+ process.exitCode = 1;
419
+ })());
420
+ designer
421
+ .command("publish <design>")
422
+ .description("Publish the draft as the next immutable version")
423
+ .option("--note <text>", "what changed, for the version history")
424
+ .action((ref, options) => run(async () => {
425
+ const ctx = requireContext(global());
426
+ const design = await resolveDesign(ctx, ref);
427
+ const result = await ctx.qoren.designer.designs.publish(design.designId, {
428
+ ...(options.note ? { note: options.note } : {}),
429
+ });
430
+ emit(result, () => printPublish(design.name, result));
431
+ })());
432
+ designer
433
+ .command("rollback <design> <version>")
434
+ .description("Republish an earlier version as the next one")
435
+ .action((ref, version) => run(async () => {
436
+ const n = parseVersion(version);
437
+ const ctx = requireContext(global());
438
+ const design = await resolveDesign(ctx, ref);
439
+ const result = await ctx.qoren.designer.designs.rollback(design.designId, n);
440
+ emit(result, () => printPublish(design.name, result));
441
+ })());
442
+ designer
443
+ .command("versions <design>")
444
+ .description("List a design's published versions")
445
+ .action((ref) => run(async () => {
446
+ const ctx = requireContext(global());
447
+ const design = await resolveDesign(ctx, ref);
448
+ const versions = await ctx.qoren.designer.designs.versions(design.designId);
449
+ emit(versions, () => table(versions, [
450
+ { header: "version", value: (v) => `v${v.version}` },
451
+ { header: "published", value: (v) => ms(v.publishedAt) },
452
+ { header: "agents", value: (v) => String(v.instanceCount) },
453
+ { header: "required inputs", value: (v) => v.requiredInputKeys.join(", ") },
454
+ { header: "note", value: (v) => v.changelog ?? "" },
455
+ ]));
456
+ })());
457
+ designer
458
+ .command("agents <design>")
459
+ .alias("instances")
460
+ .description("List the Custom agents deployed from a design")
461
+ .action((ref) => run(async () => {
462
+ const ctx = requireContext(global());
463
+ const design = await resolveDesign(ctx, ref);
464
+ const instances = await ctx.qoren.designer.designs.instances(design.designId);
465
+ emit(instances, () => table(instances, [
466
+ { header: "agent", value: (i) => i.agentId },
467
+ { header: "name", value: (i) => i.name },
468
+ { header: "status", value: (i) => statusColor(i.status) },
469
+ {
470
+ header: "version",
471
+ value: (i) => (i.activeVersion == null ? "" : `v${i.activeVersion}`) +
472
+ (i.versionPolicy === "pinned" ? dim(" pinned") : ""),
473
+ },
474
+ {
475
+ header: "waiting on",
476
+ value: (i) => i.pendingUpgrade
477
+ ? `v${i.pendingUpgrade.version}: ${i.pendingUpgrade.missingInputs.join(", ")}`
478
+ : "",
479
+ },
480
+ ]));
481
+ })());
482
+ designer
483
+ .command("test <design>")
484
+ .description("Run the current draft once, as the console's test panel does")
485
+ .option("--trigger <nodeId>", "the trigger to fire (needed when the design has several)")
486
+ .option("--message <text>", "for a chat trigger: the message to send")
487
+ .option("--payload <json>", "what the trigger hands the graph, inline or as @file.json")
488
+ .option("--input <key=value>", HELP_INPUT, collectPairs)
489
+ .option("--execute-outputs", "really send emails, replies and HTTP calls (they dry-run by default)")
490
+ .option("--wait", "wait for the run to finish and print its trace and result")
491
+ .option("--timeout <duration>", "with --wait: give up after this long, e.g. 10m", "30m")
492
+ .action((ref, options) => run(async () => {
493
+ if (options.message !== undefined && options.payload !== undefined) {
494
+ throw new Error("Choose --message or --payload, not both.");
495
+ }
496
+ const timeout = parseWaitTimeout(options.timeout);
497
+ const ctx = requireContext(global());
498
+ const design = await resolveDesign(ctx, ref);
499
+ const detail = await ctx.qoren.designer.designs.get(design.designId);
500
+ const trigger = options.trigger ?? (detail.triggers.length === 1 ? detail.triggers[0]?.nodeId : undefined);
501
+ if (!trigger) {
502
+ throw new Error(detail.triggers.length === 0
503
+ ? "The draft has no trigger to fire. Add one first."
504
+ : `Pick a trigger with --trigger: ${detail.triggers.map((t) => t.nodeId).join(", ")}.`);
505
+ }
506
+ const payload = options.payload !== undefined
507
+ ? readJsonArg(options.payload, "--payload")
508
+ : options.message !== undefined
509
+ ? { message: { text: options.message, attachments: [] } }
510
+ : {};
511
+ const result = await ctx.qoren.designer.designs.testRun(design.designId, {
512
+ triggerNodeId: trigger,
513
+ payload,
514
+ ...nonEmpty(bindInputs(detail.inputs, options.input ?? [])),
515
+ ...(options.executeOutputs ? { executeOutputs: true } : {}),
516
+ });
517
+ const checkBack = `qoren designer test-get ${design.designId} ${result.runId}`;
518
+ if (!options.wait) {
519
+ emit(result, () => {
520
+ note(`Test run ${bold(result.runId)} started from ${trigger}.`);
521
+ note(dim(`Follow it with: ${checkBack}`));
522
+ note(dim(`Stop it with: qoren designer test-cancel ${design.designId} ${result.runId}`));
523
+ });
524
+ return;
525
+ }
526
+ note(dim(`Test run ${result.runId} started from ${trigger}.`));
527
+ const waited = await waitForRun(() => ctx.qoren.designer.designs.getTestRun(design.designId, result.runId), timeout);
528
+ // A test is for debugging, so show the whole trace, not just the outcome.
529
+ reportWaitedRun(waited, options.timeout, checkBack, { trace: true });
530
+ })());
531
+ designer
532
+ .command("test-get <design> <runId>")
533
+ .description("Show one test run: status, steps and result")
534
+ .action((ref, runId) => run(async () => {
535
+ const ctx = requireContext(global());
536
+ const design = await resolveDesign(ctx, ref);
537
+ const r = await ctx.qoren.designer.designs.getTestRun(design.designId, runId);
538
+ emit(r, () => printRun(r));
539
+ })());
540
+ designer
541
+ .command("test-cancel <design> <runId>")
542
+ .description("Stop a test run that is still going")
543
+ .action((ref, runId) => run(async () => {
544
+ const ctx = requireContext(global());
545
+ const design = await resolveDesign(ctx, ref);
546
+ const res = await ctx.qoren.designer.designs.cancelTestRun(design.designId, runId);
547
+ emit(res, () => note(res.cancelled
548
+ ? `Test run ${bold(res.runId)} cancelled.`
549
+ : `Test run ${bold(res.runId)} had already finished; nothing to cancel.`));
550
+ })());
551
+ designer
552
+ .command("deploy <design>")
553
+ .description("Deploy the latest published version as a new Custom agent")
554
+ .requiredOption("--name <name>", "display name")
555
+ .option("--slug <slug>", "identity, unique in your account (defaults to the name)")
556
+ .option("--client <client>", "the agency client it works for, by id or name")
557
+ .option("--pin <version>", "stay on this version instead of following the latest")
558
+ .option("--input <key=value>", HELP_INPUT, collectPairs)
559
+ .option("--budget <credits>", "per-run credit ceiling (default 1,000, which is $1)")
560
+ .action((ref, options) => run(async () => {
561
+ const pinned = options.pin !== undefined ? parseVersion(options.pin) : undefined;
562
+ const budget = options.budget !== undefined ? parseCredits(options.budget) : undefined;
563
+ const ctx = requireContext(global());
564
+ const design = await resolveDesign(ctx, ref);
565
+ const detail = await ctx.qoren.designer.designs.get(design.designId);
566
+ if (!detail.latestInputs) {
567
+ throw new Error(`${design.name} has no published version yet. Publish it first: qoren designer publish ${design.designId}`);
568
+ }
569
+ const customerId = options.client
570
+ ? (await resolveClient(ctx, options.client)).id
571
+ : undefined;
572
+ const result = await gated(ctx.qoren.designer.designs.deploy(design.designId, {
573
+ name: options.name,
574
+ ...(options.slug ? { slug: options.slug } : {}),
575
+ ...(customerId ? { customerId } : {}),
576
+ ...(pinned !== undefined
577
+ ? { versionPolicy: "pinned", pinnedVersion: pinned }
578
+ : { versionPolicy: "follow_latest" }),
579
+ ...(budget !== undefined ? { runGuardCredits: budget } : {}),
580
+ ...bindInputs(detail.latestInputs, options.input ?? []),
581
+ }));
582
+ emit(result, () => {
583
+ note(`Deployed ${bold(options.name)} as Custom agent ${bold(result.agentId)}.`);
584
+ if (result.instance.status === "needs_inputs") {
585
+ warn("It needs more inputs before it can run. Set them with qoren designer set.");
586
+ }
587
+ note(dim(`Try it: qoren agent chat ${result.agentId} "hello"`));
588
+ });
589
+ })());
590
+ designer
591
+ .command("starters")
592
+ .description("List the starter designs Qoren authored")
593
+ .action(() => run(async () => {
594
+ const ctx = requireContext(global());
595
+ const starters = await ctx.qoren.designer.starters();
596
+ emit(starters, () => table(starters, [
597
+ { header: "slug", value: (s) => s.slug },
598
+ { header: "name", value: (s) => s.name },
599
+ { header: "category", value: (s) => s.category },
600
+ { header: "blurb", value: (s) => s.blurb },
601
+ ]));
602
+ })());
603
+ designer
604
+ .command("models")
605
+ .description("List the models this account may use in a design")
606
+ .action(() => run(async () => {
607
+ const ctx = requireContext(global());
608
+ const models = await ctx.qoren.designer.models();
609
+ emit(models, () => {
610
+ note(dim(models.billingMode === "byok"
611
+ ? "On your own key: any model with tool support."
612
+ : "On managed billing: the curated model list."));
613
+ table(models.models, [
614
+ { header: "id", value: (m) => m.id },
615
+ { header: "name", value: (m) => m.name ?? "" },
616
+ { header: "tools", value: (m) => (m.supportsTools ? "yes" : "") },
617
+ { header: "in $/M", value: (m) => (m.promptUsdPerM ? m.promptUsdPerM.toFixed(2) : "") },
618
+ { header: "out $/M", value: (m) => (m.completionUsdPerM ? m.completionUsdPerM.toFixed(2) : "") },
619
+ { header: "context", value: (m) => (m.contextLength ? m.contextLength.toLocaleString("en-US") : "") },
620
+ ]);
621
+ });
622
+ })());
623
+ designer
624
+ .command("show <agent>")
625
+ .description("Show which design a Custom agent runs, and its inputs, version, budget and state")
626
+ .action((agentId) => run(async () => {
627
+ const ctx = requireContext(global());
628
+ const v = await ctx.qoren.designer.getSettings(agentId);
629
+ emit(v, () => printAgentDesigner(v));
630
+ })());
631
+ designer
632
+ .command("set <agent>")
633
+ .description("Change a Custom agent's inputs, version, budget or pause state")
634
+ .option("--name <name>", "rename it")
635
+ .option("--pin <version>", "stay on this version")
636
+ .option("--follow-latest", "move to each new version as it is published")
637
+ .option("--input <key=value>", `${HELP_INPUT}; merged into what it has`, collectPairs)
638
+ .option("--replace", "with --input: replace its values instead of merging into them")
639
+ .option("--budget <credits>", "per-run credit ceiling (1,000 credits is $1)")
640
+ .option("--pause", "refuse new runs and stop its schedules")
641
+ .option("--resume", "undo --pause")
642
+ .action((agentId, options) => run(async () => {
643
+ if (options.pin !== undefined && options.followLatest) {
644
+ throw new Error("Choose --pin or --follow-latest, not both.");
645
+ }
646
+ if (options.pause && options.resume) {
647
+ throw new Error("Choose --pause or --resume, not both.");
648
+ }
649
+ const change = {
650
+ ...(options.name ? { name: options.name } : {}),
651
+ ...(options.pin !== undefined
652
+ ? { versionPolicy: "pinned", pinnedVersion: parseVersion(options.pin) }
653
+ : {}),
654
+ ...(options.followLatest ? { versionPolicy: "follow_latest" } : {}),
655
+ ...(options.budget !== undefined ? { runGuardCredits: parseCredits(options.budget) } : {}),
656
+ ...(options.pause ? { paused: true } : {}),
657
+ ...(options.resume ? { paused: false } : {}),
658
+ };
659
+ const ctx = requireContext(global());
660
+ if (options.input?.length) {
661
+ const current = await ctx.qoren.designer.getSettings(agentId);
662
+ const detail = await ctx.qoren.designer.designs.get(current.designId);
663
+ const bound = nonEmpty(bindInputs(detail.latestInputs ?? detail.inputs, options.input));
664
+ Object.assign(change, options.replace ? bound : mergeBindings(current, bound));
665
+ }
666
+ if (Object.keys(change).length === 0) {
667
+ throw new Error("Nothing to change. See qoren designer set --help.");
668
+ }
669
+ const result = await ctx.qoren.designer.updateSettings(agentId, change);
670
+ emit(result, () => {
671
+ const s = result.designer;
672
+ note(`Updated ${bold(result.agent.name)}.`);
673
+ details([
674
+ ["Version", s.versionPolicy === "pinned" ? `pinned to v${s.pinnedVersion}` : "follows the latest"],
675
+ ["Budget", `${s.runGuardCredits.toLocaleString("en-US")} credits per run`],
676
+ ["Paused", s.paused ? "yes" : "no"],
677
+ ["Status", result.instance ? statusColor(result.instance.status) : ""],
678
+ ]);
679
+ });
680
+ })());
681
+ }
682
+ //# sourceMappingURL=designer.js.map