@coderook/cli 0.23.0 → 0.25.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.
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  /**
8
- * CodeRook from the command line.
8
+ * cbx — the CodeBox engine from the command line, talking to CodeRook.
9
9
  *
10
10
  * The same engines the desktop application uses — the same scanner, the same
11
11
  * ignore rules, the same upload and download — with a terminal in front of
@@ -24,8 +24,11 @@ const api_js_1 = require("./api.js");
24
24
  const registry_js_1 = require("./registry.js");
25
25
  const help_js_1 = require("./help.js");
26
26
  const progress_js_1 = require("./progress.js");
27
+ const publish_js_1 = require("./publish.js");
27
28
  const project_commands_js_1 = require("./project_commands.js");
28
29
  const track_commands_js_1 = require("./track_commands.js");
30
+ const tracks_js_1 = require("../../desktop-app/src/main/tracks.js");
31
+ const transfer_command_js_1 = require("./transfer_command.js");
29
32
  const mcp_js_1 = require("./mcp.js");
30
33
  const skill_command_js_1 = require("./skill_command.js");
31
34
  const licence_commands_js_1 = require("./licence_commands.js");
@@ -43,7 +46,7 @@ const runner_js_1 = require("./runner.js");
43
46
  const config_js_1 = require("./config.js");
44
47
  /*
45
48
  Read from the package rather than written twice. A hardcoded copy had
46
- already drifted from the published version, which makes `coderook doctor`
49
+ already drifted from the published version, which makes `cbx doctor`
47
50
  worse than useless when working out what somebody is actually running.
48
51
  */
