@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/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # Camox CLI
2
+
3
+ ## Runtime lookup and monorepos
4
+
5
+ Runtime-backed commands (`status`, `pages`, `blocks`, `layouts`, `env`, `files`,
6
+ `preview`, and `logout`) accept `--cwd PATH`:
7
+
8
+ ```sh
9
+ camox pages list --cwd apps/site
10
+ camox status --cwd apps/site
11
+ camox files upload --cwd apps/site --file ./hero.webp --alt "Product screenshot"
12
+ ```
13
+
14
+ The CLI searches for `node_modules/.camox/runtime.json` starting in that directory
15
+ and walking upward. Without `--cwd`, lookup starts in the shell's current directory.
16
+ There is no workspace or child-directory discovery. Run the app's dev server or
17
+ build once to generate its runtime file.
18
+
19
+ Relative `--cwd` paths resolve against the shell's current directory. This option
20
+ only affects runtime lookup: other paths, such as `--file ./hero.webp`, still
21
+ resolve against the shell's current directory. Missing or non-directory paths fail
22
+ instead of falling back to the shell's directory. Environment selection is unchanged:
23
+ your development environment by default, with explicit `--production` where supported.
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
+
46
+ ## Page SEO
47
+
48
+ Use `pages get` to read metadata and `pages update` to edit it:
49
+
50
+ ```sh
51
+ camox pages get --path /about --json
52
+ camox pages update --path /about --meta-title "About us" --meta-description "Meet the team."
53
+ camox pages update --id 42 --meta-description ""
54
+ camox pages update --id 42 --ai-seo on
55
+ camox pages update --id 42 --ai-seo off
56
+ camox pages get --path /about --live
57
+ camox pages publish --path /about
58
+ ```
59
+
60
+ - Updates require exactly one of `--id` or `--path` and at least one update field.
61
+ Omitted fields are preserved; empty strings clear metadata.
62
+ - Setting either metadata field disables automatic SEO for **both** fields.
63
+ Manual metadata cannot be combined with `--ai-seo on`.
64
+ - `--ai-seo off` preserves current metadata. `--ai-seo on` schedules asynchronous
65
+ generation; the returned page contains current values, not necessarily generated
66
+ values. Use `pages get` to inspect them later.
67
+ - Changes affect the draft only. Publish separately to update live metadata.
68
+ `pages get --live` reads the published snapshot; without it, reads use the draft.
69
+ - Commands support `--project SLUG`, `--production`, and `--json`, with the usual
70
+ development-environment default and structured JSON output/errors. `--production`
71
+ selects the environment; it does not publish the draft.
72
+
73
+ See `camox pages update --help` for options and examples.
74
+
75
+ ## Media
76
+
77
+ Run `camox login` and start your project's dev server (or build once) so the CLI
78
+ can discover the project's runtime configuration.
79
+
80
+ ```sh
81
+ # Upload a local file with manual alt text.
82
+ camox files upload --file ./hero.webp --alt "Product screenshot" --json
83
+
84
+ # Download a remote file and store a copy in Camox.
85
+ camox files upload --url https://example.com/hero.webp --alt "Product screenshot"
86
+
87
+ # Optionally override the uploaded filename.
88
+ camox files upload --url https://example.com/download --filename hero.webp --ai-metadata off
89
+
90
+ # Inspect the returned ID and URL; use the ID in your block's media field.
91
+ camox files list
92
+ camox files get --id 123
93
+
94
+ camox files update --id 123 --filename hero.webp
95
+ camox files update --id 123 --alt "Updated description"
96
+ camox files update --id 123 --alt ""
97
+ camox files update --id 123 --ai-metadata on
98
+
99
+ # Replace the underlying file everywhere it is referenced, preserving its ID.
100
+ camox files update --id 123 --file ./new-hero.webp
101
+ camox files update --id 123 --url https://example.com/new-hero.webp
102
+
103
+ # Replace content and override metadata together.
104
+ camox files update --id 123 --file ./new-hero.webp --filename hero.webp --alt "New screenshot"
105
+ ```
106
+
107
+ - Upload requires exactly one of `--file` or `--url`. URLs are copied, not linked.
108
+ - `--alt` disables AI metadata, including when its value is an empty string.
109
+ It cannot be combined with `--ai-metadata on`. Manual edits are protected from
110
+ already-running AI jobs.
111
+ - Without metadata flags, raster-image uploads use automatic metadata. Other
112
+ file types do not. AI may update both the filename and alt text asynchronously.
113
+ - `--ai-metadata on` schedules generation for raster images; success does not mean
114
+ generation has completed. Use `files get` to inspect the current record.
115
+ - Update requires at least one of `--file`, `--url`, `--filename`, `--alt` or
116
+ `--ai-metadata`. `--file` and `--url` are mutually exclusive; without either,
117
+ only metadata changes. Renaming alone preserves the file ID, URL and AI setting.
118
+ - Replacing content preserves the file ID and all `_fileId` references in the
119
+ selected environment. The URL changes to avoid stale caches. Direct URLs in
120
+ stored block/repeatable-item content are migrated within that environment;
121
+ URLs hardcoded outside Camox cannot be rewritten.
122
+ - Replacements preserve omitted alt text and AI settings. The filename defaults
123
+ to the new source filename; `--filename` overrides it. Binary metadata and
124
+ explicit metadata changes are saved together. If AI remains enabled, raster
125
+ replacements schedule generation on the original file ID; other types skip
126
+ generation. Disable AI to prevent future automatic renaming or alt changes.
127
+ - Files are shared assets, not page drafts: replacing one affects every reference
128
+ to it in the selected environment. No page publish step is required.
129
+ - Failed replacements leave the original file unchanged and remove the unused
130
+ uploaded binary. After a network timeout, use `files get --id ID` before retrying
131
+ because the server may already have completed the operation.
132
+ - All commands support `--project SLUG`, `--production`, and `--json`. The default
133
+ environment is `dev:<your-email>`, not production. IDs are scoped to the selected
134
+ project and environment.
135
+ - Uploads return the stored file record, including `id`, `url`, `mimeType`, `alt`,
136
+ and `aiMetadataEnabled`. Output is JSON; errors are structured JSON on stderr.
137
+ - Files are limited to 100 MiB. URL downloads allow at most five redirects and
138
+ have a two-minute timeout; uploads have a separate two-minute timeout.
139
+ Downloads stream through a temporary file that is removed afterward.
140
+ - Only HTTP(S) URLs without embedded credentials are supported. Signed query
141
+ URLs work and are not included in download errors. Camox credentials are never
142
+ sent to the source URL. Remote authentication flags and batch uploads are not
143
+ supported.
144
+
145
+ See `camox files --help`, `camox files upload --help`, and
146
+ `camox files update --help` for command options.
@@ -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 };