carboncanvas-cli 0.1.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/CONTRACT.md ADDED
@@ -0,0 +1,94 @@
1
+ # Publish contract (`POST /api/publish`)
2
+
3
+ This file is the **source of truth** for the hosted-publish endpoint, the way
4
+ `src/CommentLayer/networkClient.ts` is for the comments API. The CLI
5
+ (`carboncanvas-cli`) is built to this exact shape; the server half must match it
6
+ byte-for-byte. **Change both sides together.**
7
+
8
+ Client implementation: `cli/src/upload.ts` (`publishBundle`, `buildPublishForm`).
9
+ Token verification for `login`: `GET /api/entitlements` with
10
+ `Authorization: Bearer <token>` — 200 means the token is a live creator session.
11
+
12
+ ---
13
+
14
+ ## Request
15
+
16
+ ```
17
+ POST {serviceUrl}/api/publish
18
+ Authorization: Bearer <token>
19
+ Content-Type: multipart/form-data
20
+ ```
21
+
22
+ Multipart parts:
23
+
24
+ | part | type | contents |
25
+ |----------|---------------------|----------|
26
+ | `bundle` | file, `application/gzip`, filename `bundle.tar.gz` | a `.tar.gz` of the built **static** dist directory (entries rooted at `.`, so `index.html` is at the archive root) |
27
+ | `meta` | text (JSON string) | the `PublishMeta` object below |
28
+
29
+ ### `meta` (JSON string)
30
+
31
+ ```jsonc
32
+ {
33
+ "canvasId": "cnv_…", // optional — target an EXISTING canvas (the baked id or --canvas)
34
+ "slug": "my-app", // optional — requested slug for the review link (--name)
35
+ "label": "main", // optional — version label (git branch or --label; default "preview")
36
+ "branch": "main", // optional — git branch (auto-filled)
37
+ "commit": "a1b2c3d", // optional — git short SHA (auto-filled)
38
+ "fresh": false // optional — true = mint a THROWAWAY canvas, ignore canvasId (--fresh)
39
+ }
40
+ ```
41
+
42
+ Notes for the server:
43
+ - When `fresh` is true, the CLI omits `canvasId` — provision a throwaway canvas.
44
+ - When `fresh` is falsy and `canvasId` is present, associate the deploy with that
45
+ canvas (idempotent re-publish → new deploy, same canvas, comments re-pin).
46
+ - When neither is present, auto-provision (`ensureCanvas`) so the deploy isn't
47
+ orphaned.
48
+ - The bundle's `index.html` already carries `<meta name="cc-deploy"
49
+ content="<label>@<shortsha>">` (the CLI stamps it into the packaged copy). The
50
+ server does not need to inject it.
51
+ - `bundle.tar.gz` unpacks to a static site: `index.html` at the root, no server
52
+ manifest (the CLI refuses those client-side, but the server re-validates).
53
+
54
+ ---
55
+
56
+ ## Responses
57
+
58
+ ### 200 OK — `PublishResult`
59
+
60
+ ```jsonc
61
+ {
62
+ "canvasId": "cnv_…", // the canvas the deploy is bound to
63
+ "deployId": "dep_…", // this immutable deploy
64
+ "url": "https://my-app.<host>", // the review link (slug root alias)
65
+ "dashboardUrl": "https://<host>/dashboard/…", // where to manage it
66
+ "expiresAt": "2026-08-06T00:00:00.000Z", // ISO string, or null if it never expires
67
+ "slug": "my-app" // the resolved slug
68
+ }
69
+ ```
70
+
71
+ ### Error responses — `{ error, message }`
72
+
73
+ | status | meaning | CLI behavior |
74
+ |--------|---------|--------------|
75
+ | `401` | invalid / expired token | tells the user to run `carboncanvas login` |
76
+ | `402` | `publish` capability not entitled | surfaces `message` + an upgrade hint |
77
+ | `400` | validation failed (size cap, bad bundle) | surfaces `message` |
78
+ | `413` | payload too large | surfaces `message` |
79
+ | `503` | publish / CDN provider not configured server-side | surfaces `message` |
80
+
81
+ All error bodies use the foundation's `{ error: string, message: string }` shape.
82
+ The CLI reads `message` for the human-facing line; `error` is a stable machine code.
83
+
84
+ ---
85
+
86
+ ## Client-side pre-checks (server MUST re-validate)
87
+
88
+ The CLI enforces these before upload; the server treats them as advisory and
89
+ re-checks (a hand-crafted request can skip the CLI):
90
+
91
+ - **Size caps:** ~50 MB packed / 2,000 files. Over → the CLI refuses locally; the
92
+ server should still enforce (→ 400 / 413).
93
+ - **Static-ness:** `index.html` at root, no server manifest (`.next/standalone`,
94
+ `.output/server`, `functions/`, `server.js`/`server.mjs`, `_worker.js`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Roitch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # carboncanvas-cli
2
+
3
+ The Carbon Canvas CLI. Build your prototype locally and publish the static output
4
+ to a hosted, commentable **review link** — one verb, no deploy platform to set up.
5
+
6
+ The bin name is **`carboncanvas`**, so you run `carboncanvas publish`. This is a
7
+ separate package from the `carboncanvas` library (which stays dependency-free);
8
+ the CLI carries the upload/tar/auth deps.
9
+
10
+ ```bash
11
+ # one-off (no install)
12
+ npx carboncanvas-cli login
13
+ npx carboncanvas-cli publish
14
+
15
+ # or install as a dev dep / globally, then use the `carboncanvas` bin
16
+ npm i -D carboncanvas-cli
17
+ npx carboncanvas publish
18
+ ```
19
+
20
+ ## `carboncanvas login`
21
+
22
+ Verifies a creator API token (from the dashboard → Settings → API tokens) against
23
+ the backend and stores it in `~/.carboncanvas/config.json` (mode `0600`), keyed by
24
+ backend URL.
25
+
26
+ ```bash
27
+ carboncanvas login # prompts for a token (hidden input)
28
+ carboncanvas login --token cc_xxx # non-interactive
29
+ ```
30
+
31
+ - `CARBON_TOKEN` overrides the stored token at use time.
32
+ - `--service-url <url>` / `CARBON_SERVICE_URL` selects the backend
33
+ (default `https://quilt-production-65f6.up.railway.app`).
34
+ - `carboncanvas logout` removes the stored token; `carboncanvas whoami` checks it.
35
+
36
+ ## `carboncanvas publish`
37
+
38
+ Runs, in order:
39
+
40
+ 1. **Detect** the project (package manager from lockfile, framework + build script
41
+ from `package.json`).
42
+ 2. **Build** with the detected script — streamed to your terminal. A build failure
43
+ stops here; we never upload a broken build. (Skip with `--dist`.)
44
+ 3. **Validate it's static** — refuses loudly if the output needs a server
45
+ (`.next/standalone`, `.output/server`, `functions/`, `server.js`, …).
46
+ 4. **Canvas-config check** — greps the bundle for a `cnv_` canvasId and warns if
47
+ it's missing (the canvas would be view-only). It never rewrites your bundle.
48
+ 5. **Name-preservation check** — warns if the build minified your component names
49
+ (the Inspector would show `t`/`Bn` instead of real names). Fix by spreading
50
+ `carbonPreset()` from `carboncanvas/vite` into your `vite.config` (it sets
51
+ `esbuild: { keepNames: true }`), then rebuild.
52
+ 6. **Stamp** a `<meta name="cc-deploy" content="<label>@<shortsha>">` into the
53
+ packaged copy of `index.html` (never your file on disk).
54
+ 7. **Package** the dist to a `.tar.gz` (≤ ~50 MB / 2,000 files).
55
+ 8. **Upload** to `POST /api/publish` and print the review link + dashboard URL.
56
+
57
+ ### Flags
58
+
59
+ | flag | meaning |
60
+ |------|---------|
61
+ | `--dir <dir>` | project directory (default: cwd) |
62
+ | `--dist <dir>` | skip the build; publish this pre-built static dir |
63
+ | `--canvas <id>` | target an existing canvas instead of the baked one |
64
+ | `--fresh` | publish to a throwaway canvas (feedback won't touch main) |
65
+ | `--name <slug>` | request a specific slug for the review link |
66
+ | `--label <label>` | version label (default: git branch, else `preview`) |
67
+ | `--service-url` | backend URL (env `CARBON_SERVICE_URL`) |
68
+ | `--token` | token override (env `CARBON_TOKEN`) |
69
+
70
+ ## The static-only limitation (and the alternative)
71
+
72
+ `publish` hosts **static builds** (Vite / CRA / Astro-static / SvelteKit static
73
+ adapter) and apps that talk to a hosted backend (Supabase/Firebase). It does
74
+ **not** run a server for you.
75
+
76
+ If your app needs real server routes / SSR / a local DB, that's fine — **deploy it
77
+ yourself** (Vercel, Netlify, Railway, your own host) and point Carbon Canvas at the
78
+ live URL. Bring-your-own-deployment is the default on-ramp; `publish` is the
79
+ opt-in convenience for the local-agent workflow that has code but no URL.
80
+
81
+ ## Published links are public (Phase 1)
82
+
83
+ Anyone with the URL can view a published prototype — the link is **not** gated by
84
+ the canvas's access policy (an invite-only canvas restricts its *comments*, not
85
+ the hosted pages). Don't publish sensitive prototypes yet; auth-gated private
86
+ links (`--private`) arrive with viewer-auth Phase 2's edge gate.
87
+
88
+ ## Requirements
89
+
90
+ Node ≥ 18 (uses built-in `fetch` / `FormData` / `Blob`). Only runtime dep: `tar`.
package/dist/build.js ADDED
@@ -0,0 +1,32 @@
1
+ // Run the author's build script in their environment, streaming output to our
2
+ // stderr. Build failure rejects with a non-zero exit — the agent fixes it
3
+ // locally; we never upload a broken build.
4
+ import { spawn } from 'node:child_process';
5
+ export class BuildError extends Error {
6
+ code;
7
+ constructor(code, command) {
8
+ super(`Build command failed (exit ${code}): ${command}`);
9
+ this.name = 'BuildError';
10
+ this.code = code;
11
+ }
12
+ }
13
+ export function runBuild(command, cwd) {
14
+ return new Promise((resolve, reject) => {
15
+ const child = spawn(command, {
16
+ cwd,
17
+ shell: true,
18
+ stdio: ['inherit', 'inherit', 'inherit'],
19
+ // Author's env — build reads their .env, NODE_ENV, etc. as usual.
20
+ env: process.env,
21
+ });
22
+ child.on('error', (err) => reject(err));
23
+ child.on('close', (code) => {
24
+ const c = code ?? 1;
25
+ if (c === 0)
26
+ resolve({ code: 0 });
27
+ else
28
+ reject(new BuildError(c, command));
29
+ });
30
+ });
31
+ }
32
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,2CAA2C;AAE3C,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAM3C,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,IAAI,CAAS;IACb,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,8BAA8B,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,GAAW;IACnD,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAClD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE;YAC3B,GAAG;YACH,KAAK,EAAE,IAAI;YACX,KAAK,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;YACxC,kEAAkE;YAClE,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACxC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;YACpB,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;;gBAC7B,MAAM,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,19 @@
1
+ // Canvas-config check (plan amendment B.2 — publish injects NOTHING).
2
+ //
3
+ // The canvasId + serviceUrl are baked into the author's JSX at *their* build
4
+ // time. All we do is grep the built bundle for a `cnv_` id and warn if it's
5
+ // absent (the published canvas would have no comments). We NEVER rewrite or
6
+ // inject config into the bundle.
7
+ import { readBundleText } from './walk.js';
8
+ // canvasIds look like `cnv_` + url-safe chars (see server schema / networkClient).
9
+ const CANVAS_ID_RE = /cnv_[A-Za-z0-9_-]{4,}/;
10
+ export async function findCanvasId(distDir) {
11
+ const text = await readBundleText(distDir, ['.js', '.mjs', '.cjs', '.html']);
12
+ const m = text.match(CANVAS_ID_RE);
13
+ return { canvasId: m ? m[0] : null };
14
+ }
15
+ export const NO_CANVAS_ID_WARNING = 'No canvasId (cnv_…) found in the built bundle — the published canvas will be ' +
16
+ 'view-only (no comments). Register a canvas on the dashboard and add ' +
17
+ 'comments={{ canvasId, serviceUrl }} to your <DesignCanvas>, then rebuild. ' +
18
+ 'Publishing anyway.';
19
+ //# sourceMappingURL=canvasConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasConfig.js","sourceRoot":"","sources":["../src/canvasConfig.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,iCAAiC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,mFAAmF;AACnF,MAAM,YAAY,GAAG,uBAAuB,CAAC;AAM7C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAe;IAChD,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACnC,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAC/B,+EAA+E;IAC/E,sEAAsE;IACtE,4EAA4E;IAC5E,oBAAoB,CAAC"}
@@ -0,0 +1,16 @@
1
+ // Shared constants for the Carbon Canvas CLI.
2
+ //
3
+ // The prod backend URL is the same Railway service the comments client talks to
4
+ // (server/README.md: "running on Railway at https://quilt-production-65f6.up.railway.app").
5
+ // It can be overridden per-invocation with `--service-url` or `CARBON_SERVICE_URL`.
6
+ export const DEFAULT_SERVICE_URL = 'https://quilt-production-65f6.up.railway.app';
7
+ // The command name a user types (bin name), used in help/error text.
8
+ export const BIN_NAME = 'carboncanvas';
9
+ // Client-side size caps (the server re-validates — these mirror plan §6b/B.3).
10
+ export const MAX_BUNDLE_BYTES = 50 * 1024 * 1024; // ~50 MB packed source dir
11
+ export const MAX_BUNDLE_FILES = 2000;
12
+ // Where the paste-token config lives (mode 0600). Keyed by serviceUrl so an
13
+ // author with multiple backends (prod + self-hosted) keeps distinct tokens.
14
+ export const CONFIG_DIR_NAME = '.carboncanvas';
15
+ export const CONFIG_FILE_NAME = 'config.json';
16
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,gFAAgF;AAChF,4FAA4F;AAC5F,oFAAoF;AACpF,MAAM,CAAC,MAAM,mBAAmB,GAAG,8CAA8C,CAAC;AAElF,qEAAqE;AACrE,MAAM,CAAC,MAAM,QAAQ,GAAG,cAAc,CAAC;AAEvC,+EAA+E;AAC/E,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,2BAA2B;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAC;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC"}
package/dist/detect.js ADDED
@@ -0,0 +1,80 @@
1
+ // Project detection: package manager (from lockfile), framework (from deps),
2
+ // and the build script (from package.json). Pure & testable — takes a root dir.
3
+ import { promises as fs } from 'node:fs';
4
+ import * as path from 'node:path';
5
+ const LOCKFILES = [
6
+ ['bun.lockb', 'bun'],
7
+ ['bun.lock', 'bun'],
8
+ ['pnpm-lock.yaml', 'pnpm'],
9
+ ['yarn.lock', 'yarn'],
10
+ ['package-lock.json', 'npm'],
11
+ ];
12
+ async function exists(p) {
13
+ try {
14
+ await fs.access(p);
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ export async function inferPackageManager(root) {
22
+ for (const [file, pm] of LOCKFILES) {
23
+ if (await exists(path.join(root, file)))
24
+ return pm;
25
+ }
26
+ return 'npm'; // default happy path
27
+ }
28
+ export function detectFramework(pkg) {
29
+ const all = { ...pkg.dependencies, ...pkg.devDependencies };
30
+ if ('@sveltejs/kit' in all)
31
+ return 'sveltekit';
32
+ if ('astro' in all)
33
+ return 'astro';
34
+ if ('react-scripts' in all)
35
+ return 'cra';
36
+ if ('vite' in all)
37
+ return 'vite';
38
+ return 'unknown';
39
+ }
40
+ // Pick the build script. Prefer "build"; fall back to a script whose command
41
+ // mentions a known bundler. Returns the SCRIPT NAME (to run via the pm), not the
42
+ // command string.
43
+ export function detectBuildScript(pkg) {
44
+ const scripts = pkg.scripts ?? {};
45
+ if (scripts.build)
46
+ return 'build';
47
+ for (const [name, cmd] of Object.entries(scripts)) {
48
+ if (/\b(vite build|astro build|react-scripts build|svelte-kit build|tsc && vite)\b/.test(cmd)) {
49
+ return name;
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ // Build the shell command that runs a named npm script under the detected pm.
55
+ export function runScriptCommand(pm, script) {
56
+ switch (pm) {
57
+ case 'pnpm':
58
+ return `pnpm run ${script}`;
59
+ case 'yarn':
60
+ return `yarn ${script}`;
61
+ case 'bun':
62
+ return `bun run ${script}`;
63
+ case 'npm':
64
+ default:
65
+ return `npm run ${script}`;
66
+ }
67
+ }
68
+ export async function readPackageJson(root) {
69
+ const p = path.join(root, 'package.json');
70
+ const raw = await fs.readFile(p, 'utf8');
71
+ return JSON.parse(raw);
72
+ }
73
+ export async function detectProject(root) {
74
+ const packageJson = await readPackageJson(root);
75
+ const packageManager = await inferPackageManager(root);
76
+ const framework = detectFramework(packageJson);
77
+ const buildScript = detectBuildScript(packageJson);
78
+ return { root, packageJson, packageManager, framework, buildScript };
79
+ }
80
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
package/dist/errors.js ADDED
@@ -0,0 +1,11 @@
1
+ // A user-facing error the CLI shell prints (message only, no stack) before
2
+ // exiting non-zero. Distinct from unexpected errors, which print a stack.
3
+ export class UserError extends Error {
4
+ hint;
5
+ constructor(message, hint) {
6
+ super(message);
7
+ this.name = 'UserError';
8
+ this.hint = hint;
9
+ }
10
+ }
11
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAE1E,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClC,IAAI,CAAU;IACd,YAAY,OAAe,EAAE,IAAa;QACxC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
package/dist/index.js ADDED
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ // Carbon Canvas CLI — thin shell over the command modules. Arg parsing via
3
+ // node:util parseArgs (no commander dep).
4
+ import { parseArgs } from 'node:util';
5
+ import * as log from './logger.js';
6
+ import { UserError } from './errors.js';
7
+ import { PublishError } from './upload.js';
8
+ import { DEFAULT_SERVICE_URL, BIN_NAME } from './constants.js';
9
+ import { loginCommand } from './loginCommand.js';
10
+ import { publishCommand } from './publishCommand.js';
11
+ import { clearToken, storedToken } from './tokenStore.js';
12
+ import { verifyToken } from './verify.js';
13
+ import { resolveToken } from './tokenStore.js';
14
+ const VERSION = '0.0.0';
15
+ function resolveServiceUrl(flag) {
16
+ return (flag || process.env.CARBON_SERVICE_URL || DEFAULT_SERVICE_URL).replace(/\/+$/, '');
17
+ }
18
+ const HELP = `${BIN_NAME} — publish your prototype to a hosted, commentable review link.
19
+
20
+ USAGE
21
+ ${BIN_NAME} <command> [options]
22
+
23
+ COMMANDS
24
+ login Verify and store a creator API token (paste-token).
25
+ publish Build locally and upload the static output; prints a review link.
26
+ logout Remove the stored token for a backend.
27
+ whoami Show whether a stored/env token is valid.
28
+ help Show this help.
29
+
30
+ COMMON OPTIONS
31
+ --service-url <url> Backend to target (env CARBON_SERVICE_URL;
32
+ default ${DEFAULT_SERVICE_URL}).
33
+ --token <token> Use this token instead of the stored one (env CARBON_TOKEN).
34
+ -h, --help Show help.
35
+ -v, --version Show version.
36
+
37
+ publish OPTIONS
38
+ --dir <dir> Project directory (default: cwd).
39
+ --dist <dir> Skip the build; publish this pre-built static directory.
40
+ --canvas <id> Target an existing canvas (cnv_…) instead of the baked one.
41
+ --fresh Publish to a throwaway canvas (feedback won't touch main).
42
+ --name <slug> Request a specific slug for the review link.
43
+ --label <label> Version label (default: git branch, else "preview").
44
+
45
+ EXAMPLES
46
+ ${BIN_NAME} login
47
+ ${BIN_NAME} publish
48
+ npx carboncanvas-cli publish --dist dist --name my-prototype
49
+ ${BIN_NAME} publish --fresh --label redesign-empty-states
50
+
51
+ NOTE
52
+ publish covers STATIC builds only (Vite/CRA/Astro-static/SvelteKit-static).
53
+ For apps that need a server, deploy them yourself and point Carbon Canvas at the URL.`;
54
+ async function main(argv) {
55
+ const command = argv[0] && !argv[0].startsWith('-') ? argv[0] : undefined;
56
+ const rest = command ? argv.slice(1) : argv;
57
+ // Global flags handled before dispatch.
58
+ const globalPeek = rest.concat(command ?? []);
59
+ if (!command && (globalPeek.includes('-v') || globalPeek.includes('--version'))) {
60
+ log.out(VERSION);
61
+ return 0;
62
+ }
63
+ if (!command || command === 'help' || globalPeek.includes('-h') || globalPeek.includes('--help')) {
64
+ if (!command || command === 'help') {
65
+ log.info(HELP);
66
+ return 0;
67
+ }
68
+ }
69
+ const { values } = parseArgs({
70
+ args: rest,
71
+ allowPositionals: true,
72
+ options: {
73
+ 'service-url': { type: 'string' },
74
+ token: { type: 'string' },
75
+ dir: { type: 'string' },
76
+ dist: { type: 'string' },
77
+ canvas: { type: 'string' },
78
+ fresh: { type: 'boolean' },
79
+ name: { type: 'string' },
80
+ label: { type: 'string' },
81
+ help: { type: 'boolean', short: 'h' },
82
+ version: { type: 'boolean', short: 'v' },
83
+ },
84
+ });
85
+ if (values.help) {
86
+ log.info(HELP);
87
+ return 0;
88
+ }
89
+ if (values.version) {
90
+ log.out(VERSION);
91
+ return 0;
92
+ }
93
+ const serviceUrl = resolveServiceUrl(values['service-url']);
94
+ switch (command) {
95
+ case 'login':
96
+ await loginCommand({ serviceUrl, token: values.token });
97
+ return 0;
98
+ case 'publish':
99
+ await publishCommand({
100
+ serviceUrl,
101
+ token: values.token,
102
+ dir: values.dir,
103
+ dist: values.dist,
104
+ canvas: values.canvas,
105
+ fresh: Boolean(values.fresh),
106
+ name: values.name,
107
+ label: values.label,
108
+ });
109
+ return 0;
110
+ case 'logout': {
111
+ const removed = await clearToken(serviceUrl);
112
+ if (removed)
113
+ log.success(`Removed stored token for ${serviceUrl}.`);
114
+ else
115
+ log.info(`No stored token for ${serviceUrl}.`);
116
+ return 0;
117
+ }
118
+ case 'whoami': {
119
+ const token = await resolveToken(serviceUrl, values.token);
120
+ if (!token) {
121
+ log.info(`Not logged in for ${serviceUrl}.`);
122
+ return 1;
123
+ }
124
+ const stored = await storedToken(serviceUrl);
125
+ const src = values.token ? '--token' : process.env.CARBON_TOKEN ? 'CARBON_TOKEN' : stored ? 'stored config' : 'unknown';
126
+ const result = await verifyToken(serviceUrl, token);
127
+ if (result.ok) {
128
+ log.success(`Token (${src}) is valid for ${serviceUrl}.`);
129
+ return 0;
130
+ }
131
+ log.error(`Token (${src}) was rejected (HTTP ${result.status}) by ${serviceUrl}.`);
132
+ return 1;
133
+ }
134
+ default:
135
+ log.error(`Unknown command: ${command}`);
136
+ log.info(`Run \`${BIN_NAME} help\` for usage.`);
137
+ return 2;
138
+ }
139
+ }
140
+ main(process.argv.slice(2))
141
+ .then((code) => process.exit(code))
142
+ .catch((err) => {
143
+ if (err instanceof UserError) {
144
+ log.error(err.message);
145
+ if (err.hint)
146
+ log.info(' ' + err.hint);
147
+ process.exit(1);
148
+ }
149
+ if (err instanceof PublishError) {
150
+ log.error(err.message);
151
+ if (err.hint)
152
+ log.info(' ' + err.hint);
153
+ process.exit(1);
154
+ }
155
+ log.error('Unexpected error:');
156
+ // eslint-disable-next-line no-console
157
+ console.error(err);
158
+ process.exit(1);
159
+ });
160
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/logger.js ADDED
@@ -0,0 +1,25 @@
1
+ // Tiny logging shell. No color deps — just semantic prefixes to stderr so stdout
2
+ // stays clean for the one machine-readable line (the published URL) if we ever
3
+ // want to pipe it. Everything human goes to stderr.
4
+ /* eslint-disable no-console */
5
+ export function info(msg) {
6
+ process.stderr.write(msg + '\n');
7
+ }
8
+ export function step(msg) {
9
+ process.stderr.write('→ ' + msg + '\n');
10
+ }
11
+ export function warn(msg) {
12
+ process.stderr.write('⚠ ' + msg + '\n');
13
+ }
14
+ export function error(msg) {
15
+ process.stderr.write('✗ ' + msg + '\n');
16
+ }
17
+ export function success(msg) {
18
+ process.stderr.write('✓ ' + msg + '\n');
19
+ }
20
+ // The one line we intentionally send to stdout (the published URL), so
21
+ // `URL=$(carboncanvas publish ... | tail -1)` works.
22
+ export function out(msg) {
23
+ process.stdout.write(msg + '\n');
24
+ }
25
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,+EAA+E;AAC/E,oDAAoD;AAEpD,+BAA+B;AAE/B,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,uEAAuE;AACvE,qDAAqD;AACrD,MAAM,UAAU,GAAG,CAAC,GAAW;IAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,33 @@
1
+ // `carboncanvas login` — paste-token auth (v1; device-code deferred, plan §9).
2
+ //
3
+ // Prompts for (or accepts --token) a creator API token from the dashboard,
4
+ // verifies it against the backend, and stores it in ~/.carboncanvas/config.json
5
+ // (0600) keyed by serviceUrl. CARBON_TOKEN overrides the stored token at use
6
+ // time; --service-url / CARBON_SERVICE_URL selects the backend.
7
+ import * as log from './logger.js';
8
+ import { UserError } from './errors.js';
9
+ import { promptSecret } from './prompt.js';
10
+ import { verifyToken } from './verify.js';
11
+ import { writeToken } from './tokenStore.js';
12
+ export async function loginCommand(opts) {
13
+ let token = opts.token?.trim();
14
+ if (!token) {
15
+ log.info(`Paste a creator API token from the dashboard (${opts.serviceUrl}).`);
16
+ log.info('Find it under Settings → API tokens. Input is hidden.');
17
+ token = await promptSecret('Token: ');
18
+ }
19
+ if (!token) {
20
+ throw new UserError('No token provided.');
21
+ }
22
+ log.step(`Verifying token against ${opts.serviceUrl} …`);
23
+ const result = await verifyToken(opts.serviceUrl, token, opts.fetchImpl);
24
+ if (!result.ok) {
25
+ if (result.status === 401) {
26
+ throw new UserError('That token was rejected (401). Double-check you copied it in full.');
27
+ }
28
+ throw new UserError(`Could not verify the token (HTTP ${result.status}). Try again or check --service-url.`);
29
+ }
30
+ await writeToken(opts.serviceUrl, token);
31
+ log.success(`Logged in. Token saved for ${opts.serviceUrl}.`);
32
+ }
33
+ //# sourceMappingURL=loginCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loginCommand.js","sourceRoot":"","sources":["../src/loginCommand.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,2EAA2E;AAC3E,gFAAgF;AAChF,6EAA6E;AAC7E,gEAAgE;AAEhE,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAkB,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAQ7C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAkB;IACnD,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,GAAG,CAAC,IAAI,CAAC,iDAAiD,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;QAC/E,GAAG,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;QAClE,KAAK,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAC5C,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,2BAA2B,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACzE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,MAAM,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC1B,MAAM,IAAI,SAAS,CAAC,oEAAoE,CAAC,CAAC;QAC5F,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,oCAAoC,MAAM,CAAC,MAAM,sCAAsC,CACxF,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACzC,GAAG,CAAC,OAAO,CAAC,8BAA8B,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;AAChE,CAAC"}