@coderook/cli 0.34.11 → 0.36.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 +71 -0
- package/dist/cbx/src/core/worktree.js +29 -2
- package/dist/cli/src/api.js +16 -0
- package/dist/cli/src/cli.js +97 -0
- package/dist/cli/src/github_bridge.js +403 -0
- package/dist/cli/src/mods_command.js +518 -0
- package/dist/cli/src/zip_writer.js +188 -0
- package/package.json +1 -1
- package/skills/coderook/SKILL.md +45 -1
|
@@ -0,0 +1,188 @@
|
|
|
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.crc32 = crc32;
|
|
7
|
+
exports.listFiles = listFiles;
|
|
8
|
+
exports.writeZip = writeZip;
|
|
9
|
+
/**
|
|
10
|
+
* Write a folder as a zip, one file at a time, to disk.
|
|
11
|
+
*
|
|
12
|
+
* Mod repositories take each part as a zip, and a game's mod can hold
|
|
13
|
+
* hundreds of thousands of files and run to gigabytes — ZT1's art packs do —
|
|
14
|
+
* so the archive is written as it goes rather than built in memory, and
|
|
15
|
+
* ZIP64 records are used once sizes, offsets or the file count pass what the
|
|
16
|
+
* classic format can hold. Each file is deflated, or stored when deflating
|
|
17
|
+
* would not make it smaller (already-compressed images and audio).
|
|
18
|
+
*/
|
|
19
|
+
const node_fs_1 = require("node:fs");
|
|
20
|
+
const promises_1 = require("node:fs/promises");
|
|
21
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
22
|
+
const node_zlib_1 = require("node:zlib");
|
|
23
|
+
const CRC_TABLE = (() => {
|
|
24
|
+
const table = new Uint32Array(256);
|
|
25
|
+
for (let n = 0; n < 256; n += 1) {
|
|
26
|
+
let c = n;
|
|
27
|
+
for (let k = 0; k < 8; k += 1)
|
|
28
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
29
|
+
table[n] = c >>> 0;
|
|
30
|
+
}
|
|
31
|
+
return table;
|
|
32
|
+
})();
|
|
33
|
+
function crc32(bytes) {
|
|
34
|
+
let crc = 0xffffffff;
|
|
35
|
+
for (let i = 0; i < bytes.length; i += 1)
|
|
36
|
+
crc = CRC_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
|
|
37
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
38
|
+
}
|
|
39
|
+
const MAX32 = 0xffffffff;
|
|
40
|
+
const MAX16 = 0xffff;
|
|
41
|
+
function dosTime(when) {
|
|
42
|
+
return {
|
|
43
|
+
time: (when.getHours() << 11) | (when.getMinutes() << 5) | Math.floor(when.getSeconds() / 2),
|
|
44
|
+
date: ((Math.max(1980, when.getFullYear()) - 1980) << 9) | ((when.getMonth() + 1) << 5) | when.getDate(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Every file under a folder, as zip paths with forward slashes, in a stable order. */
|
|
48
|
+
async function listFiles(root, skip = () => false) {
|
|
49
|
+
const found = [];
|
|
50
|
+
const walk = async (dir) => {
|
|
51
|
+
const entries = (await (0, promises_1.readdir)(node_path_1.default.join(root, dir), { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const relative = dir ? `${dir}/${entry.name}` : entry.name;
|
|
54
|
+
if (skip(relative))
|
|
55
|
+
continue;
|
|
56
|
+
if (entry.isSymbolicLink())
|
|
57
|
+
continue;
|
|
58
|
+
if (entry.isDirectory())
|
|
59
|
+
await walk(relative);
|
|
60
|
+
else if (entry.isFile())
|
|
61
|
+
found.push(relative);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
await walk("");
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Zip these files (paths relative to root) into `out`. `extra` adds entries
|
|
69
|
+
* written from memory — a generated manifest, for one.
|
|
70
|
+
*/
|
|
71
|
+
async function writeZip(root, files, out, extra = [], onProgress) {
|
|
72
|
+
const stream = (0, node_fs_1.createWriteStream)(out);
|
|
73
|
+
let failed = null;
|
|
74
|
+
stream.on("error", (error) => {
|
|
75
|
+
failed = error;
|
|
76
|
+
});
|
|
77
|
+
const write = async (chunk) => {
|
|
78
|
+
if (failed)
|
|
79
|
+
throw failed;
|
|
80
|
+
if (!stream.write(chunk))
|
|
81
|
+
await new Promise((resolve) => stream.once("drain", resolve));
|
|
82
|
+
if (failed)
|
|
83
|
+
throw failed;
|
|
84
|
+
};
|
|
85
|
+
const written = [];
|
|
86
|
+
let offset = 0;
|
|
87
|
+
const { time, date } = dosTime(new Date());
|
|
88
|
+
const extraNames = new Set(extra.map((one) => one.path));
|
|
89
|
+
const sources = [
|
|
90
|
+
...extra.map((one) => ({ path: one.path, load: async () => one.bytes })),
|
|
91
|
+
...files.filter((file) => !extraNames.has(file)).map((file) => ({ path: file, load: () => (0, promises_1.readFile)(node_path_1.default.join(root, file)) })),
|
|
92
|
+
];
|
|
93
|
+
for (const [index, source] of sources.entries()) {
|
|
94
|
+
const raw = await source.load();
|
|
95
|
+
const deflated = (0, node_zlib_1.deflateRawSync)(raw, { level: 6 });
|
|
96
|
+
const store = deflated.length >= raw.length;
|
|
97
|
+
const body = store ? raw : deflated;
|
|
98
|
+
const name = Buffer.from(source.path, "utf8");
|
|
99
|
+
const crc = crc32(raw);
|
|
100
|
+
const zip64 = raw.length >= MAX32 || body.length >= MAX32;
|
|
101
|
+
const local = Buffer.alloc(30);
|
|
102
|
+
local.writeUInt32LE(0x04034b50, 0);
|
|
103
|
+
local.writeUInt16LE(zip64 ? 45 : 20, 4);
|
|
104
|
+
local.writeUInt16LE(0x0800, 6); /* UTF-8 names */
|
|
105
|
+
local.writeUInt16LE(store ? 0 : 8, 8);
|
|
106
|
+
local.writeUInt16LE(time, 10);
|
|
107
|
+
local.writeUInt16LE(date, 12);
|
|
108
|
+
local.writeUInt32LE(crc, 14);
|
|
109
|
+
local.writeUInt32LE(zip64 ? MAX32 : body.length, 18);
|
|
110
|
+
local.writeUInt32LE(zip64 ? MAX32 : raw.length, 22);
|
|
111
|
+
local.writeUInt16LE(name.length, 26);
|
|
112
|
+
const localExtra = zip64 ? zip64Extra([raw.length, body.length]) : Buffer.alloc(0);
|
|
113
|
+
local.writeUInt16LE(localExtra.length, 28);
|
|
114
|
+
await write(local);
|
|
115
|
+
await write(name);
|
|
116
|
+
if (localExtra.length)
|
|
117
|
+
await write(localExtra);
|
|
118
|
+
await write(body);
|
|
119
|
+
written.push({ name, crc, method: store ? 0 : 8, compressed: body.length, size: raw.length, offset, time, date });
|
|
120
|
+
offset += local.length + name.length + localExtra.length + body.length;
|
|
121
|
+
onProgress?.(index + 1, sources.length);
|
|
122
|
+
}
|
|
123
|
+
const centralStart = offset;
|
|
124
|
+
for (const entry of written) {
|
|
125
|
+
const big = [entry.size >= MAX32, entry.compressed >= MAX32, entry.offset >= MAX32];
|
|
126
|
+
const values = [
|
|
127
|
+
...(big[0] ? [entry.size] : []),
|
|
128
|
+
...(big[1] ? [entry.compressed] : []),
|
|
129
|
+
...(big[2] ? [entry.offset] : []),
|
|
130
|
+
];
|
|
131
|
+
const extraField = values.length ? zip64Extra(values) : Buffer.alloc(0);
|
|
132
|
+
const central = Buffer.alloc(46);
|
|
133
|
+
central.writeUInt32LE(0x02014b50, 0);
|
|
134
|
+
central.writeUInt16LE(values.length ? 45 : 20, 4);
|
|
135
|
+
central.writeUInt16LE(values.length ? 45 : 20, 6);
|
|
136
|
+
central.writeUInt16LE(0x0800, 8);
|
|
137
|
+
central.writeUInt16LE(entry.method, 10);
|
|
138
|
+
central.writeUInt16LE(entry.time, 12);
|
|
139
|
+
central.writeUInt16LE(entry.date, 14);
|
|
140
|
+
central.writeUInt32LE(entry.crc, 16);
|
|
141
|
+
central.writeUInt32LE(big[1] ? MAX32 : entry.compressed, 20);
|
|
142
|
+
central.writeUInt32LE(big[0] ? MAX32 : entry.size, 24);
|
|
143
|
+
central.writeUInt16LE(entry.name.length, 28);
|
|
144
|
+
central.writeUInt16LE(extraField.length, 30);
|
|
145
|
+
central.writeUInt32LE((0o100644 * 0x10000) >>> 0, 38); /* a plain file, never a link */
|
|
146
|
+
central.writeUInt32LE(big[2] ? MAX32 : entry.offset, 42);
|
|
147
|
+
await write(central);
|
|
148
|
+
await write(entry.name);
|
|
149
|
+
if (extraField.length)
|
|
150
|
+
await write(extraField);
|
|
151
|
+
offset += central.length + entry.name.length + extraField.length;
|
|
152
|
+
}
|
|
153
|
+
const centralSize = offset - centralStart;
|
|
154
|
+
const needs64 = written.length >= MAX16 || centralStart >= MAX32 || centralSize >= MAX32;
|
|
155
|
+
if (needs64) {
|
|
156
|
+
const record = Buffer.alloc(56);
|
|
157
|
+
record.writeUInt32LE(0x06064b50, 0);
|
|
158
|
+
record.writeBigUInt64LE(44n, 4);
|
|
159
|
+
record.writeUInt16LE(45, 12);
|
|
160
|
+
record.writeUInt16LE(45, 14);
|
|
161
|
+
record.writeBigUInt64LE(BigInt(written.length), 24);
|
|
162
|
+
record.writeBigUInt64LE(BigInt(written.length), 32);
|
|
163
|
+
record.writeBigUInt64LE(BigInt(centralSize), 40);
|
|
164
|
+
record.writeBigUInt64LE(BigInt(centralStart), 48);
|
|
165
|
+
const locator = Buffer.alloc(20);
|
|
166
|
+
locator.writeUInt32LE(0x07064b50, 0);
|
|
167
|
+
locator.writeBigUInt64LE(BigInt(offset), 8);
|
|
168
|
+
locator.writeUInt32LE(1, 16);
|
|
169
|
+
await write(record);
|
|
170
|
+
await write(locator);
|
|
171
|
+
}
|
|
172
|
+
const end = Buffer.alloc(22);
|
|
173
|
+
end.writeUInt32LE(0x06054b50, 0);
|
|
174
|
+
end.writeUInt16LE(needs64 ? MAX16 : written.length, 8);
|
|
175
|
+
end.writeUInt16LE(needs64 ? MAX16 : written.length, 10);
|
|
176
|
+
end.writeUInt32LE(needs64 ? MAX32 : centralSize, 12);
|
|
177
|
+
end.writeUInt32LE(needs64 ? MAX32 : centralStart, 16);
|
|
178
|
+
await write(end);
|
|
179
|
+
await new Promise((resolve, reject) => stream.end((error) => (error ? reject(error) : resolve())));
|
|
180
|
+
return { files: written.length, bytes: offset + (needs64 ? 76 : 0) + 22 };
|
|
181
|
+
}
|
|
182
|
+
function zip64Extra(values) {
|
|
183
|
+
const field = Buffer.alloc(4 + values.length * 8);
|
|
184
|
+
field.writeUInt16LE(0x0001, 0);
|
|
185
|
+
field.writeUInt16LE(values.length * 8, 2);
|
|
186
|
+
values.forEach((value, index) => field.writeBigUInt64LE(BigInt(value), 4 + index * 8));
|
|
187
|
+
return field;
|
|
188
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderook/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.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, 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, resolve a save that landed at the same time as somebody else's,
|
|
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, resolve a save that landed at the same time as somebody else's, build a project's executables for Windows, Linux and macOS from one machine (cbx build), or publish a mod to a game's mod repository or set one up (cbx mods).
|
|
4
4
|
allowed-tools: Bash Read
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -306,6 +306,50 @@ unasked. `--ship` attaches the results to the newest save, so **ask before
|
|
|
306
306
|
`--ship`**, as before any save. A project's own build description lives in
|
|
307
307
|
`coderook.build.json`.
|
|
308
308
|
|
|
309
|
+
## Reviewing proposals with GitHub tools
|
|
310
|
+
|
|
311
|
+
When someone wants CodeRabbit, Blacksmith or GitHub Actions on their CodeRook
|
|
312
|
+
proposals, that is `cbx github`: a GitHub repository mirrors each open
|
|
313
|
+
proposal as a pull request, and the results come back as checks named
|
|
314
|
+
`github/<check>` and `review/<reviewer>`.
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
cbx github setup OWNER/REPO # token limited to this project, secret, workflow file
|
|
318
|
+
cbx submit # the workflow has to be saved into the project
|
|
319
|
+
cbx github sync --repo OWNER/REPO
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Ask before `setup`**: it creates a token and stores it in GitHub. The person
|
|
323
|
+
adds the second secret, `CODEROOK_BRIDGE_PUSH` (a GitHub fine-grained token
|
|
324
|
+
with Contents and Pull requests read/write), themselves, because it is their
|
|
325
|
+
GitHub credential. Every proposal also carries a `coderook/malware` check; a
|
|
326
|
+
failure names the file. Do not try to get round it.
|
|
327
|
+
|
|
328
|
+
## Mod repositories for games
|
|
329
|
+
|
|
330
|
+
A game's maker can run a mod repository: players' game clients read a signed
|
|
331
|
+
catalogue from it, modders publish to it, and nothing is listed until it
|
|
332
|
+
passes the repository's rules and a malware scan.
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
cbx mods info zt1 # a repository's rules, for anybody
|
|
336
|
+
cbx mods publish ./BetterTrees --repo zt1 # zip, upload, wait for the checks
|
|
337
|
+
cbx mods publish ./HD-Art --repo zt1 --part animals # one part of a bigger mod
|
|
338
|
+
cbx mods list zt1 # your mods and how their checks went
|
|
339
|
+
cbx mods create my-game --name "My Game" --allow .png,.ogg,.json
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`publish` reads the folder's `mod.json` (or the repository's manifest) for the
|
|
343
|
+
mod's id, name, folder and version; `--id`, `--name`, `--folder` and
|
|
344
|
+
`--version` override it. It waits up to 30 minutes for the checks and prints
|
|
345
|
+
what failed, file by file — pass that on as it is. Programs and scripts
|
|
346
|
+
(`.exe`, `.dll`, `.ps1`, `.js` …) are refused in every repository, so do not
|
|
347
|
+
try to rename one past the check. **Ask before `publish`**, as before any
|
|
348
|
+
save: it makes the mod public once it passes. Ask before `remove`, `block`
|
|
349
|
+
and `delete`, which only the person should decide. The game-side protocol
|
|
350
|
+
(signed requests, the catalogue signature, downloads) is at
|
|
351
|
+
https://coderook.com/docs#mod-repositories.
|
|
352
|
+
|
|
309
353
|
## Things not to do
|
|
310
354
|
|
|
311
355
|
- Do not run `cbx delete` unless the person has asked for that project to
|