@cross-deck/cli
Advanced tools
+273
-16
@@ -103,6 +103,6 @@ #!/usr/bin/env node | ||
| } | ||
| function printCheck(label, ok2, detail) { | ||
| function printCheck(label2, ok2, detail) { | ||
| const icon = ok2 ? "\u2713" : "\u2717"; | ||
| const stream = ok2 ? process.stdout : process.stderr; | ||
| stream.write(` ${icon} ${label} | ||
| stream.write(` ${icon} ${label2} | ||
| `); | ||
@@ -646,4 +646,4 @@ if (detail) stream.write(` ${detail} | ||
| if (!res.ok) { | ||
| const desc = data.error_description || data.error || text || `HTTP ${res.status}`; | ||
| throw new Error(desc); | ||
| const desc3 = data.error_description || data.error || text || `HTTP ${res.status}`; | ||
| throw new Error(desc3); | ||
| } | ||
@@ -703,6 +703,6 @@ return data; | ||
| function openBrowser(url) { | ||
| const cmd = (0, import_node_os2.platform)() === "darwin" ? "open" : (0, import_node_os2.platform)() === "win32" ? "cmd" : "xdg-open"; | ||
| const cmd2 = (0, import_node_os2.platform)() === "darwin" ? "open" : (0, import_node_os2.platform)() === "win32" ? "cmd" : "xdg-open"; | ||
| const args = (0, import_node_os2.platform)() === "win32" ? ["/c", "start", "", url] : [url]; | ||
| try { | ||
| const child = (0, import_node_child_process.spawn)(cmd, args, { stdio: "ignore", detached: true }); | ||
| const child = (0, import_node_child_process.spawn)(cmd2, args, { stdio: "ignore", detached: true }); | ||
| child.on("error", () => { | ||
@@ -759,2 +759,13 @@ }); | ||
| }); | ||
| let email; | ||
| try { | ||
| const res = await fetch(`${baseUrl}/v1/workspace/projects`, { | ||
| headers: { Authorization: `Bearer ${tokens.accessToken}`, Accept: "application/json" } | ||
| }); | ||
| if (res.ok) { | ||
| const body = await res.json(); | ||
| email = body.data?.account?.email ?? void 0; | ||
| } | ||
| } catch { | ||
| } | ||
| const store = credentialStore(); | ||
@@ -766,6 +777,7 @@ store.save({ | ||
| baseUrl, | ||
| loggedInAt: (/* @__PURE__ */ new Date()).toISOString() | ||
| loggedInAt: (/* @__PURE__ */ new Date()).toISOString(), | ||
| }); | ||
| process.stdout.write(` | ||
| \u2713 Signed in. Access renews automatically for ~30 days. | ||
| \u2713 Signed in${email ? ` as ${email}` : ""}. Access renews automatically for ~30 days. | ||
| `); | ||
@@ -818,3 +830,5 @@ process.stdout.write(` Scopes: ${tokens.scope || scope} | ||
| baseUrl, | ||
| loggedInAt: creds.loggedInAt | ||
| loggedInAt: creds.loggedInAt, | ||
| email: creds.email | ||
| // preserve the cached identity across refresh rotations | ||
| }); | ||
@@ -829,2 +843,3 @@ return { accessToken: tokens.accessToken, baseUrl, scope: tokens.scope || creds.scope }; | ||
| let projectCount = null; | ||
| let email = null; | ||
| try { | ||
@@ -837,2 +852,3 @@ const res = await fetch(`${session.baseUrl}/v1/workspace/projects`, { | ||
| projectCount = Array.isArray(body.data?.projects) ? body.data.projects.length : null; | ||
| email = body.data?.account?.email ?? null; | ||
| } | ||
@@ -844,2 +860,4 @@ } catch { | ||
| `); | ||
| if (email) process.stdout.write(` Account: ${email} | ||
| `); | ||
| process.stdout.write(` API: ${session.baseUrl} | ||
@@ -872,9 +890,37 @@ `); | ||
| const store = credentialStore(); | ||
| const had = store.load() !== null; | ||
| let creds; | ||
| try { | ||
| creds = store.load(); | ||
| } catch { | ||
| store.clear(); | ||
| process.stdout.write("\n\u2713 Signed out on this machine \u2014 the stored credential was wiped.\n\n"); | ||
| return 0; | ||
| } | ||
| if (!creds) { | ||
| process.stdout.write("\nYou weren't signed in. Nothing to do.\n\n"); | ||
| return 0; | ||
| } | ||
| let serverRevoked = false; | ||
| try { | ||
| const url = `${creds.baseUrl.replace(/\/+$/, "")}/oauth/revoke`; | ||
| const res = await fetch(url, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| token: creds.refreshToken, | ||
| token_type_hint: "refresh_token", | ||
| client_id: creds.clientId | ||
| }) | ||
| }); | ||
| serverRevoked = res.ok; | ||
| } catch { | ||
| serverRevoked = false; | ||
| } | ||
| store.clear(); | ||
| if (had) { | ||
| if (serverRevoked) { | ||
| process.stdout.write("\n\u2713 Signed out \u2014 this session was revoked server-side and wiped from this machine.\n"); | ||
| process.stdout.write(" Manage every connected machine from Settings \u2192 Developer in the dashboard.\n\n"); | ||
| } else { | ||
| process.stdout.write("\n\u2713 Signed out on this machine \u2014 the stored credential was wiped.\n"); | ||
| process.stdout.write(" To revoke it everywhere, use Settings \u2192 Developer in the dashboard.\n\n"); | ||
| } else { | ||
| process.stdout.write("\nYou weren't signed in. Nothing to do.\n\n"); | ||
| process.stdout.write(" Couldn't confirm the server-side revoke (offline?). Revoke it anywhere from Settings \u2192 Developer in the dashboard.\n\n"); | ||
| } | ||
@@ -1339,8 +1385,200 @@ return 0; | ||
| // src/launcher.ts | ||
| var isTTY = !!process.stdout.isTTY; | ||
| var useColor2 = isTTY && !process.env.NO_COLOR; | ||
| var truecolor = useColor2 && /truecolor|24bit/i.test(process.env.COLORTERM ?? ""); | ||
| var X_FROM = [255, 61, 46]; | ||
| var X_TO = [255, 154, 61]; | ||
| var CORAL = [255, 107, 84]; | ||
| var WORD = [241, 234, 226]; | ||
| var DESC = [140, 131, 123]; | ||
| var OKGREEN = [111, 207, 155]; | ||
| var FAINT = [92, 83, 75]; | ||
| var tc = (rgb, s) => truecolor ? `\x1B[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${s}\x1B[0m` : useColor2 ? `\x1B[91m${s}\x1B[0m` : s; | ||
| var ansi = (code, s) => useColor2 ? `\x1B[${code}m${s}\x1B[0m` : s; | ||
| var dim = (s) => ansi("2", s); | ||
| var coral = (s) => tc(CORAL, s); | ||
| var desc = (s) => truecolor ? tc(DESC, s) : dim(s); | ||
| var X_ART = [ | ||
| "\u2588\u2588\u2557 \u2588\u2588\u2557", | ||
| "\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D", | ||
| " \u255A\u2588\u2588\u2588\u2554\u255D ", | ||
| " \u2588\u2588\u2554\u2588\u2588\u2557 ", | ||
| "\u2588\u2588\u2554\u255D \u2588\u2588\u2557", | ||
| "\u255A\u2550\u255D \u255A\u2550\u255D" | ||
| ]; | ||
| function brandX() { | ||
| const n = X_ART.length - 1; | ||
| return X_ART.map((row, i) => { | ||
| const t = n === 0 ? 0 : i / n; | ||
| const rgb = [ | ||
| Math.round(X_FROM[0] + (X_TO[0] - X_FROM[0]) * t), | ||
| Math.round(X_FROM[1] + (X_TO[1] - X_FROM[1]) * t), | ||
| Math.round(X_FROM[2] + (X_TO[2] - X_FROM[2]) * t) | ||
| ]; | ||
| return " " + tc(rgb, row); | ||
| }).join("\n"); | ||
| } | ||
| function wordmark() { | ||
| return ` ${tc(WORD, "crossdeck")} ${coral("CLI")} ${desc(`\xB7 v${version()}`)}`; | ||
| } | ||
| var TAGLINE = "The platform that joins revenue, errors, and identity \u2014 by identity \u2014 now in your terminal."; | ||
| function label(text) { | ||
| return ` ${desc(text.toUpperCase())}`; | ||
| } | ||
| function cmd(name, description, pad) { | ||
| return ` ${coral(name.padEnd(pad))} ${desc(description)}`; | ||
| } | ||
| var CLI_VERSION = "0.0.0"; | ||
| function setLauncherVersion(v) { | ||
| CLI_VERSION = v; | ||
| } | ||
| function version() { | ||
| return CLI_VERSION; | ||
| } | ||
| function renderLauncher() { | ||
| const creds = safeLoad(); | ||
| const out = []; | ||
| out.push(""); | ||
| out.push(brandX()); | ||
| out.push(""); | ||
| out.push(wordmark()); | ||
| out.push(` ${desc(TAGLINE)}`); | ||
| out.push(""); | ||
| if (!creds) { | ||
| out.push(` ${truecolor ? tc(FAINT, "\u25CB") : dim("\u25CB")} Not signed in.`); | ||
| out.push(""); | ||
| out.push(label("Get started")); | ||
| out.push(cmd("crossdeck login", "Sign in \u2014 opens your browser", 24)); | ||
| out.push(cmd("crossdeck login --key \u2026", "Non-interactive (CI): use a project secret key", 24)); | ||
| out.push(""); | ||
| out.push(` ${desc("Docs")} ${tc(WORD, "cross-deck.com/docs")}`); | ||
| out.push(` ${desc("Dashboard")} ${tc(WORD, "app.cross-deck.com")}`); | ||
| out.push(""); | ||
| out.push(` ${desc("New here? Run 'crossdeck login' to connect this machine.")}`); | ||
| } else { | ||
| const who = creds.email ? `Signed in as ${creds.email}` : "Signed in"; | ||
| out.push(` ${tc(OKGREEN, "\u2713")} ${who}`); | ||
| const active = getActiveProject(); | ||
| if (active) { | ||
| out.push(` ${coral("\u25CF")} ${active}`); | ||
| } else { | ||
| out.push(` ${truecolor ? tc(FAINT, "\u25CB") : dim("\u25CB")} ${desc("No active project \u2014 run 'crossdeck use <id>'")}`); | ||
| } | ||
| out.push(""); | ||
| out.push(label("Stand up the engine")); | ||
| out.push(cmd("crossdeck init", "Create a project + app, mint keys", 24)); | ||
| out.push(cmd("crossdeck apps create", "Add an app to a project", 24)); | ||
| out.push(cmd("crossdeck use", "Switch the active project", 24)); | ||
| out.push(""); | ||
| out.push(label("Read the facts")); | ||
| out.push(cmd("crossdeck revenue", "MRR & recognised cash", 24)); | ||
| out.push(cmd("crossdeck analytics", "Traffic & acquisition", 24)); | ||
| out.push(cmd("crossdeck errors", "An issue, stitched to who it hit", 24)); | ||
| out.push(""); | ||
| out.push(` ${desc("Docs")} ${tc(WORD, "cross-deck.com/docs")}`); | ||
| out.push(` ${desc("Run 'crossdeck help' for the full command tree.")}`); | ||
| } | ||
| out.push(""); | ||
| process.stdout.write(out.join("\n") + "\n"); | ||
| } | ||
| function safeLoad() { | ||
| try { | ||
| return credentialStore().load(); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| // src/help.ts | ||
| var isTTY2 = !!process.stdout.isTTY; | ||
| var useColor3 = isTTY2 && !process.env.NO_COLOR; | ||
| var truecolor2 = useColor3 && /truecolor|24bit/i.test(process.env.COLORTERM ?? ""); | ||
| var CORAL2 = [255, 107, 84]; | ||
| var WORD2 = [241, 234, 226]; | ||
| var DESC2 = [140, 131, 123]; | ||
| var tc2 = (rgb, s) => truecolor2 ? `\x1B[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${s}\x1B[0m` : useColor3 ? `\x1B[91m${s}\x1B[0m` : s; | ||
| var ansi2 = (code, s) => useColor3 ? `\x1B[${code}m${s}\x1B[0m` : s; | ||
| var dim2 = (s) => ansi2("2", s); | ||
| var bold = (s) => ansi2("1", s); | ||
| var coral2 = (s) => tc2(CORAL2, s); | ||
| var desc2 = (s) => truecolor2 ? tc2(DESC2, s) : dim2(s); | ||
| var GROUPS = [ | ||
| { | ||
| title: "Account", | ||
| note: "Sign in with your browser (OAuth). Grants this machine provisioning + read access.", | ||
| cmds: [ | ||
| { name: "login", desc: "Sign in \u2014 opens your browser (--read-only, --no-browser)" }, | ||
| { name: "whoami", desc: "Show the active session \u2014 API, scopes, portfolio size" }, | ||
| { name: "logout", desc: "Sign out \u2014 revokes this machine server-side" } | ||
| ] | ||
| }, | ||
| { | ||
| title: "Stand up the engine", | ||
| cmds: [ | ||
| { name: "init", desc: "Zero to installed: project + app, mint keys, print the SDK snippet" }, | ||
| { name: "projects create", desc: "Create a project (-n name, --business-model, --activate)" }, | ||
| { name: "projects list", desc: "List the projects you own" }, | ||
| { name: "apps create", desc: "Create an app + mint publishable keys (-P web|ios|android)" }, | ||
| { name: "apps list", desc: "List apps (all, or one project's with -p)" }, | ||
| { name: "use", desc: "Set (or show) the active project for later commands" } | ||
| ] | ||
| }, | ||
| { | ||
| title: "Read the facts", | ||
| note: "Read-only. Every number is self-describing \u2014 coverage and semantics travel with it.", | ||
| cmds: [ | ||
| { name: "revenue", desc: "Recognised cash for a project (GET /v1/revenue)" }, | ||
| { name: "analytics", desc: "Signup acquisition sources (GET /v1/acquisition)" }, | ||
| { name: "errors", desc: "One issue, stitched to who it hit (-i <fingerprint>)" } | ||
| ] | ||
| }, | ||
| { | ||
| title: "Source maps \xB7 CI", | ||
| note: "Key-based (cd_sk_\u2026), no login. What the Errors tab means by 'install the CLI'.", | ||
| cmds: [ | ||
| { name: "sourcemaps upload", desc: "Upload .map files so the dashboard resolves minified frames" }, | ||
| { name: "upload-sourcemaps", desc: "Permanent alias for `sourcemaps upload`" }, | ||
| { name: "doctor", desc: "Validate auth + API reachability without uploading" } | ||
| ] | ||
| } | ||
| ]; | ||
| var CLI_VERSION2 = "0.0.0"; | ||
| function setHelpVersion(v) { | ||
| CLI_VERSION2 = v; | ||
| } | ||
| function renderHelp() { | ||
| const pad = Math.max( | ||
| ...GROUPS.flatMap((g) => g.cmds.map((c2) => c2.name.length)) | ||
| ); | ||
| const out = []; | ||
| out.push(""); | ||
| out.push(` ${bold(tc2(WORD2, "crossdeck"))} ${coral2("CLI")} ${desc2(`\xB7 v${CLI_VERSION2}`)}`); | ||
| out.push(` ${desc2("Sign in, provision projects & apps, and read the facts \u2014 from your terminal.")}`); | ||
| out.push(""); | ||
| out.push(` ${desc2("Usage:")} crossdeck ${coral2("<command>")} ${desc2("[options]")}`); | ||
| out.push(` crossdeck ${desc2("# the launcher")}`); | ||
| out.push(` crossdeck help ${coral2("<command>")} ${desc2("# flags for one command")}`); | ||
| for (const g of GROUPS) { | ||
| out.push(""); | ||
| out.push(` ${desc2(g.title.toUpperCase())}`); | ||
| if (g.note) out.push(` ${dim2(g.note)}`); | ||
| for (const c2 of g.cmds) { | ||
| out.push(` ${coral2(c2.name.padEnd(pad))} ${desc2(c2.desc)}`); | ||
| } | ||
| } | ||
| out.push(""); | ||
| out.push(` ${desc2("Docs")} ${tc2(WORD2, "cross-deck.com/docs/cli")} ${desc2("Global flags:")} --json, --base-url <url>`); | ||
| out.push(""); | ||
| process.stdout.write(out.join("\n") + "\n"); | ||
| } | ||
| // src/cli.ts | ||
| var VERSION = "1.2.0"; | ||
| var VERSION = "1.3.0"; | ||
| setLauncherVersion(VERSION); | ||
| setHelpVersion(VERSION); | ||
| var program = new import_commander.Command(); | ||
| program.name("crossdeck").description( | ||
| "Crossdeck CLI \u2014 sign in, provision projects & apps, and monitor errors, analytics, and revenue from your terminal." | ||
| ).version(VERSION); | ||
| ).version(VERSION).helpCommand(false); | ||
| program.command("login").description("Sign in to Crossdeck in your browser (OAuth). Grants this machine provisioning + read access.").option("--read-only", "Request read-only scope (no project/app/key creation).", false).option("--scope <scope>", "Override the exact space-separated OAuth scope to request.").option("--no-browser", "Print the sign-in URL instead of opening a browser (headless machines).").option("--base-url <url>", "Crossdeck API base URL. Defaults to https://api.cross-deck.com.").action(async (opts) => { | ||
@@ -1430,2 +1668,21 @@ process.exit( | ||
| }); | ||
| program.command("help").argument("[command]", "Show flags for one command instead of the whole tree.").description("Show the command tree, or one command's flags.").action((command) => { | ||
| if (!command) { | ||
| renderHelp(); | ||
| process.exit(0); | ||
| } | ||
| const target = program.commands.find((cmd2) => cmd2.name() === command); | ||
| if (target) { | ||
| target.outputHelp(); | ||
| process.exit(0); | ||
| } | ||
| process.stderr.write(`Unknown command: ${command} | ||
| `); | ||
| renderHelp(); | ||
| process.exit(1); | ||
| }); | ||
| if (process.argv.length <= 2) { | ||
| renderLauncher(); | ||
| process.exit(0); | ||
| } | ||
| program.parseAsync(process.argv).catch((err) => { | ||
@@ -1432,0 +1689,0 @@ console.error(err instanceof Error ? err.message : String(err)); |
+273
-16
@@ -100,6 +100,6 @@ // src/cli.ts | ||
| } | ||
| function printCheck(label, ok2, detail) { | ||
| function printCheck(label2, ok2, detail) { | ||
| const icon = ok2 ? "\u2713" : "\u2717"; | ||
| const stream = ok2 ? process.stdout : process.stderr; | ||
| stream.write(` ${icon} ${label} | ||
| stream.write(` ${icon} ${label2} | ||
| `); | ||
@@ -643,4 +643,4 @@ if (detail) stream.write(` ${detail} | ||
| if (!res.ok) { | ||
| const desc = data.error_description || data.error || text || `HTTP ${res.status}`; | ||
| throw new Error(desc); | ||
| const desc3 = data.error_description || data.error || text || `HTTP ${res.status}`; | ||
| throw new Error(desc3); | ||
| } | ||
@@ -700,6 +700,6 @@ return data; | ||
| function openBrowser(url) { | ||
| const cmd = platform2() === "darwin" ? "open" : platform2() === "win32" ? "cmd" : "xdg-open"; | ||
| const cmd2 = platform2() === "darwin" ? "open" : platform2() === "win32" ? "cmd" : "xdg-open"; | ||
| const args = platform2() === "win32" ? ["/c", "start", "", url] : [url]; | ||
| try { | ||
| const child = spawn(cmd, args, { stdio: "ignore", detached: true }); | ||
| const child = spawn(cmd2, args, { stdio: "ignore", detached: true }); | ||
| child.on("error", () => { | ||
@@ -756,2 +756,13 @@ }); | ||
| }); | ||
| let email; | ||
| try { | ||
| const res = await fetch(`${baseUrl}/v1/workspace/projects`, { | ||
| headers: { Authorization: `Bearer ${tokens.accessToken}`, Accept: "application/json" } | ||
| }); | ||
| if (res.ok) { | ||
| const body = await res.json(); | ||
| email = body.data?.account?.email ?? void 0; | ||
| } | ||
| } catch { | ||
| } | ||
| const store = credentialStore(); | ||
@@ -763,6 +774,7 @@ store.save({ | ||
| baseUrl, | ||
| loggedInAt: (/* @__PURE__ */ new Date()).toISOString() | ||
| loggedInAt: (/* @__PURE__ */ new Date()).toISOString(), | ||
| }); | ||
| process.stdout.write(` | ||
| \u2713 Signed in. Access renews automatically for ~30 days. | ||
| \u2713 Signed in${email ? ` as ${email}` : ""}. Access renews automatically for ~30 days. | ||
| `); | ||
@@ -815,3 +827,5 @@ process.stdout.write(` Scopes: ${tokens.scope || scope} | ||
| baseUrl, | ||
| loggedInAt: creds.loggedInAt | ||
| loggedInAt: creds.loggedInAt, | ||
| email: creds.email | ||
| // preserve the cached identity across refresh rotations | ||
| }); | ||
@@ -826,2 +840,3 @@ return { accessToken: tokens.accessToken, baseUrl, scope: tokens.scope || creds.scope }; | ||
| let projectCount = null; | ||
| let email = null; | ||
| try { | ||
@@ -834,2 +849,3 @@ const res = await fetch(`${session.baseUrl}/v1/workspace/projects`, { | ||
| projectCount = Array.isArray(body.data?.projects) ? body.data.projects.length : null; | ||
| email = body.data?.account?.email ?? null; | ||
| } | ||
@@ -841,2 +857,4 @@ } catch { | ||
| `); | ||
| if (email) process.stdout.write(` Account: ${email} | ||
| `); | ||
| process.stdout.write(` API: ${session.baseUrl} | ||
@@ -869,9 +887,37 @@ `); | ||
| const store = credentialStore(); | ||
| const had = store.load() !== null; | ||
| let creds; | ||
| try { | ||
| creds = store.load(); | ||
| } catch { | ||
| store.clear(); | ||
| process.stdout.write("\n\u2713 Signed out on this machine \u2014 the stored credential was wiped.\n\n"); | ||
| return 0; | ||
| } | ||
| if (!creds) { | ||
| process.stdout.write("\nYou weren't signed in. Nothing to do.\n\n"); | ||
| return 0; | ||
| } | ||
| let serverRevoked = false; | ||
| try { | ||
| const url = `${creds.baseUrl.replace(/\/+$/, "")}/oauth/revoke`; | ||
| const res = await fetch(url, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| token: creds.refreshToken, | ||
| token_type_hint: "refresh_token", | ||
| client_id: creds.clientId | ||
| }) | ||
| }); | ||
| serverRevoked = res.ok; | ||
| } catch { | ||
| serverRevoked = false; | ||
| } | ||
| store.clear(); | ||
| if (had) { | ||
| if (serverRevoked) { | ||
| process.stdout.write("\n\u2713 Signed out \u2014 this session was revoked server-side and wiped from this machine.\n"); | ||
| process.stdout.write(" Manage every connected machine from Settings \u2192 Developer in the dashboard.\n\n"); | ||
| } else { | ||
| process.stdout.write("\n\u2713 Signed out on this machine \u2014 the stored credential was wiped.\n"); | ||
| process.stdout.write(" To revoke it everywhere, use Settings \u2192 Developer in the dashboard.\n\n"); | ||
| } else { | ||
| process.stdout.write("\nYou weren't signed in. Nothing to do.\n\n"); | ||
| process.stdout.write(" Couldn't confirm the server-side revoke (offline?). Revoke it anywhere from Settings \u2192 Developer in the dashboard.\n\n"); | ||
| } | ||
@@ -1336,8 +1382,200 @@ return 0; | ||
| // src/launcher.ts | ||
| var isTTY = !!process.stdout.isTTY; | ||
| var useColor2 = isTTY && !process.env.NO_COLOR; | ||
| var truecolor = useColor2 && /truecolor|24bit/i.test(process.env.COLORTERM ?? ""); | ||
| var X_FROM = [255, 61, 46]; | ||
| var X_TO = [255, 154, 61]; | ||
| var CORAL = [255, 107, 84]; | ||
| var WORD = [241, 234, 226]; | ||
| var DESC = [140, 131, 123]; | ||
| var OKGREEN = [111, 207, 155]; | ||
| var FAINT = [92, 83, 75]; | ||
| var tc = (rgb, s) => truecolor ? `\x1B[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${s}\x1B[0m` : useColor2 ? `\x1B[91m${s}\x1B[0m` : s; | ||
| var ansi = (code, s) => useColor2 ? `\x1B[${code}m${s}\x1B[0m` : s; | ||
| var dim = (s) => ansi("2", s); | ||
| var coral = (s) => tc(CORAL, s); | ||
| var desc = (s) => truecolor ? tc(DESC, s) : dim(s); | ||
| var X_ART = [ | ||
| "\u2588\u2588\u2557 \u2588\u2588\u2557", | ||
| "\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D", | ||
| " \u255A\u2588\u2588\u2588\u2554\u255D ", | ||
| " \u2588\u2588\u2554\u2588\u2588\u2557 ", | ||
| "\u2588\u2588\u2554\u255D \u2588\u2588\u2557", | ||
| "\u255A\u2550\u255D \u255A\u2550\u255D" | ||
| ]; | ||
| function brandX() { | ||
| const n = X_ART.length - 1; | ||
| return X_ART.map((row, i) => { | ||
| const t = n === 0 ? 0 : i / n; | ||
| const rgb = [ | ||
| Math.round(X_FROM[0] + (X_TO[0] - X_FROM[0]) * t), | ||
| Math.round(X_FROM[1] + (X_TO[1] - X_FROM[1]) * t), | ||
| Math.round(X_FROM[2] + (X_TO[2] - X_FROM[2]) * t) | ||
| ]; | ||
| return " " + tc(rgb, row); | ||
| }).join("\n"); | ||
| } | ||
| function wordmark() { | ||
| return ` ${tc(WORD, "crossdeck")} ${coral("CLI")} ${desc(`\xB7 v${version()}`)}`; | ||
| } | ||
| var TAGLINE = "The platform that joins revenue, errors, and identity \u2014 by identity \u2014 now in your terminal."; | ||
| function label(text) { | ||
| return ` ${desc(text.toUpperCase())}`; | ||
| } | ||
| function cmd(name, description, pad) { | ||
| return ` ${coral(name.padEnd(pad))} ${desc(description)}`; | ||
| } | ||
| var CLI_VERSION = "0.0.0"; | ||
| function setLauncherVersion(v) { | ||
| CLI_VERSION = v; | ||
| } | ||
| function version() { | ||
| return CLI_VERSION; | ||
| } | ||
| function renderLauncher() { | ||
| const creds = safeLoad(); | ||
| const out = []; | ||
| out.push(""); | ||
| out.push(brandX()); | ||
| out.push(""); | ||
| out.push(wordmark()); | ||
| out.push(` ${desc(TAGLINE)}`); | ||
| out.push(""); | ||
| if (!creds) { | ||
| out.push(` ${truecolor ? tc(FAINT, "\u25CB") : dim("\u25CB")} Not signed in.`); | ||
| out.push(""); | ||
| out.push(label("Get started")); | ||
| out.push(cmd("crossdeck login", "Sign in \u2014 opens your browser", 24)); | ||
| out.push(cmd("crossdeck login --key \u2026", "Non-interactive (CI): use a project secret key", 24)); | ||
| out.push(""); | ||
| out.push(` ${desc("Docs")} ${tc(WORD, "cross-deck.com/docs")}`); | ||
| out.push(` ${desc("Dashboard")} ${tc(WORD, "app.cross-deck.com")}`); | ||
| out.push(""); | ||
| out.push(` ${desc("New here? Run 'crossdeck login' to connect this machine.")}`); | ||
| } else { | ||
| const who = creds.email ? `Signed in as ${creds.email}` : "Signed in"; | ||
| out.push(` ${tc(OKGREEN, "\u2713")} ${who}`); | ||
| const active = getActiveProject(); | ||
| if (active) { | ||
| out.push(` ${coral("\u25CF")} ${active}`); | ||
| } else { | ||
| out.push(` ${truecolor ? tc(FAINT, "\u25CB") : dim("\u25CB")} ${desc("No active project \u2014 run 'crossdeck use <id>'")}`); | ||
| } | ||
| out.push(""); | ||
| out.push(label("Stand up the engine")); | ||
| out.push(cmd("crossdeck init", "Create a project + app, mint keys", 24)); | ||
| out.push(cmd("crossdeck apps create", "Add an app to a project", 24)); | ||
| out.push(cmd("crossdeck use", "Switch the active project", 24)); | ||
| out.push(""); | ||
| out.push(label("Read the facts")); | ||
| out.push(cmd("crossdeck revenue", "MRR & recognised cash", 24)); | ||
| out.push(cmd("crossdeck analytics", "Traffic & acquisition", 24)); | ||
| out.push(cmd("crossdeck errors", "An issue, stitched to who it hit", 24)); | ||
| out.push(""); | ||
| out.push(` ${desc("Docs")} ${tc(WORD, "cross-deck.com/docs")}`); | ||
| out.push(` ${desc("Run 'crossdeck help' for the full command tree.")}`); | ||
| } | ||
| out.push(""); | ||
| process.stdout.write(out.join("\n") + "\n"); | ||
| } | ||
| function safeLoad() { | ||
| try { | ||
| return credentialStore().load(); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| // src/help.ts | ||
| var isTTY2 = !!process.stdout.isTTY; | ||
| var useColor3 = isTTY2 && !process.env.NO_COLOR; | ||
| var truecolor2 = useColor3 && /truecolor|24bit/i.test(process.env.COLORTERM ?? ""); | ||
| var CORAL2 = [255, 107, 84]; | ||
| var WORD2 = [241, 234, 226]; | ||
| var DESC2 = [140, 131, 123]; | ||
| var tc2 = (rgb, s) => truecolor2 ? `\x1B[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${s}\x1B[0m` : useColor3 ? `\x1B[91m${s}\x1B[0m` : s; | ||
| var ansi2 = (code, s) => useColor3 ? `\x1B[${code}m${s}\x1B[0m` : s; | ||
| var dim2 = (s) => ansi2("2", s); | ||
| var bold = (s) => ansi2("1", s); | ||
| var coral2 = (s) => tc2(CORAL2, s); | ||
| var desc2 = (s) => truecolor2 ? tc2(DESC2, s) : dim2(s); | ||
| var GROUPS = [ | ||
| { | ||
| title: "Account", | ||
| note: "Sign in with your browser (OAuth). Grants this machine provisioning + read access.", | ||
| cmds: [ | ||
| { name: "login", desc: "Sign in \u2014 opens your browser (--read-only, --no-browser)" }, | ||
| { name: "whoami", desc: "Show the active session \u2014 API, scopes, portfolio size" }, | ||
| { name: "logout", desc: "Sign out \u2014 revokes this machine server-side" } | ||
| ] | ||
| }, | ||
| { | ||
| title: "Stand up the engine", | ||
| cmds: [ | ||
| { name: "init", desc: "Zero to installed: project + app, mint keys, print the SDK snippet" }, | ||
| { name: "projects create", desc: "Create a project (-n name, --business-model, --activate)" }, | ||
| { name: "projects list", desc: "List the projects you own" }, | ||
| { name: "apps create", desc: "Create an app + mint publishable keys (-P web|ios|android)" }, | ||
| { name: "apps list", desc: "List apps (all, or one project's with -p)" }, | ||
| { name: "use", desc: "Set (or show) the active project for later commands" } | ||
| ] | ||
| }, | ||
| { | ||
| title: "Read the facts", | ||
| note: "Read-only. Every number is self-describing \u2014 coverage and semantics travel with it.", | ||
| cmds: [ | ||
| { name: "revenue", desc: "Recognised cash for a project (GET /v1/revenue)" }, | ||
| { name: "analytics", desc: "Signup acquisition sources (GET /v1/acquisition)" }, | ||
| { name: "errors", desc: "One issue, stitched to who it hit (-i <fingerprint>)" } | ||
| ] | ||
| }, | ||
| { | ||
| title: "Source maps \xB7 CI", | ||
| note: "Key-based (cd_sk_\u2026), no login. What the Errors tab means by 'install the CLI'.", | ||
| cmds: [ | ||
| { name: "sourcemaps upload", desc: "Upload .map files so the dashboard resolves minified frames" }, | ||
| { name: "upload-sourcemaps", desc: "Permanent alias for `sourcemaps upload`" }, | ||
| { name: "doctor", desc: "Validate auth + API reachability without uploading" } | ||
| ] | ||
| } | ||
| ]; | ||
| var CLI_VERSION2 = "0.0.0"; | ||
| function setHelpVersion(v) { | ||
| CLI_VERSION2 = v; | ||
| } | ||
| function renderHelp() { | ||
| const pad = Math.max( | ||
| ...GROUPS.flatMap((g) => g.cmds.map((c2) => c2.name.length)) | ||
| ); | ||
| const out = []; | ||
| out.push(""); | ||
| out.push(` ${bold(tc2(WORD2, "crossdeck"))} ${coral2("CLI")} ${desc2(`\xB7 v${CLI_VERSION2}`)}`); | ||
| out.push(` ${desc2("Sign in, provision projects & apps, and read the facts \u2014 from your terminal.")}`); | ||
| out.push(""); | ||
| out.push(` ${desc2("Usage:")} crossdeck ${coral2("<command>")} ${desc2("[options]")}`); | ||
| out.push(` crossdeck ${desc2("# the launcher")}`); | ||
| out.push(` crossdeck help ${coral2("<command>")} ${desc2("# flags for one command")}`); | ||
| for (const g of GROUPS) { | ||
| out.push(""); | ||
| out.push(` ${desc2(g.title.toUpperCase())}`); | ||
| if (g.note) out.push(` ${dim2(g.note)}`); | ||
| for (const c2 of g.cmds) { | ||
| out.push(` ${coral2(c2.name.padEnd(pad))} ${desc2(c2.desc)}`); | ||
| } | ||
| } | ||
| out.push(""); | ||
| out.push(` ${desc2("Docs")} ${tc2(WORD2, "cross-deck.com/docs/cli")} ${desc2("Global flags:")} --json, --base-url <url>`); | ||
| out.push(""); | ||
| process.stdout.write(out.join("\n") + "\n"); | ||
| } | ||
| // src/cli.ts | ||
| var VERSION = "1.2.0"; | ||
| var VERSION = "1.3.0"; | ||
| setLauncherVersion(VERSION); | ||
| setHelpVersion(VERSION); | ||
| var program = new Command(); | ||
| program.name("crossdeck").description( | ||
| "Crossdeck CLI \u2014 sign in, provision projects & apps, and monitor errors, analytics, and revenue from your terminal." | ||
| ).version(VERSION); | ||
| ).version(VERSION).helpCommand(false); | ||
| program.command("login").description("Sign in to Crossdeck in your browser (OAuth). Grants this machine provisioning + read access.").option("--read-only", "Request read-only scope (no project/app/key creation).", false).option("--scope <scope>", "Override the exact space-separated OAuth scope to request.").option("--no-browser", "Print the sign-in URL instead of opening a browser (headless machines).").option("--base-url <url>", "Crossdeck API base URL. Defaults to https://api.cross-deck.com.").action(async (opts) => { | ||
@@ -1427,2 +1665,21 @@ process.exit( | ||
| }); | ||
| program.command("help").argument("[command]", "Show flags for one command instead of the whole tree.").description("Show the command tree, or one command's flags.").action((command) => { | ||
| if (!command) { | ||
| renderHelp(); | ||
| process.exit(0); | ||
| } | ||
| const target = program.commands.find((cmd2) => cmd2.name() === command); | ||
| if (target) { | ||
| target.outputHelp(); | ||
| process.exit(0); | ||
| } | ||
| process.stderr.write(`Unknown command: ${command} | ||
| `); | ||
| renderHelp(); | ||
| process.exit(1); | ||
| }); | ||
| if (process.argv.length <= 2) { | ||
| renderLauncher(); | ||
| process.exit(0); | ||
| } | ||
| program.parseAsync(process.argv).catch((err) => { | ||
@@ -1429,0 +1686,0 @@ console.error(err instanceof Error ? err.message : String(err)); |
+1
-1
| { | ||
| "name": "@cross-deck/cli", | ||
| "version": "1.2.0", | ||
| "version": "1.3.0", | ||
| "description": "Crossdeck CLI — upload source maps so production stack traces resolve back to original file:line:function.", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
496227
15.06%3983
14.68%72
12.5%20
25%