@coderook/cli 0.44.0 → 0.46.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.
@@ -36,15 +36,7 @@ const config_js_1 = require("./config.js");
36
36
  const project_choice_js_1 = require("./project_choice.js");
37
37
  const project_commands_js_1 = require("./project_commands.js");
38
38
  const publish_js_1 = require("./publish.js");
39
- const ESC = String.fromCharCode(27);
40
- const paint = (code) => (value) => `${ESC}[${code}m${value}${ESC}[0m`;
41
- const dim = paint("2");
42
- const bold = paint("1");
43
- const red = paint("31");
44
- const green = paint("32");
45
- const accent = paint("33");
46
- const short = (id) => id.slice(0, 10);
47
- const plural = (count, one, many = `${one}s`) => `${count.toLocaleString()} ${count === 1 ? one : many}`;
39
+ const local_status_js_1 = require("./local_status.js");
48
40
  function text(parsed, ...names) {
49
41
  for (const name of names) {
50
42
  const value = parsed.flags.get(name);
@@ -77,23 +69,12 @@ function localFirst(local, otherwise, from = () => node_process_1.default.cwd())
77
69
  return repository ? local(parsed, repository) : otherwise(parsed);
78
70
  };
79
71
  }
80
- function printChanges(changes, limit = 50) {
81
- const rows = [
82
- ...changes.added.map((file) => [green("added "), file]),
83
- ...changes.modified.map((file) => [accent("changed "), file]),
84
- ...changes.deleted.map((file) => [red("deleted "), file]),
85
- ].sort((left, right) => (left[1] < right[1] ? -1 : left[1] > right[1] ? 1 : 0));
86
- for (const [mark, file] of rows.slice(0, limit))
87
- console.log(` ${mark}${file}`);
88
- if (rows.length > limit)
89
- console.log(dim(` … and ${rows.length - limit} more`));
90
- }
91
72
  function describeSave(one) {
92
73
  const when = new Date(one.created);
93
74
  const date = Number.isNaN(when.getTime())
94
75
  ? one.created
95
76
  : when.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
96
- return `${accent(short(one.id))} ${one.message.split("\n")[0]} ${dim(`${one.author} · ${date}`)}`;
77
+ return `${(0, local_status_js_1.accent)((0, local_status_js_1.short)(one.id))} ${one.message.split("\n")[0]} ${(0, local_status_js_1.dim)(`${one.author} · ${date}`)}`;
97
78
  }
98
79
  /** Refusals are printed as sentences, not stack traces. */
99
80
  async function reporting(work) {
@@ -102,29 +83,29 @@ async function reporting(work) {
102
83
  }
103
84
  catch (error) {
104
85
  if (error instanceof remote_js_1.RemoteAhead || error instanceof remote_js_1.RemoteDiffers || error instanceof remote_js_1.NoRemote) {
105
- console.error(red(error.message));
86
+ console.error((0, local_status_js_1.red)(error.message));
106
87
  return 1;
107
88
  }
108
89
  const failure = (0, publish_js_1.classifyPublishFailure)(error);
109
90
  if (failure?.kind === "credentials") {
110
- console.error(red(failure.message));
111
- console.error(dim("Take the key out of that save, or push it deliberately with --allow-secrets."));
91
+ console.error((0, local_status_js_1.red)(failure.message));
92
+ console.error((0, local_status_js_1.dim)("Take the key out of that save, or push it deliberately with --allow-secrets."));
112
93
  return 1;
113
94
  }
114
95
  if (failure?.kind === "interrupted") {
115
- console.error(red(`The connection failed part way: ${failure.message}`));
116
- console.error(dim("Push again. Saves already on CodeRook are not sent twice."));
96
+ console.error((0, local_status_js_1.red)(`The connection failed part way: ${failure.message}`));
97
+ console.error((0, local_status_js_1.dim)("Push again. Saves already on CodeRook are not sent twice."));
117
98
  return 1;
118
99
  }
119
100
  if (error instanceof history_js_1.UnsavedChanges || error instanceof history_js_1.UnresolvedConflicts) {
120
- console.error(red(error.message));
101
+ console.error((0, local_status_js_1.red)(error.message));
121
102
  for (const file of error.files.slice(0, 20))
122
103
  console.error(` ${file}`);
123
104
  if (error.files.length > 20)
124
- console.error(dim(` … and ${error.files.length - 20} more`));
105
+ console.error((0, local_status_js_1.dim)(` … and ${error.files.length - 20} more`));
125
106
  return 1;
126
107
  }
127
- console.error(red(error instanceof Error ? error.message : String(error)));
108
+ console.error((0, local_status_js_1.red)(error instanceof Error ? error.message : String(error)));
128
109
  return 1;
129
110
  }
130
111
  }
