@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +378 -220
- package/dist/cli/src/api.js +41 -2
- package/dist/cli/src/cli.js +271 -78
- package/dist/cli/src/forge_url.js +68 -0
- package/dist/cli/src/git_history.js +188 -0
- package/dist/cli/src/git_remote.js +1217 -0
- package/dist/cli/src/git_remote_bin.js +22 -0
- package/dist/cli/src/help.js +8 -8
- package/dist/cli/src/licence_commands.js +5 -5
- package/dist/cli/src/mcp.js +1 -1
- package/dist/cli/src/publish.js +37 -0
- package/dist/cli/src/registry.js +1 -1
- package/dist/cli/src/runner.js +2 -2
- package/dist/cli/src/service_commands.js +53 -3
- package/dist/cli/src/skill_command.js +3 -3
- package/dist/cli/src/track_commands.js +7 -7
- package/dist/cli/src/transfer_command.js +331 -0
- package/dist/desktop-app/src/main/tracks.js +12 -2
- package/package.json +7 -4
- package/skills/coderook/SKILL.md +130 -130
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.commandTransfer = commandTransfer;
|
|
7
|
+
/**
|
|
8
|
+
* Moving a whole repository off a git host and onto CodeRook.
|
|
9
|
+
*
|
|
10
|
+
* `cbx import` takes a snapshot: the files as they are now, one version, no
|
|
11
|
+
* history. That is the right answer for "get this onto CodeRook quickly" and
|
|
12
|
+
* the wrong one for "move off GitHub", where the history, the branches, the
|
|
13
|
+
* tags and the issues are most of what somebody is worried about losing.
|
|
14
|
+
*
|
|
15
|
+
* ## It uses the public route on purpose
|
|
16
|
+
*
|
|
17
|
+
* The history goes across by running `git push` against the CodeRook remote —
|
|
18
|
+
* the same command anybody else would type, through the same helper. Nothing
|
|
19
|
+
* here reaches past it into a private path.
|
|
20
|
+
*
|
|
21
|
+
* That is deliberate, because the claim being made is interoperability. A
|
|
22
|
+
* transfer that worked through a back door would prove only that a back door
|
|
23
|
+
* exists. This one works because `git push coderook --all` works; if that ever
|
|
24
|
+
* breaks, this breaks with it, loudly, rather than quietly diverging from what
|
|
25
|
+
* users are told to do.
|
|
26
|
+
*
|
|
27
|
+
* ## What crosses, and what cannot
|
|
28
|
+
*
|
|
29
|
+
* Commits, branches, tags and the project's own description come across.
|
|
30
|
+
* Issues come across when a read token is supplied. Pull requests, reviews,
|
|
31
|
+
* CI configuration, stars and collaborator lists do not — some because
|
|
32
|
+
* CodeRook has no such object, and one because it would be wrong: adding
|
|
33
|
+
* somebody to a project is an invitation they have to accept, not a field to
|
|
34
|
+
* copy. The report at the end says which is which rather than leaving the
|
|
35
|
+
* absence to be discovered.
|
|
36
|
+
*
|
|
37
|
+
* ## Tokens
|
|
38
|
+
*
|
|
39
|
+
* Read from the environment, never from a flag and never stored. A read-only
|
|
40
|
+
* token is enough, and no OAuth flow is offered: holding somebody's forge
|
|
41
|
+
* credentials to run a one-off migration sits badly beside a front page that
|
|
42
|
+
* promises their work is not handed to anybody.
|
|
43
|
+
*/
|
|
44
|
+
const node_child_process_1 = require("node:child_process");
|
|
45
|
+
const promises_1 = require("node:fs/promises");
|
|
46
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
47
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
48
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
49
|
+
const api_js_1 = require("./api.js");
|
|
50
|
+
const forge_url_js_1 = require("./forge_url.js");
|
|
51
|
+
const dim = (value) => `[2m${value}[0m`;
|
|
52
|
+
const bold = (value) => `[1m${value}[0m`;
|
|
53
|
+
const red = (value) => `[31m${value}[0m`;
|
|
54
|
+
const green = (value) => `[32m${value}[0m`;
|
|
55
|
+
const accent = (value) => `[33m${value}[0m`;
|
|
56
|
+
/** A read token from the environment, or nothing. Never a flag. */
|
|
57
|
+
function forgeToken(forge) {
|
|
58
|
+
for (const name of forge.tokenNames) {
|
|
59
|
+
const value = node_process_1.default.env[name]?.trim();
|
|
60
|
+
if (value)
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
async function readJson(url, token, forge) {
|
|
66
|
+
const headers = {
|
|
67
|
+
accept: "application/json",
|
|
68
|
+
"user-agent": "cbx-transfer",
|
|
69
|
+
};
|
|
70
|
+
if (token) {
|
|
71
|
+
headers.authorization =
|
|
72
|
+
forge.kind === "gitlab" ? `Bearer ${token}` : `token ${token}`;
|
|
73
|
+
}
|
|
74
|
+
const response = await fetch(url, { headers });
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(`${response.status} from ${new URL(url).pathname}`);
|
|
77
|
+
}
|
|
78
|
+
return response.json();
|
|
79
|
+
}
|
|
80
|
+
/** What the forge says about the repository itself. */
|
|
81
|
+
async function describeSource(forge, token) {
|
|
82
|
+
const encoded = encodeURIComponent(`${forge.owner}/${forge.repo}`);
|
|
83
|
+
const url = forge.kind === "github"
|
|
84
|
+
? `https://api.${forge.host}/repos/${forge.owner}/${forge.repo}`
|
|
85
|
+
: forge.kind === "gitlab"
|
|
86
|
+
? `https://${forge.host}/api/v4/projects/${encoded}`
|
|
87
|
+
: `https://${forge.host}/api/v1/repos/${forge.owner}/${forge.repo}`;
|
|
88
|
+
try {
|
|
89
|
+
const body = (await readJson(url, token, forge));
|
|
90
|
+
const isPrivate = body.private === true || body.visibility === "private" || body.internal === true;
|
|
91
|
+
return {
|
|
92
|
+
description: String(body.description ?? "").slice(0, 1000),
|
|
93
|
+
visibility: isPrivate ? "private" : "public",
|
|
94
|
+
defaultBranch: body.default_branch ?? null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Open issues on the source, oldest first so numbering reads sensibly. */
|
|
102
|
+
async function readIssues(forge, token) {
|
|
103
|
+
const encoded = encodeURIComponent(`${forge.owner}/${forge.repo}`);
|
|
104
|
+
const url = forge.kind === "github"
|
|
105
|
+
? `https://api.${forge.host}/repos/${forge.owner}/${forge.repo}/issues?state=open&per_page=100`
|
|
106
|
+
: forge.kind === "gitlab"
|
|
107
|
+
? `https://${forge.host}/api/v4/projects/${encoded}/issues?state=opened&per_page=100`
|
|
108
|
+
: `https://${forge.host}/api/v1/repos/${forge.owner}/${forge.repo}/issues?state=open&limit=100`;
|
|
109
|
+
const body = (await readJson(url, token, forge));
|
|
110
|
+
return body
|
|
111
|
+
/*
|
|
112
|
+
GitHub returns pull requests through the issues endpoint. They are not
|
|
113
|
+
issues, and CodeRook has nothing that a pull request becomes, so copying
|
|
114
|
+
them across as issues would be inventing content rather than moving it.
|
|
115
|
+
*/
|
|
116
|
+
.filter((one) => !one.pull_request)
|
|
117
|
+
.map((one) => ({
|
|
118
|
+
title: String(one.title ?? "").slice(0, 200),
|
|
119
|
+
body: String(one.body ?? one.description ?? "").slice(0, 20_000),
|
|
120
|
+
number: Number(one.number ?? one.iid ?? 0),
|
|
121
|
+
closed: false,
|
|
122
|
+
}))
|
|
123
|
+
.filter((one) => one.title)
|
|
124
|
+
.reverse();
|
|
125
|
+
}
|
|
126
|
+
function run(command, args, cwd) {
|
|
127
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, {
|
|
128
|
+
cwd,
|
|
129
|
+
encoding: "utf8",
|
|
130
|
+
maxBuffer: 1024 * 1024 * 64,
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
ok: result.status === 0,
|
|
134
|
+
out: `${result.stdout ?? ""}${result.stderr ?? ""}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function commandTransfer(parsed) {
|
|
138
|
+
const address = parsed.positional[0];
|
|
139
|
+
if (!address) {
|
|
140
|
+
console.error(red("Which repository? Try: cbx transfer https://github.com/owner/project"));
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
let forge;
|
|
144
|
+
try {
|
|
145
|
+
forge = (0, forge_url_js_1.readForgeUrl)(address);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error(red(error instanceof Error ? error.message : String(error)));
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
if (!run("git", ["--version"]).ok) {
|
|
152
|
+
console.error(red("Transferring needs git on this machine."));
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
/*
|
|
156
|
+
The push goes through the remote helper, which git finds on PATH by name.
|
|
157
|
+
Checking now turns a confusing failure halfway through a clone into one
|
|
158
|
+
sentence before anything is downloaded.
|
|
159
|
+
*/
|
|
160
|
+
if (!run("git", ["remote-coderook", "--probe"]).ok) {
|
|
161
|
+
const help = run("git", ["help", "-a"]);
|
|
162
|
+
if (!/remote-coderook/.test(help.out)) {
|
|
163
|
+
console.error(red("git cannot find `git-remote-coderook` on this machine."));
|
|
164
|
+
console.error("It ships with this tool, so this usually means cbx was run from a\n" +
|
|
165
|
+
"checkout rather than installed. Install it globally and try again:\n" +
|
|
166
|
+
` ${accent("npm install --global @coderook/cli")}`);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const wanted = (typeof parsed.flags.get("name") === "string"
|
|
171
|
+
? String(parsed.flags.get("name"))
|
|
172
|
+
: forge.repo).toLowerCase();
|
|
173
|
+
const existing = await (0, api_js_1.findProject)(wanted);
|
|
174
|
+
if (existing) {
|
|
175
|
+
console.error(red(`You already have a project called ${existing.slug}.`));
|
|
176
|
+
console.error(`Transfer creates a project; it does not merge into one. Choose another\n` +
|
|
177
|
+
`name with ${accent("--name")}, or delete that project first.`);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
const token = forgeToken(forge);
|
|
181
|
+
console.log(`${bold("Transferring")} ${forge.owner}/${forge.repo} ${dim(`from ${forge.label}`)}`);
|
|
182
|
+
console.log(dim(token
|
|
183
|
+
? ` Using the read token in ${forge.tokenNames.find((name) => node_process_1.default.env[name]?.trim())}.`
|
|
184
|
+
: ` No token set. Public repositories work; set ${forge.tokenNames[0]} for a private one or for issues.`));
|
|
185
|
+
const scratch = await (0, promises_1.mkdtemp)(node_path_1.default.join(node_os_1.default.tmpdir(), "cbx-transfer-"));
|
|
186
|
+
const checkout = node_path_1.default.join(scratch, forge.repo);
|
|
187
|
+
const report = [];
|
|
188
|
+
try {
|
|
189
|
+
/*
|
|
190
|
+
A full clone, not the shallow one `import` uses. The whole point here is
|
|
191
|
+
the history, and `--depth 1` would silently deliver a single commit under
|
|
192
|
+
a command whose name promises everything.
|
|
193
|
+
*/
|
|
194
|
+
console.log(`\n${dim("Fetching the repository with its full history…")}`);
|
|
195
|
+
const cloned = run("git", ["clone", "--no-single-branch", forge.clone, checkout]);
|
|
196
|
+
if (!cloned.ok) {
|
|
197
|
+
console.error(red(`\nCould not clone ${forge.clone}`));
|
|
198
|
+
console.error(cloned.out.trim().split("\n").slice(-4).join("\n"));
|
|
199
|
+
if (!token) {
|
|
200
|
+
console.error(dim(`\nIf it is private, set ${forge.tokenNames[0]} and try again.`));
|
|
201
|
+
}
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
/*
|
|
205
|
+
Every branch, not only the one checked out. A fresh clone has remote
|
|
206
|
+
tracking refs for the rest, and nothing local pointing at them, so
|
|
207
|
+
`--all` would push exactly one branch.
|
|
208
|
+
*/
|
|
209
|
+
const remoteBranches = run("git", [
|
|
210
|
+
"for-each-ref", "--format=%(refname:short)", "refs/remotes/origin",
|
|
211
|
+
], checkout).out
|
|
212
|
+
.split("\n").map((one) => one.trim()).filter(Boolean)
|
|
213
|
+
/*
|
|
214
|
+
The remote's HEAD symref comes back as plain `origin`, not
|
|
215
|
+
`origin/HEAD`, so filtering on the latter leaves a phantom entry that
|
|
216
|
+
inflates the branch count and sends git looking for `origin/origin`.
|
|
217
|
+
*/
|
|
218
|
+
.filter((one) => one.startsWith("origin/"))
|
|
219
|
+
.map((one) => one.slice("origin/".length))
|
|
220
|
+
.filter((one) => one && one !== "HEAD");
|
|
221
|
+
for (const branch of remoteBranches) {
|
|
222
|
+
run("git", ["branch", "--force", branch, `origin/${branch}`], checkout);
|
|
223
|
+
}
|
|
224
|
+
const commits = Number(run("git", ["rev-list", "--all", "--count"], checkout).out.trim() || "0");
|
|
225
|
+
const tags = run("git", ["tag", "-l"], checkout).out.split("\n").filter(Boolean);
|
|
226
|
+
console.log(` ${commits} commit${commits === 1 ? "" : "s"} · ` +
|
|
227
|
+
`${remoteBranches.length} branch${remoteBranches.length === 1 ? "" : "es"} · ` +
|
|
228
|
+
`${tags.length} tag${tags.length === 1 ? "" : "s"}`);
|
|
229
|
+
if (commits > 200) {
|
|
230
|
+
console.log(dim(` Each commit becomes a version, so this will take a while —\n` +
|
|
231
|
+
` roughly ${Math.round((commits * 11) / 60)} minutes. It resumes if interrupted.`));
|
|
232
|
+
}
|
|
233
|
+
run("git", ["remote", "add", "coderook", `coderook://${wanted}`], checkout);
|
|
234
|
+
console.log(`\n${dim("Publishing the history…")}`);
|
|
235
|
+
const pushed = run("git", ["push", "coderook", "--all"], checkout);
|
|
236
|
+
node_process_1.default.stderr.write(pushed.out);
|
|
237
|
+
if (!pushed.ok) {
|
|
238
|
+
console.error(red("\nThe history did not transfer completely."));
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
report.push(`${commits} commits across ${remoteBranches.length} branch(es)`);
|
|
242
|
+
if (tags.length) {
|
|
243
|
+
const pushedTags = run("git", ["push", "coderook", "--tags"], checkout);
|
|
244
|
+
node_process_1.default.stderr.write(pushedTags.out);
|
|
245
|
+
report.push(pushedTags.ok
|
|
246
|
+
? `${tags.length} tag(s) as releases`
|
|
247
|
+
: `tags were refused — see above`);
|
|
248
|
+
}
|
|
249
|
+
const project = await (0, api_js_1.findProject)(wanted);
|
|
250
|
+
if (!project) {
|
|
251
|
+
console.error(red("\nThe project was not created. Nothing else was changed."));
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
// What the repository says about itself.
|
|
255
|
+
const facts = await describeSource(forge, token);
|
|
256
|
+
if (facts) {
|
|
257
|
+
const asked = parsed.flags.get("visibility");
|
|
258
|
+
/*
|
|
259
|
+
Private unless asked otherwise, even when the source is public.
|
|
260
|
+
Mirroring visibility is the faithful thing and publishing somebody's
|
|
261
|
+
code by side effect is the unrecoverable thing, so the safe reading
|
|
262
|
+
wins and the report says what was chosen.
|
|
263
|
+
*/
|
|
264
|
+
const visibility = asked === "same"
|
|
265
|
+
? facts.visibility
|
|
266
|
+
: asked === "public"
|
|
267
|
+
? "public"
|
|
268
|
+
: "private";
|
|
269
|
+
await (0, api_js_1.updateProject)(project.id, {
|
|
270
|
+
...(facts.description ? { description: facts.description } : {}),
|
|
271
|
+
visibility,
|
|
272
|
+
});
|
|
273
|
+
report.push(`description and visibility (${visibility}` +
|
|
274
|
+
`${visibility !== facts.visibility ? `, source was ${facts.visibility}` : ""})`);
|
|
275
|
+
/*
|
|
276
|
+
Said rather than left to be noticed. Creating a project always makes a
|
|
277
|
+
`main` line, so a repository whose default is anything else arrives
|
|
278
|
+
with an empty one beside its real branches — and `cbx tracks` marks it
|
|
279
|
+
as the current line, which reads as though the transfer lost the work.
|
|
280
|
+
*/
|
|
281
|
+
if (facts.defaultBranch && facts.defaultBranch !== "main") {
|
|
282
|
+
report.push(`default line is ${facts.defaultBranch}; the empty "main" beside it ` +
|
|
283
|
+
`was made when the project was created`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
report.push("description could not be read from the source");
|
|
288
|
+
}
|
|
289
|
+
// Issues, only when asked and only with a token.
|
|
290
|
+
if (parsed.flags.has("issues")) {
|
|
291
|
+
if (!token) {
|
|
292
|
+
report.push(`issues skipped — set ${forge.tokenNames[0]} and run with --issues again`);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
try {
|
|
296
|
+
const issues = await readIssues(forge, token);
|
|
297
|
+
let made = 0;
|
|
298
|
+
for (const issue of issues) {
|
|
299
|
+
await (0, api_js_1.createIssue)(project.id, {
|
|
300
|
+
title: issue.title,
|
|
301
|
+
body: `${issue.body}\n\n` +
|
|
302
|
+
`— transferred from ${forge.label} ${forge.owner}/${forge.repo}#${issue.number}`,
|
|
303
|
+
});
|
|
304
|
+
made += 1;
|
|
305
|
+
}
|
|
306
|
+
report.push(`${made} open issue(s)`);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
report.push(`issues failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
console.log(`\n${green("Transferred")} ${bold(project.slug)}`);
|
|
314
|
+
for (const line of report)
|
|
315
|
+
console.log(` ${green("·")} ${line}`);
|
|
316
|
+
console.log(`\n${dim("Not carried across:")}`);
|
|
317
|
+
console.log(dim(" · pull requests and reviews — CodeRook has no equivalent object"));
|
|
318
|
+
console.log(dim(" · CI configuration — runs are started deliberately, not by a push"));
|
|
319
|
+
console.log(dim(" · collaborators — access is an invitation to accept, not a field to copy"));
|
|
320
|
+
console.log(dim(" · stars, forks and watchers"));
|
|
321
|
+
if (!parsed.flags.has("issues")) {
|
|
322
|
+
console.log(dim(" · issues — pass --issues to bring the open ones"));
|
|
323
|
+
}
|
|
324
|
+
console.log(`\nFetch it anywhere with ${accent(`cbx clone ${project.slug}`)}` +
|
|
325
|
+
`${dim(", or ")}${accent(`git clone coderook://${project.slug}`)}${dim(".")}`);
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
await (0, promises_1.rm)(scratch, { recursive: true, force: true });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -77,8 +77,18 @@ class Tracks {
|
|
|
77
77
|
* accept, so this passes the name through rather than pre-judging it —
|
|
78
78
|
* a rule enforced in two places is a rule that will disagree with itself.
|
|
79
79
|
*/
|
|
80
|
-
async create(repositoryId, name
|
|
81
|
-
|
|
80
|
+
async create(repositoryId, name,
|
|
81
|
+
/*
|
|
82
|
+
Where the line starts, when the caller knows better than "wherever the
|
|
83
|
+
project is now". Somebody clicking New line means from here; a git branch
|
|
84
|
+
means from the version matching the commit it forked at, which is
|
|
85
|
+
usually not the head. Omitted keeps the original behaviour exactly.
|
|
86
|
+
*/
|
|
87
|
+
fromVersionId) {
|
|
88
|
+
const body = await this.call(`/v1/repositories/${repositoryId}/tracks`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: JSON.stringify(fromVersionId ? { name, fromVersionId } : { name }),
|
|
91
|
+
});
|
|
82
92
|
return body.track;
|
|
83
93
|
}
|
|
84
94
|
/** Merges that have not been applied or abandoned. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "CodeRook from the command line, on any operating system",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"homepage": "https://coderook.com",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"node": ">=20.11.0"
|
|
18
18
|
},
|
|
19
19
|
"bin": {
|
|
20
|
-
"
|
|
20
|
+
"cbx": "dist/cli/src/cli.js",
|
|
21
|
+
"coderook": "dist/cli/src/cli.js",
|
|
22
|
+
"git-remote-coderook": "dist/cli/src/git_remote_bin.js"
|
|
21
23
|
},
|
|
22
24
|
"files": [
|
|
23
25
|
".claude-plugin",
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
"test:matrix": "node test/state-matrix.mjs",
|
|
35
37
|
"test:attempt": "node test/attempt-identity.mjs",
|
|
36
38
|
"test:get": "node test/get-safety.mjs",
|
|
37
|
-
"test:live": "node test/e2e.mjs --allow-production && node test/state-matrix.mjs --allow-production && node test/get-safety.mjs --allow-production && node test/get-protects-edits.mjs --allow-production && node test/upgrade-migration.mjs --allow-production && node test/fault-get.mjs --allow-production && node test/version-floor.mjs --allow-production && node test/fault-submit.mjs --allow-production && node test/race-attempts.mjs --allow-production && node test/attempt-identity.mjs --allow-production",
|
|
39
|
+
"test:live": "node test/e2e.mjs --allow-production && node test/state-matrix.mjs --allow-production && node test/get-safety.mjs --allow-production && node test/get-track.mjs --allow-production && node test/get-protects-edits.mjs --allow-production && node test/upgrade-migration.mjs --allow-production && node test/fault-get.mjs --allow-production && node test/version-floor.mjs --allow-production && node test/fault-submit.mjs --allow-production && node test/race-attempts.mjs --allow-production && node test/attempt-identity.mjs --allow-production",
|
|
38
40
|
"test:getedits": "node test/get-protects-edits.mjs",
|
|
39
41
|
"test:upgrade": "node test/upgrade-migration.mjs",
|
|
40
42
|
"test:faultget": "node test/fault-get.mjs",
|
|
@@ -42,7 +44,8 @@
|
|
|
42
44
|
"test:faultsubmit": "node test/fault-submit.mjs",
|
|
43
45
|
"test:race": "node test/race-attempts.mjs",
|
|
44
46
|
"test:runner": "node test/runner-live.mjs",
|
|
45
|
-
"sync:plugin": "node scripts/sync-plugin-version.mjs"
|
|
47
|
+
"sync:plugin": "node scripts/sync-plugin-version.mjs",
|
|
48
|
+
"test:track": "node test/get-track.mjs"
|
|
46
49
|
},
|
|
47
50
|
"devDependencies": {
|
|
48
51
|
"@types/node": "24.10.1",
|
package/skills/coderook/SKILL.md
CHANGED
|
@@ -1,130 +1,130 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: coderook
|
|
3
|
-
description: Save, browse and restore versions of a project on CodeRook — a version host where every version is a complete snapshot and there is no git to learn. Use when asked to save or submit work to CodeRook, check what has changed, look at a project's versions or files, fetch a project, start or switch a line of work, or resolve a save that landed at the same time as somebody else's.
|
|
4
|
-
allowed-tools: Bash Read
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# CodeRook
|
|
8
|
-
|
|
9
|
-
CodeRook stores whole snapshots of a folder. A version names every file the
|
|
10
|
-
project had at that moment, so restoring one never depends on the versions
|
|
11
|
-
around it, and there is no staging area, no branches to rebase and no history
|
|
12
|
-
to rewrite.
|
|
13
|
-
|
|
14
|
-
Everything here is the `coderook` command line. Run it with Bash.
|
|
15
|
-
|
|
16
|
-
## Before anything else
|
|
17
|
-
|
|
18
|
-
`
|
|
19
|
-
person needs to run `
|
|
20
|
-
token, so do not attempt it on their behalf.
|
|
21
|
-
|
|
22
|
-
If the command is not found at all, this machine has the skill but not the
|
|
23
|
-
command line. Either install it once with
|
|
24
|
-
`npm install --global @coderook/cli`, or put `npx -y @coderook/cli` where
|
|
25
|
-
`coderook` appears below — the commands are identical, npx is just slower
|
|
26
|
-
to start.
|
|
27
|
-
|
|
28
|
-
## Reading
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
`status` is the one to reach for when somebody asks what is uncommitted, what
|
|
38
|
-
changed, or whether anything needs saving. It reads the folder and changes
|
|
39
|
-
nothing.
|
|
40
|
-
|
|
41
|
-
## Saving
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Send only what changed; the version still names every file. Write the message
|
|
48
|
-
yourself from the actual diff rather than asking for one — a message like
|
|
49
|
-
"update" helps nobody reading the history later.
|
|
50
|
-
|
|
51
|
-
**Ask before running this.** Saving is the one thing here that leaves a mark on
|
|
52
|
-
somebody's account, and a version saved by mistake is a version they have to
|
|
53
|
-
explain. Propose it, say what it would send, and let them agree.
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
that freely; it is safe and it is the honest way to answer "what would this
|
|
57
|
-
upload?".
|
|
58
|
-
|
|
59
|
-
## Fetching
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`get` protects local edits by default. `
|
|
67
|
-
to make an exact copy — only run it when the person has said so in those terms.
|
|
68
|
-
|
|
69
|
-
## Lines of work
|
|
70
|
-
|
|
71
|
-
A project can have more than one line, so two people can save without one
|
|
72
|
-
landing on top of the other.
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Switching says where the next save goes and nothing else — no files move.
|
|
82
|
-
Run `
|
|
83
|
-
|
|
84
|
-
## When two saves collide
|
|
85
|
-
|
|
86
|
-
If somebody saved while this folder was behind, the second save becomes a merge
|
|
87
|
-
waiting on a decision rather than overwriting anything.
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Read the conflict out to the person and let them choose. Do not pick a side for
|
|
95
|
-
them: the whole reason it stopped is that the service could not tell which copy
|
|
96
|
-
was wanted.
|
|
97
|
-
|
|
98
|
-
## What is worth leaving out
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Rules live in the project's own `.gitignore`, so CodeRook, git and the website
|
|
106
|
-
all read one file.
|
|
107
|
-
|
|
108
|
-
## Bundles
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Things not to do
|
|
117
|
-
|
|
118
|
-
- Do not run `
|
|
119
|
-
be deleted, by name, in this conversation.
|
|
120
|
-
- Do not run `
|
|
121
|
-
again and gains nothing.
|
|
122
|
-
- Do not guess a project name. `
|
|
123
|
-
already linked needs no name at all.
|
|
124
|
-
|
|
125
|
-
## If something refuses
|
|
126
|
-
|
|
127
|
-
The commands explain themselves — pass the message on rather than rewording it.
|
|
128
|
-
A refusal that says a token was not accepted means signing in; one that says a
|
|
129
|
-
project does not allow machines means its owner turned that off deliberately,
|
|
130
|
-
and the answer is to say so, not to find another route in.
|
|
1
|
+
---
|
|
2
|
+
name: coderook
|
|
3
|
+
description: Save, browse and restore versions of a project on CodeRook — a version host where every version is a complete snapshot and there is no git to learn. Use when asked to save or submit work to CodeRook, check what has changed, look at a project's versions or files, fetch a project, start or switch a line of work, or resolve a save that landed at the same time as somebody else's.
|
|
4
|
+
allowed-tools: Bash Read
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CodeRook
|
|
8
|
+
|
|
9
|
+
CodeRook stores whole snapshots of a folder. A version names every file the
|
|
10
|
+
project had at that moment, so restoring one never depends on the versions
|
|
11
|
+
around it, and there is no staging area, no branches to rebase and no history
|
|
12
|
+
to rewrite.
|
|
13
|
+
|
|
14
|
+
Everything here is the `coderook` command line. Run it with Bash.
|
|
15
|
+
|
|
16
|
+
## Before anything else
|
|
17
|
+
|
|
18
|
+
`cbx whoami` says who this machine is signed in as. If it refuses, the
|
|
19
|
+
person needs to run `cbx sign-in` themselves — it takes a personal access
|
|
20
|
+
token, so do not attempt it on their behalf.
|
|
21
|
+
|
|
22
|
+
If the command is not found at all, this machine has the skill but not the
|
|
23
|
+
command line. Either install it once with
|
|
24
|
+
`npm install --global @coderook/cli`, or put `npx -y @coderook/cli` where
|
|
25
|
+
`coderook` appears below — the commands are identical, npx is just slower
|
|
26
|
+
to start.
|
|
27
|
+
|
|
28
|
+
## Reading
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cbx status # what has changed in this folder since its last version
|
|
32
|
+
cbx projects # every project on the account
|
|
33
|
+
cbx versions [project] # what has been saved, newest first
|
|
34
|
+
cbx tracks [project] # the lines a project has, and any waiting on a decision
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`status` is the one to reach for when somebody asks what is uncommitted, what
|
|
38
|
+
changed, or whether anything needs saving. It reads the folder and changes
|
|
39
|
+
nothing.
|
|
40
|
+
|
|
41
|
+
## Saving
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cbx submit -m "What changed, in a sentence"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Send only what changed; the version still names every file. Write the message
|
|
48
|
+
yourself from the actual diff rather than asking for one — a message like
|
|
49
|
+
"update" helps nobody reading the history later.
|
|
50
|
+
|
|
51
|
+
**Ask before running this.** Saving is the one thing here that leaves a mark on
|
|
52
|
+
somebody's account, and a version saved by mistake is a version they have to
|
|
53
|
+
explain. Propose it, say what it would send, and let them agree.
|
|
54
|
+
|
|
55
|
+
`cbx submit -n` shows exactly what would be sent without sending it. Use
|
|
56
|
+
that freely; it is safe and it is the honest way to answer "what would this
|
|
57
|
+
upload?".
|
|
58
|
+
|
|
59
|
+
## Fetching
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cbx get # bring this folder up to date
|
|
63
|
+
cbx clone <project> [dir] # fetch a project into a new folder
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`get` protects local edits by default. `cbx get --replace` discards them
|
|
67
|
+
to make an exact copy — only run it when the person has said so in those terms.
|
|
68
|
+
|
|
69
|
+
## Lines of work
|
|
70
|
+
|
|
71
|
+
A project can have more than one line, so two people can save without one
|
|
72
|
+
landing on top of the other.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cbx track # which line this folder saves to
|
|
76
|
+
cbx track spike --new # start a line and switch to it
|
|
77
|
+
cbx track main # switch back
|
|
78
|
+
cbx submit --track spike -m "…"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Switching says where the next save goes and nothing else — no files move.
|
|
82
|
+
Run `cbx get` afterwards to bring that line's files in.
|
|
83
|
+
|
|
84
|
+
## When two saves collide
|
|
85
|
+
|
|
86
|
+
If somebody saved while this folder was behind, the second save becomes a merge
|
|
87
|
+
waiting on a decision rather than overwriting anything.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cbx merges # this folder's saves waiting on a decision
|
|
91
|
+
cbx merge <ref> # look at one, and decide
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Read the conflict out to the person and let them choose. Do not pick a side for
|
|
95
|
+
them: the whole reason it stopped is that the service could not tell which copy
|
|
96
|
+
was wanted.
|
|
97
|
+
|
|
98
|
+
## What is worth leaving out
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cbx ignore --suggest # dependency directories, build output, virtual environments
|
|
102
|
+
cbx ignore --suggest --apply # add the confident ones to .gitignore
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Rules live in the project's own `.gitignore`, so CodeRook, git and the website
|
|
106
|
+
all read one file.
|
|
107
|
+
|
|
108
|
+
## Bundles
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cbx bundle . project.cbx # the whole project, every version, as one file
|
|
112
|
+
cbx inspect project.cbx # what is inside, without unpacking
|
|
113
|
+
cbx unbundle project.cbx ./restored
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Things not to do
|
|
117
|
+
|
|
118
|
+
- Do not run `cbx delete` unless the person has asked for that project to
|
|
119
|
+
be deleted, by name, in this conversation.
|
|
120
|
+
- Do not run `cbx sign-out`. It costs them a token they have to fetch
|
|
121
|
+
again and gains nothing.
|
|
122
|
+
- Do not guess a project name. `cbx projects` lists them; a folder that is
|
|
123
|
+
already linked needs no name at all.
|
|
124
|
+
|
|
125
|
+
## If something refuses
|
|
126
|
+
|
|
127
|
+
The commands explain themselves — pass the message on rather than rewording it.
|
|
128
|
+
A refusal that says a token was not accepted means signing in; one that says a
|
|
129
|
+
project does not allow machines means its owner turned that off deliberately,
|
|
130
|
+
and the answer is to say so, not to find another route in.
|