@skrr-ai/cli 0.1.105 → 0.1.106

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.
@@ -124,6 +124,13 @@ export declare const LIVE_LOG_RESOLUTION_RUNTIME_VERSION = "0.8.119";
124
124
  * that file and its rotated siblings itself.
125
125
  */
126
126
  export declare const ROTATED_LOGS_RUNTIME_VERSION = "0.8.120";
127
+ /**
128
+ * The first runtime whose `logs --rotated --lines N` says where a window that
129
+ * cut older lines starts, and whose `logs --path` names the rotated siblings
130
+ * on stderr (OSK-16228). 0.8.120 knows `--rotated` but says neither, so for it
131
+ * the CLI reads the files itself, exactly as it does for an older runtime.
132
+ */
133
+ export declare const ROTATED_WINDOW_NOTE_RUNTIME_VERSION = "0.8.121";
127
134
  /**
128
135
  * Rotated siblings of a log, OLDEST FIRST (OSK-16214). The daemon's logger
129
136
  * renames `daemon.app.log` to `daemon.app.1.log`, `.1` to `.2` and so on at
@@ -146,6 +153,31 @@ export declare function readLastLines(filePath: string, maxLines: number): strin
146
153
  * file. A file renamed away between the listing and the read is skipped.
147
154
  */
148
155
  export declare function readLastLinesAcross(files: readonly string[], maxLines: number): string[];
156
+ /**
157
+ * Where a `--rotated --lines N` window began when it left older lines out
158
+ * (OSK-16228). `startLine` is 1-based within `file`; `totalLines` counts the
159
+ * whole set. A copy of `daemon/src/rotated-log-files.ts`, like the helpers above.
160
+ */
161
+ export interface LogWindowCut {
162
+ file: string;
163
+ startLine: number;
164
+ totalLines: number;
165
+ }
166
+ /** Lines in a file, split as `readLastLines` splits them; read in chunks. */
167
+ export declare function countLines(filePath: string): number;
168
+ /**
169
+ * `readLastLinesAcross`, plus where the window began when it cut older lines.
170
+ * Without it, `--rotated --lines 20000 | grep <id>` for an id 90k lines back
171
+ * printed nothing and said nothing, which reads as "never logged". Lines are
172
+ * counted only once the window is full, and only from the file it stopped in
173
+ * back.
174
+ */
175
+ export declare function readLogWindow(files: readonly string[], maxLines: number): {
176
+ lines: string[];
177
+ cut: LogWindowCut | null;
178
+ };
179
+ /** The stderr sentence for a window that left older lines out. */
180
+ export declare function describeLogWindowCut(cut: LogWindowCut, shown: number): string;
149
181
  /** Every byte of `files`, in order, to `out` — streamed, honouring back-pressure. */
150
182
  export declare function streamLogFiles(files: readonly string[], out: NodeJS.WritableStream): Promise<void>;
151
183
  /** `x.y.z` from a version string, or null. A prerelease suffix is ignored. */
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.STDERR_FLAG_DESCRIPTION = exports.ROTATED_LOGS_RUNTIME_VERSION = exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = void 0;
6
+ exports.STDERR_FLAG_DESCRIPTION = exports.ROTATED_WINDOW_NOTE_RUNTIME_VERSION = exports.ROTATED_LOGS_RUNTIME_VERSION = exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = void 0;
7
7
  exports.describeLogAge = describeLogAge;
8
8
  exports.resolveLegacyDaemonLogPath = resolveLegacyDaemonLogPath;
9
9
  exports.resolveDaemonLogsProfile = resolveDaemonLogsProfile;
@@ -15,6 +15,9 @@ exports.rotatedLogSiblings = rotatedLogSiblings;
15
15
  exports.describeRotatedSiblings = describeRotatedSiblings;
16
16
  exports.readLastLines = readLastLines;
17
17
  exports.readLastLinesAcross = readLastLinesAcross;
18
+ exports.countLines = countLines;
19
+ exports.readLogWindow = readLogWindow;
20
+ exports.describeLogWindowCut = describeLogWindowCut;
18
21
  exports.streamLogFiles = streamLogFiles;
19
22
  exports.parseRuntimeVersion = parseRuntimeVersion;
20
23
  exports.runtimeVersionAtLeast = runtimeVersionAtLeast;
@@ -228,6 +231,13 @@ exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = '0.8.119';
228
231
  * that file and its rotated siblings itself.
229
232
  */
230
233
  exports.ROTATED_LOGS_RUNTIME_VERSION = '0.8.120';
234
+ /**
235
+ * The first runtime whose `logs --rotated --lines N` says where a window that
236
+ * cut older lines starts, and whose `logs --path` names the rotated siblings
237
+ * on stderr (OSK-16228). 0.8.120 knows `--rotated` but says neither, so for it
238
+ * the CLI reads the files itself, exactly as it does for an older runtime.
239
+ */
240
+ exports.ROTATED_WINDOW_NOTE_RUNTIME_VERSION = '0.8.121';
231
241
  /**
232
242
  * Rotated siblings of a log, OLDEST FIRST (OSK-16214). The daemon's logger
233
243
  * renames `daemon.app.log` to `daemon.app.1.log`, `.1` to `.2` and so on at
@@ -304,19 +314,90 @@ function readLastLines(filePath, maxLines) {
304
314
  * file. A file renamed away between the listing and the read is skipped.
305
315
  */
