@crewx/cli 0.9.0-rc.139 → 0.9.0-rc.140

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.
@@ -7,5 +7,6 @@
7
7
  * crewx db push --dry-run Show preview only, no changes
8
8
  */
9
9
  export declare const TASK_LOG_MIGRATION_HELP = "Usage:\n crewx db migrate-task-logs --dry-run [--db PATH]\n crewx db migrate-task-logs --apply [--db PATH] [--batch-tasks N]\n crewx db migrate-task-logs --verify [--db PATH]\n\nModes:\n --dry-run Read and validate legacy blobs without writing or creating a backup.\n --apply Create a consistent SQLite backup, then migrate one task per transaction.\n --verify Check source invariants, event sequences, counts, and orphan rows.\n\nApply policy:\n Only blob tasks with status success, failed, or completed are migrated.\n pending, running, paused, and unknown statuses are deferred for a later run.\n There is no --force or --include-active option. Apply requires the estimated\n database/event growth plus a 10 GiB free-space reserve.\n\nExit codes:\n 0 Completed with no malformed rows or invariant failures.\n 1 Safe tasks completed but a row/invariant failed, or apply was blocked.\n 2 Invalid command-line arguments.\n\nThe command never runs automatically at server startup and never runs VACUUM.";
10
+ export declare const REQUEST_LOG_MIGRATION_HELP = "Usage:\n crewx db migrate-request-logs [--batch N] [--dry-run]\n\nMoves the request log rows still stored in crewx.db into logs.db, the file next\nto it. Each distinct request/response body is stored once in logs.db, keyed by\nits SHA-256; a row points to its bodies by hash, so nothing is dropped.\n\nOptions:\n --batch N Rows per batch (default 250; a batch is also capped at about 8 MB).\n --dry-run Count rows and distinct bodies without writing anything (logs.db is not created).\n\nSafe while crewx is running: each batch is committed to logs.db, verified there,\nand only then deleted from crewx.db in one short transaction. Interrupt it and run\nit again at any time. The database is chosen like everywhere else (CREWX_DB, else\n~/.crewx/crewx.db). No VACUUM and no DROP: crewx.db keeps its size until a later compaction.\n\nExit codes:\n 0 Every row is in logs.db and none is left in crewx.db (or the dry run finished).\n 1 Rows are still in crewx.db, or an error stopped the move (progress is kept).\n 2 Invalid command-line arguments.";
10
11
  export declare const USAGE_BACKFILL_HELP = "Usage:\n crewx db backfill-usage [--dry-run|--apply|--verify] [--db PATH] [--backup PATH]\n crewx db backfill-usage --restore SNAPSHOT [--db PATH]\n\nModes:\n --dry-run Show provider/reason recovery plans without changing the database (default).\n --apply Create a row-level undo snapshot, then account one task per transaction.\n --verify Recheck remaining targets and usageAccounting markers without changing the database.\n --restore Restore rows from SNAPSHOT when their current values still match the snapshot after-state.\n\nApply policy:\n Only success, failed, and completed tasks with zero recorded usage are considered.\n Workflow/MCP anchors and plugin/mock rows are excluded. Rows completed within 24 hours\n are not marked unavailable when their logs do not contain usage.\n\nExit codes:\n 0 Completed successfully.\n 1 Apply was blocked or one or more task updates failed.\n 2 Invalid command-line arguments.";
11
12
  export declare function handleDb(args: string[]): Promise<void>;
@@ -11,7 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  return (mod && mod.__esModule) ? mod : { "default": mod };
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.USAGE_BACKFILL_HELP = exports.TASK_LOG_MIGRATION_HELP = void 0;
14
+ exports.USAGE_BACKFILL_HELP = exports.REQUEST_LOG_MIGRATION_HELP = exports.TASK_LOG_MIGRATION_HELP = void 0;
15
15
  exports.handleDb = handleDb;
16
16
  const path_1 = __importDefault(require("path"));
17
17
  const os_1 = __importDefault(require("os"));
@@ -20,6 +20,7 @@ const repository_1 = require("@crewx/sdk/repository");
20
20
  const repository_2 = require("@crewx/sdk/repository");
21
21
  const repository_3 = require("@crewx/sdk/repository");
22
22
  const repository_4 = require("@crewx/sdk/repository");
