@agents24/cli 0.2.0 → 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/dist/cli.js CHANGED
@@ -2,30 +2,33 @@
2
2
  import {
3
3
  assertImportAllowed,
4
4
  createRemoteClient,
5
+ forkPackage,
5
6
  initializePackage,
6
7
  loadPackageFiles,
7
8
  packPackage,
9
+ packPackageSnapshot,
8
10
  parseMappings,
11
+ replacePackageFiles,
9
12
  shouldPromptForDependency,
10
13
  validatePackage
11
- } from "./chunk-NLUCQTMN.js";
14
+ } from "./chunk-YOT3B62P.js";
12
15
 
13
16
  // src/cli.ts
14
- import { basename, join, resolve } from "path";
15
- import { chmod, mkdir, readFile, stat, writeFile } from "fs/promises";
16
- import { createInterface } from "readline/promises";
17
- import { createHash, createHmac, randomBytes } 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";
18
21
  import { spawn } from "child_process";
19
- import { hostname } from "os";
20
- import { parse as parseYaml } from "yaml";
21
22
  import { isCancel, password, select, text } from "@clack/prompts";
22
- var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "remote", "yes", "allow-incomplete", "no-write-env", "prune"]);
23
- var LIFECYCLE_COMMANDS = /* @__PURE__ */ new Set(["prepare", "plan", "apply", "dev", "publish", "setup", "status", "link", "resources"]);
24
- var DEFAULT_API_BASE_URL = "https://api.agents24.dev";
25
- var DEFAULT_LOCAL_CLIENT_ORIGINS = ["http://localhost:5173", "http://127.0.0.1:5173"];
26
- function parse(argv) {
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) {
27
28
  const lifecycle = LIFECYCLE_COMMANDS.has(argv[0]);
28
- if (!lifecycle && (argv[0] !== "package" || !argv[1])) throw new Error("Usage: agents24 <prepare|plan|apply|dev|publish|setup|status|link|resources> | agents24 package <init|validate|pack|export|compile|preview|import>");
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
+ }
29
32
  const flags = /* @__PURE__ */ new Map();
30
33
  const positionals = [];
31
34
  for (let index = lifecycle ? 1 : 2; index < argv.length; index += 1) {
@@ -51,11 +54,76 @@ function flag(parsed, name) {
51
54
  function values(parsed, name) {
52
55
  return parsed.flags.get(name) || [];
53
56
  }
54
- function mappings(parsed) {
55
- return parseMappings(values(parsed, "map"));
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);
91
+ }
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
+ };
101
+ }
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);
56
118
  }
57
- function upload(data, input) {
58
- return { data, filename: basename(input).endsWith(".zip") ? basename(input) : "resource.agents24.zip" };
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
+ };
59
127
  }