@@ -134,13 +115,13 @@ async function commandInit(parsed) {
134
115
  const folder = node_path_1.default.resolve(parsed.positional[0] ?? node_process_1.default.cwd());
135
116
  const inside = await localHistory(folder);
136
117
  if (inside && inside.root !== folder) {
137
- console.error(red(`${folder} is already inside the history at ${inside.root}.`));
138
- console.error(dim("One history per project. Run cbx commands from anywhere inside it."));
118
+ console.error((0, local_status_js_1.red)(`${folder} is already inside the history at ${inside.root}.`));
119
+ console.error((0, local_status_js_1.dim)("One history per project. Run cbx commands from anywhere inside it."));
139
120
  return 1;
140
121
  }
141
122
  const repository = await (0, history_js_1.init)(folder, text(parsed, "line") ?? undefined);
142
- console.log(`Started a history in ${bold(repository.root)} on ${accent(await repository.currentLine())}.`);
143
- console.log(dim("It lives in .cbx/ and never leaves this machine by itself. Run " +
123
+ console.log(`Started a history in ${(0, local_status_js_1.bold)(repository.root)} on ${(0, local_status_js_1.accent)(await repository.currentLine())}.`);
124
+ console.log((0, local_status_js_1.dim)("It lives in .cbx/ and never leaves this machine by itself. Run " +
144
125
  `cbx save -m "…" to record the folder as it is.`));
145
126
  return 0;
146
127
  });
@@ -152,7 +133,7 @@ async function commandSave(parsed) {
152
133
  const message = text(parsed, "m", "message") ?? "";
153
134
  const merging = await (0, merge_js_1.readMergeState)(repository);
154
135
  if (!message.trim() && !merging) {
155
- console.error(red('A save needs a message: cbx save -m "what changed"'));
136
+ console.error((0, local_status_js_1.red)('A save needs a message: cbx save -m "what changed"'));
156
137
  return 1;
157
138
  }
158
139
  const interactive = node_process_1.default.stdout.isTTY;
@@ -167,7 +148,7 @@ async function commandSave(parsed) {
167
148
  if (now - shown < 100 && done < total)
168
149
  return;
169
150
  shown = now;
170
- node_process_1.default.stdout.write(`\r${dim(`Reading ${done.toLocaleString()} of ${total.toLocaleString()} files`)} `);
151
+ node_process_1.default.stdout.write(`\r${(0, local_status_js_1.dim)(`Reading ${done.toLocaleString()} of ${total.toLocaleString()} files`)} `);
171
152
  }
172
153
  : undefined,
173
154
  });
@@ -178,46 +159,17 @@ async function commandSave(parsed) {
178
159
  return 0;
179
160
  }
180
161
  const count = (0, history_js_1.changeCount)(result.changes);
181
- console.log(`Saved ${accent(short(result.save.id))} on ${accent(result.save.line)}: ` +
162
+ console.log(`Saved ${(0, local_status_js_1.accent)((0, local_status_js_1.short)(result.save.id))} on ${(0, local_status_js_1.accent)(result.save.line)}: ` +
182
163
  `${result.save.message.split("\n")[0]}`);
183
- console.log(dim(`${plural(count, "file")} changed · ${plural(result.files, "file")}, ` +
164
+ console.log((0, local_status_js_1.dim)(`${(0, local_status_js_1.plural)(count, "file")} changed · ${(0, local_status_js_1.plural)(result.files, "file")}, ` +
184
165
  `${bytes(result.bytes)} in the save`));
185
- printChanges(result.changes, 20);
166
+ (0, local_status_js_1.printChanges)(result.changes, 20);
186
167
  return 0;
187
168
  });
188
169
  }
189
170
  /* ---- status ---------------------------------------------------------- */