49
52
  const VERSION = (() => {
@@ -297,7 +300,7 @@ async function commandStatus(parsed) {
297
300
  if (stale.length) {
298
301
  console.log(dim(`${stale.length} file${stale.length === 1 ? "" : "s"} in the saved version ` +
299
302
  `${stale.length === 1 ? "is" : "are"} newer than the cop${stale.length === 1 ? "y" : "ies"} here ` +
300
- `— run ${accent("coderook get")} to bring ${stale.length === 1 ? "it" : "them"} down.`));
303
+ `— run ${accent("cbx get")} to bring ${stale.length === 1 ? "it" : "them"} down.`));
301
304
  for (const [path] of stale.slice(0, 10))
302
305
  console.log(dim(` behind ${path}`));
303
306
  }
@@ -328,7 +331,7 @@ async function commandImport(parsed) {
328
331
  if (!url) {
329
332
  console.error(red("Nothing to import from."));
330
333
  console.error("Give the address of a repository, for example:");
331
- console.error(` ${accent("coderook import https://github.com/owner/project")}`);
334
+ console.error(` ${accent("cbx import https://github.com/owner/project")}`);
332
335
  return 1;
333
336
  }
334
337
  if (!(0, import_command_js_1.looksLikeRepositoryUrl)(url)) {
@@ -408,7 +411,7 @@ async function commandImport(parsed) {
408
411
  if (code === 0 && plan.temporary) {
409
412
  console.log(dim(`The working copy stays at ${plan.folder} until you remove it.
410
413
  ` +
411
- `Run ${accent("coderook get " + plan.name)} anywhere to fetch it fresh.`));
414
+ `Run ${accent("cbx get " + plan.name)} anywhere to fetch it fresh.`));
412
415
  }
413
416
  return code;
414
417
  }
@@ -455,7 +458,23 @@ async function commandSubmit(parsed) {
455
458
  const rules = importing
456
459
  ? { shared: "", local: "" }
457
460
  : await (0, worktree_js_1.readRules)(folder);
458
- const files = await (0, worktree_js_1.changedFiles)(folder, rules, baseline);
461
+ /*
462
+ Whether a file that is no longer here means "delete it".
463
+
464
+ The default says no, and that is the right default: a missing path is
465
+ usually an unmounted drive, a folder moved while something was open, or a
466
+ scan that ran mid-copy — and the cost of guessing wrong is somebody's work
467
+ removed from the one place it was safe. So the saved copy is left alone
468
+ (docs/UPLOAD_POLICY.md).
469
+
470
+ It also meant nothing on this side could ever remove a file. Publishing
471
+ from git can, because there the question does not arise: `git rm` is a
472
+ recorded act, so the deletion is known rather than inferred. That left the
473
+ remote helper as the only route to something the tool itself could not do,
474
+ which is the wrong way round — so this is that capability, asked for
475
+ plainly.
476
+ */
477
+ const files = await (0, worktree_js_1.changedFiles)(folder, rules, baseline, hasFlag(parsed, "sync") ? "synchronize" : "add-and-update");
459
478
  /*
460
479
  An upgraded folder with no materialisation record and something that
461
480
  differs from its recorded version is ambiguous in the one way that
@@ -478,8 +497,8 @@ async function commandSubmit(parsed) {
478
497
  console.error(`${files.length} file${files.length === 1 ? " differs" : "s differ"} here:`);
479
498
  for (const file of files.slice(0, 20))
480
499
  console.error(` ${file.path}`);
481
- console.error(`\nIf that is your work, send it with ${accent("coderook submit --force")}.` +
482
- `\nIf it is an old copy, take theirs with ${accent("coderook get --replace")}.`);
500
+ console.error(`\nIf that is your work, send it with ${accent("cbx submit --force")}.` +
501
+ `\nIf it is an old copy, take theirs with ${accent("cbx get --replace")}.`);
483
502
  return 1;
484
503
  }
485
504
  }
@@ -493,7 +512,7 @@ async function commandSubmit(parsed) {
493
512
  console.log(behindOn
494
513
  ? `Nothing to submit; your own work is all saved. ${behindOn} file` +
495
514
  `${behindOn === 1 ? "" : "s"} here ${behindOn === 1 ? "is" : "are"} ` +
496
- `behind the saved version — run ${accent("coderook get")}.`
515
+ `behind the saved version — run ${accent("cbx get")}.`
497
516
  : "Nothing to submit; this folder matches the saved version.");
498
517
  return 0;
499
518
  }
@@ -591,18 +610,23 @@ Pass ${accent("--allow-secrets")} if these are not real keys.`);
591
610
  if (added) {
592
611
  console.log(`${accent("Added an MIT licence")}, so other people may use this.`);
593
612
  console.log(dim(` Change it with `) +
594
- accent("coderook licence <name>") +
613
+ accent("cbx licence <name>") +
595
614
  dim(`, or remove it with `) +
596
- accent("coderook licence none") +
615
+ accent("cbx licence none") +
597
616
  dim("."));
598
617
  console.log("");
599
618
  }
600
619
  const line = progressLine();
601
620
  const uploader = new upload_js_1.Uploader(config_js_1.credentials);
602
- const uploadRequest = {
621
+ /*
622
+ Built by the shared description, so this and the git remote helper cannot
623
+ drift. They already had: the helper listed deletions in `deletions` alone
624
+ and published versions that still held the deleted file.
625
+ */
626
+ const uploadRequest = (0, publish_js_1.uploadRequestFor)({
603
627
  localPath: folder,
604
- include: files.map((file) => file.path),
605
- deletions: files.filter((file) => file.deleted).map((file) => file.path),
628
+ changed: files.filter((file) => !file.deleted).map((file) => file.path),
629
+ deleted: files.filter((file) => file.deleted).map((file) => file.path),
606
630
  message,
607
631
  /*
608
632
  A folder's name is the right default and the wrong answer for import,
@@ -625,11 +649,8 @@ Pass ${accent("--allow-secrets")} if these are not real keys.`);
625
649
  would reject the publication and the import would be lossy.
626
650
  */
627
651
  ...(hasFlag(parsed, "allow-ignored") ? { allowIgnored: true } : {}),
628
- ...(link?.baseVersionId
629
- ? { expectedHeadVersionId: link.baseVersionId }
630
- : {}),
631
652
  ...(link ? { known: link.local ?? link.manifest ?? {} } : {}),
632
- };
653
+ });
633
654
  let result;
634
655
  try {
635
656
  const plan = await uploader.plan(uploadRequest, (progress) => {
@@ -660,23 +681,22 @@ Pass ${accent("--allow-secrets")} if these are not real keys.`);
660
681
  }
661
682
  catch (error) {
662
683
  done(line);
663
- const code = error.code;
664
- const text = error instanceof Error ? error.message : String(error);
665
684
  /*
666
- A connection that fails part way through says nothing about whether
667
- the work was done. It may well have beenthe service records the
668
- attempt, so running the same command again is answered with the
669
- version it already made rather than a second one. Saying so is the
670
- difference between a person retrying and a person wondering.
685
+ Classified by the shared description rather than by a private regular
686
+ expression, so a push and a submit disagree about nothing including
687
+ what counts as "the connection failed" and what that means for whether
688
+ the work was saved.
671
689
  */
672
- if (!code && /fetch failed|ECONNRESET|socket hang up|network|ETIMEDOUT/i.test(text)) {
690
+ const failure = (0, publish_js_1.classifyPublishFailure)(error);
691
+ const text = error instanceof Error ? error.message : String(error);
692
+ if (failure?.kind === "interrupted") {
673
693
  console.error(red(`
674
694
  The connection failed: ${text}`));
675
695
  console.error(`Your work may already have been saved. Run the same command again —` +
676
696
  ` it will not create a second version.`);
677
697
  return 1;
678
698
  }
679
- if (code === "merge_required" || code === "track_moved") {
699
+ if (failure?.kind === "conflict") {
680
700
  /*
681
701
  Somebody else saved to this project first. Everything that did not
682
702
  overlap has already been combined by the service; what is left is a
@@ -684,7 +704,7 @@ The connection failed: ${text}`));
684
704
  at it rather than to try again harder.
685
705
  */
686
706
  console.error(red(error instanceof Error ? error.message : String(error)));
687
- console.error(`\nRun ${accent("coderook get")} to bring the latest version down, then` +
707
+ console.error(`\nRun ${accent("cbx get")} to bring the latest version down, then` +
688
708
  ` submit again. Nothing was changed on your account.`);
689
709
  return 1;
690
710
  }
@@ -704,8 +724,8 @@ The connection failed: ${text}`));
704
724
  for (const conflict of result.mergeTrack.conflicts.slice(0, 20)) {
705
725
  console.log(` ${red(conflict.path)} ${dim(conflict.kind)}`);
706
726
  }
707
- console.log(`\nRun ${accent(`coderook merge ${result.mergeTrack.reference}`)} to decide,` +
708
- ` or ${accent("coderook merges")} to see everything waiting.`);
727
+ console.log(`\nRun ${accent(`cbx merge ${result.mergeTrack.reference}`)} to decide,` +
728
+ ` or ${accent("cbx merges")} to see everything waiting.`);
709
729
  return 0;
710
730
  }
711
731
  /*
@@ -714,9 +734,29 @@ The connection failed: ${text}`));
714
734
  attempt name exists to make survivable.
