@camox/cli 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as getProjectBySlug, i as createProject, n as checkSlugAvailability, o as listOrganizations, r as createOrganization, s as setActiveOrganization, t as callTool } from "./api-COfB7AKI.mjs";
3
- import { a as writeAuthTokenForUrl, i as verifyOneTimeToken, n as readAuthTokenForUrl, r as removeAuthTokenForUrl } from "./auth-core-cCuk5aZy.mjs";
3
+ import { a as writeAuthTokenForUrl, i as verifyOneTimeToken, n as readAuthTokenForUrl, r as removeAuthTokenForUrl, t as normalizeUrl } from "./auth-core-4Jdf5q-u.mjs";
4
4
  import { object, or } from "@optique/core/constructs";
5
5
  import { message } from "@optique/core/message";
6
6
  import { defineProgram } from "@optique/core/program";
@@ -12,9 +12,11 @@ import { execSync, spawn, spawnSync } from "node:child_process";
12
12
  import http from "node:http";
13
13
  import * as p from "@clack/prompts";
14
14
  import { log } from "@clack/prompts";
15
- import fs from "node:fs";
16
- import path from "node:path";
15
+ import fs, { openAsBlob } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import path, { basename, extname, join } from "node:path";
17
18
  import { ZodError, z } from "zod";
19
+ import { mkdtemp, open, rm, stat } from "node:fs/promises";
18
20
  import { fileURLToPath } from "node:url";
19
21
  import slugify from "slugify";
20
22
  //#region src/lib/auth.ts
@@ -188,11 +190,17 @@ const SIDECAR = path.join("node_modules", ".camox", "runtime.json");
188
190
  var RuntimeNotFoundError = class extends Error {
189
191
  cwd;
190
192
  constructor(cwd) {
191
- super(`No camox runtime found. Looked for "${SIDECAR}" walking up from ${cwd}. The camox vite plugin writes this file on dev/build — start your project's dev server (or run a build) once before invoking camox.`);
193
+ super(`No Camox runtime found. Looked for "${SIDECAR}" starting from ${cwd} and its parent directories. Change into your app directory or pass --cwd <app-path>. If you're already targeting the correct app, run its dev server or build once to generate the runtime file.`);
192
194
  this.cwd = cwd;
193
195
  this.name = "RuntimeNotFoundError";
194
196
  }
195
197
  };