306
316
  function readLastLinesAcross(files, maxLines) {
317
+ return readLogWindow(files, maxLines).lines;
318
+ }
319
+ /** Lines in a file, split as `readLastLines` splits them; read in chunks. */
320
+ function countLines(filePath) {
321
+ const descriptor = (0, node_fs_1.openSync)(filePath, 'r');
322
+ try {
323
+ const buffer = Buffer.alloc(64 * 1024);
324
+ let count = 0;
325
+ let last = -1;
326
+ let position = 0;
327
+ for (;;) {
328
+ const read = (0, node_fs_1.readSync)(descriptor, buffer, 0, buffer.length, position);
329
+ if (read === 0)
330
+ break;
331
+ position += read;
332
+ for (let i = 0; i < read; i += 1)
333
+ if (buffer[i] === 0x0a)
334
+ count += 1;
335
+ last = buffer[read - 1];
336
+ }
337
+ return last === -1 || last === 0x0a ? count : count + 1;
338
+ }
339
+ finally {
340
+ (0, node_fs_1.closeSync)(descriptor);
341
+ }
342
+ }
343
+ /**
344
+ * `readLastLinesAcross`, plus where the window began when it cut older lines.
345
+ * Without it, `--rotated --lines 20000 | grep <id>` for an id 90k lines back
346
+ * printed nothing and said nothing, which reads as "never logged". Lines are
347
+ * counted only once the window is full, and only from the file it stopped in
348
+ * back.
349
+ */
350
+ function readLogWindow(files, maxLines) {
307
351
  const collected = [];
308
352
  let remaining = maxLines;
353
+ let boundary = null;
309
354
  for (let i = files.length - 1; i >= 0 && remaining > 0; i -= 1) {
310
355
  try {
311
356
  const lines = readLastLines(files[i], remaining);
312
357
  collected.unshift(lines);
313
358
  remaining -= lines.length;
359
+ if (remaining <= 0)
360
+ boundary = { index: i, taken: lines.length };
314
361
  }
315
362
  catch {
316
363
  // Rotated away mid-read; the rest are still worth showing.
317
364
  }
318
365
  }
319
- return collected.flat();
366
+ const lines = collected.flat();
367
+ if (boundary === null)
368
+ return { lines, cut: null };
369
+ let boundaryLines;
370
+ try {
371
+ boundaryLines = countLines(files[boundary.index]);
372
+ }
373
+ catch {
374
+ return { lines, cut: null };
375
+ }
376
+ let olderLines = 0;
377
+ for (const file of files.slice(0, boundary.index)) {
378
+ try {
379
+ olderLines += countLines(file);
380
+ }
381
+ catch {
382
+ // Rotated away since the read; it is not in the window either way.
383
+ }
384
+ }
385
+ const startLine = Math.max(1, boundaryLines - boundary.taken + 1);
386
+ if (startLine === 1 && olderLines === 0)
387
+ return { lines, cut: null };
388
+ return {
389
+ lines,
390
+ cut: {
391
+ file: files[boundary.index],
392
+ startLine,
393
+ totalLines: olderLines + (startLine - 1) + lines.length,
394
+ },
395
+ };
396
+ }
397
+ /** The stderr sentence for a window that left older lines out. */
398
+ function describeLogWindowCut(cut, shown) {
399
+ return (`Showing the last ${shown} of ${cut.totalLines} lines; the window starts at line ` +
400
+ `${cut.startLine} of ${node_path_1.default.basename(cut.file)}. Drop --lines to read the full history.`);
320
401
  }
321
402
  /** Every byte of `files`, in order, to `out` — streamed, honouring back-pressure. */
