@cargo-ai/cli 1.0.25 → 1.0.26

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 (30) hide show
  1. package/README.md +17 -17
  2. package/build/commands/cdk/index.d.ts +4 -0
  3. package/build/commands/cdk/index.d.ts.map +1 -0
  4. package/build/commands/cdk/index.js +539 -0
  5. package/build/commands/cdk/init.d.ts +3 -0
  6. package/build/commands/cdk/init.d.ts.map +1 -0
  7. package/build/commands/cdk/init.js +86 -0
  8. package/build/commands/{workflow → cdk}/inputTypes.d.ts +6 -0
  9. package/build/commands/cdk/inputTypes.d.ts.map +1 -0
  10. package/build/commands/{workflow → cdk}/inputTypes.js +115 -17
  11. package/build/commands/cdk/types.d.ts +4 -0
  12. package/build/commands/cdk/types.d.ts.map +1 -0
  13. package/build/commands/{workflow/sync.js → cdk/types.js} +139 -208
  14. package/build/commands/hosting/app.js +1 -1
  15. package/build/commands/hosting/worker.js +1 -1
  16. package/build/commands/runHandler.d.ts +5 -1
  17. package/build/commands/runHandler.d.ts.map +1 -1
  18. package/build/commands/runHandler.js +36 -4
  19. package/build/commands/templateUtils.d.ts.map +1 -0
  20. package/build/index.js +2 -0
  21. package/package.json +4 -3
  22. package/build/commands/hosting/templateUtils.d.ts.map +0 -1
  23. package/build/commands/workflow/index.d.ts +0 -4
  24. package/build/commands/workflow/index.d.ts.map +0 -1
  25. package/build/commands/workflow/index.js +0 -10
  26. package/build/commands/workflow/inputTypes.d.ts.map +0 -1
  27. package/build/commands/workflow/sync.d.ts +0 -4
  28. package/build/commands/workflow/sync.d.ts.map +0 -1
  29. /package/build/commands/{hosting/templateUtils.d.ts → templateUtils.d.ts} +0 -0
  30. /package/build/commands/{hosting/templateUtils.js → templateUtils.js} +0 -0
package/README.md CHANGED
@@ -98,23 +98,23 @@ cargo-ai orchestration workflow --help
98
98
 
99
99
  ### Domains and example commands
100
100
 
