@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 +71 -2
- package/dist/cli/src/cli.js +167 -10
- package/dist/cli/src/mcp.js +349 -0
- package/dist/cli/src/track_commands.js +157 -0
- package/dist/desktop-app/src/main/compress.js +95 -0
- package/dist/desktop-app/src/main/detect.js +137 -0
- package/dist/desktop-app/src/main/download.js +162 -15
- package/dist/desktop-app/src/main/profile.js +73 -0
- package/dist/desktop-app/src/main/retry.js +96 -0
- package/dist/desktop-app/src/main/solid.js +103 -0
- package/dist/desktop-app/src/main/staging.js +140 -0
- package/dist/desktop-app/src/main/tracks.js +132 -0
- package/dist/desktop-app/src/main/upload.js +1039 -55
- package/dist/desktop-app/src/main/worktree.js +214 -25
- package/package.json +1 -1
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
|
|
66
|
-
coderook rules --init
|
|
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
|
```
|
package/dist/cli/src/cli.js
CHANGED
|
@@ -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: [
|
|
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",
|