@puckeditor/cli 0.24.0-canary.55558da4 → 0.24.0-canary.6f40e09b

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
@@ -20,6 +20,7 @@ Other commands:
20
20
  npx @puckeditor/cli add editor # add the Puck editor
21
21
  npx @puckeditor/cli add cloud # connect Puck Cloud
22
22
  npx @puckeditor/cli add ai # add the Puck AI plugin
23
+ npx @puckeditor/cli connect # log in to Puck Cloud again for a new API key
23
24
  npx @puckeditor/cli status # show what's set up
24
25
  npx @puckeditor/cli doctor # diagnose problems
25
26
  npx @puckeditor/cli frameworks # show supported frameworks and versions
package/dist/bin.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/bin.ts
4
4
  import os from "os";
5
- import path30 from "path";
5
+ import path32 from "path";
6
6
  import fs7 from "fs";
7
7
  import { fileURLToPath } from "url";
8
8
 
@@ -38,6 +38,7 @@ var exitCodeByError = {
38
38
  "PUCK-CLI-FRAMEWORK-MISMATCH": EXIT_CODES.unsupported,
39
39
  "PUCK-CLI-BACKEND-REQUIRED": EXIT_CODES.usage,
40
40
  "PUCK-CLI-BACKEND-URL-REQUIRED": EXIT_CODES.usage,
41
+ "PUCK-CLI-EXTERNAL-BACKEND": EXIT_CODES.unsupported,
41
42
  "PUCK-CLI-WORKSPACE-TARGET-NOT-FOUND": EXIT_CODES.unsupported,
42
43
  "PUCK-CLI-TYPESCRIPT-REQUIRED": EXIT_CODES.unsupported,
43
44
  "PUCK-CLI-TARGET-DIR-NOT-EMPTY": EXIT_CODES.filesystem,
@@ -67,9 +68,6 @@ var CliError = class extends Error {
67
68
  // src/args.ts
68
69
  import { parseArgs } from "util";
69
70
 
70
- // src/detect/framework.ts
71
- import path3 from "path";
72
-
73
71
  // src/constants.ts
74
72
  var ENV_KEY = "PUCK_API_KEY";
75
73
  var DEFAULT_CLOUD_URL = "https://cloud.puckeditor.com";
@@ -82,6 +80,9 @@ var CLOUD_CLIENT_PACKAGE = "@puckeditor/cloud-client";
82
80
  var PLUGIN_AI_PACKAGE = "@puckeditor/plugin-ai";
83
81
  var CANONICAL_INVOCATION = "npx @puckeditor/cli";
84
82
 
83
+ // src/detect/framework.ts
84
+ import path3 from "path";
85
+
85
86
  // src/ast/parse.ts
86
87
  import { parse } from "@babel/parser";
87
88
  var isTsx = (filename) => /\.(tsx|jsx|js|mjs|cjs)$/.test(filename);
@@ -799,6 +800,7 @@ var MUTATING = [
799
800
  var COMMAND_OPTIONS = {
800
801
  init: [...GLOBAL, ...MUTATING, "framework", "name", "ai", "no-ai"],
801
802
  add: [...GLOBAL, ...MUTATING],
803
+ connect: [...GLOBAL, "api-key", "config", "wait"],
802
804
  status: ["json", "help", "version", "cwd", "workspace", "config"],
803
805
  doctor: ["json", "help", "version", "cwd", "workspace", "config", "offline"],
804
806
  docs: ["json", "help", "version"],
@@ -808,6 +810,7 @@ var COMMAND_OPTIONS = {
808
810
  var COMMANDS = [
809
811
  "init",
810
812
  "add",
813
+ "connect",
811
814
  "status",
812
815
  "doctor",
813
816
  "docs",
@@ -836,7 +839,7 @@ var parseCliArgs = (argv) => {
836
839
  if (!COMMANDS.includes(first)) {
837
840
  throw new CliError(
838
841
  "PUCK-CLI-UNKNOWN-COMMAND",
839
- `Unknown command "${first}". Run \`puck --help\` to see available commands.`
842
+ `Unknown command "${first}". Run \`${CANONICAL_INVOCATION} --help\` to see available commands.`
840
843
  );
841
844
  }
842
845
  command = first;
@@ -847,7 +850,7 @@ var parseCliArgs = (argv) => {
847
850
  if (!allowed.includes(key)) {
848
851
  throw new CliError(
849
852
  "PUCK-CLI-INVALID-ARGS",
850
- `--${key} can't be used with \`${command ?? "puck"}\`.`
853
+ `--${key} can't be used with \`${command ?? CANONICAL_INVOCATION}\`.`
851
854
  );
852
855
  }
853
856
  }
@@ -1156,6 +1159,10 @@ var COMMANDS2 = [
1156
1159
  name: "add <editor|cloud|ai>",
1157
1160
  description: "Add one capability (ai includes cloud, cloud includes editor)"
1158
1161
  },
1162
+ {
1163
+ name: "connect",
1164
+ description: "Log in to Puck Cloud again and replace PUCK_API_KEY"
1165
+ },
1159
1166
  { name: "status", description: "Show what's installed and configured" },
1160
1167
  {
1161
1168
  name: "doctor",
@@ -1504,7 +1511,8 @@ var templates_default = {
1504
1511
  "*.tsbuildinfo",
1505
1512
  "eslint.config.mjs",
1506
1513
  ".env*"
1507
- ]
1514
+ ],
1515
+ allowBuilds: ["esbuild"]
1508
1516
  };
1509
1517
 
1510
1518
  // src/templates/source.ts
@@ -2550,10 +2558,10 @@ var analyzeRoutes = (code, filename) => {
2550
2558
  const topLevel = new Set(exported.elements.filter(Boolean));
2551
2559
  walk(exported, (node) => {
2552
2560
  if (!isRouteCall(node, routeLocal)) return;
2553
- const path31 = stringArg(node, 0);
2561
+ const path33 = stringArg(node, 0);
2554
2562
  const isTop = topLevel.has(node);
2555
- calls.push({ path: path31, file: stringArg(node, 1), topLevel: isTop });
2556
- if (path31 === "*") {
2563
+ calls.push({ path: path33, file: stringArg(node, 1), topLevel: isTop });
2564
+ if (path33 === "*") {
2557
2565
  if (isTop) splat = node;
2558
2566
  else nestedSplat = true;
2559
2567
  }
@@ -4843,7 +4851,7 @@ var presentHuman = (out, result, secrets) => {
4843
4851
  };
4844
4852
 
4845
4853
  // src/commands/init.ts
4846
- import path27 from "path";
4854
+ import path28 from "path";
4847
4855
 
4848
4856
  // src/io/vfs.ts
4849
4857
  import fs2 from "fs";
@@ -5313,7 +5321,7 @@ var resolveTarget = async (rc, opts = {}) => {
5313
5321
  };
5314
5322
 
5315
5323
  // src/commands/mutate.ts
5316
- import path26 from "path";
5324
+ import path27 from "path";
5317
5325
 
5318
5326
  // src/plan/planner.ts
5319
5327
  import path22 from "path";
@@ -5571,6 +5579,66 @@ var planEnvWrite = (p, secret) => {
5571
5579
  planGitignore(p, target);
5572
5580
  };
5573
5581
 
5582
+ // src/plan/pnpm-builds.ts
5583
+ import path24 from "path";
5584
+ import { Document, isMap, isSeq, parseDocument } from "yaml";
5585
+ var LEGACY_LISTS = [
5586
+ "onlyBuiltDependencies",
5587
+ "neverBuiltDependencies",
5588
+ "ignoredBuiltDependencies"
5589
+ ];
5590
+ var allowAll = (names) => Object.fromEntries(names.map((name) => [name, true]));
5591
+ var withAllowedBuilds = (text, names) => {
5592
+ const doc = parseDocument(text ?? "");
5593
+ if (doc.errors.length > 0) return null;
5594
+ if (doc.get("dangerouslyAllowAllBuilds") === true) return null;
5595
+ const allowBuilds = doc.get("allowBuilds");
5596
+ const covered = /* @__PURE__ */ new Set();
5597
+ if (isMap(allowBuilds)) {
5598
+ for (const item of allowBuilds.items) covered.add(String(item.key));
5599
+ }
5600
+ for (const list of LEGACY_LISTS) {
5601
+ const seq = doc.get(list);
5602
+ if (isSeq(seq)) for (const item of seq.toJSON()) covered.add(String(item));
5603
+ }
5604
+ const missing = names.filter((name) => !covered.has(name));
5605
+ if (missing.length === 0) return null;
5606
+ const legacy = doc.get("onlyBuiltDependencies");
5607
+ if (isMap(allowBuilds)) {
5608
+ for (const name of missing) allowBuilds.set(name, true);
5609
+ } else if (isSeq(legacy)) {
5610
+ for (const name of missing) legacy.add(name);
5611
+ } else if (doc.contents === null) {
5612
+ const created = new Document({ allowBuilds: allowAll(missing) });
5613
+ created.commentBefore = " Dependencies allowed to run install scripts (required by pnpm 11+)";
5614
+ return created.toString();
5615
+ } else {
5616
+ doc.set("allowBuilds", allowAll(missing));
5617
+ }
5618
+ return doc.toString();
5619
+ };
5620
+ var planAllowedBuilds = (planner) => {
5621
+ const { ctx, vfs } = planner;
5622
+ if (ctx.packageManager.name !== "pnpm") return;
5623
+ const ws = ctx.workspace;
5624
+ if (ws && ws.source !== "pnpm-workspace.yaml") return;
5625
+ const file = path24.join(ws?.root ?? ctx.root, "pnpm-workspace.yaml");
5626
+ const names = templates_default.allowBuilds;
5627
+ const list = names.join(", ");
5628
+ const next = withAllowedBuilds(vfs.readText(file), names);
5629
+ if (next === null) return;
5630
+ vfs.write(file, next);
5631
+ if (!ws) return;
5632
+ planner.steps.push({
5633
+ id: "bootstrap:allow-builds",
5634
+ kind: "modify_file",
5635
+ capability: "bootstrap",
5636
+ summary: `Allow ${list} build scripts in the workspace's pnpm-workspace.yaml`,
5637
+ path: file,
5638
+ edits: []
5639
+ });
5640
+ };
5641
+
5574
5642
  // src/plan/backend.ts
5575
5643
  var choicesFor = (options, wantsCloud) => [
5576
5644
  { value: "add", label: options.addLabel },
@@ -5640,14 +5708,14 @@ var resolveBackend = async (rc, options, capabilities, planned) => {
5640
5708
 
5641
5709
  // src/auth/session-store.ts
5642
5710
  import fs4 from "fs";
5643
- import path24 from "path";
5711
+ import path25 from "path";
5644
5712
  import { createHash as createHash2 } from "crypto";
5645
5713
  var SessionStore = class {
5646
5714
  #file;
5647
5715
  constructor(cacheDir, projectRoot, baseUrl) {
5648
5716
  const key = createHash2("sha256").update(`${projectRoot}
5649
5717
  ${baseUrl}`).digest("hex").slice(0, 32);
5650
- this.#file = path24.join(cacheDir, "puck", "connect", `${key}.json`);
5718
+ this.#file = path25.join(cacheDir, "puck", "connect", `${key}.json`);
5651
5719
  }
5652
5720
  get path() {
5653
5721
  return this.#file;
@@ -5661,14 +5729,14 @@ ${baseUrl}`).digest("hex").slice(0, 32);
5661
5729
  }
5662
5730
  }
5663
5731
  save(session) {
5664
- fs4.mkdirSync(path24.dirname(this.#file), { recursive: true, mode: 448 });
5732
+ fs4.mkdirSync(path25.dirname(this.#file), { recursive: true, mode: 448 });
5665
5733
  fs4.writeFileSync(this.#file, JSON.stringify(session), { mode: 384 });
5666
5734
  }
5667
5735
  clear() {
5668
5736
  fs4.rmSync(this.#file, { force: true });
5669
5737
  }
5670
5738
  };
5671
- var cacheDirFor = (env2, homedir) => env2.XDG_CACHE_HOME || path24.join(homedir, ".cache");
5739
+ var cacheDirFor = (env2, homedir) => env2.XDG_CACHE_HOME || path25.join(homedir, ".cache");
5672
5740
 
5673
5741
  // src/auth/credentials.ts
5674
5742
  var apiKeysUrl = (rc) => new URL(API_KEYS_PATH, rc.cloud.baseUrl).toString();
@@ -5891,7 +5959,7 @@ var promptForKey = async (rc) => {
5891
5959
  warnings: []
5892
5960
  };
5893
5961
  };
5894
- var resolveCredential = async (rc, state, info) => {
5962
+ var resolveCredential = async (rc, state, info, { fresh = false } = {}) => {
5895
5963
  const warnings = [];
5896
5964
  const fromExplicit = async (value, source) => {
5897
5965
  const secret = secretFrom(
@@ -5917,10 +5985,15 @@ var resolveCredential = async (rc, state, info) => {
5917
5985
  };
5918
5986
  if (rc.flags.apiKey !== void 0)
5919
5987
  return fromExplicit(rc.flags.apiKey, "flag");
5920
- if (rc.deps.env[ENV_KEY])
5988
+ if (fresh && rc.deps.env[ENV_KEY]) {
5989
+ warnings.push({
5990
+ code: "PUCK-CLI-W-KEY-IN-PROCESS-ENV",
5991
+ message: `${ENV_KEY} is set in your environment and overrides the new key in the env file. Unset it to use the new key.`
5992
+ });
5993
+ } else if (rc.deps.env[ENV_KEY])
5921
5994
  return fromExplicit(rc.deps.env[ENV_KEY], "process.env");
5922
5995
  const existing = state.cloud.apiKey;
5923
- if (existing.present && existing.value) {
5996
+ if (!fresh && existing.present && existing.value) {
5924
5997
  const secret = secretFrom(
5925
5998
  rc,
5926
5999
  existing.value,
@@ -5981,7 +6054,7 @@ var resolveCredential = async (rc, state, info) => {
5981
6054
 
5982
6055
  // src/apply/applier.ts
5983
6056
  import fs5 from "fs";
5984
- import path25 from "path";
6057
+ import path26 from "path";
5985
6058
  var tail = (text, lines = 20) => text.trim().split("\n").slice(-lines).join("\n");
5986
6059
  var run = async (rc, spec) => {
5987
6060
  rc.log(`$ ${formatCommand(spec)}`);
@@ -6011,14 +6084,14 @@ ${result.stderr}`))
6011
6084
  }
6012
6085
  };
6013
6086
  var writeFile = (file, content, mode) => {
6014
- fs5.mkdirSync(path25.dirname(file), { recursive: true });
6087
+ fs5.mkdirSync(path26.dirname(file), { recursive: true });
6015
6088
  if (mode === void 0) {
6016
6089
  fs5.writeFileSync(file, content);
6017
6090
  return;
6018
6091
  }
6019
- const temp = path25.join(
6020
- path25.dirname(file),
6021
- `.${path25.basename(file)}.${process.pid}.tmp`
6092
+ const temp = path26.join(
6093
+ path26.dirname(file),
6094
+ `.${path26.basename(file)}.${process.pid}.tmp`
6022
6095
  );
6023
6096
  fs5.writeFileSync(temp, content, { mode });
6024
6097
  fs5.chmodSync(temp, mode);
@@ -6219,6 +6292,7 @@ var runMutation = async (rc, input) => {
6219
6292
  }
6220
6293
  planCapabilities(planner, capabilities);
6221
6294
  planner.finalize();
6295
+ if (bootstrap) planAllowedBuilds(planner);
6222
6296
  if (scaffoldStep) planner.steps.unshift(scaffoldStep);
6223
6297
  const wantsCloud = capabilities.includes("cloud") && planner.backend?.mode !== "external" && !state.cloud.external;
6224
6298
  const needsConsent = planner.steps.length > 0 || wantsCloud && !state.cloud.apiKey.present;
@@ -6257,7 +6331,7 @@ var runMutation = async (rc, input) => {
6257
6331
  if (wantsCloud) {
6258
6332
  credential = await resolveCredential(rc, state, {
6259
6333
  projectRoot: root,
6260
- projectName: bootstrap?.appName ?? ctx.packageJson?.name ?? path26.basename(root),
6334
+ projectName: bootstrap?.appName ?? ctx.packageJson?.name ?? path27.basename(root),
6261
6335
  framework: ctx.framework.id
6262
6336
  });
6263
6337
  for (const w of credential.warnings) planner.warn(w.code, w.message);
@@ -6448,14 +6522,14 @@ var runInit = async (rc) => {
6448
6522
  { globs: target.workspace.globs }
6449
6523
  );
6450
6524
  }
6451
- parentDir = path27.join(target.workspace.root, appsDir);
6525
+ parentDir = path28.join(target.workspace.root, appsDir);
6452
6526
  workspaceRoot = target.workspace.root;
6453
6527
  for (const pkg of target.workspace.packages) {
6454
6528
  if (pkg.name) existingNames.add(pkg.name);
6455
- existingNames.add(path27.basename(pkg.dir));
6529
+ existingNames.add(path28.basename(pkg.dir));
6456
6530
  }
6457
6531
  } else {
6458
- if (vfs.exists(path27.join(target.root, "package.json"))) {
6532
+ if (vfs.exists(path28.join(target.root, "package.json"))) {
6459
6533
  const read = readPackageJson(vfs, target.root);
6460
6534
  const deps = depsOf(read.status === "ok" ? read.pkg : null);
6461
6535
  const detection = detectFramework(vfs, target.root, deps);
@@ -6545,7 +6619,7 @@ var runInit = async (rc) => {
6545
6619
  `A workspace package named "${name}" already exists.`
6546
6620
  );
6547
6621
  }
6548
- const dir = inPlace ? parentDir : path27.join(parentDir, name);
6622
+ const dir = inPlace ? parentDir : path28.join(parentDir, name);
6549
6623
  if (!inPlace && vfs.exists(dir) && !isEmptyDir(vfs, dir)) {
6550
6624
  throw new CliError(
6551
6625
  "PUCK-CLI-TARGET-DIR-NOT-EMPTY",
@@ -6564,7 +6638,7 @@ var runInit = async (rc) => {
6564
6638
  bootstrap: {
6565
6639
  framework,
6566
6640
  dir,
6567
- appName: name ?? sanitizeAppName(path27.basename(dir)),
6641
+ appName: name ?? sanitizeAppName(path28.basename(dir)),
6568
6642
  workspaceRoot
6569
6643
  },
6570
6644
  workspace: target.kind === "workspace-empty" ? target.summary : null
@@ -6578,7 +6652,7 @@ var runAdd = async (rc, positionals) => {
6578
6652
  "PUCK-CLI-INVALID-ARGS",
6579
6653
  `Specify what to add: ${CAPABILITY_IDS.join(
6580
6654
  ", "
6581
- )}. For example \`puck add cloud\`.`
6655
+ )}. For example \`${CANONICAL_INVOCATION} add cloud\`.`
6582
6656
  );
6583
6657
  }
6584
6658
  const unknown = positionals.filter(
@@ -6616,6 +6690,144 @@ var runAdd = async (rc, positionals) => {
6616
6690
  });
6617
6691
  };
6618
6692
 
6693
+ // src/commands/connect.ts
6694
+ import path29 from "path";
6695
+ var runConnect = async (rc) => {
6696
+ const { deps, flags } = rc;
6697
+ const result = emptyResult("connect", flags.dryRun);
6698
+ const target = await resolveTarget(rc);
6699
+ if (target.kind === "action") {
6700
+ result.status = "action_required";
6701
+ result.message = target.action.message;
6702
+ result.actions = [target.action];
6703
+ return result;
6704
+ }
6705
+ if (target.kind === "workspace-empty") {
6706
+ throw new CliError(
6707
+ "PUCK-CLI-UNSUPPORTED-FRAMEWORK",
6708
+ `No supported app was found in this workspace. Run \`${CANONICAL_INVOCATION} init\` to create one.`,
6709
+ { fix: `${CANONICAL_INVOCATION} init --name <name>` }
6710
+ );
6711
+ }
6712
+ const base = baseDir(rc);
6713
+ const root = target.root;
6714
+ const vfs = new Vfs();
6715
+ const ctx = detectProject({
6716
+ vfs,
6717
+ root,
6718
+ cwd: base,
6719
+ packageManagerFlag: flags.packageManager,
6720
+ env: deps.env
6721
+ });
6722
+ assertSupported(rc, ctx);
6723
+ const state = detectState(vfs, ctx, deps.env, { configFlag: flags.config });
6724
+ if (state.cloud.external) {
6725
+ throw new CliError(
6726
+ "PUCK-CLI-EXTERNAL-BACKEND",
6727
+ "This app sends Puck Cloud requests to a server elsewhere, and the key lives there. Run connect against that server instead.",
6728
+ { fix: `${CANONICAL_INVOCATION} connect --cwd <server dir>` }
6729
+ );
6730
+ }
6731
+ const planner = new Planner(vfs, ctx, state, deps.templates, deps.cliVersion);
6732
+ for (const warning of ctx.warnings)
6733
+ planner.warn("PUCK-CLI-W-DETECTION", warning);
6734
+ const cloudReady = state.cloud.clientInstalled && Boolean(state.cloud.routeFile) && state.cloud.routeRegistered !== false;
6735
+ const where = root === base ? "" : ` --cwd ${displayPath(base, root)}`;
6736
+ if (!cloudReady) {
6737
+ planner.warn(
6738
+ "PUCK-CLI-W-CLOUD-NOT-SET-UP",
6739
+ `Puck Cloud isn't set up in this app yet, so nothing uses ${ENV_KEY}. Run \`${CANONICAL_INVOCATION} add cloud\` to set it up.`
6740
+ );
6741
+ }
6742
+ const finish = (status, message) => {
6743
+ result.status = status;
6744
+ result.message = message;
6745
+ result.project = projectSummary(ctx, target.workspace);
6746
+ result.cloud = cloudSummary(state, "none");
6747
+ result.warnings = planner.warnings;
6748
+ return result;
6749
+ };
6750
+ if (!flags.dryRun && !rc.interactive && !flags.yes) {
6751
+ result.actions = [
6752
+ {
6753
+ id: "confirm",
6754
+ type: "confirm_plan",
6755
+ required: true,
6756
+ flag: "--yes",
6757
+ message: `Re-run with --yes to log in to Puck Cloud and replace ${ENV_KEY}.`,
6758
+ rerun: `${rerunCommand(rc)} --yes`
6759
+ }
6760
+ ];
6761
+ return finish(
6762
+ "action_required",
6763
+ `Re-run with --yes to log in to Puck Cloud and replace ${ENV_KEY}.`
6764
+ );
6765
+ }
6766
+ const credential = await resolveCredential(
6767
+ rc,
6768
+ state,
6769
+ {
6770
+ projectRoot: root,
6771
+ projectName: ctx.packageJson?.name ?? path29.basename(root),
6772
+ framework: ctx.framework.id
6773
+ },
6774
+ { fresh: true }
6775
+ );
6776
+ for (const w of credential.warnings) planner.warn(w.code, w.message);
6777
+ if (credential.kind === "action") {
6778
+ result.actions = [credential.action];
6779
+ return finish(
6780
+ "action_required",
6781
+ credential.action.type === "browser_login" ? "Puck Cloud login required. No changes have been made yet." : "A Puck API key is required. No changes have been made yet."
6782
+ );
6783
+ }
6784
+ if (credential.kind === "none") {
6785
+ planner.warn(
6786
+ "PUCK-CLI-W-KEY-NEEDED",
6787
+ "You'll be asked to log in to Puck Cloud when you run this without --dry-run."
6788
+ );
6789
+ return finish("success", "Dry run: no changes were made.");
6790
+ }
6791
+ planEnvWrite(planner, credential.secret);
6792
+ result.plan = { steps: serializeSteps(base, planner.steps) };
6793
+ if (flags.dryRun) return finish("success", "Dry run: no changes were made.");
6794
+ const outcome = await applyPlan(rc, vfs, planner.steps);
6795
+ const after = new Vfs();
6796
+ const verifiedCtx = detectProject({
6797
+ vfs: after,
6798
+ root,
6799
+ cwd: base,
6800
+ packageManagerFlag: flags.packageManager,
6801
+ env: deps.env
6802
+ });
6803
+ const verifiedState = detectState(after, verifiedCtx, deps.env, {
6804
+ configFlag: flags.config
6805
+ });
6806
+ const check = credential.verified;
6807
+ finish(
6808
+ "success",
6809
+ `Connected to Puck Cloud. ${planner.steps.length ? `Saved ${ENV_KEY} to ${displayPath(
6810
+ base,
6811
+ verifiedState.cloud.apiKey.file ?? root
6812
+ )}.` : `${ENV_KEY} was already set to this key.`}`
6813
+ );
6814
+ result.project = projectSummary(verifiedCtx, target.workspace);
6815
+ result.cloud = cloudSummary(
6816
+ verifiedState,
6817
+ check === "valid" ? "remote" : check === "invalid" ? "failed" : "local"
6818
+ );
6819
+ result.changed = planner.steps.length > 0;
6820
+ result.filesCreated = outcome.created.map((f) => displayPath(base, f)).sort();
6821
+ result.filesModified = [...outcome.created, ...outcome.modified].map((f) => displayPath(base, f)).sort();
6822
+ const pm = verifiedCtx.packageManager.name;
6823
+ const cdPrefix = root === base ? "" : `cd ${displayPath(base, root)} && `;
6824
+ result.nextSteps = cloudReady ? [
6825
+ "# Restart the dev server so it picks up the new key",
6826
+ `${cdPrefix}${pm} ${pm === "npm" ? "run " : ""}dev`
6827
+ ] : [`${CANONICAL_INVOCATION} add cloud${where}`];
6828
+ return result;
6829
+ };
6830
+
6619
6831
  // src/commands/status.ts
6620
6832
  var runStatus = async (rc) => {
6621
6833
  const base = baseDir(rc);
@@ -6689,7 +6901,7 @@ var runStatus = async (rc) => {
6689
6901
  };
6690
6902
 
6691
6903
  // src/commands/doctor.ts
6692
- import path28 from "path";
6904
+ import path30 from "path";
6693
6905
  var finding = (check, status, evidence, fix = null) => ({ check, status, evidence, fix });
6694
6906
  var runDoctor = async (rc) => {
6695
6907
  const base = baseDir(rc);
@@ -6774,7 +6986,7 @@ var runDoctor = async (rc) => {
6774
6986
  finding(
6775
6987
  "project.package_json",
6776
6988
  "ok",
6777
- displayPath(base, path28.join(ctx.root, "package.json"))
6989
+ displayPath(base, path30.join(ctx.root, "package.json"))
6778
6990
  )
6779
6991
  );
6780
6992
  result.project = projectSummary(ctx, target.workspace);
@@ -7016,7 +7228,7 @@ var runDoctor = async (rc) => {
7016
7228
  "cloud.connection",
7017
7229
  "fail",
7018
7230
  "Puck Cloud rejected the API key",
7019
- `${CANONICAL_INVOCATION} add cloud${where}`
7231
+ `${CANONICAL_INVOCATION} connect${where}`
7020
7232
  )
7021
7233
  );
7022
7234
  } else {
@@ -7243,6 +7455,7 @@ var runCli = async (argv, deps) => {
7243
7455
  try {
7244
7456
  if (command === "init") result = await runInit(rc);
7245
7457
  else if (command === "add") result = await runAdd(rc, positionals);
7458
+ else if (command === "connect") result = await runConnect(rc);
7246
7459
  else if (command === "status") result = await runStatus(rc);
7247
7460
  else if (command === "docs") result = await runDocs(rc, positionals);
7248
7461
  else if (command === "frameworks")
@@ -7350,7 +7563,7 @@ var runDevServer = (spec, onReady) => new Promise((resolve) => {
7350
7563
 
7351
7564
  // src/docs/source.ts
7352
7565
  import fs6 from "fs";
7353
- import path29 from "path";
7566
+ import path31 from "path";
7354
7567
  var DirDocsSource = class {
7355
7568
  #dir;
7356
7569
  constructor(dir) {
@@ -7358,21 +7571,21 @@ var DirDocsSource = class {
7358
7571
  }
7359
7572
  list() {
7360
7573
  return JSON.parse(
7361
- fs6.readFileSync(path29.join(this.#dir, "index.json"), "utf8")
7574
+ fs6.readFileSync(path31.join(this.#dir, "index.json"), "utf8")
7362
7575
  );
7363
7576
  }
7364
7577
  read(page) {
7365
- return fs6.readFileSync(path29.join(this.#dir, `${page}.md`), "utf8");
7578
+ return fs6.readFileSync(path31.join(this.#dir, `${page}.md`), "utf8");
7366
7579
  }
7367
7580
  };
7368
7581
 
7369
7582
  // src/bin.ts
7370
- var distDir = path30.dirname(fileURLToPath(import.meta.url));
7371
- var templatesDir = path30.join(distDir, "templates");
7583
+ var distDir = path32.dirname(fileURLToPath(import.meta.url));
7584
+ var templatesDir = path32.join(distDir, "templates");
7372
7585
  var templates = new DirTemplateSource(templatesDir, {
7373
7586
  gitignoreName: "_gitignore",
7374
7587
  manifest: () => JSON.parse(
7375
- fs7.readFileSync(path30.join(templatesDir, "manifest.json"), "utf8")
7588
+ fs7.readFileSync(path32.join(templatesDir, "manifest.json"), "utf8")
7376
7589
  )
7377
7590
  });
7378
7591
  var env = { ...process.env };
@@ -7382,14 +7595,14 @@ runCli(process.argv.slice(2), {
7382
7595
  stdout: process.stdout,
7383
7596
  stderr: process.stderr,
7384
7597
  stdinIsTTY: Boolean(process.stdin.isTTY),
7385
- cliVersion: "0.24.0-canary.55558da4",
7598
+ cliVersion: "0.24.0-canary.6f40e09b",
7386
7599
  runner: createRunner(env),
7387
7600
  fetch: globalThis.fetch,
7388
7601
  openUrl,
7389
7602
  runDevServer,
7390
7603
  createPrompter: () => createInquirerPrompter(process.stderr),
7391
7604
  templates,
7392
- docs: new DirDocsSource(path30.join(distDir, "docs")),
7605
+ docs: new DirDocsSource(path32.join(distDir, "docs")),
7393
7606
  homedir: os.homedir(),
7394
7607
  hostname: os.hostname(),
7395
7608
  now: () => Date.now(),
package/dist/docs/cli.md CHANGED
@@ -159,6 +159,7 @@ npx skills add puckeditor/skills --skill puck
159
159
  | `add editor` | Add the Puck editor |
160
160
  | `add cloud` | Connect Puck Cloud (adds the editor first if needed) |
161
161
  | `add ai` | Add the Puck AI plugin (adds Puck Cloud first if needed) |
162
+ | `connect` | Log in to Puck Cloud again and replace `PUCK_API_KEY` |
162
163
  | `status` | Show what's installed and configured |
163
164
  | `doctor` | Diagnose problems and suggest fixes. Never makes changes |
164
165
  | `frameworks` | Show the supported frameworks and versions |
@@ -195,6 +196,12 @@ To use an existing key, prefer the environment over `--api-key`, which is visibl
195
196
  PUCK_API_KEY=your-key npx @puckeditor/cli add cloud --yes
196
197
  ```
197
198
 
199
+ To log in again, for example to replace a revoked key, run `connect`. It always starts a new login and replaces the existing key, without changing any code:
200
+
201
+ ```sh
202
+ npx @puckeditor/cli connect
203
+ ```
204
+
198
205
  Remember to set `PUCK_API_KEY` in your production environment too. `react-router-serve` and Cloudflare Workers don't load `.env` files; on Workers, use `wrangler secret put PUCK_API_KEY`.
199
206
 
200
207
  The `/api/puck` route and the editor are public by default. Add authentication before deploying.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@puckeditor/cli",
3
- "version": "0.24.0-canary.55558da4",
3
+ "version": "0.24.0-canary.6f40e09b",
4
4
  "description": "Set up Puck and Puck Cloud from the command line. Built for coding agents, friendly for humans.",
5
5
  "author": "Chris Villa <chris@puckeditor.com>",
6
6
  "repository": {