101
- | Domain | Description | Example commands |
102
- | ------------------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
103
- | **init** | Workspace initialisation (user, workspace, datasets, etc.) | `cargo-ai init` |
104
- | **orchestration** | Workflows, plays, runs, batches, tools, templates | `cargo-ai orchestration workflow list`, `cargo-ai orchestration run list --workflow-uuid <uuid>` |
105
- | **workspaceManagement** | Workspaces, users, tokens, roles, folders | `cargo-ai workspaceManagement workspaces list`, `cargo-ai workspaceManagement token list` |
106
- | **storage** | Datasets, models, relationships, runs, records | `cargo-ai storage dataset list`, `cargo-ai storage model list` |
107
- | **connection** | Connectors and integrations | `cargo-ai connection connector list`, `cargo-ai connection integration list` |
108
- | **billing** | Usage and subscription | `cargo-ai billing subscription get`, `cargo-ai billing usage get-metrics --payload '{"from":"2025-01-01","to":"2025-01-31"}'` |
109
- | **segmentation** | Segments and changes | `cargo-ai segmentation segment list`, `cargo-ai segmentation change list --payload '{}'` |
110
- | **revenue-organization** | Allocations, capacities, members, territories | `cargo-ai revenue-organization member list`, `cargo-ai revenue-organization territory list` |
111
- | **expression** | Recipes and expression evaluation | `cargo-ai expression recipe list`, `cargo-ai expression eval evaluate --payload '{}'` |
112
- | **system-of-record** | System of record, client, logs | `cargo-ai system-of-record sor list`, `cargo-ai system-of-record log list --payload '{}'` |
113
- | **user-management** | Current user (no workspace context) | `cargo-ai user-management user get-current` |
114
- | **ai** | AI templates, agents, releases, chats, MCP, files | `cargo-ai ai template list`, `cargo-ai ai agent list`, `cargo-ai ai file list` |
115
- | **context** | Context repository, runtime sandbox, and knowledge graph | `cargo-ai context repository get`, `cargo-ai context runtime browse --path <path>`, `cargo-ai context graph get` |
116
- | **hosting** | Cargo Hosting apps (Vite SPAs), workers, and deployments | `cargo-ai hosting app list`, `cargo-ai hosting worker list`, `cargo-ai hosting deployment list --payload '{}'` |
117
- | **workflow** | Workflow SDK developer tools: per-workspace type sync/codegen | `cargo-ai workflow sync` (SDK deploy lives at `cargo-ai orchestration release deploy-draft --file <path>`) |
101
+ | Domain | Description | Example commands |
102
+ | ------------------------ | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
103
+ | **init** | Workspace initialisation (user, workspace, datasets, etc.) | `cargo-ai init` |
104
+ | **orchestration** | Workflows, plays, runs, batches, tools, templates | `cargo-ai orchestration workflow list`, `cargo-ai orchestration run list --workflow-uuid <uuid>` |
105
+ | **workspaceManagement** | Workspaces, users, tokens, roles, folders | `cargo-ai workspaceManagement workspaces list`, `cargo-ai workspaceManagement token list` |
106
+ | **storage** | Datasets, models, relationships, runs, records | `cargo-ai storage dataset list`, `cargo-ai storage model list` |
107
+ | **connection** | Connectors and integrations | `cargo-ai connection connector list`, `cargo-ai connection integration list` |
108
+ | **billing** | Usage and subscription | `cargo-ai billing subscription get`, `cargo-ai billing usage get-metrics --payload '{"from":"2025-01-01","to":"2025-01-31"}'` |
109
+ | **segmentation** | Segments and changes | `cargo-ai segmentation segment list`, `cargo-ai segmentation change list --payload '{}'` |
110
+ | **revenue-organization** | Allocations, capacities, members, territories | `cargo-ai revenue-organization member list`, `cargo-ai revenue-organization territory list` |
111
+ | **expression** | Recipes and expression evaluation | `cargo-ai expression recipe list`, `cargo-ai expression eval evaluate --payload '{}'` |
112
+ | **system-of-record** | System of record, client, logs | `cargo-ai system-of-record sor list`, `cargo-ai system-of-record log list --payload '{}'` |
113
+ | **user-management** | Current user (no workspace context) | `cargo-ai user-management user get-current` |
114
+ | **ai** | AI templates, agents, releases, chats, MCP, files | `cargo-ai ai template list`, `cargo-ai ai agent list`, `cargo-ai ai file list` |
115
+ | **context** | Context repository, runtime sandbox, and knowledge graph | `cargo-ai context repository get`, `cargo-ai context runtime browse --path <path>`, `cargo-ai context graph get` |
116
+ | **hosting** | Cargo Hosting apps (Vite SPAs), workers, and deployments | `cargo-ai hosting app list`, `cargo-ai hosting worker list`, `cargo-ai hosting deployment list --payload '{}'` |
117
+ | **cdk** | Declarative IaC: define resources in code, deploy, generate types | `cargo-ai cdk init <dir>`, `cargo-ai cdk types`, `cargo-ai cdk plan`, `cargo-ai cdk deploy` |
118
118
 
119
119
  Commands that accept complex payloads use a `--payload <json>` option (e.g. `cargo-ai orchestration play create --payload '{"name":"My Play",...}'`). Use `--help` on any subcommand for options.