190
171
  async function localStatus(_parsed, repository) {
191
- return reporting(async () => {
192
- const now = await (0, history_js_1.status)(repository);
193
- console.log(`${bold(node_path_1.default.basename(repository.root))} on ${accent(now.line)}` +
194
- (now.head ? dim(` last saved ${short(now.head.id)}: ${now.head.message.split("\n")[0]}`) : dim(" nothing saved yet")));
195
- if (now.merging) {
196
- const left = now.merging.unresolved.length;
197
- console.log(`Merging ${accent(short(now.merging.theirs))}: ` +
198
- (left ? red(`${plural(left, "file")} still ${left === 1 ? "has" : "have"} conflict markers`) : green("no conflict markers left")));
199
- for (const conflict of now.merging.conflicts) {
200
- const how = conflict.kind === "text"
201
- ? now.merging.unresolved.includes(conflict.path) ? "markers in it" : "edited"
202
- : conflict.kind === "binary"
203
- ? "both changed; this line's copy is in the folder"
204
- : conflict.kind === "deleted-here"
205
- ? "deleted here, changed there; their copy is in the folder"
206
- : "changed here, deleted there; this copy is in the folder";
207
- console.log(` ${red("conflict")} ${conflict.path} ${dim(how)}`);
208
- }
209
- console.log(dim(`Finish with cbx save, take a side with cbx merge --mine or --theirs, or cbx merge --cancel.`));
210
- }
211
- const count = (0, history_js_1.changeCount)(now.changes);
212
- if (!count) {
213
- console.log("Nothing has changed since the last save.");
214
- return 0;
215
- }
216
- console.log(`${plural(count, "file")} changed since the last save:`);
217
- printChanges(now.changes);
218
- console.log(dim(`cbx diff shows the lines; cbx save -m "…" records them.`));
219
- return 0;
220
- });
172
+ return reporting(() => (0, local_status_js_1.printLocalStatus)(repository));
221
173
  }
222
174
  /* ---- log ------------------------------------------------------------- */
223
175
  async function localLog(parsed, repository) {
@@ -228,7 +180,7 @@ async function localLog(parsed, repository) {
228
180
  limit: Number.isFinite(limit) && limit > 0 ? limit : 20,
229
181
  });
230
182
  if (!saves.length) {
231
- console.log(`Nothing saved on ${accent(await repository.currentLine())} yet.`);
183
+ console.log(`Nothing saved on ${(0, local_status_js_1.accent)(await repository.currentLine())} yet.`);
232
184
  return 0;
233
185
  }
234
186
  for (const one of saves)
@@ -252,30 +204,30 @@ async function localDiff(parsed, repository) {
252
204
  console.log(to ? "Those two saves hold the same files." : "Nothing has changed since the last save.");
253
205
  return 0;
254
206
  }
255
- const mark = { added: green("+"), modified: accent("~"), deleted: red("-") };
207
+ const mark = { added: (0, local_status_js_1.green)("+"), modified: (0, local_status_js_1.accent)("~"), deleted: (0, local_status_js_1.red)("-") };
256
208
  for (const change of changes) {
257
- console.log(`${mark[change.kind]} ${bold(change.path)}`);
209
+ console.log(`${mark[change.kind]} ${(0, local_status_js_1.bold)(change.path)}`);
258
210
  if (has(parsed, "name-only"))
259
211
  continue;
260
212
  if (change.executableChanged)
261
- console.log(dim(" execute permission changed"));
213
+ console.log((0, local_status_js_1.dim)(" execute permission changed"));
262
214
  if (change.summary) {
263
- console.log(dim(` ${change.summary}`));
215
+ console.log((0, local_status_js_1.dim)(` ${change.summary}`));
264
216
  continue;
265
217
  }
266
218
  for (const chunk of change.chunks ?? []) {
267
219
  if (chunk.kind === "gap") {
268
- console.log(dim(` ... ${chunk.skipped} unchanged`));
220
+ console.log((0, local_status_js_1.dim)(` ... ${chunk.skipped} unchanged`));
269
221
  continue;
270
222
  }
271
223
  for (const line of chunk.lines) {
272
224
  const at = String(line.candidateLine ?? line.targetLine ?? "").padStart(5);
273
225
  if (line.side === "same")
274
- console.log(dim(`${at} ${line.text}`));
226
+ console.log((0, local_status_js_1.dim)(`${at} ${line.text}`));
275
227
  else if (line.side === "candidate")
276
- console.log(`${dim(at)} ${green("+")} ${green(line.text)}`);
228
+ console.log(`${(0, local_status_js_1.dim)(at)} ${(0, local_status_js_1.green)("+")} ${(0, local_status_js_1.green)(line.text)}`);
277
229
  else
278
- console.log(`${dim(at)} ${red("-")} ${red(line.text)}`);
230
+ console.log(`${(0, local_status_js_1.dim)(at)} ${(0, local_status_js_1.red)("-")} ${(0, local_status_js_1.red)(line.text)}`);
279
231
  }
280
232
  }
281
233
  }
@@ -294,18 +246,18 @@ async function commandRestore(parsed) {
294
246
  });
295
247
  const touched = result.written.length + result.removed.length;
296
248
  if (!touched) {
297
- console.log(`Already matches ${accent(short(result.save.id))}; nothing to put back.`);
249
+ console.log(`Already matches ${(0, local_status_js_1.accent)((0, local_status_js_1.short)(result.save.id))}; nothing to put back.`);
298
250
  return 0;
299
251
  }
