codebyplan 1.13.13 → 1.13.15

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.
Files changed (31) hide show
  1. package/dist/cli.js +319 -63
  2. package/package.json +1 -1
  3. package/templates/agents/cbp-e2e-maestro.md +26 -3
  4. package/templates/agents/cbp-e2e-playwright.md +24 -3
  5. package/templates/agents/cbp-e2e-tauri.md +25 -2
  6. package/templates/agents/cbp-e2e-vscode.md +28 -3
  7. package/templates/agents/cbp-e2e-xcuitest.md +40 -4
  8. package/templates/agents/cbp-task-check.md +2 -0
  9. package/templates/context/testing/e2e.md +57 -9
  10. package/templates/hooks/validate-structure-patterns.sh +1 -1
  11. package/templates/rules/e2e-mandatory.md +19 -2
  12. package/templates/rules/task-routing-recommendation.md +51 -0
  13. package/templates/settings.project.base.json +8 -1
  14. package/templates/skills/cbp-checkpoint-end/SKILL.md +18 -1
  15. package/templates/skills/cbp-frontend-ui/SKILL.md +9 -7
  16. package/templates/skills/cbp-round-check/SKILL.md +28 -4
  17. package/templates/skills/cbp-round-end/SKILL.md +35 -12
  18. package/templates/skills/cbp-round-execute/SKILL.md +60 -13
  19. package/templates/skills/cbp-round-input/SKILL.md +31 -10
  20. package/templates/skills/cbp-round-start/SKILL.md +35 -14
  21. package/templates/skills/cbp-round-update/SKILL.md +43 -12
  22. package/templates/skills/cbp-standalone-task-check/SKILL.md +152 -0
  23. package/templates/skills/cbp-standalone-task-complete/SKILL.md +201 -0
  24. package/templates/skills/cbp-standalone-task-create/SKILL.md +150 -0
  25. package/templates/skills/cbp-standalone-task-start/SKILL.md +177 -0
  26. package/templates/skills/cbp-standalone-task-testing/SKILL.md +210 -0
  27. package/templates/skills/cbp-task-check/SKILL.md +5 -5
  28. package/templates/skills/cbp-task-complete/SKILL.md +9 -22
  29. package/templates/skills/cbp-task-create/SKILL.md +11 -31
  30. package/templates/skills/cbp-task-start/SKILL.md +6 -13
  31. package/templates/skills/cbp-task-testing/SKILL.md +5 -5
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
14
14
  var init_version = __esm({
15
15
  "src/lib/version.ts"() {
16
16
  "use strict";
17
- VERSION = "1.13.13";
17
+ VERSION = "1.13.15";
18
18
  PACKAGE_NAME = "codebyplan";
19
19
  }
20
20
  });
@@ -697,7 +697,7 @@ function isRetryable(err) {
697
697
  return false;
698
698
  }
699
699
  function delay(ms) {
700
- return new Promise((resolve7) => setTimeout(resolve7, ms));
700
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
701
701
  }
702
702
  async function request(method, path8, options) {
703
703
  const url = buildUrl(path8, options?.params);
@@ -1055,7 +1055,7 @@ var init_device_flow = __esm({
1055
1055
  this.name = "OAuthInvalidClientError";
1056
1056
  }
1057
1057
  };
1058
- defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
1058
+ defaultSleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
1059
1059
  }
1060
1060
  });
1061
1061
 
@@ -1880,9 +1880,9 @@ async function writeMcpConfig(scope) {
1880
1880
  return configPath;
1881
1881
  }