120
120
 
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { Api } from "../../api.js";
3
+ export declare function registerCdkCommands(program: Command, getApi: () => Api): void;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/cdk/index.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAkBxC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAqpB7E"}
@@ -0,0 +1,539 @@
1
+ import { acquireLock, appendAudit, apply, compile, destroy, detectDrift, emptyState, formatPlan, importResource, liveExecutors, liveReaders, loadResources, newRunId, plan, readState, reconcileDrift, removeResources, restoreSnapshot, snapshotState, writeState, } from "@cargo-ai/cdk";
2
+ import { getConfig } from "../../config.js";
3
+ import { colors, confirm, ExitCodes, failWith, info, outputJson, startSpinner, success, } from "../runHandler.js";
4
+ import { registerInitCommand } from "./init.js";
5
+ import { registerTypesCommand } from "./types.js";
6
+ // `cargo-ai cdk` — declarative deploy of a repo of Cargo resources. The
7
+ // engine (load / compile / apply / state) lives in @cargo-ai/cdk; the CLI parses
8
+ // args, authenticates, confirms, and prints.
9
+ export function registerCdkCommands(program, getApi) {
10
+ const cdk = program
11
+ .command("cdk")
12
+ .description("Cargo CDK — define resources in code and deploy them (plan/deploy)");
13
+ // `cdk init` — scaffold a starter project; `cdk types` — generate per-workspace
14
+ // TS types (typed connector/model config).
15
+ registerInitCommand(cdk);
16
+ registerTypesCommand(cdk, getApi);
17
+ cdk
18
+ .command("plan")
19
+ .description("Compile the resource tree and diff against cargo.state.json. Offline — no API calls.")
20
+ .option("--dir <path>", "Repo root containing the resource files (default: cwd)")
21
+ .option("--json", "Output the raw plan result as JSON")
22
+ .action(async (opts) => {
23
+ // Run from the repo root so relative code paths (defineWorker/App/File)
24
+ // and cargo.state.json resolve against it.
25
+ if (opts.dir !== undefined)
26
+ process.chdir(opts.dir);
27
+ const root = ".";
28
+ let result;
29
+ try {
30
+ result = await plan(root);
31
+ }
32
+ catch (error) {
33
+ failWith(`Failed to load cdk repo: ${message(error)}`, {
34
+ code: ExitCodes.InvalidUsage,
35
+ });
36
+ }
37
+ if (opts.json === true)
38
+ outputJson(result);
39
+ else
40
+ process.stdout.write(`${formatPlan(result)}\n`);
41
+ if (result.errors.length > 0)
42
+ process.exitCode = ExitCodes.GenericError;
43
+ });
44
+ cdk
45
+ .command("deploy")
46
+ .description("Deploy the resource tree to Cargo — create/update resources and write cargo.state.json.")
47
+ .option("--dir <path>", "Repo root containing the resource files (default: cwd)")
48
+ .option("--yes", "Skip the confirmation prompt")
49
+ .option("--dry-run", "Show the plan without applying")
50
+ .option("--prune", "Delete resources tracked in state but no longer in code")
51
+ .option("--refresh", "Re-read live resources first and re-apply any modified out-of-band")
52
+ .option("--recreate-deleted", "With --refresh, also restore resources deleted outside the CDK (off by default)")
53
+ .option("--force", "Steal the state lock held by another run")
54
+ .option("--json", "Output the deploy result as JSON")
55
+ .action(async (opts) => {
56
+ // Run from the repo root so relative code paths (defineWorker/App/File)
57
+ // and cargo.state.json resolve against it.
58
+ if (opts.dir !== undefined)
59
+ process.chdir(opts.dir);
60
+ const root = ".";
61
+ const workspaceUuid = await resolveWorkspaceUuid(getApi);
62
+ const runId = newRunId();
63
+ // Load the resource tree ONCE (loadResources resets the registry, so it
64
+ // must not run twice in a process), then reuse the nodes for plan + apply.
65
+ let nodes;
66
+ try {
67
+ nodes = await loadResources(root);
68
+ }
69
+ catch (error) {
70
+ failWith(`Failed to load cdk repo: ${message(error)}`, {
71
+ code: ExitCodes.InvalidUsage,
72
+ });
73
+ }
74
+ // Hold the state lock for the whole read→plan→apply sequence (not just
75
+ // the apply), so a concurrent run can't rewrite cargo.state.json between
76
+ // our read and our write. --dry-run is read-only, so it skips the lock.
77
+ if (opts.dryRun !== true) {
78
+ try {
79
+ acquireLock(root, "deploy", opts.force === true);
80
+ }
81
+ catch (error) {
82
+ failWith(message(error), { code: ExitCodes.GenericError });
83
+ }
84
+ }
85
+ const prior = readState(root);
86
+ if (prior !== undefined && prior.workspaceUuid !== workspaceUuid) {
87
+ failWith(`cargo.state.json belongs to workspace ${prior.workspaceUuid}, but the selected workspace is ${workspaceUuid}. Refusing to deploy (would orphan resources).`, { code: ExitCodes.InvalidUsage });
88
+ }
89
+ const baseState = prior ?? emptyState(workspaceUuid);
90
+ // Build the authenticated client lazily so a plain `--dry-run` stays
91
+ // offline; `--refresh` and the apply both reuse this one instance.
92
+ let apiClient;
93
+ const api = () => {
94
+ if (apiClient === undefined)
95
+ apiClient = getApi();
96
+ return apiClient;
97
+ };
98
+ // With --refresh, re-read the live resources and fold out-of-band drift
99
+ // into the state before planning: a resource edited in the UI re-applies
100
+ // (its code-hash is invalidated) and one deleted externally is recreated.
101
+ let state = baseState;
102
+ if (opts.refresh === true &&
103
+ Object.keys(baseState.resources).length > 0) {
104
+ let drift;
105
+ try {
106
+ drift = await detectDrift(baseState, liveReaders(api()));
107
+ }
108
+ catch (error) {
109
+ failWith(`Refresh failed: ${message(error)}`, {
110
+ code: ExitCodes.GenericError,
111
+ });
112
+ }
113
+ const modifiedExternally = drift.filter((d) => d.status === "modified");
114
+ const deletedExternally = drift.filter((d) => d.status === "deleted");
115
+ const changed = [...modifiedExternally, ...deletedExternally];
116
+ if (changed.length === 0) {
117
+ info("Refresh: no out-of-band drift.");
118
+ }
119
+ else {
120
+ info(`Refresh found ${String(changed.length)} drifted resource(s):`);
121
+ for (const d of modifiedExternally) {
122
+ info(` modified externally: ${d.id}`);
123
+ }
124
+ for (const d of deletedExternally) {
125
+ info(` deleted externally: ${d.id}`);
126
+ }
127
+ // A resource present in code but deleted in the UI is ambiguous —
128
+ // an accident to restore, or an intentional removal. Don't silently
129
+ // recreate it; force the decision unless --recreate-deleted opts in.
130
+ if (deletedExternally.length > 0 && opts.recreateDeleted !== true) {
131
+ failWith(`${String(deletedExternally.length)} resource(s) were deleted outside the CDK ` +
132
+ `(${deletedExternally.map((d) => d.id).join(", ")}). Re-run with ` +
133
+ `--recreate-deleted to restore them, or remove them from your code if ` +
134
+ `the deletion was intentional.`, { code: ExitCodes.InvalidUsage });
135
+ }
136
+ // Modified always re-applies; deleted only reconciles (→ recreate)
137
+ // once the user has opted in above.
138
+ const effectiveDrift = opts.recreateDeleted === true ? drift : modifiedExternally;
139
+ state = reconcileDrift(baseState, effectiveDrift);
140
+ }
141
+ }
142
+ const planResult = compile({ nodes, state });
143
+ info(formatPlan(planResult));
144
+ if (planResult.errors.length > 0) {
145
+ failWith("Plan has errors — fix them before deploying.", {
146
+ code: ExitCodes.GenericError,
147
+ });
148
+ }
149
+ const deletes = planResult.plan.filter((e) => e.change === "delete");
150
+ const hasApplyWork = planResult.plan.some((e) => e.change === "create" || e.change === "update");
151
+ const willPrune = opts.prune === true && deletes.length > 0;
152
+ if (opts.dryRun === true) {
153
+ // Make the dry run reflect the --prune decision.
154
+ if (deletes.length > 0) {
155
+ info(willPrune
156
+ ? `Would prune ${String(deletes.length)} resource(s): ${deletes.map((e) => e.id).join(", ")}`
157
+ : `${String(deletes.length)} resource(s) would be left in place — add --prune to remove them.`);
158
+ }
159
+ return;
160
+ }
161
+ if (!hasApplyWork && !willPrune) {
162
+ if (deletes.length > 0) {
163
+ info(`${String(deletes.length)} resource(s) in state are no longer in code. Re-run with --prune to remove them.`);
164
+ }
165
+ else {
166
+ success("Nothing to deploy — everything is up to date.");
167
+ }
168
+ return;
169
+ }
170
+ if (opts.yes !== true) {
171
+ const ask = willPrune
172
+ ? `Deploy to workspace ${workspaceUuid} and prune ${String(deletes.length)} resource(s)?`
173
+ : `Deploy to workspace ${workspaceUuid}?`;
174
+ const ok = await confirm(ask);
175
+ if (!ok)
176
+ failWith("Aborted.", { code: ExitCodes.GenericError });
177
+ }
178
+ // Snapshot the pre-deploy state so `cdk rollback` can restore it.
179
+ snapshotState(root);
180
+ // Live progress: a spinner naming the resource currently being applied,
181
+ // leaving a ✓ line behind for each completed one. Suppressed for --json
182
+ // so machine output on stdout stays clean.
183
+ const spinner = opts.json === true ? undefined : startSpinner("Deploying…");
184
+ const onProgress = spinner === undefined
185
+ ? undefined
186
+ : (event) => {
187
+ if (event.phase === "start") {
188
+ const verb = event.change === "create" ? "Creating" : "Updating";
189
+ spinner.update(`${verb} ${colors.bold(event.id)} ${colors.dim(`(${event.index}/${event.total})`)}`);
190
+ }
191
+ else {
192
+ const done = event.change === "create" ? "created" : "updated";
193
+ spinner.log(` ${colors.green("✓")} ${event.id} ${colors.dim(done)}`);
194
+ }
195
+ };
196
+ let result;
197
+ try {
198
+ result = await apply({ nodes, state }, liveExecutors(api()), // throws NotAuthenticated if not logged in
199
+ (next) => writeState(root, next), liveReaders(api()), // capture a live fingerprint for drift detection
200
+ onProgress);
201
+ }
202
+ catch (error) {
203
+ spinner?.stop();
204
+ appendAudit(root, {
205
+ runId,
206
+ at: new Date().toISOString(),
207
+ command: "deploy",
208
+ workspaceUuid,
209
+ ok: false,
210
+ error: message(error),
211
+ });
212
+ failWith(`Deploy failed: ${message(error)}`, {
213
+ code: ExitCodes.GenericError,
214
+ extra: {
215
+ runId,
216
+ hint: "Resources created before the failure were saved to cargo.state.json — fix the error and re-run to continue.",
217
+ },
218
+ });
219
+ }
220
+ spinner?.stop();
221
+ let pruned = {
222
+ removed: [],
223
+ released: [],
224
+ };
225
+ if (willPrune) {
226
+ try {
227
+ pruned = await removeResources(root, api(), deletes.map((e) => e.id));
228
+ }
229
+ catch (error) {
230
+ appendAudit(root, {
231
+ runId,
232
+ at: new Date().toISOString(),
233
+ command: "deploy",
234
+ workspaceUuid,
235
+ ok: false,
236
+ error: `prune: ${message(error)}`,
237
+ });
238
+ failWith(`Prune failed: ${message(error)}`, {
239
+ code: ExitCodes.GenericError,
240
+ extra: { runId },
241
+ });
242
+ }
243
+ }
244
+ const created = result.applied.filter((e) => e.change === "create").length;
245
+ const updated = result.applied.filter((e) => e.change === "update").length;
246
+ appendAudit(root, {
247
+ runId,
248
+ at: new Date().toISOString(),
249
+ command: "deploy",
250
+ workspaceUuid,
251
+ ok: true,
252
+ summary: {
253
+ created,
254
+ updated,
255
+ unchanged: result.skipped.length,
256
+ pruned: pruned.removed.length,
257
+ released: pruned.released.length,
258
+ },
259
+ });
260
+ if (opts.json === true) {
261
+ outputJson({
262
+ created,
263
+ updated,
264
+ skipped: result.skipped.length,
265
+ pruned: pruned.removed.length,
266
+ released: pruned.released.length,
267
+ });
268
+ }
269
+ else {
270
+ success(`${String(created)} created, ${String(updated)} updated, ${String(result.skipped.length)} unchanged.`);
271
+ if (pruned.removed.length > 0 || pruned.released.length > 0) {
272
+ info(`Pruned ${String(pruned.removed.length)}${pruned.released.length > 0 ? ` (released ${String(pruned.released.length)} adopted)` : ""}.`);
273
+ }
274
+ else if (deletes.length > 0) {
275
+ info(`${String(deletes.length)} resource(s) no longer in code — re-run with --prune to remove them.`);
276
+ }
277
+ // First deploy: nudge the user to commit state and ignore the lock.
278
+ if (prior === undefined) {
279
+ info("Wrote cargo.state.json — commit it (it links your code to deployed resources). Add cargo.state.lock to .gitignore.");
280
+ }
281
+ }
282
+ });
283
+ cdk
284
+ .command("refresh")
285
+ .description("Re-read live resources and report drift from the last deploy (read-only).")
286
+ .option("--dir <path>", "Repo root (default: cwd)")
287
+ .option("--json", "Output the drift report as JSON")
288
+ .action(async (opts) => {
289
+ if (opts.dir !== undefined)
290
+ process.chdir(opts.dir);
291
+ const root = ".";
292
+ const workspaceUuid = await resolveWorkspaceUuid(getApi);
293
+ const state = readState(root);
294
+ if (state === undefined || Object.keys(state.resources).length === 0) {
295
+ info("No state — nothing to refresh.");
296
+ return;
297
+ }
298
+ // Reading another workspace's state against the selected one would report
299
+ // everything as deleted — refuse rather than print misleading drift.
300
+ if (state.workspaceUuid !== workspaceUuid) {
301
+ failWith(`cargo.state.json belongs to workspace ${state.workspaceUuid}, not the selected ${workspaceUuid}.`, { code: ExitCodes.InvalidUsage });
302
+ }
303
+ const api = getApi();
304
+ let drift;
305
+ try {
306
+ drift = await detectDrift(state, liveReaders(api));
307
+ }
308
+ catch (error) {
309
+ failWith(`Refresh failed: ${message(error)}`, {
310
+ code: ExitCodes.GenericError,
311
+ });
312
+ }
313
+ if (opts.json === true) {
314
+ outputJson(drift);
315
+ return;
316
+ }
317
+ const drifted = drift.filter((d) => d.status === "modified" || d.status === "deleted");
318
+ if (drifted.length === 0) {
319
+ success(`In sync — ${String(drift.length)} resource(s) match the last deploy.`);
320
+ return;
321
+ }
322
+ info(`${String(drifted.length)} resource(s) drifted from code:`);
323
+ for (const d of drifted) {
324
+ info(` ${d.status === "deleted" ? "deleted externally" : "modified externally"}: ${d.id}`);
325
+ }
326
+ info("Run 'cdk deploy --refresh' to re-apply code over the drift.");
327
+ });
328
+ cdk
329
+ .command("import")
330
+ .argument("<id>", "Code resource id to bind (e.g. agent:sdr)")
331
+ .argument("<uuid>", "The live resource's uuid in the workspace")
332
+ .description("Bind an existing live resource to a code resource in cargo.state.json.")
333
+ .option("--dir <path>", "Repo root (default: cwd)")
334
+ .option("--force", "Steal the state lock held by another run")
335
+ .option("--json", "Output the result as JSON")
336
+ .action(async (id, uuid, opts) => {
337
+ if (opts.dir !== undefined)
338
+ process.chdir(opts.dir);
339
+ const root = ".";
340
+ const workspaceUuid = await resolveWorkspaceUuid(getApi);
341
+ const api = getApi();
342
+ const runId = newRunId();
343
+ try {
344
+ acquireLock(root, "import", opts.force === true);
345
+ }
346
+ catch (error) {
347
+ failWith(message(error), { code: ExitCodes.GenericError });
348
+ }
349
+ snapshotState(root); // enable `cdk rollback`
350
+ let result;
351
+ try {
352
+ result = await importResource(root, api, workspaceUuid, id, uuid);
353
+ }
354
+ catch (error) {
355
+ appendAudit(root, {
356
+ runId,
357
+ at: new Date().toISOString(),
358
+ command: "import",
359
+ workspaceUuid,
360
+ ok: false,
361
+ error: message(error),
362
+ });
363
+ failWith(`Import failed: ${message(error)}`, {
364
+ code: ExitCodes.GenericError,
365
+ extra: { runId },
366
+ });
367
+ }
368
+ appendAudit(root, {
369
+ runId,
370
+ at: new Date().toISOString(),
371
+ command: "import",
372
+ workspaceUuid,
373
+ ok: true,
374
+ summary: { id, uuid, kind: result.kind },
375
+ });
376
+ if (opts.json === true)
377
+ outputJson(result);
378
+ else
379
+ success(`Imported ${id} → ${uuid} (${result.kind}).`);
380
+ });
381
+ cdk
382
+ .command("rollback")
383
+ .description("Restore cargo.state.json from the snapshot taken before the last deploy/destroy/import.")
384
+ .option("--dir <path>", "Repo root (default: cwd)")
385
+ .option("--yes", "Skip the confirmation prompt")
386
+ .option("--force", "Steal the state lock held by another run")
387
+ .action(async (opts) => {
388
+ if (opts.dir !== undefined)
389
+ process.chdir(opts.dir);
390
+ const root = ".";
391
+ const workspaceUuid = await resolveWorkspaceUuid(getApi);
392
+ const runId = newRunId();
393
+ if (opts.yes !== true) {
394
+ const ok = await confirm("Restore cargo.state.json from the last snapshot? (restores the state file only — not live resources)");
395
+ if (!ok)
396
+ failWith("Aborted.", { code: ExitCodes.GenericError });
397
+ }
398
+ try {
399
+ acquireLock(root, "rollback", opts.force === true);
400
+ }
401
+ catch (error) {
402
+ failWith(message(error), { code: ExitCodes.GenericError });
403
+ }
404
+ if (!restoreSnapshot(root)) {
405
+ failWith("No snapshot found (cargo.state.bak.json) — nothing to roll back.", { code: ExitCodes.InvalidUsage });
406
+ }
407
+ appendAudit(root, {
408
+ runId,
409
+ at: new Date().toISOString(),
410
+ command: "rollback",
411
+ workspaceUuid,
412
+ ok: true,
413
+ });
414
+ success("Restored cargo.state.json from the snapshot. Run 'cargo-ai cdk deploy --refresh' to reconcile live resources to it.");
415
+ });
416
+ cdk
417
+ .command("destroy")
418
+ .description("Tear down resources recorded in cargo.state.json (deletes by uuid).")
419
+ .option("--dir <path>", "Repo root (default: cwd)")
420
+ .option("--yes", "Skip the confirmation prompt")
421
+ .option("--target <id>", "Only destroy this resource id (e.g. play:welcome)")
422
+ .option("--all", "Destroy every state-tracked resource (required for blanket teardown)")
423
+ .option("--force", "Steal the state lock held by another run")
424
+ .option("--json", "Output the result as JSON")
425
+ .action(async (opts) => {
426
+ // Run from the repo root so relative code paths (defineWorker/App/File)
427
+ // and cargo.state.json resolve against it.
428
+ if (opts.dir !== undefined)
429
+ process.chdir(opts.dir);
430
+ const root = ".";
431
+ const api = getApi();
432
+ const workspaceUuid = await resolveWorkspaceUuid(getApi);
433
+ const runId = newRunId();
434
+ // Blanket teardown must be explicit — `--yes` alone can't nuke a whole
435
+ // workspace; you must opt in with --target <id> or --all.
436
+ if (opts.target === undefined && opts.all !== true) {
437
+ failWith("Specify --target <id> to remove one resource, or --all to remove everything in state.", { code: ExitCodes.InvalidUsage });
438
+ }
439
+ // Lock before reading state so the read→delete sequence is consistent.
440
+ try {
441
+ acquireLock(root, "destroy", opts.force === true);
442
+ }
443
+ catch (error) {
444
+ failWith(message(error), { code: ExitCodes.GenericError });
445
+ }
446
+ const state = readState(root);
447
+ if (state === undefined || Object.keys(state.resources).length === 0) {
448
+ info("Nothing to destroy — no state.");
449
+ return;
450
+ }
451
+ if (state.workspaceUuid !== workspaceUuid) {
452
+ failWith(`cargo.state.json belongs to workspace ${state.workspaceUuid}, not the selected ${workspaceUuid}.`, { code: ExitCodes.InvalidUsage });
453
+ }
454
+ const ids = opts.target !== undefined
455
+ ? opts.target in state.resources
456
+ ? [opts.target]
457
+ : []
458
+ : Object.keys(state.resources);
459
+ if (ids.length === 0) {
460
+ // An explicit --target that matches nothing is a usage error, not a
461
+ // success — a scripted `destroy --target X && deploy` must not believe
462
+ // X was torn down when the id was simply mistyped.
463
+ failWith(`"${String(opts.target)}" is not in state — nothing to destroy. Run \`cargo-ai cdk plan\` to see the tracked ids.`, { code: ExitCodes.InvalidUsage });
464
+ }
465
+ // Always show exactly what will be deleted, even with --yes.
466
+ info(`About to remove ${String(ids.length)} resource(s) from workspace ${workspaceUuid}:`);
467
+ for (const id of ids)
468
+ info(` - ${id}`);
469
+ if (opts.yes !== true) {
470
+ const ok = await confirm(`Delete these ${String(ids.length)} resource(s)?`);
471
+ if (!ok)
472
+ failWith("Aborted.", { code: ExitCodes.GenericError });
473
+ }
474
+ snapshotState(root); // enable `cdk rollback`
475
+ let result;
476
+ try {
477
+ result = await destroy(root, api, { target: opts.target });
478
+ }
479
+ catch (error) {
480
+ appendAudit(root, {
481
+ runId,
482
+ at: new Date().toISOString(),
483
+ command: "destroy",
484
+ workspaceUuid,
485
+ ok: false,
486
+ error: message(error),
487
+ });
488
+ failWith(`Destroy failed: ${message(error)}`, {
489
+ code: ExitCodes.GenericError,
490
+ extra: {
491
+ runId,
492
+ hint: "Resources removed before the failure are gone and dropped from state — re-run to remove the rest.",
493
+ },
494
+ });
495
+ }
496
+ appendAudit(root, {
497
+ runId,
498
+ at: new Date().toISOString(),
499
+ command: "destroy",
500
+ workspaceUuid,
501
+ ok: true,
502
+ summary: {
503
+ removed: result.removed.length,
504
+ released: result.released.length,
505
+ ids: result.removed,
506
+ },
507
+ });
508
+ if (opts.json === true)
509
+ outputJson(result);
510
+ else {
511
+ success(result.removed.length === 0
512
+ ? "Nothing removed."
513
+ : `Removed ${String(result.removed.length)}: ${result.removed.join(", ")}`);
514
+ if (result.released.length > 0) {
515
+ info(`Released ${String(result.released.length)} adopted (left in place): ${result.released.join(", ")}`);
516
+ }
517
+ }
518
+ });
519
+ }
520
+ // Resolve the workspace to operate on. Prefers an explicit `CARGO_WORKSPACE_UUID`
521
+ // / credentials-file workspace; otherwise falls back to the workspace the token
522
+ // is bound to (the same lookup `cargo-ai whoami` uses), so a workspace-scoped
523
+ // token "just works" without setting an env var.
524
+ async function resolveWorkspaceUuid(getApi) {
525
+ const { workspaceUuid } = getConfig();
526
+ if (workspaceUuid !== undefined)
527
+ return workspaceUuid;
528
+ try {
529
+ const { workspace } = await getApi().workspaceManagement.workspace.getCurrent();
530
+ return workspace.uuid;
531
+ }
532
+ catch (error) {
533
+ failWith(`No workspace selected and couldn't resolve one from your credentials (${message(error)}). ` +
534
+ "Run 'cargo-ai login' or set CARGO_WORKSPACE_UUID.", { code: ExitCodes.NotAuthenticated });
535
+ }
536
+ }
537
+ function message(error) {
538
+ return error instanceof Error ? error.message : String(error);
539
+ }
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerInitCommand(parent: Command): void;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/cdk/init.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBzC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAyEzD"}