300
- console.log(`Put back ${plural(touched, "file")} from ${accent(short(result.save.id))}: ` +
252
+ console.log(`Put back ${(0, local_status_js_1.plural)(touched, "file")} from ${(0, local_status_js_1.accent)((0, local_status_js_1.short)(result.save.id))}: ` +
301
253
  `${result.save.message.split("\n")[0]}`);
302
254
  for (const file of result.written.slice(0, 20))
303
- console.log(` ${green("restored")} ${file}`);
255
+ console.log(` ${(0, local_status_js_1.green)("restored")} ${file}`);
304
256
  for (const file of result.removed.slice(0, 20))
305
- console.log(` ${red("removed ")} ${file}`);
257
+ console.log(` ${(0, local_status_js_1.red)("removed ")} ${file}`);
306
258
  if (touched > 40)
307
- console.log(dim(` … and more`));
308
- console.log(dim("Your line has not moved. Save to record the folder as it is now."));
259
+ console.log((0, local_status_js_1.dim)(` … and more`));
260
+ console.log((0, local_status_js_1.dim)("Your line has not moved. Save to record the folder as it is now."));
309
261
  return 0;
310
262
  });
311
263
  }
@@ -316,10 +268,10 @@ async function localSwitch(parsed, repository) {
316
268
  if (!line) {
317
269
  const current = await repository.currentLine();
318
270
  for (const one of await repository.lines()) {
319
- console.log(`${one.name === current ? accent("*") : " "} ${one.name} ${dim(short(one.tip))}`);
271
+ console.log(`${one.name === current ? (0, local_status_js_1.accent)("*") : " "} ${one.name} ${(0, local_status_js_1.dim)((0, local_status_js_1.short)(one.tip))}`);
320
272
  }
321
273
  if (!(await repository.lineExists(current))) {
322
- console.log(`${accent("*")} ${current} ${dim("nothing saved yet")}`);
274
+ console.log(`${(0, local_status_js_1.accent)("*")} ${current} ${(0, local_status_js_1.dim)("nothing saved yet")}`);
323
275
  }
324
276
  return 0;
325
277
  }
@@ -328,14 +280,14 @@ async function localSwitch(parsed, repository) {
328
280
  force: has(parsed, "force", "f"),
329
281
  });
330
282
  if (result.created) {
331
- console.log(`Started ${accent(result.line)}` +
332
- (result.save ? ` from ${accent(short(result.save.id))}.` : ".") +
333
- dim(" No files changed."));
283
+ console.log(`Started ${(0, local_status_js_1.accent)(result.line)}` +
284
+ (result.save ? ` from ${(0, local_status_js_1.accent)((0, local_status_js_1.short)(result.save.id))}.` : ".") +
285
+ (0, local_status_js_1.dim)(" No files changed."));
334
286
  return 0;
335
287
  }
336
288
  const touched = result.written.length + result.removed.length;
337
- console.log(`On ${accent(result.line)} now` +
338
- (touched ? `, ${plural(touched, "file")} changed.` : ", and no files needed changing."));
289
+ console.log(`On ${(0, local_status_js_1.accent)(result.line)} now` +
290
+ (touched ? `, ${(0, local_status_js_1.plural)(touched, "file")} changed.` : ", and no files needed changing."));
339
291
  return 0;
340
292
  });
341
293
  }
@@ -397,25 +349,25 @@ async function localPush(parsed, repository) {
397
349
  if (event.kind === "upload")
398
350
  uploadDisplay.report(event.progress);
399
351
  if (event.kind === "save" && interactive) {
400
- node_process_1.default.stdout.write(`\r${dim(`Sending ${event.index + 1} of ${event.total}: ${event.save.message.split("\n")[0].slice(0, 50)}`)} `);
352
+ node_process_1.default.stdout.write(`\r${(0, local_status_js_1.dim)(`Sending ${event.index + 1} of ${event.total}: ${event.save.message.split("\n")[0].slice(0, 50)}`)} `);
401
353
  }
402
354
  if (event.kind === "pushed" || event.kind === "unchanged") {
403
355
  uploadDisplay.done();
404
356
  if (interactive)
405
357
  node_process_1.default.stdout.write(`\r${" ".repeat(80)}\r`);
406
- console.log(` ${accent(short(event.save.id))} ` +
407
- (event.kind === "pushed" ? green(`v${event.sequence}`) : dim("already there")) +
358
+ console.log(` ${(0, local_status_js_1.accent)((0, local_status_js_1.short)(event.save.id))} ` +
359
+ (event.kind === "pushed" ? (0, local_status_js_1.green)(`v${event.sequence}`) : (0, local_status_js_1.dim)("already there")) +
408
360
  ` ${event.save.message.split("\n")[0]}`);
409
361
  }
410
362
  },
411
363
  }).finally(() => uploadDisplay.done());