198
+ var RuntimeDirectoryError = class extends Error {
199
+ constructor(directory) {
200
+ super(`Runtime lookup directory "${directory}" does not exist, is not a directory, or cannot be accessed. Check --cwd <app-path>.`);
201
+ this.name = "RuntimeDirectoryError";
202
+ }
203
+ };
196
204
  var RuntimeMalformedError = class extends Error {
197
205
  constructor(file, reason) {
198
206
  super(`Camox runtime sidecar at ${file} is malformed (${reason}). Re-run your project's dev server to regenerate it.`);
@@ -205,7 +213,13 @@ var RuntimeMalformedError = class extends Error {
205
213
  * that tell the user how to fix it.
206
214
  */
207
215
  function loadRuntime(cwd = process.cwd()) {
208
- let dir = cwd;
216
+ let dir = path.resolve(cwd);
217
+ try {
218
+ if (!fs.statSync(dir).isDirectory()) throw new RuntimeDirectoryError(dir);
219
+ } catch {
220
+ throw new RuntimeDirectoryError(dir);
221
+ }
222
+ const start = dir;
209
223
  while (true) {
210
224
  const candidate = path.join(dir, SIDECAR);
211
225
  if (fs.existsSync(candidate)) {
@@ -223,7 +237,7 @@ function loadRuntime(cwd = process.cwd()) {
223
237
  }
224
238
  }
225
239
  const parent = path.dirname(dir);
226
- if (parent === dir) throw new RuntimeNotFoundError(cwd);
240
+ if (parent === dir) throw new RuntimeNotFoundError(start);
227
241
  dir = parent;
228
242
  }
229
243
  }
@@ -239,7 +253,7 @@ function compact(input) {
239
253
  for (const [k, v] of Object.entries(input)) if (v !== void 0) out[k] = v;
240
254
  return out;
241
255
  }
242
- function fail(err, code) {
256
+ function fail$1(err, code) {
243
257
  printError(err);
244
258
  process.exit(code);
245
259
  }
@@ -247,7 +261,7 @@ async function resolveProjectId(token, slug, apiUrl) {
247
261
  try {
248
262
  return (await getProjectBySlug(token, slug, apiUrl)).id;
249
263
  } catch (err) {
250
- return fail({
264
+ return fail$1({
251
265
  code: "PROJECT_LOOKUP_FAILED",
252
266
  message: `Could not load project "${slug}".`,
253
267
  details: err instanceof Error ? err.message : String(err)
@@ -258,7 +272,7 @@ async function callRemote(params) {
258
272
  try {
259
273
  return await callTool(params);
260
274
  } catch (err) {
261
- return fail(asCliError(err), 1);
275
+ return fail$1(asCliError(err), 1);
262
276
  }
263
277
  }
264
278
  /**
@@ -272,37 +286,46 @@ async function callRemote(params) {
272
286
  * from the sidecar — it's derived from auth (`dev:<email>`) by default,
273
287
  * with `--production` as the only opt-in for prod.
274
288
  */
275
- async function dispatch(opts) {
289
+ async function resolveCommandContext(opts) {
276
290
  let runtime;
277
291
  try {
278
- runtime = loadRuntime();
292
+ runtime = loadRuntime(opts.cwd);
279
293
  } catch (err) {
280
- if (err instanceof RuntimeNotFoundError || err instanceof RuntimeMalformedError) return fail({
294
+ if (err instanceof RuntimeDirectoryError) return fail$1({
295
+ code: "INVALID_CWD",
296
+ message: err.message
297
+ }, 2);
298
+ if (err instanceof RuntimeNotFoundError || err instanceof RuntimeMalformedError) return fail$1({
281
299
  code: "RUNTIME_NOT_FOUND",
282
300
  message: err.message
283
301
  }, 2);
284
302
  throw err;
285
303
  }
286
304
  const slug = opts.projectFlag?.trim() || process.env.CAMOX_PROJECT?.trim() || runtime.projectSlug;
287
- const token = readAuthTokenForUrl(runtime.authenticationUrl);
288
- if (!token) return fail({
305
+ const token = readAuthTokenForUrl(runtime.authenticationUrl, opts.cwd);
306
+ if (!token) return fail$1({
289
307
  code: "NOT_AUTHENTICATED",
290
308
  message: `No stored credentials for ${runtime.authenticationUrl}. Run \`camox login\` against this backend first.`
291
309
  }, 2);
292
- if (!opts.production && !token.email) return fail({
310
+ if (!opts.production && !token.email) return fail$1({
293
311
  code: "AUTH_INCOMPLETE",
294
312
  message: "Auth token is missing an email. Run `camox login` to refresh credentials."
295
313
  }, 2);
296
314
  const environmentName = opts.production ? "production" : `dev:${token.email}`;
297
315
  const projectId = await resolveProjectId(token.token, slug, runtime.apiUrl);
298
- const response = await callRemote({
316
+ return {
299
317
  token: token.token,
300
318
  apiUrl: runtime.apiUrl,
301
319
  environmentName,
302
320
  projectId,
303
- name: opts.toolName,
304
- args: compact(opts.args),
305
321
  disableTelemetry: runtime.disableTelemetry
322
+ };
323
+ }
324
+ async function dispatch(opts) {
325
+ const response = await callRemote({
326
+ ...await resolveCommandContext(opts),
327
+ name: opts.toolName,
328
+ args: compact(opts.args)
306
329
  });
307
330
  if (!response.ok) {
308
331
  printError(response.error);
@@ -312,19 +335,24 @@ async function dispatch(opts) {
312
335
  process.exit(0);
313
336
  }
314
337
  //#endregion
338
+ //#region src/lib/runtime-options.ts
339
+ const cwdFlag = optional(option("--cwd", string({ metavar: "PATH" }), { description: message`Start runtime lookup in this directory and walk upward. Relative to the shell's current directory; does not change how other file paths resolve.` }));
340
+ //#endregion
315
341
  //#region src/commands/blocks.ts
316
342
  const projectFlag$3 = optional(option("--project", string({ metavar: "SLUG" })));
317
343
  const jsonFlag$3 = option("--json");
318
344
  const productionFlag$2 = option("--production");
319
345
  const liveFlag$1 = option("--live");
320
- const parser$8 = command("blocks", or(command("types", object({
346
+ const parser$11 = command("blocks", or(command("types", object({
321
347
  command: constant("blocks.types"),
348
+ cwd: cwdFlag,
322
349
  project: projectFlag$3,
323
350
  production: productionFlag$2,
324
351
  json: jsonFlag$3
325
352
  })), command("describe", object({
326
353
  command: constant("blocks.describe"),
327
354
  type: multiple(option("--type", string({ metavar: "TYPE" })), { min: 1 }),
355
+ cwd: cwdFlag,
328
356
  project: projectFlag$3,
329
357
  production: productionFlag$2,
330
358
  json: jsonFlag$3
@@ -332,6 +360,7 @@ const parser$8 = command("blocks", or(command("types", object({
332
360
  command: constant("blocks.get"),
333
361
  id: option("--id", integer({ metavar: "ID" })),
334
362
  live: liveFlag$1,
363
+ cwd: cwdFlag,
335
364
  project: projectFlag$3,
336
365
  production: productionFlag$2,
337
366
  json: jsonFlag$3
@@ -339,6 +368,7 @@ const parser$8 = command("blocks", or(command("types", object({
339
368
  command: constant("blocks.get-many"),
340
369
  id: multiple(option("--id", integer({ metavar: "ID" })), { min: 1 }),
341
370
  live: liveFlag$1,
371
+ cwd: cwdFlag,
342
372
  project: projectFlag$3,
343
373
  production: productionFlag$2,
344
374
  json: jsonFlag$3
@@ -353,6 +383,7 @@ const parser$8 = command("blocks", or(command("types", object({
353
383
  beforeId: optional(option("--before-id", integer({ metavar: "ID" }))),
354
384
  afterPosition: optional(option("--after-position", string({ metavar: "POS" }))),
355
385
  beforePosition: optional(option("--before-position", string({ metavar: "POS" }))),
386
+ cwd: cwdFlag,
356
387
  project: projectFlag$3,
357
388
  production: productionFlag$2,
358
389
  json: jsonFlag$3
@@ -361,6 +392,7 @@ const parser$8 = command("blocks", or(command("types", object({
361
392
  id: option("--id", integer({ metavar: "ID" })),
362
393
  content: optional(option("--content", string({ metavar: "JSON" }))),
363
394
  settings: optional(option("--settings", string({ metavar: "JSON" }))),
395
+ cwd: cwdFlag,
364
396
  project: projectFlag$3,
365
397
  production: productionFlag$2,
366
398
  json: jsonFlag$3
@@ -372,12 +404,14 @@ const parser$8 = command("blocks", or(command("types", object({
372
404
  beforeId: optional(option("--before-id", integer({ metavar: "ID" }))),
373
405
  afterPosition: optional(option("--after-position", string({ metavar: "POS" }))),
374
406
  beforePosition: optional(option("--before-position", string({ metavar: "POS" }))),
407
+ cwd: cwdFlag,
375
408
  project: projectFlag$3,
376
409
  production: productionFlag$2,
377
410
  json: jsonFlag$3
378
411
  })), command("delete", object({
379
412
  command: constant("blocks.delete"),
380
413
  id: option("--id", integer({ metavar: "ID" })),
414
+ cwd: cwdFlag,
381
415
  project: projectFlag$3,
382
416
  production: productionFlag$2,
383
417
  json: jsonFlag$3
@@ -408,8 +442,9 @@ function collectPositioningArgs(args) {
408
442
  toolArgs
409
443
  };
410
444
  }
411
- async function handler$8(args) {
445
+ async function handler$11(args) {
412
446
  const outputMode = args.json ? "json" : "auto";
447
+ const cwd = args.cwd;
413
448
  const projectFlag = args.project;
414
449
  const production = args.production;
415
450
  try {
@@ -417,6 +452,7 @@ async function handler$8(args) {
417
452
  case "blocks.types": return dispatch({
418
453
  toolName: "listBlockTypes",
419
454
  args: {},
455
+ cwd,
420
456
  projectFlag,
421
457
  production,
422
458
  outputMode
@@ -424,6 +460,7 @@ async function handler$8(args) {
424
460
  case "blocks.describe": return dispatch({
425
461
  toolName: "describeBlockTypes",
426
462
  args: { types: [...args.type] },
463
+ cwd,
427
464
  projectFlag,
428
465
  production,
429
466
  outputMode
@@ -434,6 +471,7 @@ async function handler$8(args) {
434
471
  id: args.id,
435
472
  source: args.live ? "live" : "draft"
436
473
  },
474
+ cwd,
437
475
  projectFlag,
438
476
  production,
439
477
  outputMode
@@ -444,6 +482,7 @@ async function handler$8(args) {
444
482
  ids: [...args.id],
445
483
  source: args.live ? "live" : "draft"
446
484
  },
485
+ cwd,
447
486
  projectFlag,
448
487
  production,
449
488
  outputMode
@@ -468,6 +507,7 @@ async function handler$8(args) {
468
507
  settings,
469
508
  ...toolArgs
470
509
  },
510
+ cwd,
471
511
  projectFlag,
472
512
  production,
473
513
  outputMode
@@ -483,6 +523,7 @@ async function handler$8(args) {
483
523
  content,
484
524
  settings
485
525
  },
526
+ cwd,
486
527
  projectFlag,
487
528
  production,
488
529
  outputMode
@@ -510,6 +551,7 @@ async function handler$8(args) {
510
551
  id: args.id,
511
552
  ...toolArgs
512
553
  },
554
+ cwd,
513
555
  projectFlag,
514
556
  production,
515
557
  outputMode
@@ -518,6 +560,7 @@ async function handler$8(args) {
518
560
  case "blocks.delete": return dispatch({
519
561
  toolName: "deleteBlock",
520
562
  args: { id: args.id },
563
+ cwd,
521
564
  projectFlag,
522
565
  production,
523
566
  outputMode
@@ -529,32 +572,74 @@ async function handler$8(args) {
529
572
  }
530
573
  }
531
574
  //#endregion
575
+ //#region src/commands/comments.ts
576
+ const common$1 = {
577
+ cwd: cwdFlag,
578
+ project: optional(option("--project", string({ metavar: "SLUG" }))),
579
+ production: option("--production"),
580
+ json: option("--json")
581
+ };
582
+ const parser$10 = command("comments", or(command("list", object({
583
+ command: constant("comments.list"),
584
+ pageId: option("--page-id", integer({
585
+ min: 1,
586
+ metavar: "ID"
587
+ })),
588
+ ...common$1
589
+ }), { description: message`List all comments (including resolved ones) for a page.` }), command("resolve", object({
590
+ command: constant("comments.resolve"),
591
+ pageId: option("--page-id", integer({
592
+ min: 1,
593
+ metavar: "ID"
594
+ })),
595
+ id: option("--id", string({ metavar: "UUID" })),
596
+ ...common$1
597
+ }), { description: message`Resolve a comment by its UUID and page ID. Use comments list to find both.` })), { description: message`Read and resolve page comments in the selected environment. Defaults to development; use --production to target production.` });
598
+ async function handler$10(args) {
599
+ return dispatch({
600
+ toolName: args.command === "comments.list" ? "listComments" : "resolveComment",
601
+ args: {
602
+ pageId: args.pageId,
603
+ ...args.command === "comments.resolve" ? { id: args.id } : {}
604
+ },
605
+ cwd: args.cwd,
606
+ projectFlag: args.project,
607
+ production: args.production,
608
+ outputMode: args.json ? "json" : "auto"
609
+ });
610
+ }
611
+ //#endregion
532
612
  //#region src/commands/env.ts
533
613
  const projectFlag$2 = optional(option("--project", string({ metavar: "SLUG" })));
534
614
  const jsonFlag$2 = option("--json");
535
615
  const yesFlag = option("--yes", "-y");
536
- const parser$7 = command("env", or(command("check", object({
616
+ const parser$9 = command("env", or(command("check", object({
537
617
  command: constant("env.check"),
618
+ cwd: cwdFlag,
538
619
  project: projectFlag$2,
539
620
  json: jsonFlag$2
540
621
  })), command("push", object({
541
622
  command: constant("env.push"),
542
623
  yes: yesFlag,
624
+ cwd: cwdFlag,
543
625
  project: projectFlag$2,
544
626
  json: jsonFlag$2
545
627
  })), command("pull", object({
546
628
  command: constant("env.pull"),
547
629
  yes: yesFlag,
630
+ cwd: cwdFlag,
548
631
  project: projectFlag$2,
549
632
  json: jsonFlag$2
550
633
  }))));
551
- async function handler$7(args) {
634
+ async function handler$9(args) {
552
635
  const outputMode = args.json ? "json" : "auto";
636
+ const cwd = args.cwd;
553
637
  const projectFlag = args.project;
554
638
  switch (args.command) {
555
639
  case "env.check": return dispatch({
556
640
  toolName: "checkEnvironmentCompatibility",
557
641
  args: {},
642
+ cwd,
558
643
  projectFlag,
559
644
  outputMode
560
645
  });
@@ -571,12 +656,265 @@ async function handler$7(args) {
571
656
  return dispatch({
572
657
  toolName: "replicateEnvironment",
573
658
  args: { direction: isPush ? "push" : "pull" },
659
+ cwd,
574
660
  projectFlag,
575
661
  outputMode
576
662
  });
577
663
  }
578
664
  }
579
665
  }
666
+ const TIMEOUT_MS = 12e4;
667
+ const MAX_REDIRECTS = 5;
668
+ function fail(code, message) {
669
+ throw Object.assign(new Error(message), { __cliError: {
670
+ code,
671
+ message
672
+ } });
673
+ }
674
+ function validateMetadata(options) {
675
+ if (options.alt !== void 0 && options.aiMetadata === "on") fail("INVALID_ARGS", "--alt cannot be combined with --ai-metadata on.");
676
+ }
677
+ function validateUpload(options) {
678
+ if (options.file !== void 0 === (options.url !== void 0)) fail("INVALID_ARGS", "Pass exactly one of --file or --url.");
679
+ validateMetadata(options);
680
+ validateFilename(options.filename);
681
+ }
682
+ function validateFilename(filename) {
683
+ if (filename !== void 0 && (!filename.trim() || /[\\/]/.test(filename) || filename.split("").some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || filename === "." || filename === "..")) fail("INVALID_ARGS", "--filename must be a non-empty filename without directories or control characters.");
684
+ }
685
+ function httpUrl(value) {
686
+ let url;
687
+ try {
688
+ url = new URL(value);
689
+ } catch {
690
+ return fail("DOWNLOAD_FAILED", "Invalid media URL.");
691
+ }
692
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) fail("DOWNLOAD_FAILED", "Media URLs must use HTTP(S) without embedded credentials.");
693
+ return url;
694
+ }
695
+ const MIME_TYPES = {
696
+ ".jpg": "image/jpeg",
697
+ ".jpeg": "image/jpeg",
698
+ ".png": "image/png",
699
+ ".webp": "image/webp",
700
+ ".gif": "image/gif",
701
+ ".avif": "image/avif",
702
+ ".svg": "image/svg+xml",
703
+ ".ico": "image/x-icon",
704
+ ".bmp": "image/bmp",
705
+ ".pdf": "application/pdf",
706
+ ".mp4": "video/mp4",
707
+ ".webm": "video/webm",
708
+ ".mov": "video/quicktime",
709
+ ".mp3": "audio/mpeg",
710
+ ".wav": "audio/wav",
711
+ ".ogg": "audio/ogg",
712
+ ".txt": "text/plain",
713
+ ".json": "application/json"
714
+ };
715
+ function mimeType(filename) {
716
+ return MIME_TYPES[extname(filename).toLowerCase()] ?? "application/octet-stream";
717
+ }
718
+ async function download(source, destination) {
719
+ try {
720
+ let url = httpUrl(source);
721
+ const signal = AbortSignal.timeout(TIMEOUT_MS);
722
+ for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects++) {
723
+ const response = await fetch(url, {
724
+ redirect: "manual",
725
+ signal
726
+ });
727
+ if ([
728
+ 301,
729
+ 302,
730
+ 303,
731
+ 307,
732
+ 308
733
+ ].includes(response.status)) {
734
+ await response.body?.cancel();
735
+ const location = response.headers.get("location");
736
+ if (!location || redirects === MAX_REDIRECTS) fail("DOWNLOAD_FAILED", "Missing redirect location or too many redirects.");
737
+ url = httpUrl(new URL(location, url).href);
738
+ continue;
739
+ }
740
+ if (!response.ok || !response.body) {
741
+ await response.body?.cancel();
742
+ fail("DOWNLOAD_FAILED", `Media download failed (HTTP ${response.status}).`);
743
+ }
744
+ if (Number(response.headers.get("content-length")) > 104857600) {
745
+ await response.body.cancel();
746
+ fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
747
+ }
748
+ const file = await open(destination, "w");
749
+ let size = 0;
750
+ try {
751
+ for await (const chunk of response.body) {
752
+ size += chunk.byteLength;
753
+ if (size > 104857600) fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
754
+ await file.writeFile(chunk);
755
+ }
756
+ } finally {
757
+ await file.close();
758
+ }
759
+ const filename = basename(decodeURIComponent(url.pathname)) || "upload";
760
+ return {
761
+ filename,
762
+ mimeType: response.headers.get("content-type")?.split(";")[0]?.trim() || mimeType(filename)
763
+ };
764
+ }
765
+ return fail("DOWNLOAD_FAILED", "Too many redirects.");
766
+ } catch (error) {
767
+ if (error && typeof error === "object" && "__cliError" in error) throw error;
768
+ return fail("DOWNLOAD_FAILED", "Could not download media (network, timeout, or local I/O failure).");
769
+ }
770
+ }
771
+ async function uploadFile(context, options, targetId) {
772
+ validateUpload(options);
773
+ let temporaryDirectory;
774
+ try {
775
+ let path = options.file;
776
+ let filename = path ? basename(path) : "upload";
777
+ let type = mimeType(filename);
778
+ if (options.url !== void 0) {
779
+ temporaryDirectory = await mkdtemp(join(tmpdir(), "camox-upload-"));
780
+ path = join(temporaryDirectory, "download");
781
+ const downloaded = await download(options.url, path);
782
+ filename = downloaded.filename;
783
+ type = downloaded.mimeType;
784
+ }
785
+ if (!path) fail("INVALID_ARGS", "Provide a file path.");
786
+ const info = await stat(path);
787
+ if (!info.isFile()) fail("INVALID_ARGS", "Upload source must be a regular file.");
788
+ if (info.size > 104857600) fail("FILE_TOO_LARGE", "File exceeds the 100 MiB limit.");
789
+ const body = new FormData();
790
+ if (type === "application/octet-stream") type = mimeType(options.filename ?? filename);
791
+ body.set("file", await openAsBlob(path, { type }), options.filename ?? filename);
792
+ body.set("projectId", String(context.projectId));
793
+ if (options.alt !== void 0) body.set("alt", options.alt);
794
+ if (options.aiMetadata !== void 0 || options.alt !== void 0) body.set("aiMetadataEnabled", String(options.alt === void 0 && options.aiMetadata === "on"));
795
+ const headers = {
796
+ Authorization: `Bearer ${context.token}`,
797
+ "x-camox-client": "cli",
798
+ "x-environment-name": context.environmentName
799
+ };
800
+ if (context.disableTelemetry) headers["x-camox-telemetry-disabled"] = "1";
801
+ let response;
802
+ try {
803
+ const endpoint = targetId === void 0 ? "/files/upload" : `/files/${targetId}/content`;
804
+ response = await fetch(`${context.apiUrl}${endpoint}`, {
805
+ method: "POST",
806
+ headers,
807
+ body,
808
+ signal: AbortSignal.timeout(TIMEOUT_MS),
809
+ redirect: "error"
810
+ });
811
+ } catch {
812
+ return fail("UPLOAD_FAILED", targetId === void 0 ? "Media upload failed (network or timeout). Check files list before retrying." : `File replacement failed (network or timeout). Check files get --id ${targetId} before retrying.`);
813
+ }
814
+ if (!response.ok) fail("UPLOAD_FAILED", `${targetId === void 0 ? "Media upload" : "File replacement"} failed (HTTP ${response.status}).`);
815
+ return await response.json();
816
+ } finally {
817
+ if (temporaryDirectory) await rm(temporaryDirectory, {
818
+ recursive: true,
819
+ force: true
820
+ });
821
+ }
822
+ }
823
+ //#endregion
824
+ //#region src/commands/files.ts
825
+ const common = {
826
+ cwd: cwdFlag,
827
+ project: optional(option("--project", string({ metavar: "SLUG" }))),
828
+ production: option("--production"),
829
+ json: option("--json")
830
+ };
831
+ const metadata = {
832
+ alt: optional(option("--alt", string({ metavar: "TEXT" }), { description: message`Alt text (including an empty string). Disables automatic metadata.` })),
833
+ aiMetadata: optional(option("--ai-metadata", choice(["on", "off"]), { description: message`Enable or disable automatic metadata. Cannot enable alongside --alt. Enabling schedules generation for raster images.` }))
834
+ };
835
+ const parser$8 = command("files", or(command("upload", object({
836
+ command: constant("files.upload"),
837
+ file: optional(option("--file", string({ metavar: "PATH" }), { description: message`Local file to upload. Pass exactly one of --file or --url.` })),
838
+ url: optional(option("--url", string({ metavar: "URL" }), { description: message`HTTP(S) URL to download and copy into Camox, not an external reference.` })),
839
+ filename: optional(option("--filename", string({ metavar: "NAME" }), { description: message`Override the local basename or final URL path filename.` })),
840
+ ...metadata,
841
+ ...common
842
+ }), { description: message`Upload media (maximum 100 MiB). Example: camox files upload --url https://example.com/hero.webp --alt "Product screenshot". Returns the stored file ID and URL. Without metadata options, raster images use automatic metadata.` }), command("update", object({
843
+ command: constant("files.update"),
844
+ id: option("--id", integer({
845
+ min: 1,
846
+ metavar: "ID"
847
+ })),
848
+ file: optional(option("--file", string({ metavar: "PATH" }), { description: message`Replace file content from a local path, keeping its ID and references. Mutually exclusive with --url.` })),
849
+ url: optional(option("--url", string({ metavar: "URL" }), { description: message`Download and replace file content from an HTTP(S) URL, keeping its ID and references. Mutually exclusive with --file.` })),
850
+ filename: optional(option("--filename", string({ metavar: "NAME" }), { description: message`Set the filename. Metadata-only renames preserve the URL; replacement uses the source filename unless overridden. Disable AI to prevent future automatic renaming.` })),
851
+ ...metadata,
852
+ ...common
853
+ }), { description: message`Update metadata or replace file content with --file or --url (maximum 100 MiB). Replacing affects every reference to this file in the selected environment; its ID stays unchanged but its URL changes. Omitted alt text and AI settings are preserved. Example: camox files update --id 123 --file ./hero.jpg --alt "Updated description". Use --alt "" for intentionally empty alt text.` }), command("get", object({
854
+ command: constant("files.get"),
855
+ id: option("--id", integer({
856
+ min: 1,
857
+ metavar: "ID"
858
+ })),
859
+ ...common
860
+ }), { description: message`Inspect a file in the selected project and environment.` }), command("list", object({
861
+ command: constant("files.list"),
862
+ ...common
863
+ }), { description: message`List files in the selected project and environment.` })), { description: message`Upload and manage media. Defaults to your development environment; --production explicitly targets production.` });
864
+ async function handler$8(args) {
865
+ const outputMode = args.json ? "json" : "auto";
866
+ const options = {
867
+ cwd: args.cwd,
868
+ projectFlag: args.project,
869
+ production: args.production,
870
+ outputMode
871
+ };
872
+ try {
873
+ if (args.command === "files.upload") {
874
+ validateUpload(args);
875
+ printResult(await uploadFile(await resolveCommandContext(options), args), outputMode);
876
+ process.exit(0);
877
+ }
878
+ if (args.command === "files.list") return dispatch({
879
+ ...options,
880
+ toolName: "listFiles",
881
+ args: {}
882
+ });
883
+ if (args.command === "files.get") return dispatch({
884
+ ...options,
885
+ toolName: "getFile",
886
+ args: { id: args.id }
887
+ });
888
+ if (args.file !== void 0 || args.url !== void 0) {
889
+ validateUpload(args);
890
+ printResult(await uploadFile(await resolveCommandContext(options), args, args.id), outputMode);
891
+ process.exit(0);
892
+ }
893
+ validateMetadata(args);
894
+ validateFilename(args.filename);
895
+ if (args.alt === void 0 && args.aiMetadata === void 0 && args.filename === void 0) {
896
+ printError({
897
+ code: "INVALID_ARGS",
898
+ message: "Pass --file, --url, --filename, --alt or --ai-metadata."
899
+ });
900
+ process.exit(2);
901
+ }
902
+ return dispatch({
903
+ ...options,
904
+ toolName: "updateFile",
905
+ args: {
906
+ id: args.id,
907
+ alt: args.alt,
908
+ filename: args.filename,
909
+ aiMetadataEnabled: args.aiMetadata === void 0 ? void 0 : args.aiMetadata === "on"
910
+ }
911
+ });
912
+ } catch (err) {
913
+ const error = asCliError(err);
914
+ printError(error);
915
+ process.exit(error.code === "INVALID_ARGS" ? 2 : 1);
916
+ }
917
+ }
580
918
  //#endregion
581
919
  //#region src/lib/agent-skills.ts
582
920
  function createAgentSkillLinks(targetDir) {
@@ -584,8 +922,10 @@ function createAgentSkillLinks(targetDir) {
584
922
  const claudeSkillsDir = path.join(targetDir, ".claude", "skills");
585
923
  fs.mkdirSync(agentsSkillsDir, { recursive: true });
586
924
  fs.mkdirSync(claudeSkillsDir, { recursive: true });
587
- fs.symlinkSync("../../node_modules/camox/skills/camox", path.join(agentsSkillsDir, "camox"));
588
- fs.symlinkSync("../../.agents/skills/camox", path.join(claudeSkillsDir, "camox"));
925
+ for (const skill of ["camox", "camoxify"]) {
926
+ fs.symlinkSync(`../../node_modules/camox/skills/${skill}`, path.join(agentsSkillsDir, skill));
927
+ fs.symlinkSync(`../../.agents/skills/${skill}`, path.join(claudeSkillsDir, skill));
928
+ }
589
929
  }
590
930
  //#endregion
591
931
  //#region src/lib/utils.ts
@@ -656,8 +996,8 @@ function copyDir(src, dest, replacements) {
656
996
  }
657
997
  //#endregion
658
998
  //#region src/commands/init.ts
659
- const parser$6 = command("init", object({ command: constant("init") }));
660
- const handler$6 = init;
999
+ const parser$7 = command("init", object({ command: constant("init") }));
1000
+ const handler$7 = init;
661
1001
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
662
1002
  const ownPkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, "..", "package.json"), "utf-8"));
663
1003
  const PNPM_WORKSPACE = `allowBuilds:
@@ -929,20 +1269,23 @@ src/routeTree.gen.ts
929
1269
  const projectFlag$1 = optional(option("--project", string({ metavar: "SLUG" })));
930
1270
  const jsonFlag$1 = option("--json");
931
1271
  const productionFlag$1 = option("--production");
932
- const parser$5 = command("layouts", or(command("list", object({
1272
+ const parser$6 = command("layouts", or(command("list", object({
933
1273
  command: constant("layouts.list"),
1274
+ cwd: cwdFlag,
934
1275
  project: projectFlag$1,
935
1276
  production: productionFlag$1,
936
1277
  json: jsonFlag$1
937
1278
  }))));
938
- async function handler$5(args) {
1279
+ async function handler$6(args) {
939
1280
  const outputMode = args.json ? "json" : "auto";
1281
+ const cwd = args.cwd;
940
1282
  const projectFlag = args.project;
941
1283
  const production = args.production;
942
1284
  switch (args.command) {
943
1285
  case "layouts.list": return dispatch({
944
1286
  toolName: "listLayouts",
945
1287
  args: {},
1288
+ cwd,
946
1289
  projectFlag,
947
1290
  production,
948
1291
  outputMode
@@ -951,8 +1294,8 @@ async function handler$5(args) {
951
1294
  }
952
1295
  //#endregion
953
1296
  //#region src/commands/login.ts
954
- const parser$4 = command("login", object({ command: constant("login") }));
955
- const handler$4 = login;
1297
+ const parser$5 = command("login", object({ command: constant("login") }));
1298
+ const handler$5 = login;
956
1299
  async function login() {
957
1300
  p.intro("camox login");
958
1301
  try {
@@ -966,26 +1309,29 @@ async function login() {
966
1309
  }
967
1310
  //#endregion
968
1311
  //#region src/commands/logout.ts
969
- const parser$3 = command("logout", object({ command: constant("logout") }));
970
- const handler$3 = logout;
971
- function logout() {
1312
+ const parser$4 = command("logout", object({
1313
+ command: constant("logout"),
1314
+ cwd: cwdFlag
1315
+ }));
1316
+ const handler$4 = logout;
1317
+ function logout(args = {}) {
972
1318
  p.intro("camox logout");
973
1319
  let authenticationUrl = null;
974
1320
  try {
975
- authenticationUrl = loadRuntime().authenticationUrl;
1321
+ authenticationUrl = loadRuntime(args.cwd).authenticationUrl;
976
1322
  } catch (error) {
977
- if (error instanceof RuntimeMalformedError) {
1323
+ if (error instanceof RuntimeMalformedError || error instanceof RuntimeDirectoryError || args.cwd !== void 0 && error instanceof RuntimeNotFoundError) {
978
1324
  log.error(error.message);
979
1325
  return;
980
1326
  }
981
1327
  if (!(error instanceof RuntimeNotFoundError)) throw error;
982
1328
  }
983
- const token = authenticationUrl ? readAuthTokenForUrl(authenticationUrl) : readAuthToken();
1329
+ const token = authenticationUrl ? readAuthTokenForUrl(authenticationUrl, args.cwd) : readAuthToken();
984
1330
  if (!token) {
985
1331
  log.error("Not logged in.");
986
1332
  return;
987
1333
  }
988
- if (authenticationUrl) removeAuthTokenForUrl(authenticationUrl);
1334
+ if (authenticationUrl) removeAuthTokenForUrl(authenticationUrl, args.cwd);
989
1335
  else removeAuthToken();
990
1336
  p.log.success(`Logged out from ${token.name}.`);
991
1337
  }
@@ -997,6 +1343,7 @@ const productionFlag = option("--production");
997
1343
  const liveFlag = option("--live");
998
1344
  const list = command("list", object({
999
1345
  command: constant("pages.list"),
1346
+ cwd: cwdFlag,
1000
1347
  project: projectFlag,
1001
1348
  production: productionFlag,
1002
1349
  json: jsonFlag
@@ -1006,6 +1353,7 @@ const get = command("get", object({
1006
1353
  id: optional(option("--id", integer({ metavar: "ID" }))),
1007
1354
  path: optional(option("--path", string({ metavar: "PATH" }))),
1008
1355
  live: liveFlag,
1356
+ cwd: cwdFlag,
1009
1357
  project: projectFlag,
1010
1358
  production: productionFlag,
1011
1359
  json: jsonFlag
@@ -1015,6 +1363,7 @@ const publish = command("publish", object({
1015
1363
  id: optional(option("--id", integer({ metavar: "ID" }))),
1016
1364
  path: optional(option("--path", string({ metavar: "PATH" }))),
1017
1365
  noLayout: option("--no-layout"),
1366
+ cwd: cwdFlag,
1018
1367
  project: projectFlag,
1019
1368
  production: productionFlag,
1020
1369
  json: jsonFlag
@@ -1023,6 +1372,7 @@ const unpublish = command("unpublish", object({
1023
1372
  command: constant("pages.unpublish"),
1024
1373
  id: optional(option("--id", integer({ metavar: "ID" }))),
1025
1374
  path: optional(option("--path", string({ metavar: "PATH" }))),
1375
+ cwd: cwdFlag,
1026
1376
  project: projectFlag,
1027
1377
  production: productionFlag,
1028
1378
  json: jsonFlag
@@ -1031,50 +1381,64 @@ const discardChanges = command("discard-changes", object({
1031
1381
  command: constant("pages.discard-changes"),
1032
1382
  id: optional(option("--id", integer({ metavar: "ID" }))),
1033
1383
  path: optional(option("--path", string({ metavar: "PATH" }))),
1384
+ cwd: cwdFlag,
1034
1385
  project: projectFlag,
1035
1386
  production: productionFlag,
1036
1387
  json: jsonFlag
1037
1388
  }));
1038
- const parser$2 = command("pages", or(list, get, command("create", object({
1389
+ const parser$3 = command("pages", or(list, get, command("create", object({
1039
1390
  command: constant("pages.create"),
1040
1391
  nickname: optional(option("--nickname", string({ metavar: "TEXT" }))),
1041
1392
  pathSegment: option("--path-segment", string({ metavar: "SEGMENT" })),
1042
1393
  layoutId: option("--layout-id", integer({ metavar: "ID" })),
1043
1394
  parentPageId: optional(option("--parent-page-id", integer({ metavar: "ID" }))),
1395
+ cwd: cwdFlag,
1044
1396
  project: projectFlag,
1045
1397
  production: productionFlag,
1046
1398
  json: jsonFlag
1047
1399
  })), command("update", object({
1048
1400
  command: constant("pages.update"),
1049
- id: option("--id", integer({ metavar: "ID" })),
1401
+ id: optional(option("--id", integer({
1402
+ min: 1,
1403
+ metavar: "ID"
1404
+ }))),
1405
+ path: optional(option("--path", string({ metavar: "PATH" }))),
1406
+ metaTitle: optional(option("--meta-title", string({ metavar: "TEXT" }), { description: message`Set the SEO title. An empty string clears it. Disables automatic SEO for both fields.` })),
1407
+ metaDescription: optional(option("--meta-description", string({ metavar: "TEXT" }), { description: message`Set the SEO description. An empty string clears it. Disables automatic SEO for both fields.` })),
1408
+ aiSeo: optional(option("--ai-seo", choice(["on", "off"]), { description: message`Enable or disable automatic SEO. Enabling schedules asynchronous generation; it cannot be combined with manual metadata. Disabling preserves current metadata.` })),
1050
1409
  nickname: optional(option("--nickname", string({ metavar: "TEXT" }))),
1051
1410
  pathSegment: optional(option("--path-segment", string({ metavar: "SEGMENT" }))),
1052
1411
  parentPageId: optional(option("--parent-page-id", integer({ metavar: "ID" }))),
1412
+ cwd: cwdFlag,
1053
1413
  project: projectFlag,
1054
1414
  production: productionFlag,
1055
1415
  json: jsonFlag
1056
- })), command("set-layout", object({
1416
+ }), { description: message`Update a page draft, including in --production. Pass exactly one of --id or --path and at least one update field. Omitted fields are preserved. Publish separately with pages publish. Example: camox pages update --path /about --meta-title "About us" --meta-description "Meet the team". Read metadata with pages get; add --live for published metadata.` }), command("set-layout", object({
1057
1417
  command: constant("pages.set-layout"),
1058
1418
  id: option("--id", integer({ metavar: "ID" })),
1059
1419
  layoutId: option("--layout-id", integer({ metavar: "ID" })),
1420
+ cwd: cwdFlag,
1060
1421
  project: projectFlag,
1061
1422
  production: productionFlag,
1062
1423
  json: jsonFlag
1063
1424
  })), command("delete", object({
1064
1425
  command: constant("pages.delete"),
1065
1426
  id: option("--id", integer({ metavar: "ID" })),
1427
+ cwd: cwdFlag,
1066
1428
  project: projectFlag,
1067
1429
  production: productionFlag,
1068
1430
  json: jsonFlag
1069
1431
  })), publish, unpublish, discardChanges));
1070
- async function handler$2(args) {
1432
+ async function handler$3(args) {
1071
1433
  const outputMode = args.json ? "json" : "auto";
1434
+ const cwd = args.cwd;
1072
1435
  const projectFlag = args.project;
1073
1436
  const production = args.production;
1074
1437
  switch (args.command) {
1075
1438
  case "pages.list": return dispatch({
1076
1439
  toolName: "listPages",
1077
1440
  args: {},
1441
+ cwd,
1078
1442
  projectFlag,
1079
1443
  production,
1080
1444
  outputMode
@@ -1093,6 +1457,7 @@ async function handler$2(args) {
1093
1457
  ...args.id != null ? { id: args.id } : { path: args.path },
1094
1458
  source: args.live ? "live" : "draft"
1095
1459
  },
1460
+ cwd,
1096
1461
  projectFlag,
1097
1462
  production,
1098
1463
  outputMode
@@ -1105,28 +1470,63 @@ async function handler$2(args) {
1105
1470
  layoutId: args.layoutId,
1106
1471
  parentPageId: args.parentPageId
1107
1472
  },
1473
+ cwd,
1108
1474
  projectFlag,
1109
1475
  production,
1110
1476
  outputMode
1111
1477
  });
1112
- case "pages.update": return dispatch({
1113
- toolName: "updatePage",
1114
- args: {
1115
- id: args.id,
1116
- nickname: args.nickname,
1117
- pathSegment: args.pathSegment,
1118
- parentPageId: args.parentPageId
1119
- },
1120
- projectFlag,
1121
- production,
1122
- outputMode
1123
- });
1478
+ case "pages.update":
1479
+ if (args.id == null === (args.path == null)) {
1480
+ printError({
1481
+ code: "INVALID_ARGS",
1482
+ message: "Pass exactly one of --id or --path."
1483
+ });
1484
+ process.exit(2);
1485
+ }
1486
+ if (args.aiSeo === "on" && (args.metaTitle !== void 0 || args.metaDescription !== void 0)) {
1487
+ printError({
1488
+ code: "INVALID_ARGS",
1489
+ message: "Cannot enable automatic SEO alongside manual metadata."
1490
+ });
1491
+ process.exit(2);
1492
+ }
1493
+ if ([
1494
+ args.nickname,
1495
+ args.pathSegment,
1496
+ args.parentPageId,
1497
+ args.metaTitle,
1498
+ args.metaDescription,
1499
+ args.aiSeo
1500
+ ].every((value) => value === void 0)) {
1501
+ printError({
1502
+ code: "INVALID_ARGS",
1503
+ message: "Pass at least one field to update."
1504
+ });
1505
+ process.exit(2);
1506
+ }
1507
+ return dispatch({
1508
+ toolName: "updatePage",
1509
+ args: {
1510
+ ...args.id != null ? { id: args.id } : { path: args.path },
1511
+ metaTitle: args.metaTitle,
1512
+ metaDescription: args.metaDescription,
1513
+ aiSeoEnabled: args.aiSeo === void 0 ? void 0 : args.aiSeo === "on",
1514
+ nickname: args.nickname,
1515
+ pathSegment: args.pathSegment,
1516
+ parentPageId: args.parentPageId
1517
+ },
1518
+ cwd,
1519
+ projectFlag,
1520
+ production,
1521
+ outputMode
1522
+ });
1124
1523
  case "pages.set-layout": return dispatch({
1125
1524
  toolName: "setPageLayout",
1126
1525
  args: {
1127
1526
  id: args.id,
1128
1527
  layoutId: args.layoutId
1129
1528
  },
1529
+ cwd,
1130
1530
  projectFlag,
1131
1531
  production,
1132
1532
  outputMode
@@ -1134,6 +1534,7 @@ async function handler$2(args) {
1134
1534
  case "pages.delete": return dispatch({
1135
1535
  toolName: "deletePage",
1136
1536
  args: { id: args.id },
1537
+ cwd,
1137
1538
  projectFlag,
1138
1539
  production,
1139
1540
  outputMode
@@ -1152,6 +1553,7 @@ async function handler$2(args) {
1152
1553
  ...args.id != null ? { id: args.id } : { path: args.path },
1153
1554
  alsoPublishLayout: !args.noLayout
1154
1555
  },
1556
+ cwd,
1155
1557
  projectFlag,
1156
1558
  production,
1157
1559
  outputMode
@@ -1167,6 +1569,7 @@ async function handler$2(args) {
1167
1569
  return dispatch({
1168
1570
  toolName: "unpublishPage",
1169
1571
  args: args.id != null ? { id: args.id } : { path: args.path },
1572
+ cwd,
1170
1573
  projectFlag,
1171
1574
  production,
1172
1575
  outputMode
@@ -1182,6 +1585,7 @@ async function handler$2(args) {
1182
1585
  return dispatch({
1183
1586
  toolName: "discardPageChanges",
1184
1587
  args: args.id != null ? { id: args.id } : { path: args.path },
1588
+ cwd,
1185
1589
  projectFlag,
1186
1590
  production,
1187
1591
  outputMode
@@ -1189,6 +1593,61 @@ async function handler$2(args) {
1189
1593
  }
1190
1594
  }
1191
1595
  //#endregion
1596
+ //#region src/commands/preview.ts
1597
+ const parser$2 = command("preview", object({
1598
+ command: constant("preview"),
1599
+ cwd: cwdFlag,
1600
+ url: option("--url", string()),
1601
+ json: option("--json")
1602
+ }), { description: message`Create a single-use sign-in URL for local draft verification. Open it in your automation browser within three minutes. The URL is a credential; do not share it.` });
1603
+ async function handler$2(args) {
1604
+ try {
1605
+ const url = new URL(args.url);
1606
+ if (!["http:", "https:"].includes(url.protocol) || ![
1607
+ "localhost",
1608
+ "127.0.0.1",
1609
+ "[::1]"
1610
+ ].includes(url.hostname) || url.username || url.password) throw new Error("Preview requires an http(s) loopback URL without credentials.");
1611
+ const runtime = loadRuntime(args.cwd);
1612
+ const auth = readAuthTokenForUrl(runtime.authenticationUrl, args.cwd);
1613
+ if (!auth?.email) {
1614
+ printError({
1615
+ code: "NOT_AUTHENTICATED",
1616
+ message: `Run camox login for ${runtime.authenticationUrl} first.`
1617
+ });
1618
+ process.exitCode = 2;
1619
+ return;
1620
+ }
1621
+ const response = await fetch(`${normalizeUrl(runtime.apiUrl)}/api/auth/one-time-token/generate`, {
1622
+ headers: { Authorization: `Bearer ${auth.token}` },
1623
+ redirect: "error",
1624
+ signal: AbortSignal.timeout(15e3)
1625
+ });
1626
+ if (!response.ok) throw new Error(`Could not create preview sign-in (${response.status}). Check your session with camox login.`);
1627
+ const data = await response.json();
1628
+ if (typeof data.token !== "string" || !data.token) throw new Error("Authentication backend returned no one-time token.");
1629
+ const target = {
1630
+ projectSlug: runtime.projectSlug,
1631
+ environmentName: `dev:${auth.email}`,
1632
+ apiUrl: normalizeUrl(runtime.apiUrl)
1633
+ };
1634
+ url.searchParams.set("camox-preview", JSON.stringify(target));
1635
+ url.searchParams.set("ott", data.token);
1636
+ printResult({
1637
+ url: url.href,
1638
+ ...target,
1639
+ source: "draft",
1640
+ warning: "Single-use sign-in credential, valid for three minutes. Open in your verification browser; do not share or commit."
1641
+ }, args.json ? "json" : "auto");
1642
+ } catch (error) {
1643
+ printError({
1644
+ code: "PREVIEW_FAILED",
1645
+ message: error instanceof Error ? error.message : "Could not create preview."
1646
+ });
1647
+ process.exitCode = 2;
1648
+ }
1649
+ }
1650
+ //#endregion
1192
1651
  //#region src/commands/release.ts
1193
1652
  const parser$1 = command("release", object({ command: constant("release") }));
1194
1653
  function handler$1() {
@@ -1220,14 +1679,22 @@ function handler$1() {
1220
1679
  //#region src/commands/status.ts
1221
1680
  const parser = command("status", object({
1222
1681
  command: constant("status"),
1682
+ cwd: cwdFlag,
1223
1683
  production: option("--production"),
1224
1684
  json: option("--json")
1225
1685
  }));
1226
1686
  async function handler(args) {
1227
1687
  let runtime;
1228
1688
  try {
1229
- runtime = loadRuntime();
1689
+ runtime = loadRuntime(args.cwd);
1230
1690
  } catch (err) {
1691
+ if (err instanceof RuntimeDirectoryError) {
1692
+ printError({
1693
+ code: "INVALID_CWD",
1694
+ message: err.message
1695
+ });
1696
+ process.exit(2);
1697
+ }
1231
1698
  if (err instanceof RuntimeNotFoundError || err instanceof RuntimeMalformedError) {
1232
1699
  printError({
1233
1700
  code: "RUNTIME_NOT_FOUND",
@@ -1237,7 +1704,7 @@ async function handler(args) {
1237
1704
  }
1238
1705
  throw err;
1239
1706
  }
1240
- const token = readAuthTokenForUrl(runtime.authenticationUrl);
1707
+ const token = readAuthTokenForUrl(runtime.authenticationUrl, args.cwd);
1241
1708
  const environmentName = args.production ? "production" : token?.email ? `dev:${token.email}` : null;
1242
1709
  const status = {
1243
1710
  projectSlug: runtime.projectSlug,
@@ -1267,7 +1734,7 @@ async function handler(args) {
1267
1734
  //#endregion
1268
1735
  //#region src/index.ts
1269
1736
  const result = runSync(defineProgram({
1270
- parser: or(parser$6, parser$4, parser$3, parser$1, parser, parser$2, parser$8, parser$5, parser$7),
1737
+ parser: or(parser$7, parser$5, parser$4, parser$1, parser, parser$2, parser$3, parser$11, parser$10, parser$6, parser$9, parser$8),
1271
1738
  metadata: {
1272
1739
  name: "camox",
1273
1740
  brief: message`Camox CLI`
@@ -1275,17 +1742,20 @@ const result = runSync(defineProgram({
1275
1742
  }), { help: "both" });
1276
1743
  switch (result.command) {
1277
1744
  case "init":
1278
- await handler$6();
1745
+ await handler$7();
1279
1746
  break;
1280
1747
  case "login":
1281
- await handler$4();
1748
+ await handler$5();
1282
1749
  break;
1283
1750
  case "logout":
1284
- handler$3();
1751
+ handler$4(result);
1285
1752
  break;
1286
1753
  case "release":
1287
1754
  handler$1();
1288
1755
  break;
1756
+ case "preview":
1757
+ await handler$2(result);
1758
+ break;
1289
1759
  case "status":
1290
1760
  await handler(result);
1291
1761
  break;
@@ -1298,7 +1768,7 @@ switch (result.command) {
1298
1768
  case "pages.publish":
1299
1769
  case "pages.unpublish":
1300
1770
  case "pages.discard-changes":
1301
- await handler$2(result);
1771
+ await handler$3(result);
1302
1772
  break;
1303
1773
  case "blocks.types":
1304
1774
  case "blocks.describe":
@@ -1308,15 +1778,25 @@ switch (result.command) {
1308
1778
  case "blocks.edit":
1309
1779
  case "blocks.move":
1310
1780
  case "blocks.delete":
1781
+ await handler$11(result);
1782
+ break;
1783
+ case "comments.list":
1784
+ case "comments.resolve":
1785
+ await handler$10(result);
1786
+ break;
1787
+ case "files.upload":
1788
+ case "files.update":
1789
+ case "files.get":
1790
+ case "files.list":
1311
1791
  await handler$8(result);
1312
1792
  break;
1313
1793
  case "layouts.list":
1314
- await handler$5(result);
1794
+ await handler$6(result);
1315
1795
  break;
1316
1796
  case "env.check":
1317
1797
  case "env.push":
1318
1798
  case "env.pull":
1319
- await handler$7(result);
1799
+ await handler$9(result);
1320
1800
  break;
1321
1801
  }
1322
1802
  //#endregion