60
128
  async function packageDirectory(input) {
61
129
  try {
@@ -64,31 +132,6 @@ async function packageDirectory(input) {
64
132
  return void 0;
65
133
  }
66
134
  }
67
- async function installationId(input) {
68
- const explicit = String(process.env.AGENTS24_INSTALLATION_ID || "").trim();
69
- if (explicit) return explicit;
70
- const directory = await packageDirectory(input);
71
- if (!directory) return void 0;
72
- try {
73
- return (await readFile(join(directory, ".agents24/installation-id"), "utf8")).trim() || void 0;
74
- } catch (error) {
75
- if (error.code === "ENOENT") return void 0;
76
- throw error;
77
- }
78
- }
79
- async function writeInstallationId(input, id) {
80
- if (String(process.env.AGENTS24_INSTALLATION_ID || "").trim()) return void 0;
81
- const directory = await packageDirectory(input);
82
- if (!directory) return void 0;
83
- const stateDirectory = join(directory, ".agents24");
84
- await mkdir(stateDirectory, { recursive: true, mode: 448 });
85
- const target = join(stateDirectory, "installation-id");
86
- await writeFile(target, `${id}
87
- `, { mode: 384 });
88
- await chmod(target, 384);
89
- await ensureIgnored(directory, [".agents24/", ".env.local"]);
90
- return target;
91
- }
92
135
  async function ensureIgnored(directory, entries) {
93
136
  const target = join(directory, ".gitignore");
94
137
  let current = "";
@@ -104,6 +147,12 @@ async function ensureIgnored(directory, entries) {
104
147
  await writeFile(target, `${current}${separator}${missing.join("\n")}
105
148
  `);
106
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";
107
156
  function parseEnv(textValue) {
108
157
  const result = {};
109
158
  for (const line of textValue.split(/\r?\n/)) {
@@ -118,7 +167,7 @@ function parseEnv(textValue) {
118
167
  }
119
168
  return result;
120
169
  }
121
- async function packageSecretRequirements(input) {
170
+ async function requirements(input) {
122
171
  const files = await loadPackageFiles(input);
123
172
  const manifest = parseYaml(files.get("agents24.yaml") || "");
124
173
  const requires = manifest.requires && typeof manifest.requires === "object" ? manifest.requires : {};
@@ -135,10 +184,10 @@ async function preparePackage(input) {
135
184
  if (!directory) throw new Error("prepare requires a Resource Package directory");
136
185
  const local = await validatePackage(input);
137
186
  if (!local.valid) throw Object.assign(new Error("Resource package is invalid"), { diagnostics: local.diagnostics });
138
- const target = join(directory, ".env.local");
187
+ const target = join2(directory, ".env.local");
139
188
  let current = "";
140
189
  try {
141
- current = await readFile(target, "utf8");
190
+ current = await readFile2(target, "utf8");
142
191
  } catch (error) {
143
192
  if (error.code !== "ENOENT") throw error;
144
193
  }
@@ -146,7 +195,7 @@ async function preparePackage(input) {
146
195
  const generated = [];
147
196
  const preserved = [];
148
197
  let next = current;
149
- for (const requirement of await packageSecretRequirements(input)) {
198
+ for (const requirement of await requirements(input)) {
150
199
  if (values2[requirement.env] || process.env[requirement.env]) {
151
200
  preserved.push(requirement.env);
152
201
  continue;
@@ -157,9 +206,9 @@ async function preparePackage(input) {
157
206
  `;
158
207
  generated.push(requirement.env);
159
208
  }
160
- await writeFile(target, next, { mode: 384 });
209
+ await writeFile2(target, next, { mode: 384 });
161
210
  await chmod(target, 384);
162
- await ensureIgnored(directory, [".env.local", ".agents24/"]);
211
+ await ensureIgnored(directory, [".env.local"]);
163
212
  return { ok: true, package: directory, env_file: target, generated, preserved };
164
213
  }
165
214
  async function secretValues(input) {
@@ -167,21 +216,341 @@ async function secretValues(input) {
167
216
  let local = {};
168
217
  if (directory) {
169
218
  try {
170
- local = parseEnv(await readFile(join(directory, ".env.local"), "utf8"));
219
+ local = parseEnv(await readFile2(join2(directory, ".env.local"), "utf8"));
171
220
  } catch (error) {
172
221
  if (error.code !== "ENOENT") throw error;
173
222
  }
174
223
  }
175
224
  const result = {};
176
- for (const requirement of await packageSecretRequirements(input)) {
225
+ for (const requirement of await requirements(input)) {
177
226
  const value = String(process.env[requirement.env] || local[requirement.env] || "");
178
227
  if (value) result[requirement.key] = value;
179
228
  }
180
229
  return result;
181
230
  }
182
- function safeResult(result) {
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 : {};
242
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
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
+ );
246
+ prompt.close();
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
+ };
310
+ }
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;
317
+ const dependencies = Array.isArray(preview.dependencies) ? preview.dependencies.filter((item) => Boolean(item && typeof item === "object" && item.status === "unresolved")) : [];
318
+ const unique = new Map(dependencies.map((item) => [String(item.requirement_key || item.id), item]));
319
+ if (!unique.size) return current;
320
+ const prompt = createInterface2({ input: process.stdin, output: process.stdout });
321
+ try {
322
+ const next = { ...current };
323
+ for (const [key, dependency] of unique) {
324
+ if (!shouldPromptForDependency(dependency)) continue;
325
+ if (dependency.kind === "secret") {
326
+ const answer2 = await prompt.question(`${dependency.source_name || key} secret ($secret:name, blank to omit): `);
327
+ if (answer2.trim()) next[key] = answer2.trim();
328
+ continue;
329
+ }
330
+ const response = await client.resourcePackages.candidates({
331
+ kind: String(dependency.kind),
332
+ query: String(dependency.source_name || ""),
333
+ requiredCapability: dependency.required_capability === "embedding" ? "embedding" : dependency.required_capability === "chat" ? "chat" : void 0
334
+ });
335
+ const candidates = Array.isArray(response.items) ? response.items : [];
336
+ process.stdout.write(`${dependency.source_name || key}: ${candidates.map((item, index) => `${index + 1}) ${item.name}`).join(" ")}
337
+ `);
338
+ const answer = await prompt.question("Choose a candidate number (blank to omit): ");
339
+ const selected = candidates[Number(answer) - 1];
340
+ if (selected?.id) next[key] = String(selected.id);
341
+ }
342
+ return next;
343
+ } finally {
344
+ prompt.close();
345
+ }
346
+ }
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
+ }
183
425
  return result;
184
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
+ }
185
554
  async function confirmation(parsed, preview) {
186
555
  const resources = Array.isArray(preview.resources) ? preview.resources : [];
187
556
  const yes = flag(parsed, "yes") === "true";
@@ -191,7 +560,7 @@ async function confirmation(parsed, preview) {
191
560
  interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
192
561
  });
193
562
  if (yes) return;
194
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
563
+ const prompt = createInterface3({ input: process.stdin, output: process.stdout });
195
564
  const answer = await prompt.question(`Import ${resources.length} resource draft(s)? [y/N] `);
196
565
  prompt.close();
197
566
  if (!/^y(?:es)?$/i.test(answer.trim())) throw new Error("Import cancelled");
@@ -201,7 +570,7 @@ async function applyConfirmation(parsed, plan) {
201
570
  if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("apply requires --yes in noninteractive mode");
202
571
  const actions = Array.isArray(plan.actions) ? plan.actions.length : 0;
203
572
  const removals = Array.isArray(plan.content) ? plan.content.reduce((count, item) => count + (Array.isArray(item.remove) ? item.remove.length : 0), 0) : 0;
204
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
573
+ const prompt = createInterface3({ input: process.stdin, output: process.stdout });
205
574
  const answer = await prompt.question(`Apply ${actions} resource action(s)${removals ? ` and ${removals} content removal(s)` : ""}? [y/N] `);
206
575
  prompt.close();
207
576
  if (!/^y(?:es)?$/i.test(answer.trim())) throw new Error("Apply cancelled");
@@ -216,57 +585,16 @@ async function pollOperation(client, operation) {
216
585
  }
217
586
  throw new Error("Resource apply did not finish within 15 minutes");
218
587
  }
219
- async function promptMappings(parsed, client, preview, current) {
220
- if (!process.stdin.isTTY || !process.stdout.isTTY || flag(parsed, "yes") === "true") return current;
221
- const dependencies = Array.isArray(preview.dependencies) ? preview.dependencies.filter((item) => Boolean(item && typeof item === "object" && item.status === "unresolved")) : [];
222
- const unique = new Map(dependencies.map((item) => [String(item.requirement_key || item.id), item]));
223
- if (!unique.size) return current;
224
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
225
- try {
226
- const next = { ...current };
227
- for (const [key, dependency] of unique) {
228
- if (!shouldPromptForDependency(dependency)) {
229
- continue;
230
- }
231
- if (dependency.kind === "secret") {
232
- const answer2 = await prompt.question(`${dependency.source_name || key} secret ($secret:name, blank to omit): `);
233
- if (answer2.trim()) next[key] = answer2.trim();
234
- continue;
235
- }
236
- const response = await client.resourceBundles.candidates({
237
- kind: String(dependency.kind),
238
- query: String(dependency.source_name || ""),
239
- requiredCapability: dependency.required_capability === "embedding" ? "embedding" : dependency.required_capability === "chat" ? "chat" : void 0
240
- });
241
- const candidates = Array.isArray(response.items) ? response.items : [];
242
- process.stdout.write(`${dependency.source_name || key}: ${candidates.map((item, index) => `${index + 1}) ${item.name}`).join(" ")}
243
- `);
244
- const answer = await prompt.question("Choose a candidate number (blank to omit): ");
245
- const selected = candidates[Number(answer) - 1];
246
- if (selected?.id) next[key] = String(selected.id);
247
- }
248
- return next;
249
- } finally {
250
- prompt.close();
251
- }
252
- }
253
- async function compiledPackage(parsed, client) {
588
+ async function packedValidatedPackage(parsed) {
254
589
  const input = parsed.positionals[0];
255
590
  if (!input) throw new Error(`${parsed.command} requires a package directory or ZIP`);
256
591
  const local = await validatePackage(input);
257
592
  if (!local.valid) throw Object.assign(new Error("Resource package is invalid"), { diagnostics: local.diagnostics });
258
593
  const data = await packPackage(input);
259
- const remoteClient = client || await createRemoteClient();
260
- const result = await remoteClient.resourcePackages.compilePackage(upload(data, input));
261
- return { data, result };
262
- }
263
- function canonical(value) {
264
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
265
- if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
266
- return JSON.stringify(value);
594
+ return { data, filename: `${basename2(resolve3(input))}.agents24.zip` };
267
595
  }
268
- function importKey(bundle, map) {
269
- return `import-${createHash("sha256").update(canonical({ bundle, mappings: map })).digest("hex").slice(0, 40)}`;
596
+ function importKey(packageHash, map) {
597
+ return `import-${createHash2("sha256").update(canonicalJson({ package_hash: packageHash, mappings: map })).digest("hex").slice(0, 40)}`;
270
598
  }
271
599
  function interactive(parsed) {
272
600
  return flag(parsed, "yes") !== "true" && Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -275,6 +603,7 @@ function cancelled(value) {
275
603
  if (isCancel(value)) throw new Error("Installation cancelled");
276
604
  }
277
605
  async function apiKeyForLifecycle(parsed) {
606
+ await loadPackageLifecycleEnvironment(parsed.positionals[0]);
278
607
  let apiKey = String(process.env.AGENTS24_API_KEY || "").trim();
279
608
  if (!apiKey && interactive(parsed)) {
280
609
  const answer = await password({ message: "Agents24 API key", validate: (value) => String(value || "").trim() ? void 0 : "The API key is required." });
@@ -283,11 +612,37 @@ async function apiKeyForLifecycle(parsed) {
283
612
  }
284
613
  if (!apiKey) throw new Error("AGENTS24_API_KEY is required for noninteractive resource management");
285
614
  if (/[\r\n\0]/.test(apiKey)) throw new Error("AGENTS24_API_KEY contains invalid control characters");
615
+ registerSensitiveValue(apiKey);
286
616
  return apiKey;
287
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
+ }
288
643
  function clientAppBaseUrl(environment = process.env) {
289
644
  const baseUrl = String(environment.AGENTS24_BASE_URL || "").trim().replace(/\/+$/, "");
290
- if (!baseUrl || baseUrl === DEFAULT_API_BASE_URL) return void 0;
645
+ if (!baseUrl || baseUrl === DEFAULT_API_BASE_URL2) return void 0;
291
646
  let parsed;
292
647
  try {
293
648
  parsed = new URL(baseUrl);
@@ -300,9 +655,9 @@ function clientAppBaseUrl(environment = process.env) {
300
655
  return baseUrl;
301
656
  }
302
657
  async function appInfoAt(directory, explicit) {
303
- const target = resolve(directory);
658
+ const target = resolve3(directory);
304
659
  try {
305
- const manifest = JSON.parse(await readFile(join(target, "package.json"), "utf8"));
660
+ const manifest = JSON.parse(await readFile3(join3(target, "package.json"), "utf8"));
306
661
  const metadata = manifest.agents24 && typeof manifest.agents24 === "object" ? manifest.agents24 : {};
307
662
  const integration = String(metadata.integration || "");
308
663
  if (integration !== "client-deployment" && integration !== "bff") {
@@ -365,66 +720,106 @@ async function clientDeployment(parsed, client, agent, app) {
365
720
  allowed_origins: origins,
366
721
  ...oidcRaw ? { oidc: JSON.parse(oidcRaw) } : {}
367
722
  };
368
- return client.clientDeployments.create(request, { idempotencyKey: `deploy-${createHash("sha256").update(canonical(request)).digest("hex").slice(0, 40)}` });
723
+ return client.clientDeployments.create(request, { idempotencyKey: `deploy-${createHash2("sha256").update(canonicalJson(request)).digest("hex").slice(0, 40)}` });
369
724
  }
370
725
  async function updateEnvFile(app, values2) {
371
- const target = join(app.directory, ".env.local");
726
+ const target = join3(app.directory, ".env.local");
372
727
  let content = "";
373
728
  try {
374
- content = await readFile(target, "utf8");
729
+ content = await readFile3(target, "utf8");
375
730
  } catch (error) {
376
731
  if (error.code !== "ENOENT") throw error;
377
732
  }
378
733
  let next = content;
379
734
  for (const [name, value] of Object.entries(values2)) {
380
- const line = `${name}=${JSON.stringify(value)}`;
735
+ const line = `${name}=${typeof value === "string" ? JSON.stringify(value) : value.value}`;
381
736
  const pattern = new RegExp(`^${name}=.*$`, "m");
382
737
  next = pattern.test(next) ? next.replace(pattern, line) : `${next}${next && !next.endsWith("\n") ? "\n" : ""}${line}
383
738
  `;
384
739
  }
385
- await writeFile(target, next, { mode: 384 });
386
- await chmod(target, 384);
740
+ await writeFile3(target, next, { mode: 384 });
741
+ await chmod2(target, 384);
387
742
  return target;
388
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
+ }
389
781
  async function ensureInstallation(input, client, data) {
390
- const current = await installationId(input);
391
- if (current) return current;
392
- const initial = await client.resourceInstallations.plan(upload(data, input));
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);
393
785
  const created = await client.resourceInstallations.create(
394
- { package_name: String(initial.package_name), operation_id: String(initial.operation_id) },
395
- { idempotencyKey: `installation-${String(initial.package_hash).slice(0, 40)}` }
786
+ { package_id: identity.packageId, package_name: String(initial.package_name), operation_id: String(initial.operation_id) },
787
+ { idempotencyKey: `installation-${identity.packageId}` }
396
788
  );
397
- const id = String(created.id);
398
- await writeInstallationId(input, id);
399
- return id;
789
+ return String(created.id);
400
790
  }
401
791
  async function applyDraft(parsed, options = {}) {
402
792
  const input = parsed.positionals[0];
403
793
  if (!input) throw new Error(`${parsed.command} requires a Resource Package directory or ZIP`);
404
- const apiKey = await apiKeyForLifecycle(parsed);
405
- const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
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 });
406
797
  const data = await packPackage(input);
407
- const currentId = await installationId(input);
408
- const plan = await client.resourceInstallations.plan({
409
- ...upload(data, input),
410
- ...currentId ? { installationId: currentId } : {},
411
- prune: flag(parsed, "prune") === "true",
412
- development: options.development === true
798
+ let plan = await client.resourceInstallations.plan({
799
+ ...packageUpload(data, input),
800
+ prune: flag(parsed, "prune") === "true"
413
801
  });
414
- if (plan.can_apply !== true) throw Object.assign(new Error("The Resource Package plan is blocked"), { plan });
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 });
415
812
  if (!options.skipConfirmation) await applyConfirmation(parsed, plan);
416
813
  const secrets = await secretValues(input);
417
814
  let operation = await client.resourceInstallations.apply(String(plan.operation_id), {
418
815
  primary_resource_key: flag(parsed, "agent"),
419
816
  integration_mode: options.integrationMode || flag(parsed, "integration") || "publish-only",
420
- secrets,
421
- ...options.developmentSessions ? { development_sessions: options.developmentSessions } : {}
817
+ secrets
422
818
  }, { idempotencyKey: `apply-${String(plan.package_hash).slice(0, 40)}` });
423
819
  operation = await pollOperation(client, operation);
424
820
  if (operation.status !== "completed") throw Object.assign(new Error("Draft apply failed"), { operation });
425
821
  const installationIdValue = String(operation.installation_id || "");
426
822
  if (!installationIdValue) throw new Error("Apply completed without an installation ID");
427
- await writeInstallationId(input, installationIdValue);
428
823
  const result = operation.result && typeof operation.result === "object" ? operation.result : {};
429
824
  const agentId = String(result.primary_agent_id || "");
430
825
  const resources = Array.isArray(result.resources) ? result.resources : [];
@@ -434,164 +829,72 @@ async function applyDraft(parsed, options = {}) {
434
829
  async function publishDraft(parsed, applied) {
435
830
  const input = applied?.input || parsed.positionals[0];
436
831
  if (!input) throw new Error("publish requires a Resource Package directory or ZIP");
832
+ await packageIdentity(input);
437
833
  const apiKey = applied?.apiKey || await apiKeyForLifecycle(parsed);
438
834
  const client = applied?.client || await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
439
- const id = applied?.installationId || await installationId(input);
440
- if (!id) throw new Error("No installation is linked; run agents24 apply first");
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");
441
838
  const data = await packPackage(input);
442
- const plan = applied?.plan || await client.resourceInstallations.plan({ ...upload(data, input), installationId: id });
839
+ const plan = applied?.plan || await client.resourceInstallations.plan(packageUpload(data, input));
443
840
  const publication = await client.resourceInstallations.publish(id, { package_hash: String(plan.package_hash) }, {
444
841
  idempotencyKey: `publish-${String(plan.package_hash).slice(0, 40)}`
445
842
  });
446
- return { publication, client, apiKey, input, installation_id: id, plan };
447
- }
448
- async function developmentArtifacts(input) {
449
- const directory = await packageDirectory(input);
450
- if (!directory) throw new Error("dev requires a Resource Package directory");
451
- const files = await loadPackageFiles(input);
452
- const manifest = parseYaml(files.get("agents24.yaml") || "");
453
- const resources = Array.isArray(manifest.resources) ? manifest.resources : [];
454
- const requires = manifest.requires && typeof manifest.requires === "object" ? manifest.requires : {};
455
- const requiredSecrets = requires.secrets && typeof requires.secrets === "object" ? requires.secrets : {};
456
- const result = [];
457
- for (const resource of resources) {
458
- const path = String(resource.path || "");
459
- const sourceText = files.get(path);
460
- if (!sourceText) continue;
461
- const source = parseYaml(sourceText);
462
- if (source.execution_target !== "self_hosted") continue;
463
- const development = source.development && typeof source.development === "object" ? source.development : void 0;
464
- if (!development) continue;
465
- const server = source.server && typeof source.server === "object" ? source.server : {};
466
- const auth = server.auth && typeof server.auth === "object" ? server.auth : {};
467
- const secretRequirement = String(auth.secret || "");
468
- const secretKey = secretRequirement.startsWith("$secrets.") ? secretRequirement.slice("$secrets.".length) : "";
469
- const secretDeclaration = secretKey && requiredSecrets[secretKey] && typeof requiredSecrets[secretKey] === "object" ? requiredSecrets[secretKey] : void 0;
470
- const cwd = resolve(directory, String(development.cwd || "."));
471
- if (cwd !== directory && !cwd.startsWith(`${directory}/`)) throw new Error(`Artifact ${resource.key} development.cwd must remain inside the package`);
472
- result.push({
473
- key: String(resource.key || ""),
474
- baseUrl: String(development.base_url || ""),
475
- command: development.command ? String(development.command) : void 0,
476
- args: Array.isArray(development.args) ? development.args.map(String) : [],
477
- cwd,
478
- protocol: {
479
- manifest_path: server.manifest_path,
480
- health_path: server.health_path,
481
- verify_path: server.verify_path,
482
- invoke_path: server.invoke_path,
483
- auth_mode: auth.mode,
484
- signing_secret_requirement: auth.secret
485
- },
486
- signingSecretEnv: secretDeclaration?.env ? String(secretDeclaration.env) : void 0
487
- });
488
- }
489
- return result;
490
- }
491
- async function waitForServer(baseUrl, healthPath) {
492
- const target = new URL(String(healthPath || "/.well-known/agents24/artifact/health"), baseUrl);
493
- for (let attempt = 0; attempt < 60; attempt += 1) {
494
- try {
495
- const response = await fetch(target);
496
- if (response.ok) return;
497
- } catch {
498
- }
499
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 250));
500
- }
501
- throw new Error(`Local Artifact server did not become healthy at ${target}`);
502
- }
503
- async function openDevelopmentRelay(client, installationIdValue, artifact, localEnv) {
504
- const admission = await client.resourceInstallations.prepareDevelopmentSession(installationIdValue, {
505
- resource_key: artifact.key,
506
- machine_label: hostname()
507
- });
508
- const apiUrl = new URL(String(process.env.AGENTS24_BASE_URL || DEFAULT_API_BASE_URL));
509
- apiUrl.protocol = apiUrl.protocol === "https:" ? "wss:" : "ws:";
510
- apiUrl.pathname = String(admission.relay_path);
511
- const socket = new WebSocket(apiUrl, [
512
- "agents24-artifact-relay",
513
- `agents24-credential-${String(admission.relay_token)}`
514
- ]);
515
- const state = {};
516
- await new Promise((resolveReady, rejectReady) => {
517
- socket.addEventListener("error", () => rejectReady(new Error(`Artifact relay failed for ${artifact.key}`)), { once: true });
518
- socket.addEventListener("message", async (event) => {
519
- const message = JSON.parse(String(event.data));
520
- if (message.type === "ready") {
521
- resolveReady();
522
- return;
523
- }
524
- if (message.type === "superseded" || message.type === "revoked") {
525
- state.terminalReason = String(message.type);
526
- socket.close();
527
- return;
528
- }
529
- if (message.type !== "request") return;
530
- const method = message.operation === "health" || message.operation === "manifest" ? "GET" : "POST";
531
- try {
532
- const requestBody = method === "POST" ? JSON.stringify(message.body || {}) : void 0;
533
- const headers = method === "POST" ? { "content-type": "application/json" } : {};
534
- if (method === "POST" && String(artifact.protocol.auth_mode || "hmac_sha256") === "hmac_sha256") {
535
- const signingSecret = String(
536
- artifact.signingSecretEnv && (localEnv[artifact.signingSecretEnv] || process.env[artifact.signingSecretEnv]) || ""
537
- );
538
- if (!signingSecret) throw new Error(`Artifact ${artifact.key} signing secret is unavailable`);
539
- const timestamp = String(Date.now());
540
- const signature = createHmac("sha256", signingSecret).update(`${timestamp}.${requestBody}`).digest("hex");
541
- headers["x-agents24-timestamp"] = timestamp;
542
- headers["x-agents24-signature"] = `sha256=${signature}`;
543
- }
544
- const response = await fetch(new URL(String(message.path || "/"), artifact.baseUrl), {
545
- method,
546
- headers,
547
- body: requestBody
548
- });
549
- const textBody = await response.text();
550
- let body = textBody;
551
- try {
552
- body = textBody ? JSON.parse(textBody) : null;
553
- } catch {
554
- }
555
- socket.send(JSON.stringify({ type: "response", request_id: message.request_id, status: response.status, body }));
556
- } catch {
557
- socket.send(JSON.stringify({ type: "response", request_id: message.request_id, status: 502, body: { status: "failed" } }));
558
- }
559
- });
560
- const heartbeat = setInterval(() => {
561
- if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "heartbeat" }));
562
- }, 15e3);
563
- socket.addEventListener("close", () => clearInterval(heartbeat), { once: true });
564
- });
565
- return { sessionId: String(admission.id), socket, state };
843
+ return { publication, installationId: id };
566
844
  }
567
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
+ }
568
852
  if (parsed.command === "prepare") {
569
853
  const input = parsed.positionals[0];
570
854
  if (!input) throw new Error("prepare requires a Resource Package directory");
855
+ await packageIdentity(input);
571
856
  return preparePackage(input);
572
857
  }
573
858
  if (parsed.command === "plan") {
574
859
  const input = parsed.positionals[0];
575
860
  if (!input) throw new Error("plan requires a Resource Package directory or ZIP");
861
+ await packageIdentity(input);
576
862
  const apiKey = await apiKeyForLifecycle(parsed);
577
863
  const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
578
864
  const data = await packPackage(input);
579
- const currentId = await installationId(input);
580
865
  const plan = await client.resourceInstallations.plan({
581
- ...upload(data, input),
582
- ...currentId ? { installationId: currentId } : {},
866
+ ...packageUpload(data, input),
583
867
  prune: flag(parsed, "prune") === "true"
584
868
  });
585
869
  return { ok: plan.can_apply === true, plan };
586
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
+ }
587
879
  if (parsed.command === "status") {
588
880
  const input = parsed.positionals[0];
589
881
  if (!input) throw new Error("status requires a Resource Package directory or ZIP");
590
- const id = await installationId(input);
591
- if (!id) throw new Error("No installation is linked; run agents24 apply/link first or set AGENTS24_INSTALLATION_ID");
882
+ const identity = await packageIdentity(input);
592
883
  const apiKey = await apiKeyForLifecycle(parsed);
593
- const status = await (await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey })).resourceInstallations.get(id);
594
- return { ok: true, installation: status };
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);
595
898
  }
596
899
  if (parsed.command === "resources") {
597
900
  if (parsed.positionals[0] !== "list") throw new Error("Usage: agents24 resources list --kind <kind>");
@@ -610,20 +913,11 @@ async function execute(parsed) {
610
913
  if (!input) throw new Error("link requires a Resource Package directory or ZIP");
611
914
  const assignments = values(parsed, "resource");
612
915
  if (!assignments.length) throw new Error("link requires --resource <stable-key>=<uuid>");
916
+ await packageIdentity(input);
613
917
  const apiKey = await apiKeyForLifecycle(parsed);
614
918
  const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
615
- let id = await installationId(input);
616
- if (!id) {
617
- const data = await packPackage(input);
618
- const initialPlan = await client.resourceInstallations.plan(upload(data, input));
619
- const created = await client.resourceInstallations.create(
620
- { package_name: String(initialPlan.package_name), operation_id: String(initialPlan.operation_id) },
621
- { idempotencyKey: `installation-${String(initialPlan.package_hash).slice(0, 40)}` }
622
- );
623
- id = String(created.id);
624
- await writeInstallationId(input, id);
625
- await client.resourceInstallations.plan({ ...upload(data, input), installationId: id });
626
- }
919
+ const data = await packPackage(input);
920
+ const id = await ensureInstallation(input, client, data);
627
921
  const linked = [];
628
922
  for (const assignment of assignments) {
629
923
  const separator = assignment.indexOf("=");
@@ -631,18 +925,34 @@ async function execute(parsed) {
631
925
  const resourceKey = assignment.slice(0, separator);
632
926
  const resourceId = assignment.slice(separator + 1);
633
927
  linked.push(await client.resourceInstallations.link(id, { resource_key: resourceKey, resource_id: resourceId }, {
634
- idempotencyKey: `link-${createHash("sha256").update(`${id}:${assignment}`).digest("hex").slice(0, 40)}`
928
+ idempotencyKey: `link-${createHash2("sha256").update(`${id}:${assignment}`).digest("hex").slice(0, 40)}`
635
929
  }));
636
930
  }
637
931
  return { ok: true, installation_id: id, linked };
638
932
  }
639
933
  if (parsed.command === "apply") {
640
- const applied = await applyDraft(parsed);
641
- return { ok: true, phase: "draft_applied", installation_id: applied.installationId, agent_id: applied.agent.id, plan: applied.plan, operation: applied.operation };
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
+ };
642
947
  }
643
948
  if (parsed.command === "publish") {
644
949
  const published = await publishDraft(parsed);
645
- return { ok: true, phase: "published", ...published };
950
+ return {
951
+ ok: true,
952
+ phase: "published",
953
+ installation_id: published.installationId,
954
+ publication: publicationProjection(published.publication)
955
+ };
646
956
  }
647
957
  if (parsed.command === "setup") {
648
958
  const app = await resolveApp(parsed);
@@ -662,26 +972,45 @@ async function execute(parsed) {
662
972
  let envFile;
663
973
  if (app && flag(parsed, "no-write-env") !== "true" && integration !== "publish-only") {
664
974
  const localClientBaseUrl = integration === "client-deployment" ? clientAppBaseUrl() : void 0;
665
- envFile = await updateEnvFile(app, integration === "client-deployment" ? { VITE_AGENTS24_DEPLOYMENT_ID: String(deploymentId), ...localClientBaseUrl ? { VITE_AGENTS24_BASE_URL: localClientBaseUrl } : {} } : { AGENTS24_API_KEY: applied.apiKey, AGENTS24_AGENT_ID: String(applied.agent.id) });
975
+ envFile = await updateEnvFile(app, integration === "client-deployment" ? { VITE_AGENTS24_DEPLOYMENT_ID: String(deploymentId), ...localClientBaseUrl ? { VITE_AGENTS24_BASE_URL: localClientBaseUrl } : {} } : bffApplicationValues(applied));
666
976
  }
667
- return { ok: true, phase: deployment ? "deployed" : "published", integration, installation_id: applied.installationId, agent_id: applied.agent.id, publication: published.publication, ...deployment ? { deployment, deployment_id: deploymentId } : {}, ...envFile ? { env_file: envFile } : {} };
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
+ };
668
987
  }
669
988
  if (parsed.command === "dev") {
670
989
  const input = parsed.positionals[0];
671
990
  if (!input) throw new Error("dev requires a Resource Package directory");
991
+ await packageIdentity(input);
672
992
  await preparePackage(input);
673
- const apiKey = await apiKeyForLifecycle(parsed);
674
- const client = await createRemoteClient({ ...process.env, AGENTS24_API_KEY: apiKey });
675
- const data = await packPackage(input);
676
- const id = await ensureInstallation(input, client, data);
677
- const configuredSecrets = await secretValues(input);
678
- if (Object.keys(configuredSecrets).length) await client.resourceInstallations.configureSecrets(id, { secrets: configuredSecrets });
679
993
  const declared = await developmentArtifacts(input);
680
994
  const selectedKey = flag(parsed, "artifact");
681
995
  const artifacts = selectedKey ? declared.filter((item) => item.key === selectedKey) : declared;
682
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 });
683
1012
  const directory = await packageDirectory(input);
684
- const localEnv = directory ? parseEnv(await readFile(join(directory, ".env.local"), "utf8")) : {};
1013
+ const localEnv = directory ? parseEnv(await readFile3(join3(directory, ".env.local"), "utf8")) : {};
685
1014
  const children = [];
686
1015
  const relays = [];
687
1016
  try {
@@ -691,14 +1020,7 @@ async function execute(parsed) {
691
1020
  relays.push(await openDevelopmentRelay(client, id, artifact, localEnv));
692
1021
  }
693
1022
  const developmentSessions = Object.fromEntries(artifacts.map((item, index) => [item.key, relays[index].sessionId]));
694
- for (const artifact of declared) {
695
- if (developmentSessions[artifact.key]) continue;
696
- const status = await client.resourceInstallations.developmentSessionStatus(id, artifact.key);
697
- if (status.status !== "connected" || !status.id) throw new Error(`Artifact ${artifact.key} requires an active development connection`);
698
- developmentSessions[artifact.key] = String(status.id);
699
- }
700
- const applied = await applyDraft(parsed, { development: true, developmentSessions, skipConfirmation: true });
701
- process.stdout.write(`${JSON.stringify({ ok: true, phase: "development_connected", installation_id: applied.installationId, development_sessions: developmentSessions }, null, 2)}
1023
+ process.stdout.write(`${JSON.stringify({ ok: true, phase: "development_connected", installation_id: id, development_sessions: developmentSessions, draft_applied: Boolean(applied) }, null, 2)}
702
1024
  `);
703
1025
  await new Promise((resolveStop) => {
704
1026
  let stopping = false;
@@ -721,7 +1043,6 @@ async function execute(parsed) {
721
1043
  const replacement = await openDevelopmentRelay(client, id, artifacts[index], localEnv);
722
1044
  relays[index] = replacement;
723
1045
  developmentSessions[artifacts[index].key] = replacement.sessionId;
724
- await applyDraft(parsed, { development: true, developmentSessions, skipConfirmation: true });
725
1046
  reconnecting.delete(index);
726
1047
  watch(replacement, index);
727
1048
  return;
@@ -743,8 +1064,8 @@ async function execute(parsed) {
743
1064
  }
744
1065
  }
745
1066
  if (parsed.command === "init") {
746
- const directory = resolve(parsed.positionals[0] || ".");
747
- const name = flag(parsed, "name") || basename(directory);
1067
+ const directory = resolve3(parsed.positionals[0] || ".");
1068
+ const name = flag(parsed, "name") || basename2(directory);
748
1069
  const packageName = await initializePackage(directory, name);
749
1070
  return { ok: true, directory, package: packageName };
750
1071
  }
@@ -755,7 +1076,7 @@ async function execute(parsed) {
755
1076
  let remote;
756
1077
  if (flag(parsed, "remote") === "true" && local.valid) {
757
1078
  const data = await packPackage(input);
758
- remote = await (await createRemoteClient()).resourcePackages.validatePackage(upload(data, input));
1079
+ remote = await (await createRemoteClient()).resourcePackages.validatePackage(packageUpload(data, input));
759
1080
  }
760
1081
  return { ok: local.valid && (!remote || remote.valid === true), local: { ...local, files: void 0 }, ...remote ? { remote } : {} };
761
1082
  }
@@ -763,8 +1084,8 @@ async function execute(parsed) {
763
1084
  const input = parsed.positionals[0];
764
1085
  if (!input) throw new Error("pack requires a package directory");
765
1086
  const data = await packPackage(input);
766
- const output = resolve(flag(parsed, "output") || `${basename(resolve(input))}.agents24.zip`);
767
- await writeFile(output, data);
1087
+ const output = resolve3(flag(parsed, "output") || `${basename2(resolve3(input))}.agents24.zip`);
1088
+ await writeFile3(output, data);
768
1089
  return { ok: true, output, bytes: data.byteLength };
769
1090
  }
770
1091
  if (parsed.command === "export") {
@@ -780,72 +1101,42 @@ async function execute(parsed) {
780
1101
  }
781
1102
  const request = { selectors };
782
1103
  const archive = await (await createRemoteClient()).resourcePackages.exportPackage(request);
783
- const output = resolve(flag(parsed, "output") || archive.filename);
784
- await writeFile(output, archive.data);
1104
+ const output = resolve3(flag(parsed, "output") || archive.filename);
1105
+ await writeFile3(output, archive.data);
785
1106
  return { ok: true, output, bytes: archive.data.byteLength };
786
1107
  }
787
- if (parsed.command === "compile") {
788
- const { result } = await compiledPackage(parsed);
789
- const output = flag(parsed, "output");
790
- if (output) await writeFile(resolve(output), `${JSON.stringify(result.bundle, null, 2)}
791
- `);
792
- return { ok: result.valid === true, ...output ? { output: resolve(output) } : {}, result: safeResult(result) };
793
- }
794
1108
  if (parsed.command === "preview" || parsed.command === "import") {
795
1109
  const client = await createRemoteClient();
796
- const { result } = await compiledPackage(parsed, client);
797
- if (result.valid !== true || !result.bundle || typeof result.bundle !== "object") throw new Error("Remote compilation did not produce a bundle");
1110
+ const { data, filename } = await packedValidatedPackage(parsed);
798
1111
  const request = {
799
- bundle: result.bundle,
1112
+ data,
1113
+ filename,
800
1114
  mappings: mappings(parsed)
801
1115
  };
802
- let preview = await client.resourceBundles.importPreview(request);
803
- request.mappings = await promptMappings(parsed, client, preview, request.mappings);
804
- if (Object.keys(request.mappings).length) preview = await client.resourceBundles.importPreview(request);
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);
805
1119
  if (parsed.command === "preview") return { ok: preview.can_import === true, preview };
806
1120
  if (preview.can_import !== true) return { ok: false, preview };
807
1121
  await confirmation(parsed, preview);
808
- const imported = await client.resourceBundles.importBundle(request, { idempotencyKey: importKey(request.bundle, request.mappings) });
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) });
809
1125
  return { ok: true, phase: "imported", preview, result: imported };
810
1126
  }
811
1127
  throw new Error(`Unknown package command: ${parsed.command}`);
812
1128
  }
813
- function diagnostics(error) {
814
- if (!error || typeof error !== "object") return void 0;
815
- const record = error;
816
- if (Array.isArray(record.diagnostics)) return record.diagnostics;
817
- if (!record.details || typeof record.details !== "object" || Array.isArray(record.details)) return void 0;
818
- const detail = record.details.detail;
819
- if (!detail || typeof detail !== "object" || Array.isArray(detail)) return void 0;
820
- const value = detail.diagnostics;
821
- return Array.isArray(value) ? value : void 0;
822
- }
823
- function errorMessage(error, parsed) {
824
- void parsed;
825
- return error instanceof Error ? error.message : "Unexpected CLI failure";
826
- }
827
1129
  async function run(argv = process.argv.slice(2)) {
828
1130
  let parsed;
829
1131
  try {
830
- parsed = parse(argv);
1132
+ parsed = parseArguments(argv);
831
1133
  const result = await execute(parsed);
832
- if (flag(parsed, "json") === "true") process.stdout.write(`${JSON.stringify(result)}
833
- `);
834
- else if (result.ok === false) process.stderr.write(`${JSON.stringify(result, null, 2)}
835
- `);
836
- else process.stdout.write(`${JSON.stringify(result, null, 2)}
837
- `);
1134
+ writeResult(result, parsed);
838
1135
  return result.ok === false ? 1 : 0;
839
1136
  } catch (error) {
840
- const payload = {
841
- ok: false,
842
- error: errorMessage(error, parsed),
843
- ...error && typeof error === "object" && "phase" in error ? { phase: error.phase } : {},
844
- ...error && typeof error === "object" && "imported" in error ? { import_result: error.imported } : {},
845
- ...diagnostics(error) ? { diagnostics: diagnostics(error) } : {}
846
- };
847
- const body = parsed && flag(parsed, "json") === "true" ? JSON.stringify(payload) : JSON.stringify(payload, null, 2);
848
- 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)}
849
1140
  `);
850
1141
  return 1;
851
1142
  }