412
364
  if (!result.pushed.length) {
413
- console.log(`Everything on ${accent(result.line)} is already on CodeRook.`);
365
+ console.log(`Everything on ${(0, local_status_js_1.accent)(result.line)} is already on CodeRook.`);
414
366
  return 0;
415
367
  }
416
368
  const last = result.pushed[result.pushed.length - 1];
417
- console.log(`Pushed ${plural(result.pushed.length, "save")} to CodeRook on ${accent(result.line)}; ` +
418
- `it is at ${accent(`v${last.sequence}`)} now.`);
369
+ console.log(`Pushed ${(0, local_status_js_1.plural)(result.pushed.length, "save")} to CodeRook on ${(0, local_status_js_1.accent)(result.line)}; ` +
370
+ `it is at ${(0, local_status_js_1.accent)(`v${last.sequence}`)} now.`);
419
371
  if (result.repositoryId && result.head) {
420
372
  await recordLink(repository, result.line, result.repositoryId, result.head, result.head.manifest);
421
373
  }
@@ -442,7 +394,7 @@ async function localPull(parsed, repository) {
442
394
  if (!interactive)
443
395
  return;
444
396
  if (event.kind === "version") {
445
- node_process_1.default.stdout.write(`\r${dim(`Fetching v${event.version.sequence} (${event.index + 1} of ${event.total})`)}${" ".repeat(20)}`);
397
+ node_process_1.default.stdout.write(`\r${(0, local_status_js_1.dim)(`Fetching v${event.version.sequence} (${event.index + 1} of ${event.total})`)}${" ".repeat(20)}`);
446
398
  }
447
399
  },
448
400
  });
