aui-agent-builder 0.3.76 → 0.3.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +157 -26
  2. package/dist/api-client/index.d.ts +41 -0
  3. package/dist/api-client/index.d.ts.map +1 -1
  4. package/dist/api-client/index.js +98 -0
  5. package/dist/api-client/index.js.map +1 -1
  6. package/dist/commands/agents.d.ts.map +1 -1
  7. package/dist/commands/agents.js +6 -8
  8. package/dist/commands/agents.js.map +1 -1
  9. package/dist/commands/import-agent.js +8 -8
  10. package/dist/commands/import-agent.js.map +1 -1
  11. package/dist/commands/integration.js +1 -1
  12. package/dist/commands/integration.js.map +1 -1
  13. package/dist/commands/login.js +1 -1
  14. package/dist/commands/login.js.map +1 -1
  15. package/dist/commands/pull-agent.js +1 -1
  16. package/dist/commands/pull-agent.js.map +1 -1
  17. package/dist/commands/push.js +228 -85
  18. package/dist/commands/push.js.map +1 -1
  19. package/dist/commands/serve.d.ts.map +1 -1
  20. package/dist/commands/serve.js +1 -4
  21. package/dist/commands/serve.js.map +1 -1
  22. package/dist/commands/version-snapshot.d.ts +21 -0
  23. package/dist/commands/version-snapshot.d.ts.map +1 -0
  24. package/dist/commands/version-snapshot.js +669 -0
  25. package/dist/commands/version-snapshot.js.map +1 -0
  26. package/dist/commands/version.d.ts +2 -1
  27. package/dist/commands/version.d.ts.map +1 -1
  28. package/dist/commands/version.js +111 -42
  29. package/dist/commands/version.js.map +1 -1
  30. package/dist/index.js +71 -9
  31. package/dist/index.js.map +1 -1
  32. package/dist/ui/views/PushView.d.ts +3 -1
  33. package/dist/ui/views/PushView.d.ts.map +1 -1
  34. package/dist/ui/views/PushView.js +8 -2
  35. package/dist/ui/views/PushView.js.map +1 -1
  36. package/package.json +1 -1
