@coderook/cli 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,13 +59,51 @@ the desktop is recognised rather than treated as new.
59
59
  uploaded again, and the version it records still names every file in the
60
60
  project — a version is a snapshot, not a difference.
61
61
 
62
+ ## Lines of work
63
+
64
+ A project can have more than one line, so two people can save without one
65
+ landing on top of the other.
66
+
67
+ ```
68
+ coderook tracks the lines this project has
69
+ coderook track the line this folder saves to
70
+ coderook track spike --new start a line and switch to it
71
+ coderook track main switch back
72
+ coderook submit --track spike -m "Try the other encoder"
73
+ ```
74
+
75
+ Switching says where the next save goes and nothing else. No files move and
76
+ nothing is fetched, so it is instant and safe to change your mind — run
77
+ `coderook get` afterwards to bring that line's files into the folder.
78
+
79
+ The line is remembered per folder, not per account, so two checkouts of one
80
+ project can sit on different lines. A folder that has never been switched
81
+ saves to `main`, which is what every folder meant before lines existed.
82
+
83
+ A name that does not exist is refused rather than created. A typo in a branch
84
+ name is an ordinary thing to do, and a line called `mian` puts work somewhere
85
+ nobody will look for it — `--new` is how you say you meant it.
86
+
87
+ If two saves land on the same line anyway, the second becomes a merge waiting
88
+ on a decision. `coderook merges` lists them and `coderook merge <ref>` walks
89
+ through it; `coderook tracks` shows them beside the ordinary lines, because
90
+ somebody looking for where they can save needs to see the one they cannot.
91
+
62
92
  ## Ignore rules
63
93
 
64
94
  ```
65
- coderook rules show the rules in force
66
- coderook rules --init write a starter .gitignore
95
+ coderook rules show the rules in force
96
+ coderook rules --init write a starter .gitignore
97
+ coderook rules --suggest what is here that probably should not be sent
98
+ coderook rules --suggest --apply add the confident ones
67
99
  ```
68
100
 
101
+ `--suggest` looks at what the folder would actually send and names the
102
+ dependency directories, virtual environments and build output in it, with how
103
+ much each is costing you. Lines marked `+` are safe to assume and are the ones
104
+ `--apply` writes; anything that might be the work itself is marked `?` and left
105
+ for you, because the cost of guessing wrong is a project that is not backed up.
106
+
69
107
  Rules live in the project's own `.gitignore`, so CodeRook, git, the website
70
108
  and the desktop application all read one file. A `!` line puts something back.
71
109
  Personal exclusions that should not reach a collaborator belong in
@@ -84,6 +122,37 @@ it helps, raw where it does not, and a SHA-256 for every chunk and file.
84
122
  Extraction is verified and atomic — a damaged bundle fails rather than leaving
85
123
  a half-written tree.
86
124
 
125
+ ## Claude Code and Codex
126
+
127
+ CodeRook can be a tool your assistant uses while it works. Both speak the same
128
+ protocol, so one server covers them.
129
+
130
+ Claude Code:
131
+
132
+ ```
133
+ claude mcp add coderook -- coderook mcp
134
+ ```
135
+
136
+ Codex, in `~/.codex/config.toml`:
137
+
138
+ ```toml
139
+ [mcp_servers.coderook]
140
+ command = "coderook"
141
+ args = ["mcp"]
142
+ ```
143
+
144
+ It offers five things, all of which read: your projects, a project's versions,
145
+ the files in a version, one file's contents at a version, and what has changed
146
+ in a local folder.
147
+
148
+ It cannot save a version, delete anything, or sign in or out. That is
149
+ deliberate — an assistant that goes wrong can waste your time but not your
150
+ work, and saving stays something a person types. A project whose owner has
151
+ turned off machine reading is refused in words rather than as a status code.
152
+
153
+ The server is launched by the assistant and exits with it; there is no reason
154
+ to run `coderook mcp` by hand.
155
+
87
156
  ## Checking things
88
157
 