@@ -451,29 +403,29 @@ async function localPull(parsed, repository) {
451
403
  for (const save of result.imported)
452
404
  console.log(` ${describeSave(save)}`);
453
405
  if (result.merged?.conflicts) {
454
- console.log(`CodeRook's ${accent(result.line)} and this history had both moved on, and merging them left ` +
455
- red(plural(result.merged.conflicts.length, "conflict")) + ":");
406
+ console.log(`CodeRook's ${(0, local_status_js_1.accent)(result.line)} and this history had both moved on, and merging them left ` +
407
+ (0, local_status_js_1.red)((0, local_status_js_1.plural)(result.merged.conflicts.length, "conflict")) + ":");
456
408
  for (const conflict of result.merged.conflicts)
457
- console.log(` ${red("conflict")} ${conflict.path}`);
458
- console.log(dim("Fix them, then cbx save and cbx push. cbx status lists them; cbx merge --cancel calls it off."));
409
+ console.log(` ${(0, local_status_js_1.red)("conflict")} ${conflict.path}`);
410
+ console.log((0, local_status_js_1.dim)("Fix them, then cbx save and cbx push. cbx status lists them; cbx merge --cancel calls it off."));
459
411
  return 1;
460
412
  }
461
413
  if (result.merged?.save) {
462
414
  const touched = result.written.length + result.removed.length;
463
- console.log(`Merged ${plural(result.imported.length, "save")} from CodeRook's ${accent(result.line)} with this history's own` +
464
- (touched ? `; ${plural(touched, "file")} changed.` : ".") +
415
+ console.log(`Merged ${(0, local_status_js_1.plural)(result.imported.length, "save")} from CodeRook's ${(0, local_status_js_1.accent)(result.line)} with this history's own` +
416
+ (touched ? `; ${(0, local_status_js_1.plural)(touched, "file")} changed.` : ".") +
465
417
  ` Push to send the merge.`);
466
418
  return 0;
467
419
  }
468
420
  if (!result.moved) {
469
421
  console.log(result.imported.length
470
- ? `Fetched ${plural(result.imported.length, "save")}.`
471
- : `Already up to date with CodeRook's ${accent(result.line)}.`);
422
+ ? `Fetched ${(0, local_status_js_1.plural)(result.imported.length, "save")}.`
423
+ : `Already up to date with CodeRook's ${(0, local_status_js_1.accent)(result.line)}.`);
472
424
  return 0;
473
425
  }
474
426
  const touched = result.written.length + result.removed.length;
475
- console.log(`Pulled ${plural(result.imported.length, "save")} on ${accent(result.line)}` +
476
- (touched ? `; ${plural(touched, "file")} changed.` : "."));
427
+ console.log(`Pulled ${(0, local_status_js_1.plural)(result.imported.length, "save")} on ${(0, local_status_js_1.accent)(result.line)}` +
428
+ (touched ? `; ${(0, local_status_js_1.plural)(touched, "file")} changed.` : "."));
477
429
  const pulledFrom = (await (0, remote_js_1.readRemote)(repository)).repositoryId;
478
430
  const tip = await repository.head();
479
431
  if (pulledFrom && result.head && tip) {
@@ -489,7 +441,7 @@ async function localMerge(parsed, repository) {
489
441
  return reporting(async () => {
490
442
  if (has(parsed, "cancel", "abort")) {
491
443
  const put = await (0, history_js_1.cancelMerge)(repository);
492
- console.log(`Merge called off; ${plural(put.length, "file")} put back as the last save had them.`);
444
+ console.log(`Merge called off; ${(0, local_status_js_1.plural)(put.length, "file")} put back as the last save had them.`);
493
445
  return 0;
494
446
  }
495
447
  const pathText = text(parsed, "path");
@@ -498,15 +450,15 @@ async function localMerge(parsed, repository) {
498
450
  if (!has(parsed, side))
499
451
  continue;
500
452
  const settled = await (0, history_js_1.resolveConflicts)(repository, side, only);
501
- console.log(`Took ${side === "mine" ? "this line's" : "the other"} side of ${plural(settled.length, "file")}:`);
453
+ console.log(`Took ${side === "mine" ? "this line's" : "the other"} side of ${(0, local_status_js_1.plural)(settled.length, "file")}:`);
502
454
  for (const file of settled)
503
455
  console.log(` ${file}`);
504
- console.log(dim("Save when the rest are settled."));
456
+ console.log((0, local_status_js_1.dim)("Save when the rest are settled."));
505
457
  return 0;
506
458
  }
507
459
  const other = parsed.positional[0];
508
460
  if (!other) {
509
- console.error(red("Merge what? Name a line or a save: cbx merge feature"));
461
+ console.error((0, local_status_js_1.red)("Merge what? Name a line or a save: cbx merge feature"));
510
462
  return 1;
511
463
  }
512
464
  const outcome = await (0, history_js_1.merge)(repository, other, { message: text(parsed, "m", "message") });
@@ -515,16 +467,16 @@ async function localMerge(parsed, repository) {
515
467
  console.log(`${other} is already part of this line.`);
516
468
  }
517
469
  else if (outcome.kind === "fast-forward") {
518
- console.log(`Moved to ${accent(short(outcome.save.id))}; ${plural(touched, "file")} changed. Nothing needed merging.`);
470
+ console.log(`Moved to ${(0, local_status_js_1.accent)((0, local_status_js_1.short)(outcome.save.id))}; ${(0, local_status_js_1.plural)(touched, "file")} changed. Nothing needed merging.`);
519
471
  }
520
472
  else if (outcome.kind === "merged") {
521
- console.log(`Merged ${accent(other)} as ${accent(short(outcome.save.id))}; ${plural(touched, "file")} changed.`);
473
+ console.log(`Merged ${(0, local_status_js_1.accent)(other)} as ${(0, local_status_js_1.accent)((0, local_status_js_1.short)(outcome.save.id))}; ${(0, local_status_js_1.plural)(touched, "file")} changed.`);
522
474
  }
523
475
  else {
524
- console.log(`Merged what could be merged; ${red(plural(outcome.state.conflicts.length, "conflict"))} left:`);
476
+ console.log(`Merged what could be merged; ${(0, local_status_js_1.red)((0, local_status_js_1.plural)(outcome.state.conflicts.length, "conflict"))} left:`);
525
477
  for (const conflict of outcome.state.conflicts)
526
- console.log(` ${red("conflict")} ${conflict.path}`);
527
- console.log(dim("Fix them, then cbx save. Or take a side: cbx merge --theirs [--path file]. Or cbx merge --cancel."));
478
+ console.log(` ${(0, local_status_js_1.red)("conflict")} ${conflict.path}`);
479
+ console.log((0, local_status_js_1.dim)("Fix them, then cbx save. Or take a side: cbx merge --theirs [--path file]. Or cbx merge --cancel."));
528
480
  return 1;
529
481
  }
530
482
  return 0;
@@ -0,0 +1,72 @@
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.plural = exports.short = exports.accent = exports.green = exports.red = exports.bold = exports.dim = void 0;
7
+ exports.printChanges = printChanges;
8
+ exports.printLocalStatus = printLocalStatus;
9
+ /**
10
+ * What `cbx status` prints in a folder with a local history, kept apart from
11
+ * the rest of the local commands so it can run without loading them.
12
+ *
13
+ * Status is the command people run most, and in a local history it needs only
14
+ * the history itself: no sign-in, no network, none of the upload or project
15
+ * code. bin.ts answers it from here directly; the full command table uses the
16
+ * same function, so the two can never print differently.
17
+ */
18
+ const node_path_1 = __importDefault(require("node:path"));
19
+ const history_js_1 = require("../../cbx/src/local/history.js");
20
+ const ESC = String.fromCharCode(27);
21
+ const paint = (code) => (value) => `${ESC}[${code}m${value}${ESC}[0m`;
22
+ exports.dim = paint("2");
23
+ exports.bold = paint("1");
24
+ exports.red = paint("31");
25
+ exports.green = paint("32");
26
+ exports.accent = paint("33");
27
+ const short = (id) => id.slice(0, 10);
28
+ exports.short = short;
29
+ const plural = (count, one, many = `${one}s`) => `${count.toLocaleString()} ${count === 1 ? one : many}`;
30
+ exports.plural = plural;
31
+ function printChanges(changes, limit = 50) {
32
+ const rows = [
33
+ ...changes.added.map((file) => [(0, exports.green)("added "), file]),
34
+ ...changes.modified.map((file) => [(0, exports.accent)("changed "), file]),
35
+ ...changes.deleted.map((file) => [(0, exports.red)("deleted "), file]),
36
+ ].sort((left, right) => (left[1] < right[1] ? -1 : left[1] > right[1] ? 1 : 0));
37
+ for (const [mark, file] of rows.slice(0, limit))
38
+ console.log(` ${mark}${file}`);
39
+ if (rows.length > limit)
40
+ console.log((0, exports.dim)(` … and ${rows.length - limit} more`));
41
+ }
42
+ /** The status of a local history, printed. */
43
+ async function printLocalStatus(repository) {
44
+ const now = await (0, history_js_1.status)(repository);
45
+ console.log(`${(0, exports.bold)(node_path_1.default.basename(repository.root))} on ${(0, exports.accent)(now.line)}` +
46
+ (now.head ? (0, exports.dim)(` last saved ${(0, exports.short)(now.head.id)}: ${now.head.message.split("\n")[0]}`) : (0, exports.dim)(" nothing saved yet")));
47
+ if (now.merging) {
48
+ const left = now.merging.unresolved.length;
49
+ console.log(`Merging ${(0, exports.accent)((0, exports.short)(now.merging.theirs))}: ` +
50
+ (left ? (0, exports.red)(`${(0, exports.plural)(left, "file")} still ${left === 1 ? "has" : "have"} conflict markers`) : (0, exports.green)("no conflict markers left")));
51
+ for (const conflict of now.merging.conflicts) {
52
+ const how = conflict.kind === "text"
53
+ ? now.merging.unresolved.includes(conflict.path) ? "markers in it" : "edited"
54
+ : conflict.kind === "binary"
55
+ ? "both changed; this line's copy is in the folder"
56
+ : conflict.kind === "deleted-here"
57
+ ? "deleted here, changed there; their copy is in the folder"
58
+ : "changed here, deleted there; this copy is in the folder";
59
+ console.log(` ${(0, exports.red)("conflict")} ${conflict.path} ${(0, exports.dim)(how)}`);
60
+ }
61
+ console.log((0, exports.dim)(`Finish with cbx save, take a side with cbx merge --mine or --theirs, or cbx merge --cancel.`));
62
+ }
63
+ const count = (0, history_js_1.changeCount)(now.changes);
64
+ if (!count) {
65
+ console.log("Nothing has changed since the last save.");
66
+ return 0;
67
+ }
68
+ console.log(`${(0, exports.plural)(count, "file")} changed since the last save:`);
69
+ printChanges(now.changes);
70
+ console.log((0, exports.dim)(`cbx diff shows the lines; cbx save -m "…" records them.`));
71
+ return 0;
72
+ }
@@ -16,7 +16,6 @@ exports.pause = pause;
16
16
  * than 250 ms is abandoned too, so a perfectly healthy connection came back as
17
17
  * the two words "fetch failed". curl and browsers never showed it.
18
18
  */
19
- const node_dns_1 = __importDefault(require("node:dns"));
20
19
  const node_net_1 = __importDefault(require("node:net"));
21
20
  /** Called once, before any request. Explicit settings in NODE_OPTIONS win. */
22
21
  function tuneNetwork() {
@@ -30,12 +29,27 @@ function tuneNetwork() {
30
29
  }
31
30
  }
32
31
  if (!options.includes("dns-result-order")) {
33
- try {
34
- node_dns_1.default.setDefaultResultOrder("ipv4first");
35
- }
36
- catch {
37
- /* Older Node: nothing to tune. */
38
- }
32
+ /*
33
+ Applied just before the first request rather than at start. Loading
34
+ node:dns sets up its resolver, about twenty milliseconds, which commands
35
+ that never touch the network (a local status, a local save) were paying
36
+ on every run. Every request here goes through fetch.
37
+ */
38
+ const original = globalThis.fetch;
39
+ let tuned = false;
40
+ globalThis.fetch = ((...args) => {
41
+ if (!tuned) {
42
+ tuned = true;
43
+ try {
44
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
45
+ require("node:dns").setDefaultResultOrder("ipv4first");
46
+ }
47
+ catch {
48
+ /* Older Node: nothing to tune. */
49
+ }
50
+ }
51
+ return original(...args);
52
+ });
39
53
  }
40
54
  }
41
55
  /** Whether a failure happened before any answer came back. */
@@ -56,13 +56,19 @@ async function resolveProject(reference) {
56
56
  }
57
57
  return project;
58
58
  }
59
- /** The four answers, in the order the site shows them. */
59
+ /** The answers, in the order the site shows them. */
60
60
  const AI_SWITCHES = [
61
61
  [
62
62
  "read",
63
63
  "aiRead",
64
64
  "Can AI read this?",
65
- "reading, indexing, summarising and model training",
65
+ "reading, indexing, summarising and quoting in answers",
66
+ ],
67
+ [
68
+ "train",
69
+ "aiTrain",
70
+ "Can AI train on this?",
71
+ "using what it reads to train a model",
66
72
  ],
67
73
  [
68
74
  "download",
@@ -122,8 +128,8 @@ async function commandAi(parsed) {
122
128
  console.log(` ${(0, colour_js_1.dim)(meaning)}`);
123
129
  }
124
130
  console.log("");
125
- console.log((0, colour_js_1.dim)("Everything is allowed until somebody says otherwise. Reading off is the one"));
126
- console.log((0, colour_js_1.dim)("that refuses model training."));
131
+ console.log((0, colour_js_1.dim)("Everything but training is allowed until somebody says otherwise; training"));
132
+ console.log((0, colour_js_1.dim)("is refused until the owner allows it, and reading off refuses it too."));
127
133
  console.log((0, colour_js_1.dim)("Change these on the website (Settings → Access & AI) or in the desktop app."));
128
134
  return 0;
129
135
  }
@@ -37,7 +37,7 @@ exports.GROUP_ORDER = [
37
37
  * from this list still appears, after the ones that are in it.
38
38
  */
39
39
  exports.LISTING_ORDER = [
40
- "sign-in", "whoami", "sign-out", "doctor", "projects", "clone", "link", "unlink", "import", "transfer",
40
+ "sign-in", "whoami", "sign-out", "doctor", "update", "projects", "clone", "link", "unlink", "import", "transfer",
41
41
  "status", "submit", "diff", "get", "track", "ignore", "licence",
42
42
  "init", "save", "log", "restore", "switch", "push", "pull",
43
43
  "tracks", "line-rules", "merges", "merge", "propose", "proposals", "pr",
@@ -0,0 +1,37 @@
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.reminderStamp = exports.REMIND_EVERY_MS = void 0;
7
+ exports.reminderDue = reminderDue;
8
+ /**
9
+ * Whether the unread-messages reminder is due after a command.
10
+ *
11
+ * Kept apart from workspace_threads.ts, which needs the API client, so the
12
+ * quick status in bin.ts can ask without loading it: when the reminder is
13
+ * due, status goes the long way and the reminder is shown exactly as before.
14
+ */
15
+ const promises_1 = require("node:fs/promises");
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const config_js_1 = require("./config.js");
18
+ exports.REMIND_EVERY_MS = 5 * 60_000;
19
+ /** When the reminder last asked, at most every REMIND_EVERY_MS. */
20
+ const reminderStamp = () => node_path_1.default.join((0, config_js_1.configDirectory)(), "discussions-checked");
21
+ exports.reminderStamp = reminderStamp;
22
+ /** Only at a terminal, never for these commands, and at most every five minutes. */
23
+ async function reminderDue(command) {
24
+ if (!process.stdout.isTTY || process.env.CODEROOK_NO_REMINDERS)
25
+ return false;
26
+ if (["workspace", "sign-in", "sign-out", "help", "mcp"].includes(command))
27
+ return false;
28
+ try {
29
+ const last = Number((await (0, promises_1.readFile)((0, exports.reminderStamp)(), "utf8")).trim());
30
+ if (Date.now() - last < exports.REMIND_EVERY_MS)
31
+ return false;
32
+ }
33
+ catch {
34
+ /* Never checked on this machine. */
35
+ }
36
+ return true;
37
+ }