@@ -0,0 +1,669 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { render } from "ink";
3
+ import { Box, Text } from "ink";
4
+ import inquirer from "inquirer";
5
+ import { getConfig, loadSession, loadProjectConfig, findProjectRoot, loadAgentSettingsApiKey, } from "../config/index.js";
6
+ import { AUIClient } from "../api-client/index.js";
7
+ import { AuthenticationError } from "../errors/index.js";
8
+ import { isJsonMode, outputJson, outputJsonError, stderrLog } from "../utils/json-output.js";
9
+ import { Header, StatusLine, Spinner, ErrorDisplay, Divider, KeyValueGroup, } from "../ui/components/index.js";
10
+ import { colors } from "../ui/theme.js";
11
+ // ─── Ink Rendering Helpers ───
12
+ function log(node) {
13
+ const { unmount } = render(node);
14
+ unmount();
15
+ }
16
+ function startSpinner(label) {
17
+ const inst = render(_jsx(Spinner, { label: label }));
18
+ return {
19
+ succeed(msg) {
20
+ inst.unmount();
21
+ log(_jsx(StatusLine, { kind: "success", label: msg }));
22
+ },
23
+ warn(msg) {
24
+ inst.unmount();
25
+ log(_jsx(StatusLine, { kind: "warning", label: msg }));
26
+ },
27
+ fail(msg) {
28
+ inst.unmount();
29
+ log(_jsx(StatusLine, { kind: "error", label: msg }));
30
+ },
31
+ stop() {
32
+ inst.unmount();
33
+ },
34
+ };
35
+ }
36
+ // ─── Helpers ───
37
+ function getAuthenticatedClient() {
38
+ const config = getConfig();
39
+ if (!config.isAuthenticated) {
40
+ throw new AuthenticationError("Not logged in.");
41
+ }
42
+ const client = new AUIClient({
43
+ baseUrl: config.apiUrl,
44
+ authToken: config.authToken,
45
+ accountId: config.accountId,
46
+ organizationId: config.organizationId,
47
+ environment: config.environment,
48
+ });
49
+ const key = loadAgentSettingsApiKey();
50
+ if (key)
51
+ client.setAgentSettingsApiKey(key);
52
+ return client;
53
+ }
54
+ /**
55
+ * Resolve the agent for snapshot commands. Tries (in order):
56
+ * 1. Explicit agent ID (assumed to be agent-management ID)
57
+ * 2. .auirc agent_id (network ID) → agent-management lookup (project context)
58
+ * 3. Session's agent_management_id
59
+ * 4. Session's network_id → agent-management lookup
60
+ *
61
+ * Project config takes priority over session — if you're inside a project,
62
+ * that's the agent you mean.
63
+ */
64
+ async function resolveAgentForSnapshot(client, explicitAgentId) {
65
+ if (explicitAgentId) {
66
+ return client.agentManagement.getAgent(explicitAgentId);
67
+ }
68
+ const session = loadSession();
69
+ const projectRoot = findProjectRoot();
70
+ const projectConfig = projectRoot ? loadProjectConfig(projectRoot) : null;
71
+ const projectNetworkId = projectConfig?.agent_id;
72
+ // 1. Project config wins
73
+ if (projectNetworkId) {
74
+ try {
75
+ const resp = await client.agentManagement.listAgents(client.getOrganizationId(), 1, 50, { network_id: projectNetworkId });
76
+ const match = resp.items.find((a) => a.scope.network_id === projectNetworkId || a.id === projectNetworkId);
77
+ if (match)
78
+ return match;
79
+ }
80
+ catch {
81
+ // fall through
82
+ }
83
+ }
84
+ // 2. Session agent (only if not inside a project)
85
+ if (!projectNetworkId && session?.agent_management_id) {
86
+ try {
87
+ return await client.agentManagement.getAgent(session.agent_management_id);
88
+ }
89
+ catch {
90
+ // stale ID, fall through
91
+ }
92
+ }
93
+ // 3. Session network_id as last resort
94
+ const fallbackNetworkId = session?.network_id;
95
+ if (fallbackNetworkId) {
96
+ try {
97
+ const resp = await client.agentManagement.listAgents(client.getOrganizationId(), 1, 50, { network_id: fallbackNetworkId });
98
+ const match = resp.items.find((a) => a.scope.network_id === fallbackNetworkId || a.id === fallbackNetworkId);
99
+ if (match)
100
+ return match;
101
+ }
102
+ catch {
103
+ // fall through
104
+ }
105
+ }
106
+ throw new Error("Could not resolve agent. Pass --agent-id explicitly, or run `aui import-agent` first.");
107
+ }
108
+ /**
109
+ * Resolve the version to use for snapshot commands. Tries (in order):
110
+ * 1. Explicit version ID
111
+ * 2. .auirc version_id
112
+ * 3. Active version on the agent
113
+ * 4. Interactive prompt
114
+ */
115
+ async function resolveVersionForSnapshot(client, agent, explicitVersionId) {
116
+ if (explicitVersionId) {
117
+ return client.agentManagement.getVersion(agent.id, explicitVersionId);
118
+ }
119
+ const projectRoot = findProjectRoot();
120
+ const projectConfig = projectRoot ? loadProjectConfig(projectRoot) : null;
121
+ if (projectConfig?.version_id) {
122
+ try {
123
+ return await client.agentManagement.getVersion(agent.id, projectConfig.version_id);
124
+ }
125
+ catch {
126
+ // stale, fall through
127
+ }
128
+ }
129
+ const versionsResp = await client.agentManagement.listVersions(agent.id, 1, 50);
130
+ const versions = versionsResp.items;
131
+ if (versions.length === 0) {
132
+ throw new Error("No versions found for this agent.");
133
+ }
134
+ if (agent.active_version_id) {
135
+ const active = versions.find((v) => v.id === agent.active_version_id);
136
+ if (active)
137
+ return active;
138
+ }
139
+ if (isJsonMode()) {
140
+ return versions[0];
141
+ }
142
+ const { selected } = await inquirer.prompt([
143
+ {
144
+ type: "list",
145
+ name: "selected",
146
+ message: "Select version:",
147
+ choices: versions.map((v) => ({
148
+ name: `v${v.version_number} [${v.status}] ${v.label || ""}`,
149
+ value: v,
150
+ })),
151
+ pageSize: 15,
152
+ },
153
+ ]);
154
+ return selected;
155
+ }
156
+ /**
157
+ * Probe the snapshot endpoint to discover all snapshots for a version.
158
+ * Snapshots are tagged v{N}.0, v{N}.1, v{N}.2, ... where N is the version number.
159
+ * The first push to a version creates v{N}.0; subsequent pushes increment.
160
+ * We probe sequentially and stop after a 404 (allowing one gap for safety).
161
+ */
162
+ async function discoverSnapshots(client, agentId, versionId, versionNumber, maxProbes = 200) {
163
+ const found = [];
164
+ let consecutive404s = 0;
165
+ for (let i = 0; i <= maxProbes; i++) {
166
+ const tag = `v${versionNumber}.${i}`;
167
+ try {
168
+ const data = await client.agentManagement.getSnapshot(agentId, versionId, {
169
+ versionTag: tag,
170
+ expiresIn: 15,
171
+ });
172
+ found.push(data);
173
+ consecutive404s = 0;
174
+ }
175
+ catch (err) {
176
+ const status = err.statusCode
177
+ ?? err.status;
178
+ if (status === 404) {
179
+ consecutive404s++;
180
+ // Stop after 2 consecutive 404s to handle a possible gap at i=0 vs i=1
181
+ if (consecutive404s >= 2)
182
+ break;
183
+ continue;
184
+ }
185
+ throw err;
186
+ }
187
+ }
188
+ return found;
189
+ }
190
+ // ─── Interactive Menu ───
191
+ export async function versionSnapshotMenu() {
192
+ const client = getAuthenticatedClient();
193
+ log(_jsx(Header, { title: "Version Snapshots" }));
194
+ const agent = await resolveAgentForSnapshot(client);
195
+ log(_jsx(StatusLine, { kind: "info", label: `Agent: ${agent.name}` }));
196
+ const version = await resolveVersionForSnapshot(client, agent);
197
+ log(_jsx(StatusLine, { kind: "info", label: `Version: v${version.version_number} [${version.status}]` }));
198
+ const { action } = await inquirer.prompt([
199
+ {
200
+ type: "list",
201
+ name: "action",
202
+ message: "What would you like to do?",
203
+ choices: [
204
+ { name: "List snapshots for this version", value: "list" },
205
+ { name: "View a snapshot's manifest", value: "get" },
206
+ { name: "Diff two snapshots", value: "diff" },
207
+ new inquirer.Separator(),
208
+ { name: "Back", value: "back" },
209
+ ],
210
+ },
211
+ ]);
212
+ if (action === "back")
213
+ return;
214
+ const spinner = startSpinner(`Discovering snapshots for v${version.version_number}...`);
215
+ const snapshots = await discoverSnapshots(client, agent.id, version.id, version.version_number);
216
+ spinner.succeed(`Found ${snapshots.length} snapshot(s)`);
217
+ if (snapshots.length === 0) {
218
+ log(_jsx(StatusLine, { kind: "muted", label: "No snapshots yet. Push to this version to create one." }));
219
+ return;
220
+ }
221
+ if (action === "list") {
222
+ renderSnapshotList(snapshots);
223
+ return;
224
+ }
225
+ if (action === "get") {
226
+ const { tag } = await inquirer.prompt([
227
+ {
228
+ type: "list",
229
+ name: "tag",
230
+ message: "Select snapshot:",
231
+ choices: snapshots.map((s) => ({
232
+ name: `${s.manifest.version_tag} — ${s.manifest.files.length} file(s) — ${new Date(s.manifest.created_at).toLocaleString()}`,
233
+ value: s.manifest.version_tag,
234
+ })),
235
+ pageSize: 15,
236
+ },
237
+ ]);
238
+ const snap = snapshots.find((s) => s.manifest.version_tag === tag);
239
+ renderSnapshotDetails(snap);
240
+ return;
241
+ }
242
+ if (action === "diff") {
243
+ if (snapshots.length < 2) {
244
+ log(_jsx(StatusLine, { kind: "warning", label: "Need at least 2 snapshots to diff." }));
245
+ return;
246
+ }
247
+ const { tagA } = await inquirer.prompt([
248
+ {
249
+ type: "list",
250
+ name: "tagA",
251
+ message: "Select first snapshot (older):",
252
+ choices: snapshots.map((s) => ({
253
+ name: `${s.manifest.version_tag} — ${new Date(s.manifest.created_at).toLocaleString()}`,
254
+ value: s.manifest.version_tag,
255
+ })),
256
+ pageSize: 15,
257
+ },
258
+ ]);
259
+ const { tagB } = await inquirer.prompt([
260
+ {
261
+ type: "list",
262
+ name: "tagB",
263
+ message: "Select second snapshot (newer):",
264
+ choices: snapshots
265
+ .filter((s) => s.manifest.version_tag !== tagA)
266
+ .map((s) => ({
267
+ name: `${s.manifest.version_tag} — ${new Date(s.manifest.created_at).toLocaleString()}`,
268
+ value: s.manifest.version_tag,
269
+ })),
270
+ pageSize: 15,
271
+ },
272
+ ]);
273
+ const a = snapshots.find((s) => s.manifest.version_tag === tagA);
274
+ const b = snapshots.find((s) => s.manifest.version_tag === tagB);
275
+ const { showFull } = await inquirer.prompt([
276
+ {
277
+ type: "confirm",
278
+ name: "showFull",
279
+ message: "Download changed files and show JSON-level diff?",
280
+ default: false,
281
+ },
282
+ ]);
283
+ await renderSnapshotDiff(a, b, showFull);
284
+ }
285
+ }
286
+ export async function versionSnapshotList(options = {}) {
287
+ const client = getAuthenticatedClient();
288
+ const json = isJsonMode();
289
+ try {
290
+ const agent = await resolveAgentForSnapshot(client, options.agentId);
291
+ const version = await resolveVersionForSnapshot(client, agent, options.versionId);
292
+ if (json)
293
+ stderrLog(`Discovering snapshots for v${version.version_number}...`);
294
+ const spinner = json ? null : startSpinner(`Discovering snapshots for v${version.version_number}...`);
295
+ const snapshots = await discoverSnapshots(client, agent.id, version.id, version.version_number);
296
+ if (spinner)
297
+ spinner.succeed(`Found ${snapshots.length} snapshot(s)`);
298
+ else
299
+ stderrLog(`Found ${snapshots.length} snapshot(s)`);
300
+ if (json) {
301
+ outputJson({
302
+ agent: { id: agent.id, name: agent.name },
303
+ version: { id: version.id, version_number: version.version_number, status: version.status },
304
+ snapshots: snapshots.map((s) => ({
305
+ version_tag: s.manifest.version_tag,
306
+ created_at: s.manifest.created_at,
307
+ file_count: s.manifest.files.length,
308
+ total_size: s.manifest.files.reduce((sum, f) => sum + f.size, 0),
309
+ files: s.manifest.files.map((f) => ({
310
+ filename: f.filename,
311
+ size: f.size,
312
+ sha256: f.sha256,
313
+ })),
314
+ })),
315
+ });
316
+ return;
317
+ }
318
+ if (snapshots.length === 0) {
319
+ log(_jsx(StatusLine, { kind: "muted", label: "No snapshots yet. Push to this version to create one." }));
320
+ return;
321
+ }
322
+ renderSnapshotList(snapshots);
323
+ }
324
+ catch (error) {
325
+ if (json) {
326
+ outputJsonError({
327
+ code: "SNAPSHOT_LIST_FAILED",
328
+ message: error instanceof Error ? error.message : String(error),
329
+ });
330
+ }
331
+ else {
332
+ log(_jsx(ErrorDisplay, { error: error }));
333
+ }
334
+ }
335
+ }
336
+ // ─── Get ───
337
+ export async function versionSnapshotGet(tagArg, options = {}) {
338
+ const client = getAuthenticatedClient();
339
+ const json = isJsonMode();
340
+ try {
341
+ const agent = await resolveAgentForSnapshot(client, options.agentId);
342
+ const version = await resolveVersionForSnapshot(client, agent, options.versionId);
343
+ let tag = tagArg;
344
+ if (!tag) {
345
+ const spinner = json ? null : startSpinner(`Discovering snapshots for v${version.version_number}...`);
346
+ const snapshots = await discoverSnapshots(client, agent.id, version.id, version.version_number);
347
+ if (spinner)
348
+ spinner.succeed(`Found ${snapshots.length} snapshot(s)`);
349
+ if (snapshots.length === 0) {
350
+ if (json) {
351
+ outputJsonError({ code: "NO_SNAPSHOTS", message: "No snapshots found for this version." });
352
+ }
353
+ else {
354
+ log(_jsx(StatusLine, { kind: "muted", label: "No snapshots found for this version." }));
355
+ }
356
+ return;
357
+ }
358
+ if (json) {
359
+ tag = snapshots[snapshots.length - 1].manifest.version_tag;
360
+ }
361
+ else {
362
+ const answer = await inquirer.prompt([
363
+ {
364
+ type: "list",
365
+ name: "tag",
366
+ message: "Select snapshot:",
367
+ choices: snapshots.map((s) => ({
368
+ name: `${s.manifest.version_tag} — ${s.manifest.files.length} file(s) — ${new Date(s.manifest.created_at).toLocaleString()}`,
369
+ value: s.manifest.version_tag,
370
+ })),
371
+ pageSize: 15,
372
+ },
373
+ ]);
374
+ tag = answer.tag;
375
+ }
376
+ }
377
+ const fetchSpinner = json ? null : startSpinner(`Fetching snapshot ${tag}...`);
378
+ const snap = await client.agentManagement.getSnapshot(agent.id, version.id, {
379
+ versionTag: tag,
380
+ expiresIn: 15,
381
+ });
382
+ if (fetchSpinner)
383
+ fetchSpinner.succeed(`Snapshot ${tag} fetched`);
384
+ if (json) {
385
+ outputJson(snap);
386
+ return;
387
+ }
388
+ renderSnapshotDetails(snap);
389
+ }
390
+ catch (error) {
391
+ if (json) {
392
+ outputJsonError({
393
+ code: "SNAPSHOT_GET_FAILED",
394
+ message: error instanceof Error ? error.message : String(error),
395
+ });
396
+ }
397
+ else {
398
+ log(_jsx(ErrorDisplay, { error: error }));
399
+ }
400
+ }
401
+ }
402
+ export async function versionSnapshotDiff(tagA, tagB, options = {}) {
403
+ const client = getAuthenticatedClient();
404
+ const json = isJsonMode();
405
+ try {
406
+ const agent = await resolveAgentForSnapshot(client, options.agentId);
407
+ const version = await resolveVersionForSnapshot(client, agent, options.versionId);
408
+ let resolvedA = tagA;
409
+ let resolvedB = tagB;
410
+ if (!resolvedA || !resolvedB) {
411
+ const spinner = json ? null : startSpinner(`Discovering snapshots for v${version.version_number}...`);
412
+ const snapshots = await discoverSnapshots(client, agent.id, version.id, version.version_number);
413
+ if (spinner)
414
+ spinner.succeed(`Found ${snapshots.length} snapshot(s)`);
415
+ if (snapshots.length < 2) {
416
+ if (json) {
417
+ outputJsonError({
418
+ code: "INSUFFICIENT_SNAPSHOTS",
419
+ message: "Need at least 2 snapshots to diff.",
420
+ });
421
+ }
422
+ else {
423
+ log(_jsx(StatusLine, { kind: "warning", label: "Need at least 2 snapshots to diff." }));
424
+ }
425
+ return;
426
+ }
427
+ if (!resolvedA) {
428
+ if (json) {
429
+ resolvedA = snapshots[snapshots.length - 2].manifest.version_tag;
430
+ }
431
+ else {
432
+ const answer = await inquirer.prompt([
433
+ {
434
+ type: "list",
435
+ name: "tag",
436
+ message: "Select first snapshot (older):",
437
+ choices: snapshots.map((s) => ({
438
+ name: `${s.manifest.version_tag} — ${new Date(s.manifest.created_at).toLocaleString()}`,
439
+ value: s.manifest.version_tag,
440
+ })),
441
+ pageSize: 15,
442
+ },
443
+ ]);
444
+ resolvedA = answer.tag;
445
+ }
446
+ }
447
+ if (!resolvedB) {
448
+ if (json) {
449
+ resolvedB = snapshots[snapshots.length - 1].manifest.version_tag;
450
+ }
451
+ else {
452
+ const answer = await inquirer.prompt([
453
+ {
454
+ type: "list",
455
+ name: "tag",
456
+ message: "Select second snapshot (newer):",
457
+ choices: snapshots
458
+ .filter((s) => s.manifest.version_tag !== resolvedA)
459
+ .map((s) => ({
460
+ name: `${s.manifest.version_tag} — ${new Date(s.manifest.created_at).toLocaleString()}`,
461
+ value: s.manifest.version_tag,
462
+ })),
463
+ pageSize: 15,
464
+ },
465
+ ]);
466
+ resolvedB = answer.tag;
467
+ }
468
+ }
469
+ }
470
+ const fetchSpinner = json ? null : startSpinner(`Fetching ${resolvedA} and ${resolvedB}...`);
471
+ const [snapA, snapB] = await Promise.all([
472
+ client.agentManagement.getSnapshot(agent.id, version.id, {
473
+ versionTag: resolvedA,
474
+ expiresIn: 15,
475
+ }),
476
+ client.agentManagement.getSnapshot(agent.id, version.id, {
477
+ versionTag: resolvedB,
478
+ expiresIn: 15,
479
+ }),
480
+ ]);
481
+ if (fetchSpinner)
482
+ fetchSpinner.succeed("Snapshots fetched");
483
+ await renderSnapshotDiff(snapA, snapB, options.full ?? false, json);
484
+ }
485
+ catch (error) {
486
+ if (json) {
487
+ outputJsonError({
488
+ code: "SNAPSHOT_DIFF_FAILED",
489
+ message: error instanceof Error ? error.message : String(error),
490
+ });
491
+ }
492
+ else {
493
+ log(_jsx(ErrorDisplay, { error: error }));
494
+ }
495
+ }
496
+ }
497
+ // ─── Renderers ───
498
+ function renderSnapshotList(snapshots) {
499
+ log(_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "\u25C6 " }), _jsxs(Text, { bold: true, children: [snapshots.length, " snapshot(s)"] })] }), _jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, color: colors.muted, children: "Tag" }) }), _jsx(Box, { width: 26, children: _jsx(Text, { bold: true, color: colors.muted, children: "Created" }) }), _jsx(Box, { width: 10, children: _jsx(Text, { bold: true, color: colors.muted, children: "Files" }) }), _jsx(Box, { children: _jsx(Text, { bold: true, color: colors.muted, children: "Total Size" }) })] }), _jsx(Divider, { width: 70 }), snapshots.map((s) => {
500
+ const totalSize = s.manifest.files.reduce((sum, f) => sum + f.size, 0);
501
+ return (_jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { color: colors.success, bold: true, children: s.manifest.version_tag }) }), _jsx(Box, { width: 26, children: _jsx(Text, { color: colors.muted, children: new Date(s.manifest.created_at).toLocaleString() }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: colors.muted, children: s.manifest.files.length }) }), _jsx(Box, { children: _jsx(Text, { color: colors.muted, children: formatBytes(totalSize) }) })] }, s.manifest.version_tag));
502
+ })] }));
503
+ }
504
+ function renderSnapshotDetails(snap) {
505
+ const totalSize = snap.manifest.files.reduce((sum, f) => sum + f.size, 0);
506
+ log(_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Header, { title: `Snapshot ${snap.manifest.version_tag}` }), _jsx(KeyValueGroup, { items: [
507
+ { label: "Tag:", value: snap.manifest.version_tag },
508
+ { label: "Agent ID:", value: snap.manifest.agent_id },
509
+ { label: "Version ID:", value: snap.manifest.version_id },
510
+ { label: "Created:", value: new Date(snap.manifest.created_at).toLocaleString() },
511
+ { label: "Expires:", value: new Date(snap.expires_at).toLocaleString() },
512
+ { label: "Files:", value: String(snap.manifest.files.length) },
513
+ { label: "Total size:", value: formatBytes(totalSize) },
514
+ ] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, children: "Files:" }) }), snap.manifest.files.map((f) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Box, { width: 36, children: _jsx(Text, { children: f.filename }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: colors.muted, children: formatBytes(f.size) }) }), _jsx(Box, { children: _jsxs(Text, { color: colors.muted, children: [f.sha256.substring(0, 12), "\u2026"] }) })] }, f.filename))), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.muted, children: "Use " }), _jsx(Text, { bold: true, children: "aui version snapshot diff" }), _jsx(Text, { color: colors.muted, children: " to compare with another snapshot." })] })] }));
515
+ }
516
+ function computeManifestDiff(a, b) {
517
+ const aMap = new Map(a.manifest.files.map((f) => [f.filename, f]));
518
+ const bMap = new Map(b.manifest.files.map((f) => [f.filename, f]));
519
+ const allNames = new Set([...aMap.keys(), ...bMap.keys()]);
520
+ const entries = [];
521
+ for (const name of allNames) {
522
+ const oldFile = aMap.get(name);
523
+ const newFile = bMap.get(name);
524
+ if (oldFile && !newFile) {
525
+ entries.push({ filename: name, status: "removed", oldFile });
526
+ }
527
+ else if (!oldFile && newFile) {
528
+ entries.push({ filename: name, status: "added", newFile });
529
+ }
530
+ else if (oldFile && newFile) {
531
+ if (oldFile.sha256 !== newFile.sha256) {
532
+ entries.push({ filename: name, status: "modified", oldFile, newFile });
533
+ }
534
+ else {
535
+ entries.push({ filename: name, status: "unchanged", oldFile, newFile });
536
+ }
537
+ }
538
+ }
539
+ return entries;
540
+ }
541
+ async function downloadSnapshotFile(url) {
542
+ const resp = await globalThis.fetch(url);
543
+ if (!resp.ok) {
544
+ throw new Error(`Failed to download file: ${resp.status} ${resp.statusText}`);
545
+ }
546
+ return resp.json();
547
+ }
548
+ function diffJson(oldObj, newObj, prefix = "") {
549
+ const diffs = [];
550
+ if (oldObj !== null &&
551
+ newObj !== null &&
552
+ typeof oldObj === "object" &&
553
+ typeof newObj === "object" &&
554
+ !Array.isArray(oldObj) &&
555
+ !Array.isArray(newObj)) {
556
+ const oldRecord = oldObj;
557
+ const newRecord = newObj;
558
+ const allKeys = new Set([...Object.keys(oldRecord), ...Object.keys(newRecord)]);
559
+ for (const key of allKeys) {
560
+ const p = prefix ? `${prefix}.${key}` : key;
561
+ if (!(key in oldRecord)) {
562
+ diffs.push({ path: p, type: "added", newValue: newRecord[key] });
563
+ }
564
+ else if (!(key in newRecord)) {
565
+ diffs.push({ path: p, type: "removed", oldValue: oldRecord[key] });
566
+ }
567
+ else {
568
+ diffs.push(...diffJson(oldRecord[key], newRecord[key], p));
569
+ }
570
+ }
571
+ return diffs;
572
+ }
573
+ if (JSON.stringify(oldObj) !== JSON.stringify(newObj)) {
574
+ diffs.push({ path: prefix || "(root)", type: "changed", oldValue: oldObj, newValue: newObj });
575
+ }
576
+ return diffs;
577
+ }
578
+ function truncate(value, maxLen = 100) {
579
+ const str = typeof value === "string" ? value : JSON.stringify(value);
580
+ if (str === undefined)
581
+ return "(undefined)";
582
+ return str.length > maxLen ? str.substring(0, maxLen) + "…" : str;
583
+ }
584
+ async function renderSnapshotDiff(a, b, showFull, json = false) {
585
+ const entries = computeManifestDiff(a, b);
586
+ const changed = entries.filter((e) => e.status !== "unchanged");
587
+ const unchanged = entries.filter((e) => e.status === "unchanged").length;
588
+ if (json) {
589
+ const fullDiffs = [];
590
+ for (const entry of changed) {
591
+ const result = { ...entry };
592
+ if (showFull && entry.status === "modified") {
593
+ try {
594
+ const [oldData, newData] = await Promise.all([
595
+ downloadSnapshotFile(a.files[entry.filename]),
596
+ downloadSnapshotFile(b.files[entry.filename]),
597
+ ]);
598
+ result.json_diff = diffJson(oldData, newData);
599
+ }
600
+ catch {
601
+ // skip
602
+ }
603
+ }
604
+ fullDiffs.push(result);
605
+ }
606
+ outputJson({
607
+ from: a.manifest.version_tag,
608
+ to: b.manifest.version_tag,
609
+ changed: changed.length,
610
+ unchanged,
611
+ diffs: fullDiffs,
612
+ });
613
+ return;
614
+ }
615
+ log(_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Header, { title: `${a.manifest.version_tag} → ${b.manifest.version_tag}` }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Changed: " }), _jsx(Text, { color: "yellow", bold: true, children: changed.length }), _jsx(Text, { color: colors.muted, children: " Unchanged: " }), _jsx(Text, { color: colors.muted, children: unchanged })] })] }));
616
+ if (changed.length === 0) {
617
+ log(_jsx(StatusLine, { kind: "success", label: "Snapshots are identical." }));
618
+ return;
619
+ }
620
+ log(_jsx(Box, { flexDirection: "column", paddingX: 1, marginTop: 1, children: changed.map((e) => {
621
+ const symbol = e.status === "added" ? "+" : e.status === "removed" ? "-" : "~";
622
+ const color = e.status === "added" ? "green" : e.status === "removed" ? "red" : "yellow";
623
+ const sizeInfo = e.status === "modified"
624
+ ? `${formatBytes(e.oldFile.size)} → ${formatBytes(e.newFile.size)}`
625
+ : e.status === "added"
626
+ ? formatBytes(e.newFile.size)
627
+ : formatBytes(e.oldFile.size);
628
+ return (_jsxs(Box, { children: [_jsx(Box, { width: 3, children: _jsx(Text, { color: color, bold: true, children: symbol }) }), _jsx(Box, { width: 36, children: _jsx(Text, { children: e.filename }) }), _jsx(Box, { children: _jsxs(Text, { color: colors.muted, children: [e.status, " (", sizeInfo, ")"] }) })] }, e.filename));
629
+ }) }));
630
+ if (showFull) {
631
+ log(_jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, children: "Field-level changes:" }) }));
632
+ for (const entry of changed) {
633
+ if (entry.status !== "modified")
634
+ continue;
635
+ const fileSpinner = startSpinner(`Downloading ${entry.filename}...`);
636
+ try {
637
+ const [oldData, newData] = await Promise.all([
638
+ downloadSnapshotFile(a.files[entry.filename]),
639
+ downloadSnapshotFile(b.files[entry.filename]),
640
+ ]);
641
+ fileSpinner.stop();
642
+ const fieldDiffs = diffJson(oldData, newData);
643
+ log(_jsxs(Box, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "yellow", bold: true, children: ["~ ", entry.filename] }) }), fieldDiffs.length === 0 ? (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.muted, children: "(no field-level diff)" }) })) : (fieldDiffs.map((fd, i) => {
644
+ if (fd.type === "added") {
645
+ return (_jsxs(Box, { marginLeft: 2, children: [_jsxs(Text, { color: "green", bold: true, children: ["+ ", fd.path, ": "] }), _jsx(Text, { color: colors.muted, children: truncate(fd.newValue) })] }, i));
646
+ }
647
+ if (fd.type === "removed") {
648
+ return (_jsxs(Box, { marginLeft: 2, children: [_jsxs(Text, { color: "red", bold: true, children: ["- ", fd.path, ": "] }), _jsx(Text, { color: colors.muted, children: truncate(fd.oldValue) })] }, i));
649
+ }
650
+ return (_jsxs(Box, { marginLeft: 2, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["~ ", fd.path, ": "] }), _jsx(Text, { color: colors.muted, children: truncate(fd.oldValue) }), _jsx(Text, { color: colors.muted, children: " \u2192 " }), _jsx(Text, { color: colors.muted, children: truncate(fd.newValue) })] }, i));
651
+ }))] }));
652
+ }
653
+ catch (err) {
654
+ fileSpinner.fail(`Failed to download ${entry.filename}: ${err instanceof Error ? err.message : String(err)}`);
655
+ }
656
+ }
657
+ }
658
+ else {
659
+ log(_jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsx(Text, { color: colors.muted, children: "Tip: Use " }), _jsx(Text, { bold: true, children: "--full" }), _jsx(Text, { color: colors.muted, children: " to download files and see field-level JSON diffs." })] }));
660
+ }
661
+ }
662
+ function formatBytes(bytes) {
663
+ if (bytes < 1024)
664
+ return `${bytes} B`;
665
+ if (bytes < 1024 * 1024)
666
+ return `${(bytes / 1024).toFixed(1)} KB`;
667
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
668
+ }
669
+ //# sourceMappingURL=version-snapshot.js.map