@coderook/cli 0.42.1 → 0.42.3
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.
|
@@ -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, with cbx or plain git, from Claude Code.",
|
|
5
|
-
"version": "0.42.
|
|
5
|
+
"version": "0.42.3",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ACCA Gaming Productions",
|
|
8
8
|
"url": "https://coderook.com"
|
package/dist/cbx/src/core/cbx.js
CHANGED
|
@@ -11,7 +11,7 @@ exports.readManifest = readManifest;
|
|
|
11
11
|
exports.unpackBundle = unpackBundle;
|
|
12
12
|
exports.collectEntries = collectEntries;
|
|
13
13
|
/**
|
|
14
|
-
* The
|
|
14
|
+
* The CBX `.cbx` bundle, as `prototype/cbx_benchmark.py` writes it.
|
|
15
15
|
*
|
|
16
16
|
* This is a port of that format, not a second one. The byte layout, the
|
|
17
17
|
* manifest keys, the codec rule and the chunk index are taken from the
|
|
@@ -294,12 +294,12 @@ async function readManifest(source) {
|
|
|
294
294
|
try {
|
|
295
295
|
const size = (await handle.stat()).size;
|
|
296
296
|
if (size < HEADER_BYTES + FOOTER_BYTES) {
|
|
297
|
-
throw new Error("That file is too small to be a
|
|
297
|
+
throw new Error("That file is too small to be a CBX bundle");
|
|
298
298
|
}
|
|
299
299
|
const header = Buffer.alloc(HEADER_BYTES);
|
|
300
300
|
await handle.read(header, 0, HEADER_BYTES, 0);
|
|
301
301
|
if (header.subarray(0, 4).toString("latin1") !== exports.HEADER_MAGIC) {
|
|
302
|
-
throw new Error("That file is not a
|
|
302
|
+
throw new Error("That file is not a CBX bundle");
|
|
303
303
|
}
|
|
304
304
|
const version = header.readUInt16LE(4);
|
|
305
305
|
if (!SUPPORTED_FORMAT_VERSIONS.has(version)) {
|
|
@@ -110,7 +110,7 @@ const RULES = [
|
|
|
110
110
|
{ extension: ".ckpt", reason: "Model weights — large, and usually downloadable", recommended: false },
|
|
111
111
|
{ extension: ".pt", reason: "Model weights — large, and usually downloadable", recommended: false },
|
|
112
112
|
{ extension: ".pth", reason: "Model weights — large, and usually downloadable", recommended: false },
|
|
113
|
-
{ extension: ".cbx", reason: "A
|
|
113
|
+
{ extension: ".cbx", reason: "A CBX bundle of this project, inside the project", recommended: true },
|
|
114
114
|
{ extension: ".pyc", reason: "Python bytecode", recommended: true },
|
|
115
115
|
{ extension: ".log", reason: "Log files", recommended: false },
|
|
116
116
|
{ file: "Local State", reason: "Browser profile data", recommended: true },
|
package/dist/cli/src/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.REGISTRY = void 0;
|
|
8
8
|
/**
|
|
9
|
-
* cbx — the
|
|
9
|
+
* cbx — the CBX engine from the command line, talking to CodeRook.
|
|
10
10
|
*
|
|
11
11
|
* The same engines the desktop application uses — the same scanner, the same
|
|
12
12
|
* ignore rules, the same upload and download — with a terminal in front of
|
|
@@ -65,6 +65,7 @@ const diff_command_js_1 = require("./diff_command.js");
|
|
|
65
65
|
const network_js_1 = require("./network.js");
|
|
66
66
|
const local_commands_js_1 = require("./local_commands.js");
|
|
67
67
|
const profile_js_1 = require("../../cbx/src/core/profile.js");
|
|
68
|
+
const remote_restore_js_1 = require("./remote_restore.js");
|
|
68
69
|
const detect_js_1 = require("../../cbx/src/core/detect.js");
|
|
69
70
|
const cbx_js_1 = require("../../cbx/src/core/cbx.js");
|
|
70
71
|
const api_js_2 = require("./api.js");
|
|
@@ -103,9 +104,22 @@ function bytes(value) {
|
|
|
103
104
|
/** One line that rewrites itself, so progress does not scroll the terminal. */
|
|
104
105
|
function progressLine() {
|
|
105
106
|
let last = 0;
|
|
107
|
+
let lastPlain = 0;
|
|
106
108
|
return (text) => {
|
|
107
|
-
|
|
109
|
+
/*
|
|
110
|
+
Without a terminal (a script, a bot, a CI log) the redrawn bar cannot
|
|
111
|
+
work, and this used to print nothing at all: a 1.79 GB clone ran for
|
|
112
|
+
minutes in silence and was taken for a hang (2026-11-05). So a plain
|
|
113
|
+
line instead, at most every ten seconds.
|
|
114
|
+
*/
|
|
115
|
+
if (!node_process_1.default.stdout.isTTY) {
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
if (now - lastPlain < 10_000)
|
|
118
|
+
return;
|
|
119
|
+
lastPlain = now;
|
|
120
|
+
node_process_1.default.stdout.write(`${text.replace(/\[[0-9;]*m/g, "").replace(/[█░▏▎▍▌▋▊▉]+/g, "").replace(/\s+/g, " ").trim()}\n`);
|
|
108
121
|
return;
|
|
122
|
+
}
|
|
109
123
|
/*
|
|
110
124
|
Measured without the colour codes. Padding to the raw length of a string
|
|
111
125
|
carrying escape sequences overshoots by however many invisible
|
|
@@ -2470,15 +2484,17 @@ const SPECS = [
|
|
|
2470
2484
|
detail: "With no paths, makes the whole folder match the save; with paths,\n" +
|
|
2471
2485
|
"only those files and folders. The default is the last save, which\n" +
|
|
2472
2486
|
"throws away what you changed since.\n\n" +
|
|
2487
|
+
"In a folder linked to a CodeRook project, the save is a Version on\n" +
|
|
2488
|
+
"CodeRook: the one the folder is on, or --from vN.\n\n" +
|
|
2473
2489
|
"Unsaved changes to a file it would overwrite stop it, and it lists\n" +
|
|
2474
2490
|
"them. Add --force to discard them. Files the history has never held\n" +
|
|
2475
2491
|
"are left alone either way, and your line does not move.",
|
|
2476
2492
|
options: [
|
|
2477
|
-
{ flags: "--from <save>", description: "a line, line~N, or the start of a save id" },
|
|
2493
|
+
{ flags: "--from <save>", description: "a line, line~N, or the start of a save id; vN in a linked folder" },
|
|
2478
2494
|
{ flags: "-f, --force", description: "overwrite unsaved changes" },
|
|
2479
2495
|
],
|
|
2480
|
-
examples: ["cbx restore src/player.ts --force", "cbx restore --from main~3"],
|
|
2481
|
-
run: local_commands_js_1.commandRestore,
|
|
2496
|
+
examples: ["cbx restore src/player.ts --force", "cbx restore --from main~3", "cbx restore website/app --from v272"],
|
|
2497
|
+
run: (0, local_commands_js_1.localFirst)((parsed) => (0, local_commands_js_1.commandRestore)(parsed), remote_restore_js_1.commandRemoteRestore),
|
|
2482
2498
|
},
|
|
2483
2499
|
{
|
|
2484
2500
|
name: "switch",
|
package/dist/cli/src/help.js
CHANGED
|
@@ -39,7 +39,7 @@ function renderHelp(registry, version) {
|
|
|
39
39
|
const listed = registry.specs.filter((spec) => !spec.deprecatedBy);
|
|
40
40
|
const column = Math.min(nameColumn(listed), exports.USAGE_COLUMN);
|
|
41
41
|
const lines = [
|
|
42
|
-
`${(0, colour_js_1.bold)("cbx")} ${(0, colour_js_1.dim)(version)} — the
|
|
42
|
+
`${(0, colour_js_1.bold)("cbx")} ${(0, colour_js_1.dim)(version)} — the CBX engine, for CodeRook`,
|
|
43
43
|
"",
|
|
44
44
|
];
|
|
45
45
|
for (const group of registry_js_1.GROUP_ORDER) {
|
|
@@ -0,0 +1,174 @@
|
|
|
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.linkedRoot = linkedRoot;
|
|
7
|
+
exports.selectFiles = selectFiles;
|
|
8
|
+
exports.projectPath = projectPath;
|
|
9
|
+
exports.commandRemoteRestore = commandRemoteRestore;
|
|
10
|
+
/**
|
|
11
|
+
* `cbx restore` in a folder linked to a CodeRook project.
|
|
12
|
+
*
|
|
13
|
+
* `restore` used to know only the local history (`cbx init` and `cbx save`),
|
|
14
|
+
* so in a folder made by `cbx clone` or `cbx link`, the kind nearly everyone
|
|
15
|
+
* has, it answered "No history here" while `cbx log` beside it worked.
|
|
16
|
+
* The folder does have a history; it lives on CodeRook. This puts files back
|
|
17
|
+
* the way a saved Version had them, by default the Version the folder is on.
|
|
18
|
+
*
|
|
19
|
+
* Same rules as the local restore:
|
|
20
|
+
* - an unsaved edit to a file it would overwrite stops it, listed, unless
|
|
21
|
+
* --force is given;
|
|
22
|
+
* - files the Version does not hold are left alone;
|
|
23
|
+
* - the folder's link does not move, so `cbx submit` afterwards saves the
|
|
24
|
+
* folder as it now is, and `cbx get` still sees restored-from-older files
|
|
25
|
+
* as edits rather than silently overwriting them.
|
|
26
|
+
*/
|
|
27
|
+
const node_crypto_1 = require("node:crypto");
|
|
28
|
+
const node_fs_1 = require("node:fs");
|
|
29
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
30
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
31
|
+
const download_js_1 = require("../../cbx/src/core/download.js");
|
|
32
|
+
const safe_path_js_1 = require("../../cbx/src/shared/safe_path.js");
|
|
33
|
+
const config_js_1 = require("./config.js");
|
|
34
|
+
const ESC = String.fromCharCode(27);
|
|
35
|
+
const paint = (code) => (value) => `${ESC}[${code}m${value}${ESC}[0m`;
|
|
36
|
+
const dim = paint("2");
|
|
37
|
+
const red = paint("31");
|
|
38
|
+
const green = paint("32");
|
|
39
|
+
const accent = paint("33");
|
|
40
|
+
const plural = (count, one, many = `${one}s`) => `${count} ${count === 1 ? one : many}`;
|
|
41
|
+
async function digestOfFile(full) {
|
|
42
|
+
try {
|
|
43
|
+
const hash = (0, node_crypto_1.createHash)("sha256");
|
|
44
|
+
for await (const block of (0, node_fs_1.createReadStream)(full))
|
|
45
|
+
hash.update(block);
|
|
46
|
+
return hash.digest("hex");
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** The linked folder this directory is in, walking up as `git` does. */
|
|
53
|
+
async function linkedRoot(from) {
|
|
54
|
+
let at = node_path_1.default.resolve(from);
|
|
55
|
+
for (;;) {
|
|
56
|
+
const link = await (0, config_js_1.readLink)(at);
|
|
57
|
+
if (link)
|
|
58
|
+
return { root: at, link };
|
|
59
|
+
const parent = node_path_1.default.dirname(at);
|
|
60
|
+
if (parent === at)
|
|
61
|
+
return null;
|
|
62
|
+
at = parent;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Project paths a restore covers: the named files and everything under named folders. */
|
|
66
|
+
function selectFiles(files, wanted) {
|
|
67
|
+
if (!wanted.length)
|
|
68
|
+
return files;
|
|
69
|
+
return files.filter((file) => wanted.some((one) => one === "" || file.path === one || file.path.startsWith(`${one}/`)));
|
|
70
|
+
}
|
|
71
|
+
/** A path typed on the command line, as a path inside the project ("" for the root). */
|
|
72
|
+
function projectPath(root, cwd, typed) {
|
|
73
|
+
const relative = node_path_1.default.relative(root, node_path_1.default.resolve(cwd, typed));
|
|
74
|
+
if (relative.startsWith("..") || node_path_1.default.isAbsolute(relative))
|
|
75
|
+
return null;
|
|
76
|
+
return relative.split(node_path_1.default.sep).join("/").replace(/\/+$/, "");
|
|
77
|
+
}
|
|
78
|
+
async function commandRemoteRestore(parsed) {
|
|
79
|
+
const found = await linkedRoot(node_process_1.default.cwd());
|
|
80
|
+
if (!found) {
|
|
81
|
+
console.error(red("No history here."));
|
|
82
|
+
console.error(dim(" This folder is not linked to a CodeRook project and has no local history. ") +
|
|
83
|
+
accent("cbx link <project>") + dim(" connects it; ") + accent("cbx init") +
|
|
84
|
+
dim(" starts a history on this machine."));
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
const { root, link } = found;
|
|
88
|
+
const force = parsed.flags.has("force") || parsed.flags.has("f");
|
|
89
|
+
const wanted = [];
|
|
90
|
+
for (const typed of parsed.positional) {
|
|
91
|
+
const one = projectPath(root, node_process_1.default.cwd(), typed);
|
|
92
|
+
if (one === null) {
|
|
93
|
+
console.error(red(`${typed} is outside the project folder (${root}).`));
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
wanted.push(one);
|
|
97
|
+
}
|
|
98
|
+
const downloader = new download_js_1.Downloader(config_js_1.credentials);
|
|
99
|
+
const versions = await downloader.versions(link.repositoryId);
|
|
100
|
+
const from = parsed.flags.get("from");
|
|
101
|
+
let version;
|
|
102
|
+
if (typeof from === "string") {
|
|
103
|
+
const sequence = /^v?(\d+)$/i.exec(from.trim());
|
|
104
|
+
version = sequence
|
|
105
|
+
? versions.find((one) => one.sequence === Number(sequence[1]))
|
|
106
|
+
: versions.find((one) => one.id.startsWith(from.trim()));
|
|
107
|
+
if (!version) {
|
|
108
|
+
console.error(red(`No version ${from} in ${link.slug}.`) + dim(" cbx versions lists them."));
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const base = link.baseVersionId ?? link.versionId;
|
|
114
|
+
version =
|
|
115
|
+
versions.find((one) => one.id === base) ??
|
|
116
|
+
versions.find((one) => one.sequence === link.sequence);
|
|
117
|
+
if (!version) {
|
|
118
|
+
console.error(red("This folder's saved version could not be found.") +
|
|
119
|
+
dim(" Name one with --from vN."));
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const files = selectFiles(await downloader.files(link.repositoryId, version.id), wanted);
|
|
124
|
+
if (!files.length) {
|
|
125
|
+
console.error(red(`v${version.sequence} holds nothing at ${wanted.join(", ") || "the project root"}.`));
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
const toWrite = [];
|
|
129
|
+
const edited = [];
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
const unsafe = (0, safe_path_js_1.whyUnsafe)(file.path);
|
|
132
|
+
if (unsafe) {
|
|
133
|
+
console.error(red(`Skipped ${JSON.stringify(file.path)}: ${unsafe}.`));
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const here = await digestOfFile(node_path_1.default.join(root, ...file.path.split("/")));
|
|
137
|
+
if (here === file.sha256)
|
|
138
|
+
continue;
|
|
139
|
+
/*
|
|
140
|
+
Different from the version. If it still matches what CodeRook last placed
|
|
141
|
+
here, it is only an older or newer copy and may be replaced. Otherwise it
|
|
142
|
+
is somebody's unsaved work, and that needs --force.
|
|
143
|
+
*/
|
|
144
|
+
const placed = link.local?.[file.path];
|
|
145
|
+
if (here !== null && here !== placed && !force)
|
|
146
|
+
edited.push(file.path);
|
|
147
|
+
toWrite.push(file);
|
|
148
|
+
}
|
|
149
|
+
if (edited.length) {
|
|
150
|
+
console.error(red(`${plural(edited.length, "file")} here ${edited.length === 1 ? "has" : "have"} ` +
|
|
151
|
+
`unsaved changes that restoring would overwrite:`));
|
|
152
|
+
for (const one of edited.slice(0, 20))
|
|
153
|
+
console.error(` ${one}`);
|
|
154
|
+
if (edited.length > 20)
|
|
155
|
+
console.error(dim(` …and ${edited.length - 20} more`));
|
|
156
|
+
console.error(`\nAdd ${accent("--force")} to discard them, or save them first with ${accent("cbx submit")}.`);
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
if (!toWrite.length) {
|
|
160
|
+
console.log(`Already matches ${accent(`v${version.sequence}`)}; nothing to put back.`);
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
for (const file of toWrite) {
|
|
164
|
+
await downloader.fileTo(link.repositoryId, version.id, file, node_path_1.default.join(root, ...file.path.split("/")));
|
|
165
|
+
}
|
|
166
|
+
console.log(`Put back ${plural(toWrite.length, "file")} from ${accent(`v${version.sequence}`)}: ` +
|
|
167
|
+
`${version.message.split("\n")[0]}`);
|
|
168
|
+
for (const file of toWrite.slice(0, 20))
|
|
169
|
+
console.log(` ${green("restored")} ${file.path}`);
|
|
170
|
+
if (toWrite.length > 20)
|
|
171
|
+
console.log(dim(` …and ${toWrite.length - 20} more`));
|
|
172
|
+
console.log(dim("The folder's link has not moved. cbx submit saves the folder as it is now."));
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.42.
|
|
3
|
+
"version": "0.42.3",
|
|
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",
|