@coderook/cli 0.31.7 → 0.31.9
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/.claude-plugin/plugin.json +1 -1
- package/README.md +401 -389
- package/dist/desktop-app/src/main/upload.js +17 -0
- package/dist/desktop-app/src/main/worktree.js +67 -5
- package/package.json +9 -3
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "coderook",
|
|
3
3
|
"displayName": "CodeRook",
|
|
4
4
|
"description": "Save, browse and restore whole-snapshot versions of a project on CodeRook, from Claude Code.",
|
|
5
|
-
"version": "0.31.
|
|
5
|
+
"version": "0.31.9",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ACCA Gaming Productions",
|
|
8
8
|
"url": "https://coderook.com"
|
package/README.md
CHANGED
|
@@ -1,389 +1,401 @@
|
|
|
1
|
-
# cbx
|
|
2
|
-
|
|
3
|
-
The command
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
npm
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`cbx
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
1
|
+
# cbx
|
|
2
|
+
|
|
3
|
+
The command line for [CodeRook](https://coderook.com), version control and
|
|
4
|
+
project hosting for projects of any size. Every save is a complete,
|
|
5
|
+
compressed snapshot of the folder; projects are private or public at your
|
|
6
|
+
choice; and nothing stored on CodeRook is used to train AI. There is also a
|
|
7
|
+
[desktop app](https://coderook.com/download) and the website, and all three do
|
|
8
|
+
the same things. [How CodeRook works](https://coderook.com/docs).
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
npm install -g @coderook/cli
|
|
12
|
+
cbx login
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The command is `cbx`, and the split it names is the same one git and GitHub
|
|
16
|
+
have. **CBX is the engine** — content-defined chunking, whole-snapshot
|
|
17
|
+
versions, the `.cbx` bundle format. **CodeRook is the host** it talks to. You
|
|
18
|
+
run `cbx`; you sign in to CodeRook.
|
|
19
|
+
|
|
20
|
+
That is why the names do not match everywhere, and the mismatch is on purpose:
|
|
21
|
+
|
|
22
|
+
| | |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `cbx …` | the tool — what you type |
|
|
25
|
+
| `coderook.com`, `@coderook/cli` | the service, and the package it ships in |
|
|
26
|
+
| `coderook://project` | a remote URL, which names the host it points at |
|
|
27
|
+
| `CODEROOK_TOKEN`, `%APPDATA%\CodeRook` | credentials for a CodeRook account |
|
|
28
|
+
|
|
29
|
+
It uses the same engines as the desktop application — the same scanner, the
|
|
30
|
+
same `.gitignore` rules, the same upload, download and `.cbx` bundling — so a
|
|
31
|
+
project moved by one is understood by the other.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
Requires Node 20.11 or later, and nothing else.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
npm install --global @coderook/cli
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That installs `cbx`. It also installs `coderook` as an alias for it, so
|
|
42
|
+
anything written against the old name keeps working; the two are the same
|
|
43
|
+
program and either can be used.
|
|
44
|
+
|
|
45
|
+
Or from this repository:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
cd cli
|
|
49
|
+
npm install
|
|
50
|
+
npm run build
|
|
51
|
+
npm link
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Sign in
|
|
55
|
+
|
|
56
|
+
Create a personal access token in **Settings** on coderook.com, then:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
cbx sign-in
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The token is written to a file only your account can read:
|
|
63
|
+
|
|
64
|
+
| Platform | Location |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| Windows | `%APPDATA%\CodeRook` |
|
|
67
|
+
| macOS | `~/Library/Application Support/CodeRook` |
|
|
68
|
+
| Linux | `$XDG_CONFIG_HOME/coderook`, or `~/.config/coderook` |
|
|
69
|
+
|
|
70
|
+
For automation, set `CODEROOK_TOKEN` instead and nothing is written to disk.
|
|
71
|
+
`CODEROOK_API_URL` points the tool at a different service.
|
|
72
|
+
|
|
73
|
+
## Everyday use
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
cbx status what is here that is not saved yet
|
|
77
|
+
cbx submit -m "Fixed the parser"
|
|
78
|
+
cbx get fetch the latest version over this folder
|
|
79
|
+
cbx clone my-project fetch a project into a new folder
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`status` and `submit` act on the current directory unless you name another.
|
|
83
|
+
The first time either runs in a folder, it looks for a project on your account
|
|
84
|
+
whose name matches and links the two, so a folder you already uploaded from
|
|
85
|
+
the desktop is recognised rather than treated as new.
|
|
86
|
+
|
|
87
|
+
`submit` sends only what changed. Files the service already holds are not
|
|
88
|
+
uploaded again, and the version it records still names every file in the
|
|
89
|
+
project — a version is a snapshot, not a difference.
|
|
90
|
+
|
|
91
|
+
## Lines of work
|
|
92
|
+
|
|
93
|
+
A project can have more than one line, so two people can save without one
|
|
94
|
+
landing on top of the other.
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
cbx tracks the lines this project has
|
|
98
|
+
cbx track the line this folder saves to
|
|
99
|
+
cbx track spike --new start a line and switch to it
|
|
100
|
+
cbx track main switch back
|
|
101
|
+
cbx submit --track spike -m "Try the other encoder"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Switching says where the next save goes and nothing else. No files move and
|
|
105
|
+
nothing is fetched, so it is instant and safe to change your mind — run
|
|
106
|
+
`cbx get` afterwards to bring that line's files into the folder.
|
|
107
|
+
|
|
108
|
+
The line is remembered per folder, not per account, so two checkouts of one
|
|
109
|
+
project can sit on different lines. A folder that has never been switched
|
|
110
|
+
saves to `main`, which is what every folder meant before lines existed.
|
|
111
|
+
|
|
112
|
+
A name that does not exist is refused rather than created. A typo in a branch
|
|
113
|
+
name is an ordinary thing to do, and a line called `mian` puts work somewhere
|
|
114
|
+
nobody will look for it — `--new` is how you say you meant it.
|
|
115
|
+
|
|
116
|
+
If two saves land on the same line anyway, the second becomes a merge waiting
|
|
117
|
+
on a decision. `cbx merges` lists them and `cbx merge <ref>` walks
|
|
118
|
+
through it; `cbx tracks` shows them beside the ordinary lines, because
|
|
119
|
+
somebody looking for where they can save needs to see the one they cannot.
|
|
120
|
+
|
|
121
|
+
## Ignore rules
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
cbx ignore show the rules in force
|
|
125
|
+
cbx ignore --init write a starter .gitignore
|
|
126
|
+
cbx ignore --suggest what is here that probably should not be sent
|
|
127
|
+
cbx ignore --suggest --apply add the confident ones
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`cbx rules` is the same command under another name, so anything already
|
|
131
|
+
written against it keeps working.
|
|
132
|
+
|
|
133
|
+
`--suggest` looks at what the folder would actually send and names the
|
|
134
|
+
dependency directories, virtual environments and build output in it, with how
|
|
135
|
+
much each is costing you. Lines marked `+` are safe to assume and are the ones
|
|
136
|
+
`--apply` writes; anything that might be the work itself is marked `?` and left
|
|
137
|
+
for you, because the cost of guessing wrong is a project that is not backed up.
|
|
138
|
+
|
|
139
|
+
Rules live in the project's own `.gitignore`, so CodeRook, git, the website
|
|
140
|
+
and the desktop application all read one file. A `!` line puts something back.
|
|
141
|
+
Personal exclusions that should not reach a collaborator belong in
|
|
142
|
+
`.git/info/exclude`.
|
|
143
|
+
|
|
144
|
+
## Moving a repository here
|
|
145
|
+
|
|
146
|
+
`cbx import` takes a snapshot — the files as they are now, one version, no
|
|
147
|
+
history. To move off a host entirely, with the history intact:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
cbx transfer https://github.com/owner/project
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
GitHub, GitLab, Codeberg and any self-hosted Gitea or Forgejo. Every commit
|
|
154
|
+
becomes a version and every branch a line, so it takes a while on a long
|
|
155
|
+
history — the commit count and an estimate are printed before it starts.
|
|
156
|
+
|
|
157
|
+
**It goes through the public route on purpose.** The history is published by
|
|
158
|
+
running `git push` against the CodeRook remote, exactly as you would by hand.
|
|
159
|
+
A transfer that used a private path would prove only that a private path
|
|
160
|
+
exists.
|
|
161
|
+
|
|
162
|
+
| | |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| Comes across | commits, every branch, tags as releases, the description |
|
|
165
|
+
| With `--issues` and a read token | the open issues |
|
|
166
|
+
| Does not come across | pull requests and reviews, CI configuration, collaborators, stars |
|
|
167
|
+
|
|
168
|
+
The project is created **private** whatever the source was — publishing
|
|
169
|
+
somebody's code as a side effect of moving it is not recoverable. Pass
|
|
170
|
+
`--visibility same` to mirror the source, or `--public`.
|
|
171
|
+
|
|
172
|
+
A private repository needs a read token in the environment — `GITHUB_TOKEN`,
|
|
173
|
+
`GITLAB_TOKEN` or `FORGE_TOKEN`. It is never taken as a flag, never stored,
|
|
174
|
+
and there is no OAuth flow: holding your forge credentials to run a one-off
|
|
175
|
+
migration sits badly beside a promise that your work is not handed to anybody.
|
|
176
|
+
|
|
177
|
+
The report at the end lists what arrived and what did not, rather than leaving
|
|
178
|
+
the absence to be discovered later.
|
|
179
|
+
|
|
180
|
+
## Using git instead
|
|
181
|
+
|
|
182
|
+
Installing the CLI also installs `git-remote-coderook`, so git talks to
|
|
183
|
+
CodeRook directly — both ways:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
git clone coderook://my-project
|
|
187
|
+
git push coderook main
|
|
188
|
+
git fetch
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
It exists so that every editor's built-in git panel works with CodeRook
|
|
192
|
+
without a plugin. If you are not already living in git, `cbx submit` is
|
|
193
|
+
the simpler tool and this is not an upgrade on it.
|
|
194
|
+
|
|
195
|
+
**Pushing.** Each commit becomes a version, oldest first, with its message
|
|
196
|
+
kept. Later pushes send only what is new, so the usual case — a few commits —
|
|
197
|
+
is quick; a first push of a long history is not, and the helper says how many
|
|
198
|
+
commits it is about to publish before it starts. Branches become lines, and a
|
|
199
|
+
branch pushed for the first time starts from the commit it forked at rather
|
|
200
|
+
than from wherever the project happens to be.
|
|
201
|
+
|
|
202
|
+
**Cloning and fetching.** Every version becomes a commit, keeping its message,
|
|
203
|
+
its date and who published it. This works on any project, including one that
|
|
204
|
+
has never been near git — a project built entirely with `cbx submit`
|
|
205
|
+
clones into a normal git history. A fetch only downloads versions this clone
|
|
206
|
+
does not already have.
|
|
207
|
+
|
|
208
|
+
**What it does not do:**
|
|
209
|
+
|
|
210
|
+
| | |
|
|
211
|
+
| --- | --- |
|
|
212
|
+
| Tag kind | A lightweight tag comes back annotated. CodeRook records a name, not which sort of tag made it. |
|
|
213
|
+
| Force pushes, branch deletion | Refused — versions are immutable, so there is nothing to rewind to. |
|
|
214
|
+
| Submodules | Skipped on push, and it says which paths. |
|
|
215
|
+
| The executable bit | Everything arrives as a normal file. A script cloned back needs `chmod +x`. |
|
|
216
|
+
|
|
217
|
+
**On round trips.** Push then clone gives you back the same *files*, not the
|
|
218
|
+
same *commit ids*. A commit id covers its author, committer and timestamps,
|
|
219
|
+
and CodeRook records who published a version rather than the original stamps —
|
|
220
|
+
so the history you get back is a faithful copy of the contents and an honest
|
|
221
|
+
approximation of the commits. Two people cloning the same project do get
|
|
222
|
+
identical ids as each other.
|
|
223
|
+
|
|
224
|
+
Merges are asymmetric for the same reason. A publish states one base version,
|
|
225
|
+
so pushing a git merge records one parent and the fork is lost — the merged
|
|
226
|
+
tree is exact, the shape is not. Reading back is richer: a version that really
|
|
227
|
+
has two parents is rebuilt as a real git merge.
|
|
228
|
+
|
|
229
|
+
Each version records the commit it came from, so a colleague pushing the same
|
|
230
|
+
project does not republish history the project already holds. Pushing a git
|
|
231
|
+
history into a project whose versions came from somewhere else is refused
|
|
232
|
+
rather than interleaved.
|
|
233
|
+
|
|
234
|
+
### The names are the same
|
|
235
|
+
|
|
236
|
+
There is no translation table to learn, because there is nothing to translate.
|
|
237
|
+
The commands carry git's names:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
cbx push cbx pull cbx clone
|
|
241
|
+
cbx log cbx status cbx branch
|
|
242
|
+
cbx checkout cbx merge cbx tag
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Each is the CodeRook command it always was, reachable by the name a git user
|
|
246
|
+
would reach for: `push` is `submit`, `pull` is `get`, `log` is `versions`,
|
|
247
|
+
`branch` is `tracks`, `checkout` is `track`, `tag` is `release`. The original
|
|
248
|
+
names still work and mean the same thing.
|
|
249
|
+
|
|
250
|
+
That is also what the git remote does. `git push` performs the operation
|
|
251
|
+
`cbx push` performs — the same code decides what changed, the same code
|
|
252
|
+
publishes it. The remote is a way to reach these commands from git, not a
|
|
253
|
+
second implementation of them that happens to agree.
|
|
254
|
+
|
|
255
|
+
**What does not appear on that list**, because git has no such idea and
|
|
256
|
+
inventing one would be pretending: `projects`, `delete`, `people`, `watch`,
|
|
257
|
+
`issues`, `actions`, `runs`, `logs`, `runner`, `tokens`, `licence`, `ai`. Use
|
|
258
|
+
them by name; nothing about them changes when a version arrives through git.
|
|
259
|
+
|
|
260
|
+
`git commit` has no CodeRook equivalent either, and should not: it is local to
|
|
261
|
+
git, and a version is not made until something is published.
|
|
262
|
+
|
|
263
|
+
### Tags and releases
|
|
264
|
+
|
|
265
|
+
`git push --tags` publishes each tag as a release, because that is the same
|
|
266
|
+
statement in the other vocabulary — the service puts it plainly: *"a release
|
|
267
|
+
is a Version with a name on it, not a different kind of object."*
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
git tag -a v1.0 -m "First stable release"
|
|
271
|
+
git push coderook v1.0
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The tag names the version its commit already became; it does not publish
|
|
275
|
+
anything, so push the branch first. An annotated tag's message becomes the
|
|
276
|
+
release notes and a lightweight tag brings none — the commit's own message is
|
|
277
|
+
deliberately *not* used, since nobody wrote it about the release.
|
|
278
|
+
|
|
279
|
+
Cloning reverses it: every release comes back as a tag on the right commit.
|
|
280
|
+
|
|
281
|
+
Assets are separate and unaffected. They attach to a version from a run's
|
|
282
|
+
artifacts, so a release made by pushing a tag simply has none until something
|
|
283
|
+
adds them — which is what a release without a build has always looked like.
|
|
284
|
+
|
|
285
|
+
## Bundles
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
cbx bundle pack this project as <name>.cbx
|
|
289
|
+
cbx unbundle backup.cbx extract one
|
|
290
|
+
cbx inspect backup.cbx see what it holds (--files to list them)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
A `.cbx` is the CodeBox bundle format: content-defined chunks, Zstandard where
|
|
294
|
+
it helps, raw where it does not, and a SHA-256 for every chunk and file.
|
|
295
|
+
Extraction is verified and atomic — a damaged bundle fails rather than leaving
|
|
296
|
+
a half-written tree.
|
|
297
|
+
|
|
298
|
+
## Claude Code
|
|
299
|
+
|
|
300
|
+
Run this **in a terminal**, not inside Claude Code:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
cbx skill
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
That is the whole setup. It writes `~/.claude/skills/coderook/SKILL.md`, and
|
|
307
|
+
from then on you ask for what you want in ordinary words:
|
|
308
|
+
|
|
309
|
+
> save this to CodeRook
|
|
310
|
+
|
|
311
|
+
> what have I changed?
|
|
312
|
+
|
|
313
|
+
> what versions does this project have?
|
|
314
|
+
|
|
315
|
+
Claude works out which commands to run. You can also call it by name with
|
|
316
|
+
`/coderook`.
|
|
317
|
+
|
|
318
|
+
**The skill is called `coderook`, not `cbx`.** `cbx` is the command line, run
|
|
319
|
+
in a terminal; the skill is the file that teaches Claude how to use it. Typing
|
|
320
|
+
`cbx skill` *into* Claude Code asks it for a skill by that name and it will
|
|
321
|
+
tell you there is not one — which is correct, and is why the line above says
|
|
322
|
+
where to run it.
|
|
323
|
+
|
|
324
|
+
`cbx skill --project` writes it into `./.claude` instead of your home
|
|
325
|
+
folder, so it travels with the repository and everybody who clones it has it
|
|
326
|
+
too.
|
|
327
|
+
|
|
328
|
+
Sign in once per machine first — `cbx sign-in` — or set `CODEROOK_TOKEN`
|
|
329
|
+
in automation. The skill will not save anything without asking you first, and
|
|
330
|
+
never deletes.
|
|
331
|
+
|
|
332
|
+
## Installing it as a plugin instead
|
|
333
|
+
|
|
334
|
+
If you would rather not install the command line first, CodeRook publishes a
|
|
335
|
+
plugin marketplace of its own:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
/plugin marketplace add https://coderook.com/marketplace.json
|
|
339
|
+
/plugin install coderook@coderook
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
That fetches the same package from npm and brings the skill with it. The
|
|
343
|
+
marketplace is a plain file on coderook.com rather than a git repository, so
|
|
344
|
+
there is no repository to clone and no second account anywhere.
|
|
345
|
+
|
|
346
|
+
One thing to know: a plugin install puts the package in Claude Code's plugin
|
|
347
|
+
folder, not on your `PATH`. The skill copes with that on its own by falling
|
|
348
|
+
back to `npx -y @coderook/cli`, but `npm install --global @coderook/cli` is
|
|
349
|
+
faster if you plan to run commands yourself as well.
|
|
350
|
+
|
|
351
|
+
## A structured connection instead
|
|
352
|
+
|
|
353
|
+
The skill teaches Claude the command line, which needs no configuration. If you
|
|
354
|
+
would rather it had structured tools, there is an MCP server as well. It works
|
|
355
|
+
with Codex too, which is the reason it exists.
|
|
356
|
+
|
|
357
|
+
Claude Code:
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
claude mcp add --scope user coderook -- npx -y @coderook/cli mcp
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Codex, in `~/.codex/config.toml`:
|
|
364
|
+
|
|
365
|
+
```toml
|
|
366
|
+
[mcp_servers.coderook]
|
|
367
|
+
command = "npx"
|
|
368
|
+
args = ["-y", "@coderook/cli", "mcp"]
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
To share it with everyone on a project, commit this beside the code:
|
|
372
|
+
|
|
373
|
+
```json
|
|
374
|
+
// .mcp.json
|
|
375
|
+
{
|
|
376
|
+
"mcpServers": {
|
|
377
|
+
"coderook": {
|
|
378
|
+
"command": "npx",
|
|
379
|
+
"args": ["-y", "@coderook/cli", "mcp"]
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
It offers five things, all of which read: your projects, a project's versions,
|
|
386
|
+
the files in a version, one file's contents at a version, and what has changed
|
|
387
|
+
in a local folder. It cannot save, delete, or sign in or out.
|
|
388
|
+
|
|
389
|
+
## Checking things
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
cbx doctor
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Reports the tool's version, where its configuration lives, whether the service
|
|
396
|
+
is reachable, and who this machine is signed in as.
|
|
397
|
+
|
|
398
|
+
## Exit codes
|
|
399
|
+
|
|
400
|
+
`0` on success, `1` on failure. Every command prints why it failed on standard
|
|
401
|
+
error, so a script can branch on the status and log the reason.
|
|
@@ -19,6 +19,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
19
19
|
const faults_js_1 = require("./faults.js");
|
|
20
20
|
const identify_js_1 = require("./identify.js");
|
|
21
21
|
const worktree_js_1 = require("./worktree.js");
|
|
22
|
+
const rules_js_1 = require("./rules.js");
|
|
22
23
|
const publish_name_js_1 = require("./publish_name.js");
|
|
23
24
|
const compress_js_1 = require("./compress.js");
|
|
24
25
|
const compression_policy_js_1 = require("../shared/compression_policy.js");
|
|
@@ -142,6 +143,8 @@ class Uploader {
|
|
|
142
143
|
*/
|
|
143
144
|
plannedLayouts = new Map();
|
|
144
145
|
plannedQuote = null;
|
|
146
|
+
/** Files sent only because git tracks them; named in the save so the service allows them. */
|
|
147
|
+
trackedByGit = [];
|
|
145
148
|
/*
|
|
146
149
|
Digests worked out while planning, so sending does not work them out again.
|
|
147
150
|
|
|
@@ -407,6 +410,13 @@ class Uploader {
|
|
|
407
410
|
const rules = request.allowIgnored
|
|
408
411
|
? { shared: "", local: "" }
|
|
409
412
|
: await (0, worktree_js_1.readRules)(request.localPath);
|
|
413
|
+
/*
|
|
414
|
+
What the survey keeps only because git tracks it, named for the
|
|
415
|
+
service: its own check of the rules would otherwise refuse them as a
|
|
416
|
+
rule this client forgot. See gitTracked.
|
|
417
|
+
*/
|
|
418
|
+
this.trackedByGit = [];
|
|
419
|
+
const tracked = request.allowIgnored ? null : await (0, worktree_js_1.gitTracked)(request.localPath);
|
|
410
420
|
/*
|
|
411
421
|
Surveyed, not listed.
|
|
412
422
|
|
|
@@ -443,6 +453,12 @@ class Uploader {
|
|
|
443
453
|
percent: 6,
|
|
444
454
|
bytesPerSecond: 0,
|
|
445
455
|
})));
|
|
456
|
+
if (tracked) {
|
|
457
|
+
const layers = await (0, worktree_js_1.collectLayers)(request.localPath, rules);
|
|
458
|
+
this.trackedByGit = everything
|
|
459
|
+
.map((file) => file.path)
|
|
460
|
+
.filter((file) => tracked.files.has(file) && (0, rules_js_1.excludes)(file, false, layers));
|
|
461
|
+
}
|
|
446
462
|
(0, profile_js_1.counted)("files surveyed", everything.length);
|
|
447
463
|
const unticked = new Set(request.excluded ?? []);
|
|
448
464
|
const ticked = request.excluded
|
|
@@ -854,6 +870,7 @@ class Uploader {
|
|
|
854
870
|
: { expectedHeadVersionId: request.expectedHeadVersionId }),
|
|
855
871
|
...(request.track ? { track: request.track } : {}),
|
|
856
872
|
...(request.allowIgnored ? { allowIgnored: true } : {}),
|
|
873
|
+
...(this.trackedByGit.length ? { trackedByGit: this.trackedByGit } : {}),
|
|
857
874
|
...(request.allowSecrets ? { allowSecrets: true } : {}),
|
|
858
875
|
...(request.linesAdded === undefined ? {} : { linesAdded: request.linesAdded }),
|
|
859
876
|
...(request.linesRemoved === undefined
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.IGNORE_HEADING = exports.KEEP_HEADING = exports.LOCAL_EXCLUDE_FILE = exports.IGNORE_FILE = exports.STARTER_IGNORE = exports.CODEROOK_TAIL = exports.CODEROOK_TAIL_HEADING = exports.KEPT_ANYWAY = exports.KEPT_ANYWAY_HEADING = exports.ALSO_LEFT_OUT = exports.ALSO_LEFT_OUT_HEADING = exports.STARTER_MARKER = exports.EVALUATION_FILE_LIMIT = void 0;
|
|
6
|
+
exports.IGNORE_HEADING = exports.KEEP_HEADING = exports.TRACKED_BY_GIT = exports.LOCAL_EXCLUDE_FILE = exports.IGNORE_FILE = exports.STARTER_IGNORE = exports.CODEROOK_TAIL = exports.CODEROOK_TAIL_HEADING = exports.KEPT_ANYWAY = exports.KEPT_ANYWAY_HEADING = exports.ALSO_LEFT_OUT = exports.ALSO_LEFT_OUT_HEADING = exports.STARTER_MARKER = exports.EVALUATION_FILE_LIMIT = void 0;
|
|
7
7
|
exports.ruleTarget = ruleTarget;
|
|
8
8
|
exports.keptTargets = keptTargets;
|
|
9
9
|
exports.withoutOpposites = withoutOpposites;
|
|
@@ -11,6 +11,7 @@ exports.starterAuthored = starterAuthored;
|
|
|
11
11
|
exports.missingStarterRules = missingStarterRules;
|
|
12
12
|
exports.missingEngineRules = missingEngineRules;
|
|
13
13
|
exports.isRepository = isRepository;
|
|
14
|
+
exports.gitTracked = gitTracked;
|
|
14
15
|
exports.migrateRetiredRules = migrateRetiredRules;
|
|
15
16
|
exports.starterFor = starterFor;
|
|
16
17
|
exports.moveRule = moveRule;
|
|
@@ -404,6 +405,39 @@ async function isRepository(root) {
|
|
|
404
405
|
return false;
|
|
405
406
|
}
|
|
406
407
|
}
|
|
408
|
+
/** The label a file kept because git tracks it carries, where a rule would be. */
|
|
409
|
+
exports.TRACKED_BY_GIT = "(tracked by git)";
|
|
410
|
+
/**
|
|
411
|
+
* The files git tracks here, which an ignore rule does not take away.
|
|
412
|
+
*
|
|
413
|
+
* Git applies .gitignore only to files it does not track yet. Once a file is
|
|
414
|
+
* committed, a rule written later — or a broad one like `storage/` that also
|
|
415
|
+
* matches a source folder three levels down — leaves it exactly where it is.
|
|
416
|
+
* This applied the rules to everything, so a project whose .gitignore said
|
|
417
|
+
* `storage/` arrived without `internal/storage/*.go`: in git, whole; on
|
|
418
|
+
* CodeRook, missing the six files that made it build.
|
|
419
|
+
*
|
|
420
|
+
* Null when the folder is not a repository, or git cannot be asked — then the
|
|
421
|
+
* rules alone decide, as they always did.
|
|
422
|
+
*/
|
|
423
|
+
async function gitTracked(root) {
|
|
424
|
+
if (!(await isRepository(root)))
|
|
425
|
+
return null;
|
|
426
|
+
const listed = await git(root, "ls-files", "-z");
|
|
427
|
+
if (listed === null)
|
|
428
|
+
return null;
|
|
429
|
+
const files = new Set(listed.split(String.fromCharCode(0)).filter(Boolean));
|
|
430
|
+
const folders = new Set();
|
|
431
|
+
for (const file of files) {
|
|
432
|
+
for (let cut = file.lastIndexOf("/"); cut > 0; cut = file.lastIndexOf("/", cut - 1)) {
|
|
433
|
+
const folder = file.slice(0, cut);
|
|
434
|
+
if (folders.has(folder))
|
|
435
|
+
break;
|
|
436
|
+
folders.add(folder);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return { files, folders };
|
|
440
|
+
}
|
|
407
441
|
/**
|
|
408
442
|
* Convert the rules an earlier build wrote into one .gitignore.
|
|
409
443
|
*
|
|
@@ -519,6 +553,15 @@ async function moveRule(root, file, keep) {
|
|
|
519
553
|
const directory = await (0, promises_1.stat)(node_path_1.default.join(root, bare))
|
|
520
554
|
.then((entry) => entry.isDirectory())
|
|
521
555
|
.catch(() => file.endsWith("/"));
|
|
556
|
+
/*
|
|
557
|
+
A file git tracks cannot be ignored by a rule — git keeps it, and so does
|
|
558
|
+
this. Writing the rule anyway would look like it worked and change
|
|
559
|
+
nothing, so it is refused with what would actually leave it out.
|
|
560
|
+
*/
|
|
561
|
+
if (!keep && !directory && (await gitTracked(root))?.files.has(bare)) {
|
|
562
|
+
throw new Error(`git tracks ${bare}, so a rule cannot leave it out. Untick it for one ` +
|
|
563
|
+
`save, or stop git tracking it: git rm --cached "${bare}"`);
|
|
564
|
+
}
|
|
522
565
|
const target = directory ? `${bare}/` : bare;
|
|
523
566
|
const line = keep ? `!${target}` : target;
|
|
524
567
|
/*
|
|
@@ -885,6 +928,8 @@ stats,
|
|
|
885
928
|
/** Filled with how much of the folder the cap allowed through. */
|
|
886
929
|
outcome) {
|
|
887
930
|
const layers = await collectLayers(root, rules);
|
|
931
|
+
/* What git already tracks goes up whatever a rule says; see gitTracked. */
|
|
932
|
+
const tracked = await gitTracked(root);
|
|
888
933
|
const files = [];
|
|
889
934
|
const present = new Set();
|
|
890
935
|
const pending = [root];
|
|
@@ -926,7 +971,9 @@ outcome) {
|
|
|
926
971
|
// Skipping an excluded directory outright is what keeps this fast,
|
|
927
972
|
// but a `!` rule exists to rescue what is inside one, so the subtree
|
|
928
973
|
// is only skipped when no negation could reach into it.
|
|
929
|
-
if ((0, rules_js_1.excludes)(relative, true, layers) &&
|
|
974
|
+
if ((0, rules_js_1.excludes)(relative, true, layers) &&
|
|
975
|
+
!(0, rules_js_1.negationReachesInto)(relative, layers) &&
|
|
976
|
+
!tracked?.folders.has(relative)) {
|
|
930
977
|
continue;
|
|
931
978
|
}
|
|
932
979
|
pending.push(full);
|
|
@@ -934,7 +981,7 @@ outcome) {
|
|
|
934
981
|
}
|
|
935
982
|
if (!entry.isFile())
|
|
936
983
|
continue;
|
|
937
|
-
if ((0, rules_js_1.excludes)(relative, false, layers))
|
|
984
|
+
if ((0, rules_js_1.excludes)(relative, false, layers) && !tracked?.files.has(relative))
|
|
938
985
|
continue;
|
|
939
986
|
wanted.push({ full, relative });
|
|
940
987
|
}
|
|
@@ -1060,6 +1107,7 @@ async function surveyFiles(root, rules,
|
|
|
1060
1107
|
*/
|
|
1061
1108
|
onFound) {
|
|
1062
1109
|
const layers = await collectLayers(root, rules);
|
|
1110
|
+
const tracked = await gitTracked(root);
|
|
1063
1111
|
const found = [];
|
|
1064
1112
|
/*
|
|
1065
1113
|
A level at a time, eight in flight.
|
|
@@ -1094,7 +1142,8 @@ onFound) {
|
|
|
1094
1142
|
const relative = node_path_1.default.relative(root, full).split(node_path_1.default.sep).join("/");
|
|
1095
1143
|
if (entry.isDirectory()) {
|
|
1096
1144
|
if ((0, rules_js_1.excludes)(relative, true, layers) &&
|
|
1097
|
-
!(0, rules_js_1.negationReachesInto)(relative, layers)
|
|
1145
|
+
!(0, rules_js_1.negationReachesInto)(relative, layers) &&
|
|
1146
|
+
!tracked?.folders.has(relative)) {
|
|
1098
1147
|
continue;
|
|
1099
1148
|
}
|
|
1100
1149
|
next.push(full);
|
|
@@ -1102,7 +1151,7 @@ onFound) {
|
|
|
1102
1151
|
}
|
|
1103
1152
|
if (!entry.isFile())
|
|
1104
1153
|
continue;
|
|
1105
|
-
if ((0, rules_js_1.excludes)(relative, false, layers))
|
|
1154
|
+
if ((0, rules_js_1.excludes)(relative, false, layers) && !tracked?.files.has(relative))
|
|
1106
1155
|
continue;
|
|
1107
1156
|
here.push({ full, relative });
|
|
1108
1157
|
}
|
|
@@ -1408,6 +1457,7 @@ function relativeTo(root, full) {
|
|
|
1408
1457
|
*/
|
|
1409
1458
|
async function evaluateRules(root, rules, options = {}) {
|
|
1410
1459
|
const layers = await collectLayers(root, rules);
|
|
1460
|
+
const tracked = await gitTracked(root);
|
|
1411
1461
|
const impactBytes = new Map();
|
|
1412
1462
|
const impactFiles = new Map();
|
|
1413
1463
|
const excluded = [];
|
|
@@ -1449,6 +1499,18 @@ async function evaluateRules(root, rules, options = {}) {
|
|
|
1449
1499
|
});
|
|
1450
1500
|
continue;
|
|
1451
1501
|
}
|
|
1502
|
+
if (rule && tracked?.files.has(relative)) {
|
|
1503
|
+
/*
|
|
1504
|
+
A rule matches it, and git tracks it, so it goes up regardless — and
|
|
1505
|
+
is listed with what kept it, beside the files a `!` rule keeps.
|
|
1506
|
+
*/
|
|
1507
|
+
keptFiles += 1;
|
|
1508
|
+
keptBytes += size;
|
|
1509
|
+
uploadFiles += 1;
|
|
1510
|
+
uploadBytes += size;
|
|
1511
|
+
excluded.push({ path: relative, bytes: size, rule: exports.TRACKED_BY_GIT, forceKept: true });
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1452
1514
|
if (rule) {
|
|
1453
1515
|
skippedFiles += 1;
|
|
1454
1516
|
skippedBytes += size;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.31.
|
|
4
|
-
"description": "CodeRook
|
|
3
|
+
"version": "0.31.9",
|
|
4
|
+
"description": "The command line for CodeRook (coderook.com): version control and project hosting for projects of any size. Every save is a compressed snapshot, private or public, never used to train AI.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"homepage": "https://coderook.com",
|
|
7
7
|
"bugs": {
|
|
@@ -9,9 +9,15 @@
|
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
11
|
"coderook",
|
|
12
|
+
"cbx",
|
|
13
|
+
"version-control",
|
|
14
|
+
"vcs",
|
|
12
15
|
"versioning",
|
|
16
|
+
"project-hosting",
|
|
13
17
|
"backup",
|
|
14
|
-
"
|
|
18
|
+
"snapshots",
|
|
19
|
+
"large-files",
|
|
20
|
+
"gamedev"
|
|
15
21
|
],
|
|
16
22
|
"engines": {
|
|
17
23
|
"node": ">=20.11.0"
|