89
158
  ```
@@ -24,12 +24,15 @@ const registry_js_1 = require("./registry.js");
24
24
  const help_js_1 = require("./help.js");
25
25
  const progress_js_1 = require("./progress.js");
26
26
  const project_commands_js_1 = require("./project_commands.js");
27
+ const track_commands_js_1 = require("./track_commands.js");
28
+ const mcp_js_1 = require("./mcp.js");
27
29
  const service_commands_js_1 = require("./service_commands.js");
28
30
  const worktree_js_1 = require("../../desktop-app/src/main/worktree.js");
29
31
  const upload_js_1 = require("../../desktop-app/src/main/upload.js");
30
32
  const download_js_1 = require("../../desktop-app/src/main/download.js");
31
33
  const faults_js_1 = require("../../desktop-app/src/main/faults.js");
32
34
  const identify_js_1 = require("../../desktop-app/src/main/identify.js");
35
+ const detect_js_1 = require("../../desktop-app/src/main/detect.js");
33
36
  const cbx_js_1 = require("../../desktop-app/src/main/cbx.js");
34
37
  const api_js_2 = require("./api.js");
35
38
  const runner_js_1 = require("./runner.js");
@@ -379,6 +382,14 @@ async function commandSubmit(parsed) {
379
382
  message,
380
383
  projectName: link?.slug ?? node_path_1.default.basename(folder),
381
384
  repositoryId: link?.repositoryId ?? null,
385
+ /*
386
+ The line this save belongs on: named on the command, or whichever one
387
+ the folder is set to. Sent by name rather than resolved here, because
388
+ the service owns the rules about which names exist and which may be
389
+ written to — a rule enforced in two places is one that will disagree
390
+ with itself.
391
+ */
392
+ track: flagText(parsed, "track") ?? (await (0, track_commands_js_1.trackFor)(folder)),
382
393
  /*
383
394
  Only claimed when this folder has actually been reconciled with a
384
395
  known version. A folder linked before versions were recorded says
@@ -409,7 +420,7 @@ async function commandSubmit(parsed) {
409
420
  difference between a person retrying and a person wondering.
410
421
  */
411
422
  if (!code && /fetch failed|ECONNRESET|socket hang up|network|ETIMEDOUT/i.test(text)) {
412
- console.error(red(`
423
+ console.error(red(`
413
424
  The connection failed: ${text}`));
414
425
  console.error(`Your work may already have been saved. Run the same command again —` +
415
426
  ` it will not create a second version.`);
@@ -589,7 +600,7 @@ async function commandGet(parsed) {
589
600
  `changed while the interrupted fetch was stopped:`));
590
601
  for (const file of changedInTheGap.slice(0, 20))
591
602
  console.error(` ${file.path}`);
592
- console.error(`
603
+ console.error(`
593
604
  Save them with ${accent("coderook submit")}, or finish the fetch and ` +
594
605
  `discard them with ${accent("coderook get --replace")}.`);
595
606
  return 1;
@@ -695,8 +706,66 @@ held, mode = "replace") {
695
706
  console.log(`${accent(`v${latest.sequence}`)} · ${result.files} files · ${bytes(result.bytes)} into ${destination}`);
696
707
  return 0;
697
708
  }
709
+ /**
710
+ * What this folder is carrying that probably is not the work.
711
+ *
712
+ * The desktop offers these when a folder is added — a virtual environment, a
713
+ * node_modules, a build directory — and the command line never did, so a
714
+ * project set up from a terminal sent its dependencies to the service and
715
+ * paid for the storage. The detector is the desktop's own rather than a
716
+ * second opinion written here, because two rules about what counts as
717
+ * generated output is two answers to the same question.
718
+ *
719
+ * Nothing is written without being asked for. Listing is the default: a tool
720
+ * that silently edits your .gitignore is one you have to check up on.
721
+ */
722
+ async function suggestRules(folder, apply) {
723
+ const rules = await (0, worktree_js_1.readRules)(folder);
724
+ const files = await (0, worktree_js_1.changedFiles)(folder, rules, null);
725
+ const suggestions = (0, detect_js_1.suggestExclusions)(await (0, worktree_js_1.fileSizes)(folder, files));
726
+ if (!suggestions.length) {
727
+ console.log(dim("Nothing here looks like it should be left out."));
728
+ return 0;
729
+ }
730
+ for (const one of suggestions) {
731
+ const mark = one.recommended ? green("+") : dim("?");
732
+ console.log(`${mark} ${one.pattern.padEnd(26)} ${dim(`${bytes(one.bytes)} · ${one.files} file${one.files === 1 ? "" : "s"} · ${one.reason}`)}`);
733
+ }
734
+ /*
735
+ Only the confident ones are written. Generated output and dependency
736
+ directories are safe to assume; anything that might be the work itself is
737
+ marked with a question and left for a person, because the cost of guessing
738
+ wrong is somebody's project not being backed up.
739
+ */
740
+ const recommended = suggestions.filter((one) => one.recommended);
741
+ if (!apply) {
742
+ console.log("");
743
+ console.log(dim("Add the + ones with ") + accent("coderook ignore --suggest --apply"));
744
+ return 0;
745
+ }
746
+ if (!recommended.length) {
747
+ console.log("");
748
+ console.log(dim("None of these are safe to assume — add them by hand."));
749
+ return 0;
750
+ }
751
+ const addition = (0, detect_js_1.rulesFromSuggestions)(recommended);
752
+ const shared = rules.shared.trimEnd();
753
+ await (0, worktree_js_1.writeRules)(folder, {
754
+ shared: shared ? `${shared}
755
+
756
+ ${addition}` : addition,
757
+ local: rules.local,
758
+ });
759
+ console.log("");
760
+ console.log(green(`Added ${recommended.length} rule${recommended.length === 1 ? "" : "s"} to `) +
761
+ node_path_1.default.join(folder, ".gitignore"));
762
+ return 0;
763
+ }
698
764
  async function commandRules(parsed) {
699
765
  const folder = folderFor(parsed);
766
+ if (hasFlag(parsed, "suggest")) {
767
+ return suggestRules(folder, hasFlag(parsed, "apply"));
768
+ }
700
769
  if (hasFlag(parsed, "init")) {
701
770
  const existing = await (0, worktree_js_1.readRules)(folder);
702
771
  await (0, worktree_js_1.writeRules)(folder, {
@@ -810,7 +879,7 @@ async function commandMerges(parsed) {
810
879
  const counts = merge.conflicts;
811
880
  console.log(`${accent(merge.reference)} ${counts ? `${counts.unresolved} of ${counts.total} still to decide` : ""} ${dim(new Date(merge.createdAt).toLocaleString())}`);
812
881
  }
813
- console.log(dim(`
882
+ console.log(dim(`
814
883
  Run coderook merge <reference> to look at one.`));
815
884
  return 0;
816
885
  }
