@coderook/cli 0.31.13 → 0.32.1

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.
Files changed (58) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/LICENSE +202 -0
  3. package/NOTICE +8 -0
  4. package/README.md +53 -1
  5. package/dist/cbx/LICENSE +202 -0
  6. package/dist/cbx/NOTICE +8 -0
  7. package/dist/{desktop-app/src/main → cbx/src/core}/cbx.js +31 -0
  8. package/dist/{desktop-app/src/main → cbx/src/core}/download.js +62 -1
  9. package/dist/{desktop-app/src/main → cbx/src/core}/rules.js +2 -0
  10. package/dist/cbx/src/local/errors.js +23 -0
  11. package/dist/cbx/src/local/history.js +522 -0
  12. package/dist/cbx/src/local/merge.js +277 -0
  13. package/dist/cbx/src/local/merge_text.js +148 -0
  14. package/dist/cbx/src/local/remote.js +437 -0
  15. package/dist/cbx/src/local/repository.js +324 -0
  16. package/dist/cbx/src/local/snapshot.js +338 -0
  17. package/dist/cbx/src/local/store.js +258 -0
  18. package/dist/{desktop-app → cbx}/src/shared/safe_path.js +12 -0
  19. package/dist/cli/src/api.js +1 -1
  20. package/dist/cli/src/cli.js +166 -24
  21. package/dist/cli/src/diff_command.js +2 -2
  22. package/dist/cli/src/git_remote.js +6 -6
  23. package/dist/cli/src/licence_commands.js +3 -3
  24. package/dist/cli/src/local_commands.js +506 -0
  25. package/dist/cli/src/mcp.js +2 -2
  26. package/dist/cli/src/pages_command.js +2 -2
  27. package/dist/cli/src/registry.js +1 -0
  28. package/dist/cli/src/runner.js +2 -2
  29. package/dist/cli/src/track_commands.js +1 -1
  30. package/package.json +11 -5
  31. package/skills/coderook/SKILL.md +24 -0
  32. package/LICENSE.txt +0 -46
  33. /package/dist/{desktop-app/src/main → cbx/src/core}/compress.js +0 -0
  34. /package/dist/{desktop-app/src/main → cbx/src/core}/credentials.js +0 -0
  35. /package/dist/{desktop-app/src/main → cbx/src/core}/detect.js +0 -0
  36. /package/dist/{desktop-app/src/main → cbx/src/core}/faults.js +0 -0
  37. /package/dist/{desktop-app/src/main → cbx/src/core}/identify.js +0 -0
  38. /package/dist/{desktop-app/src/main → cbx/src/core}/ignore_templates.js +0 -0
  39. /package/dist/{desktop-app/src/main → cbx/src/core}/licence_texts.js +0 -0
  40. /package/dist/{desktop-app/src/main → cbx/src/core}/licences.js +0 -0
  41. /package/dist/{desktop-app/src/main → cbx/src/core}/profile.js +0 -0
  42. /package/dist/{desktop-app/src/main → cbx/src/core}/publish_name.js +0 -0
  43. /package/dist/{desktop-app/src/main → cbx/src/core}/retry.js +0 -0
  44. /package/dist/{desktop-app/src/main → cbx/src/core}/secret_patterns.js +0 -0
  45. /package/dist/{desktop-app/src/main → cbx/src/core}/solid.js +0 -0
  46. /package/dist/{desktop-app/src/main → cbx/src/core}/staging.js +0 -0
  47. /package/dist/{desktop-app/src/main → cbx/src/core}/tracks.js +0 -0
  48. /package/dist/{desktop-app/src/main → cbx/src/core}/transfer_progress.js +0 -0
  49. /package/dist/{desktop-app/src/main → cbx/src/core}/upload.js +0 -0
  50. /package/dist/{desktop-app/src/main → cbx/src/core}/worktree.js +0 -0
  51. /package/dist/{desktop-app → cbx}/src/shared/chunking.js +0 -0
  52. /package/dist/{desktop-app → cbx}/src/shared/compression_policy.js +0 -0
  53. /package/dist/{desktop-app → cbx}/src/shared/delta.js +0 -0
  54. /package/dist/{desktop-app → cbx}/src/shared/format.js +0 -0
  55. /package/dist/{desktop-app → cbx}/src/shared/merge_diff.js +0 -0
  56. /package/dist/{desktop-app → cbx}/src/shared/publication.js +0 -0
  57. /package/dist/{desktop-app → cbx}/src/shared/telemetry.js +0 -0
  58. /package/dist/{desktop-app → cbx}/src/shared/types.js +0 -0
