codex-cleaner 0.0.1 → 0.0.3
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/dist/cleaner.d.ts +6 -5
- package/dist/cleaner.js +57 -33
- package/dist/cli.js +24 -2
- package/dist/wizard.js +3 -0
- package/package.json +4 -6
package/dist/cleaner.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
3
|
import type { BlockingProcess, CleanerOptions, CompactWhere } from "./types.js";
|
|
4
4
|
type CodexSpawnCommand = {
|
|
5
5
|
args: string[];
|
|
@@ -30,22 +30,23 @@ export declare function compactWhere(args: {
|
|
|
30
30
|
protectRecent: boolean;
|
|
31
31
|
protectedIds: Set<string>;
|
|
32
32
|
}): CompactWhere;
|
|
33
|
-
export declare function collectCompactCandidateStats(db:
|
|
33
|
+
export declare function collectCompactCandidateStats(db: DatabaseSync, args: {
|
|
34
34
|
archivedOnly: boolean;
|
|
35
35
|
cutoffMs: number;
|
|
36
36
|
maxChars: number;
|
|
37
37
|
protectRecent: boolean;
|
|
38
38
|
protectedIds: Set<string>;
|
|
39
39
|
}): Record<string, unknown>;
|
|
40
|
-
export declare function collectStaleArchiveCandidateStats(db:
|
|
40
|
+
export declare function collectStaleArchiveCandidateStats(db: DatabaseSync, codexHome: string, args: {
|
|
41
41
|
cutoffMs: number;
|
|
42
42
|
protectedIds: Set<string>;
|
|
43
43
|
statRollouts?: boolean;
|
|
44
44
|
}): Record<string, unknown>;
|
|
45
|
-
export declare function collectLogCleanupStats(db:
|
|
45
|
+
export declare function collectLogCleanupStats(db: DatabaseSync, options: CleanerOptions): Record<string, unknown>;
|
|
46
46
|
export declare function collectTuiLogCleanupStats(logPath: string, keepMib: number): Record<string, unknown>;
|
|
47
|
-
export declare function collectOrphanRolloutArchiveStats(db:
|
|
47
|
+
export declare function collectOrphanRolloutArchiveStats(db: DatabaseSync, codexHome: string, args: {
|
|
48
48
|
cutoffMs: number;
|
|
49
|
+
protectedIds: Set<string>;
|
|
49
50
|
}): Record<string, unknown>;
|
|
50
51
|
export declare function nextBackupPath(dbPath: string, backupDir: string, now?: Date): string;
|
|
51
52
|
export declare function nextFileBackupPath(filePath: string, backupDir: string, now?: Date): string;
|
package/dist/cleaner.js
CHANGED
|
@@ -2,8 +2,8 @@ import { execFile, spawn, spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { backup as sqliteBackup, DatabaseSync } from "node:sqlite";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
|
-
import Database from "better-sqlite3";
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
8
|
const THREAD_COLUMNS_TO_CAP = ["title", "preview", "first_user_message"];
|
|
9
9
|
const APP_SERVER_REQUEST_TIMEOUT_MS = 60_000;
|
|
@@ -373,7 +373,10 @@ export function buildScanReport(options) {
|
|
|
373
373
|
});
|
|
374
374
|
}
|
|
375
375
|
if (options.archiveOrphanRollouts) {
|
|
376
|
-
report.orphanRolloutArchiveCandidates = collectOrphanRolloutArchiveStats(db, codexHome, {
|
|
376
|
+
report.orphanRolloutArchiveCandidates = collectOrphanRolloutArchiveStats(db, codexHome, {
|
|
377
|
+
cutoffMs,
|
|
378
|
+
protectedIds,
|
|
379
|
+
});
|
|
377
380
|
}
|
|
378
381
|
report.recentSample = queryAll(db, `
|
|
379
382
|
SELECT id, archived, updated_at, updated_at_ms, source, agent_role,
|
|
@@ -438,7 +441,7 @@ export async function compactMetadata(options) {
|
|
|
438
441
|
throw new Error("--apply requires --confirm-lossy-metadata");
|
|
439
442
|
}
|
|
440
443
|
if (options.apply && Number(before.rows) > 0) {
|
|
441
|
-
backupPath = await
|
|
444
|
+
backupPath = await backupOpenSqliteDatabase(db, stateDb, resolveBackupDir(options, codexHome));
|
|
442
445
|
const where = compactWhere({
|
|
443
446
|
archivedOnly: options.archivedOnly,
|
|
444
447
|
cutoffMs,
|
|
@@ -447,9 +450,8 @@ export async function compactMetadata(options) {
|
|
|
447
450
|
protectedIds,
|
|
448
451
|
});
|
|
449
452
|
const assignments = THREAD_COLUMNS_TO_CAP.map((column) => `${column} = CASE WHEN length(${column}) > @maxChars THEN substr(${column}, 1, @maxChars) ELSE ${column} END`).join(", ");
|
|
450
|
-
const update = db
|
|
451
|
-
|
|
452
|
-
changedRows = Number(tx().changes);
|
|
453
|
+
const update = prepareStatement(db, `UPDATE threads SET ${assignments} WHERE ${where.sql}`);
|
|
454
|
+
changedRows = Number(runTransaction(db, () => update.run(where.params).changes));
|
|
453
455
|
}
|
|
454
456
|
const after = collectCompactCandidateStats(db, {
|
|
455
457
|
archivedOnly: options.archivedOnly,
|
|
@@ -592,10 +594,11 @@ export function archiveOrphanRollouts(options) {
|
|
|
592
594
|
const codexHome = resolveCodexHome(options);
|
|
593
595
|
const stateDb = path.join(codexHome, "state_5.sqlite");
|
|
594
596
|
const cutoffMs = recentCutoffMs(options.keepRecentDays);
|
|
597
|
+
const protectedIds = allProtectedIds(loadThreadProtection(codexHome, loadGlobalState(codexHome)));
|
|
595
598
|
const beforeDb = openReadonlyDb(stateDb);
|
|
596
599
|
let beforePlan;
|
|
597
600
|
try {
|
|
598
|
-
beforePlan = buildOrphanRolloutPlan(beforeDb, codexHome, { cutoffMs });
|
|
601
|
+
beforePlan = buildOrphanRolloutPlan(beforeDb, codexHome, { cutoffMs, protectedIds });
|
|
599
602
|
}
|
|
600
603
|
finally {
|
|
601
604
|
beforeDb.close();
|
|
@@ -614,6 +617,7 @@ export function archiveOrphanRollouts(options) {
|
|
|
614
617
|
codexHome,
|
|
615
618
|
policy: {
|
|
616
619
|
keepRecentDays: options.keepRecentDays,
|
|
620
|
+
protectedThreads: protectedIds.size,
|
|
617
621
|
recentCutoffMs: cutoffMs,
|
|
618
622
|
recentCutoffUtc: millisToIso(cutoffMs),
|
|
619
623
|
},
|
|
@@ -645,6 +649,7 @@ export function archiveOrphanRollouts(options) {
|
|
|
645
649
|
codexHome,
|
|
646
650
|
policy: {
|
|
647
651
|
keepRecentDays: options.keepRecentDays,
|
|
652
|
+
protectedThreads: protectedIds.size,
|
|
648
653
|
recentCutoffMs: cutoffMs,
|
|
649
654
|
recentCutoffUtc: millisToIso(cutoffMs),
|
|
650
655
|
},
|
|
@@ -657,7 +662,7 @@ export function archiveOrphanRollouts(options) {
|
|
|
657
662
|
const afterDb = openReadonlyDb(stateDb);
|
|
658
663
|
let afterPlan;
|
|
659
664
|
try {
|
|
660
|
-
afterPlan = buildOrphanRolloutPlan(afterDb, codexHome, { cutoffMs });
|
|
665
|
+
afterPlan = buildOrphanRolloutPlan(afterDb, codexHome, { cutoffMs, protectedIds });
|
|
661
666
|
}
|
|
662
667
|
finally {
|
|
663
668
|
afterDb.close();
|
|
@@ -669,6 +674,7 @@ export function archiveOrphanRollouts(options) {
|
|
|
669
674
|
generatedAt: new Date().toISOString(),
|
|
670
675
|
policy: {
|
|
671
676
|
keepRecentDays: options.keepRecentDays,
|
|
677
|
+
protectedThreads: protectedIds.size,
|
|
672
678
|
recentCutoffMs: cutoffMs,
|
|
673
679
|
recentCutoffUtc: millisToIso(cutoffMs),
|
|
674
680
|
},
|
|
@@ -749,23 +755,20 @@ export async function cleanLogs(options) {
|
|
|
749
755
|
const hasRowChanges = Number(before.cap_rows) > 0 || Number(before.delete_rows) > 0;
|
|
750
756
|
const shouldVacuum = Number(beforeSpace.freelist_count) > 0 || hasRowChanges;
|
|
751
757
|
if (options.apply && shouldVacuum) {
|
|
752
|
-
backupPath = await
|
|
758
|
+
backupPath = await backupOpenSqliteDatabase(db, logsDb, resolveBackupDir(options, codexHome));
|
|
753
759
|
const cutoffSeconds = logCutoffSeconds(options.keepLogDays);
|
|
754
760
|
if (hasRowChanges) {
|
|
755
|
-
|
|
756
|
-
deletedRows = Number(db
|
|
757
|
-
cappedRows = Number(db
|
|
758
|
-
.prepare(`
|
|
761
|
+
runTransaction(db, () => {
|
|
762
|
+
deletedRows = Number(prepareStatement(db, "DELETE FROM logs WHERE ts < @cutoffSeconds").run({ cutoffSeconds }).changes);
|
|
763
|
+
cappedRows = Number(prepareStatement(db, `
|
|
759
764
|
UPDATE logs
|
|
760
765
|
SET estimated_bytes = max(0, estimated_bytes - (length(feedback_log_body) - @maxLogBodyChars)),
|
|
761
766
|
feedback_log_body = substr(feedback_log_body, 1, @maxLogBodyChars)
|
|
762
767
|
WHERE ts >= @cutoffSeconds
|
|
763
768
|
AND feedback_log_body IS NOT NULL
|
|
764
769
|
AND length(feedback_log_body) > @maxLogBodyChars
|
|
765
|
-
`)
|
|
766
|
-
.run({ cutoffSeconds, maxLogBodyChars: options.maxLogBodyChars }).changes);
|
|
770
|
+
`).run({ cutoffSeconds, maxLogBodyChars: options.maxLogBodyChars }).changes);
|
|
767
771
|
});
|
|
768
|
-
tx();
|
|
769
772
|
}
|
|
770
773
|
db.exec("VACUUM");
|
|
771
774
|
queryAll(db, "PRAGMA wal_checkpoint(TRUNCATE)");
|
|
@@ -912,9 +915,7 @@ export async function scheduleBackupPrune(options) {
|
|
|
912
915
|
};
|
|
913
916
|
}
|
|
914
917
|
export function compactWhere(args) {
|
|
915
|
-
const clauses = [
|
|
916
|
-
`(${THREAD_COLUMNS_TO_CAP.map((column) => `length(${column}) > @maxChars`).join(" OR ")})`,
|
|
917
|
-
];
|
|
918
|
+
const clauses = [`(${THREAD_COLUMNS_TO_CAP.map((column) => `length(${column}) > @maxChars`).join(" OR ")})`];
|
|
918
919
|
const params = {
|
|
919
920
|
cutoffMs: args.cutoffMs,
|
|
920
921
|
maxChars: args.maxChars,
|
|
@@ -1185,6 +1186,10 @@ function buildOrphanRolloutPlan(db, codexHome, args) {
|
|
|
1185
1186
|
skipped.push({ ...move, reason: "recent" });
|
|
1186
1187
|
continue;
|
|
1187
1188
|
}
|
|
1189
|
+
if (threadId && args.protectedIds.has(threadId)) {
|
|
1190
|
+
skipped.push({ ...move, reason: "protected" });
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1188
1193
|
if (move.indexed) {
|
|
1189
1194
|
skipped.push({ ...move, reason: "session-indexed" });
|
|
1190
1195
|
continue;
|
|
@@ -1216,6 +1221,7 @@ function buildOrphanRolloutPlan(db, codexHome, args) {
|
|
|
1216
1221
|
unindexed_size_mib: fileMoveListSizeMib(unindexed),
|
|
1217
1222
|
skipped_files: skipped.length,
|
|
1218
1223
|
skipped_recent_files: skipped.filter((move) => move.reason === "recent").length,
|
|
1224
|
+
skipped_protected_files: skipped.filter((move) => move.reason === "protected").length,
|
|
1219
1225
|
skipped_session_indexed_files: skipped.filter((move) => move.reason === "session-indexed").length,
|
|
1220
1226
|
skipped_destination_exists_files: skipped.filter((move) => move.reason === "destination-exists").length,
|
|
1221
1227
|
oldest_candidate_modified_utc: millisToIso(minNumberOrNull(modifiedValues)),
|
|
@@ -1368,15 +1374,18 @@ function backupFileStats(files) {
|
|
|
1368
1374
|
};
|
|
1369
1375
|
}
|
|
1370
1376
|
async function backupSqliteDatabase(dbPath, backupDir) {
|
|
1371
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
1372
|
-
const backupPath = nextBackupPath(dbPath, backupDir);
|
|
1373
1377
|
const db = openWritableDb(dbPath);
|
|
1374
1378
|
try {
|
|
1375
|
-
await db
|
|
1379
|
+
return await backupOpenSqliteDatabase(db, dbPath, backupDir);
|
|
1376
1380
|
}
|
|
1377
1381
|
finally {
|
|
1378
1382
|
db.close();
|
|
1379
1383
|
}
|
|
1384
|
+
}
|
|
1385
|
+
async function backupOpenSqliteDatabase(db, dbPath, backupDir) {
|
|
1386
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
1387
|
+
const backupPath = nextBackupPath(dbPath, backupDir);
|
|
1388
|
+
await sqliteBackup(db, backupPath);
|
|
1380
1389
|
return backupPath;
|
|
1381
1390
|
}
|
|
1382
1391
|
function backupRegularFile(filePath, backupDir) {
|
|
@@ -1393,7 +1402,7 @@ async function vacuumSqliteDatabase(args) {
|
|
|
1393
1402
|
const beforeSpace = collectSqliteSpaceStats(db);
|
|
1394
1403
|
if (args.apply && Number(beforeSpace.freelist_count) > 0) {
|
|
1395
1404
|
if (args.backupBeforeVacuum) {
|
|
1396
|
-
backupPath = await
|
|
1405
|
+
backupPath = await backupOpenSqliteDatabase(db, args.dbPath, args.backupDir);
|
|
1397
1406
|
}
|
|
1398
1407
|
db.exec("VACUUM");
|
|
1399
1408
|
queryAll(db, "PRAGMA wal_checkpoint(TRUNCATE)");
|
|
@@ -1424,10 +1433,7 @@ function nextOrphanRolloutManifestPath(backupDir, now = new Date()) {
|
|
|
1424
1433
|
return nextTimestampedPath(backupDir, "orphan-rollouts", ".manifest.bak", now);
|
|
1425
1434
|
}
|
|
1426
1435
|
function nextTimestampedPath(backupDir, name, suffix, now) {
|
|
1427
|
-
const stamp = now
|
|
1428
|
-
.toISOString()
|
|
1429
|
-
.replace(/[-:]/g, "")
|
|
1430
|
-
.replace(".", "_");
|
|
1436
|
+
const stamp = now.toISOString().replace(/[-:]/g, "").replace(".", "_");
|
|
1431
1437
|
const base = `${name}.${stamp}`;
|
|
1432
1438
|
let candidate = path.join(backupDir, `${base}${suffix}`);
|
|
1433
1439
|
let collision = 2;
|
|
@@ -1604,21 +1610,38 @@ function timestampForName(date) {
|
|
|
1604
1610
|
function openReadonlyDb(file) {
|
|
1605
1611
|
if (!fs.existsSync(file))
|
|
1606
1612
|
throw new Error(`SQLite database not found: ${file}`);
|
|
1607
|
-
return new
|
|
1613
|
+
return new DatabaseSync(file, { readOnly: true });
|
|
1608
1614
|
}
|
|
1609
1615
|
function openWritableDb(file) {
|
|
1610
1616
|
if (!fs.existsSync(file))
|
|
1611
1617
|
throw new Error(`SQLite database not found: ${file}`);
|
|
1612
|
-
const db = new
|
|
1613
|
-
db.
|
|
1614
|
-
db.
|
|
1618
|
+
const db = new DatabaseSync(file);
|
|
1619
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
1620
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
1615
1621
|
return db;
|
|
1616
1622
|
}
|
|
1617
1623
|
function queryAll(db, sql, params) {
|
|
1618
|
-
return db
|
|
1624
|
+
return prepareStatement(db, sql).all(params ?? {});
|
|
1619
1625
|
}
|
|
1620
1626
|
function queryOne(db, sql, params) {
|
|
1621
|
-
return db
|
|
1627
|
+
return prepareStatement(db, sql).get(params ?? {}) ?? {};
|
|
1628
|
+
}
|
|
1629
|
+
function prepareStatement(db, sql) {
|
|
1630
|
+
const statement = db.prepare(sql);
|
|
1631
|
+
statement.setAllowUnknownNamedParameters(true);
|
|
1632
|
+
return statement;
|
|
1633
|
+
}
|
|
1634
|
+
function runTransaction(db, fn) {
|
|
1635
|
+
db.exec("BEGIN IMMEDIATE");
|
|
1636
|
+
try {
|
|
1637
|
+
const result = fn();
|
|
1638
|
+
db.exec("COMMIT");
|
|
1639
|
+
return result;
|
|
1640
|
+
}
|
|
1641
|
+
catch (error) {
|
|
1642
|
+
db.exec("ROLLBACK");
|
|
1643
|
+
throw error;
|
|
1644
|
+
}
|
|
1622
1645
|
}
|
|
1623
1646
|
function tryQueryAll(db, sql) {
|
|
1624
1647
|
try {
|
|
@@ -2227,6 +2250,7 @@ function printOrphanRolloutArchiveCandidate(title, value) {
|
|
|
2227
2250
|
"unindexed_files",
|
|
2228
2251
|
"empty_dir_candidates",
|
|
2229
2252
|
"skipped_recent_files",
|
|
2253
|
+
"skipped_protected_files",
|
|
2230
2254
|
"skipped_session_indexed_files",
|
|
2231
2255
|
"skipped_destination_exists_files",
|
|
2232
2256
|
"oldest_candidate_modified_utc",
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { parseArgs } from "node:util";
|
|
3
|
-
import { buildScanReport, archiveOrphanRollouts, checkpointWal, cleanCodex, compactMetadata, emitReport, pruneBackups, requireStoppedOrReadonlyAllowed, scanBackups, scheduleBackupPrune, } from "./cleaner.js";
|
|
4
|
-
import { runWizard } from "./wizard.js";
|
|
5
4
|
const USAGE = `
|
|
6
5
|
codex-cleaner [command] [options]
|
|
7
6
|
|
|
@@ -131,7 +130,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
131
130
|
pruneLogs: Boolean(parsed.values["prune-logs"]),
|
|
132
131
|
pruneTuiLog: Boolean(parsed.values["prune-tui-log"]),
|
|
133
132
|
};
|
|
133
|
+
const reexecCode = reexecWithSqliteWarningDisabled(argv);
|
|
134
|
+
if (reexecCode != null)
|
|
135
|
+
return reexecCode;
|
|
136
|
+
const { archiveOrphanRollouts, buildScanReport, checkpointWal, cleanCodex, compactMetadata, emitReport, pruneBackups, requireStoppedOrReadonlyAllowed, scanBackups, scheduleBackupPrune, } = await import("./cleaner.js");
|
|
134
137
|
if (!command) {
|
|
138
|
+
const { runWizard } = await import("./wizard.js");
|
|
135
139
|
return runWizard(options);
|
|
136
140
|
}
|
|
137
141
|
if (command !== "backups") {
|
|
@@ -178,6 +182,24 @@ function isMutating(command, options) {
|
|
|
178
182
|
function allowsRunningFileOnlyMutation(command, options) {
|
|
179
183
|
return command === "archive-orphan-rollouts" && options.apply && options.allowRunningOrphanRolloutArchive;
|
|
180
184
|
}
|
|
185
|
+
function reexecWithSqliteWarningDisabled(argv) {
|
|
186
|
+
const alreadyDisabled = process.env.NODE_NO_WARNINGS === "1" ||
|
|
187
|
+
process.execArgv.includes("--no-warnings") ||
|
|
188
|
+
process.execArgv.includes("--disable-warning=ExperimentalWarning");
|
|
189
|
+
if (alreadyDisabled)
|
|
190
|
+
return null;
|
|
191
|
+
if (!process.allowedNodeEnvironmentFlags.has("--disable-warning=ExperimentalWarning"))
|
|
192
|
+
return null;
|
|
193
|
+
if (!process.argv[1])
|
|
194
|
+
return null;
|
|
195
|
+
const result = spawnSync(process.execPath, [...process.execArgv, "--disable-warning=ExperimentalWarning", process.argv[1], ...argv], {
|
|
196
|
+
stdio: "inherit",
|
|
197
|
+
windowsHide: true,
|
|
198
|
+
});
|
|
199
|
+
if (result.error)
|
|
200
|
+
throw result.error;
|
|
201
|
+
return result.status ?? 1;
|
|
202
|
+
}
|
|
181
203
|
function parsePositiveInt(raw, name) {
|
|
182
204
|
const value = Number(raw);
|
|
183
205
|
if (!Number.isInteger(value) || value <= 0) {
|
package/dist/wizard.js
CHANGED
|
@@ -252,6 +252,9 @@ function printDryRunSummary(report) {
|
|
|
252
252
|
if (Number(orphanRollouts.skipped_recent_files) > 0) {
|
|
253
253
|
console.log(pc.yellow(` Skipped ${String(orphanRollouts.skipped_recent_files)} orphan rollouts inside the recent window.`));
|
|
254
254
|
}
|
|
255
|
+
if (Number(orphanRollouts.skipped_protected_files) > 0) {
|
|
256
|
+
console.log(pc.yellow(` Skipped ${String(orphanRollouts.skipped_protected_files)} protected orphan rollouts.`));
|
|
257
|
+
}
|
|
255
258
|
if (Number(orphanRollouts.skipped_session_indexed_files) > 0) {
|
|
256
259
|
console.log(pc.yellow(` Skipped ${String(orphanRollouts.skipped_session_indexed_files)} DB-orphaned rollouts still present in session_index.jsonl.`));
|
|
257
260
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-cleaner",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Guarded cleanup and compaction utility for local Codex state.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"packageManager": "npm@11.
|
|
7
|
+
"packageManager": "npm@11.15.0",
|
|
8
8
|
"bin": {
|
|
9
9
|
"codex-cleaner": "dist/cli.js"
|
|
10
10
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=22.
|
|
16
|
+
"node": ">=22.16"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc -p tsconfig.json",
|
|
@@ -21,16 +21,14 @@
|
|
|
21
21
|
"format": "prettier --check .",
|
|
22
22
|
"lint": "eslint .",
|
|
23
23
|
"prepack": "npm run build",
|
|
24
|
-
"test": "
|
|
24
|
+
"test": "node scripts/run-vitest.js"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@inquirer/prompts": "^8.4.3",
|
|
28
|
-
"better-sqlite3": "^12.10.0",
|
|
29
28
|
"picocolors": "^1.1.1"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
32
31
|
"@eslint/js": "^10.0.1",
|
|
33
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
34
32
|
"@types/node": "^25.9.1",
|
|
35
33
|
"eslint": "^10.4.0",
|
|
36
34
|
"globals": "^17.6.0",
|