@blockrun/franklin 3.15.19 → 3.15.20

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.
@@ -3,17 +3,25 @@ import path from 'node:path';
3
3
  import chalk from 'chalk';
4
4
  import { BLOCKRUN_DIR } from '../config.js';
5
5
  const LOG_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log');
6
+ const ARCHIVE_LOG_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log.1');
6
7
  const LEGACY_LOG_FILE = path.join(BLOCKRUN_DIR, 'runcode-debug.log');
7
- const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB auto-rotate threshold
8
8
  export function logsCommand(options) {
9
9
  if (options.clear) {
10
+ let cleared = false;
10
11
  try {
11
12
  fs.unlinkSync(LOG_FILE);
12
- console.log(chalk.green('Logs cleared.'));
13
+ cleared = true;
13
14
  }
14
- catch {
15
- console.log(chalk.dim('No log file to clear.'));
15
+ catch { /* may not exist */ }
16
+ try {
17
+ fs.unlinkSync(ARCHIVE_LOG_FILE);
18
+ cleared = true;
16
19
  }
20
+ catch { /* may not exist */ }
21
+ if (cleared)
22
+ console.log(chalk.green('Logs cleared.'));
23
+ else
24
+ console.log(chalk.dim('No log file to clear.'));
17
25
  return;
18
26
  }
19
27
  // Migrate legacy log file
@@ -23,23 +31,17 @@ export function logsCommand(options) {
23
31
  }
24
32
  catch { /* best effort */ }
25
33
  }
26
- if (!fs.existsSync(LOG_FILE)) {
34
+ // Logger now self-rotates on write (in src/logger.ts). The previous
35
+ // in-place "slice off the first half" rotation here was destructive
36
+ // — every invocation that crossed 10 MB silently dropped half the
37
+ // history. With self-rotation in place this command no longer needs
38
+ // to mutate the file at all; it just stitches the archive + live
39
+ // log for display.
40
+ if (!fs.existsSync(LOG_FILE) && !fs.existsSync(ARCHIVE_LOG_FILE)) {
27
41
  console.log(chalk.dim('No logs yet. Start franklin with --debug to enable logging:'));
28
42
  console.log(chalk.bold(' franklin start --debug'));
29
43
  return;
30
44
  }
31
- // Auto-rotate: if file is over threshold, keep only last half
32
- try {
33
- const stat = fs.statSync(LOG_FILE);
34
- if (stat.size > MAX_LOG_SIZE) {
35
- const content = fs.readFileSync(LOG_FILE, 'utf-8');
36
- const lines = content.split('\n');
37
- const half = lines.slice(Math.floor(lines.length / 2));
38
- fs.writeFileSync(LOG_FILE, half.join('\n'));
39
- console.log(chalk.dim(`(Rotated log — was ${(stat.size / 1024 / 1024).toFixed(1)}MB)`));
40
- }
41
- }
42
- catch { /* ignore rotation errors */ }
43
45
  const parsed = parseInt(options.lines || '50', 10);
44
46
  const tailLines = isNaN(parsed) ? 50 : Math.max(1, Math.min(10000, parsed));
45
47
  if (options.follow) {
@@ -78,8 +80,15 @@ export function logsCommand(options) {
78
80
  }
79
81
  function printLastLines(n) {
80
82
  try {
81
- const content = fs.readFileSync(LOG_FILE, 'utf-8');
82
- const lines = content.split('\n').filter(Boolean);
83
+ // Logger self-rotates to franklin-debug.log.1 when the live log
84
+ // crosses 10MB. Stitch the archive on first so requests for "last N"
85
+ // can span the rotation boundary — without this, immediately after
86
+ // a rotation `franklin logs --lines 1000` would show only whatever
87
+ // lines have been written since rotation, even though the archive
88
+ // is sitting right next to it.
89
+ const archive = fs.existsSync(ARCHIVE_LOG_FILE) ? fs.readFileSync(ARCHIVE_LOG_FILE, 'utf-8') : '';
90
+ const live = fs.existsSync(LOG_FILE) ? fs.readFileSync(LOG_FILE, 'utf-8') : '';
91
+ const lines = (archive + live).split('\n').filter(Boolean);
83
92
  const start = Math.max(0, lines.length - n);
84
93
  const slice = lines.slice(start);
85
94
  if (start > 0) {
package/dist/logger.js CHANGED
@@ -14,6 +14,20 @@ import fs from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import { BLOCKRUN_DIR } from './config.js';
16
16
  const LOG_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log');
17
+ const ARCHIVE_FILE = path.join(BLOCKRUN_DIR, 'franklin-debug.log.1');
18
+ // Self-rotation threshold. When the live log crosses this size on a
19
+ // write, rename it to franklin-debug.log.1 (overwriting any previous
20
+ // archive) and start fresh. Non-destructive: one full archive of the
21
+ // most recent ROTATE_AT_BYTES is always retained, so users can still
22
+ // read history across the rotation. Earlier behavior (only triggered
23
+ // by `franklin logs`, sliced the file in half in-place) lost history
24
+ // outright and only ran if the user happened to invoke `franklin logs`.
25
+ const ROTATE_AT_BYTES = 10 * 1024 * 1024; // 10 MB
26
+ // Probe every N writes to amortize the stat() — average debug entry is
27
+ // ~80 bytes, so 1000 writes (~80 KB worth) between checks keeps the
28
+ // overhead negligible while still catching a runaway log within seconds.
29
+ const ROTATE_PROBE_EVERY_N_WRITES = 1000;
30
+ let writesSinceRotateProbe = 0;
17
31
  // Strip ANSI escapes + carriage returns so the log stays grep-able.
18
32
  const ANSI_RE = /\x1b\[[0-9;]*m|\x1b\][^\x07]*\x07|\r/g;
19
33
  let debugMode = false;
@@ -36,9 +50,42 @@ function ensureDir() {
36
50
  }
37
51
  catch { /* readonly mount / disk full — keep trying so a remount recovers */ }
38
52
  }
53
+ function maybeRotate() {
54
+ try {
55
+ if (!fs.existsSync(LOG_FILE))
56
+ return;
57
+ const { size } = fs.statSync(LOG_FILE);
58
+ if (size < ROTATE_AT_BYTES)
59
+ return;
60
+ // renameSync overwrites an existing target on POSIX, which is what
61
+ // we want — single archive, always the most recent rotation. On
62
+ // Windows the rename can EEXIST; in that case unlink the archive
63
+ // first and retry. If even that fails, fall through silently rather
64
+ // than leaving the log file in a half-rotated state.
65
+ try {
66
+ fs.renameSync(LOG_FILE, ARCHIVE_FILE);
67
+ }
68
+ catch {
69
+ try {
70
+ fs.unlinkSync(ARCHIVE_FILE);
71
+ }
72
+ catch { /* may not exist */ }
73
+ try {
74
+ fs.renameSync(LOG_FILE, ARCHIVE_FILE);
75
+ }
76
+ catch { /* give up */ }
77
+ }
78
+ }
79
+ catch { /* best effort */ }
80
+ }
39
81
  function writeFile(level, msg) {
40
82
  ensureDir();
41
83
  try {
84
+ writesSinceRotateProbe++;
85
+ if (writesSinceRotateProbe >= ROTATE_PROBE_EVERY_N_WRITES) {
86
+ writesSinceRotateProbe = 0;
87
+ maybeRotate();
88
+ }
42
89
  const clean = msg.replace(ANSI_RE, '');
43
90
  fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] [${level.toUpperCase()}] ${clean}\n`);
44
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.19",
3
+ "version": "3.15.20",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {