@camox/cli 0.43.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## Runtime lookup and monorepos
4
4
 
5
5
  Runtime-backed commands (`status`, `pages`, `blocks`, `layouts`, `env`, `files`,
6
- and `logout`) accept `--cwd PATH`:
6
+ `preview`, and `logout`) accept `--cwd PATH`:
7
7
 
8
8
  ```sh
9
9
  camox pages list --cwd apps/site
@@ -22,6 +22,27 @@ resolve against the shell's current directory. Missing or non-directory paths fa
22
22
  instead of falling back to the shell's directory. Environment selection is unchanged:
23
23
  your development environment by default, with explicit `--production` where supported.
24
24
 
25
+ ## Authenticated local draft preview
26
+
27
+ CLI login does not sign in a fresh browser. To verify draft writes without publishing:
28
+
29
+ ```sh
30
+ camox preview --url http://localhost:3000/about --json
31
+ camox preview --cwd apps/site --url http://localhost:3000/about --json
32
+ ```
33
+
34
+ Open the returned `url` in the browser you use for verification. Wait for
35
+ `[data-camox-preview="ready"]`, which identifies the project and developer
36
+ environment after authenticated draft rendering. Errors appear as
37
+ `[data-camox-preview="error"]`; expired or consumed links require a fresh command.
38
+ The CLI and SDK must both support the preview handoff.
39
+
40
+ The URL transfers your signed-in session: treat it as a credential, do not share
41
+ or commit it, and use it within three minutes. Only HTTP(S) loopback URLs are
42
+ accepted. The destination must match the CLI's project, backend, and developer
43
+ environment. There is no production mode or automatic default-browser opening.
44
+ The local dev server must already be running.
45
+
25
46
  ## Page SEO
26
47
 
27
48
  Use `pages get` to read metadata and `pages update` to edit it:
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ //#region src/lib/auth-core.ts
6
+ function globalAuthFile() {
7
+ return path.join(os.homedir(), ".camox", "auth.json");
8
+ }
9
+ function localAuthFile(cwd) {
10
+ let directory = path.resolve(cwd);
11
+ while (true) {
12
+ if (fs.existsSync(path.join(directory, ".git"))) {
13
+ const file = path.join(directory, ".camox", "auth.json");
14
+ return fs.existsSync(file) ? file : null;
15
+ }
16
+ const parent = path.dirname(directory);
17
+ if (parent === directory) return null;
18
+ directory = parent;
19
+ }
20
+ }
21
+ function normalizeUrl(url) {
22
+ return url.replace(/\/+$/, "");
23
+ }
24
+ function readAllTokens(file) {
25
+ if (!fs.existsSync(file)) return {};
26
+ const entries = JSON.parse(fs.readFileSync(file, "utf-8"));
27
+ if (!entries || typeof entries !== "object" || Array.isArray(entries)) throw new Error(`Invalid Camox credentials file: ${file}`);
28
+ return entries;
29
+ }
30
+ function writeAllTokens(file, tokens) {
31
+ fs.mkdirSync(path.dirname(file), { recursive: true });
32
+ fs.writeFileSync(file, JSON.stringify(tokens, null, 2), { mode: 384 });
33
+ fs.chmodSync(file, 384);
34
+ }
35
+ function credentialFile(authenticationUrl, cwd) {
36
+ const local = localAuthFile(cwd);
37
+ if (local && Object.hasOwn(readAllTokens(local), normalizeUrl(authenticationUrl))) return local;
38
+ return globalAuthFile();
39
+ }
40
+ /** Look up stored credentials for a specific Camox authentication backend. */
41
+ function readAuthTokenForUrl(authenticationUrl, cwd = process.cwd()) {
42
+ const entry = readAllTokens(credentialFile(authenticationUrl, cwd))[normalizeUrl(authenticationUrl)];
43
+ if (entry?.token && entry?.name && typeof entry.email === "string") return entry;
44
+ return null;
45
+ }
46
+ function writeAuthTokenForUrl(authenticationUrl, token, cwd = process.cwd()) {
47
+ const file = credentialFile(authenticationUrl, cwd);
48
+ const tokens = readAllTokens(file);
49
+ tokens[normalizeUrl(authenticationUrl)] = token;
50
+ writeAllTokens(file, tokens);
51
+ }
52
+ function removeAuthTokenForUrl(authenticationUrl, cwd = process.cwd()) {
53
+ const file = credentialFile(authenticationUrl, cwd);
54
+ const tokens = readAllTokens(file);
55
+ if (file !== globalAuthFile()) {
56
+ tokens[normalizeUrl(authenticationUrl)] = null;
57
+ writeAllTokens(file, tokens);
58
+ return;
59
+ }
60
+ delete tokens[normalizeUrl(authenticationUrl)];
61
+ if (Object.keys(tokens).length > 0) {
62
+ writeAllTokens(file, tokens);
63
+ return;
64
+ }
65
+ fs.rmSync(file, { force: true });
66
+ }
67
+ async function verifyOneTimeToken(apiUrl, token) {
68
+ const res = await fetch(`${normalizeUrl(apiUrl)}/api/auth/one-time-token/verify`, {
69
+ method: "POST",
70
+ headers: { "Content-Type": "application/json" },
71
+ body: JSON.stringify({ token })
72
+ });
73
+ if (!res.ok) throw new Error(`OTT verification failed: ${res.status}`);
74
+ const data = await res.json();
75
+ const user = data.user;
76
+ if (!user?.name) throw new Error("No user info in verification response");
77
+ const sessionToken = data.session?.token;
78
+ if (!sessionToken) throw new Error("No session token in verification response");
79
+ return {
80
+ name: user.name,
81
+ email: user.email ?? "",
82
+ token: sessionToken
83
+ };
84
+ }
85
+ //#endregion
86
+ export { writeAuthTokenForUrl as a, verifyOneTimeToken as i, readAuthTokenForUrl as n, removeAuthTokenForUrl as r, normalizeUrl as t };
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";
@@ -302,7 +302,7 @@ async function resolveCommandContext(opts) {
302
302
  throw err;
303
303
  }
304
304
  const slug = opts.projectFlag?.trim() || process.env.CAMOX_PROJECT?.trim() || runtime.projectSlug;
305
- const token = readAuthTokenForUrl(runtime.authenticationUrl);
305
+ const token = readAuthTokenForUrl(runtime.authenticationUrl, opts.cwd);
306
306
  if (!token) return fail$1({
307
307
  code: "NOT_AUTHENTICATED",
308
308
  message: `No stored credentials for ${runtime.authenticationUrl}. Run \`camox login\` against this backend first.`