@@ -0,0 +1,258 @@
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.ObjectStore = exports.PACKED_OBJECT_LIMIT = exports.DIGEST = void 0;
7
+ exports.digestOf = digestOf;
8
+ /**
9
+ * The object store under `.cbx/objects`: everything a local history holds.
10
+ *
11
+ * Every object is named by the SHA-256 of its original bytes and kept as a
12
+ * chunk frame (see `core/cbx.ts`), so it is compressed when that saves at
13
+ * least two percent and verified whenever it is read. Pieces of files, the
14
+ * trees that list them and the saves that name a tree are all objects; an
15
+ * object never changes once written, which is what makes writing one safe to
16
+ * repeat and a store safe to copy while nothing is saving.
17
+ *
18
+ * .cbx/objects/ab/cdef… one object on its own ("loose")
19
+ * .cbx/objects/packs/<id>.pack many small objects, frame after frame
20
+ * .cbx/objects/packs/<id>.idx where each of them starts, as JSON
21
+ *
22
+ * Small objects go into packs because a file per object is what made the
23
+ * first save of twenty thousand small files take thirty-six seconds on
24
+ * Windows, nearly all of it creating files. A pack is written whole and
25
+ * renamed into place before its index is, so an index on disk always
26
+ * describes a finished pack; a pack with no index is an interrupted write
27
+ * and is never read.
28
+ */
29
+ const node_crypto_1 = require("node:crypto");
30
+ const promises_1 = require("node:fs/promises");
31
+ const node_path_1 = __importDefault(require("node:path"));
32
+ const cbx_js_1 = require("../core/cbx.js");
33
+ exports.DIGEST = /^[0-9a-f]{64}$/;
34
+ /** Objects at most this big are packed rather than written on their own. */
35
+ exports.PACKED_OBJECT_LIMIT = 256 * 1024;
36
+ /** A pack is written out once it holds this much. */
37
+ const PACK_BYTES = 32 * 1024 * 1024;
38
+ function digestOf(bytes) {
39
+ return (0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex");
40
+ }
41
+ class ObjectStore {
42
+ directory;
43
+ /* Fan-out folders already known to exist, so each is made once, not per object. */
44
+ made = new Set();
45
+ /* Every packed object, once the indexes have been read. */
46
+ packed = null;
47
+ /* Small objects waiting to be written as one pack. */
48
+ pending = new Map();
49
+ /* The batch being written right now, still readable while it is. */
50
+ writing = new Map();
51
+ pendingBytes = 0;
52
+ flushing = null;
53
+ /*
54
+ Packs held open while an operation reads from them. Opening a pack for
55
+ each object made restoring twenty thousand small files take three
56
+ minutes. Closed by `release`, which every operation calls when it ends,
57
+ because Windows will not delete a folder holding an open file.
58
+ */
59
+ handles = new Map();
60
+ constructor(directory) {
61
+ this.directory = directory;
62
+ }
63
+ get packDirectory() {
64
+ return node_path_1.default.join(this.directory, "packs");
65
+ }
66
+ where(digest) {
67
+ if (!exports.DIGEST.test(digest))
68
+ throw new Error(`Not an object name: ${digest}`);
69
+ return node_path_1.default.join(this.directory, digest.slice(0, 2), digest.slice(2));
70
+ }
71
+ async packs() {
72
+ if (this.packed)
73
+ return this.packed;
74
+ const found = new Map();
75
+ let names = [];
76
+ try {
77
+ names = await (0, promises_1.readdir)(this.packDirectory);
78
+ }
79
+ catch {
80
+ /* No packs yet. */
81
+ }
82
+ for (const name of names.filter((one) => one.endsWith(".idx")).sort()) {
83
+ const pack = node_path_1.default.join(this.packDirectory, name.replace(/\.idx$/, ".pack"));
84
+ const index = JSON.parse(await (0, promises_1.readFile)(node_path_1.default.join(this.packDirectory, name), "utf8"));
85
+ for (const [digest, [offset, length]] of Object.entries(index)) {
86
+ found.set(digest, { pack, offset, length });
87
+ }
88
+ }
89
+ this.packed = found;
90
+ return found;
91
+ }
92
+ async loose(digest) {
93
+ try {
94
+ await (0, promises_1.stat)(this.where(digest));
95
+ return true;
96
+ }
97
+ catch {
98
+ return false;
99
+ }
100
+ }
101
+ async has(digest) {
102
+ if (this.pending.has(digest) || this.writing.has(digest))
103
+ return true;
104
+ if ((await this.packs()).has(digest))
105
+ return true;
106
+ return this.loose(digest);
107
+ }
108
+ /**
109
+ * Keep `raw`, returning its name.
110
+ *
111
+ * A small object is held until there is a pack's worth, then written with
112
+ * the others; call `flush` before anything else needs to read it from disk.
113
+ * A large one is written beside its final name and renamed into place, so
114
+ * an interrupted save never leaves half an object under a real name. An
115
+ * object already present is not written again: same name, same bytes.
116
+ */
117
+ async put(raw, options = {}) {
118
+ const digest = digestOf(raw);
119
+ if (await this.has(digest))
120
+ return digest;
121
+ const { frame } = (0, cbx_js_1.chunkFrame)(raw);
122
+ if (!options.loose && raw.length <= exports.PACKED_OBJECT_LIMIT) {
123
+ this.pending.set(digest, frame);
124
+ this.pendingBytes += frame.length;
125
+ if (this.pendingBytes >= PACK_BYTES)
126
+ await this.flush();
127
+ return digest;
128
+ }
129
+ const target = this.where(digest);
130
+ const folder = node_path_1.default.dirname(target);
131
+ if (!this.made.has(folder)) {
132
+ await (0, promises_1.mkdir)(folder, { recursive: true });
133
+ this.made.add(folder);
134
+ }
135
+ const partial = `${target}.${(0, node_crypto_1.randomBytes)(6).toString("hex")}.partial`;
136
+ await (0, promises_1.writeFile)(partial, frame);
137
+ try {
138
+ await (0, promises_1.rename)(partial, target);
139
+ }
140
+ catch (error) {
141
+ await (0, promises_1.rm)(partial, { force: true });
142
+ /* Another writer got there first with the same bytes. */
143
+ if (!(await this.loose(digest)))
144
+ throw error;
145
+ }
146
+ return digest;
147
+ }
148
+ /** Write whatever small objects are waiting as one pack. */
149
+ async flush() {
150
+ /* One pack at a time; a second caller waits for the first and goes again. */
151
+ while (this.flushing)
152
+ await this.flushing;
153
+ if (!this.pending.size)
154
+ return;
155
+ const taken = this.pending;
156
+ this.pending = new Map();
157
+ this.pendingBytes = 0;
158
+ this.writing = taken;
159
+ this.flushing = this.writePack(taken).finally(() => {
160
+ this.writing = new Map();
161
+ this.flushing = null;
162
+ });
163
+ await this.flushing;
164
+ }
165
+ async writePack(objects) {
166
+ await (0, promises_1.mkdir)(this.packDirectory, { recursive: true });
167
+ const index = {};
168
+ const frames = [];
169
+ let offset = 0;
170
+ for (const [digest, frame] of [...objects].sort(([left], [right]) => (left < right ? -1 : 1))) {
171
+ index[digest] = [offset, frame.length];
172
+ frames.push(frame);
173
+ offset += frame.length;
174
+ }
175
+ const body = Buffer.concat(frames);
176
+ const id = digestOf(body);
177
+ const pack = node_path_1.default.join(this.packDirectory, `${id}.pack`);
178
+ const idx = node_path_1.default.join(this.packDirectory, `${id}.idx`);
179
+ await (0, promises_1.writeFile)(`${pack}.partial`, body);
180
+ await (0, promises_1.rename)(`${pack}.partial`, pack);
181
+ await (0, promises_1.writeFile)(`${idx}.partial`, JSON.stringify(index));
182
+ await (0, promises_1.rename)(`${idx}.partial`, idx);
183
+ const known = await this.packs();
184
+ for (const [digest, [at, length]] of Object.entries(index)) {
185
+ known.set(digest, { pack, offset: at, length });
186
+ }
187
+ }
188
+ async get(digest) {
189
+ const waiting = this.pending.get(digest) ?? this.writing.get(digest);
190
+ if (waiting)
191
+ return (0, cbx_js_1.openChunkFrame)(waiting, digest);
192
+ const located = (await this.packs()).get(digest);
193
+ if (located) {
194
+ let opening = this.handles.get(located.pack);
195
+ if (!opening) {
196
+ opening = (0, promises_1.open)(located.pack, "r");
197
+ this.handles.set(located.pack, opening);
198
+ }
199
+ const handle = await opening;
200
+ const frame = Buffer.alloc(located.length);
201
+ const { bytesRead } = await handle.read(frame, 0, located.length, located.offset);
202
+ if (bytesRead !== located.length) {
203
+ throw new Error(`Object ${digest.slice(0, 12)} runs past the end of its pack`);
204
+ }
205
+ return (0, cbx_js_1.openChunkFrame)(frame, digest);
206
+ }
207
+ let frame;
208
+ try {
209
+ frame = await (0, promises_1.readFile)(this.where(digest));
210
+ }
211
+ catch {
212
+ throw new Error(`The history is missing object ${digest.slice(0, 12)}`);
213
+ }
214
+ return (0, cbx_js_1.openChunkFrame)(frame, digest);
215
+ }
216
+ /** Close any packs held open for reading. Safe to call at any time. */
217
+ async release() {
218
+ const open = [...this.handles.values()];
219
+ this.handles.clear();
220
+ for (const opening of open) {
221
+ try {
222
+ await (await opening).close();
223
+ }
224
+ catch {
225
+ /* Never opened, or already closed. */
226
+ }
227
+ }
228
+ }
229
+ /** Stored on its own, so a save or tree is one file a person can find. */
230
+ async putJson(value) {
231
+ return this.put(Buffer.from(JSON.stringify(value), "utf8"), { loose: true });
232
+ }
233
+ async getJson(digest) {
234
+ return JSON.parse((await this.get(digest)).toString("utf8"));
235
+ }
236
+ /** Every object whose name starts with `prefix` (at least four characters). */
237
+ async matching(prefix) {
238
+ if (!/^[0-9a-f]{4,64}$/.test(prefix))
239
+ return [];
240
+ const found = new Set();
241
+ try {
242
+ const rest = prefix.slice(2);
243
+ for (const name of await (0, promises_1.readdir)(node_path_1.default.join(this.directory, prefix.slice(0, 2)))) {
244
+ if (name.length === 62 && name.startsWith(rest))
245
+ found.add(prefix.slice(0, 2) + name);
246
+ }
247
+ }
248
+ catch {
249
+ /* No loose objects under that prefix. */
250
+ }
251
+ for (const digest of (await this.packs()).keys()) {
252
+ if (digest.startsWith(prefix))
253
+ found.add(digest);
254
+ }
255
+ return [...found];
256
+ }
257
+ }
258
+ exports.ObjectStore = ObjectStore;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.isGitDirectoryName = isGitDirectoryName;
7
+ exports.isCbxDirectoryName = isCbxDirectoryName;
7
8
  exports.whyUnsafe = whyUnsafe;
8
9
  exports.safeParts = safeParts;
9
10
  exports.inside = inside;
@@ -38,6 +39,15 @@ function isGitDirectoryName(part) {
38
39
  const name = part.split(":")[0].replace(/[. ]+$/, "").toLowerCase();
39
40
  return name === ".git" || /^git~\d+$/.test(name);
40
41
  }
42
+ /**
43
+ * A name CBX keeps its local history under, compared the way git's is.
44
+ *
45
+ * A version holding `.cbx/lines/main` would otherwise move somebody's line to
46
+ * a save of the version's choosing the moment it was fetched.
47
+ */
48
+ function isCbxDirectoryName(part) {
49
+ return part.split(":")[0].replace(/[. ]+$/, "").toLowerCase() === ".cbx";
50
+ }
41
51
  /** Why this path must not be written, or null when it may. */
42
52
  function whyUnsafe(relative) {
43
53
  if (!relative)
@@ -52,6 +62,8 @@ function whyUnsafe(relative) {
52
62
  return "it climbs out of the folder";
53
63
  if (isGitDirectoryName(part))
54
64
  return "it writes into git's own directory";
65
+ if (isCbxDirectoryName(part))
66
+ return "it writes into CBX's own history";
55
67
  /* A stream or a drive-relative name on Windows; refused by the service too. */
56
68
  if (process.platform === "win32" && part.includes(":")) {
57
69
  return "it names a Windows data stream";
@@ -58,7 +58,7 @@ exports.addWebhook = addWebhook;
58
58
  exports.removeWebhook = removeWebhook;
59
59
  exports.collisionCheck = collisionCheck;
60
60
  /** The small part of the API the command-line tool needs directly. */
61
- const identify_js_1 = require("../../desktop-app/src/main/identify.js");
61
+ const identify_js_1 = require("../../cbx/src/core/identify.js");
62
62
  const config_js_1 = require("./config.js");
63
63
  /**
64
64
  * How long one request may go unanswered before it is treated as lost.
@@ -30,24 +30,25 @@ const attach_command_js_1 = require("./attach_command.js");
30
30
  const pages_command_js_1 = require("./pages_command.js");
31
31
  const project_commands_js_1 = require("./project_commands.js");
32
32
  const track_commands_js_1 = require("./track_commands.js");
33
- const tracks_js_1 = require("../../desktop-app/src/main/tracks.js");
33
+ const tracks_js_1 = require("../../cbx/src/core/tracks.js");
34
34
  const transfer_command_js_1 = require("./transfer_command.js");
35
35
  const mcp_js_1 = require("./mcp.js");
36
36
  const skill_command_js_1 = require("./skill_command.js");
37
37
  const licence_commands_js_1 = require("./licence_commands.js");
38
38
  const service_commands_js_1 = require("./service_commands.js");
39
- const worktree_js_1 = require("../../desktop-app/src/main/worktree.js");
40
- const rules_js_1 = require("../../desktop-app/src/main/rules.js");
41
- const upload_js_1 = require("../../desktop-app/src/main/upload.js");
42
- const download_js_1 = require("../../desktop-app/src/main/download.js");
43
- const safe_path_js_1 = require("../../desktop-app/src/shared/safe_path.js");
44
- const faults_js_1 = require("../../desktop-app/src/main/faults.js");
39
+ const worktree_js_1 = require("../../cbx/src/core/worktree.js");
40
+ const rules_js_1 = require("../../cbx/src/core/rules.js");
41
+ const upload_js_1 = require("../../cbx/src/core/upload.js");
42
+ const download_js_1 = require("../../cbx/src/core/download.js");
43
+ const safe_path_js_1 = require("../../cbx/src/shared/safe_path.js");
44
+ const faults_js_1 = require("../../cbx/src/core/faults.js");
45
45
  const version_commands_js_1 = require("./version_commands.js");
46
- const identify_js_1 = require("../../desktop-app/src/main/identify.js");
46
+ const identify_js_1 = require("../../cbx/src/core/identify.js");
47
47
  const diff_command_js_1 = require("./diff_command.js");
48
- const profile_js_1 = require("../../desktop-app/src/main/profile.js");
49
- const detect_js_1 = require("../../desktop-app/src/main/detect.js");
50
- const cbx_js_1 = require("../../desktop-app/src/main/cbx.js");
48
+ const local_commands_js_1 = require("./local_commands.js");
49
+ const profile_js_1 = require("../../cbx/src/core/profile.js");
50
+ const detect_js_1 = require("../../cbx/src/core/detect.js");
51
+ const cbx_js_1 = require("../../cbx/src/core/cbx.js");
51
52
  const api_js_2 = require("./api.js");
52
53
  const import_command_js_1 = require("./import_command.js");
53
54
  const runner_js_1 = require("./runner.js");
@@ -154,7 +155,12 @@ function parse(argv, spec) {
154
155
  their options rely on it, and narrowing that here would trade one
155
156
  quiet misreading for another.
156
157
  */
157
- const wants = takesValue.size ? takesValue.has(name) : true;
158
+ /*
159
+ A command that declares any options has declared them all, including
160
+ when none of them takes a value. Keying this on the value-taking ones
161
+ alone let `cbx switch -c spike` read "spike" as the value of `-c`.
162
+ */
163
+ const wants = spec?.options?.length ? takesValue.has(name) : true;
158
164
  if (wants && next !== undefined && !next.startsWith("-")) {
159
165
  flags.set(name, next);
160
166
  at += 1;
@@ -1961,12 +1967,14 @@ const SPECS = [
1961
1967
  summary: "what is here that is not saved yet",
1962
1968
  usage: "status [folder]",
1963
1969
  detail: "Compares the folder against the last version you sent and lists what\n" +
1964
- "changed. Says nothing about other people's work — use `merges` for that.",
1965
- run: commandStatus,
1970
+ "changed. Says nothing about other people's work — use `merges` for that.\n\n" +
1971
+ "In a folder with a history of its own (see `cbx init`), compares\n" +
1972
+ "against the last local save instead.",
1973
+ run: (0, local_commands_js_1.localFirst)(local_commands_js_1.localStatus, commandStatus, (parsed) => folderFor(parsed)),
1966
1974
  },
1967
1975
  {
1968
1976
  name: "submit",
1969
- aliases: ["publish", "push"],
1977
+ aliases: ["publish"],
1970
1978
  group: "Working with a folder",
1971
1979
  summary: "send the changes as a new save",
1972
1980
  usage: 'submit [folder] -m "…"',
@@ -2017,7 +2025,9 @@ const SPECS = [
2017
2025
  "compares two saved versions — which the service answers from its own\n" +
2018
2026
  "stored trees, so an untouched corner of a large project costs nothing.\n\n" +
2019
2027
  "A version can be given as a number, as v12, or by the name it was\n" +
2020
- "released under.",
2028
+ "released under.\n\n" +
2029
+ "In a folder with a history of its own (see `cbx init`), the same\n" +
2030
+ "positions name local saves: a line, a line~N, or the start of an id.",
2021
2031
  options: [
2022
2032
  { flags: "--name-only", description: "list the files and not their lines" },
2023
2033
  { flags: "--path <text>", description: "only paths containing this" },
@@ -2027,8 +2037,94 @@ const SPECS = [
2027
2037
  },
2028
2038
  { flags: "--project <name>", description: "which project" },
2029
2039
  ],
2030
- examples: ["cbx diff", "cbx diff 12", "cbx diff 11 12 --name-only"],
2031
- run: diff_command_js_1.commandDiff,
2040
+ examples: ["cbx diff", "cbx diff 12", "cbx diff 11 12 --name-only", "cbx diff main~1 main"],
2041
+ run: (0, local_commands_js_1.localFirst)(local_commands_js_1.localDiff, diff_command_js_1.commandDiff),
2042
+ },
2043
+ {
2044
+ name: "init",
2045
+ group: "History on this machine",
2046
+ summary: "start a history in this folder, kept on this machine",
2047
+ usage: "init [folder]",
2048
+ detail: "Makes a .cbx/ folder holding the project's history, so you can save,\n" +
2049
+ "compare and go back with no connection at all. Nothing in it is sent\n" +
2050
+ "anywhere by itself.\n\n" +
2051
+ "Once a folder has one, `status`, `diff`, `log` and `switch` answer from\n" +
2052
+ "it. `submit` still sends the folder to CodeRook as it always has.",
2053
+ options: [{ flags: "--line <name>", description: "what to call the first line (default main)" }],
2054
+ examples: ["cbx init", "cbx init my-game --line dev"],
2055
+ run: local_commands_js_1.commandInit,
2056
+ },
2057
+ {
2058
+ name: "save",
2059
+ group: "History on this machine",
2060
+ summary: "record the folder as it is now, on this machine",
2061
+ usage: 'save -m "…"',
2062
+ detail: "Records every file the ignore rules let through, on the line the\n" +
2063
+ "folder is on. Only files that changed are read again, and a large file\n" +
2064
+ "is split so an edit stores the pieces around it rather than all of it.\n\n" +
2065
+ "A save identical to the last one is not made.",
2066
+ options: [
2067
+ { flags: "-m, --message <text>", description: "what changed" },
2068
+ { flags: "--author <name>", description: "who to record, instead of the configured name" },
2069
+ { flags: "-f, --force", description: "finish a merge even though conflict markers remain" },
2070
+ ],
2071
+ examples: ['cbx save -m "Tune the jump height"'],
2072
+ run: local_commands_js_1.commandSave,
2073
+ },
2074
+ {
2075
+ name: "log",
2076
+ group: "History on this machine",
2077
+ summary: "the saves on this line, newest first",
2078
+ usage: "log [from]",
2079
+ detail: "In a folder with a history of its own, lists its saves along the line,\n" +
2080
+ "newest first. Start somewhere else by naming a line or a save.\n\n" +
2081
+ "Anywhere else, lists what has been saved to the project on CodeRook,\n" +
2082
+ "as `cbx versions` does.",
2083
+ options: [
2084
+ { flags: "--limit <n>", description: "how many to show (default 20)" },
2085
+ { flags: "--versions", description: "on CodeRook: only the ones that were named" },
2086
+ { flags: "--commits", description: "on CodeRook: only the ones that were not" },
2087
+ { flags: "--notes", description: "on CodeRook: print what each one says about itself" },
2088
+ ],
2089
+ examples: ["cbx log", "cbx log feature --limit 5"],
2090
+ run: (0, local_commands_js_1.localFirst)(local_commands_js_1.localLog, project_commands_js_1.commandVersions),
2091
+ },
2092
+ {
2093
+ name: "restore",
2094
+ group: "History on this machine",
2095
+ summary: "put files back the way a save had them",
2096
+ usage: "restore [paths…] [--from <save>]",
2097
+ detail: "With no paths, makes the whole folder match the save; with paths,\n" +
2098
+ "only those files and folders. The default is the last save, which\n" +
2099
+ "throws away what you changed since.\n\n" +
2100
+ "Unsaved changes to a file it would overwrite stop it, and it lists\n" +
2101
+ "them. Add --force to discard them. Files the history has never held\n" +
2102
+ "are left alone either way, and your line does not move.",
2103
+ options: [
2104
+ { flags: "--from <save>", description: "a line, line~N, or the start of a save id" },
2105
+ { flags: "-f, --force", description: "overwrite unsaved changes" },
2106
+ ],
2107
+ examples: ["cbx restore src/player.ts --force", "cbx restore --from main~3"],
2108
+ run: local_commands_js_1.commandRestore,
2109
+ },
2110
+ {
2111
+ name: "switch",
2112
+ group: "History on this machine",
2113
+ summary: "move this folder to another line of work",
2114
+ usage: "switch [line]",
2115
+ detail: "In a folder with a history of its own, rewrites only the files the two\n" +
2116
+ "lines disagree about; unsaved edits elsewhere come along. Unsaved\n" +
2117
+ "edits to a file it would change stop it. -c starts a new line from the\n" +
2118
+ "current save without changing any files. With no line, lists them.\n\n" +
2119
+ "Anywhere else, changes the CodeRook line this folder saves to, as\n" +
2120
+ "`cbx track` does.",
2121
+ options: [
2122
+ { flags: "-c, --create", description: "start a new line here" },
2123
+ { flags: "-f, --force", description: "discard unsaved changes that are in the way" },
2124
+ { flags: "-n, --new", description: "on CodeRook: start this line from where the project is now" },
2125
+ ],
2126
+ examples: ["cbx switch", "cbx switch -c spike", "cbx switch main"],
2127
+ run: (0, local_commands_js_1.localFirst)(local_commands_js_1.localSwitch, track_commands_js_1.commandTrack),
2032
2128
  },
2033
2129
  {
2034
2130
  name: "unlink",
@@ -2120,7 +2216,7 @@ const SPECS = [
2120
2216
  different lines, which is most of the point of having them.
2121
2217
  */
2122
2218
  name: "track",
2123
- aliases: ["switch", "checkout"],
2219
+ aliases: ["checkout"],
2124
2220
  group: "Working with a folder",
2125
2221
  summary: "show or change the line this folder saves to",
2126
2222
  usage: "track [name]",
@@ -2154,7 +2250,6 @@ const SPECS = [
2154
2250
  },
2155
2251
  {
2156
2252
  name: "get",
2157
- aliases: ["pull"],
2158
2253
  group: "Working with a folder",
2159
2254
  summary: "bring this folder up to date",
2160
2255
  usage: "get [folder]",
@@ -2251,7 +2346,6 @@ const SPECS = [
2251
2346
  },
2252
2347
  {
2253
2348
  name: "versions",
2254
- aliases: ["log"],
2255
2349
  group: "Your projects",
2256
2350
  summary: "what has been saved to a project",
2257
2351
  usage: "versions [project]",
@@ -2798,9 +2892,17 @@ const SPECS = [
2798
2892
  {
2799
2893
  name: "merge",
2800
2894
  group: "When somebody saved first",
2801
- summary: "look at one, and decide",
2802
- usage: "merge <ref>",
2895
+ summary: "merge a line here, or decide one waiting on CodeRook",
2896
+ usage: "merge <line | ref>",
2897
+ detail: "In a folder with a history of its own, merges a line or a save into\n" +
2898
+ "this one. What only one side changed is taken; a text file both\n" +
2899
+ "changed is merged line by line; only what is left is a conflict,\n" +
2900
+ "marked in the file. Fix them and save, or take a side whole with\n" +
2901
+ "--mine or --theirs, or call it off with --cancel.\n\n" +
2902
+ "Anywhere else, decides a merge waiting on CodeRook.",
2803
2903
  options: [
2904
+ { flags: "-m, --message <text>", description: "what the merge save says" },
2905
+ { flags: "--abort", description: "the same as --cancel" },
2804
2906
  { flags: "--mine", description: "keep your side" },
2805
2907
  { flags: "--theirs", description: "keep theirs" },
2806
2908
  { flags: "--both", description: "keep both" },
@@ -2809,7 +2911,7 @@ const SPECS = [
2809
2911
  { flags: "--cancel", description: "call the merge off; your upload stays stored" },
2810
2912
  { flags: "--ask <usernames>", description: "ask people to review it, comma separated" },
2811
2913
  ],
2812
- run: commandMerge,
2914
+ run: (0, local_commands_js_1.localFirst)(local_commands_js_1.localMerge, commandMerge),
2813
2915
  },
2814
2916
  {
2815
2917
  name: "bundle",
@@ -2922,6 +3024,46 @@ const SPECS = [
2922
3024
  run: () => commandDoctor(),
2923
3025
  },
2924
3026
  ];
3027
+ /*
3028
+ `push` and `pull` mean the local history in a folder that has one, and
3029
+ `submit` and `get` everywhere else, which is what they always meant. Built
3030
+ from those two rows so every option they take still parses when the name
3031
+ falls through to them.
3032
+ */
3033
+ function localTwin(name, original, local, row, extra) {
3034
+ const spec = SPECS.find((one) => one.name === original);
3035
+ const taken = new Set(extra.map((one) => one.flags));
3036
+ return {
3037
+ name,
3038
+ group: "History on this machine",
3039
+ ...row,
3040
+ options: [...extra, ...(spec.options ?? []).filter((one) => !taken.has(one.flags))],
3041
+ run: (0, local_commands_js_1.localFirst)(local, spec.run),
3042
+ };
3043
+ }
3044
+ SPECS.push(localTwin("push", "submit", local_commands_js_1.localPush, {
3045
+ summary: "send this line's saves to CodeRook",
3046
+ usage: "push",
3047
+ detail: "In a folder with a history of its own, sends each save on this line that\n" +
3048
+ "CodeRook does not have yet, oldest first, as a save of its own. Only\n" +
3049
+ "what changed in each is sent. The first push creates the project,\n" +
3050
+ "private, unless the folder is already linked to one.\n\n" +
3051
+ "If CodeRook's line has saves this history does not, nothing is sent:\n" +
3052
+ "run `cbx pull` first.\n\n" +
3053
+ "Anywhere else, this is `cbx submit`.",
3054
+ examples: ["cbx push", "cbx push --allow-secrets"],
3055
+ }, [{ flags: "-y, --yes", description: "go ahead past the checks that ask first" }]), localTwin("pull", "get", local_commands_js_1.localPull, {
3056
+ summary: "bring CodeRook's saves on this line into this history",
3057
+ usage: "pull [project]",
3058
+ detail: "In a folder with a history of its own, fetches the saves CodeRook has on\n" +
3059
+ "this line and this history does not, and moves the folder to the newest.\n" +
3060
+ "Only files that differ are fetched and rewritten; an unsaved edit to\n" +
3061
+ "one of them stops it. Name a project to pull from it the first time.\n\n" +
3062
+ "If this history has saves CodeRook does not, CodeRook's are fetched\n" +
3063
+ "but the folder stays put: merging a local history is not built yet.\n\n" +
3064
+ "Anywhere else, this is `cbx get`.",
3065
+ examples: ["cbx pull", "cbx pull my-project"],
3066
+ }, [{ flags: "-f, --force", description: "discard unsaved changes that are in the way" }]));
2925
3067
  const REGISTRY = (0, registry_js_1.buildRegistry)(SPECS);
2926
3068
  async function main(argv) {
2927
3069
  const [name, ...rest] = argv;
@@ -27,8 +27,8 @@ const node_process_1 = __importDefault(require("node:process"));
27
27
  const api_js_1 = require("./api.js");
28
28
  const config_js_1 = require("./config.js");
29
29
  const project_commands_js_1 = require("./project_commands.js");
30
- const merge_diff_js_1 = require("../../desktop-app/src/shared/merge_diff.js");
31
- const worktree_js_1 = require("../../desktop-app/src/main/worktree.js");
30
+ const merge_diff_js_1 = require("../../cbx/src/shared/merge_diff.js");
31
+ const worktree_js_1 = require("../../cbx/src/core/worktree.js");
32
32
  /*
33
33
  Built rather than typed, so the escape character never sits literally in the
34
34
  source. The other command files spell theirs out; this reads the same on a
@@ -109,15 +109,15 @@ exports.main = main;
109
109
  * where the original branched.
110
110
  */
111
111
  const node_child_process_1 = require("node:child_process");
112
- const safe_path_js_1 = require("../../desktop-app/src/shared/safe_path.js");
112
+ const safe_path_js_1 = require("../../cbx/src/shared/safe_path.js");
113
113
  const promises_1 = require("node:fs/promises");
114
114
  const node_os_1 = __importDefault(require("node:os"));
115
115
  const node_path_1 = __importDefault(require("node:path"));
116
116
  const node_process_1 = __importDefault(require("node:process"));
117
- const upload_js_1 = require("../../desktop-app/src/main/upload.js");
117
+ const upload_js_1 = require("../../cbx/src/core/upload.js");
118
118
  const stored_zip_js_1 = require("./stored_zip.js");
119
- const tracks_js_1 = require("../../desktop-app/src/main/tracks.js");
120
- const identify_js_1 = require("../../desktop-app/src/main/identify.js");
119
+ const tracks_js_1 = require("../../cbx/src/core/tracks.js");
120
+ const identify_js_1 = require("../../cbx/src/core/identify.js");
121
121
  const config_js_1 = require("./config.js");
122
122
  const version_js_1 = require("./version.js");
123
123
  const publish_js_1 = require("./publish.js");
@@ -673,7 +673,7 @@ depth = null) {
673
673
  ? ` (${chosen.length - fresh.length} already here)`
674
674
  : "") +
675
675
  ".");
676
- const { Downloader } = await Promise.resolve().then(() => __importStar(require("../../desktop-app/src/main/download.js")));
676
+ const { Downloader } = await Promise.resolve().then(() => __importStar(require("../../cbx/src/core/download.js")));
677
677
  const downloader = new Downloader(config_js_1.credentials);
678
678
  const scratch = await (0, promises_1.mkdtemp)(node_path_1.default.join(node_os_1.default.tmpdir(), "coderook-fetch-"));
679
679
  try {
@@ -1262,7 +1262,7 @@ async function doPush(requests, url) {
1262
1262
  throw error;
1263
1263
  }
1264
1264
  }
1265
- const { Downloader } = await Promise.resolve().then(() => __importStar(require("../../desktop-app/src/main/download.js")));
1265
+ const { Downloader } = await Promise.resolve().then(() => __importStar(require("../../cbx/src/core/download.js")));
1266
1266
  const held = await new Downloader(config_js_1.credentials).files(repositoryId, baseVersionId);
1267
1267
  manifest = Object.fromEntries(held.map((file) => [file.path, file.sha256]));
1268
1268
  local = { ...manifest };
@@ -21,10 +21,10 @@ exports.commandLicence = commandLicence;
21
21
  exports.commandIgnoreTemplate = commandIgnoreTemplate;
22
22
  const promises_1 = require("node:fs/promises");
23
23
  const node_path_1 = __importDefault(require("node:path"));
24
- const licences_js_1 = require("../../desktop-app/src/main/licences.js");
24
+ const licences_js_1 = require("../../cbx/src/core/licences.js");
25
25
  Object.defineProperty(exports, "licenceNewProject", { enumerable: true, get: function () { return licences_js_1.licenceNewProject; } });
26
- const ignore_templates_js_1 = require("../../desktop-app/src/main/ignore_templates.js");
27
- const worktree_js_1 = require("../../desktop-app/src/main/worktree.js");
26
+ const ignore_templates_js_1 = require("../../cbx/src/core/ignore_templates.js");
27
+ const worktree_js_1 = require("../../cbx/src/core/worktree.js");
28
28
  const api_js_1 = require("./api.js");
29
29
  const dim = (value) => `${value}`;
30
30
  const bold = (value) => `${value}`;