715
735
  */
716
736
  (0, faults_js_1.maybeFail)("submit:after-commit");
737
+ /*
738
+ What the project is actually called, not what this folder is called.
739
+
740
+ A first publish recorded the folder's own name, which is right only when
741
+ nothing renamed the project — and two things routinely do: `--name`, and
742
+ the service turning a display name into a slug. When they differ, every
743
+ command that finds the project through this folder looks up a name that
744
+ does not exist: `tracks`, `versions`, `issues`, `releases`, `people`,
745
+ `watch`, `delete` and the rest all answered "No project matching …" in a
746
+ folder that had just published to it successfully.
747
+
748
+ Asked of the service rather than guessed, and only on a first publish,
749
+ because that is the only time this is not already known.
750
+ */
751
+ let recordedSlug = link?.slug;
752
+ if (!recordedSlug) {
753
+ recordedSlug = await (0, api_js_2.projects)()
754
+ .then((all) => all.find((candidate) => candidate.id === result.repositoryId)?.slug)
755
+ .catch(() => undefined);
756
+ }
717
757
  await (0, config_js_1.writeLink)(folder, {
718
758
  repositoryId: result.repositoryId,
719
- slug: link?.slug ?? node_path_1.default.basename(folder),
759
+ slug: recordedSlug ?? uploadRequest.projectName ?? node_path_1.default.basename(folder),
720
760
  sequence: result.sequence,
721
761
  // What this folder is now working from, so the next submit can say so.
722
762
  versionId: result.versionId,
@@ -815,8 +855,8 @@ async function commandGet(parsed) {
815
855
  console.error(red("This folder was connected by an older version of CodeRook,"));
816
856
  console.error("which did not record what it had received, so changed files here");
817
857
  console.error("cannot be told apart from copies left behind by a merge.\n");
818
- console.error(`Save them with ${accent("coderook submit")} if they are your work, or ` +
819
- `take the\nsaved version exactly with ${accent("coderook get --replace")}.`);
858
+ console.error(`Save them with ${accent("cbx submit")} if they are your work, or ` +
859
+ `take the\nsaved version exactly with ${accent("cbx get --replace")}.`);
820
860
  return 1;
821
861
  }
822
862
  }
@@ -846,15 +886,15 @@ async function commandGet(parsed) {
846
886
  incoming.has(file.path) &&
847
887
  edited.some((one) => one.path === file.path));
848
888
  if (!changedInTheGap.length) {
849
- return fetchInto(link.repositoryId, folder, link.slug, link.local ?? null, "replace");
889
+ return fetchInto(link.repositoryId, folder, link.slug, link.local ?? null, "replace", await (0, track_commands_js_1.trackFor)(folder));
850
890
  }
851
891
  console.error(red(`${changedInTheGap.length} file${changedInTheGap.length === 1 ? "" : "s"} ` +
852
892
  `changed while the interrupted fetch was stopped:`));
853
893
  for (const file of changedInTheGap.slice(0, 20))
854
894
  console.error(` ${file.path}`);
855
895
  console.error(`
856
- Save them with ${accent("coderook submit")}, or finish the fetch and ` +
857
- `discard them with ${accent("coderook get --replace")}.`);
896
+ Save them with ${accent("cbx submit")}, or finish the fetch and ` +
897
+ `discard them with ${accent("cbx get --replace")}.`);
858
898
  return 1;
859
899
  }
860
900
  /*
@@ -874,8 +914,8 @@ Save them with ${accent("coderook submit")}, or finish the fetch and ` +
874
914
  console.error(` ${file.path}`);
875
915
  if (atRisk.length > 20)
876
916
  console.error(dim(` …and ${atRisk.length - 20} more`));
877
- console.error(`\nSave them first with ${accent("coderook submit")}, or discard them ` +
878
- `with ${accent("coderook get --replace")}.`);
917
+ console.error(`\nSave them first with ${accent("cbx submit")}, or discard them ` +
918
+ `with ${accent("cbx get --replace")}.`);
879
919
  return 1;
880
920
  }
881
921
  }