322
403
  async function streamLogFiles(files, out) {
@@ -623,30 +704,46 @@ class DaemonLogs extends core_1.Command {
623
704
  const runtimeKnowsRotated = plan.kind === 'forward' &&
624
705
  plan.runtimeVersion !== null &&
625
706
  runtimeVersionAtLeast(plan.runtimeVersion, exports.ROTATED_LOGS_RUNTIME_VERSION) === true;
626
- if (plan.kind === 'forward' && flags.rotated && !runtimeKnowsRotated) {
707
+ // OSK-16228: a `--rotated --lines N` window that cuts older lines, and
708
+ // `--path` with rotated siblings beside it, are said on stderr only from
709
+ // ROTATED_WINDOW_NOTE_RUNTIME_VERSION. Below it, read the files here.
710
+ const runtimeSaysWindow = plan.kind === 'forward' &&
711
+ plan.runtimeVersion !== null &&
712
+ runtimeVersionAtLeast(plan.runtimeVersion, exports.ROTATED_WINDOW_NOTE_RUNTIME_VERSION) === true;
713
+ if (plan.kind === 'forward' && !runtimeSaysWindow && (flags.rotated || flags.path)) {
627
714
  const resolved = await resolveForwardedLogPath(flags);
628
- if (!resolved) {
715
+ if (resolved) {
716
+ plan = {
717
+ kind: 'read',
718
+ path: resolved,
719
+ note: null,
720
+ missingHint: 'The live daemon has not written it yet; `skrr daemon status` shows whether one is running.',
721
+ };
722
+ }
723
+ else if (flags.rotated && !runtimeKnowsRotated) {
629
724
  this.error(`The installed runtime${plan.runtimeVersion ? ` (${plan.runtimeVersion})` : ''} predates ` +
630
725
  `--rotated (${exports.ROTATED_LOGS_RUNTIME_VERSION}) and did not name its log file, so its ` +
631
726
  'rotated files cannot be found. Upgrade it (`skrr daemon update`), or list the ' +
632
727
  'directory `skrr daemon logs --path` prints.', { exit: 1 });
633
728
  }
634
- plan = {
635
- kind: 'read',
636
- path: resolved,
637
- note: null,
638
- missingHint: 'The live daemon has not written it yet; `skrr daemon status` shows whether one is running.',
639
- };
729
+ // Otherwise forward: the runtime still answers, just without the note.
640
730
  }
641
731
  if (plan.kind === 'read') {
642
732
  const logPath = plan.path;
643
733
  const siblings = rotatedLogSiblings(logPath);
644
- // `--path` prints the one path and nothing else, on either stream —
645
- // under --rotated, every file oldest first, the current one last.
734
+ // `--path` prints the one path and nothing else on stdout — under
735
+ // --rotated, every file oldest first, the current one last. Without
736
+ // --rotated, stderr names the siblings a grep of that path would miss
737
+ // (OSK-16228).
646
738
  if (flags.path) {
647
739
  for (const file of flags.rotated ? siblings : [])
648
740
  this.log(file);
649
741
  this.log(logPath);
742
+ if (!flags.rotated) {
743
+ const hint = describeRotatedSiblings(logPath, siblings);
744
+ if (hint)
745
+ this.logToStderr(hint);
746
+ }
650
747
  return;
651
748
  }
652
749
  if (plan.note)
@@ -668,11 +765,16 @@ class DaemonLogs extends core_1.Command {
668
765
  await streamLogFiles([...siblings, logPath], process.stdout);
669
766
  }
670
767
  else {
671
- const lines = flags.rotated
672
- ? readLastLinesAcross([...siblings, logPath], flags.lines ?? 200)
673
- : readDaemonLogTail(logPath, flags.lines ?? 200);
768
+ const window = flags.rotated
769
+ ? readLogWindow([...siblings, logPath], flags.lines ?? 200)
770
+ : null;
771
+ const lines = window ? window.lines : readDaemonLogTail(logPath, flags.lines ?? 200);
674
772
  if (lines.length > 0)
675
773
  this.log(lines.join('\n'));
774
+ // OSK-16228: after the content, so it is the last thing a terminal
775
+ // shows, and on stderr, so a grep over stdout is unchanged.
776
+ if (window?.cut)
777
+ this.logToStderr(describeLogWindowCut(window.cut, lines.length));
676
778
  }
677
779
  if (flags.follow) {
678
780
  let position = (0, node_fs_1.statSync)(logPath).size;
@@ -40,7 +40,17 @@ class TasksRetrySchedule extends base_command_1.BaseCommand {
40
40
  const t = result;
41
41
  const ref = t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : '');
42
42
  const when = flags.at ?? t.scheduledFor ?? '(server default)';
43
- this.log(`Re-armed schedule for task ${t.id ?? t._id ?? taskRef}${ref ? ` (${ref})` : ''} → ${when}`);
43
+ const retry = t.scheduleRetry;
44
+ const subject = `task ${t.id ?? t._id ?? taskRef}${ref ? ` (${ref})` : ''}`;
45
+ if (retry?.changed === false) {
46
+ this.log(`Schedule for ${subject} is already armed for ${when}; nothing changed.`);
47
+ }
48
+ else if (retry?.armedFirstSchedule) {
49
+ this.log(`Armed a first schedule for ${subject} → ${when} (it had none; \`tasks schedule\` is the command for that).`);
50
+ }
51
+ else {
52
+ this.log(`Re-armed schedule for ${subject} → ${when}`);
53
+ }
44
54
  }
45
55
  }
46
56
  exports.default = TasksRetrySchedule;
@@ -132497,5 +132497,5 @@
132497
132497
  ]
132498
132498
  }
132499
132499
  },
132500
- "version": "0.1.105"
132500
+ "version": "0.1.106"
132501
132501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skrr-ai/cli",
3
- "version": "0.1.105",
3
+ "version": "0.1.106",
4
4
  "description": "Command-line interface for skrr",
5
5
  "license": "ISC",
6
6
  "author": "skrr",