@coderook/cli 0.33.0 → 0.34.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 +4 -3
- package/README.md +64 -9
- package/dist/cbx/src/core/upload.js +4 -0
- package/dist/cbx/src/local/remote.js +1 -0
- package/dist/cli/src/build_command.js +808 -0
- package/dist/cli/src/cli.js +278 -74
- package/dist/cli/src/config.js +6 -0
- package/dist/cli/src/folder_match.js +62 -0
- package/dist/cli/src/git_remote.js +76 -11
- package/dist/cli/src/local_commands.js +23 -2
- package/dist/cli/src/mcp.js +39 -3
- package/dist/cli/src/project_choice.js +122 -0
- package/dist/cli/src/publish.js +1 -0
- package/package.json +1 -1
- package/skills/coderook/SKILL.md +66 -2
|
@@ -934,7 +934,7 @@ depth = null) {
|
|
|
934
934
|
await (0, promises_1.rm)(scratch, { recursive: true, force: true });
|
|
935
935
|
}
|
|
936
936
|
}
|
|
937
|
-
async function doPush(requests, url) {
|
|
937
|
+
async function doPush(requests, url, options = {}) {
|
|
938
938
|
/*
|
|
939
939
|
A push never goes to the account named in the URL — it goes to the one the
|
|
940
940
|
token belongs to. Publishing somebody's work to a different account on
|
|
@@ -988,6 +988,19 @@ async function doPush(requests, url) {
|
|
|
988
988
|
if (!repositoryId)
|
|
989
989
|
repositoryId = (await (0, api_js_1.findProject)(slug))?.id ?? null;
|
|
990
990
|
state = repositoryId ? await remoteState(repositoryId) : null;
|
|
991
|
+
/*
|
|
992
|
+
A clone knows commits the messages do not name: the ones it built when
|
|
993
|
+
it imported each version. Counted as published, so a branch started in
|
|
994
|
+
a clone forks from the version it was cloned at — and a clone of a
|
|
995
|
+
project saved with `cbx submit` is not mistaken for unrelated history.
|
|
996
|
+
*/
|
|
997
|
+
if (state && marks) {
|
|
998
|
+
for (const [versionId, mark] of marks.ofVersion) {
|
|
999
|
+
const sha = marks.sha.get(mark);
|
|
1000
|
+
if (sha && !state.versionOfSha.has(sha))
|
|
1001
|
+
state.versionOfSha.set(sha, versionId);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
991
1004
|
};
|
|
992
1005
|
const knownRefs = () => {
|
|
993
1006
|
const found = new Map();
|
|
@@ -1154,11 +1167,36 @@ async function doPush(requests, url) {
|
|
|
1154
1167
|
the ordinary act of pushing a second branch to a project git already
|
|
1155
1168
|
owns, which is the common case and not a conflict of any kind.
|
|
1156
1169
|
*/
|
|
1170
|
+
/*
|
|
1171
|
+
Unless the person says this repository *is* that project.
|
|
1172
|
+
|
|
1173
|
+
The usual way here is a working repository whose CodeRook project was
|
|
1174
|
+
started with `cbx submit` or the desktop app: same files, different
|
|
1175
|
+
history. Refusing outright sent people to make a second project, which
|
|
1176
|
+
is the duplicate this exists to prevent. `-o adopt` takes the middle
|
|
1177
|
+
road: the project's saves stay as they are, this commit's tree is saved
|
|
1178
|
+
on top as one save that names the commit, and from then on the project
|
|
1179
|
+
and the repository agree, so later pushes add one save per commit.
|
|
1180
|
+
*/
|
|
1181
|
+
let adopting = null;
|
|
1157
1182
|
if (state && !already && state.versionCount > 0 && state.versionOfSha.size === 0) {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1183
|
+
const line = state.tracks.find((candidate) => candidate.name === branch && candidate.kind === "line");
|
|
1184
|
+
if (options.adopt && line?.headVersionId) {
|
|
1185
|
+
adopting = { headVersionId: line.headVersionId };
|
|
1186
|
+
say(` adopting "${slug}": its ${state.versionCount} save${state.versionCount === 1 ? "" : "s"} stay as they are;` +
|
|
1187
|
+
` ${tip.slice(0, 8)} is saved on top as one save,\n and later pushes add one save per commit.`);
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
send(`error ${request.dst} the project "${slug}" already has ${state.versionCount} version${state.versionCount === 1 ? "" : "s"} that did not come from git; pushing would publish this history a second time`);
|
|
1191
|
+
say(options.adopt
|
|
1192
|
+
? `\n The project has no line called "${branch}" to adopt. Push the branch it saves on, usually main.\n`
|
|
1193
|
+
: `\n If this repository holds that project, save it on top and link the two:\n` +
|
|
1194
|
+
` git push -o adopt coderook ${branch}\n` +
|
|
1195
|
+
` Your git history is not replayed; the commit you push becomes one save,\n` +
|
|
1196
|
+
` and every push after it sends one save per commit.\n\n` +
|
|
1197
|
+
` Or start from the project instead: git clone coderook://${slug}\n`);
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1162
1200
|
}
|
|
1163
1201
|
/*
|
|
1164
1202
|
A branch the project has never seen starts at the newest commit it
|
|
@@ -1173,10 +1211,12 @@ async function doPush(requests, url) {
|
|
|
1173
1211
|
}
|
|
1174
1212
|
const from = already ?? fork?.sha ?? null;
|
|
1175
1213
|
const range = from ? `${from}..${tip}` : tip;
|
|
1176
|
-
const commits =
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1214
|
+
const commits = adopting
|
|
1215
|
+
? [tip]
|
|
1216
|
+
: git(["rev-list", "--reverse", "--topo-order", range])
|
|
1217
|
+
.split("\n")
|
|
1218
|
+
.map((line) => line.trim())
|
|
1219
|
+
.filter(Boolean);
|
|
1180
1220
|
if (!commits.length) {
|
|
1181
1221
|
/*
|
|
1182
1222
|
Nothing to publish does not mean nothing to do. A branch whose tip
|
|
@@ -1236,7 +1276,14 @@ async function doPush(requests, url) {
|
|
|
1236
1276
|
let previous = git_history_js_1.EMPTY_TREE;
|
|
1237
1277
|
let currentRepositoryId = repositoryId;
|
|
1238
1278
|
let published = 0;
|
|
1239
|
-
if (
|
|
1279
|
+
if (adopting && repositoryId) {
|
|
1280
|
+
baseVersionId = adopting.headVersionId;
|
|
1281
|
+
const { Downloader } = await Promise.resolve().then(() => __importStar(require("../../cbx/src/core/download.js")));
|
|
1282
|
+
const held = await new Downloader(config_js_1.credentials).files(repositoryId, baseVersionId);
|
|
1283
|
+
manifest = Object.fromEntries(held.map((file) => [file.path, file.sha256]));
|
|
1284
|
+
local = { ...manifest };
|
|
1285
|
+
}
|
|
1286
|
+
else if (from && repositoryId && state) {
|
|
1240
1287
|
previous = from;
|
|
1241
1288
|
if (already) {
|
|
1242
1289
|
const track = state.tracks.find((candidate) => candidate.name === branch && candidate.kind === "line");
|
|
@@ -1281,6 +1328,17 @@ async function doPush(requests, url) {
|
|
|
1281
1328
|
}
|
|
1282
1329
|
const changes = await applyCommit(catFile, scratch, previous, sha);
|
|
1283
1330
|
previous = sha;
|
|
1331
|
+
if (adopting) {
|
|
1332
|
+
/*
|
|
1333
|
+
The commit's tree is the whole truth: what the project holds and
|
|
1334
|
+
the commit does not is removed, exactly as a later push would.
|
|
1335
|
+
*/
|
|
1336
|
+
const inTree = new Set(git(["ls-tree", "-r", "-z", "--name-only", sha]).split("\0").filter(Boolean));
|
|
1337
|
+
for (const file of Object.keys(manifest)) {
|
|
1338
|
+
if (!inTree.has(file) && !changes.deleted.includes(file))
|
|
1339
|
+
changes.deleted.push(file);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1284
1342
|
if (changes.skipped.length) {
|
|
1285
1343
|
say(` skipped ${changes.skipped.length} submodule path(s) in ${sha.slice(0, 8)}`);
|
|
1286
1344
|
}
|
|
@@ -1463,6 +1521,8 @@ async function main(argv) {
|
|
|
1463
1521
|
let unservable = false;
|
|
1464
1522
|
/** `git clone --depth N`, when git passed one. */
|
|
1465
1523
|
let depth = null;
|
|
1524
|
+
/** `git push -o adopt`. */
|
|
1525
|
+
let adopt = false;
|
|
1466
1526
|
for await (const line of lines()) {
|
|
1467
1527
|
const command = line.trim();
|
|
1468
1528
|
if (command === "capabilities") {
|
|
@@ -1486,6 +1546,11 @@ async function main(argv) {
|
|
|
1486
1546
|
depth = Number(value);
|
|
1487
1547
|
send("ok");
|
|
1488
1548
|
}
|
|
1549
|
+
else if (name === "push-option" && value === "adopt") {
|
|
1550
|
+
/* `git push -o adopt`: see the adopting note in doPush. */
|
|
1551
|
+
adopt = true;
|
|
1552
|
+
send("ok");
|
|
1553
|
+
}
|
|
1489
1554
|
else {
|
|
1490
1555
|
send("unsupported");
|
|
1491
1556
|
}
|
|
@@ -1633,7 +1698,7 @@ async function main(argv) {
|
|
|
1633
1698
|
send("");
|
|
1634
1699
|
continue;
|
|
1635
1700
|
}
|
|
1636
|
-
await doPush(batch, url);
|
|
1701
|
+
await doPush(batch, url, { adopt });
|
|
1637
1702
|
}
|
|
1638
1703
|
continue;
|
|
1639
1704
|
}
|
|
@@ -32,6 +32,7 @@ const repository_js_1 = require("../../cbx/src/local/repository.js");
|
|
|
32
32
|
const remote_js_1 = require("../../cbx/src/local/remote.js");
|
|
33
33
|
const api_js_1 = require("./api.js");
|
|
34
34
|
const config_js_1 = require("./config.js");
|
|
35
|
+
const project_choice_js_1 = require("./project_choice.js");
|
|
35
36
|
const project_commands_js_1 = require("./project_commands.js");
|
|
36
37
|
const publish_js_1 = require("./publish.js");
|
|
37
38
|
const ESC = String.fromCharCode(27);
|
|
@@ -363,11 +364,31 @@ async function recordLink(repository, line, repositoryId, head, manifest) {
|
|
|
363
364
|
async function localPush(parsed, repository) {
|
|
364
365
|
return reporting(async () => {
|
|
365
366
|
const link = await (0, config_js_1.readLink)(repository.root);
|
|
367
|
+
/*
|
|
368
|
+
A history that has never been pushed, in a folder linked to nothing,
|
|
369
|
+
is asked which project it belongs to, as `cbx submit` asks: naming it
|
|
370
|
+
after the folder joined any project that shared the name.
|
|
371
|
+
*/
|
|
372
|
+
let chosen = null;
|
|
373
|
+
if (!link && !(await (0, remote_js_1.readRemote)(repository)).repositoryId) {
|
|
374
|
+
const into = parsed.flags.get("into");
|
|
375
|
+
const named = parsed.flags.get("name");
|
|
376
|
+
chosen = await (0, project_choice_js_1.chooseProject)({
|
|
377
|
+
folder: repository.root,
|
|
378
|
+
name: typeof named === "string" ? named : node_path_1.default.basename(repository.root),
|
|
379
|
+
...(typeof into === "string" ? { into } : {}),
|
|
380
|
+
startNew: has(parsed, "new"),
|
|
381
|
+
command: "cbx push",
|
|
382
|
+
});
|
|
383
|
+
if (!chosen)
|
|
384
|
+
return 1;
|
|
385
|
+
}
|
|
366
386
|
const interactive = node_process_1.default.stdout.isTTY;
|
|
367
387
|
const result = await (0, remote_js_1.push)(repository, {
|
|
368
388
|
credentials: config_js_1.credentials,
|
|
369
|
-
projectName: link?.slug ?? node_path_1.default.basename(repository.root),
|
|
370
|
-
repositoryId: link?.repositoryId ?? null,
|
|
389
|
+
projectName: link?.slug ?? (chosen ? ("id" in chosen ? chosen.slug : chosen.name) : node_path_1.default.basename(repository.root)),
|
|
390
|
+
repositoryId: link?.repositoryId ?? (chosen && "id" in chosen ? chosen.id : null),
|
|
391
|
+
joinExisting: false,
|
|
371
392
|
allowSecrets: has(parsed, "allow-secrets"),
|
|
372
393
|
acknowledged: has(parsed, "yes", "y"),
|
|
373
394
|
report: (event) => {
|
package/dist/cli/src/mcp.js
CHANGED
|
@@ -23,6 +23,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.commandMcp = commandMcp;
|
|
26
|
+
const node_fs_1 = require("node:fs");
|
|
26
27
|
const promises_1 = require("node:fs/promises");
|
|
27
28
|
const node_os_1 = __importDefault(require("node:os"));
|
|
28
29
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -236,9 +237,17 @@ const TOOLS = [
|
|
|
236
237
|
: node_process_1.default.cwd());
|
|
237
238
|
const link = await (0, config_js_1.readLink)(folder);
|
|
238
239
|
const rules = await (0, worktree_js_1.readRules)(folder);
|
|
239
|
-
|
|
240
|
+
/*
|
|
241
|
+
Measured against what the folder last held, as `cbx status` measures.
|
|
242
|
+
Passing nothing here called every file in the folder new.
|
|
243
|
+
*/
|
|
244
|
+
const baseline = link?.manifest
|
|
245
|
+
? new Map(Object.entries(link.local ?? link.manifest))
|
|
246
|
+
: null;
|
|
247
|
+
const changed = await (0, worktree_js_1.changedFiles)(folder, rules, baseline);
|
|
248
|
+
const saving = howToSave(folder, link?.slug ?? null);
|
|
240
249
|
if (!changed.length)
|
|
241
|
-
return
|
|
250
|
+
return `Nothing here has changed since the last version.\n${saving}`;
|
|
242
251
|
/*
|
|
243
252
|
Said in the words the scan actually produces: a file the folder no
|
|
244
253
|
longer has is a deletion, one with nothing removed is new, and the
|
|
@@ -256,10 +265,37 @@ const TOOLS = [
|
|
|
256
265
|
lines.push(`… and ${changed.length - lines.length} more`);
|
|
257
266
|
}
|
|
258
267
|
const where = link ? `${link.slug} (v${link.sequence})` : "not linked to a project";
|
|
259
|
-
return `${folder} — ${where}\n${changed.length} changed\n${lines.join("\n")}`;
|
|
268
|
+
return `${folder} — ${where}\n${changed.length} changed\n${lines.join("\n")}\n${saving}`;
|
|
260
269
|
},
|
|
261
270
|
},
|
|
262
271
|
];
|
|
272
|
+
/**
|
|
273
|
+
* Which way this folder is saved, said to the assistant reading the status.
|
|
274
|
+
*
|
|
275
|
+
* Saving stays something a person agrees to, so this names the command and
|
|
276
|
+
* runs nothing. A git repository is pointed at git: each commit then keeps its
|
|
277
|
+
* own message, where `cbx submit` would make one snapshot of all of them.
|
|
278
|
+
*/
|
|
279
|
+
function howToSave(folder, slug) {
|
|
280
|
+
let config = null;
|
|
281
|
+
try {
|
|
282
|
+
config = (0, node_fs_1.readFileSync)(node_path_1.default.join(folder, ".git", "config"), "utf8");
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
/* Not a git repository. */
|
|
286
|
+
}
|
|
287
|
+
if (config !== null) {
|
|
288
|
+
const remote = config.match(/\[remote "([^"]+)"\][^[]*?url\s*=\s*coderook:/);
|
|
289
|
+
if (remote)
|
|
290
|
+
return `To save: commit, then git push ${remote[1]} <branch> (ask first).`;
|
|
291
|
+
return (`This is a git repository with no CodeRook remote. To save each commit: ` +
|
|
292
|
+
`git remote add coderook coderook://${slug ?? "<project>"}, then git push coderook <branch> ` +
|
|
293
|
+
`(git push -o adopt the first time if the project was saved with cbx submit). Ask first.`);
|
|
294
|
+
}
|
|
295
|
+
if (slug)
|
|
296
|
+
return `To save: cbx submit -m "…" (ask first).`;
|
|
297
|
+
return `Not linked. To save, ask which project it is: cbx submit --into <project> or cbx submit --new.`;
|
|
298
|
+
}
|
|
263
299
|
/* ------------------------------------------------------------------ wire */
|
|
264
300
|
function reply(id, result) {
|
|
265
301
|
node_process_1.default.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}\n`);
|
|
@@ -0,0 +1,122 @@
|
|
|
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.likenessLine = likenessLine;
|
|
7
|
+
exports.chooseProject = chooseProject;
|
|
8
|
+
/*
|
|
9
|
+
Which project an unlinked folder's first save goes into, decided by the
|
|
10
|
+
person rather than by a name that happens to match.
|
|
11
|
+
|
|
12
|
+
Shared by `cbx submit` and a local history's first `cbx push`: both used to
|
|
13
|
+
name the project after the folder, join it if the account already had one
|
|
14
|
+
of that name, and start a new one otherwise, without a word either way.
|
|
15
|
+
*/
|
|
16
|
+
const promises_1 = require("node:readline/promises");
|
|
17
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
18
|
+
const api_js_1 = require("./api.js");
|
|
19
|
+
const config_js_1 = require("./config.js");
|
|
20
|
+
const folder_match_js_1 = require("./folder_match.js");
|
|
21
|
+
const download_js_1 = require("../../cbx/src/core/download.js");
|
|
22
|
+
const ESC = String.fromCharCode(27);
|
|
23
|
+
const colour = node_process_1.default.stdout.isTTY && !node_process_1.default.env.NO_COLOR;
|
|
24
|
+
const paint = (code) => (value) => colour ? `${ESC}[${code}m${value}${ESC}[0m` : value;
|
|
25
|
+
const dim = paint("2");
|
|
26
|
+
const red = paint("31");
|
|
27
|
+
const accent = paint("33");
|
|
28
|
+
/**
|
|
29
|
+
* One line about a project an unlinked folder might be, with how much of it
|
|
30
|
+
* is here: the number that tells a stale copy from a separate project.
|
|
31
|
+
*/
|
|
32
|
+
async function likenessLine(folder, like, width) {
|
|
33
|
+
const { project } = like;
|
|
34
|
+
let here = "";
|
|
35
|
+
if (project.versionCount) {
|
|
36
|
+
try {
|
|
37
|
+
const downloader = new download_js_1.Downloader(config_js_1.credentials);
|
|
38
|
+
const latest = (await downloader.versions(project.id))[0];
|
|
39
|
+
if (latest) {
|
|
40
|
+
const paths = (await downloader.files(project.id, latest.id)).map((file) => file.path);
|
|
41
|
+
here = ` · ${(0, folder_match_js_1.pathsPresent)(folder, paths)} of its ${paths.length} files are here at the same path`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* The list is still worth showing without the count. */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return (`${accent(project.slug.padEnd(width))} v${project.versionCount}` +
|
|
49
|
+
dim(`${here}${like.sameName ? " · same name as this folder" : ""}`));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `--into` and `--new` say it outright, which is what a script or an
|
|
53
|
+
* assistant must do. Without either, a folder whose name is like a project
|
|
54
|
+
* already on the account is asked about at a terminal and refused elsewhere,
|
|
55
|
+
* with the two commands that would settle it. A name like nothing on the
|
|
56
|
+
* account is a new project, as it always was.
|
|
57
|
+
*
|
|
58
|
+
* Returns the project to save into, a new project's name, or null to stop.
|
|
59
|
+
*/
|
|
60
|
+
async function chooseProject(input) {
|
|
61
|
+
const { folder, name, into, startNew, command } = input;
|
|
62
|
+
if (into && startNew) {
|
|
63
|
+
console.error(red("Pass --into or --new, not both."));
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (into) {
|
|
67
|
+
const project = await (0, api_js_1.findProject)(into);
|
|
68
|
+
if (!project) {
|
|
69
|
+
console.error(red(`No project matching "${into}" on your account.`));
|
|
70
|
+
console.error(dim(` ${accent("cbx projects")} lists them.`));
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return project;
|
|
74
|
+
}
|
|
75
|
+
const likes = (0, folder_match_js_1.similarProjects)(await (0, api_js_1.projects)(), name);
|
|
76
|
+
const taken = likes.find((like) => like.sameName);
|
|
77
|
+
if (startNew) {
|
|
78
|
+
if (!taken)
|
|
79
|
+
return { name };
|
|
80
|
+
console.error(red(`Your account already has a project called ${taken.project.slug}.`));
|
|
81
|
+
console.error(dim(" Save into it with ") + accent(`--into ${taken.project.slug}`) +
|
|
82
|
+
dim(", or give the new one another name with ") + accent("--name <name>") + dim("."));
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (!likes.length)
|
|
86
|
+
return { name };
|
|
87
|
+
const width = Math.max(...likes.map((like) => like.project.slug.length));
|
|
88
|
+
const lines = await Promise.all(likes.map((like) => likenessLine(folder, like, width)));
|
|
89
|
+
const interactive = Boolean(node_process_1.default.stdin.isTTY && node_process_1.default.stdout.isTTY);
|
|
90
|
+
const say = interactive ? console.log : console.error;
|
|
91
|
+
say(`This folder is not linked to a project, and your account has ${likes.length === 1 ? "one" : "some"} like it:`);
|
|
92
|
+
lines.forEach((line, at) => say(` ${interactive ? `${at + 1} ` : ""}${line}`));
|
|
93
|
+
if (!interactive) {
|
|
94
|
+
console.error(`\nNothing was sent. Say which:`);
|
|
95
|
+
console.error(` ${accent(`${command} --into ${likes[0].project.slug}`)} ${dim("save into that project")}`);
|
|
96
|
+
console.error(` ${accent(`${command} --new${taken ? " --name <another-name>" : ""}`)} ${dim("start a separate project")}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const reader = (0, promises_1.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
|
|
100
|
+
try {
|
|
101
|
+
const answer = (await reader.question(`\nSave into one of these (its number), start a new project (n), or stop (Enter): `)).trim().toLowerCase();
|
|
102
|
+
const picked = Number(answer);
|
|
103
|
+
if (Number.isInteger(picked) && picked >= 1 && picked <= likes.length) {
|
|
104
|
+
return likes[picked - 1].project;
|
|
105
|
+
}
|
|
106
|
+
if (answer !== "n" && answer !== "new")
|
|
107
|
+
return null;
|
|
108
|
+
if (!taken)
|
|
109
|
+
return { name };
|
|
110
|
+
const other = (await reader.question(`Name for the new project: `)).trim();
|
|
111
|
+
if (!other)
|
|
112
|
+
return null;
|
|
113
|
+
if (likes.some((like) => like.project.slug === (0, folder_match_js_1.slugFor)(other))) {
|
|
114
|
+
console.error(red(`${(0, folder_match_js_1.slugFor)(other)} is taken too.`));
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return { name: other };
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
reader.close();
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/cli/src/publish.js
CHANGED
|
@@ -13,6 +13,7 @@ function uploadRequestFor(input) {
|
|
|
13
13
|
message: input.message,
|
|
14
14
|
projectName: input.projectName,
|
|
15
15
|
repositoryId: input.repositoryId,
|
|
16
|
+
...(input.joinExisting === false ? { joinExisting: false } : {}),
|
|
16
17
|
baseVersionId: input.baseVersionId,
|
|
17
18
|
...(input.track ? { track: input.track } : {}),
|
|
18
19
|
...(input.allowIgnored ? { allowIgnored: true } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
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": "Apache-2.0",
|
|
6
6
|
"homepage": "https://coderook.com",
|
package/skills/coderook/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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
|
|
3
|
+
description: Save, browse and restore versions of a project on CodeRook — a version host where every version is a complete snapshot, reached with the cbx command or with plain git. Use when asked to save, submit or push 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
4
|
allowed-tools: Bash Read
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -11,7 +11,13 @@ project had at that moment, so restoring one never depends on the versions
|
|
|
11
11
|
around it, and there is no staging area, no branches to rebase and no history
|
|
12
12
|
to rewrite.
|
|
13
13
|
|
|
14
|
-
Everything here is the `
|
|
14
|
+
Everything here is the `cbx` command line (also installed as `coderook`), or
|
|
15
|
+
git itself through CodeRook's remote helper. Run them with Bash.
|
|
16
|
+
|
|
17
|
+
**Which one to use.** If the folder is a git repository (`git rev-parse` works
|
|
18
|
+
in it), prefer git — see *Working in a git repository* below. Each commit then
|
|
19
|
+
arrives with its own message, instead of one snapshot standing in for many.
|
|
20
|
+
Use `cbx submit` for folders that are not git repositories.
|
|
15
21
|
|
|
16
22
|
## Before anything else
|
|
17
23
|
|
|
@@ -44,6 +50,21 @@ nothing.
|
|
|
44
50
|
cbx submit -m "What changed, in a sentence"
|
|
45
51
|
```
|
|
46
52
|
|
|
53
|
+
**The first save from a folder must say which project it is.** A folder that
|
|
54
|
+
is not linked goes to a new project named after it — unless the account has a
|
|
55
|
+
project with a name like it, and then `submit` stops and lists them. Do not
|
|
56
|
+
guess: show the person the list and ask. Then run one of:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cbx submit --into <project> -m "…" # this folder is that project
|
|
60
|
+
cbx submit --new -m "…" # a separate project (add --name if the name is taken)
|
|
61
|
+
cbx link <project> # link only; send nothing yet
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Never start a second project for work that already has one. A folder copied to
|
|
65
|
+
another machine, another operating system or another path is still the same
|
|
66
|
+
project: `cbx link` it.
|
|
67
|
+
|
|
47
68
|
Send only what changed; the version still names every file. Write the message
|
|
48
69
|
yourself from the actual diff rather than asking for one — a message like
|
|
49
70
|
"update" helps nobody reading the history later.
|
|
@@ -56,6 +77,33 @@ explain. Propose it, say what it would send, and let them agree.
|
|
|
56
77
|
that freely; it is safe and it is the honest way to answer "what would this
|
|
57
78
|
upload?".
|
|
58
79
|
|
|
80
|
+
## Working in a git repository
|
|
81
|
+
|
|
82
|
+
CodeRook is a git remote. `npm install --global @coderook/cli` puts the helper
|
|
83
|
+
(`git-remote-coderook`) on PATH; the plugin and `npx` do not, so check with
|
|
84
|
+
`git ls-remote coderook://<project>` before relying on it.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git remote -v # is there a coderook:// remote already?
|
|
88
|
+
git remote add coderook coderook://<project> # if not
|
|
89
|
+
git push coderook main # each new commit becomes a save
|
|
90
|
+
git push coderook <branch> # a branch becomes a line
|
|
91
|
+
git push coderook v1.2.0 # a tag names a version
|
|
92
|
+
git fetch coderook # bring other people's saves in
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If the project was started with `cbx submit` or the desktop app, the first push
|
|
96
|
+
is refused with a message saying so. When the repository holds that project,
|
|
97
|
+
`git push -o adopt coderook main` saves the current commit on top of what is
|
|
98
|
+
there, once; plain pushes work from then on. **Ask before adopting** — it is a
|
|
99
|
+
save like any other. If the project is somebody else's work under the same
|
|
100
|
+
name, push to a new name instead.
|
|
101
|
+
|
|
102
|
+
Git's limits here: no force pushes or branch deletions (saves are permanent), a
|
|
103
|
+
merge commit arrives as one save holding the merged tree, and a commit holding
|
|
104
|
+
a credential is refused — take the key out of history rather than looking for
|
|
105
|
+
a way round.
|
|
106
|
+
|
|
59
107
|
## Fetching
|
|
60
108
|
|
|
61
109
|
```bash
|
|
@@ -234,6 +282,20 @@ file with its size, SHA-256 and `url`. Updaters have no account, so the
|
|
|
234
282
|
project they read must be public; keep private code in a separate project and
|
|
235
283
|
ship releases from a public one.
|
|
236
284
|
|
|
285
|
+
## Building executables for every platform
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
cbx build --plan # how this machine would build each target
|
|
289
|
+
cbx build --target win,linux,mac # build them into .coderook/build/<target>/
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
It builds on this machine, with the tools it has, and uploads nothing. Run
|
|
293
|
+
`--plan` first and read it out: a target marked "no" says what would build it
|
|
294
|
+
(Docker, a Mac, cargo-zigbuild) — pass that on rather than installing things
|
|
295
|
+
unasked. `--ship` attaches the results to the newest save, so **ask before
|
|
296
|
+
`--ship`**, as before any save. A project's own build description lives in
|
|
297
|
+
`coderook.build.json`.
|
|
298
|
+
|
|
237
299
|
## Things not to do
|
|
238
300
|
|
|
239
301
|
- Do not run `cbx delete` unless the person has asked for that project to
|
|
@@ -242,6 +304,8 @@ ship releases from a public one.
|
|
|
242
304
|
again and gains nothing.
|
|
243
305
|
- Do not guess a project name. `cbx projects` lists them; a folder that is
|
|
244
306
|
already linked needs no name at all.
|
|
307
|
+
- Do not pass `--new` to get past the question `submit` asks. It exists for a
|
|
308
|
+
separate project the person wants, not to avoid choosing.
|
|
245
309
|
- Do not run `cbx take-down`. It destroys the files in a version and they do
|
|
246
310
|
not come back. Say it exists and let its owner run it.
|
|
247
311
|
|