23
+ const repository_5 = require("@crewx/sdk/repository");
23
24
  exports.TASK_LOG_MIGRATION_HELP = `Usage:
24
25
  crewx db migrate-task-logs --dry-run [--db PATH]
25
26
  crewx db migrate-task-logs --apply [--db PATH] [--batch-tasks N]
@@ -42,6 +43,26 @@ Exit codes:
42
43
  2 Invalid command-line arguments.
43
44
 
44
45
  The command never runs automatically at server startup and never runs VACUUM.`;
46
+ exports.REQUEST_LOG_MIGRATION_HELP = `Usage:
47
+ crewx db migrate-request-logs [--batch N] [--dry-run]
48
+
49
+ Moves the request log rows still stored in crewx.db into logs.db, the file next
50
+ to it. Each distinct request/response body is stored once in logs.db, keyed by
51
+ its SHA-256; a row points to its bodies by hash, so nothing is dropped.
52
+
53
+ Options:
54
+ --batch N Rows per batch (default 250; a batch is also capped at about 8 MB).
55
+ --dry-run Count rows and distinct bodies without writing anything (logs.db is not created).
56
+
57
+ Safe while crewx is running: each batch is committed to logs.db, verified there,
58
+ and only then deleted from crewx.db in one short transaction. Interrupt it and run
59
+ it again at any time. The database is chosen like everywhere else (CREWX_DB, else
60
+ ~/.crewx/crewx.db). No VACUUM and no DROP: crewx.db keeps its size until a later compaction.
61
+
62
+ Exit codes:
63
+ 0 Every row is in logs.db and none is left in crewx.db (or the dry run finished).
64
+ 1 Rows are still in crewx.db, or an error stopped the move (progress is kept).
65
+ 2 Invalid command-line arguments.`;
45
66
  exports.USAGE_BACKFILL_HELP = `Usage:
46
67
  crewx db backfill-usage [--dry-run|--apply|--verify] [--db PATH] [--backup PATH]
47
68
  crewx db backfill-usage --restore SNAPSHOT [--db PATH]
@@ -122,6 +143,10 @@ async function handleDb(args) {
122
143
  await handleTaskLogMigration(args.slice(1));
123
144
  return;
124
145
  }
146
+ if (subcommand === 'migrate-request-logs') {
147
+ await handleRequestLogMigration(args.slice(1));
148
+ return;
149
+ }
125
150
  if (subcommand === 'backfill-usage') {
126
151
  await handleUsageBackfill(args.slice(1));
127
152
  return;
@@ -129,6 +154,7 @@ async function handleDb(args) {
129
154
  if (subcommand === '--help' || subcommand === '-h') {
130
155
  console.log('Usage: crewx db push [--force] [--dry-run]');
131
156
  console.log(exports.TASK_LOG_MIGRATION_HELP);
157
+ console.log(exports.REQUEST_LOG_MIGRATION_HELP);
132
158
  return;
133
159
  }
134
160
  if (!subcommand || subcommand === 'push') {
@@ -292,6 +318,132 @@ async function handleTaskLogMigration(args) {
292
318
  process.exitCode = 1;
293
319
  }
294
320
  }
321
+ class RequestLogMigrationUsageError extends Error {
322
+ }
323
+ function parseRequestLogMigrationArgs(args) {
324
+ let batchRows;
325
+ let dryRun = false;
326
+ const parseBatch = (value) => {
327
+ const parsed = Number(value);
328
+ if (!value || !Number.isSafeInteger(parsed) || parsed < 1) {
329
+ throw new RequestLogMigrationUsageError('--batch must be a positive integer.');
330
+ }
331
+ return parsed;
332
+ };
333
+ for (let index = 0; index < args.length; index += 1) {
334
+ const arg = args[index];
335
+ if (arg === '--help' || arg === '-h')
336
+ throw new RequestLogMigrationUsageError(exports.REQUEST_LOG_MIGRATION_HELP);
337
+ if (arg === '--dry-run') {
338
+ dryRun = true;
339
+ continue;
340
+ }
341
+ if (arg === '--batch') {
342
+ batchRows = parseBatch(args[index + 1]);
343
+ index += 1;
344
+ continue;
345
+ }
346
+ if (arg.startsWith('--batch=')) {
347
+ batchRows = parseBatch(arg.slice('--batch='.length));
348
+ continue;
349
+ }
350
+ throw new RequestLogMigrationUsageError(`Unknown option: ${arg}`);
351
+ }
352
+ return { batchRows, dryRun };
353
+ }
354
+ function formatBytes(bytes) {
355
+ if (bytes < 1024)
356
+ return `${bytes} B`;
357
+ const units = ['KiB', 'MiB', 'GiB', 'TiB'];
358
+ let value = bytes;
359
+ let unit = -1;
360
+ while (value >= 1024 && unit < units.length - 1) {
361
+ value /= 1024;
362
+ unit += 1;
363
+ }
364
+ return `${value.toFixed(value >= 100 ? 0 : 1)} ${units[unit]} (${bytes} bytes)`;
365
+ }
366
+ function percentile(values, p) {
367
+ const sorted = [...values].sort((a, b) => a - b);
368
+ return sorted[Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length))];
369
+ }
370
+ function formatSeconds(ms) {
371
+ return `${(ms / 1000).toFixed(1)}s`;
372
+ }
373
+ const REQUEST_LOG_PROGRESS_INTERVAL_MS = 10_000;
374
+ function printRequestLogSummary(result, status) {
375
+ if (result.dryRun) {
376
+ const scan = result.scan;
377
+ console.log('[crewx] Dry run finished — nothing was written.');
378
+ console.log(` Rows scanned: ${scan?.rows ?? 0}`);
379
+ console.log(` Bodies: ${scan?.bodies ?? 0} (${formatBytes(scan?.bodyBytes ?? 0)})`);
380
+ console.log(` Distinct bodies: ${scan?.distinctBodies ?? 0} (${formatBytes(scan?.distinctBodyBytes ?? 0)}) — what logs.db would store`);
381
+ console.log(` Elapsed: ${formatSeconds(result.elapsedMs)}`);
382
+ return;
383
+ }
384
+ console.log('[crewx] Request-log move finished.');
385
+ console.log(` Moved rows: ${result.movedRows}`);
386
+ console.log(` Remaining rows in crewx.db: ${status.crewxRows}`);
387
+ console.log(` logs.db request_logs: ${status.logsRows ?? 0}`);
388
+ console.log(` logs.db request_log_bodies: ${status.logsBodies ?? 0}`);
389
+ console.log(` logs.db size: ${formatBytes(status.logsFileBytes)} (+ wal ${formatBytes(status.logsWalBytes)})`);
390
+ console.log(` crewx.db freelist pages: ${status.crewxFreelistPages}`);
391
+ console.log(` Batches: ${result.batches}, bodies added: ${result.bodiesAdded}`);
392
+ if (result.deleteTxMs.length > 0) {
393
+ console.log(` crewx.db delete transaction per batch: p50 ${percentile(result.deleteTxMs, 50).toFixed(1)} ms, p90 ${percentile(result.deleteTxMs, 90).toFixed(1)} ms, max ${result.maxDeleteTxMs.toFixed(1)} ms`);
394
+ console.log(` Largest crewx.db -wal seen after a batch: ${formatBytes(result.maxWalBytes)}`);
395
+ }
396
+ console.log(` Elapsed: ${formatSeconds(result.elapsedMs)}`);
397
+ if (status.crewxRows > 0) {
398
+ console.log(` ${status.crewxRows} row(s) are still in crewx.db (new rows arrived or the run stopped early). Run it again.`);
399
+ }
400
+ }
401
+ async function handleRequestLogMigration(args) {
402
+ let parsed;
403
+ try {
404
+ parsed = parseRequestLogMigrationArgs(args);
405
+ }
406
+ catch (error) {
407
+ const message = error instanceof Error ? error.message : String(error);
408
+ if (message === exports.REQUEST_LOG_MIGRATION_HELP) {
409
+ console.log(message);
410
+ }
411
+ else {
412
+ console.error(message);
413
+ console.error(exports.REQUEST_LOG_MIGRATION_HELP);
414
+ process.exitCode = 2;
415
+ }
416
+ return;
417
+ }
418
+ try {
419
+ const before = (0, repository_4.inspectRequestLogsMove)();
420
+ console.log(`[crewx] Request logs → logs.db${parsed.dryRun ? ' (dry run)' : ''}`);
421
+ console.log(` crewx.db: ${before.crewxDbPath}`);
422
+ console.log(` logs.db: ${before.logsDbPath}`);
423
+ console.log(` Rows to move: ${before.crewxRows}`);
424
+ let lastPrint = Date.now();
425
+ const onProgress = (progress) => {
426
+ const now = Date.now();
427
+ if (now - lastPrint < REQUEST_LOG_PROGRESS_INTERVAL_MS)
428
+ return;
429
+ lastPrint = now;
430
+ const done = parsed.dryRun ? progress.scannedRows : progress.movedRows;
431
+ const remaining = Math.max(0, before.crewxRows - done);
432
+ console.log(` ${parsed.dryRun ? 'scanned' : 'moved'} ${done} | remaining ~${remaining} | elapsed ${formatSeconds(progress.elapsedMs)}`);
433
+ };
434
+ const result = (0, repository_4.moveRequestLogsToLogsDb)({
435
+ batchRows: parsed.batchRows,
436
+ dryRun: parsed.dryRun,
437
+ onProgress,
438
+ });
439
+ printRequestLogSummary(result, (0, repository_4.inspectRequestLogsMove)());
440
+ process.exitCode = parsed.dryRun || result.remainingRows === 0 ? 0 : 1;
441
+ }
442
+ catch (error) {
443
+ console.error(error instanceof Error ? error.message : String(error));
444
+ process.exitCode = 1;
445
+ }
446
+ }
295
447
  function parseUsageBackfillArgs(args) {
296
448
  let mode;
297
449
  let dbPath;
@@ -447,7 +599,7 @@ async function handleUsageBackfill(args) {
447
599
  return;
448
600
  }
449
601
  try {
450
- const report = await (0, repository_4.runUsageBackfill)(parsed);
602
+ const report = await (0, repository_5.runUsageBackfill)(parsed);
451
603
  console.log(formatUsageBackfillReport(report));
452
604
  process.exitCode = report.ok ? 0 : 1;
453
605
  }
package/dist/main.js CHANGED
@@ -339,6 +339,9 @@ Database:
339
339
  --dry-run Validate and estimate without writing
340
340
  --apply Consistent backup + resumable task-by-task apply
341
341
  --verify Check event/source invariants and parity
342
+ db migrate-request-logs Move request logs from crewx.db into logs.db (bodies stored once)
343
+ --batch N Rows per batch (default 250)
344
+ --dry-run Count rows and distinct bodies without writing
342
345
 
343
346
  Desktop Shortcut (Windows only):
344
347
  shortcut status [--json] Show current shortcut status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/cli",
3
- "version": "0.9.0-rc.139",
3
+ "version": "0.9.0-rc.140",
4
4
  "license": "UNLICENSED",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -25,20 +25,20 @@
25
25
  "better-sqlite3": "*",
26
26
  "dotenv": "17.2.3",
27
27
  "isomorphic-git": "1.37.1",
28
- "@crewx/sdk": "0.9.0-rc.139",
29
- "@crewx/knowledge-core": "0.1.17-rc.149",
30
- "@crewx/memory": "0.1.23-rc.156",
31
- "@crewx/search": "0.1.10-rc.135",
32
- "@crewx/doc": "0.1.9-rc.132",
33
- "@crewx/wbs": "0.1.10-rc.165",
34
- "@crewx/workflow": "0.3.22-rc.185",
35
- "@crewx/skill": "0.1.20-rc.33",
36
- "@crewx/cron": "0.1.10-rc.174",
37
- "@crewx/chromex": "0.1.0-rc.172",
38
- "@crewx/app": "0.1.0-rc.20",
39
- "@crewx/shared": "0.0.6-rc.54",
40
- "@crewx/notify": "0.1.0-rc.110",
41
- "@crewx/wi": "0.1.10-rc.159"
28
+ "@crewx/sdk": "0.9.0-rc.140",
29
+ "@crewx/search": "0.1.10-rc.136",
30
+ "@crewx/knowledge-core": "0.1.17-rc.150",
31
+ "@crewx/memory": "0.1.23-rc.157",
32
+ "@crewx/doc": "0.1.9-rc.133",
33
+ "@crewx/wbs": "0.1.10-rc.166",
34
+ "@crewx/cron": "0.1.10-rc.175",
35
+ "@crewx/skill": "0.1.20-rc.34",
36
+ "@crewx/workflow": "0.3.22-rc.186",
37
+ "@crewx/chromex": "0.1.0-rc.173",
38
+ "@crewx/notify": "0.1.0-rc.111",
39
+ "@crewx/shared": "0.0.6-rc.55",
40
+ "@crewx/wi": "0.1.10-rc.160",
41
+ "@crewx/app": "0.1.0-rc.21"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/better-sqlite3": "*",