@@ -885,12 +925,14 @@ Save them with ${accent("coderook submit")}, or finish the fetch and ` +
885
925
  what the version actually changed is written over — unless somebody asks
886
926
  for an exact copy, which is how a damaged folder is repaired.
887
927
  */
888
- return fetchInto(link.repositoryId, folder, link.slug, link.local ?? null, replacing ? "replace" : "reconcile");
928
+ return fetchInto(link.repositoryId, folder, link.slug, link.local ?? null, replacing ? "replace" : "reconcile",
929
+ // The line this folder saves to, so catching up brings that line down.
930
+ await (0, track_commands_js_1.trackFor)(folder));
889
931
  }
890
932
  async function commandClone(parsed) {
891
933
  const reference = parsed.positional[0];
892
934
  if (!reference) {
893
- console.error(red("Which project? Try: coderook clone <project>"));
935
+ console.error(red("Which project? Try: cbx clone <project>"));
894
936
  return 1;
895
937
  }
896
938
  const project = await (0, api_js_2.findProject)(reference);
@@ -903,7 +945,34 @@ async function commandClone(parsed) {
903
945
  return 1;
904
946
  }
905
947
  const destination = node_path_1.default.resolve(parsed.positional[1] ?? project.slug);
906
- return fetchInto(project.id, destination, project.slug);
948
+ /*
949
+ A clone opens on the project's default line, or on the one named.
950
+
951
+ It used to take whichever version was newest across every line, which is
952
+ only the same thing while a project has one. As soon as somebody pushed to
953
+ a branch, that branch held the newest version — so cloning the project
954
+ handed you their unfinished work under the impression it was the project.
955
+ Nothing said so: the fetch reported a version number and the files looked
956
+ like a project, just not the one anybody asked for.
957
+
958
+ Naming a line here also removes the clone-switch-fetch dance that was the
959
+ only way to get a branch before, which is what exposed the same fault in
960
+ `get`.
961
+ */
962
+ const wanted = flagText(parsed, "track") ?? project.defaultBranch;
963
+ /*
964
+ Falling back rather than failing when the default line does not exist.
965
+ `defaultBranch` is a property of the project and a line can be renamed out
966
+ from under it; refusing to clone at all in that case would be a worse
967
+ answer than the old behaviour. An explicitly named line is still refused,
968
+ because there the person said which one they meant.
969
+ */
970
+ const lines = await new tracks_js_1.Tracks(config_js_1.credentials).list(project.id);
971
+ const usable = lines.some((line) => line.kind === "line" && line.name === wanted && line.headVersionId);
972
+ if (!usable && !flagText(parsed, "track")) {
973
+ console.log(dim(`This project has no line called ${wanted}; taking its newest version.`));
974
+ }
975
+ return fetchInto(project.id, destination, project.slug, null, "replace", usable || flagText(parsed, "track") ? wanted : undefined);
907
976
  }
908
977
  async function fetchInto(repositoryId, destination, slug,
909
978
  /*
@@ -912,9 +981,45 @@ async function fetchInto(repositoryId, destination, slug,
912
981
  is removed, because "missing from the version" cannot then be told from
913
982
  "never came from the version at all".
914
983
  */
915
- held, mode = "replace") {
984
+ held, mode = "replace",
985
+ /*
986
+ The line to fetch. Omitted means the project's newest version, which is
987
+ right for a clone and wrong for a folder that has chosen a line.
988
+ */
989
+ trackName) {
916
990
  const downloader = new download_js_1.Downloader(config_js_1.credentials);
917
- const latest = (await downloader.versions(repositoryId))[0];
991
+ const all = await downloader.versions(repositoryId);
992
+ /*
993
+ The head of this folder's line, not the newest version in the project.
994
+
995
+ Those are the same thing only while a project has one line. With two, the
996
+ newest version usually belongs to the *other* one — so switching to a line
997
+ and fetching brought down the line you had just left, reported success,
998
+ and left the folder holding the wrong contents while still claiming to
999
+ save to the line you asked for. The next publish would then be computed
1000
+ against a baseline from somewhere else.
1001
+
1002
+ Worse than a wrong answer: `track` ends by telling you to run `get`, so
1003
+ the command that set this up was the one recommending it.
1004
+ */
1005
+ let latest = all[0];
1006
+ if (trackName) {
1007
+ const chosen = (await new tracks_js_1.Tracks(config_js_1.credentials).list(repositoryId)).find((candidate) => candidate.name === trackName && candidate.kind === "line");
1008
+ if (!chosen) {
1009
+ console.error(red(`This project has no line called ${trackName}.`));
1010
+ return 1;
1011
+ }
1012
+ if (!chosen.headVersionId) {
1013
+ console.error(red(`The line ${trackName} has nothing saved to it yet.`));
1014
+ return 1;
1015
+ }
1016
+ const head = all.find((version) => version.id === chosen.headVersionId);
1017
+ if (!head) {
1018
+ console.error(red(`Could not read the current version of ${trackName}.`));
1019
+ return 1;
1020
+ }
1021
+ latest = head;
1022
+ }
918
1023
  if (!latest) {
919
1024
  console.error(red("That project has no saved version."));
920
1025
  return 1;
@@ -953,6 +1058,16 @@ held, mode = "replace") {
953
1058
  // version holds and the two agree again.
954
1059
  local: result.manifest,
955
1060
  manifest: result.manifest,
1061
+ /*
1062
+ The line this folder is on survives being caught up.
1063
+
1064
+ This record is built fresh rather than merged, so anything not named
1065
+ here is dropped — and `track` was not named. Fetching therefore moved
1066
+ the folder back to `main` every time, silently: the files were right,
1067
+ the folder reported the wrong line, and the next publish went somewhere
1068
+ nobody chose.
1069
+ */
1070
+ ...(trackName ? { track: trackName } : {}),
956
1071
  // Reached the end, so there is no longer a fetch in flight.
957
1072
  });
958
1073
  // The record is written; the run has not reported success yet.
@@ -994,7 +1109,7 @@ async function suggestRules(folder, apply) {
994
1109
  const recommended = suggestions.filter((one) => one.recommended);
995
1110
  if (!apply) {
996
1111
  console.log("");
997
- console.log(dim("Add the + ones with ") + accent("coderook ignore --suggest --apply"));
1112
+ console.log(dim("Add the + ones with ") + accent("cbx ignore --suggest --apply"));
998
1113
  return 0;
999
1114
  }
1000
1115
  if (!recommended.length) {
@@ -1056,7 +1171,7 @@ async function commandBundle(parsed) {
1056
1171
  async function commandUnbundle(parsed) {
1057
1172
  const source = parsed.positional[0];
1058
1173
  if (!source) {
1059
- console.error(red("Which bundle? Try: coderook unbundle <file.cbx> [folder]"));
1174
+ console.error(red("Which bundle? Try: cbx unbundle <file.cbx> [folder]"));
1060
1175
  return 1;
1061
1176
  }
1062
1177
  const manifest = await (0, cbx_js_1.readManifest)(node_path_1.default.resolve(source));
@@ -1073,7 +1188,7 @@ async function commandUnbundle(parsed) {
1073
1188
  async function commandInspect(parsed) {
1074
1189
  const source = parsed.positional[0];
1075
1190
  if (!source) {
1076
- console.error(red("Which bundle? Try: coderook inspect <file.cbx>"));
1191
+ console.error(red("Which bundle? Try: cbx inspect <file.cbx>"));
1077
1192
  return 1;
1078
1193
  }
1079
1194
  const manifest = await (0, cbx_js_1.readManifest)(node_path_1.default.resolve(source));
@@ -1090,7 +1205,7 @@ async function commandInspect(parsed) {
1090
1205
  }
1091
1206
  async function commandDoctor() {
1092
1207
  const service = await (0, api_js_2.health)().catch(() => null);
1093
- console.log(`CodeRook CLI ${VERSION} · Node ${node_process_1.default.versions.node} · ${node_process_1.default.platform}`);
1208
+ console.log(`cbx ${VERSION} · Node ${node_process_1.default.versions.node} · ${node_process_1.default.platform}`);
1094
1209
  console.log(`Config: ${(0, config_js_1.configDirectory)()}`);
1095
1210
  console.log(service
1096
1211
  ? `Service: ${service.status} · schema ${service.schemaVersion}` +
@@ -1134,7 +1249,7 @@ async function commandMerges(parsed) {
1134
1249
  console.log(`${accent(merge.reference)} ${counts ? `${counts.unresolved} of ${counts.total} still to decide` : ""} ${dim(new Date(merge.createdAt).toLocaleString())}`);
1135
1250
  }
1136
1251
  console.log(dim(`
