@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.
- package/LICENSE +202 -0
- package/README.md +162 -0
- package/dist/args.d.ts +5 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +23 -0
- package/dist/args.js.map +1 -0
- package/dist/commands/account.d.ts +4 -0
- package/dist/commands/account.d.ts.map +1 -0
- package/dist/commands/account.js +154 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/agent.d.ts +27 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +968 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/api.d.ts +4 -0
- package/dist/commands/api.d.ts.map +1 -0
- package/dist/commands/api.js +66 -0
- package/dist/commands/api.js.map +1 -0
- package/dist/commands/approvals.d.ts +11 -0
- package/dist/commands/approvals.d.ts.map +1 -0
- package/dist/commands/approvals.js +140 -0
- package/dist/commands/approvals.js.map +1 -0
- package/dist/commands/auth.d.ts +4 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +215 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/clients.d.ts +62 -0
- package/dist/commands/clients.d.ts.map +1 -0
- package/dist/commands/clients.js +311 -0
- package/dist/commands/clients.js.map +1 -0
- package/dist/commands/console.d.ts +4 -0
- package/dist/commands/console.d.ts.map +1 -0
- package/dist/commands/console.js +26 -0
- package/dist/commands/console.js.map +1 -0
- package/dist/commands/customAgent.d.ts +42 -0
- package/dist/commands/customAgent.d.ts.map +1 -0
- package/dist/commands/customAgent.js +449 -0
- package/dist/commands/customAgent.js.map +1 -0
- package/dist/commands/designer.d.ts +32 -0
- package/dist/commands/designer.d.ts.map +1 -0
- package/dist/commands/designer.js +682 -0
- package/dist/commands/designer.js.map +1 -0
- package/dist/commands/env.d.ts +15 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +245 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/interactive.d.ts +4 -0
- package/dist/commands/interactive.d.ts.map +1 -0
- package/dist/commands/interactive.js +30 -0
- package/dist/commands/interactive.js.map +1 -0
- package/dist/commands/jobs.d.ts +4 -0
- package/dist/commands/jobs.d.ts.map +1 -0
- package/dist/commands/jobs.js +59 -0
- package/dist/commands/jobs.js.map +1 -0
- package/dist/commands/secrets.d.ts +15 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +164 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/webhook.d.ts +4 -0
- package/dist/commands/webhook.d.ts.map +1 -0
- package/dist/commands/webhook.js +275 -0
- package/dist/commands/webhook.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +117 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +52 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +163 -0
- package/dist/context.js.map +1 -0
- package/dist/duration.d.ts +9 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +30 -0
- package/dist/duration.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/issues.d.ts +10 -0
- package/dist/issues.d.ts.map +1 -0
- package/dist/issues.js +45 -0
- package/dist/issues.js.map +1 -0
- package/dist/jobProgress.d.ts +12 -0
- package/dist/jobProgress.d.ts.map +1 -0
- package/dist/jobProgress.js +56 -0
- package/dist/jobProgress.js.map +1 -0
- package/dist/loopback.d.ts +28 -0
- package/dist/loopback.d.ts.map +1 -0
- package/dist/loopback.js +110 -0
- package/dist/loopback.js.map +1 -0
- package/dist/migrate/apply.d.ts +29 -0
- package/dist/migrate/apply.d.ts.map +1 -0
- package/dist/migrate/apply.js +135 -0
- package/dist/migrate/apply.js.map +1 -0
- package/dist/migrate/bundle.d.ts +89 -0
- package/dist/migrate/bundle.d.ts.map +1 -0
- package/dist/migrate/bundle.js +681 -0
- package/dist/migrate/bundle.js.map +1 -0
- package/dist/migrate/tar.d.ts +9 -0
- package/dist/migrate/tar.d.ts.map +1 -0
- package/dist/migrate/tar.js +89 -0
- package/dist/migrate/tar.js.map +1 -0
- package/dist/output.d.ts +46 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +152 -0
- package/dist/output.js.map +1 -0
- package/dist/program.d.ts +17 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +89 -0
- package/dist/program.js.map +1 -0
- package/dist/runtimes.d.ts +11 -0
- package/dist/runtimes.d.ts.map +1 -0
- package/dist/runtimes.js +20 -0
- package/dist/runtimes.js.map +1 -0
- package/dist/slug.d.ts +3 -0
- package/dist/slug.d.ts.map +1 -0
- package/dist/slug.js +15 -0
- package/dist/slug.js.map +1 -0
- package/dist/taskChain.d.ts +20 -0
- package/dist/taskChain.d.ts.map +1 -0
- package/dist/taskChain.js +40 -0
- package/dist/taskChain.js.map +1 -0
- package/dist/taskDelivery.d.ts +33 -0
- package/dist/taskDelivery.d.ts.map +1 -0
- package/dist/taskDelivery.js +67 -0
- package/dist/taskDelivery.js.map +1 -0
- package/dist/tui/app.d.ts +5 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +130 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/index.d.ts +12 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +34 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/model.d.ts +110 -0
- package/dist/tui/model.d.ts.map +1 -0
- package/dist/tui/model.js +257 -0
- package/dist/tui/model.js.map +1 -0
- package/dist/tui/overlay.d.ts +66 -0
- package/dist/tui/overlay.d.ts.map +1 -0
- package/dist/tui/overlay.js +216 -0
- package/dist/tui/overlay.js.map +1 -0
- package/dist/tui/panes/account.d.ts +5 -0
- package/dist/tui/panes/account.d.ts.map +1 -0
- package/dist/tui/panes/account.js +108 -0
- package/dist/tui/panes/account.js.map +1 -0
- package/dist/tui/panes/agents.d.ts +9 -0
- package/dist/tui/panes/agents.d.ts.map +1 -0
- package/dist/tui/panes/agents.js +340 -0
- package/dist/tui/panes/agents.js.map +1 -0
- package/dist/tui/panes/chat.d.ts +14 -0
- package/dist/tui/panes/chat.d.ts.map +1 -0
- package/dist/tui/panes/chat.js +169 -0
- package/dist/tui/panes/chat.js.map +1 -0
- package/dist/tui/panes/environments.d.ts +6 -0
- package/dist/tui/panes/environments.d.ts.map +1 -0
- package/dist/tui/panes/environments.js +364 -0
- package/dist/tui/panes/environments.js.map +1 -0
- package/dist/tui/panes/jobs.d.ts +5 -0
- package/dist/tui/panes/jobs.d.ts.map +1 -0
- package/dist/tui/panes/jobs.js +104 -0
- package/dist/tui/panes/jobs.js.map +1 -0
- package/dist/tui/store.d.ts +40 -0
- package/dist/tui/store.d.ts.map +1 -0
- package/dist/tui/store.js +124 -0
- package/dist/tui/store.js.map +1 -0
- package/dist/tui/ui.d.ts +95 -0
- package/dist/tui/ui.d.ts.map +1 -0
- package/dist/tui/ui.js +144 -0
- package/dist/tui/ui.js.map +1 -0
- 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
|