@agents24/cli 0.1.2 → 0.3.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/README.md +17 -8
- package/compatibility.json +6 -6
- package/dist/{chunk-MNGBA3HP.js → chunk-YOT3B62P.js} +161 -31
- package/dist/chunk-YOT3B62P.js.map +1 -0
- package/dist/cli.js +904 -199
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +71 -10
- package/dist/index.js +7 -3
- package/generated/schemas/resource-package/{1.0 → 2.0}/agent.schema.json +27 -5
- package/generated/schemas/resource-package/2.0/artifact.schema.json +67 -0
- package/generated/schemas/resource-package/{1.0 → 2.0}/manifest.schema.json +27 -4
- package/generated/schemas/resource-package/{1.0 → 2.0}/rag.schema.json +17 -3
- package/generated/schemas/resource-package/{1.0 → 2.0}/skill.schema.json +1 -1
- package/generated/schemas/resource-package/{1.0 → 2.0}/store.schema.json +8 -3
- package/generated/schemas/resource-package/{1.0 → 2.0}/workflow.schema.json +59 -3
- package/package.json +2 -2
- package/dist/chunk-MNGBA3HP.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
assertImportAllowed,
|
|
4
|
-
assertInstallPreviewReady,
|
|
5
4
|
createRemoteClient,
|
|
5
|
+
forkPackage,
|
|
6
6
|
initializePackage,
|
|
7
|
+
loadPackageFiles,
|
|
7
8
|
packPackage,
|
|
9
|
+
packPackageSnapshot,
|
|
8
10
|
parseMappings,
|
|
11
|
+
replacePackageFiles,
|
|
9
12
|
shouldPromptForDependency,
|
|
10
13
|
validatePackage
|
|
11
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YOT3B62P.js";
|
|
12
15
|
|
|
13
16
|
// src/cli.ts
|
|
14
|
-
import { basename, join, resolve } from "path";
|
|
15
|
-
import { chmod, readFile, writeFile } from "fs/promises";
|
|
16
|
-
import { createInterface } from "readline/promises";
|
|
17
|
-
import { createHash } from "crypto";
|
|
17
|
+
import { basename as basename2, dirname, join as join3, resolve as resolve3 } from "path";
|
|
18
|
+
import { chmod as chmod2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
19
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
20
|
+
import { createHash as createHash2 } from "crypto";
|
|
21
|
+
import { spawn } from "child_process";
|
|
18
22
|
import { isCancel, password, select, text } from "@clack/prompts";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
var
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
// src/cli-arguments.ts
|
|
25
|
+
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "remote", "yes", "allow-incomplete", "no-write-env", "prune", "apply"]);
|
|
26
|
+
var LIFECYCLE_COMMANDS = /* @__PURE__ */ new Set(["prepare", "plan", "apply", "pull", "dev", "publish", "setup", "status", "link", "resources", "fork"]);
|
|
27
|
+
function parseArguments(argv) {
|
|
28
|
+
const lifecycle = LIFECYCLE_COMMANDS.has(argv[0]);
|
|
29
|
+
if (!lifecycle && (argv[0] !== "package" || !argv[1])) {
|
|
30
|
+
throw new Error("Usage: agents24 <prepare|plan|apply|pull|dev|publish|setup|status|link|resources|fork> | agents24 package <init|validate|pack|export|preview|import>");
|
|
31
|
+
}
|
|
25
32
|
const flags = /* @__PURE__ */ new Map();
|
|
26
33
|
const positionals = [];
|
|
27
|
-
for (let index =
|
|
34
|
+
for (let index = lifecycle ? 1 : 2; index < argv.length; index += 1) {
|
|
28
35
|
const current = argv[index];
|
|
29
36
|
if (!current.startsWith("--")) {
|
|
30
37
|
positionals.push(current);
|
|
@@ -39,7 +46,7 @@ function parse(argv) {
|
|
|
39
46
|
if (value === void 0 || value.startsWith("--")) throw new Error(`--${rawName} requires a value`);
|
|
40
47
|
flags.set(rawName, [...flags.get(rawName) || [], value]);
|
|
41
48
|
}
|
|
42
|
-
return { command:
|
|
49
|
+
return { command: lifecycle ? argv[0] : argv[1], positionals, flags };
|
|
43
50
|
}
|
|
44
51
|
function flag(parsed, name) {
|
|
45
52
|
return parsed.flags.get(name)?.at(-1);
|
|
@@ -47,47 +54,280 @@ function flag(parsed, name) {
|
|
|
47
54
|
function values(parsed, name) {
|
|
48
55
|
return parsed.flags.get(name) || [];
|
|
49
56
|
}
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
|
|
58
|
+
// src/cli-output.ts
|
|
59
|
+
var sensitiveValues = /* @__PURE__ */ new Set();
|
|
60
|
+
function registerSensitiveValue(value) {
|
|
61
|
+
if (value.length >= 8) sensitiveValues.add(value);
|
|
62
|
+
}
|
|
63
|
+
function redactText(value) {
|
|
64
|
+
let result = value;
|
|
65
|
+
for (const secret of sensitiveValues) result = result.split(secret).join("[REDACTED]");
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function sanitize(value) {
|
|
69
|
+
if (typeof value === "string") return redactText(value);
|
|
70
|
+
if (Array.isArray(value)) return value.map(sanitize);
|
|
71
|
+
if (!value || typeof value !== "object") return value;
|
|
72
|
+
return Object.fromEntries(
|
|
73
|
+
Object.entries(value).map(([key, item]) => [key, sanitize(item)])
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
function readDiagnostics(value) {
|
|
77
|
+
return Array.isArray(value) ? value : void 0;
|
|
78
|
+
}
|
|
79
|
+
function diagnostics(error) {
|
|
80
|
+
if (!error || typeof error !== "object") return void 0;
|
|
81
|
+
const record = error;
|
|
82
|
+
const fromError = readDiagnostics(record.diagnostics);
|
|
83
|
+
if (fromError) return fromError;
|
|
84
|
+
if (!record.details || typeof record.details !== "object" || Array.isArray(record.details)) return void 0;
|
|
85
|
+
const details = record.details;
|
|
86
|
+
const fromDetails = readDiagnostics(details.diagnostics);
|
|
87
|
+
if (fromDetails) return fromDetails;
|
|
88
|
+
const nested = details.detail;
|
|
89
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return void 0;
|
|
90
|
+
return readDiagnostics(nested.diagnostics);
|
|
52
91
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
92
|
+
function failurePayload(error) {
|
|
93
|
+
const diagnosticList = diagnostics(error);
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
error: redactText(error instanceof Error ? error.message : "Unexpected CLI failure"),
|
|
97
|
+
...error && typeof error === "object" && "phase" in error ? { phase: error.phase } : {},
|
|
98
|
+
...error && typeof error === "object" && "imported" in error ? { import_result: error.imported } : {},
|
|
99
|
+
...diagnosticList ? { diagnostics: diagnosticList } : {}
|
|
100
|
+
};
|
|
55
101
|
}
|
|
56
|
-
function
|
|
102
|
+
function writeResult(result, parsed) {
|
|
103
|
+
const compact = flag(parsed, "json") === "true";
|
|
104
|
+
const safeResult = sanitize(result);
|
|
105
|
+
const body = compact ? JSON.stringify(safeResult) : JSON.stringify(safeResult, null, 2);
|
|
106
|
+
const stream = result.ok === false ? process.stderr : process.stdout;
|
|
107
|
+
stream.write(`${body}
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/cli-installation-state.ts
|
|
112
|
+
import { basename, join, resolve } from "path";
|
|
113
|
+
import { readFile, stat, writeFile } from "fs/promises";
|
|
114
|
+
function canonicalJson(value) {
|
|
115
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
116
|
+
if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
|
|
117
|
+
return JSON.stringify(value);
|
|
118
|
+
}
|
|
119
|
+
function isNotFoundResponse(error) {
|
|
120
|
+
return Boolean(error && typeof error === "object" && "status" in error && Number(error.status) === 404);
|
|
121
|
+
}
|
|
122
|
+
function packageUpload(data, input) {
|
|
123
|
+
return {
|
|
124
|
+
data,
|
|
125
|
+
filename: basename(input).endsWith(".zip") ? basename(input) : "resource.agents24.zip"
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async function packageDirectory(input) {
|
|
129
|
+
try {
|
|
130
|
+
return (await stat(input)).isDirectory() ? resolve(input) : void 0;
|
|
131
|
+
} catch {
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function ensureIgnored(directory, entries) {
|
|
136
|
+
const target = join(directory, ".gitignore");
|
|
137
|
+
let current = "";
|
|
138
|
+
try {
|
|
139
|
+
current = await readFile(target, "utf8");
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error.code !== "ENOENT") throw error;
|
|
142
|
+
}
|
|
143
|
+
const lines = new Set(current.split(/\r?\n/).map((item) => item.trim()).filter(Boolean));
|
|
144
|
+
const missing = entries.filter((item) => !lines.has(item));
|
|
145
|
+
if (!missing.length) return;
|
|
146
|
+
const separator = current && !current.endsWith("\n") ? "\n" : "";
|
|
147
|
+
await writeFile(target, `${current}${separator}${missing.join("\n")}
|
|
148
|
+
`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/cli-secrets.ts
|
|
152
|
+
import { randomBytes } from "crypto";
|
|
153
|
+
import { chmod, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
154
|
+
import { join as join2 } from "path";
|
|
155
|
+
import { parse as parseYaml } from "yaml";
|
|
156
|
+
function parseEnv(textValue) {
|
|
157
|
+
const result = {};
|
|
158
|
+
for (const line of textValue.split(/\r?\n/)) {
|
|
159
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line.trim());
|
|
160
|
+
if (!match) continue;
|
|
161
|
+
const raw = match[2].trim();
|
|
162
|
+
try {
|
|
163
|
+
result[match[1]] = raw.startsWith('"') ? JSON.parse(raw) : raw;
|
|
164
|
+
} catch {
|
|
165
|
+
result[match[1]] = raw;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
57
168
|
return result;
|
|
58
169
|
}
|
|
59
|
-
async function
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
170
|
+
async function requirements(input) {
|
|
171
|
+
const files = await loadPackageFiles(input);
|
|
172
|
+
const manifest = parseYaml(files.get("agents24.yaml") || "");
|
|
173
|
+
const requires = manifest.requires && typeof manifest.requires === "object" ? manifest.requires : {};
|
|
174
|
+
const secrets = requires.secrets && typeof requires.secrets === "object" ? requires.secrets : {};
|
|
175
|
+
return Object.entries(secrets).map(([key, raw]) => {
|
|
176
|
+
const item = raw && typeof raw === "object" ? raw : {};
|
|
177
|
+
const env = String(item.env || "").trim();
|
|
178
|
+
if (!env) throw new Error(`Secret requirement ${key} must declare env`);
|
|
179
|
+
return { key: `$secrets.${key}`, env, generate: item.generate === true };
|
|
66
180
|
});
|
|
67
|
-
|
|
181
|
+
}
|
|
182
|
+
async function preparePackage(input) {
|
|
183
|
+
const directory = await packageDirectory(input);
|
|
184
|
+
if (!directory) throw new Error("prepare requires a Resource Package directory");
|
|
185
|
+
const local = await validatePackage(input);
|
|
186
|
+
if (!local.valid) throw Object.assign(new Error("Resource package is invalid"), { diagnostics: local.diagnostics });
|
|
187
|
+
const target = join2(directory, ".env.local");
|
|
188
|
+
let current = "";
|
|
189
|
+
try {
|
|
190
|
+
current = await readFile2(target, "utf8");
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error.code !== "ENOENT") throw error;
|
|
193
|
+
}
|
|
194
|
+
const values2 = parseEnv(current);
|
|
195
|
+
const generated = [];
|
|
196
|
+
const preserved = [];
|
|
197
|
+
let next = current;
|
|
198
|
+
for (const requirement of await requirements(input)) {
|
|
199
|
+
if (values2[requirement.env] || process.env[requirement.env]) {
|
|
200
|
+
preserved.push(requirement.env);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (!requirement.generate) continue;
|
|
204
|
+
const value = randomBytes(32).toString("base64url");
|
|
205
|
+
next += `${next && !next.endsWith("\n") ? "\n" : ""}${requirement.env}=${JSON.stringify(value)}
|
|
206
|
+
`;
|
|
207
|
+
generated.push(requirement.env);
|
|
208
|
+
}
|
|
209
|
+
await writeFile2(target, next, { mode: 384 });
|
|
210
|
+
await chmod(target, 384);
|
|
211
|
+
await ensureIgnored(directory, [".env.local"]);
|
|
212
|
+
return { ok: true, package: directory, env_file: target, generated, preserved };
|
|
213
|
+
}
|
|
214
|
+
async function secretValues(input) {
|
|
215
|
+
const directory = await packageDirectory(input);
|
|
216
|
+
let local = {};
|
|
217
|
+
if (directory) {
|
|
218
|
+
try {
|
|
219
|
+
local = parseEnv(await readFile2(join2(directory, ".env.local"), "utf8"));
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error.code !== "ENOENT") throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const result = {};
|
|
225
|
+
for (const requirement of await requirements(input)) {
|
|
226
|
+
const value = String(process.env[requirement.env] || local[requirement.env] || "");
|
|
227
|
+
if (value) result[requirement.key] = value;
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/cli-pull.ts
|
|
233
|
+
import { createInterface } from "readline/promises";
|
|
234
|
+
async function confirmPull(parsed, plan) {
|
|
235
|
+
if (flag(parsed, "yes") === "true") return;
|
|
236
|
+
if (flag(parsed, "json") === "true") throw new Error("pull requires --yes with --json");
|
|
237
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("pull requires --yes in noninteractive mode");
|
|
238
|
+
const files = Array.isArray(plan.files) ? plan.files : [];
|
|
239
|
+
for (const item of files) process.stdout.write(`${String(item.status || "M")} ${String(item.path || "")}
|
|
240
|
+
`);
|
|
241
|
+
const summary = plan.summary && typeof plan.summary === "object" ? plan.summary : {};
|
|
68
242
|
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
69
|
-
const answer = await prompt.question(
|
|
243
|
+
const answer = await prompt.question(
|
|
244
|
+
`Pull platform drafts (${summary.files_added || 0} added, ${summary.files_modified || 0} modified, ${summary.files_removed || 0} removed)? [y/N] `
|
|
245
|
+
);
|
|
70
246
|
prompt.close();
|
|
71
|
-
if (!/^y(?:es)?$/i.test(answer.trim())) throw new Error("
|
|
247
|
+
if (!/^y(?:es)?$/i.test(answer.trim())) throw new Error("Pull cancelled");
|
|
248
|
+
}
|
|
249
|
+
async function pullPackage(options) {
|
|
250
|
+
const { parsed, input, client, installationId } = options;
|
|
251
|
+
const preview = await client.resourceInstallations.planPull(
|
|
252
|
+
installationId,
|
|
253
|
+
packageUpload(await packPackageSnapshot(input), input)
|
|
254
|
+
);
|
|
255
|
+
const plan = preview.plan && typeof preview.plan === "object" ? preview.plan : {};
|
|
256
|
+
await confirmPull(parsed, plan);
|
|
257
|
+
const pullId = String(preview.pull_id || "");
|
|
258
|
+
const payload = await client.resourceInstallations.downloadPull(installationId, pullId);
|
|
259
|
+
if (payload.platform_snapshot_hash !== preview.platform_snapshot_hash || payload.package_hash !== preview.package_hash || !payload.files || typeof payload.files !== "object" || Array.isArray(payload.files)) throw new Error("Pulled package snapshot does not match its preview");
|
|
260
|
+
let accepted = {};
|
|
261
|
+
await replacePackageFiles(input, payload.files, async () => {
|
|
262
|
+
accepted = await client.resourceInstallations.acceptPull(
|
|
263
|
+
installationId,
|
|
264
|
+
pullId,
|
|
265
|
+
{
|
|
266
|
+
platform_snapshot_hash: String(preview.platform_snapshot_hash || ""),
|
|
267
|
+
package_hash: String(preview.package_hash || "")
|
|
268
|
+
},
|
|
269
|
+
{ idempotencyKey: `pull-${pullId}` }
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
ok: true,
|
|
274
|
+
phase: "pulled",
|
|
275
|
+
installation_id: installationId,
|
|
276
|
+
pull_id: preview.pull_id,
|
|
277
|
+
package_hash: preview.package_hash,
|
|
278
|
+
summary: plan.summary || {},
|
|
279
|
+
files: Array.isArray(plan.files) ? plan.files : [],
|
|
280
|
+
status: accepted.status
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/cli-status.ts
|
|
285
|
+
async function installedPackageStatus(input, client, status) {
|
|
286
|
+
const callableBindings = Array.isArray(status.callable_bindings) ? status.callable_bindings : [];
|
|
287
|
+
const publicationCurrent = status.publication_state === "current";
|
|
288
|
+
const healthy = callableBindings.filter((item) => item.draft_status === "attached" && (!publicationCurrent || item.published_status === "attached")).length;
|
|
289
|
+
const compiled = await client.resourcePackages.validatePackage(
|
|
290
|
+
packageUpload(await packPackage(input), input)
|
|
291
|
+
);
|
|
292
|
+
const localPackageHash = typeof compiled.package_hash === "string" ? compiled.package_hash : null;
|
|
293
|
+
return {
|
|
294
|
+
ok: true,
|
|
295
|
+
installed: true,
|
|
296
|
+
binding_summary: {
|
|
297
|
+
total: callableBindings.length,
|
|
298
|
+
healthy,
|
|
299
|
+
unhealthy: callableBindings.length - healthy
|
|
300
|
+
},
|
|
301
|
+
synchronization: {
|
|
302
|
+
direction: status.last_sync_direction || null,
|
|
303
|
+
synchronized_at: status.last_synchronized_at || null,
|
|
304
|
+
synchronized_package_hash: status.synchronized_package_hash || null,
|
|
305
|
+
local_package_hash: localPackageHash,
|
|
306
|
+
diverged: Boolean(localPackageHash && status.synchronized_package_hash && localPackageHash !== status.synchronized_package_hash)
|
|
307
|
+
},
|
|
308
|
+
installation: status
|
|
309
|
+
};
|
|
72
310
|
}
|
|
73
|
-
|
|
74
|
-
|
|
311
|
+
|
|
312
|
+
// src/cli-requirements.ts
|
|
313
|
+
import { createHash } from "crypto";
|
|
314
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
315
|
+
async function promptImportMappings(client, preview, current, enabled) {
|
|
316
|
+
if (!enabled) return current;
|
|
75
317
|
const dependencies = Array.isArray(preview.dependencies) ? preview.dependencies.filter((item) => Boolean(item && typeof item === "object" && item.status === "unresolved")) : [];
|
|
76
318
|
const unique = new Map(dependencies.map((item) => [String(item.requirement_key || item.id), item]));
|
|
77
319
|
if (!unique.size) return current;
|
|
78
|
-
const prompt =
|
|
320
|
+
const prompt = createInterface2({ input: process.stdin, output: process.stdout });
|
|
79
321
|
try {
|
|
80
322
|
const next = { ...current };
|
|
81
323
|
for (const [key, dependency] of unique) {
|
|
82
|
-
if (!shouldPromptForDependency(dependency))
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
324
|
+
if (!shouldPromptForDependency(dependency)) continue;
|
|
85
325
|
if (dependency.kind === "secret") {
|
|
86
326
|
const answer2 = await prompt.question(`${dependency.source_name || key} secret ($secret:name, blank to omit): `);
|
|
87
327
|
if (answer2.trim()) next[key] = answer2.trim();
|
|
88
328
|
continue;
|
|
89
329
|
}
|
|
90
|
-
const response = await client.
|
|
330
|
+
const response = await client.resourcePackages.candidates({
|
|
91
331
|
kind: String(dependency.kind),
|
|
92
332
|
query: String(dependency.source_name || ""),
|
|
93
333
|
requiredCapability: dependency.required_capability === "embedding" ? "embedding" : dependency.required_capability === "chat" ? "chat" : void 0
|
|
@@ -104,26 +344,257 @@ async function promptMappings(parsed, client, preview, current) {
|
|
|
104
344
|
prompt.close();
|
|
105
345
|
}
|
|
106
346
|
}
|
|
107
|
-
async function
|
|
347
|
+
async function promptInstallationRequirementLinks(client, installationId, plan, enabled) {
|
|
348
|
+
if (!enabled) return false;
|
|
349
|
+
const requirements2 = Array.isArray(plan.external_requirements) ? plan.external_requirements.filter((item) => Boolean(
|
|
350
|
+
item && typeof item === "object" && item.required === true && item.configured !== true && !["model", "secret"].includes(String(item.kind))
|
|
351
|
+
)) : [];
|
|
352
|
+
if (!requirements2.length) return false;
|
|
353
|
+
const prompt = createInterface2({ input: process.stdin, output: process.stdout });
|
|
354
|
+
let linked = false;
|
|
355
|
+
try {
|
|
356
|
+
for (const requirement of requirements2) {
|
|
357
|
+
const key = String(requirement.requirement_key || "");
|
|
358
|
+
const response = await client.resourcePackages.candidates({ kind: String(requirement.kind), query: String(requirement.name || "") });
|
|
359
|
+
const candidates = Array.isArray(response.items) ? response.items : [];
|
|
360
|
+
process.stdout.write(`${requirement.name || key}: ${candidates.map((item, index) => `${index + 1}) ${item.name}`).join(" ")}
|
|
361
|
+
`);
|
|
362
|
+
const answer = await prompt.question("Choose a candidate number (blank to leave unresolved): ");
|
|
363
|
+
const selected = candidates[Number(answer) - 1];
|
|
364
|
+
if (!selected?.id) continue;
|
|
365
|
+
await client.resourceInstallations.link(installationId, { resource_key: key, resource_id: String(selected.id) }, {
|
|
366
|
+
idempotencyKey: `link-${createHash("sha256").update(`${installationId}:${key}:${selected.id}`).digest("hex").slice(0, 40)}`
|
|
367
|
+
});
|
|
368
|
+
linked = true;
|
|
369
|
+
}
|
|
370
|
+
return linked;
|
|
371
|
+
} finally {
|
|
372
|
+
prompt.close();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/cli-development.ts
|
|
377
|
+
import { createHmac } from "crypto";
|
|
378
|
+
import { hostname } from "os";
|
|
379
|
+
import { resolve as resolve2 } from "path";
|
|
380
|
+
import { parse as parseYaml2 } from "yaml";
|
|
381
|
+
var DEFAULT_API_BASE_URL = "https://api.agents24.dev";
|
|
382
|
+
var MANIFEST_POLL_INTERVAL_MS = 2e3;
|
|
383
|
+
async function developmentArtifacts(input) {
|
|
384
|
+
const directory = await packageDirectory(input);
|
|
385
|
+
if (!directory) throw new Error("dev requires a Resource Package directory");
|
|
386
|
+
const files = await loadPackageFiles(input);
|
|
387
|
+
const manifest = parseYaml2(files.get("agents24.yaml") || "");
|
|
388
|
+
const resources = Array.isArray(manifest.resources) ? manifest.resources : [];
|
|
389
|
+
const requires = manifest.requires && typeof manifest.requires === "object" ? manifest.requires : {};
|
|
390
|
+
const requiredSecrets = requires.secrets && typeof requires.secrets === "object" ? requires.secrets : {};
|
|
391
|
+
const result = [];
|
|
392
|
+
for (const resource of resources) {
|
|
393
|
+
const path = String(resource.path || "");
|
|
394
|
+
if (!/^artifacts\/[a-z0-9][a-z0-9-]*\/artifact\.yaml$/.test(path)) continue;
|
|
395
|
+
const sourceText = files.get(path);
|
|
396
|
+
if (!sourceText) continue;
|
|
397
|
+
const source = parseYaml2(sourceText);
|
|
398
|
+
if (source.execution_target !== "self_hosted") continue;
|
|
399
|
+
const development = source.development && typeof source.development === "object" ? source.development : void 0;
|
|
400
|
+
if (!development) continue;
|
|
401
|
+
const server = source.server && typeof source.server === "object" ? source.server : {};
|
|
402
|
+
const auth = server.auth && typeof server.auth === "object" ? server.auth : {};
|
|
403
|
+
const secretRequirement = String(auth.secret || "");
|
|
404
|
+
const secretKey = secretRequirement.startsWith("$secrets.") ? secretRequirement.slice("$secrets.".length) : "";
|
|
405
|
+
const secretDeclaration = secretKey && requiredSecrets[secretKey] && typeof requiredSecrets[secretKey] === "object" ? requiredSecrets[secretKey] : void 0;
|
|
406
|
+
const cwd = resolve2(directory, String(development.cwd || "."));
|
|
407
|
+
if (cwd !== directory && !cwd.startsWith(`${directory}/`)) throw new Error(`Artifact ${resource.key} development.cwd must remain inside the package`);
|
|
408
|
+
result.push({
|
|
409
|
+
key: String(resource.key || ""),
|
|
410
|
+
baseUrl: String(development.base_url || ""),
|
|
411
|
+
command: development.command ? String(development.command) : void 0,
|
|
412
|
+
args: Array.isArray(development.args) ? development.args.map(String) : [],
|
|
413
|
+
cwd,
|
|
414
|
+
protocol: {
|
|
415
|
+
manifest_path: server.manifest_path,
|
|
416
|
+
health_path: server.health_path,
|
|
417
|
+
verify_path: server.verify_path,
|
|
418
|
+
invoke_path: server.invoke_path,
|
|
419
|
+
auth_mode: auth.mode,
|
|
420
|
+
signing_secret_requirement: auth.secret
|
|
421
|
+
},
|
|
422
|
+
signingSecretEnv: secretDeclaration?.env ? String(secretDeclaration.env) : void 0
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
async function waitForServer(baseUrl, healthPath) {
|
|
428
|
+
const target = new URL(String(healthPath || "/.well-known/agents24/artifact/health"), baseUrl);
|
|
429
|
+
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
430
|
+
try {
|
|
431
|
+
const response = await fetch(target);
|
|
432
|
+
if (response.ok) return;
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 250));
|
|
436
|
+
}
|
|
437
|
+
throw new Error(`Local Artifact server did not become healthy at ${target}`);
|
|
438
|
+
}
|
|
439
|
+
async function openDevelopmentRelay(client, installationIdValue, artifact, localEnv) {
|
|
440
|
+
const admission = await client.resourceInstallations.prepareDevelopmentSession(installationIdValue, {
|
|
441
|
+
resource_key: artifact.key,
|
|
442
|
+
machine_label: hostname()
|
|
443
|
+
});
|
|
444
|
+
const apiUrl = new URL(String(process.env.AGENTS24_BASE_URL || DEFAULT_API_BASE_URL));
|
|
445
|
+
apiUrl.protocol = apiUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
446
|
+
apiUrl.pathname = String(admission.relay_path);
|
|
447
|
+
const socket = new WebSocket(apiUrl, [
|
|
448
|
+
"agents24-artifact-relay",
|
|
449
|
+
`agents24-credential-${String(admission.relay_token)}`
|
|
450
|
+
]);
|
|
451
|
+
const state = {};
|
|
452
|
+
let manifestHash;
|
|
453
|
+
let pendingHash;
|
|
454
|
+
let polling = false;
|
|
455
|
+
const manifestUrl = new URL(String(artifact.protocol.manifest_path || "/.well-known/agents24/artifact"), artifact.baseUrl);
|
|
456
|
+
const localManifestHash = async () => {
|
|
457
|
+
const response = await fetch(manifestUrl);
|
|
458
|
+
if (!response.ok) throw new Error("Local Artifact manifest is unavailable");
|
|
459
|
+
return canonicalJson(await response.json());
|
|
460
|
+
};
|
|
461
|
+
const pollManifest = async () => {
|
|
462
|
+
if (polling || socket.readyState !== WebSocket.OPEN || pendingHash !== void 0) return;
|
|
463
|
+
polling = true;
|
|
464
|
+
try {
|
|
465
|
+
const nextHash = await localManifestHash();
|
|
466
|
+
if (manifestHash !== void 0 && nextHash === manifestHash) return;
|
|
467
|
+
if (manifestHash === void 0) {
|
|
468
|
+
manifestHash = nextHash;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
pendingHash = nextHash;
|
|
472
|
+
socket.send(JSON.stringify({ type: "manifest_changed" }));
|
|
473
|
+
} catch {
|
|
474
|
+
pendingHash = "unavailable";
|
|
475
|
+
socket.send(JSON.stringify({ type: "manifest_changed" }));
|
|
476
|
+
} finally {
|
|
477
|
+
polling = false;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
await new Promise((resolveReady, rejectReady) => {
|
|
481
|
+
socket.addEventListener("error", () => rejectReady(new Error(`Artifact relay failed for ${artifact.key}`)), { once: true });
|
|
482
|
+
socket.addEventListener("message", async (event) => {
|
|
483
|
+
const message = JSON.parse(String(event.data));
|
|
484
|
+
if (message.type === "ready") {
|
|
485
|
+
try {
|
|
486
|
+
manifestHash = await localManifestHash();
|
|
487
|
+
} catch {
|
|
488
|
+
manifestHash = void 0;
|
|
489
|
+
}
|
|
490
|
+
resolveReady();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (message.type === "manifest_refreshed") {
|
|
494
|
+
if (pendingHash && pendingHash !== "unavailable") manifestHash = pendingHash;
|
|
495
|
+
pendingHash = void 0;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (message.type === "manifest_refresh_failed") {
|
|
499
|
+
pendingHash = void 0;
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (message.type === "superseded" || message.type === "revoked") {
|
|
503
|
+
state.terminalReason = String(message.type);
|
|
504
|
+
socket.close();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (message.type !== "request") return;
|
|
508
|
+
const method = message.operation === "health" || message.operation === "manifest" ? "GET" : "POST";
|
|
509
|
+
try {
|
|
510
|
+
const requestBody = method === "POST" ? JSON.stringify(message.body || {}) : void 0;
|
|
511
|
+
const headers = method === "POST" ? { "content-type": "application/json" } : {};
|
|
512
|
+
if (method === "POST" && String(artifact.protocol.auth_mode || "hmac_sha256") === "hmac_sha256") {
|
|
513
|
+
const signingSecret = String(
|
|
514
|
+
artifact.signingSecretEnv && (localEnv[artifact.signingSecretEnv] || process.env[artifact.signingSecretEnv]) || ""
|
|
515
|
+
);
|
|
516
|
+
if (!signingSecret) throw new Error(`Artifact ${artifact.key} signing secret is unavailable`);
|
|
517
|
+
const timestamp = String(Date.now());
|
|
518
|
+
const signature = createHmac("sha256", signingSecret).update(`${timestamp}.${requestBody}`).digest("hex");
|
|
519
|
+
headers["x-agents24-timestamp"] = timestamp;
|
|
520
|
+
headers["x-agents24-signature"] = `sha256=${signature}`;
|
|
521
|
+
}
|
|
522
|
+
const response = await fetch(new URL(String(message.path || "/"), artifact.baseUrl), { method, headers, body: requestBody });
|
|
523
|
+
const textBody = await response.text();
|
|
524
|
+
let body = textBody;
|
|
525
|
+
try {
|
|
526
|
+
body = textBody ? JSON.parse(textBody) : null;
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
socket.send(JSON.stringify({ type: "response", request_id: message.request_id, status: response.status, body }));
|
|
530
|
+
} catch {
|
|
531
|
+
socket.send(JSON.stringify({ type: "response", request_id: message.request_id, status: 502, body: { status: "failed" } }));
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
const heartbeat = setInterval(() => {
|
|
535
|
+
if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "heartbeat" }));
|
|
536
|
+
}, 15e3);
|
|
537
|
+
const manifestPoll = setInterval(() => {
|
|
538
|
+
void pollManifest();
|
|
539
|
+
}, MANIFEST_POLL_INTERVAL_MS);
|
|
540
|
+
socket.addEventListener("close", () => {
|
|
541
|
+
clearInterval(heartbeat);
|
|
542
|
+
clearInterval(manifestPoll);
|
|
543
|
+
}, { once: true });
|
|
544
|
+
});
|
|
545
|
+
return { sessionId: String(admission.id), socket, state };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/cli.ts
|
|
549
|
+
var DEFAULT_API_BASE_URL2 = "https://api.agents24.dev";
|
|
550
|
+
var DEFAULT_LOCAL_CLIENT_ORIGINS = ["http://localhost:5173", "http://127.0.0.1:5173"];
|
|
551
|
+
function mappings(parsed) {
|
|
552
|
+
return parseMappings(values(parsed, "map"));
|
|
553
|
+
}
|
|
554
|
+
async function confirmation(parsed, preview) {
|
|
555
|
+
const resources = Array.isArray(preview.resources) ? preview.resources : [];
|
|
556
|
+
const yes = flag(parsed, "yes") === "true";
|
|
557
|
+
assertImportAllowed(preview, {
|
|
558
|
+
allowIncomplete: flag(parsed, "allow-incomplete") === "true",
|
|
559
|
+
yes,
|
|
560
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
561
|
+
});
|
|
562
|
+
if (yes) return;
|
|
563
|
+
const prompt = createInterface3({ input: process.stdin, output: process.stdout });
|
|
564
|
+
const answer = await prompt.question(`Import ${resources.length} resource draft(s)? [y/N] `);
|
|
565
|
+
prompt.close();
|
|
566
|
+
if (!/^y(?:es)?$/i.test(answer.trim())) throw new Error("Import cancelled");
|
|
567
|
+
}
|
|
568
|
+
async function applyConfirmation(parsed, plan) {
|
|
569
|
+
if (flag(parsed, "yes") === "true") return;
|
|
570
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("apply requires --yes in noninteractive mode");
|
|
571
|
+
const actions = Array.isArray(plan.actions) ? plan.actions.length : 0;
|
|
572
|
+
const removals = Array.isArray(plan.content) ? plan.content.reduce((count, item) => count + (Array.isArray(item.remove) ? item.remove.length : 0), 0) : 0;
|
|
573
|
+
const prompt = createInterface3({ input: process.stdin, output: process.stdout });
|
|
574
|
+
const answer = await prompt.question(`Apply ${actions} resource action(s)${removals ? ` and ${removals} content removal(s)` : ""}? [y/N] `);
|
|
575
|
+
prompt.close();
|
|
576
|
+
if (!/^y(?:es)?$/i.test(answer.trim())) throw new Error("Apply cancelled");
|
|
577
|
+
}
|
|
578
|
+
async function pollOperation(client, operation) {
|
|
579
|
+
let current = operation;
|
|
580
|
+
for (let attempt = 0; attempt < 900; attempt += 1) {
|
|
581
|
+
const status = String(current.status || "");
|
|
582
|
+
if (["completed", "failed"].includes(status)) return current;
|
|
583
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 1e3));
|
|
584
|
+
current = await client.resourceInstallations.operationStatus(String(current.operation_id));
|
|
585
|
+
}
|
|
586
|
+
throw new Error("Resource apply did not finish within 15 minutes");
|
|
587
|
+
}
|
|
588
|
+
async function packedValidatedPackage(parsed) {
|
|
108
589
|
const input = parsed.positionals[0];
|
|
109
590
|
if (!input) throw new Error(`${parsed.command} requires a package directory or ZIP`);
|
|
110
591
|
const local = await validatePackage(input);
|
|
111
592
|
if (!local.valid) throw Object.assign(new Error("Resource package is invalid"), { diagnostics: local.diagnostics });
|
|
112
593
|
const data = await packPackage(input);
|
|
113
|
-
|
|
114
|
-
const result = await remoteClient.resourcePackages.compilePackage(upload(data, input));
|
|
115
|
-
return { data, result };
|
|
116
|
-
}
|
|
117
|
-
function canonical(value) {
|
|
118
|
-
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
119
|
-
if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
|
|
120
|
-
return JSON.stringify(value);
|
|
121
|
-
}
|
|
122
|
-
function installKey(bundle, map) {
|
|
123
|
-
return `install-${createHash("sha256").update(canonical({ bundle, mappings: map })).digest("hex").slice(0, 40)}`;
|
|
594
|
+
return { data, filename: `${basename2(resolve3(input))}.agents24.zip` };
|
|
124
595
|
}
|
|
125
|
-
function
|
|
126
|
-
return
|
|
596
|
+
function importKey(packageHash, map) {
|
|
597
|
+
return `import-${createHash2("sha256").update(canonicalJson({ package_hash: packageHash, mappings: map })).digest("hex").slice(0, 40)}`;
|
|
127
598
|
}
|
|
128
599
|
function interactive(parsed) {
|
|
129
600
|
return flag(parsed, "yes") !== "true" && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -131,20 +602,47 @@ function interactive(parsed) {
|
|
|
131
602
|
function cancelled(value) {
|
|
132
603
|
if (isCancel(value)) throw new Error("Installation cancelled");
|
|
133
604
|
}
|
|
134
|
-
async function
|
|
605
|
+
async function apiKeyForLifecycle(parsed) {
|
|
606
|
+
await loadPackageLifecycleEnvironment(parsed.positionals[0]);
|
|
135
607
|
let apiKey = String(process.env.AGENTS24_API_KEY || "").trim();
|
|
136
608
|
if (!apiKey && interactive(parsed)) {
|
|
137
609
|
const answer = await password({ message: "Agents24 API key", validate: (value) => String(value || "").trim() ? void 0 : "The API key is required." });
|
|
138
610
|
cancelled(answer);
|
|
139
611
|
apiKey = String(answer).trim();
|
|
140
612
|
}
|
|
141
|
-
if (!apiKey) throw new Error("AGENTS24_API_KEY is required for noninteractive
|
|
613
|
+
if (!apiKey) throw new Error("AGENTS24_API_KEY is required for noninteractive resource management");
|
|
142
614
|
if (/[\r\n\0]/.test(apiKey)) throw new Error("AGENTS24_API_KEY contains invalid control characters");
|
|
615
|
+
registerSensitiveValue(apiKey);
|
|
143
616
|
return apiKey;
|
|
144
617
|
}
|
|
618
|
+
async function loadPackageLifecycleEnvironment(input) {
|
|
619
|
+
if (!input) return;
|
|
620
|
+
const directory = await packageDirectory(input);
|
|
621
|
+
if (!directory) return;
|
|
622
|
+
for (const path of [join3(dirname(directory), ".env.local"), join3(directory, ".env.local")]) {
|
|
623
|
+
try {
|
|
624
|
+
const values2 = parseEnv(await readFile3(path, "utf8"));
|
|
625
|
+
for (const [name, value] of Object.entries(values2)) {
|
|
626
|
+
if (!process.env[name]) process.env[name] = value;
|
|
627
|
+
}
|
|
628
|
+
} catch (error) {
|
|
629
|
+
if (error.code !== "ENOENT") throw error;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function publicationProjection(publication) {
|
|
634
|
+
return {
|
|
635
|
+
installation_id: publication.installation_id,
|
|
636
|
+
operation_id: publication.operation_id,
|
|
637
|
+
package_hash: publication.package_hash,
|
|
638
|
+
status: publication.status,
|
|
639
|
+
...publication.published_at ? { published_at: publication.published_at } : {},
|
|
640
|
+
...publication.client_deployment_id ? { client_deployment_id: publication.client_deployment_id } : {}
|
|
641
|
+
};
|
|
642
|
+
}
|
|
145
643
|
function clientAppBaseUrl(environment = process.env) {
|
|
146
644
|
const baseUrl = String(environment.AGENTS24_BASE_URL || "").trim().replace(/\/+$/, "");
|
|
147
|
-
if (!baseUrl || baseUrl ===
|
|
645
|
+
if (!baseUrl || baseUrl === DEFAULT_API_BASE_URL2) return void 0;
|
|
148
646
|
let parsed;
|
|
149
647
|
try {
|
|
150
648
|
parsed = new URL(baseUrl);
|
|
@@ -157,9 +655,9 @@ function clientAppBaseUrl(environment = process.env) {
|
|
|
157
655
|
return baseUrl;
|
|
158
656
|
}
|
|
159
657
|
async function appInfoAt(directory, explicit) {
|
|
160
|
-
const target =
|
|
658
|
+
const target = resolve3(directory);
|
|
161
659
|
try {
|
|
162
|
-
const manifest = JSON.parse(await
|
|
660
|
+
const manifest = JSON.parse(await readFile3(join3(target, "package.json"), "utf8"));
|
|
163
661
|
const metadata = manifest.agents24 && typeof manifest.agents24 === "object" ? manifest.agents24 : {};
|
|
164
662
|
const integration = String(metadata.integration || "");
|
|
165
663
|
if (integration !== "client-deployment" && integration !== "bff") {
|
|
@@ -191,21 +689,6 @@ async function resolveInstallMode(parsed, app) {
|
|
|
191
689
|
cancelled(answer);
|
|
192
690
|
return answer;
|
|
193
691
|
}
|
|
194
|
-
async function selectInstalledAgent(parsed, imported) {
|
|
195
|
-
const agents = importedRows(imported).filter((row) => row.kind === "agent");
|
|
196
|
-
if (!agents.length) throw new Error("The installed package does not contain an Agent");
|
|
197
|
-
const requested = flag(parsed, "agent");
|
|
198
|
-
if (requested) {
|
|
199
|
-
const match = agents.find((row) => [row.id, row.resource_key, row.name].map(String).includes(requested));
|
|
200
|
-
if (!match) throw new Error(`--agent did not match an imported Agent: ${requested}`);
|
|
201
|
-
return match;
|
|
202
|
-
}
|
|
203
|
-
if (agents.length === 1) return agents[0];
|
|
204
|
-
if (!interactive(parsed)) throw new Error("--agent is required when the package contains multiple Agents");
|
|
205
|
-
const answer = await select({ message: "Which Agent should be published?", options: agents.map((row) => ({ value: String(row.id), label: String(row.name) })) });
|
|
206
|
-
cancelled(answer);
|
|
207
|
-
return agents.find((row) => String(row.id) === answer);
|
|
208
|
-
}
|
|
209
692
|
async function clientDeployment(parsed, client, agent, app) {
|
|
210
693
|
const policies = (await client.resourcePolicies.list()).filter((row) => row.is_active === true);
|
|
211
694
|
let policyId = flag(parsed, "policy-set");
|
|
@@ -237,31 +720,352 @@ async function clientDeployment(parsed, client, agent, app) {
|
|
|
237
720
|
allowed_origins: origins,
|
|
238
721
|
...oidcRaw ? { oidc: JSON.parse(oidcRaw) } : {}
|
|
239
722
|
};
|
|
240
|
-
return client.clientDeployments.create(request, { idempotencyKey: `deploy-${
|
|
723
|
+
return client.clientDeployments.create(request, { idempotencyKey: `deploy-${createHash2("sha256").update(canonicalJson(request)).digest("hex").slice(0, 40)}` });
|
|
241
724
|
}
|
|
242
725
|
async function updateEnvFile(app, values2) {
|
|
243
|
-
const target =
|
|
726
|
+
const target = join3(app.directory, ".env.local");
|
|
244
727
|
let content = "";
|
|
245
728
|
try {
|
|
246
|
-
content = await
|
|
729
|
+
content = await readFile3(target, "utf8");
|
|
247
730
|
} catch (error) {
|
|
248
731
|
if (error.code !== "ENOENT") throw error;
|
|
249
732
|
}
|
|
250
733
|
let next = content;
|
|
251
734
|
for (const [name, value] of Object.entries(values2)) {
|
|
252
|
-
const line = `${name}=${JSON.stringify(value)}`;
|
|
735
|
+
const line = `${name}=${typeof value === "string" ? JSON.stringify(value) : value.value}`;
|
|
253
736
|
const pattern = new RegExp(`^${name}=.*$`, "m");
|
|
254
737
|
next = pattern.test(next) ? next.replace(pattern, line) : `${next}${next && !next.endsWith("\n") ? "\n" : ""}${line}
|
|
255
738
|
`;
|
|
256
739
|
}
|
|
257
|
-
await
|
|
258
|
-
await
|
|
740
|
+
await writeFile3(target, next, { mode: 384 });
|
|
741
|
+
await chmod2(target, 384);
|
|
259
742
|
return target;
|
|
260
743
|
}
|
|
744
|
+
function primaryAgentAlias(applied) {
|
|
745
|
+
const aliases = Array.isArray(applied.agent.aliases) ? applied.agent.aliases : [];
|
|
746
|
+
const agentAlias = String(aliases[0] || applied.result.primary_agent_alias || "");
|
|
747
|
+
if (!agentAlias) throw new Error("Apply completed without a primary Agent alias");
|
|
748
|
+
return agentAlias;
|
|
749
|
+
}
|
|
750
|
+
function bffApplicationValues(applied) {
|
|
751
|
+
const agentAlias = primaryAgentAlias(applied);
|
|
752
|
+
return {
|
|
753
|
+
AGENTS24_API_KEY: applied.apiKey,
|
|
754
|
+
AGENTS24_AGENTS: { value: JSON.stringify([{ agentAlias }]), literal: true }
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
async function packageIdentity(input, requireValid = true) {
|
|
758
|
+
const result = await validatePackage(input);
|
|
759
|
+
const packageId = String(result.package.package_id || "");
|
|
760
|
+
if (!/^pkg_[0-9a-f]{32}$/.test(packageId)) {
|
|
761
|
+
throw new Error("Resource Package is missing a valid package_id; create an independent package with agents24 fork <source> <target>");
|
|
762
|
+
}
|
|
763
|
+
if (requireValid && !result.valid) throw Object.assign(new Error("Resource package is invalid"), { diagnostics: result.diagnostics });
|
|
764
|
+
return { packageId, packageName: String(result.package.name || "") };
|
|
765
|
+
}
|
|
766
|
+
async function resolveInstallation(input, client, requireValid = true) {
|
|
767
|
+
const { packageId } = await packageIdentity(input, requireValid);
|
|
768
|
+
return client.resourceInstallations.getByPackageId(packageId);
|
|
769
|
+
}
|
|
770
|
+
function blockedPlanMessage(plan) {
|
|
771
|
+
const blockers = Array.isArray(plan.blockers) ? plan.blockers.filter((item) => Boolean(item && typeof item === "object")) : [];
|
|
772
|
+
if (!blockers.length) return "The Resource Package plan is blocked";
|
|
773
|
+
const details = blockers.map((blocker) => {
|
|
774
|
+
const resource = String(blocker.resource_key || blocker.requirement_key || "").trim();
|
|
775
|
+
const guidance = String(blocker.guidance || "").trim();
|
|
776
|
+
const code = String(blocker.code || "PLAN_BLOCKED").trim();
|
|
777
|
+
return `${code}${resource ? ` (${resource})` : ""}${guidance ? `: ${guidance}` : ""}`;
|
|
778
|
+
});
|
|
779
|
+
return `The Resource Package plan is blocked: ${details.join("; ")}`;
|
|
780
|
+
}
|
|
781
|
+
async function ensureInstallation(input, client, data) {
|
|
782
|
+
const identity = await packageIdentity(input);
|
|
783
|
+
const initial = await client.resourceInstallations.plan(packageUpload(data, input));
|
|
784
|
+
if (initial.installation_id) return String(initial.installation_id);
|
|
785
|
+
const created = await client.resourceInstallations.create(
|
|
786
|
+
{ package_id: identity.packageId, package_name: String(initial.package_name), operation_id: String(initial.operation_id) },
|
|
787
|
+
{ idempotencyKey: `installation-${identity.packageId}` }
|
|
788
|
+
);
|
|
789
|
+
return String(created.id);
|
|
790
|
+
}
|
|
791
|
+
async function applyDraft(parsed, options = {}) {
|
|
792
|
+
const input = parsed.positionals[0];
|
|
793
|
+
if (!input) throw new Error(`${parsed.command} requires a Resource Package directory or ZIP`);
|
|
794
|
+
await packageIdentity(input);
|
|
795
|
+
const apiKey = options.apiKey || await apiKeyForLifecycle(parsed);
|
|
796
|
+
const client = options.client || await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
|
|
797
|
+
const data = await packPackage(input);
|
|
798
|
+
let plan = await client.resourceInstallations.plan({
|
|
799
|
+
...packageUpload(data, input),
|
|
800
|
+
prune: flag(parsed, "prune") === "true"
|
|
801
|
+
});
|
|
802
|
+
if (plan.can_apply !== true && interactive(parsed)) {
|
|
803
|
+
const installationId = await ensureInstallation(input, client, data);
|
|
804
|
+
if (await promptInstallationRequirementLinks(client, installationId, plan, true)) {
|
|
805
|
+
plan = await client.resourceInstallations.plan({
|
|
806
|
+
...packageUpload(data, input),
|
|
807
|
+
prune: flag(parsed, "prune") === "true"
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (plan.can_apply !== true) throw Object.assign(new Error(blockedPlanMessage(plan)), { plan });
|
|
812
|
+
if (!options.skipConfirmation) await applyConfirmation(parsed, plan);
|
|
813
|
+
const secrets = await secretValues(input);
|
|
814
|
+
let operation = await client.resourceInstallations.apply(String(plan.operation_id), {
|
|
815
|
+
primary_resource_key: flag(parsed, "agent"),
|
|
816
|
+
integration_mode: options.integrationMode || flag(parsed, "integration") || "publish-only",
|
|
817
|
+
secrets
|
|
818
|
+
}, { idempotencyKey: `apply-${String(plan.package_hash).slice(0, 40)}` });
|
|
819
|
+
operation = await pollOperation(client, operation);
|
|
820
|
+
if (operation.status !== "completed") throw Object.assign(new Error("Draft apply failed"), { operation });
|
|
821
|
+
const installationIdValue = String(operation.installation_id || "");
|
|
822
|
+
if (!installationIdValue) throw new Error("Apply completed without an installation ID");
|
|
823
|
+
const result = operation.result && typeof operation.result === "object" ? operation.result : {};
|
|
824
|
+
const agentId = String(result.primary_agent_id || "");
|
|
825
|
+
const resources = Array.isArray(result.resources) ? result.resources : [];
|
|
826
|
+
const agent = resources.find((item) => String(item.id) === agentId) || { id: agentId, name: "Agent" };
|
|
827
|
+
return { client, apiKey, input, installationId: installationIdValue, plan, operation, result, agent };
|
|
828
|
+
}
|
|
829
|
+
async function publishDraft(parsed, applied) {
|
|
830
|
+
const input = applied?.input || parsed.positionals[0];
|
|
831
|
+
if (!input) throw new Error("publish requires a Resource Package directory or ZIP");
|
|
832
|
+
await packageIdentity(input);
|
|
833
|
+
const apiKey = applied?.apiKey || await apiKeyForLifecycle(parsed);
|
|
834
|
+
const client = applied?.client || await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
|
|
835
|
+
const installed = applied ? void 0 : await resolveInstallation(input, client);
|
|
836
|
+
const id = applied?.installationId || String(installed?.id || "");
|
|
837
|
+
if (!id) throw new Error("No installation exists for this package_id; run agents24 apply first");
|
|
838
|
+
const data = await packPackage(input);
|
|
839
|
+
const plan = applied?.plan || await client.resourceInstallations.plan(packageUpload(data, input));
|
|
840
|
+
const publication = await client.resourceInstallations.publish(id, { package_hash: String(plan.package_hash) }, {
|
|
841
|
+
idempotencyKey: `publish-${String(plan.package_hash).slice(0, 40)}`
|
|
842
|
+
});
|
|
843
|
+
return { publication, installationId: id };
|
|
844
|
+
}
|
|
261
845
|
async function execute(parsed) {
|
|
846
|
+
if (parsed.command === "fork") {
|
|
847
|
+
const [source, target] = parsed.positionals;
|
|
848
|
+
if (!source || !target) throw new Error("Usage: agents24 fork <source> <target> [--name <name>]");
|
|
849
|
+
const result = await forkPackage(source, target, flag(parsed, "name"));
|
|
850
|
+
return { ok: true, directory: resolve3(target), package: result.name, package_id: result.packageId };
|
|
851
|
+
}
|
|
852
|
+
if (parsed.command === "prepare") {
|
|
853
|
+
const input = parsed.positionals[0];
|
|
854
|
+
if (!input) throw new Error("prepare requires a Resource Package directory");
|
|
855
|
+
await packageIdentity(input);
|
|
856
|
+
return preparePackage(input);
|
|
857
|
+
}
|
|
858
|
+
if (parsed.command === "plan") {
|
|
859
|
+
const input = parsed.positionals[0];
|
|
860
|
+
if (!input) throw new Error("plan requires a Resource Package directory or ZIP");
|
|
861
|
+
await packageIdentity(input);
|
|
862
|
+
const apiKey = await apiKeyForLifecycle(parsed);
|
|
863
|
+
const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
|
|
864
|
+
const data = await packPackage(input);
|
|
865
|
+
const plan = await client.resourceInstallations.plan({
|
|
866
|
+
...packageUpload(data, input),
|
|
867
|
+
prune: flag(parsed, "prune") === "true"
|
|
868
|
+
});
|
|
869
|
+
return { ok: plan.can_apply === true, plan };
|
|
870
|
+
}
|
|
871
|
+
if (parsed.command === "pull") {
|
|
872
|
+
const input = parsed.positionals[0];
|
|
873
|
+
if (!input) throw new Error("pull requires a Resource Package directory");
|
|
874
|
+
const apiKey = await apiKeyForLifecycle(parsed);
|
|
875
|
+
const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
|
|
876
|
+
const installation = await resolveInstallation(input, client, false);
|
|
877
|
+
return pullPackage({ parsed, input, client, installationId: String(installation.id || "") });
|
|
878
|
+
}
|
|
879
|
+
if (parsed.command === "status") {
|
|
880
|
+
const input = parsed.positionals[0];
|
|
881
|
+
if (!input) throw new Error("status requires a Resource Package directory or ZIP");
|
|
882
|
+
const identity = await packageIdentity(input);
|
|
883
|
+
const apiKey = await apiKeyForLifecycle(parsed);
|
|
884
|
+
const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
|
|
885
|
+
let status;
|
|
886
|
+
try {
|
|
887
|
+
status = await resolveInstallation(input, client);
|
|
888
|
+
} catch (error) {
|
|
889
|
+
if (!isNotFoundResponse(error)) throw error;
|
|
890
|
+
return {
|
|
891
|
+
ok: true,
|
|
892
|
+
installed: false,
|
|
893
|
+
package_id: identity.packageId,
|
|
894
|
+
message: "This package has not been applied to this organization. Run agents24 apply <package-directory> to create its resources."
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
return installedPackageStatus(input, client, status);
|
|
898
|
+
}
|
|
899
|
+
if (parsed.command === "resources") {
|
|
900
|
+
if (parsed.positionals[0] !== "list") throw new Error("Usage: agents24 resources list --kind <kind>");
|
|
901
|
+
const kind = flag(parsed, "kind");
|
|
902
|
+
if (!kind) throw new Error("resources list requires --kind");
|
|
903
|
+
const apiKey = await apiKeyForLifecycle(parsed);
|
|
904
|
+
const result = await (await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey })).resourceInstallations.resources({
|
|
905
|
+
kind,
|
|
906
|
+
query: flag(parsed, "query"),
|
|
907
|
+
limit: Number(flag(parsed, "limit") || 100)
|
|
908
|
+
});
|
|
909
|
+
return { ok: true, ...result };
|
|
910
|
+
}
|
|
911
|
+
if (parsed.command === "link") {
|
|
912
|
+
const input = parsed.positionals[0];
|
|
913
|
+
if (!input) throw new Error("link requires a Resource Package directory or ZIP");
|
|
914
|
+
const assignments = values(parsed, "resource");
|
|
915
|
+
if (!assignments.length) throw new Error("link requires --resource <stable-key>=<uuid>");
|
|
916
|
+
await packageIdentity(input);
|
|
917
|
+
const apiKey = await apiKeyForLifecycle(parsed);
|
|
918
|
+
const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
|
|
919
|
+
const data = await packPackage(input);
|
|
920
|
+
const id = await ensureInstallation(input, client, data);
|
|
921
|
+
const linked = [];
|
|
922
|
+
for (const assignment of assignments) {
|
|
923
|
+
const separator = assignment.indexOf("=");
|
|
924
|
+
if (separator <= 0 || separator === assignment.length - 1) throw new Error("--resource must be <stable-key>=<uuid>");
|
|
925
|
+
const resourceKey = assignment.slice(0, separator);
|
|
926
|
+
const resourceId = assignment.slice(separator + 1);
|
|
927
|
+
linked.push(await client.resourceInstallations.link(id, { resource_key: resourceKey, resource_id: resourceId }, {
|
|
928
|
+
idempotencyKey: `link-${createHash2("sha256").update(`${id}:${assignment}`).digest("hex").slice(0, 40)}`
|
|
929
|
+
}));
|
|
930
|
+
}
|
|
931
|
+
return { ok: true, installation_id: id, linked };
|
|
932
|
+
}
|
|
933
|
+
if (parsed.command === "apply") {
|
|
934
|
+
const app = await resolveApp(parsed);
|
|
935
|
+
const integration = app || flag(parsed, "integration") ? await resolveInstallMode(parsed, app) : "publish-only";
|
|
936
|
+
const applied = await applyDraft(parsed, { integrationMode: integration });
|
|
937
|
+
const envFile = app && integration === "bff" && flag(parsed, "no-write-env") !== "true" ? await updateEnvFile(app, bffApplicationValues(applied)) : void 0;
|
|
938
|
+
return {
|
|
939
|
+
ok: true,
|
|
940
|
+
phase: "draft_applied",
|
|
941
|
+
installation_id: applied.installationId,
|
|
942
|
+
agent_alias: primaryAgentAlias(applied),
|
|
943
|
+
operation_id: applied.operation.operation_id,
|
|
944
|
+
operation_status: applied.operation.status,
|
|
945
|
+
...envFile ? { env_file: envFile } : {}
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
if (parsed.command === "publish") {
|
|
949
|
+
const published = await publishDraft(parsed);
|
|
950
|
+
return {
|
|
951
|
+
ok: true,
|
|
952
|
+
phase: "published",
|
|
953
|
+
installation_id: published.installationId,
|
|
954
|
+
publication: publicationProjection(published.publication)
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
if (parsed.command === "setup") {
|
|
958
|
+
const app = await resolveApp(parsed);
|
|
959
|
+
const integration = await resolveInstallMode(parsed, app);
|
|
960
|
+
const applied = await applyDraft(parsed, { integrationMode: integration });
|
|
961
|
+
const published = await publishDraft(parsed, applied);
|
|
962
|
+
const installed = await applied.client.resourceInstallations.get(applied.installationId);
|
|
963
|
+
let deployment;
|
|
964
|
+
let deploymentId;
|
|
965
|
+
if (integration === "client-deployment") {
|
|
966
|
+
deployment = installed.client_deployment_id ? await applied.client.clientDeployments.get(String(installed.client_deployment_id)) : await clientDeployment(parsed, applied.client, applied.agent, app);
|
|
967
|
+
if (!installed.client_deployment_id) {
|
|
968
|
+
await applied.client.resourceInstallations.bindDeployment(applied.installationId, { client_deployment_id: String(deployment.id) }, { idempotencyKey: `deployment-bind-${applied.installationId}` });
|
|
969
|
+
}
|
|
970
|
+
deploymentId = String(deployment.client_id || "");
|
|
971
|
+
}
|
|
972
|
+
let envFile;
|
|
973
|
+
if (app && flag(parsed, "no-write-env") !== "true" && integration !== "publish-only") {
|
|
974
|
+
const localClientBaseUrl = integration === "client-deployment" ? clientAppBaseUrl() : void 0;
|
|
975
|
+
envFile = await updateEnvFile(app, integration === "client-deployment" ? { VITE_AGENTS24_DEPLOYMENT_ID: String(deploymentId), ...localClientBaseUrl ? { VITE_AGENTS24_BASE_URL: localClientBaseUrl } : {} } : bffApplicationValues(applied));
|
|
976
|
+
}
|
|
977
|
+
return {
|
|
978
|
+
ok: true,
|
|
979
|
+
phase: deployment ? "deployed" : "published",
|
|
980
|
+
integration,
|
|
981
|
+
installation_id: applied.installationId,
|
|
982
|
+
agent_alias: primaryAgentAlias(applied),
|
|
983
|
+
publication: publicationProjection(published.publication),
|
|
984
|
+
...deployment ? { deployment_id: deploymentId } : {},
|
|
985
|
+
...envFile ? { env_file: envFile } : {}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
if (parsed.command === "dev") {
|
|
989
|
+
const input = parsed.positionals[0];
|
|
990
|
+
if (!input) throw new Error("dev requires a Resource Package directory");
|
|
991
|
+
await packageIdentity(input);
|
|
992
|
+
await preparePackage(input);
|
|
993
|
+
const declared = await developmentArtifacts(input);
|
|
994
|
+
const selectedKey = flag(parsed, "artifact");
|
|
995
|
+
const artifacts = selectedKey ? declared.filter((item) => item.key === selectedKey) : declared;
|
|
996
|
+
if (!artifacts.length) throw new Error(selectedKey ? `Artifact ${selectedKey} has no development configuration` : "No self-hosted Artifact development configuration was found");
|
|
997
|
+
const apiKey = await apiKeyForLifecycle(parsed);
|
|
998
|
+
const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
|
|
999
|
+
const applied = flag(parsed, "apply") === "true" ? await applyDraft(parsed, { apiKey, client }) : void 0;
|
|
1000
|
+
let id = applied?.installationId;
|
|
1001
|
+
if (!id) {
|
|
1002
|
+
try {
|
|
1003
|
+
id = String((await resolveInstallation(input, client)).id || "");
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
if (!isNotFoundResponse(error)) throw error;
|
|
1006
|
+
throw new Error("Apply the package before starting Artifact development");
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (!id) throw new Error("Apply the package before starting Artifact development");
|
|
1010
|
+
const configuredSecrets = await secretValues(input);
|
|
1011
|
+
if (Object.keys(configuredSecrets).length) await client.resourceInstallations.configureSecrets(id, { secrets: configuredSecrets });
|
|
1012
|
+
const directory = await packageDirectory(input);
|
|
1013
|
+
const localEnv = directory ? parseEnv(await readFile3(join3(directory, ".env.local"), "utf8")) : {};
|
|
1014
|
+
const children = [];
|
|
1015
|
+
const relays = [];
|
|
1016
|
+
try {
|
|
1017
|
+
for (const artifact of artifacts) {
|
|
1018
|
+
if (artifact.command) children.push(spawn(artifact.command, artifact.args, { cwd: artifact.cwd, env: { ...process.env, ...localEnv }, stdio: "inherit", shell: false }));
|
|
1019
|
+
await waitForServer(artifact.baseUrl, artifact.protocol.health_path);
|
|
1020
|
+
relays.push(await openDevelopmentRelay(client, id, artifact, localEnv));
|
|
1021
|
+
}
|
|
1022
|
+
const developmentSessions = Object.fromEntries(artifacts.map((item, index) => [item.key, relays[index].sessionId]));
|
|
1023
|
+
process.stdout.write(`${JSON.stringify({ ok: true, phase: "development_connected", installation_id: id, development_sessions: developmentSessions, draft_applied: Boolean(applied) }, null, 2)}
|
|
1024
|
+
`);
|
|
1025
|
+
await new Promise((resolveStop) => {
|
|
1026
|
+
let stopping = false;
|
|
1027
|
+
const reconnecting = /* @__PURE__ */ new Set();
|
|
1028
|
+
const stop = () => {
|
|
1029
|
+
stopping = true;
|
|
1030
|
+
resolveStop();
|
|
1031
|
+
};
|
|
1032
|
+
const watch = (relay, index) => {
|
|
1033
|
+
relay.socket.addEventListener("close", async () => {
|
|
1034
|
+
if (stopping) return;
|
|
1035
|
+
if (relay.state.terminalReason || reconnecting.has(index)) {
|
|
1036
|
+
stop();
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
reconnecting.add(index);
|
|
1040
|
+
for (let attempt = 1; attempt <= 3 && !stopping; attempt += 1) {
|
|
1041
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, attempt * 500));
|
|
1042
|
+
try {
|
|
1043
|
+
const replacement = await openDevelopmentRelay(client, id, artifacts[index], localEnv);
|
|
1044
|
+
relays[index] = replacement;
|
|
1045
|
+
developmentSessions[artifacts[index].key] = replacement.sessionId;
|
|
1046
|
+
reconnecting.delete(index);
|
|
1047
|
+
watch(replacement, index);
|
|
1048
|
+
return;
|
|
1049
|
+
} catch {
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
reconnecting.delete(index);
|
|
1053
|
+
stop();
|
|
1054
|
+
}, { once: true });
|
|
1055
|
+
};
|
|
1056
|
+
process.once("SIGINT", stop);
|
|
1057
|
+
process.once("SIGTERM", stop);
|
|
1058
|
+
relays.forEach(watch);
|
|
1059
|
+
});
|
|
1060
|
+
return { ok: true, phase: "development_stopped", installation_id: id };
|
|
1061
|
+
} finally {
|
|
1062
|
+
for (const relay of relays) relay.socket.close();
|
|
1063
|
+
for (const child of children) child.kill("SIGTERM");
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
262
1066
|
if (parsed.command === "init") {
|
|
263
|
-
const directory =
|
|
264
|
-
const name = flag(parsed, "name") ||
|
|
1067
|
+
const directory = resolve3(parsed.positionals[0] || ".");
|
|
1068
|
+
const name = flag(parsed, "name") || basename2(directory);
|
|
265
1069
|
const packageName = await initializePackage(directory, name);
|
|
266
1070
|
return { ok: true, directory, package: packageName };
|
|
267
1071
|
}
|
|
@@ -272,7 +1076,7 @@ async function execute(parsed) {
|
|
|
272
1076
|
let remote;
|
|
273
1077
|
if (flag(parsed, "remote") === "true" && local.valid) {
|
|
274
1078
|
const data = await packPackage(input);
|
|
275
|
-
remote = await (await createRemoteClient()).resourcePackages.validatePackage(
|
|
1079
|
+
remote = await (await createRemoteClient()).resourcePackages.validatePackage(packageUpload(data, input));
|
|
276
1080
|
}
|
|
277
1081
|
return { ok: local.valid && (!remote || remote.valid === true), local: { ...local, files: void 0 }, ...remote ? { remote } : {} };
|
|
278
1082
|
}
|
|
@@ -280,8 +1084,8 @@ async function execute(parsed) {
|
|
|
280
1084
|
const input = parsed.positionals[0];
|
|
281
1085
|
if (!input) throw new Error("pack requires a package directory");
|
|
282
1086
|
const data = await packPackage(input);
|
|
283
|
-
const output =
|
|
284
|
-
await
|
|
1087
|
+
const output = resolve3(flag(parsed, "output") || `${basename2(resolve3(input))}.agents24.zip`);
|
|
1088
|
+
await writeFile3(output, data);
|
|
285
1089
|
return { ok: true, output, bytes: data.byteLength };
|
|
286
1090
|
}
|
|
287
1091
|
if (parsed.command === "export") {
|
|
@@ -297,141 +1101,42 @@ async function execute(parsed) {
|
|
|
297
1101
|
}
|
|
298
1102
|
const request = { selectors };
|
|
299
1103
|
const archive = await (await createRemoteClient()).resourcePackages.exportPackage(request);
|
|
300
|
-
const output =
|
|
301
|
-
await
|
|
1104
|
+
const output = resolve3(flag(parsed, "output") || archive.filename);
|
|
1105
|
+
await writeFile3(output, archive.data);
|
|
302
1106
|
return { ok: true, output, bytes: archive.data.byteLength };
|
|
303
1107
|
}
|
|
304
|
-
if (parsed.command === "
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
if (output) await writeFile(resolve(output), `${JSON.stringify(result.bundle, null, 2)}
|
|
308
|
-
`);
|
|
309
|
-
return { ok: result.valid === true, ...output ? { output: resolve(output) } : {}, result: safeResult(result) };
|
|
310
|
-
}
|
|
311
|
-
if (parsed.command === "preview" || parsed.command === "import" || parsed.command === "install") {
|
|
312
|
-
const app = parsed.command === "install" ? await resolveApp(parsed) : void 0;
|
|
313
|
-
const integration = parsed.command === "install" ? await resolveInstallMode(parsed, app) : void 0;
|
|
314
|
-
const apiKey = parsed.command === "install" ? await apiKeyForInstall(parsed) : void 0;
|
|
315
|
-
const client = apiKey ? await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey }) : await createRemoteClient();
|
|
316
|
-
const { result } = await compiledPackage(parsed, client);
|
|
317
|
-
if (result.valid !== true || !result.bundle || typeof result.bundle !== "object") throw new Error("Remote compilation did not produce a bundle");
|
|
1108
|
+
if (parsed.command === "preview" || parsed.command === "import") {
|
|
1109
|
+
const client = await createRemoteClient();
|
|
1110
|
+
const { data, filename } = await packedValidatedPackage(parsed);
|
|
318
1111
|
const request = {
|
|
319
|
-
|
|
1112
|
+
data,
|
|
1113
|
+
filename,
|
|
320
1114
|
mappings: mappings(parsed)
|
|
321
1115
|
};
|
|
322
|
-
let preview = await client.
|
|
323
|
-
request.mappings = await
|
|
324
|
-
if (Object.keys(request.mappings).length) preview = await client.
|
|
1116
|
+
let preview = await client.resourcePackages.importPreview(request);
|
|
1117
|
+
request.mappings = await promptImportMappings(client, preview, request.mappings, interactive(parsed));
|
|
1118
|
+
if (Object.keys(request.mappings).length) preview = await client.resourcePackages.importPreview(request);
|
|
325
1119
|
if (parsed.command === "preview") return { ok: preview.can_import === true, preview };
|
|
326
|
-
if (parsed.command === "install") assertInstallPreviewReady(preview);
|
|
327
1120
|
if (preview.can_import !== true) return { ok: false, preview };
|
|
328
1121
|
await confirmation(parsed, preview);
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
throw Object.assign(new Error("Publication is blocked by incomplete imported dependencies"), { phase: "post_import", imported });
|
|
334
|
-
}
|
|
335
|
-
try {
|
|
336
|
-
await client.agents.publish(String(agent.id), {
|
|
337
|
-
idempotencyKey: `publish-${createHash("sha256").update(`agent:${String(agent.id)}`).digest("hex").slice(0, 40)}`
|
|
338
|
-
});
|
|
339
|
-
} catch (error) {
|
|
340
|
-
throw Object.assign(error instanceof Error ? error : new Error("Agent publication failed"), {
|
|
341
|
-
phase: "publish",
|
|
342
|
-
imported
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
let deployment;
|
|
346
|
-
try {
|
|
347
|
-
if (integration === "client-deployment") deployment = await clientDeployment(parsed, client, agent, app);
|
|
348
|
-
} catch (error) {
|
|
349
|
-
throw Object.assign(error instanceof Error ? error : new Error("Client deployment setup failed"), {
|
|
350
|
-
phase: "deployment",
|
|
351
|
-
imported
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
const deploymentId = deployment && typeof deployment.client_id === "string" ? deployment.client_id : void 0;
|
|
355
|
-
let envFile;
|
|
356
|
-
if (app && flag(parsed, "no-write-env") !== "true" && integration !== "publish-only") {
|
|
357
|
-
try {
|
|
358
|
-
const localClientBaseUrl = integration === "client-deployment" ? clientAppBaseUrl() : void 0;
|
|
359
|
-
envFile = await updateEnvFile(app, integration === "client-deployment" ? {
|
|
360
|
-
VITE_AGENTS24_DEPLOYMENT_ID: String(deploymentId),
|
|
361
|
-
...localClientBaseUrl ? { VITE_AGENTS24_BASE_URL: localClientBaseUrl } : {}
|
|
362
|
-
} : { AGENTS24_API_KEY: apiKey, AGENTS24_AGENT_ID: String(agent.id) });
|
|
363
|
-
} catch (error) {
|
|
364
|
-
throw Object.assign(error instanceof Error ? error : new Error("Could not configure the generated app"), {
|
|
365
|
-
phase: "env_write",
|
|
366
|
-
imported
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
const packageManager = app?.packageManager || "pnpm";
|
|
371
|
-
const nextCommand = app && integration !== "publish-only" ? packageManager === "npm" ? "npm run dev" : packageManager === "bun" ? "bun run dev" : `${packageManager} dev` : void 0;
|
|
372
|
-
return {
|
|
373
|
-
ok: true,
|
|
374
|
-
phase: deployment ? "deployed" : "published",
|
|
375
|
-
integration,
|
|
376
|
-
agent_id: String(agent.id),
|
|
377
|
-
...deployment ? { deployment, deployment_id: deploymentId } : {},
|
|
378
|
-
env_written: Boolean(envFile),
|
|
379
|
-
...envFile ? { env_file: envFile } : {},
|
|
380
|
-
...nextCommand ? { next_command: nextCommand } : {},
|
|
381
|
-
preview,
|
|
382
|
-
result: imported
|
|
383
|
-
};
|
|
1122
|
+
const packageHash = String(preview.package_hash || "");
|
|
1123
|
+
if (!packageHash) throw new Error("Package preview did not return a package hash");
|
|
1124
|
+
const imported = await client.resourcePackages.importPackage(request, { idempotencyKey: importKey(packageHash, request.mappings) });
|
|
1125
|
+
return { ok: true, phase: "imported", preview, result: imported };
|
|
384
1126
|
}
|
|
385
1127
|
throw new Error(`Unknown package command: ${parsed.command}`);
|
|
386
1128
|
}
|
|
387
|
-
function diagnostics(error) {
|
|
388
|
-
if (!error || typeof error !== "object") return void 0;
|
|
389
|
-
const record = error;
|
|
390
|
-
if (Array.isArray(record.diagnostics)) return record.diagnostics;
|
|
391
|
-
if (!record.details || typeof record.details !== "object" || Array.isArray(record.details)) return void 0;
|
|
392
|
-
const detail = record.details.detail;
|
|
393
|
-
if (!detail || typeof detail !== "object" || Array.isArray(detail)) return void 0;
|
|
394
|
-
const value = detail.diagnostics;
|
|
395
|
-
return Array.isArray(value) ? value : void 0;
|
|
396
|
-
}
|
|
397
|
-
function errorMessage(error, parsed) {
|
|
398
|
-
if (parsed?.command === "install" && error && typeof error === "object" && "status" in error && error.status === 403) {
|
|
399
|
-
return "This API key cannot install Agents. Create a new Agent integration API key in Settings; existing keys cannot gain additional scopes.";
|
|
400
|
-
}
|
|
401
|
-
return error instanceof Error ? error.message : "Unexpected CLI failure";
|
|
402
|
-
}
|
|
403
|
-
function installSummary(result) {
|
|
404
|
-
const lines = ["Agent installed and published.", `Agent ID: ${String(result.agent_id)}`];
|
|
405
|
-
if (result.deployment_id) lines.push(`Deployment ID: ${String(result.deployment_id)}`);
|
|
406
|
-
if (result.env_written) lines.push(`Configured: ${String(result.env_file)}`);
|
|
407
|
-
else if (result.integration !== "publish-only") lines.push("Environment configuration was not written.");
|
|
408
|
-
if (result.next_command) lines.push(`Next: ${String(result.next_command)}`);
|
|
409
|
-
return `${lines.join("\n")}
|
|
410
|
-
`;
|
|
411
|
-
}
|
|
412
1129
|
async function run(argv = process.argv.slice(2)) {
|
|
413
1130
|
let parsed;
|
|
414
1131
|
try {
|
|
415
|
-
parsed =
|
|
1132
|
+
parsed = parseArguments(argv);
|
|
416
1133
|
const result = await execute(parsed);
|
|
417
|
-
|
|
418
|
-
`);
|
|
419
|
-
else if (result.ok === false) process.stderr.write(`${JSON.stringify(result, null, 2)}
|
|
420
|
-
`);
|
|
421
|
-
else if (parsed.command === "install") process.stdout.write(installSummary(result));
|
|
422
|
-
else process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
423
|
-
`);
|
|
1134
|
+
writeResult(result, parsed);
|
|
424
1135
|
return result.ok === false ? 1 : 0;
|
|
425
1136
|
} catch (error) {
|
|
426
|
-
const payload =
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
...error && typeof error === "object" && "phase" in error ? { phase: error.phase } : {},
|
|
430
|
-
...error && typeof error === "object" && "imported" in error ? { import_result: error.imported } : {},
|
|
431
|
-
...diagnostics(error) ? { diagnostics: diagnostics(error) } : {}
|
|
432
|
-
};
|
|
433
|
-
const body = parsed && flag(parsed, "json") === "true" ? JSON.stringify(payload) : JSON.stringify(payload, null, 2);
|
|
434
|
-
process.stderr.write(`${body}
|
|
1137
|
+
const payload = failurePayload(error);
|
|
1138
|
+
if (parsed) writeResult(payload, parsed);
|
|
1139
|
+
else process.stderr.write(`${JSON.stringify(payload, null, 2)}
|
|
435
1140
|
`);
|
|
436
1141
|
return 1;
|
|
437
1142
|
}
|