1137
- Run coderook merge <reference> to look at one.`));
1252
+ Run cbx merge <reference> to look at one.`));
1138
1253
  return 0;
1139
1254
  }
1140
1255
  /** Find a merge by the reference a person would type, such as M-2. */
@@ -1157,7 +1272,7 @@ async function findMerge(folder, reference) {
1157
1272
  async function commandMerge(parsed) {
1158
1273
  const reference = parsed.positional[0];
1159
1274
  if (!reference) {
1160
- console.error(red("Which merge? Try: coderook merge M-1"));
1275
+ console.error(red("Which merge? Try: cbx merge M-1"));
1161
1276
  return 1;
1162
1277
  }
1163
1278
  const folder = folderFor({ ...parsed, positional: parsed.positional.slice(1) });
@@ -1216,7 +1331,7 @@ ${accent(now.mergeTrack.reference)} · ${now.provisional.fileCount} files · ` +
1216
1331
  const applied = await (0, api_js_1.applyMerge)(summary.id);
1217
1332
  console.log(`
1218
1333
  Applied as ${accent(`v${applied.version.sequence}`)}.`);
1219
- console.log(dim("Run coderook get to bring it down to this folder."));
1334
+ console.log(dim("Run cbx get to bring it down to this folder."));
1220
1335
  return 0;
1221
1336
  }
1222
1337
  if (!decision) {
@@ -1227,7 +1342,7 @@ Add --path <file> for one file, then --apply when ready.`));
1227
1342
  }