@@ -343,7 +343,7 @@ const projectFlag$3 = optional(option("--project", string({ metavar: "SLUG" })))
343
343
  const jsonFlag$3 = option("--json");
344
344
  const productionFlag$2 = option("--production");
345
345
  const liveFlag$1 = option("--live");
346
- const parser$9 = command("blocks", or(command("types", object({
346
+ const parser$11 = command("blocks", or(command("types", object({
347
347
  command: constant("blocks.types"),
348
348
  cwd: cwdFlag,
349
349
  project: projectFlag$3,
@@ -442,7 +442,7 @@ function collectPositioningArgs(args) {
442
442
  toolArgs
443
443
  };
444
444
  }
445
- async function handler$9(args) {
445
+ async function handler$11(args) {
446
446
  const outputMode = args.json ? "json" : "auto";
447
447
  const cwd = args.cwd;
448
448
  const projectFlag = args.project;
@@ -572,11 +572,48 @@ async function handler$9(args) {
572
572
  }
573
573
  }
574
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
575
612
  //#region src/commands/env.ts
576
613
  const projectFlag$2 = optional(option("--project", string({ metavar: "SLUG" })));
577
614
  const jsonFlag$2 = option("--json");
578
615
  const yesFlag = option("--yes", "-y");
579
- const parser$8 = command("env", or(command("check", object({
616
+ const parser$9 = command("env", or(command("check", object({
580
617
  command: constant("env.check"),
581
618
  cwd: cwdFlag,
582
619
  project: projectFlag$2,
@@ -594,7 +631,7 @@ const parser$8 = command("env", or(command("check", object({
594
631
  project: projectFlag$2,
595
632
  json: jsonFlag$2
596
633
  }))));
597
- async function handler$8(args) {
634
+ async function handler$9(args) {
598
635
  const outputMode = args.json ? "json" : "auto";
599
636
  const cwd = args.cwd;
600
637
  const projectFlag = args.project;
@@ -795,7 +832,7 @@ const metadata = {
795
832
  alt: optional(option("--alt", string({ metavar: "TEXT" }), { description: message`Alt text (including an empty string). Disables automatic metadata.` })),
796
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.` }))
797
834
  };
798
- const parser$7 = command("files", or(command("upload", object({
835
+ const parser$8 = command("files", or(command("upload", object({
799
836
  command: constant("files.upload"),
800
837
  file: optional(option("--file", string({ metavar: "PATH" }), { description: message`Local file to upload. Pass exactly one of --file or --url.` })),
801
838
  url: optional(option("--url", string({ metavar: "URL" }), { description: message`HTTP(S) URL to download and copy into Camox, not an external reference.` })),
@@ -824,7 +861,7 @@ const parser$7 = command("files", or(command("upload", object({
824
861
  command: constant("files.list"),
825
862
  ...common
826
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.` });
827
- async function handler$7(args) {
864
+ async function handler$8(args) {
828
865
  const outputMode = args.json ? "json" : "auto";
829
866
  const options = {
830
867
  cwd: args.cwd,
@@ -959,8 +996,8 @@ function copyDir(src, dest, replacements) {
959
996
  }
960
997
  //#endregion
961
998
  //#region src/commands/init.ts
962
- const parser$6 = command("init", object({ command: constant("init") }));
963
- const handler$6 = init;
999
+ const parser$7 = command("init", object({ command: constant("init") }));
1000
+ const handler$7 = init;
964
1001
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
965
1002
  const ownPkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, "..", "package.json"), "utf-8"));
966
1003
  const PNPM_WORKSPACE = `allowBuilds:
@@ -1232,14 +1269,14 @@ src/routeTree.gen.ts
1232
1269
  const projectFlag$1 = optional(option("--project", string({ metavar: "SLUG" })));
1233
1270
  const jsonFlag$1 = option("--json");
1234
1271
  const productionFlag$1 = option("--production");
1235
- const parser$5 = command("layouts", or(command("list", object({
1272
+ const parser$6 = command("layouts", or(command("list", object({
1236
1273
  command: constant("layouts.list"),
1237
1274
  cwd: cwdFlag,
1238
1275
  project: projectFlag$1,
1239
1276
  production: productionFlag$1,
1240
1277
  json: jsonFlag$1
1241
1278
  }))));
1242
- async function handler$5(args) {
1279
+ async function handler$6(args) {
1243
1280
  const outputMode = args.json ? "json" : "auto";
1244
1281
  const cwd = args.cwd;
1245
1282
  const projectFlag = args.project;
@@ -1257,8 +1294,8 @@ async function handler$5(args) {
1257
1294
  }
1258
1295
  //#endregion
1259
1296
  //#region src/commands/login.ts
1260
- const parser$4 = command("login", object({ command: constant("login") }));
1261
- const handler$4 = login;
1297
+ const parser$5 = command("login", object({ command: constant("login") }));
1298
+ const handler$5 = login;
1262
1299
  async function login() {
1263
1300
  p.intro("camox login");
1264
1301
  try {
@@ -1272,11 +1309,11 @@ async function login() {
1272
1309
  }
1273
1310
  //#endregion
1274
1311
  //#region src/commands/logout.ts
1275
- const parser$3 = command("logout", object({
1312
+ const parser$4 = command("logout", object({
1276
1313
  command: constant("logout"),
1277
1314
  cwd: cwdFlag
1278
1315
  }));
1279
- const handler$3 = logout;
1316
+ const handler$4 = logout;
1280
1317
  function logout(args = {}) {
1281
1318
  p.intro("camox logout");
1282
1319
  let authenticationUrl = null;
@@ -1289,12 +1326,12 @@ function logout(args = {}) {
1289
1326
  }
1290
1327
  if (!(error instanceof RuntimeNotFoundError)) throw error;
1291
1328
  }
1292
- const token = authenticationUrl ? readAuthTokenForUrl(authenticationUrl) : readAuthToken();
1329
+ const token = authenticationUrl ? readAuthTokenForUrl(authenticationUrl, args.cwd) : readAuthToken();
1293
1330
  if (!token) {
1294
1331
  log.error("Not logged in.");
1295
1332
  return;
1296
1333
  }
1297
- if (authenticationUrl) removeAuthTokenForUrl(authenticationUrl);
1334
+ if (authenticationUrl) removeAuthTokenForUrl(authenticationUrl, args.cwd);
1298
1335
  else removeAuthToken();
1299
1336
  p.log.success(`Logged out from ${token.name}.`);
1300
1337
  }
@@ -1349,7 +1386,7 @@ const discardChanges = command("discard-changes", object({
1349
1386
  production: productionFlag,
1350
1387
  json: jsonFlag
1351
1388
  }));
1352
- const parser$2 = command("pages", or(list, get, command("create", object({
1389
+ const parser$3 = command("pages", or(list, get, command("create", object({
1353
1390
  command: constant("pages.create"),
1354
1391
  nickname: optional(option("--nickname", string({ metavar: "TEXT" }))),
1355
1392
  pathSegment: option("--path-segment", string({ metavar: "SEGMENT" })),
@@ -1392,7 +1429,7 @@ const parser$2 = command("pages", or(list, get, command("create", object({
1392
1429
  production: productionFlag,
1393
1430
  json: jsonFlag
1394
1431
  })), publish, unpublish, discardChanges));
1395
- async function handler$2(args) {
1432
+ async function handler$3(args) {
1396
1433
  const outputMode = args.json ? "json" : "auto";
1397
1434
  const cwd = args.cwd;
1398
1435
  const projectFlag = args.project;
@@ -1556,6 +1593,61 @@ async function handler$2(args) {
1556
1593
  }
1557
1594
  }
1558
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
1559
1651
  //#region src/commands/release.ts
1560
1652
  const parser$1 = command("release", object({ command: constant("release") }));
1561
1653
  function handler$1() {
@@ -1612,7 +1704,7 @@ async function handler(args) {
1612
1704
  }
1613
1705
  throw err;
1614
1706
  }
1615
- const token = readAuthTokenForUrl(runtime.authenticationUrl);
1707
+ const token = readAuthTokenForUrl(runtime.authenticationUrl, args.cwd);
1616
1708
  const environmentName = args.production ? "production" : token?.email ? `dev:${token.email}` : null;
1617
1709
  const status = {
1618
1710
  projectSlug: runtime.projectSlug,
@@ -1642,7 +1734,7 @@ async function handler(args) {
1642
1734
  //#endregion
1643
1735
  //#region src/index.ts
1644
1736
  const result = runSync(defineProgram({
1645
- parser: or(parser$6, parser$4, parser$3, parser$1, parser, parser$2, parser$9, parser$5, parser$8, 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),
1646
1738
  metadata: {
1647
1739
  name: "camox",
1648
1740
  brief: message`Camox CLI`
@@ -1650,17 +1742,20 @@ const result = runSync(defineProgram({
1650
1742
  }), { help: "both" });
1651
1743
  switch (result.command) {
1652
1744
  case "init":
1653
- await handler$6();
1745
+ await handler$7();
1654
1746
  break;
1655
1747
  case "login":
1656
- await handler$4();
1748
+ await handler$5();
1657
1749
  break;
1658
1750
  case "logout":
1659
- handler$3(result);
1751
+ handler$4(result);
1660
1752
  break;
1661
1753
  case "release":
1662
1754
  handler$1();
1663
1755
  break;
1756
+ case "preview":
1757
+ await handler$2(result);
1758
+ break;
1664
1759
  case "status":
1665
1760
  await handler(result);
1666
1761
  break;
@@ -1673,7 +1768,7 @@ switch (result.command) {
1673
1768
  case "pages.publish":
1674
1769
  case "pages.unpublish":
1675
1770
  case "pages.discard-changes":
1676
- await handler$2(result);
1771
+ await handler$3(result);
1677
1772
  break;
1678
1773
  case "blocks.types":
1679
1774
  case "blocks.describe":
@@ -1683,21 +1778,25 @@ switch (result.command) {
1683
1778
  case "blocks.edit":
1684
1779
  case "blocks.move":
1685
1780
  case "blocks.delete":
1686
- await handler$9(result);
1781
+ await handler$11(result);
1782
+ break;
1783
+ case "comments.list":
1784
+ case "comments.resolve":
1785
+ await handler$10(result);
1687
1786
  break;
1688
1787
  case "files.upload":
1689
1788
  case "files.update":
1690
1789
  case "files.get":
1691
1790
  case "files.list":
1692
- await handler$7(result);
1791
+ await handler$8(result);
1693
1792
  break;
1694
1793
  case "layouts.list":
1695
- await handler$5(result);
1794
+ await handler$6(result);
1696
1795
  break;
1697
1796
  case "env.check":
1698
1797
  case "env.push":
1699
1798
  case "env.pull":
1700
- await handler$8(result);
1799
+ await handler$9(result);
1701
1800
  break;
1702
1801
  }
1703
1802
  //#endregion
@@ -7,9 +7,9 @@ interface AuthToken {
7
7
  }
8
8
  declare function normalizeUrl(url: string): string;
9
9
  /** Look up stored credentials for a specific Camox authentication backend. */
10
- declare function readAuthTokenForUrl(authenticationUrl: string): AuthToken | null;
11
- declare function writeAuthTokenForUrl(authenticationUrl: string, token: AuthToken): void;
12
- declare function removeAuthTokenForUrl(authenticationUrl: string): void;
10
+ declare function readAuthTokenForUrl(authenticationUrl: string, cwd?: string): AuthToken | null;
11
+ declare function writeAuthTokenForUrl(authenticationUrl: string, token: AuthToken, cwd?: string): void;
12
+ declare function removeAuthTokenForUrl(authenticationUrl: string, cwd?: string): void;
13
13
  declare function verifyOneTimeToken(apiUrl: string, token: string): Promise<AuthToken>;
14
14
  //#endregion
15
15
  export { AuthToken, normalizeUrl, readAuthTokenForUrl, removeAuthTokenForUrl, verifyOneTimeToken, writeAuthTokenForUrl };
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { a as writeAuthTokenForUrl, i as verifyOneTimeToken, n as readAuthTokenForUrl, r as removeAuthTokenForUrl, t as normalizeUrl } from "../auth-core-cCuk5aZy.mjs";
2
+ import { a as writeAuthTokenForUrl, i as verifyOneTimeToken, n as readAuthTokenForUrl, r as removeAuthTokenForUrl, t as normalizeUrl } from "../auth-core-4Jdf5q-u.mjs";
3
3
  export { normalizeUrl, readAuthTokenForUrl, removeAuthTokenForUrl, verifyOneTimeToken, writeAuthTokenForUrl };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camox/cli",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "camox": "./dist/index.mjs"
@@ -31,7 +31,7 @@
31
31
  "@types/node": "^24.12.4",
32
32
  "@typescript/native-preview": "7.0.0-dev.20260412.1",
33
33
  "vite-plus": "^0.1.21",
34
- "@camox/api-contract": "0.43.0"
34
+ "@camox/api-contract": "0.44.0"
35
35
  },
36
36
  "nx": {
37
37
  "tags": [
@@ -14,8 +14,8 @@ export default defineConfig({
14
14
  projectSlug: "camox-template-default-01", // camox-cli:replace-slug
15
15
  // camox-cli:dev-only-start
16
16
  _internal: {
17
- authenticationUrl: "http://localhost:3274",
18
- apiUrl: "http://localhost:8787",
17
+ authenticationUrl: process.env.VITE_DASHBOARD_URL ?? "http://localhost:3274",
18
+ apiUrl: process.env.VITE_API_URL ?? "http://localhost:8787",
19
19
  },
20
20
  // camox-cli:dev-only-end
21
21
  }),
@@ -1,63 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- //#region src/lib/auth-core.ts
6
- const AUTH_DIR = path.join(os.homedir(), ".camox");
7
- const AUTH_FILE = path.join(AUTH_DIR, "auth.json");
8
- function normalizeUrl(url) {
9
- return url.replace(/\/+$/, "");
10
- }
11
- function readAllTokens() {
12
- try {
13
- return JSON.parse(fs.readFileSync(AUTH_FILE, "utf-8"));
14
- } catch {
15
- return {};
16
- }
17
- }
18
- function writeAllTokens(tokens) {
19
- fs.mkdirSync(AUTH_DIR, { recursive: true });
20
- fs.writeFileSync(AUTH_FILE, JSON.stringify(tokens, null, 2), { mode: 384 });
21
- }
22
- /** Look up stored credentials for a specific Camox authentication backend. */
23
- function readAuthTokenForUrl(authenticationUrl) {
24
- const entry = readAllTokens()[normalizeUrl(authenticationUrl)];
25
- if (entry?.token && entry?.name && typeof entry.email === "string") return entry;
26
- return null;
27
- }
28
- function writeAuthTokenForUrl(authenticationUrl, token) {
29
- const tokens = readAllTokens();
30
- tokens[normalizeUrl(authenticationUrl)] = token;
31
- writeAllTokens(tokens);
32
- }
33
- function removeAuthTokenForUrl(authenticationUrl) {
34
- const tokens = readAllTokens();
35
- delete tokens[normalizeUrl(authenticationUrl)];
36
- if (Object.keys(tokens).length > 0) {
37
- writeAllTokens(tokens);
38
- return;
39
- }
40
- try {
41
- fs.unlinkSync(AUTH_FILE);
42
- } catch {}
43
- }
44
- async function verifyOneTimeToken(apiUrl, token) {
45
- const res = await fetch(`${normalizeUrl(apiUrl)}/api/auth/one-time-token/verify`, {
46
- method: "POST",
47
- headers: { "Content-Type": "application/json" },
48
- body: JSON.stringify({ token })
49
- });
50
- if (!res.ok) throw new Error(`OTT verification failed: ${res.status}`);
51
- const data = await res.json();
52
- const user = data.user;
53
- if (!user?.name) throw new Error("No user info in verification response");
54
- const sessionToken = data.session?.token;
55
- if (!sessionToken) throw new Error("No session token in verification response");
56
- return {
57
- name: user.name,
58
- email: user.email ?? "",
59
- token: sessionToken
60
- };
61
- }
62
- //#endregion
63
- export { writeAuthTokenForUrl as a, verifyOneTimeToken as i, readAuthTokenForUrl as n, removeAuthTokenForUrl as r, normalizeUrl as t };