@coderook/cli 0.31.12 → 0.32.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 +73 -1
- package/dist/cbx/LICENSE +202 -0
- package/dist/cbx/NOTICE +8 -0
- package/dist/{desktop-app/src/main → cbx/src/core}/cbx.js +31 -0
- package/dist/{desktop-app/src/main → cbx/src/core}/download.js +62 -1
- package/dist/{desktop-app/src/main → cbx/src/core}/rules.js +2 -0
- package/dist/cbx/src/local/errors.js +23 -0
- package/dist/cbx/src/local/history.js +522 -0
- package/dist/cbx/src/local/merge.js +277 -0
- package/dist/cbx/src/local/merge_text.js +148 -0
- package/dist/cbx/src/local/remote.js +437 -0
- package/dist/cbx/src/local/repository.js +324 -0
- package/dist/cbx/src/local/snapshot.js +338 -0
- package/dist/cbx/src/local/store.js +258 -0
- package/dist/{desktop-app → cbx}/src/shared/safe_path.js +12 -0
- package/dist/cli/src/api.js +114 -2
- package/dist/cli/src/cli.js +198 -22
- package/dist/cli/src/diff_command.js +2 -2
- package/dist/cli/src/git_remote.js +6 -6
- package/dist/cli/src/licence_commands.js +3 -3
- package/dist/cli/src/local_commands.js +506 -0
- package/dist/cli/src/mcp.js +2 -2
- package/dist/cli/src/pages_command.js +625 -0
- package/dist/cli/src/project_commands.js +1 -0
- package/dist/cli/src/registry.js +1 -0
- package/dist/cli/src/runner.js +2 -2
- package/dist/cli/src/track_commands.js +1 -1
- package/package.json +2 -2
- package/skills/coderook/SKILL.md +41 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/compress.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/credentials.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/detect.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/faults.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/identify.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/ignore_templates.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/licence_texts.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/licences.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/profile.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/publish_name.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/retry.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/secret_patterns.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/solid.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/staging.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/tracks.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/transfer_progress.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/upload.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/worktree.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/chunking.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/compression_policy.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/delta.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/format.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/merge_diff.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/publication.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/telemetry.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/types.js +0 -0
|
@@ -0,0 +1,506 @@
|
|
|
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.localHistory = localHistory;
|
|
7
|
+
exports.localFirst = localFirst;
|
|
8
|
+
exports.commandInit = commandInit;
|
|
9
|
+
exports.commandSave = commandSave;
|
|
10
|
+
exports.localStatus = localStatus;
|
|
11
|
+
exports.localLog = localLog;
|
|
12
|
+
exports.localDiff = localDiff;
|
|
13
|
+
exports.commandRestore = commandRestore;
|
|
14
|
+
exports.localSwitch = localSwitch;
|
|
15
|
+
exports.localPush = localPush;
|
|
16
|
+
exports.localPull = localPull;
|
|
17
|
+
exports.localMerge = localMerge;
|
|
18
|
+
/**
|
|
19
|
+
* The local history, from a terminal: `cbx init`, `save`, `restore`, and the
|
|
20
|
+
* local side of `status`, `diff`, `log` and `switch`.
|
|
21
|
+
*
|
|
22
|
+
* None of these talk to CodeRook. A folder with a `.cbx/` history answers
|
|
23
|
+
* `status`, `diff`, `log` and `switch` from it; a folder without one gets the
|
|
24
|
+
* account-backed commands those names have always meant, so nothing anybody
|
|
25
|
+
* already relies on changes until they run `cbx init`.
|
|
26
|
+
*/
|
|
27
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
29
|
+
const history_js_1 = require("../../cbx/src/local/history.js");
|
|
30
|
+
const merge_js_1 = require("../../cbx/src/local/merge.js");
|
|
31
|
+
const repository_js_1 = require("../../cbx/src/local/repository.js");
|
|
32
|
+
const remote_js_1 = require("../../cbx/src/local/remote.js");
|
|
33
|
+
const api_js_1 = require("./api.js");
|
|
34
|
+
const config_js_1 = require("./config.js");
|
|
35
|
+
const project_commands_js_1 = require("./project_commands.js");
|
|
36
|
+
const publish_js_1 = require("./publish.js");
|
|
37
|
+
const ESC = String.fromCharCode(27);
|
|
38
|
+
const paint = (code) => (value) => `${ESC}[${code}m${value}${ESC}[0m`;
|
|
39
|
+
const dim = paint("2");
|
|
40
|
+
const bold = paint("1");
|
|
41
|
+
const red = paint("31");
|
|
42
|
+
const green = paint("32");
|
|
43
|
+
const accent = paint("33");
|
|
44
|
+
const short = (id) => id.slice(0, 10);
|
|
45
|
+
const plural = (count, one, many = `${one}s`) => `${count.toLocaleString()} ${count === 1 ? one : many}`;
|
|
46
|
+
function text(parsed, ...names) {
|
|
47
|
+
for (const name of names) {
|
|
48
|
+
const value = parsed.flags.get(name);
|
|
49
|
+
if (typeof value === "string")
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const has = (parsed, ...names) => names.some((name) => parsed.flags.has(name));
|
|
55
|
+
function bytes(value) {
|
|
56
|
+
if (value >= 1024 ** 3)
|
|
57
|
+
return `${(value / 1024 ** 3).toFixed(2)} GB`;
|
|
58
|
+
if (value >= 1024 ** 2)
|
|
59
|
+
return `${(value / 1024 ** 2).toFixed(1)} MB`;
|
|
60
|
+
if (value >= 1024)
|
|
61
|
+
return `${(value / 1024).toFixed(0)} KB`;
|
|
62
|
+
return `${value} B`;
|
|
63
|
+
}
|
|
64
|
+
/** The history the working directory is in, if it is in one. */
|
|
65
|
+
async function localHistory(from = node_process_1.default.cwd()) {
|
|
66
|
+
return repository_js_1.Repository.find(from);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A command that means the local history in a folder that has one, and what
|
|
70
|
+
* it has always meant everywhere else.
|
|
71
|
+
*/
|
|
72
|
+
function localFirst(local, otherwise, from = () => node_process_1.default.cwd()) {
|
|
73
|
+
return async (parsed) => {
|
|
74
|
+
const repository = await localHistory(from(parsed));
|
|
75
|
+
return repository ? local(parsed, repository) : otherwise(parsed);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function printChanges(changes, limit = 50) {
|
|
79
|
+
const rows = [
|
|
80
|
+
...changes.added.map((file) => [green("added "), file]),
|
|
81
|
+
...changes.modified.map((file) => [accent("changed "), file]),
|
|
82
|
+
...changes.deleted.map((file) => [red("deleted "), file]),
|
|
83
|
+
].sort((left, right) => (left[1] < right[1] ? -1 : left[1] > right[1] ? 1 : 0));
|
|
84
|
+
for (const [mark, file] of rows.slice(0, limit))
|
|
85
|
+
console.log(` ${mark}${file}`);
|
|
86
|
+
if (rows.length > limit)
|
|
87
|
+
console.log(dim(` … and ${rows.length - limit} more`));
|
|
88
|
+
}
|
|
89
|
+
function describeSave(one) {
|
|
90
|
+
const when = new Date(one.created);
|
|
91
|
+
const date = Number.isNaN(when.getTime())
|
|
92
|
+
? one.created
|
|
93
|
+
: when.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
|
|
94
|
+
return `${accent(short(one.id))} ${one.message.split("\n")[0]} ${dim(`${one.author} · ${date}`)}`;
|
|
95
|
+
}
|
|
96
|
+
/** Refusals are printed as sentences, not stack traces. */
|
|
97
|
+
async function reporting(work) {
|
|
98
|
+
try {
|
|
99
|
+
return await work();
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof remote_js_1.RemoteAhead || error instanceof remote_js_1.RemoteDiffers || error instanceof remote_js_1.NoRemote) {
|
|
103
|
+
console.error(red(error.message));
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
const failure = (0, publish_js_1.classifyPublishFailure)(error);
|
|
107
|
+
if (failure?.kind === "credentials") {
|
|
108
|
+
console.error(red(failure.message));
|
|
109
|
+
console.error(dim("Take the key out of that save, or push it deliberately with --allow-secrets."));
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
if (failure?.kind === "interrupted") {
|
|
113
|
+
console.error(red(`The connection failed part way: ${failure.message}`));
|
|
114
|
+
console.error(dim("Push again. Saves already on CodeRook are not sent twice."));
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
if (error instanceof history_js_1.UnsavedChanges || error instanceof history_js_1.UnresolvedConflicts) {
|
|
118
|
+
console.error(red(error.message));
|
|
119
|
+
for (const file of error.files.slice(0, 20))
|
|
120
|
+
console.error(` ${file}`);
|
|
121
|
+
if (error.files.length > 20)
|
|
122
|
+
console.error(dim(` … and ${error.files.length - 20} more`));
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
console.error(red(error instanceof Error ? error.message : String(error)));
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/* ---- init ------------------------------------------------------------ */
|
|
130
|
+
async function commandInit(parsed) {
|
|
131
|
+
return reporting(async () => {
|
|
132
|
+
const folder = node_path_1.default.resolve(parsed.positional[0] ?? node_process_1.default.cwd());
|
|
133
|
+
const inside = await localHistory(folder);
|
|
134
|
+
if (inside && inside.root !== folder) {
|
|
135
|
+
console.error(red(`${folder} is already inside the history at ${inside.root}.`));
|
|
136
|
+
console.error(dim("One history per project. Run cbx commands from anywhere inside it."));
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
const repository = await (0, history_js_1.init)(folder, text(parsed, "line") ?? undefined);
|
|
140
|
+
console.log(`Started a history in ${bold(repository.root)} on ${accent(await repository.currentLine())}.`);
|
|
141
|
+
console.log(dim("It lives in .cbx/ and never leaves this machine by itself. Run " +
|
|
142
|
+
`cbx save -m "…" to record the folder as it is.`));
|
|
143
|
+
return 0;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/* ---- save ------------------------------------------------------------ */
|
|
147
|
+
async function commandSave(parsed) {
|
|
148
|
+
return reporting(async () => {
|
|
149
|
+
const repository = await repository_js_1.Repository.open(node_process_1.default.cwd());
|
|
150
|
+
const message = text(parsed, "m", "message") ?? "";
|
|
151
|
+
const merging = await (0, merge_js_1.readMergeState)(repository);
|
|
152
|
+
if (!message.trim() && !merging) {
|
|
153
|
+
console.error(red('A save needs a message: cbx save -m "what changed"'));
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
const interactive = node_process_1.default.stdout.isTTY;
|
|
157
|
+
let shown = 0;
|
|
158
|
+
const result = await (0, history_js_1.save)(repository, {
|
|
159
|
+
message,
|
|
160
|
+
force: has(parsed, "force", "f"),
|
|
161
|
+
author: text(parsed, "author"),
|
|
162
|
+
progress: interactive
|
|
163
|
+
? (done, total) => {
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
if (now - shown < 100 && done < total)
|
|
166
|
+
return;
|
|
167
|
+
shown = now;
|
|
168
|
+
node_process_1.default.stdout.write(`\r${dim(`Reading ${done.toLocaleString()} of ${total.toLocaleString()} files`)} `);
|
|
169
|
+
}
|
|
170
|
+
: undefined,
|
|
171
|
+
});
|
|
172
|
+
if (interactive)
|
|
173
|
+
node_process_1.default.stdout.write(`\r${" ".repeat(60)}\r`);
|
|
174
|
+
if (!result.saved) {
|
|
175
|
+
console.log("Nothing has changed since the last save, so there is nothing to record.");
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
const count = (0, history_js_1.changeCount)(result.changes);
|
|
179
|
+
console.log(`Saved ${accent(short(result.save.id))} on ${accent(result.save.line)}: ` +
|
|
180
|
+
`${result.save.message.split("\n")[0]}`);
|
|
181
|
+
console.log(dim(`${plural(count, "file")} changed · ${plural(result.files, "file")}, ` +
|
|
182
|
+
`${bytes(result.bytes)} in the save`));
|
|
183
|
+
printChanges(result.changes, 20);
|
|
184
|
+
return 0;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/* ---- status ---------------------------------------------------------- */
|
|
188
|
+
async function localStatus(_parsed, repository) {
|
|
189
|
+
return reporting(async () => {
|
|
190
|
+
const now = await (0, history_js_1.status)(repository);
|
|
191
|
+
console.log(`${bold(node_path_1.default.basename(repository.root))} on ${accent(now.line)}` +
|
|
192
|
+
(now.head ? dim(` last saved ${short(now.head.id)}: ${now.head.message.split("\n")[0]}`) : dim(" nothing saved yet")));
|
|
193
|
+
if (now.merging) {
|
|
194
|
+
const left = now.merging.unresolved.length;
|
|
195
|
+
console.log(`Merging ${accent(short(now.merging.theirs))}: ` +
|
|
196
|
+
(left ? red(`${plural(left, "file")} still ${left === 1 ? "has" : "have"} conflict markers`) : green("no conflict markers left")));
|
|
197
|
+
for (const conflict of now.merging.conflicts) {
|
|
198
|
+
const how = conflict.kind === "text"
|
|
199
|
+
? now.merging.unresolved.includes(conflict.path) ? "markers in it" : "edited"
|
|
200
|
+
: conflict.kind === "binary"
|
|
201
|
+
? "both changed; this line's copy is in the folder"
|
|
202
|
+
: conflict.kind === "deleted-here"
|
|
203
|
+
? "deleted here, changed there; their copy is in the folder"
|
|
204
|
+
: "changed here, deleted there; this copy is in the folder";
|
|
205
|
+
console.log(` ${red("conflict")} ${conflict.path} ${dim(how)}`);
|
|
206
|
+
}
|
|
207
|
+
console.log(dim(`Finish with cbx save, take a side with cbx merge --mine or --theirs, or cbx merge --cancel.`));
|
|
208
|
+
}
|
|
209
|
+
const count = (0, history_js_1.changeCount)(now.changes);
|
|
210
|
+
if (!count) {
|
|
211
|
+
console.log("Nothing has changed since the last save.");
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
console.log(`${plural(count, "file")} changed since the last save:`);
|
|
215
|
+
printChanges(now.changes);
|
|
216
|
+
console.log(dim(`cbx diff shows the lines; cbx save -m "…" records them.`));
|
|
217
|
+
return 0;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/* ---- log ------------------------------------------------------------- */
|
|
221
|
+
async function localLog(parsed, repository) {
|
|
222
|
+
return reporting(async () => {
|
|
223
|
+
const limit = Number(text(parsed, "limit") ?? 20);
|
|
224
|
+
const saves = await (0, history_js_1.log)(repository, {
|
|
225
|
+
from: parsed.positional[0],
|
|
226
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 20,
|
|
227
|
+
});
|
|
228
|
+
if (!saves.length) {
|
|
229
|
+
console.log(`Nothing saved on ${accent(await repository.currentLine())} yet.`);
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
for (const one of saves)
|
|
233
|
+
console.log(describeSave(one));
|
|
234
|
+
return 0;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/* ---- diff ------------------------------------------------------------ */
|
|
238
|
+
async function localDiff(parsed, repository) {
|
|
239
|
+
return reporting(async () => {
|
|
240
|
+
const [from, to] = parsed.positional;
|
|
241
|
+
const contains = text(parsed, "path");
|
|
242
|
+
const context = Number(text(parsed, "context") ?? 3);
|
|
243
|
+
const changes = await (0, history_js_1.diff)(repository, {
|
|
244
|
+
from,
|
|
245
|
+
to,
|
|
246
|
+
only: contains ? (file) => file.includes(contains) : undefined,
|
|
247
|
+
context: Number.isFinite(context) && context >= 0 ? context : 3,
|
|
248
|
+
});
|
|
249
|
+
if (!changes.length) {
|
|
250
|
+
console.log(to ? "Those two saves hold the same files." : "Nothing has changed since the last save.");
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
const mark = { added: green("+"), modified: accent("~"), deleted: red("-") };
|
|
254
|
+
for (const change of changes) {
|
|
255
|
+
console.log(`${mark[change.kind]} ${bold(change.path)}`);
|
|
256
|
+
if (has(parsed, "name-only"))
|
|
257
|
+
continue;
|
|
258
|
+
if (change.executableChanged)
|
|
259
|
+
console.log(dim(" execute permission changed"));
|
|
260
|
+
if (change.summary) {
|
|
261
|
+
console.log(dim(` ${change.summary}`));
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
for (const chunk of change.chunks ?? []) {
|
|
265
|
+
if (chunk.kind === "gap") {
|
|
266
|
+
console.log(dim(` ... ${chunk.skipped} unchanged`));
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
for (const line of chunk.lines) {
|
|
270
|
+
const at = String(line.candidateLine ?? line.targetLine ?? "").padStart(5);
|
|
271
|
+
if (line.side === "same")
|
|
272
|
+
console.log(dim(`${at} ${line.text}`));
|
|
273
|
+
else if (line.side === "candidate")
|
|
274
|
+
console.log(`${dim(at)} ${green("+")} ${green(line.text)}`);
|
|
275
|
+
else
|
|
276
|
+
console.log(`${dim(at)} ${red("-")} ${red(line.text)}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return 0;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/* ---- restore --------------------------------------------------------- */
|
|
284
|
+
async function commandRestore(parsed) {
|
|
285
|
+
return reporting(async () => {
|
|
286
|
+
const repository = await repository_js_1.Repository.open(node_process_1.default.cwd());
|
|
287
|
+
const from = text(parsed, "from");
|
|
288
|
+
const result = await (0, history_js_1.restore)(repository, {
|
|
289
|
+
from,
|
|
290
|
+
only: (0, history_js_1.pathFilter)(repository, parsed.positional),
|
|
291
|
+
force: has(parsed, "force", "f"),
|
|
292
|
+
});
|
|
293
|
+
const touched = result.written.length + result.removed.length;
|
|
294
|
+
if (!touched) {
|
|
295
|
+
console.log(`Already matches ${accent(short(result.save.id))}; nothing to put back.`);
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
console.log(`Put back ${plural(touched, "file")} from ${accent(short(result.save.id))}: ` +
|
|
299
|
+
`${result.save.message.split("\n")[0]}`);
|
|
300
|
+
for (const file of result.written.slice(0, 20))
|
|
301
|
+
console.log(` ${green("restored")} ${file}`);
|
|
302
|
+
for (const file of result.removed.slice(0, 20))
|
|
303
|
+
console.log(` ${red("removed ")} ${file}`);
|
|
304
|
+
if (touched > 40)
|
|
305
|
+
console.log(dim(` … and more`));
|
|
306
|
+
console.log(dim("Your line has not moved. Save to record the folder as it is now."));
|
|
307
|
+
return 0;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/* ---- switch ---------------------------------------------------------- */
|
|
311
|
+
async function localSwitch(parsed, repository) {
|
|
312
|
+
return reporting(async () => {
|
|
313
|
+
const line = parsed.positional[0];
|
|
314
|
+
if (!line) {
|
|
315
|
+
const current = await repository.currentLine();
|
|
316
|
+
for (const one of await repository.lines()) {
|
|
317
|
+
console.log(`${one.name === current ? accent("*") : " "} ${one.name} ${dim(short(one.tip))}`);
|
|
318
|
+
}
|
|
319
|
+
if (!(await repository.lineExists(current))) {
|
|
320
|
+
console.log(`${accent("*")} ${current} ${dim("nothing saved yet")}`);
|
|
321
|
+
}
|
|
322
|
+
return 0;
|
|
323
|
+
}
|
|
324
|
+
const result = await (0, history_js_1.switchLine)(repository, line, {
|
|
325
|
+
create: has(parsed, "c", "create", "new", "n"),
|
|
326
|
+
force: has(parsed, "force", "f"),
|
|
327
|
+
});
|
|
328
|
+
if (result.created) {
|
|
329
|
+
console.log(`Started ${accent(result.line)}` +
|
|
330
|
+
(result.save ? ` from ${accent(short(result.save.id))}.` : ".") +
|
|
331
|
+
dim(" No files changed."));
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
334
|
+
const touched = result.written.length + result.removed.length;
|
|
335
|
+
console.log(`On ${accent(result.line)} now` +
|
|
336
|
+
(touched ? `, ${plural(touched, "file")} changed.` : ", and no files needed changing."));
|
|
337
|
+
return 0;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
/* ---- push and pull ---------------------------------------------------- */
|
|
341
|
+
/**
|
|
342
|
+
* Keep the folder's CodeRook link in step with what a push or pull made, so
|
|
343
|
+
* `cbx submit` and `cbx get` in the same folder start from the same place.
|
|
344
|
+
*/
|
|
345
|
+
async function recordLink(repository, line, repositoryId, head, manifest) {
|
|
346
|
+
const link = await (0, config_js_1.readLink)(repository.root);
|
|
347
|
+
const slug = (await (0, api_js_1.projectById)(repositoryId).catch(() => null))?.slug ??
|
|
348
|
+
link?.slug ??
|
|
349
|
+
node_path_1.default.basename(repository.root);
|
|
350
|
+
await (0, config_js_1.writeLink)(repository.root, {
|
|
351
|
+
...(link ?? {}),
|
|
352
|
+
repositoryId,
|
|
353
|
+
slug,
|
|
354
|
+
sequence: head.sequence,
|
|
355
|
+
track: line,
|
|
356
|
+
versionId: head.versionId,
|
|
357
|
+
baseVersionId: head.versionId,
|
|
358
|
+
observedHeadVersionId: head.versionId,
|
|
359
|
+
manifest,
|
|
360
|
+
local: manifest,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
async function localPush(parsed, repository) {
|
|
364
|
+
return reporting(async () => {
|
|
365
|
+
const link = await (0, config_js_1.readLink)(repository.root);
|
|
366
|
+
const interactive = node_process_1.default.stdout.isTTY;
|
|
367
|
+
const result = await (0, remote_js_1.push)(repository, {
|
|
368
|
+
credentials: config_js_1.credentials,
|
|
369
|
+
projectName: link?.slug ?? node_path_1.default.basename(repository.root),
|
|
370
|
+
repositoryId: link?.repositoryId ?? null,
|
|
371
|
+
allowSecrets: has(parsed, "allow-secrets"),
|
|
372
|
+
acknowledged: has(parsed, "yes", "y"),
|
|
373
|
+
report: (event) => {
|
|
374
|
+
if (event.kind === "save" && interactive) {
|
|
375
|
+
node_process_1.default.stdout.write(`\r${dim(`Sending ${event.index + 1} of ${event.total}: ${event.save.message.split("\n")[0].slice(0, 50)}`)} `);
|
|
376
|
+
}
|
|
377
|
+
if (event.kind === "pushed" || event.kind === "unchanged") {
|
|
378
|
+
if (interactive)
|
|
379
|
+
node_process_1.default.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
380
|
+
console.log(` ${accent(short(event.save.id))} ` +
|
|
381
|
+
(event.kind === "pushed" ? green(`v${event.sequence}`) : dim("already there")) +
|
|
382
|
+
` ${event.save.message.split("\n")[0]}`);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
if (!result.pushed.length) {
|
|
387
|
+
console.log(`Everything on ${accent(result.line)} is already on CodeRook.`);
|
|
388
|
+
return 0;
|
|
389
|
+
}
|
|
390
|
+
const last = result.pushed[result.pushed.length - 1];
|
|
391
|
+
console.log(`Pushed ${plural(result.pushed.length, "save")} to CodeRook on ${accent(result.line)}; ` +
|
|
392
|
+
`it is at ${accent(`v${last.sequence}`)} now.`);
|
|
393
|
+
if (result.repositoryId && result.head) {
|
|
394
|
+
await recordLink(repository, result.line, result.repositoryId, result.head, result.head.manifest);
|
|
395
|
+
}
|
|
396
|
+
return 0;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async function localPull(parsed, repository) {
|
|
400
|
+
return reporting(async () => {
|
|
401
|
+
let repositoryId = (await (0, remote_js_1.readRemote)(repository)).repositoryId;
|
|
402
|
+
const named = parsed.positional[0];
|
|
403
|
+
if (named) {
|
|
404
|
+
const project = await (0, project_commands_js_1.resolveProject)(named);
|
|
405
|
+
if (!project)
|
|
406
|
+
return 1;
|
|
407
|
+
repositoryId = project.id;
|
|
408
|
+
}
|
|
409
|
+
repositoryId ??= (await (0, config_js_1.readLink)(repository.root))?.repositoryId ?? null;
|
|
410
|
+
const interactive = node_process_1.default.stdout.isTTY;
|
|
411
|
+
const result = await (0, remote_js_1.pull)(repository, {
|
|
412
|
+
credentials: config_js_1.credentials,
|
|
413
|
+
repositoryId,
|
|
414
|
+
force: has(parsed, "force", "f"),
|
|
415
|
+
report: (event) => {
|
|
416
|
+
if (!interactive)
|
|
417
|
+
return;
|
|
418
|
+
if (event.kind === "version") {
|
|
419
|
+
node_process_1.default.stdout.write(`\r${dim(`Fetching v${event.version.sequence} (${event.index + 1} of ${event.total})`)}${" ".repeat(20)}`);
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
if (interactive)
|
|
424
|
+
node_process_1.default.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
425
|
+
for (const save of result.imported)
|
|
426
|
+
console.log(` ${describeSave(save)}`);
|
|
427
|
+
if (result.merged?.conflicts) {
|
|
428
|
+
console.log(`CodeRook's ${accent(result.line)} and this history had both moved on, and merging them left ` +
|
|
429
|
+
red(plural(result.merged.conflicts.length, "conflict")) + ":");
|
|
430
|
+
for (const conflict of result.merged.conflicts)
|
|
431
|
+
console.log(` ${red("conflict")} ${conflict.path}`);
|
|
432
|
+
console.log(dim("Fix them, then cbx save and cbx push. cbx status lists them; cbx merge --cancel calls it off."));
|
|
433
|
+
return 1;
|
|
434
|
+
}
|
|
435
|
+
if (result.merged?.save) {
|
|
436
|
+
const touched = result.written.length + result.removed.length;
|
|
437
|
+
console.log(`Merged ${plural(result.imported.length, "save")} from CodeRook's ${accent(result.line)} with this history's own` +
|
|
438
|
+
(touched ? `; ${plural(touched, "file")} changed.` : ".") +
|
|
439
|
+
` Push to send the merge.`);
|
|
440
|
+
return 0;
|
|
441
|
+
}
|
|
442
|
+
if (!result.moved) {
|
|
443
|
+
console.log(result.imported.length
|
|
444
|
+
? `Fetched ${plural(result.imported.length, "save")}.`
|
|
445
|
+
: `Already up to date with CodeRook's ${accent(result.line)}.`);
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
const touched = result.written.length + result.removed.length;
|
|
449
|
+
console.log(`Pulled ${plural(result.imported.length, "save")} on ${accent(result.line)}` +
|
|
450
|
+
(touched ? `; ${plural(touched, "file")} changed.` : "."));
|
|
451
|
+
const pulledFrom = (await (0, remote_js_1.readRemote)(repository)).repositoryId;
|
|
452
|
+
const tip = await repository.head();
|
|
453
|
+
if (pulledFrom && result.head && tip) {
|
|
454
|
+
const tree = await repository.readTree((await repository.readSave(tip)).tree);
|
|
455
|
+
const manifest = Object.fromEntries(tree.files.map((file) => [file.path, file.sha256]));
|
|
456
|
+
await recordLink(repository, result.line, pulledFrom, result.head, manifest);
|
|
457
|
+
}
|
|
458
|
+
return 0;
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
/* ---- merge ------------------------------------------------------------ */
|
|
462
|
+
async function localMerge(parsed, repository) {
|
|
463
|
+
return reporting(async () => {
|
|
464
|
+
if (has(parsed, "cancel", "abort")) {
|
|
465
|
+
const put = await (0, history_js_1.cancelMerge)(repository);
|
|
466
|
+
console.log(`Merge called off; ${plural(put.length, "file")} put back as the last save had them.`);
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
469
|
+
const pathText = text(parsed, "path");
|
|
470
|
+
const only = pathText ? (0, history_js_1.pathFilter)(repository, [pathText]) : undefined;
|
|
471
|
+
for (const side of ["mine", "theirs"]) {
|
|
472
|
+
if (!has(parsed, side))
|
|
473
|
+
continue;
|
|
474
|
+
const settled = await (0, history_js_1.resolveConflicts)(repository, side, only);
|
|
475
|
+
console.log(`Took ${side === "mine" ? "this line's" : "the other"} side of ${plural(settled.length, "file")}:`);
|
|
476
|
+
for (const file of settled)
|
|
477
|
+
console.log(` ${file}`);
|
|
478
|
+
console.log(dim("Save when the rest are settled."));
|
|
479
|
+
return 0;
|
|
480
|
+
}
|
|
481
|
+
const other = parsed.positional[0];
|
|
482
|
+
if (!other) {
|
|
483
|
+
console.error(red("Merge what? Name a line or a save: cbx merge feature"));
|
|
484
|
+
return 1;
|
|
485
|
+
}
|
|
486
|
+
const outcome = await (0, history_js_1.merge)(repository, other, { message: text(parsed, "m", "message") });
|
|
487
|
+
const touched = (outcome.kind === "up-to-date" ? 0 : outcome.written.length + outcome.removed.length);
|
|
488
|
+
if (outcome.kind === "up-to-date") {
|
|
489
|
+
console.log(`${other} is already part of this line.`);
|
|
490
|
+
}
|
|
491
|
+
else if (outcome.kind === "fast-forward") {
|
|
492
|
+
console.log(`Moved to ${accent(short(outcome.save.id))}; ${plural(touched, "file")} changed. Nothing needed merging.`);
|
|
493
|
+
}
|
|
494
|
+
else if (outcome.kind === "merged") {
|
|
495
|
+
console.log(`Merged ${accent(other)} as ${accent(short(outcome.save.id))}; ${plural(touched, "file")} changed.`);
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
console.log(`Merged what could be merged; ${red(plural(outcome.state.conflicts.length, "conflict"))} left:`);
|
|
499
|
+
for (const conflict of outcome.state.conflicts)
|
|
500
|
+
console.log(` ${red("conflict")} ${conflict.path}`);
|
|
501
|
+
console.log(dim("Fix them, then cbx save. Or take a side: cbx merge --theirs [--path file]. Or cbx merge --cancel."));
|
|
502
|
+
return 1;
|
|
503
|
+
}
|
|
504
|
+
return 0;
|
|
505
|
+
});
|
|
506
|
+
}
|
package/dist/cli/src/mcp.js
CHANGED
|
@@ -27,8 +27,8 @@ const promises_1 = require("node:fs/promises");
|
|
|
27
27
|
const node_os_1 = __importDefault(require("node:os"));
|
|
28
28
|
const node_path_1 = __importDefault(require("node:path"));
|
|
29
29
|
const node_process_1 = __importDefault(require("node:process"));
|
|
30
|
-
const download_js_1 = require("../../
|
|
31
|
-
const worktree_js_1 = require("../../
|
|
30
|
+
const download_js_1 = require("../../cbx/src/core/download.js");
|
|
31
|
+
const worktree_js_1 = require("../../cbx/src/core/worktree.js");
|
|
32
32
|
const api_js_1 = require("./api.js");
|
|
33
33
|
const config_js_1 = require("./config.js");
|
|
34
34
|
/** What the protocol calls itself. Both clients accept this revision. */
|