@@ -876,7 +945,7 @@ async function commandMerge(parsed) {
876
945
  }
877
946
  }
878
947
  const now = await (0, api_js_1.mergeTrack)(summary.id);
879
- console.log(`
948
+ console.log(`
880
949
  ${accent(now.mergeTrack.reference)} · ${now.provisional.fileCount} files · ` +
881
950
  (now.provisional.ready
882
951
  ? "ready to apply"
@@ -891,19 +960,19 @@ ${accent(now.mergeTrack.reference)} · ${now.provisional.fileCount} files · ` +
891
960
  return 1;
892
961
  }
893
962
  const applied = await (0, api_js_1.applyMerge)(summary.id);
894
- console.log(`
963
+ console.log(`
895
964
  Applied as ${accent(`v${applied.version.sequence}`)}.`);
896
965
  console.log(dim("Run coderook get to bring it down to this folder."));
897
966
  return 0;
898
967
  }
899
968
  if (!decision) {
900
- console.log(dim(`
969
+ console.log(dim(`
901
970
  --mine keeps yours, --theirs keeps what was already saved,` +
902
- ` --drop removes the file.
971
+ ` --drop removes the file.
903
972
  Add --path <file> for one file, then --apply when ready.`));
904
973
  }
905
974
  else if (now.provisional.ready) {
906
- console.log(dim(`
975
+ console.log(dim(`
907
976
  Run coderook merge ${now.mergeTrack.reference} --apply to publish it.`));
908
977
  }
909
978
  return 0;
@@ -1081,10 +1150,55 @@ const SPECS = [
1081
1150
  { flags: "-m, --message <text>", description: "what changed, in a sentence" },
1082
1151
  { flags: "-n, --dry-run", description: "show what would be sent, send nothing" },
1083
1152
  { flags: "--allow-secrets", description: "send files that look like credentials" },
1153
+ {
1154
+ flags: "--track <name>",
1155
+ description: "save onto this line, just this once",
1156
+ },
1157
+ ],
1158
+ examples: [
1159
+ 'coderook submit -m "Fix the export dialog"',
1160
+ 'coderook submit --track spike -m "Try the other encoder"',
1084
1161
  ],
1085
- examples: ['coderook submit -m "Fix the export dialog"'],
1086
1162
  run: commandSubmit,
1087
1163
  },
1164
+ {
1165
+ /*
1166
+ Where the next save goes, which is a property of this folder rather
1167
+ than of the account — two checkouts of one project can sit on
1168
+ different lines, which is most of the point of having them.
1169
+ */
1170
+ name: "track",
1171
+ aliases: ["switch"],
1172
+ group: "Working with a folder",
1173
+ summary: "show or change the line this folder saves to",
1174
+ usage: "track [name]",
1175
+ detail: "With no name, prints the line this folder saves to. With one, switches\n" +
1176
+ "to it. Switching says where the next save goes and nothing else: no\n" +
1177
+ "files move and nothing is fetched, so it is instant and safe to change\n" +
1178
+ "your mind. Run `coderook get` afterwards to bring that line's files\n" +
1179
+ "into the folder.\n\n" +
1180
+ "A name that does not exist is refused rather than created, because a\n" +
1181
+ "typo in a branch name is an ordinary thing to do and a line called\n" +
1182
+ "`mian` puts work somewhere nobody will look for it. Pass --new to\n" +
1183
+ "start one deliberately.",
1184
+ options: [
1185
+ { flags: "-n, --new", description: "start this line from where the project is now" },
1186
+ ],
1187
+ examples: ["coderook track", "coderook track spike --new", "coderook track main"],
1188
+ run: track_commands_js_1.commandTrack,
1189
+ },
1190
+ {
1191
+ name: "tracks",
1192
+ group: "Your projects",
1193
+ summary: "the lines a project has, and any waiting on a decision",
1194
+ usage: "tracks [project]",
1195
+ detail: "Lists every line on the project, marking the one this folder saves to.\n" +
1196
+ "Merges waiting on a decision are listed beside them rather than hidden,\n" +
1197
+ "because somebody looking for where they can save needs to see the one\n" +
1198
+ "they cannot. Finish one with `coderook merge`.",
1199
+ examples: ["coderook tracks", "coderook tracks my-project"],
1200
+ run: track_commands_js_1.commandTracks,
1201
+ },
1088
1202
  {
1089
1203
  name: "get",
1090
1204
  group: "Working with a folder",
@@ -1111,7 +1225,17 @@ const SPECS = [
1111
1225
  detail: "The local list of things not to send — build output, dependencies,\n" +
1112
1226
  "anything private. Unrelated to a project's rules on the service, which\n" +
1113
1227
  "are about who may do what.",
1114
- options: [{ flags: "--init", description: "write a starting set of rules" }],
1228
+ options: [
1229
+ { flags: "--init", description: "write a starting set of rules" },
1230
+ {
1231
+ flags: "--suggest",
1232
+ description: "list what is here that probably should not be sent",
1233
+ },
1234
+ {
1235
+ flags: "--apply",
1236
+ description: "with --suggest, add the confident ones to .gitignore",
1237
+ },
1238
+ ],
1115
1239
  run: commandRules,
1116
1240
  },
1117
1241
  {
@@ -1320,6 +1444,39 @@ const SPECS = [
1320
1444
  examples: ["coderook runner my-game --labels windows,signing"],
1321
1445
  run: commandRunner,
1322
1446
  },
1447
+ {
1448
+ /*
1449
+ The assistants speak one protocol between them, so this is one server
1450
+ rather than two integrations. Hidden from nobody but unlikely to be
1451
+ typed by hand: it is launched by Claude Code or Codex, talks on its own
1452
+ stdin and stdout, and exits with them.
1453
+ */
1454
+ name: "mcp",
1455
+ group: "Other",
1456
+ summary: "serve CodeRook to Claude Code, Codex and other assistants",
1457
+ usage: "mcp",
1458
+ detail: "Speaks the Model Context Protocol on stdin and stdout, so an assistant\n" +
1459
+ "can look at your projects while it works. Not run by hand — point the\n" +
1460
+ "assistant at it and it starts and stops the process itself.\n" +
1461
+ "\n" +
1462
+ "Claude Code:\n" +
1463
+ " claude mcp add coderook -- coderook mcp\n" +
1464
+ "\n" +
1465
+ "Codex, in ~/.codex/config.toml:\n" +
1466
+ " [mcp_servers.coderook]\n" +
1467
+ " command = 'coderook'\n" +
1468
+ " args = ['mcp']\n" +
1469
+ "\n" +
1470
+ "Everything it offers reads. It lists projects, versions and files, shows\n" +
1471
+ "one file at a version, and reports what has changed in a folder. It\n" +
1472
+ "cannot save a version, delete anything, or sign in or out — an assistant\n" +
1473
+ "that goes wrong can waste your time but not your work.\n" +
1474
+ "\n" +
1475
+ "A project whose owner has turned off machine reading is refused, in\n" +
1476
+ "words rather than as a status code.\n",
1477
+ examples: ["claude mcp add coderook -- coderook mcp"],
1478
+ run: () => (0, mcp_js_1.commandMcp)(),
1479
+ },
1323
1480
  {
1324
1481
  name: "doctor",
1325
1482
  group: "Other",