1228
1343
  else if (now.provisional.ready) {
1229
1344
  console.log(dim(`
1230
- Run coderook merge ${now.mergeTrack.reference} --apply to publish it.`));
1345
+ Run cbx merge ${now.mergeTrack.reference} --apply to publish it.`));
1231
1346
  }
1232
1347
  return 0;
1233
1348
  }
@@ -1247,7 +1362,7 @@ Run coderook merge ${now.mergeTrack.reference} --apply to publish it.`));
1247
1362
  async function commandRunner(parsed) {
1248
1363
  const reference = parsed.positional[0] ?? flagText(parsed, "project", "p");
1249
1364
  if (!reference) {
1250
- console.error(red("Which project? coderook runner <project>"));
1365
+ console.error(red("Which project? cbx runner <project>"));
1251
1366
  return 1;
1252
1367
  }
1253
1368
  const project = await (0, api_js_2.findProject)(reference);
@@ -1393,7 +1508,7 @@ const SPECS = [
1393
1508
  },
1394
1509
  {
1395
1510
  name: "submit",
1396
- aliases: ["publish"],
1511
+ aliases: ["publish", "push"],
1397
1512
  group: "Working with a folder",
1398
1513
  summary: "send the changes as a new version",
1399
1514
  usage: 'submit [folder] -m "…"',
@@ -1411,6 +1526,10 @@ const SPECS = [
1411
1526
  flags: "--allow-secrets",
1412
1527
  description: "send files that look like credentials, and files with keys inside",
1413
1528
  },
1529
+ {
1530
+ flags: "--sync",
1531
+ description: "treat a file that is no longer here as deleted, rather than keeping the saved copy",
1532
+ },
1414
1533
  {
1415
1534
  flags: "--track <name>",
1416
1535
  description: "save onto this line, just this once",
@@ -1421,8 +1540,8 @@ const SPECS = [
1421
1540
  },
1422
1541
  ],
1423
1542
  examples: [
1424
- 'coderook submit -m "Fix the export dialog"',
1425
- 'coderook submit --track spike -m "Try the other encoder"',
1543
+ 'cbx submit -m "Fix the export dialog"',
1544
+ 'cbx submit --track spike -m "Try the other encoder"',
1426
1545
  ],
1427
1546
  run: commandSubmit,
1428
1547
  },
@@ -1453,11 +1572,48 @@ const SPECS = [
1453
1572
  },
1454
1573
  ],
1455
1574
  examples: [
1456
- "coderook import https://github.com/owner/project",
1457
- "coderook import git@github.com:owner/project.git ./project",
1575
+ "cbx import https://github.com/owner/project",
1576
+ "cbx import git@github.com:owner/project.git ./project",
1458
1577
  ],
1459
1578
  run: commandImport,
1460
1579
  },