1882
1882
  async function fetchRepos(auth) {
1883
- const baseUrl2 = (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
1883
+ const baseUrl3 = (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
1884
1884
  const headers = auth.kind === "oauth" ? { Authorization: `Bearer ${await getAccessToken()}` } : { "x-api-key": auth.apiKey };
1885
- const res = await fetch(`${baseUrl2}/api/repos`, {
1885
+ const res = await fetch(`${baseUrl3}/api/repos`, {
1886
1886
  headers,
1887
1887
  signal: AbortSignal.timeout(1e4)
1888
1888
  });
@@ -2081,8 +2081,8 @@ async function runSetup() {
2081
2081
  const deviceId = await getOrCreateDeviceId(projectPath);
2082
2082
  let branch = "main";
2083
2083
  try {
2084
- const { execSync: execSync8 } = await import("node:child_process");
2085
- branch = execSync8("git symbolic-ref --short HEAD", {
2084
+ const { execSync: execSync9 } = await import("node:child_process");
2085
+ branch = execSync9("git symbolic-ref --short HEAD", {
2086
2086
  cwd: projectPath,
2087
2087
  encoding: "utf-8"
2088
2088
  }).trim();
@@ -3720,9 +3720,9 @@ async function eslintInit(repoId, projectPath) {
3720
3720
  Install ${missingPkgs.length} missing packages? [Y/n] `
3721
3721
  );
3722
3722
  if (confirmed) {
3723
- const { execSync: execSync8 } = await import("node:child_process");
3723
+ const { execSync: execSync9 } = await import("node:child_process");
3724
3724
  try {
3725
- execSync8(installCmd, { cwd: projectPath, stdio: "inherit" });
3725
+ execSync9(installCmd, { cwd: projectPath, stdio: "inherit" });
3726
3726
  console.log(" Packages installed.\n");
3727
3727
  } catch (err) {
3728
3728
  console.error(
@@ -4157,7 +4157,7 @@ function setRetryDelayMs(ms) {
4157
4157
  RETRY_DELAY_MS = ms;
4158
4158
  }
4159
4159
  function sleep(ms) {
4160
- return new Promise((resolve7) => setTimeout(resolve7, ms));
4160
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
4161
4161
  }
4162
4162
  function isTransientMcpError(err) {
4163
4163
  if (!(err instanceof McpError)) return false;
@@ -6374,13 +6374,262 @@ var init_version_status = __esm({
6374
6374
  }
6375
6375
  });
6376
6376
 
6377
+ // src/cli/upload-e2e-images.ts
6378
+ var upload_e2e_images_exports = {};
6379
+ __export(upload_e2e_images_exports, {
6380
+ runUploadE2eImagesCommand: () => runUploadE2eImagesCommand
6381
+ });
6382
+ import { readFile as readFile15 } from "node:fs/promises";
6383
+ import { join as join21, basename, resolve as resolve5 } from "node:path";
6384
+ import { execSync as execSync7 } from "node:child_process";
6385
+ function baseUrl2() {
6386
+ return (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
6387
+ }
6388
+ function parseArgs(args) {
6389
+ const flags = {};
6390
+ const booleans = /* @__PURE__ */ new Set();
6391
+ const positionals = [];
6392
+ for (let i = 0; i < args.length; i++) {
6393
+ const arg = args[i];
6394
+ if (arg.startsWith("--")) {
6395
+ const key = arg.slice(2);
6396
+ const next = args[i + 1];
6397
+ if (next !== void 0 && !next.startsWith("--")) {
6398
+ flags[key] = next;
6399
+ i++;
6400
+ } else {
6401
+ booleans.add(key);
6402
+ }
6403
+ } else {
6404
+ positionals.push(arg);
6405
+ }
6406
+ }
6407
+ return {
6408
+ checkpointId: positionals[0],
6409
+ repoId: flags["repo-id"],
6410
+ baseBranch: flags["base-branch"] ?? "main",
6411
+ json: booleans.has("json"),
6412
+ dryRun: booleans.has("dry-run")
6413
+ };
6414
+ }
6415
+ async function readE2eConfig(projectPath) {
6416
+ try {
6417
+ const raw = await readFile15(
6418
+ join21(projectPath, ".codebyplan", "e2e.json"),
6419
+ "utf-8"
6420
+ );
6421
+ return JSON.parse(raw);
6422
+ } catch {
6423
+ return {};
6424
+ }
6425
+ }
6426
+ function collectPngsFromGitDiff(projectPath, frameworkName, frameworkConfig, baseBranch) {
6427
+ const pathspec = frameworkConfig.test_dir ?? frameworkConfig.app;
6428
+ if (!pathspec) {
6429
+ return [];
6430
+ }
6431
+ let stdout7;
6432
+ try {
6433
+ stdout7 = execSync7(
6434
+ `git diff --name-status --diff-filter=AM "${baseBranch}...HEAD" -- "${pathspec}"`,
6435
+ { cwd: projectPath, encoding: "utf-8" }
6436
+ );
6437
+ } catch (err) {
6438
+ const msg = err instanceof Error ? err.message : String(err);
6439
+ if (msg.includes("unknown revision") || msg.includes("ambiguous argument") || msg.includes("not a git repository")) {
6440
+ return [];
6441
+ }
6442
+ process.stderr.write(`upload-e2e-images: git diff failed: ${msg}
6443
+ `);
6444
+ return [];
6445
+ }
6446
+ const results = [];
6447
+ for (const line of stdout7.split("\n")) {
6448
+ const trimmed = line.trim();
6449
+ if (!trimmed) continue;
6450
+ const tab = trimmed.indexOf(" ");
6451
+ if (tab === -1) continue;
6452
+ const status = trimmed.slice(0, tab).trim();
6453
+ const filePath = trimmed.slice(tab + 1).trim();
6454
+ if (!filePath.endsWith(".png")) continue;
6455
+ if (frameworkName === "playwright" && !filePath.includes(".spec.ts-snapshots/"))
6456
+ continue;
6457
+ const isNew = status === "A";
6458
+ results.push({
6459
+ absolutePath: join21(projectPath, filePath),
6460
+ filename: basename(filePath),
6461
+ framework: frameworkName,
6462
+ is_new: isNew
6463
+ });
6464
+ }
6465
+ return results;
6466
+ }
6467
+ function deriveTestName(absolutePath) {
6468
+ const segments = absolutePath.replace(/\\/g, "/").split("/");
6469
+ for (let i = segments.length - 2; i >= 0; i--) {
6470
+ const seg = segments[i];
6471
+ if (seg && seg.endsWith(".spec.ts-snapshots")) {
6472
+ return seg.replace(".spec.ts-snapshots", "");
6473
+ }
6474
+ }
6475
+ return basename(absolutePath, ".png");
6476
+ }
6477
+ function buildManifestItem(png) {
6478
+ const testName = deriveTestName(png.absolutePath);
6479
+ const pageOrScreen = basename(png.absolutePath, ".png");
6480
+ return {
6481
+ filename: png.filename,
6482
+ test_name: testName,
6483
+ page_or_screen: pageOrScreen,
6484
+ framework: png.framework,
6485
+ is_new: png.is_new,
6486
+ baseline_diff_pct: null
6487
+ };
6488
+ }
6489
+ async function runUploadE2eImagesCommand(args) {
6490
+ const parsed = parseArgs(args);
6491
+ if (!parsed.checkpointId) {
6492
+ process.stderr.write(
6493
+ "upload-e2e-images: missing required argument <checkpointId>\n\nUsage: codebyplan upload-e2e-images <checkpointId> [--repo-id <uuid>]\n [--base-branch <name>] [--json] [--dry-run]\n\nExample: codebyplan upload-e2e-images chk-abc-123 --base-branch main\n"
6494
+ );
6495
+ process.exit(1);
6496
+ }
6497
+ const checkpointId = parsed.checkpointId;
6498
+ const projectPath = resolve5(process.cwd());
6499
+ let repoId = parsed.repoId;
6500
+ if (!repoId) {
6501
+ const found = await findCodebyplanConfig(projectPath);
6502
+ repoId = found?.contents.repo_id;
6503
+ }
6504
+ if (!repoId) {
6505
+ process.stderr.write(
6506
+ "upload-e2e-images: could not determine repo_id.\nPass --repo-id <uuid> or ensure .codebyplan/repo.json exists.\n"
6507
+ );
6508
+ process.exit(1);
6509
+ }
6510
+ const e2eConfig = await readE2eConfig(projectPath);
6511
+ const frameworks = e2eConfig.frameworks ?? {};
6512
+ const allPngs = [];
6513
+ for (const [name, cfg] of Object.entries(frameworks)) {
6514
+ if (!cfg.enabled || !cfg.auto_run) continue;
6515
+ const pngs = collectPngsFromGitDiff(
6516
+ projectPath,
6517
+ name,
6518
+ cfg,
6519
+ parsed.baseBranch
6520
+ );
6521
+ allPngs.push(...pngs);
6522
+ }
6523
+ if (allPngs.length === 0) {
6524
+ process.stdout.write(
6525
+ `No new/changed e2e screenshots found for ${checkpointId}
6526
+ `
6527
+ );
6528
+ process.exit(0);
6529
+ }
6530
+ const manifest = allPngs.map(buildManifestItem);
6531
+ if (parsed.dryRun) {
6532
+ if (parsed.json) {
6533
+ process.stdout.write(JSON.stringify(manifest, null, 2) + "\n");
6534
+ } else {
6535
+ process.stdout.write(
6536
+ `[dry-run] Would upload ${manifest.length} screenshot(s) for checkpoint ${checkpointId}:
6537
+ `
6538
+ );
6539
+ for (const item of manifest) {
6540
+ const label = item.is_new ? "NEW" : "CHANGED";
6541
+ process.stdout.write(
6542
+ ` [${label}] ${item.filename} (${item.framework}, test: ${item.test_name})
6543
+ `
6544
+ );
6545
+ }
6546
+ }
6547
+ process.exit(0);
6548
+ }
6549
+ const formData = new FormData();
6550
+ formData.append("checkpointId", checkpointId);
6551
+ formData.append("repoId", repoId);
6552
+ formData.append("manifest", JSON.stringify(manifest));
6553
+ for (const png of allPngs) {
6554
+ let bytes;
6555
+ try {
6556
+ bytes = await readFile15(png.absolutePath);
6557
+ } catch {
6558
+ process.stderr.write(
6559
+ `upload-e2e-images: could not read file: ${png.absolutePath}
6560
+ `
6561
+ );
6562
+ process.exit(1);
6563
+ }
6564
+ const blob = new Blob([bytes], { type: "image/png" });
6565
+ formData.append("files", blob, png.filename);
6566
+ }
6567
+ const auth = await getAuthHeaders();
6568
+ const url = `${baseUrl2()}/api/checkpoint-images`;
6569
+ let res;
6570
+ try {
6571
+ res = await fetch(url, {
6572
+ method: "POST",
6573
+ headers: auth.headers,
6574
+ body: formData
6575
+ });
6576
+ } catch (err) {
6577
+ process.stderr.write(
6578
+ `upload-e2e-images: network error: ${err instanceof Error ? err.message : String(err)}
6579
+ `
6580
+ );
6581
+ process.exit(1);
6582
+ }
6583
+ if (!res.ok) {
6584
+ let bodyText = "";
6585
+ try {
6586
+ bodyText = await res.text();
6587
+ } catch {
6588
+ }
6589
+ process.stderr.write(
6590
+ `upload-e2e-images: API returned ${res.status}: ${bodyText}
6591
+ `
6592
+ );
6593
+ process.exit(1);
6594
+ }
6595
+ let result;
6596
+ try {
6597
+ result = await res.json();
6598
+ } catch {
6599
+ process.stderr.write(
6600
+ "upload-e2e-images: failed to parse API response as JSON\n"
6601
+ );
6602
+ process.exit(1);
6603
+ }
6604
+ if (parsed.json) {
6605
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
6606
+ return;
6607
+ }
6608
+ process.stdout.write(
6609
+ `Uploaded ${manifest.length} screenshot(s) for checkpoint ${checkpointId}
6610
+ `
6611
+ );
6612
+ const paths = result.data.stored_paths ?? [];
6613
+ for (const p of paths) {
6614
+ process.stdout.write(` ${p}
6615
+ `);
6616
+ }
6617
+ }
6618
+ var init_upload_e2e_images = __esm({
6619
+ "src/cli/upload-e2e-images.ts"() {
6620
+ "use strict";
6621
+ init_api();
6622
+ init_flags();
6623
+ }
6624
+ });
6625
+
6377
6626
  // src/cli/cmux-sync.ts
6378
6627
  var cmux_sync_exports = {};
6379
6628
  __export(cmux_sync_exports, {
6380
6629
  runCmuxSync: () => runCmuxSync
6381
6630
  });
6382
- import { execSync as execSync7, execFileSync as execFileSync2 } from "node:child_process";
6383
- import { basename } from "node:path";
6631
+ import { execSync as execSync8, execFileSync as execFileSync2 } from "node:child_process";
6632
+ import { basename as basename2 } from "node:path";
6384
6633
  async function runCmuxSync() {
6385
6634
  try {
6386
6635
  if (!process.env.CMUX_WORKSPACE_ID) {
@@ -6389,17 +6638,17 @@ async function runCmuxSync() {
6389
6638
  const bin = process.env.CMUX_BUNDLED_CLI_PATH || process.env.CMUX_CLAUDE_HOOK_CMUX_BIN || "cmux";
6390
6639
  let branch = "";
6391
6640
  try {
6392
- branch = execSync7("git rev-parse --abbrev-ref HEAD", {
6641
+ branch = execSync8("git rev-parse --abbrev-ref HEAD", {
6393
6642
  encoding: "utf8"
6394
6643
  }).trim();
6395
6644
  } catch {
6396
6645
  }
6397
6646
  let folder = "";
6398
6647
  try {
6399
- const toplevel = execSync7("git rev-parse --show-toplevel", {
6648
+ const toplevel = execSync8("git rev-parse --show-toplevel", {
6400
6649
  encoding: "utf8"
6401
6650
  }).trim();
6402
- folder = basename(toplevel);
6651
+ folder = basename2(toplevel);
6403
6652
  } catch {
6404
6653
  }
6405
6654
  if (branch) {
@@ -6440,19 +6689,19 @@ var init_cmux_sync = __esm({
6440
6689
  });
6441
6690
 
6442
6691
  // src/lib/migrate-local-config.ts
6443
- import { mkdir as mkdir6, readFile as readFile15, unlink as unlink2, writeFile as writeFile12 } from "node:fs/promises";
6444
- import { join as join21 } from "node:path";
6692
+ import { mkdir as mkdir6, readFile as readFile16, unlink as unlink2, writeFile as writeFile12 } from "node:fs/promises";
6693
+ import { join as join22 } from "node:path";
6445
6694
  function legacySharedPath(projectPath) {
6446
- return join21(projectPath, ".codebyplan.json");
6695
+ return join22(projectPath, ".codebyplan.json");
6447
6696
  }
6448
6697
  function legacyLocalPath(projectPath) {
6449
- return join21(projectPath, ".codebyplan.local.json");
6698
+ return join22(projectPath, ".codebyplan.local.json");
6450
6699
  }
6451
6700
  function newDirPath(projectPath) {
6452
- return join21(projectPath, ".codebyplan");
6701
+ return join22(projectPath, ".codebyplan");
6453
6702
  }
6454
6703
  function sentinelPath(projectPath) {
6455
- return join21(projectPath, ".codebyplan", "repo.json");
6704
+ return join22(projectPath, ".codebyplan", "repo.json");
6456
6705
  }
6457
6706
  async function statSafe(p) {
6458
6707
  const { stat: stat2 } = await import("node:fs/promises");
@@ -6491,7 +6740,7 @@ async function runLocalMigration(projectPath) {
6491
6740
  }
6492
6741
  let legacyRaw;
6493
6742
  try {
6494
- legacyRaw = await readFile15(legacySharedPath(projectPath), "utf-8");
6743
+ legacyRaw = await readFile16(legacySharedPath(projectPath), "utf-8");
6495
6744
  } catch {
6496
6745
  return {
6497
6746
  migrated: true,
@@ -6518,7 +6767,7 @@ async function runLocalMigration(projectPath) {
6518
6767
  let deviceId;
6519
6768
  let deviceWrittenByHelper = false;
6520
6769
  try {
6521
- const localRaw = await readFile15(legacyLocalPath(projectPath), "utf-8");
6770
+ const localRaw = await readFile16(legacyLocalPath(projectPath), "utf-8");
6522
6771
  const localParsed = JSON.parse(localRaw);
6523
6772
  if (typeof localParsed.device_id === "string") {
6524
6773
  deviceId = localParsed.device_id;
@@ -6546,7 +6795,7 @@ async function runLocalMigration(projectPath) {
6546
6795
  if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
6547
6796
  if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
6548
6797
  await writeFile12(
6549
- join21(projectPath, ".codebyplan", "repo.json"),
6798
+ join22(projectPath, ".codebyplan", "repo.json"),
6550
6799
  JSON.stringify(repoJson, null, 2) + "\n",
6551
6800
  "utf-8"
6552
6801
  );
@@ -6559,7 +6808,7 @@ async function runLocalMigration(projectPath) {
6559
6808
  if ("port_allocations" in cfg)
6560
6809
  serverJson.port_allocations = cfg.port_allocations;
6561
6810
  await writeFile12(
6562
- join21(projectPath, ".codebyplan", "server.json"),
6811
+ join22(projectPath, ".codebyplan", "server.json"),
6563
6812
  JSON.stringify(serverJson, null, 2) + "\n",
6564
6813
  "utf-8"
6565
6814
  );
@@ -6568,7 +6817,7 @@ async function runLocalMigration(projectPath) {
6568
6817
  if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
6569
6818
  if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
6570
6819
  await writeFile12(
6571
- join21(projectPath, ".codebyplan", "git.json"),
6820
+ join22(projectPath, ".codebyplan", "git.json"),
6572
6821
  JSON.stringify(gitJson, null, 2) + "\n",
6573
6822
  "utf-8"
6574
6823
  );
@@ -6576,35 +6825,35 @@ async function runLocalMigration(projectPath) {
6576
6825
  const shipmentJson = {};
6577
6826
  if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
6578
6827
  await writeFile12(
6579
- join21(projectPath, ".codebyplan", "shipment.json"),
6828
+ join22(projectPath, ".codebyplan", "shipment.json"),
6580
6829
  JSON.stringify(shipmentJson, null, 2) + "\n",
6581
6830
  "utf-8"
6582
6831
  );
6583
6832
  filesChanged.push(".codebyplan/shipment.json");
6584
6833
  const vendorJson = {};
6585
6834
  await writeFile12(
6586
- join21(projectPath, ".codebyplan", "vendor.json"),
6835
+ join22(projectPath, ".codebyplan", "vendor.json"),
6587
6836
  JSON.stringify(vendorJson, null, 2) + "\n",
6588
6837
  "utf-8"
6589
6838
  );
6590
6839
  filesChanged.push(".codebyplan/vendor.json");
6591
6840
  const e2eJson = {};
6592
6841
  await writeFile12(
6593
- join21(projectPath, ".codebyplan", "e2e.json"),
6842
+ join22(projectPath, ".codebyplan", "e2e.json"),
6594
6843
  JSON.stringify(e2eJson, null, 2) + "\n",
6595
6844
  "utf-8"
6596
6845
  );
6597
6846
  filesChanged.push(".codebyplan/e2e.json");
6598
6847
  const eslintJson = {};
6599
6848
  await writeFile12(
6600
- join21(projectPath, ".codebyplan", "eslint.json"),
6849
+ join22(projectPath, ".codebyplan", "eslint.json"),
6601
6850
  JSON.stringify(eslintJson, null, 2) + "\n",
6602
6851
  "utf-8"
6603
6852
  );
6604
6853
  filesChanged.push(".codebyplan/eslint.json");
6605
6854
  if (!deviceWrittenByHelper) {
6606
6855
  await writeFile12(
6607
- join21(projectPath, ".codebyplan", "device.local.json"),
6856
+ join22(projectPath, ".codebyplan", "device.local.json"),
6608
6857
  JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
6609
6858
  "utf-8"
6610
6859
  );
@@ -6616,9 +6865,9 @@ async function runLocalMigration(projectPath) {
6616
6865
  "Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
6617
6866
  );
6618
6867
  }
6619
- const gitignorePath = join21(projectPath, ".gitignore");
6868
+ const gitignorePath = join22(projectPath, ".gitignore");
6620
6869
  try {
6621
- const gitignoreContent = await readFile15(gitignorePath, "utf-8");
6870
+ const gitignoreContent = await readFile16(gitignorePath, "utf-8");
6622
6871
  const legacyLine = ".codebyplan.local.json";
6623
6872
  const newLine = ".codebyplan/device.local.json";
6624
6873
  const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
@@ -6669,7 +6918,7 @@ var init_migrate_local_config = __esm({
6669
6918
  // src/cli/config.ts
6670
6919
  var config_exports = {};
6671
6920
  __export(config_exports, {
6672
- readE2eConfig: () => readE2eConfig,
6921
+ readE2eConfig: () => readE2eConfig2,
6673
6922
  readGitConfig: () => readGitConfig,
6674
6923
  readRepoConfig: () => readRepoConfig,
6675
6924
  readServerConfig: () => readServerConfig,
@@ -6677,8 +6926,8 @@ __export(config_exports, {
6677
6926
  readVendorConfig: () => readVendorConfig,
6678
6927
  runConfig: () => runConfig
6679
6928
  });
6680
- import { mkdir as mkdir7, readFile as readFile16, writeFile as writeFile13 } from "node:fs/promises";
6681
- import { join as join22 } from "node:path";
6929
+ import { mkdir as mkdir7, readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
6930
+ import { join as join23 } from "node:path";
6682
6931
  async function runConfig() {
6683
6932
  const flags = parseFlags(3);
6684
6933
  const dryRun = hasFlag("dry-run", 3);
@@ -6711,14 +6960,14 @@ async function runConfig() {
6711
6960
  console.log("\n Config complete.\n");
6712
6961
  }
6713
6962
  async function syncConfigToFile(repoId, projectPath, dryRun) {
6714
- const codebyplanDir = join22(projectPath, ".codebyplan");
6963
+ const codebyplanDir = join23(projectPath, ".codebyplan");
6715
6964
  let resolvedWorktreeId;
6716
6965
  try {
6717
6966
  const deviceId = await getOrCreateDeviceId(projectPath);
6718
6967
  let branch = "main";
6719
6968
  try {
6720
- const { execSync: execSync8 } = await import("node:child_process");
6721
- branch = execSync8("git symbolic-ref --short HEAD", {
6969
+ const { execSync: execSync9 } = await import("node:child_process");
6970
+ branch = execSync9("git symbolic-ref --short HEAD", {
6722
6971
  cwd: projectPath,
6723
6972
  encoding: "utf-8"
6724
6973
  }).trim();
@@ -6854,11 +7103,11 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
6854
7103
  ];
6855
7104
  let anyUpdated = false;
6856
7105
  for (const { name, payload, createOnly } of files) {
6857
- const filePath = join22(codebyplanDir, name);
7106
+ const filePath = join23(codebyplanDir, name);
6858
7107
  const newJson = JSON.stringify(payload, null, 2) + "\n";
6859
7108
  let currentJson = "";
6860
7109
  try {
6861
- currentJson = await readFile16(filePath, "utf-8");
7110
+ currentJson = await readFile17(filePath, "utf-8");
6862
7111
  } catch {
6863
7112
  }
6864
7113
  if (createOnly && currentJson !== "") continue;
@@ -6873,8 +7122,8 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
6873
7122
  }
6874
7123
  async function readRepoConfig(projectPath) {
6875
7124
  try {
6876
- const raw = await readFile16(
6877
- join22(projectPath, ".codebyplan", "repo.json"),
7125
+ const raw = await readFile17(
7126
+ join23(projectPath, ".codebyplan", "repo.json"),
6878
7127
  "utf-8"
6879
7128
  );
6880
7129
  return JSON.parse(raw);
@@ -6884,8 +7133,8 @@ async function readRepoConfig(projectPath) {
6884
7133
  }
6885
7134
  async function readServerConfig(projectPath) {
6886
7135
  try {
6887
- const raw = await readFile16(
6888
- join22(projectPath, ".codebyplan", "server.json"),
7136
+ const raw = await readFile17(
7137
+ join23(projectPath, ".codebyplan", "server.json"),
6889
7138
  "utf-8"
6890
7139
  );
6891
7140
  return JSON.parse(raw);
@@ -6895,8 +7144,8 @@ async function readServerConfig(projectPath) {
6895
7144
  }
6896
7145
  async function readGitConfig(projectPath) {
6897
7146
  try {
6898
- const raw = await readFile16(
6899
- join22(projectPath, ".codebyplan", "git.json"),
7147
+ const raw = await readFile17(
7148
+ join23(projectPath, ".codebyplan", "git.json"),
6900
7149
  "utf-8"
6901
7150
  );
6902
7151
  return JSON.parse(raw);
@@ -6906,8 +7155,8 @@ async function readGitConfig(projectPath) {
6906
7155
  }
6907
7156
  async function readShipmentConfig(projectPath) {
6908
7157
  try {
6909
- const raw = await readFile16(
6910
- join22(projectPath, ".codebyplan", "shipment.json"),
7158
+ const raw = await readFile17(
7159
+ join23(projectPath, ".codebyplan", "shipment.json"),
6911
7160
  "utf-8"
6912
7161
  );
6913
7162
  return JSON.parse(raw);
@@ -6917,8 +7166,8 @@ async function readShipmentConfig(projectPath) {
6917
7166
  }
6918
7167
  async function readVendorConfig(projectPath) {
6919
7168
  try {
6920
- const raw = await readFile16(
6921
- join22(projectPath, ".codebyplan", "vendor.json"),
7169
+ const raw = await readFile17(
7170
+ join23(projectPath, ".codebyplan", "vendor.json"),
6922
7171
  "utf-8"
6923
7172
  );
6924
7173
  return JSON.parse(raw);
@@ -6926,10 +7175,10 @@ async function readVendorConfig(projectPath) {
6926
7175
  return null;
6927
7176
  }
6928
7177
  }
6929
- async function readE2eConfig(projectPath) {
7178
+ async function readE2eConfig2(projectPath) {
6930
7179
  try {
6931
- const raw = await readFile16(
6932
- join22(projectPath, ".codebyplan", "e2e.json"),
7180
+ const raw = await readFile17(
7181
+ join23(projectPath, ".codebyplan", "e2e.json"),
6933
7182
  "utf-8"
6934
7183
  );
6935
7184
  return JSON.parse(raw);
@@ -6985,14 +7234,14 @@ var init_server_detect = __esm({
6985
7234
  });
6986
7235
 
6987
7236
  // src/lib/port-verify.ts
6988
- import { readFile as readFile17 } from "node:fs/promises";
7237
+ import { readFile as readFile18 } from "node:fs/promises";
6989
7238
  async function verifyPorts(projectPath, portAllocations) {
6990
7239
  const mismatches = [];
6991
7240
  const allocatedPorts = new Set(portAllocations.map((a) => a.port));
6992
7241
  const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
6993
7242
  for (const pkgPath of packageJsonPaths) {
6994
7243
  try {
6995
- const raw = await readFile17(pkgPath, "utf-8");
7244
+ const raw = await readFile18(pkgPath, "utf-8");
6996
7245
  const pkg = JSON.parse(raw);
6997
7246
  const scriptPort = detectPortFromScripts(pkg);
6998
7247
  if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
@@ -7055,7 +7304,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
7055
7304
  }
7056
7305
  let pkg;
7057
7306
  try {
7058
- const raw = await readFile17(`${app.absPath}/package.json`, "utf-8");
7307
+ const raw = await readFile18(`${app.absPath}/package.json`, "utf-8");
7059
7308
  pkg = JSON.parse(raw);
7060
7309
  } catch {
7061
7310
  continue;
@@ -7263,10 +7512,10 @@ async function runTechStack() {
7263
7512
  );
7264
7513
  }
7265
7514
  try {
7266
- const { execSync: execSync8 } = await import("node:child_process");
7515
+ const { execSync: execSync9 } = await import("node:child_process");
7267
7516
  let branch = "main";
7268
7517
  try {
7269
- branch = execSync8("git symbolic-ref --short HEAD", {
7518
+ branch = execSync9("git symbolic-ref --short HEAD", {
7270
7519
  cwd: projectPath,
7271
7520
  encoding: "utf-8"
7272
7521
  }).trim();
@@ -7429,11 +7678,11 @@ async function ask(q, opts) {
7429
7678
  try {
7430
7679
  while (true) {
7431
7680
  const choices = q.choices.map((c) => `[${c.key}] ${c.label}`).join(" ");
7432
- const answer = await new Promise((resolve7) => {
7681
+ const answer = await new Promise((resolve8) => {
7433
7682
  rl.question(`${q.message}
7434
7683
  ${choices}
7435
7684
  > `, (input) => {
7436
- resolve7(input.trim().toLowerCase());
7685
+ resolve8(input.trim().toLowerCase());
7437
7686
  });
7438
7687
  });
7439
7688
  const match = q.choices.find(
@@ -8066,11 +8315,11 @@ var init_uninstall = __esm({
8066
8315
  // src/index.ts
8067
8316
  init_version();
8068
8317
  import { readFileSync as readFileSync8 } from "node:fs";
8069
- import { resolve as resolve6 } from "node:path";
8318
+ import { resolve as resolve7 } from "node:path";
8070
8319
  void (async () => {
8071
8320
  if (!process.env.CODEBYPLAN_API_KEY) {
8072
8321
  try {
8073
- const envPath = resolve6(process.cwd(), ".env.local");
8322
+ const envPath = resolve7(process.cwd(), ".env.local");
8074
8323
  const content = readFileSync8(envPath, "utf-8");
8075
8324
  for (const line of content.split("\n")) {
8076
8325
  const trimmed = line.trim();
@@ -8203,6 +8452,12 @@ void (async () => {
8203
8452
  await runVersionStatus2();
8204
8453
  process.exit(0);
8205
8454
  }
8455
+ if (arg === "upload-e2e-images") {
8456
+ const { runUploadE2eImagesCommand: runUploadE2eImagesCommand2 } = await Promise.resolve().then(() => (init_upload_e2e_images(), upload_e2e_images_exports));
8457
+ const rest = process.argv.slice(3);
8458
+ await runUploadE2eImagesCommand2(rest);
8459
+ process.exit(0);
8460
+ }
8206
8461
  if (arg === "cmux-sync") {
8207
8462
  const { runCmuxSync: runCmuxSync2 } = await Promise.resolve().then(() => (init_cmux_sync(), cmux_sync_exports));
8208
8463
  await runCmuxSync2();
@@ -8304,6 +8559,7 @@ void (async () => {
8304
8559
  codebyplan round sync-approvals Sync git diff and approvals with round/task state
8305
8560
  codebyplan bump Detect changed packages and patch-bump versions
8306
8561
  codebyplan ship Ship current feat branch to production via PR
8562
+ codebyplan upload-e2e-images Upload new/changed committed e2e PNGs for a checkpoint
8307
8563
  codebyplan scaffold-publish-workflow Write the publish-on-main GitHub workflow into ./.github/workflows/
8308
8564
  codebyplan branch migrate Rewrite branch_config from 3-branch to 2-tier model
8309
8565
  codebyplan claude Claude asset management (install/update/uninstall)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.13.13",
3
+ "version": "1.13.15",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {