ai-chat-cleaner 0.1.4 → 0.1.5
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/cli.mjs +76 -11
- package/package.json +2 -1
package/dist/cli.mjs
CHANGED
|
@@ -2,17 +2,16 @@ import process from "node:process";
|
|
|
2
2
|
import * as p from "@clack/prompts";
|
|
3
3
|
import c from "ansis";
|
|
4
4
|
import { cac } from "cac";
|
|
5
|
-
import readline from "node:readline";
|
|
5
|
+
import readline, { createInterface } from "node:readline";
|
|
6
6
|
import tildify from "tildify";
|
|
7
|
-
import { execFile } from "node:child_process";
|
|
8
7
|
import { existsSync } from "node:fs";
|
|
9
8
|
import { readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
10
|
-
import { promisify } from "node:util";
|
|
11
9
|
import pLimit from "p-limit";
|
|
12
10
|
import { basename, join } from "pathe";
|
|
13
11
|
import { rimraf } from "rimraf";
|
|
14
12
|
import { glob } from "tinyglobby";
|
|
15
13
|
import { homedir } from "node:os";
|
|
14
|
+
import { x } from "tinyexec";
|
|
16
15
|
|
|
17
16
|
//#region src/prompts.ts
|
|
18
17
|
const FIG_CHECK = c.green("◉");
|
|
@@ -243,7 +242,6 @@ function clearScreen() {
|
|
|
243
242
|
|
|
244
243
|
//#endregion
|
|
245
244
|
//#region src/utils.ts
|
|
246
|
-
const exec = promisify(execFile);
|
|
247
245
|
async function readJSON(filepath) {
|
|
248
246
|
if (!existsSync(filepath)) return;
|
|
249
247
|
return parseJSON(await readFile(filepath, "utf-8"));
|
|
@@ -314,7 +312,7 @@ function isUUID(value) {
|
|
|
314
312
|
//#endregion
|
|
315
313
|
//#region package.json
|
|
316
314
|
var name = "ai-chat-cleaner";
|
|
317
|
-
var version = "0.1.
|
|
315
|
+
var version = "0.1.5";
|
|
318
316
|
|
|
319
317
|
//#endregion
|
|
320
318
|
//#region src/constants.ts
|
|
@@ -579,17 +577,63 @@ const SHELL_SNAPSHOTS_PATH = join(AGENTS_CONFIG.codex.path, "shell_snapshots");
|
|
|
579
577
|
|
|
580
578
|
//#endregion
|
|
581
579
|
//#region src/codex/db.ts
|
|
580
|
+
const SQLITE_COLUMN_SEPARATOR = "";
|
|
581
|
+
const THREAD_TITLE_MAX_LENGTH = 240;
|
|
582
|
+
const THREAD_TITLE_ELLIPSIS = "...";
|
|
583
|
+
const THREAD_TITLE_SLICE_LENGTH = THREAD_TITLE_MAX_LENGTH - 3;
|
|
584
|
+
const THREAD_COLUMNS_SQL = `
|
|
585
|
+
SELECT
|
|
586
|
+
id,
|
|
587
|
+
rollout_path,
|
|
588
|
+
created_at,
|
|
589
|
+
updated_at,
|
|
590
|
+
source,
|
|
591
|
+
cwd,
|
|
592
|
+
CASE
|
|
593
|
+
WHEN LENGTH(${normalizeTitleSql("title")}) <= ${THREAD_TITLE_MAX_LENGTH}
|
|
594
|
+
THEN ${normalizeTitleSql("title")}
|
|
595
|
+
ELSE SUBSTR(${normalizeTitleSql("title")}, 1, ${THREAD_TITLE_SLICE_LENGTH}) || '${THREAD_TITLE_ELLIPSIS}'
|
|
596
|
+
END AS title
|
|
597
|
+
FROM threads;
|
|
598
|
+
`.trim();
|
|
582
599
|
async function readSQLite(filepath) {
|
|
583
|
-
const
|
|
584
|
-
"-
|
|
600
|
+
const process = x("sqlite3", [
|
|
601
|
+
"-batch",
|
|
602
|
+
"-noheader",
|
|
603
|
+
"-readonly",
|
|
604
|
+
"-separator",
|
|
605
|
+
SQLITE_COLUMN_SEPARATOR,
|
|
585
606
|
filepath,
|
|
586
|
-
|
|
587
|
-
]);
|
|
588
|
-
|
|
607
|
+
THREAD_COLUMNS_SQL
|
|
608
|
+
]).process;
|
|
609
|
+
if (!process?.stdout) throw new Error("Failed to start sqlite3 process");
|
|
610
|
+
const stderrChunks = [];
|
|
611
|
+
const waitForExit = new Promise((resolve, reject) => {
|
|
612
|
+
process.once("error", reject);
|
|
613
|
+
process.once("close", (code, signal) => {
|
|
614
|
+
if (code === 0) {
|
|
615
|
+
resolve();
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const suffix = stderrChunks.join("").trim();
|
|
619
|
+
const reason = signal ? `signal ${signal}` : `code ${code ?? "unknown"}`;
|
|
620
|
+
reject(/* @__PURE__ */ new Error(suffix ? `sqlite3 exited with ${reason}: ${suffix}` : `sqlite3 exited with ${reason}`));
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
process.stderr?.setEncoding("utf8");
|
|
624
|
+
process.stderr?.on("data", (chunk) => stderrChunks.push(chunk.toString()));
|
|
625
|
+
const rows = [];
|
|
626
|
+
const output = createInterface({ input: process.stdout });
|
|
627
|
+
for await (const line of output) {
|
|
628
|
+
if (!line) continue;
|
|
629
|
+
rows.push(parseThreadRow(line));
|
|
630
|
+
}
|
|
631
|
+
await waitForExit;
|
|
632
|
+
return rows;
|
|
589
633
|
}
|
|
590
634
|
async function writeSQLite(filepath, ids) {
|
|
591
635
|
if (ids.length === 0) return;
|
|
592
|
-
await
|
|
636
|
+
await x("sqlite3", [filepath, `DELETE FROM threads WHERE id IN (${ids.map(quoteSqlString).join(", ")});`], { throwOnError: true });
|
|
593
637
|
}
|
|
594
638
|
async function getDatabasePath(cwd) {
|
|
595
639
|
return (await glob("state_*.sqlite", {
|
|
@@ -604,6 +648,27 @@ function extractVersion(path) {
|
|
|
604
648
|
const version = Number.parseInt(matched[1], 10);
|
|
605
649
|
return Number.isFinite(version) ? version : 0;
|
|
606
650
|
}
|
|
651
|
+
function parseThreadRow(line) {
|
|
652
|
+
const [id, rollout_path, createdAt, updatedAt, source, cwd, title, ...rest] = line.split(SQLITE_COLUMN_SEPARATOR);
|
|
653
|
+
if (rest.length > 0) throw new Error(`Unexpected sqlite3 row format: ${line}`);
|
|
654
|
+
return {
|
|
655
|
+
id,
|
|
656
|
+
rollout_path,
|
|
657
|
+
created_at: parseInteger(createdAt, "created_at"),
|
|
658
|
+
updated_at: parseInteger(updatedAt, "updated_at"),
|
|
659
|
+
source,
|
|
660
|
+
cwd,
|
|
661
|
+
title
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function parseInteger(value, field) {
|
|
665
|
+
const parsed = Number.parseInt(value, 10);
|
|
666
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
667
|
+
throw new Error(`Invalid ${field} value: ${value}`);
|
|
668
|
+
}
|
|
669
|
+
function normalizeTitleSql(field) {
|
|
670
|
+
return `REPLACE(REPLACE(REPLACE(${field}, CHAR(31), ' '), CHAR(13), ' '), CHAR(10), ' ')`;
|
|
671
|
+
}
|
|
607
672
|
|
|
608
673
|
//#endregion
|
|
609
674
|
//#region src/codex/delete.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-chat-cleaner",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.5",
|
|
5
5
|
"description": "Clean and remove AI chat with an interactive terminal UI.",
|
|
6
6
|
"author": "jinghaihan",
|
|
7
7
|
"license": "MIT",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"pathe": "^2.0.3",
|
|
43
43
|
"rimraf": "^6.1.3",
|
|
44
44
|
"tildify": "^3.0.0",
|
|
45
|
+
"tinyexec": "^1.0.4",
|
|
45
46
|
"tinyglobby": "^0.2.15"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|