1580
+ {
1581
+ /*
1582
+ Distinct from `import`, which takes a snapshot. This moves the history,
1583
+ the branches, the tags and what the project says about itself — the
1584
+ things somebody leaving a host is actually worried about losing.
1585
+ */
1586
+ name: "transfer",
1587
+ aliases: ["migrate"],
1588
+ group: "Getting started",
1589
+ summary: "move a whole repository here from GitHub, GitLab or Codeberg",
1590
+ usage: "transfer <address>",
1591
+ detail: "Brings the commits, every branch, the tags and the description across.\n" +
1592
+ "Each commit becomes a version and each branch a line, so a long history\n" +
1593
+ "takes a while; the count and an estimate are printed before it starts.\n\n" +
1594
+ "The history is published by running `git push` against the CodeRook\n" +
1595
+ "remote — the same route anybody else uses, deliberately, because the\n" +
1596
+ "claim being made is that git repositories work here.\n\n" +
1597
+ "A private repository needs a read token in the environment:\n" +
1598
+ "GITHUB_TOKEN, GITLAB_TOKEN or FORGE_TOKEN. It is never asked for on the\n" +
1599
+ "command line and never stored.\n\n" +
1600
+ "The project is created private whatever the source was, unless you say\n" +
1601
+ "otherwise. Pull requests, CI configuration and collaborators do not come\n" +
1602
+ "across; the report at the end lists what did and what did not.",
1603
+ options: [
1604
+ { flags: "--name <name>", description: "call the project this, rather than the repository's name" },
1605
+ {
1606
+ flags: "--visibility <how>",
1607
+ description: "private (default), same as the source, or public",
1608
+ },
1609
+ { flags: "--issues", description: "also bring the open issues; needs a read token" },
1610
+ ],
1611
+ examples: [
1612
+ "cbx transfer https://github.com/owner/project",
1613
+ "cbx transfer https://codeberg.org/owner/project --visibility same",
1614
+ ],
1615
+ run: transfer_command_js_1.commandTransfer,
1616
+ },
1461
1617
  {
1462
1618
  /*
1463
1619
  Where the next save goes, which is a property of this folder rather
@@ -1465,14 +1621,14 @@ const SPECS = [
1465
1621
  different lines, which is most of the point of having them.
1466
1622
  */
1467
1623
  name: "track",
1468
- aliases: ["switch"],
1624
+ aliases: ["switch", "checkout"],
1469
1625
  group: "Working with a folder",
1470
1626
  summary: "show or change the line this folder saves to",
1471
1627
  usage: "track [name]",
1472
1628
  detail: "With no name, prints the line this folder saves to. With one, switches\n" +
1473
1629
  "to it. Switching says where the next save goes and nothing else: no\n" +
1474
1630
  "files move and nothing is fetched, so it is instant and safe to change\n" +
1475
- "your mind. Run `coderook get` afterwards to bring that line's files\n" +
1631
+ "your mind. Run `cbx get` afterwards to bring that line's files\n" +
1476
1632
  "into the folder.\n\n" +
1477
1633
  "A name that does not exist is refused rather than created, because a\n" +
1478
1634
  "typo in a branch name is an ordinary thing to do and a line called\n" +
@@ -1481,23 +1637,25 @@ const SPECS = [
1481
1637
  options: [
1482
1638
  { flags: "-n, --new", description: "start this line from where the project is now" },
1483
1639
  ],
1484
- examples: ["coderook track", "coderook track spike --new", "coderook track main"],
1640
+ examples: ["cbx track", "cbx track spike --new", "cbx track main"],
1485
1641
  run: track_commands_js_1.commandTrack,
1486
1642
  },
1487
1643
  {
1488
1644
  name: "tracks",
1645
+ aliases: ["branch", "branches"],
1489
1646
  group: "Your projects",
1490
1647
  summary: "the lines a project has, and any waiting on a decision",
1491
1648
  usage: "tracks [project]",
1492
1649
  detail: "Lists every line on the project, marking the one this folder saves to.\n" +
1493
1650
  "Merges waiting on a decision are listed beside them rather than hidden,\n" +
1494
1651
  "because somebody looking for where they can save needs to see the one\n" +
1495
- "they cannot. Finish one with `coderook merge`.",
1496
- examples: ["coderook tracks", "coderook tracks my-project"],
1652
+ "they cannot. Finish one with `cbx merge`.",
1653
+ examples: ["cbx tracks", "cbx tracks my-project"],
1497
1654
  run: track_commands_js_1.commandTracks,
1498
1655
  },
1499
1656
  {
1500
1657
  name: "get",
1658
+ aliases: ["pull"],
1501
1659
  group: "Working with a folder",
1502
1660
  summary: "bring this folder up to date",
1503
1661
  usage: "get [folder]",
@@ -1511,6 +1669,13 @@ const SPECS = [
1511
1669
  group: "Working with a folder",
1512
1670
  summary: "fetch a project into a new folder",
1513
1671
  usage: "clone <project> [dir]",
1672
+ options: [
1673
+ {
1674
+ flags: "--track <name>",
1675
+ description: "fetch this line rather than the project's newest version",
1676
+ },
1677
+ ],
1678
+ examples: ["cbx clone my-project", "cbx clone my-project --track spike"],
1514
1679
  run: commandClone,
1515
1680
  },
1516
1681
  {
@@ -1540,7 +1705,7 @@ const SPECS = [
1540
1705
  description: "replace a licence that is already there",
1541
1706
  },
1542
1707
  ],
1543
- examples: ["coderook licence", "coderook licence MIT", "coderook licence Apache-2.0"],
1708
+ examples: ["cbx licence", "cbx licence MIT", "cbx licence Apache-2.0"],
1544
1709
  run: licence_commands_js_1.commandLicence,
1545
1710
  },
1546
1711
  {
@@ -1587,6 +1752,7 @@ const SPECS = [
1587
1752
  },
1588
1753
  {
1589
1754
  name: "versions",
1755
+ aliases: ["log"],
1590
1756
  group: "Your projects",
1591
1757
  summary: "what has been saved to a project",
1592
1758
  usage: "versions [project]",
@@ -1612,9 +1778,9 @@ const SPECS = [
1612
1778
  { flags: "--request [off]", description: "automated calls to its endpoints" },
1613
1779
  ],
1614
1780
  examples: [
1615
- "coderook ai my-project",
1616
- "coderook ai my-project --read off",
1617
- "coderook ai my-project --download off --request off",
1781
+ "cbx ai my-project",
1782
+ "cbx ai my-project --read off",
1783
+ "cbx ai my-project --download off --request off",
1618
1784
  ],
1619
1785
  run: project_commands_js_1.commandAi,
1620
1786
  },
@@ -1627,16 +1793,43 @@ const SPECS = [
1627
1793
  { flags: '--new "<title>"', description: "open a new issue" },
1628
1794
  { flags: "--body <text>", description: "the description for a new one" },
1629
1795
  ],
1630
- examples: ['coderook issues my-project --new "Crash on export"'],
1796
+ examples: ['cbx issues my-project --new "Crash on export"'],
1631
1797
  run: service_commands_js_1.commandIssues,
1632
1798
  },
1633
1799
  {
1634
1800
  name: "releases",
1801
+ aliases: ["tags"],
1635
1802
  group: "Your projects",
1636
1803
  summary: "what has been released, and its files",
1637
1804
  usage: "releases [project]",
1638
1805
  run: service_commands_js_1.commandReleases,
1639
1806
  },
1807
+ {
1808
+ /*
1809
+ A release is a version with a name on it, so this names one rather than
1810
+ creating anything. Pushing a git tag does the same thing by the same
1811
+ route — the tag is the name, the tagged commit picks the version.
1812
+ */
1813
+ name: "release",
1814
+ aliases: ["tag"],
1815
+ group: "Your projects",
1816
+ summary: "give a version a name, so people know which one to fetch",
1817
+ usage: "release <name> [project]",
1818
+ detail: "Names the newest version unless you name another with --version.\n" +
1819
+ "Nothing is uploaded: a release is a version with a name on it, so the\n" +
1820
+ "version has to exist already.\n\n" +
1821
+ "The same thing happens when a git tag is pushed to a CodeRook remote —\n" +
1822
+ "`git push cbx v1.0` and `cbx release v1.0` are one action.",
1823
+ options: [
1824
+ { flags: "--version <n>", description: "name this version instead of the newest" },
1825
+ { flags: "--notes <text>", description: "what changed, for the release page" },
1826
+ ],
1827
+ examples: [
1828
+ "cbx release v1.0",
1829
+ 'cbx release v1.2 --version 41 --notes "Fixes the export dialog"',
1830
+ ],
1831
+ run: service_commands_js_1.commandRelease,
1832
+ },
1640
1833
  {
1641
1834
  name: "actions",
1642
1835
  aliases: ["workflows"],
@@ -1644,7 +1837,7 @@ const SPECS = [
1644
1837
  summary: "the automations a project has",
1645
1838
  usage: "actions [project]",
1646
1839
  detail: "Shows what each action runs, what it runs on, and how many of its\n" +
1647
- "runs have passed. Use `coderook runner` to execute them on this machine.",
1840
+ "runs have passed. Use `cbx runner` to execute them on this machine.",
1648
1841
  run: service_commands_js_1.commandWorkflows,
1649
1842
  },
1650
1843
  {
@@ -1661,7 +1854,7 @@ const SPECS = [
1661
1854
  description: "queued, running, passed, failed or cancelled",
1662
1855
  },
1663
1856
  ],
1664
- examples: ["coderook runs --status failed"],
1857
+ examples: ["cbx runs --status failed"],
1665
1858
  run: service_commands_js_1.commandRuns,
1666
1859
  },
1667
1860
  {
@@ -1672,7 +1865,7 @@ const SPECS = [
1672
1865
  detail: "The output the runner sent up, in order. Anything the command wrote to\n" +
1673
1866
  "stderr is shown in red. Exits 1 when the run failed, so this can be\n" +
1674
1867
  "the last line of a script.",
1675
- examples: ["coderook logs 12", "coderook logs #12 my-project"],
1868
+ examples: ["cbx logs 12", "cbx logs #12 my-project"],
1676
1869
  run: service_commands_js_1.commandLogs,
1677
1870
  },
1678
1871
  {
@@ -1696,7 +1889,7 @@ const SPECS = [
1696
1889
  { flags: "--invite <email>", description: "ask somebody to join" },
1697
1890
  { flags: "--role <role>", description: "what they may do (default member)" },
1698
1891
  ],
1699
- examples: ["coderook people my-project --invite sam@example.com"],
1892
+ examples: ["cbx people my-project --invite sam@example.com"],
1700
1893
  run: service_commands_js_1.commandCollaborators,
1701
1894
  },
1702
1895
  {
@@ -1781,7 +1974,7 @@ const SPECS = [
1781
1974
  { flags: "--labels <a,b>", description: "what kinds of run it answers to" },
1782
1975
  { flags: "--poll <seconds>", description: "how long between asks" },
1783
1976
  ],
1784
- examples: ["coderook runner my-game --labels windows,signing"],
1977
+ examples: ["cbx runner my-game --labels windows,signing"],
1785
1978
  run: commandRunner,
1786
1979
  },
1787
1980
  {
@@ -1805,7 +1998,7 @@ const SPECS = [
1805
1998
  "repository for everybody who clones it.\n" +
1806
1999
  "\n" +
1807
2000
  "It teaches the command line rather than the MCP server, because that\n" +
1808
- "needs no configuration at all. `coderook mcp` is still there when a\n" +
2001
+ "needs no configuration at all. `cbx mcp` is still there when a\n" +
1809
2002
  "structured connection is wanted.\n",
1810
2003
  options: [
1811
2004
  {
@@ -1813,7 +2006,7 @@ const SPECS = [
1813
2006
  description: "install into this project rather than for you",
1814
2007
  },
1815
2008
  ],
1816
- examples: ["coderook skill", "coderook skill --project"],
2009
+ examples: ["cbx skill", "cbx skill --project"],
1817
2010
  run: skill_command_js_1.commandSkill,
1818
2011
  },
1819
2012
  {
@@ -1832,11 +2025,11 @@ const SPECS = [
1832
2025
  "assistant at it and it starts and stops the process itself.\n" +
1833
2026
  "\n" +
1834
2027
  "Claude Code:\n" +
1835
- " claude mcp add coderook -- coderook mcp\n" +
2028
+ " claude mcp add coderook -- cbx mcp\n" +
1836
2029
  "\n" +
1837
2030
  "Codex, in ~/.codex/config.toml:\n" +
1838
2031
  " [mcp_servers.coderook]\n" +
1839
- " command = 'coderook'\n" +
2032
+ " command = 'cbx'\n" +
1840
2033
  " args = ['mcp']\n" +
1841
2034
  "\n" +
1842
2035
  "Everything it offers reads. It lists projects, versions and files, shows\n" +
@@ -1846,7 +2039,7 @@ const SPECS = [
1846
2039
  "\n" +
1847
2040
  "A project whose owner has turned off machine reading is refused, in\n" +
1848
2041
  "words rather than as a status code.\n",
1849
- examples: ["claude mcp add coderook -- coderook mcp"],
2042
+ examples: ["claude mcp add coderook -- cbx mcp"],
1850
2043
  run: () => (0, mcp_js_1.commandMcp)(VERSION),
1851
2044
  },
1852
2045
  {
@@ -1869,7 +2062,7 @@ async function main(argv) {
1869
2062
  return 0;
1870
2063
  }
1871
2064
  /*
1872
- `coderook help submit` and `coderook submit --help` reach the same page.
2065
+ `cbx help submit` and `cbx submit --help` reach the same page.
1873
2066
  People reach for both, and one of them silently doing something else is
1874
2067
  the kind of small betrayal that makes a tool feel unreliable.
1875
2068
  */