@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/README.md +12 -6
- package/compatibility.json +6 -6
- package/dist/{chunk-NLUCQTMN.js → chunk-YOT3B62P.js} +130 -8
- package/dist/chunk-YOT3B62P.js.map +1 -0
- package/dist/cli.js +633 -342
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +20 -10
- package/dist/index.js +7 -1
- package/generated/schemas/resource-package/2.0/agent.schema.json +20 -3
- package/generated/schemas/resource-package/2.0/manifest.schema.json +5 -0
- package/generated/schemas/resource-package/2.0/rag.schema.json +16 -2
- package/generated/schemas/resource-package/2.0/store.schema.json +1 -1
- package/generated/schemas/resource-package/2.0/workflow.schema.json +58 -2
- package/package.json +2 -2
- package/dist/chunk-NLUCQTMN.js.map +0 -1
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-
|
|
14
|
+
} from "./chunk-YOT3B62P.js";
|
|
12
15
|
|
|
13
16
|
// src/cli.ts
|
|
14
|
-
import { basename, join, resolve } from "path";
|
|
15
|
-
import { chmod
|
|
16
|
-
import { createInterface } from "readline/promises";
|
|
17
|
-
import { createHash
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
var
|
|
25
|
-
var
|
|
26
|
-
function
|
|
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]))
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
58
|
-
return
|
|
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
|
|
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 =
|
|
187
|
+
const target = join2(directory, ".env.local");
|
|
139
188
|
let current = "";
|
|
140
189
|
try {
|
|
141
|
-
current = await
|
|
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
|
|
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
|
|
209
|
+
await writeFile2(target, next, { mode: 384 });
|
|
161
210
|
await chmod(target, 384);
|
|
162
|
-
await ensureIgnored(directory, [".env.local"
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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(
|
|
269
|
-
return `import-${
|
|
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 ===
|
|
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 =
|
|
658
|
+
const target = resolve3(directory);
|
|
304
659
|
try {
|
|
305
|
-
const manifest = JSON.parse(await
|
|
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-${
|
|
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 =
|
|
726
|
+
const target = join3(app.directory, ".env.local");
|
|
372
727
|
let content = "";
|
|
373
728
|
try {
|
|
374
|
-
content = await
|
|
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
|
|
386
|
-
await
|
|
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
|
|
391
|
-
|
|
392
|
-
|
|
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-${
|
|
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
|
-
|
|
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
|
-
|
|
405
|
-
const
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
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
|
|
440
|
-
|
|
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(
|
|
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,
|
|
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
|
-
...
|
|
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
|
|
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
|
|
594
|
-
|
|
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
|
-
|
|
616
|
-
|
|
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-${
|
|
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
|
|
641
|
-
|
|
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 {
|
|
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 } : {} } :
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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 =
|
|
747
|
-
const name = flag(parsed, "name") ||
|
|
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(
|
|
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 =
|
|
767
|
-
await
|
|
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 =
|
|
784
|
-
await
|
|
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 {
|
|
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
|
-
|
|
1112
|
+
data,
|
|
1113
|
+
filename,
|
|
800
1114
|
mappings: mappings(parsed)
|
|
801
1115
|
};
|
|
802
|
-
let preview = await client.
|
|
803
|
-
request.mappings = await
|
|
804
|
-
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);
|
|
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
|
|
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 =
|
|
1132
|
+
parsed = parseArguments(argv);
|
|
831
1133
|
const result = await execute(parsed);
|
|
832
|
-
|
|
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
|
-
|
|
842
|
-
|
|
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
|
}
|