@memrosetta/cli 0.5.0 → 0.5.2
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/chunk-47SU2YUJ.js +64 -0
- package/dist/chunk-4LNXT25H.js +891 -0
- package/dist/chunk-C4ANKSCI.js +151 -0
- package/dist/chunk-CEHRM6IW.js +151 -0
- package/dist/chunk-G2W4YK2T.js +56 -0
- package/dist/chunk-GGXC7TAJ.js +139 -0
- package/dist/chunk-GRNZVSAF.js +56 -0
- package/dist/chunk-GZINXXM4.js +139 -0
- package/dist/chunk-RZFCVYTK.js +71 -0
- package/dist/chunk-US6CEDMU.js +66 -0
- package/dist/chunk-VMGX5FCY.js +64 -0
- package/dist/chunk-WYHEAKPC.js +71 -0
- package/dist/clear-32Y3U2WR.js +39 -0
- package/dist/clear-AFEJPCDA.js +39 -0
- package/dist/compress-CL5D4VVJ.js +33 -0
- package/dist/compress-UUEO7WCU.js +33 -0
- package/dist/count-U2ML5ZON.js +24 -0
- package/dist/count-VVOGYSM7.js +24 -0
- package/dist/duplicates-CEJ7WSGW.js +149 -0
- package/dist/duplicates-IBUS7CJS.js +149 -0
- package/dist/enforce-T7AS4PVD.js +381 -0
- package/dist/enforce-TC5SDPEZ.js +381 -0
- package/dist/feedback-3PJTTEOD.js +51 -0
- package/dist/feedback-IB7BHIRP.js +51 -0
- package/dist/get-TQ2U7HCD.js +30 -0
- package/dist/get-WPZIHQKW.js +30 -0
- package/dist/hooks/enforce-codex.js +88 -0
- package/dist/hooks/on-prompt.js +3 -3
- package/dist/hooks/on-stop.js +3 -3
- package/dist/index.js +30 -20
- package/dist/ingest-37UXPVT5.js +97 -0
- package/dist/ingest-TPQRH34A.js +97 -0
- package/dist/init-6YQL3RCQ.js +210 -0
- package/dist/init-ISP73KEC.js +210 -0
- package/dist/init-LHXRCCLX.js +210 -0
- package/dist/invalidate-ER2TFFWK.js +40 -0
- package/dist/invalidate-PVHUGAJ6.js +40 -0
- package/dist/maintain-NICAXFK6.js +37 -0
- package/dist/maintain-Q553GBSF.js +37 -0
- package/dist/migrate-CZL3YNQK.js +255 -0
- package/dist/migrate-FI26FSBP.js +255 -0
- package/dist/relate-5TN2WEG3.js +57 -0
- package/dist/relate-KLBMYWB3.js +57 -0
- package/dist/reset-IPOAKTJM.js +132 -0
- package/dist/reset-P62B444X.js +132 -0
- package/dist/search-AYZBKRXF.js +48 -0
- package/dist/search-JQ3MLRKS.js +48 -0
- package/dist/status-FWHUUZ4R.js +184 -0
- package/dist/status-JF2V7ZBX.js +184 -0
- package/dist/status-UV66PWUD.js +184 -0
- package/dist/store-AAJCT3PX.js +101 -0
- package/dist/store-OVDS57U5.js +101 -0
- package/dist/sync-56KJTKE7.js +542 -0
- package/dist/sync-BCKBYRXY.js +542 -0
- package/dist/working-memory-CJARSGEK.js +53 -0
- package/dist/working-memory-Z3RUGSTQ.js +53 -0
- package/package.json +6 -5
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/hooks/config.ts
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir, userInfo } from "os";
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync } from "fs";
|
|
5
|
+
var MEMROSETTA_DIR = join(homedir(), ".memrosetta");
|
|
6
|
+
var CONFIG_PATH = join(MEMROSETTA_DIR, "config.json");
|
|
7
|
+
var DB_PATH = join(MEMROSETTA_DIR, "memories.db");
|
|
8
|
+
var DEFAULT_CONFIG = {
|
|
9
|
+
dbPath: DB_PATH,
|
|
10
|
+
enableEmbeddings: true,
|
|
11
|
+
maxRecallResults: 5,
|
|
12
|
+
minQueryLength: 5,
|
|
13
|
+
maxContextChars: 2e3
|
|
14
|
+
};
|
|
15
|
+
function getDefaultDbPath() {
|
|
16
|
+
return DB_PATH;
|
|
17
|
+
}
|
|
18
|
+
function ensureDir() {
|
|
19
|
+
if (!existsSync(MEMROSETTA_DIR)) {
|
|
20
|
+
mkdirSync(MEMROSETTA_DIR, { recursive: true, mode: 448 });
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
chmodSync(MEMROSETTA_DIR, 448);
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function getConfig() {
|
|
28
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
29
|
+
return DEFAULT_CONFIG;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
return {
|
|
35
|
+
...DEFAULT_CONFIG,
|
|
36
|
+
...parsed
|
|
37
|
+
};
|
|
38
|
+
} catch {
|
|
39
|
+
return DEFAULT_CONFIG;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeConfig(config) {
|
|
43
|
+
ensureDir();
|
|
44
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
45
|
+
try {
|
|
46
|
+
chmodSync(CONFIG_PATH, 384);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function resolveCanonicalUserId(explicit) {
|
|
51
|
+
if (explicit && explicit.trim().length > 0) {
|
|
52
|
+
return explicit.trim();
|
|
53
|
+
}
|
|
54
|
+
const config = getConfig();
|
|
55
|
+
if (config.syncUserId && config.syncUserId.trim().length > 0) {
|
|
56
|
+
return config.syncUserId.trim();
|
|
57
|
+
}
|
|
58
|
+
return userInfo().username;
|
|
59
|
+
}
|
|
60
|
+
function getDefaultUserId() {
|
|
61
|
+
return resolveCanonicalUserId();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
getDefaultDbPath,
|
|
66
|
+
ensureDir,
|
|
67
|
+
getConfig,
|
|
68
|
+
writeConfig,
|
|
69
|
+
resolveCanonicalUserId,
|
|
70
|
+
getDefaultUserId
|
|
71
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/parser.ts
|
|
2
|
+
var COMMANDS = /* @__PURE__ */ new Set([
|
|
3
|
+
"store",
|
|
4
|
+
"search",
|
|
5
|
+
"ingest",
|
|
6
|
+
"get",
|
|
7
|
+
"count",
|
|
8
|
+
"clear",
|
|
9
|
+
"relate",
|
|
10
|
+
"invalidate",
|
|
11
|
+
"working-memory",
|
|
12
|
+
"maintain",
|
|
13
|
+
"compress",
|
|
14
|
+
"status",
|
|
15
|
+
"init",
|
|
16
|
+
"reset",
|
|
17
|
+
"update",
|
|
18
|
+
"feedback",
|
|
19
|
+
"sync",
|
|
20
|
+
"enforce",
|
|
21
|
+
"migrate",
|
|
22
|
+
"duplicates"
|
|
23
|
+
]);
|
|
24
|
+
function findFlag(args, flag) {
|
|
25
|
+
return args.includes(flag);
|
|
26
|
+
}
|
|
27
|
+
function findOption(args, flag) {
|
|
28
|
+
const idx = args.indexOf(flag);
|
|
29
|
+
if (idx === -1 || idx + 1 >= args.length) return void 0;
|
|
30
|
+
return args[idx + 1];
|
|
31
|
+
}
|
|
32
|
+
function parseGlobalArgs(args) {
|
|
33
|
+
const command = args.find((a) => !a.startsWith("-") && COMMANDS.has(a));
|
|
34
|
+
const db = findOption(args, "--db");
|
|
35
|
+
const formatRaw = findOption(args, "--format");
|
|
36
|
+
const format = formatRaw === "json" ? "json" : "text";
|
|
37
|
+
const noEmbeddings = findFlag(args, "--no-embeddings");
|
|
38
|
+
const help = findFlag(args, "--help") || findFlag(args, "-h");
|
|
39
|
+
const version = findFlag(args, "--version") || findFlag(args, "-v");
|
|
40
|
+
const rest = args.filter((a) => a !== command);
|
|
41
|
+
return {
|
|
42
|
+
command,
|
|
43
|
+
global: { db, format, noEmbeddings, help, version },
|
|
44
|
+
rest
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function requireOption(args, flag, name) {
|
|
48
|
+
const value = findOption(args, flag);
|
|
49
|
+
if (!value) {
|
|
50
|
+
throw new Error(`Missing required option: ${name} (${flag})`);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function optionalOption(args, flag) {
|
|
55
|
+
return findOption(args, flag);
|
|
56
|
+
}
|
|
57
|
+
function hasFlag(args, flag) {
|
|
58
|
+
return findFlag(args, flag);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
parseGlobalArgs,
|
|
63
|
+
requireOption,
|
|
64
|
+
optionalOption,
|
|
65
|
+
hasFlag
|
|
66
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConfig
|
|
3
|
+
} from "./chunk-RZFCVYTK.js";
|
|
4
|
+
|
|
5
|
+
// src/engine.ts
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { mkdirSync, existsSync } from "fs";
|
|
9
|
+
var DEFAULT_DB = join(homedir(), ".memrosetta", "memories.db");
|
|
10
|
+
var cachedEngine = null;
|
|
11
|
+
var cachedDbPath = null;
|
|
12
|
+
async function createEngineInstance(options) {
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
const dbPath = options.db ?? config.dbPath ?? DEFAULT_DB;
|
|
15
|
+
const dir = dirname(dbPath);
|
|
16
|
+
if (!existsSync(dir)) {
|
|
17
|
+
mkdirSync(dir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
const { SqliteMemoryEngine } = await import("@memrosetta/core");
|
|
20
|
+
let embedder;
|
|
21
|
+
if (!options.noEmbeddings && config.enableEmbeddings !== false) {
|
|
22
|
+
try {
|
|
23
|
+
const { HuggingFaceEmbedder } = await import("@memrosetta/embeddings");
|
|
24
|
+
const preset = options.embeddingPreset ?? config.embeddingPreset ?? "en";
|
|
25
|
+
embedder = new HuggingFaceEmbedder({ preset });
|
|
26
|
+
await embedder.initialize();
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const engine = new SqliteMemoryEngine({ dbPath, embedder });
|
|
31
|
+
await engine.initialize();
|
|
32
|
+
return engine;
|
|
33
|
+
}
|
|
34
|
+
async function getEngine(options) {
|
|
35
|
+
const config = getConfig();
|
|
36
|
+
const dbPath = options.db ?? config.dbPath ?? DEFAULT_DB;
|
|
37
|
+
if (cachedEngine && cachedDbPath === dbPath) {
|
|
38
|
+
return cachedEngine;
|
|
39
|
+
}
|
|
40
|
+
cachedEngine = await createEngineInstance(options);
|
|
41
|
+
cachedDbPath = dbPath;
|
|
42
|
+
return cachedEngine;
|
|
43
|
+
}
|
|
44
|
+
function getDefaultDbPath() {
|
|
45
|
+
return DEFAULT_DB;
|
|
46
|
+
}
|
|
47
|
+
function resolveDbPath(dbOverride) {
|
|
48
|
+
const config = getConfig();
|
|
49
|
+
return dbOverride ?? config.dbPath ?? DEFAULT_DB;
|
|
50
|
+
}
|
|
51
|
+
async function closeEngine() {
|
|
52
|
+
if (cachedEngine) {
|
|
53
|
+
await cachedEngine.close();
|
|
54
|
+
cachedEngine = null;
|
|
55
|
+
cachedDbPath = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
getEngine,
|
|
61
|
+
getDefaultDbPath,
|
|
62
|
+
resolveDbPath,
|
|
63
|
+
closeEngine
|
|
64
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/hooks/config.ts
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir, userInfo } from "os";
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync } from "fs";
|
|
5
|
+
var MEMROSETTA_DIR = join(homedir(), ".memrosetta");
|
|
6
|
+
var CONFIG_PATH = join(MEMROSETTA_DIR, "config.json");
|
|
7
|
+
var DB_PATH = join(MEMROSETTA_DIR, "memories.db");
|
|
8
|
+
var DEFAULT_CONFIG = {
|
|
9
|
+
dbPath: DB_PATH,
|
|
10
|
+
enableEmbeddings: true,
|
|
11
|
+
maxRecallResults: 5,
|
|
12
|
+
minQueryLength: 5,
|
|
13
|
+
maxContextChars: 2e3
|
|
14
|
+
};
|
|
15
|
+
function getDefaultDbPath() {
|
|
16
|
+
return DB_PATH;
|
|
17
|
+
}
|
|
18
|
+
function ensureDir() {
|
|
19
|
+
if (!existsSync(MEMROSETTA_DIR)) {
|
|
20
|
+
mkdirSync(MEMROSETTA_DIR, { recursive: true, mode: 448 });
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
chmodSync(MEMROSETTA_DIR, 448);
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function getConfig() {
|
|
28
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
29
|
+
return DEFAULT_CONFIG;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
return {
|
|
35
|
+
...DEFAULT_CONFIG,
|
|
36
|
+
...parsed
|
|
37
|
+
};
|
|
38
|
+
} catch {
|
|
39
|
+
return DEFAULT_CONFIG;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeConfig(config) {
|
|
43
|
+
ensureDir();
|
|
44
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
45
|
+
try {
|
|
46
|
+
chmodSync(CONFIG_PATH, 384);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function resolveCanonicalUserId(explicit, configLoader = getConfig) {
|
|
51
|
+
if (explicit && explicit.trim().length > 0) {
|
|
52
|
+
return explicit.trim();
|
|
53
|
+
}
|
|
54
|
+
const config = configLoader();
|
|
55
|
+
if (config.syncUserId && config.syncUserId.trim().length > 0) {
|
|
56
|
+
return config.syncUserId.trim();
|
|
57
|
+
}
|
|
58
|
+
return userInfo().username;
|
|
59
|
+
}
|
|
60
|
+
function getDefaultUserId() {
|
|
61
|
+
return resolveCanonicalUserId();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
getDefaultDbPath,
|
|
66
|
+
ensureDir,
|
|
67
|
+
getConfig,
|
|
68
|
+
writeConfig,
|
|
69
|
+
resolveCanonicalUserId,
|
|
70
|
+
getDefaultUserId
|
|
71
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasFlag,
|
|
3
|
+
optionalOption
|
|
4
|
+
} from "./chunk-US6CEDMU.js";
|
|
5
|
+
import {
|
|
6
|
+
getEngine
|
|
7
|
+
} from "./chunk-47SU2YUJ.js";
|
|
8
|
+
import {
|
|
9
|
+
output,
|
|
10
|
+
outputError
|
|
11
|
+
} from "./chunk-ET6TNQOJ.js";
|
|
12
|
+
import {
|
|
13
|
+
getDefaultUserId
|
|
14
|
+
} from "./chunk-WYHEAKPC.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/clear.ts
|
|
17
|
+
async function run(options) {
|
|
18
|
+
const { args, format, db, noEmbeddings } = options;
|
|
19
|
+
const userId = optionalOption(args, "--user") ?? getDefaultUserId();
|
|
20
|
+
const confirm = hasFlag(args, "--confirm");
|
|
21
|
+
if (!confirm) {
|
|
22
|
+
outputError(
|
|
23
|
+
"This will delete all memories for the user. Use --confirm to proceed.",
|
|
24
|
+
format
|
|
25
|
+
);
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const engine = await getEngine({ db, noEmbeddings });
|
|
30
|
+
const countBefore = await engine.count(userId);
|
|
31
|
+
await engine.clear(userId);
|
|
32
|
+
output(
|
|
33
|
+
{ userId, cleared: countBefore, message: `Cleared ${countBefore} memories` },
|
|
34
|
+
format
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
run
|
|
39
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasFlag,
|
|
3
|
+
optionalOption
|
|
4
|
+
} from "./chunk-US6CEDMU.js";
|
|
5
|
+
import {
|
|
6
|
+
getEngine
|
|
7
|
+
} from "./chunk-VMGX5FCY.js";
|
|
8
|
+
import {
|
|
9
|
+
output,
|
|
10
|
+
outputError
|
|
11
|
+
} from "./chunk-ET6TNQOJ.js";
|
|
12
|
+
import {
|
|
13
|
+
getDefaultUserId
|
|
14
|
+
} from "./chunk-RZFCVYTK.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/clear.ts
|
|
17
|
+
async function run(options) {
|
|
18
|
+
const { args, format, db, noEmbeddings } = options;
|
|
19
|
+
const userId = optionalOption(args, "--user") ?? getDefaultUserId();
|
|
20
|
+
const confirm = hasFlag(args, "--confirm");
|
|
21
|
+
if (!confirm) {
|
|
22
|
+
outputError(
|
|
23
|
+
"This will delete all memories for the user. Use --confirm to proceed.",
|
|
24
|
+
format
|
|
25
|
+
);
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const engine = await getEngine({ db, noEmbeddings });
|
|
30
|
+
const countBefore = await engine.count(userId);
|
|
31
|
+
await engine.clear(userId);
|
|
32
|
+
output(
|
|
33
|
+
{ userId, cleared: countBefore, message: `Cleared ${countBefore} memories` },
|
|
34
|
+
format
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
run
|
|
39
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
optionalOption
|
|
3
|
+
} from "./chunk-US6CEDMU.js";
|
|
4
|
+
import {
|
|
5
|
+
getEngine
|
|
6
|
+
} from "./chunk-VMGX5FCY.js";
|
|
7
|
+
import {
|
|
8
|
+
output
|
|
9
|
+
} from "./chunk-ET6TNQOJ.js";
|
|
10
|
+
import {
|
|
11
|
+
getDefaultUserId
|
|
12
|
+
} from "./chunk-RZFCVYTK.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/compress.ts
|
|
15
|
+
async function run(options) {
|
|
16
|
+
const { args, format, db, noEmbeddings } = options;
|
|
17
|
+
const userId = optionalOption(args, "--user") ?? getDefaultUserId();
|
|
18
|
+
const engine = await getEngine({ db, noEmbeddings });
|
|
19
|
+
const result = await engine.compress(userId);
|
|
20
|
+
if (format === "text") {
|
|
21
|
+
process.stdout.write(`Compression completed for user: ${userId}
|
|
22
|
+
`);
|
|
23
|
+
process.stdout.write(` Groups compressed: ${result.compressed}
|
|
24
|
+
`);
|
|
25
|
+
process.stdout.write(` Memories archived: ${result.removed}
|
|
26
|
+
`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
output({ userId, ...result }, format);
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
run
|
|
33
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
optionalOption
|
|
3
|
+
} from "./chunk-US6CEDMU.js";
|
|
4
|
+
import {
|
|
5
|
+
getEngine
|
|
6
|
+
} from "./chunk-47SU2YUJ.js";
|
|
7
|
+
import {
|
|
8
|
+
output
|
|
9
|
+
} from "./chunk-ET6TNQOJ.js";
|
|
10
|
+
import {
|
|
11
|
+
getDefaultUserId
|
|
12
|
+
} from "./chunk-WYHEAKPC.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/compress.ts
|
|
15
|
+
async function run(options) {
|
|
16
|
+
const { args, format, db, noEmbeddings } = options;
|
|
17
|
+
const userId = optionalOption(args, "--user") ?? getDefaultUserId();
|
|
18
|
+
const engine = await getEngine({ db, noEmbeddings });
|
|
19
|
+
const result = await engine.compress(userId);
|
|
20
|
+
if (format === "text") {
|
|
21
|
+
process.stdout.write(`Compression completed for user: ${userId}
|
|
22
|
+
`);
|
|
23
|
+
process.stdout.write(` Groups compressed: ${result.compressed}
|
|
24
|
+
`);
|
|
25
|
+
process.stdout.write(` Memories archived: ${result.removed}
|
|
26
|
+
`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
output({ userId, ...result }, format);
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
run
|
|
33
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
optionalOption
|
|
3
|
+
} from "./chunk-US6CEDMU.js";
|
|
4
|
+
import {
|
|
5
|
+
getEngine
|
|
6
|
+
} from "./chunk-VMGX5FCY.js";
|
|
7
|
+
import {
|
|
8
|
+
output
|
|
9
|
+
} from "./chunk-ET6TNQOJ.js";
|
|
10
|
+
import {
|
|
11
|
+
getDefaultUserId
|
|
12
|
+
} from "./chunk-RZFCVYTK.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/count.ts
|
|
15
|
+
async function run(options) {
|
|
16
|
+
const { args, format, db, noEmbeddings } = options;
|
|
17
|
+
const userId = optionalOption(args, "--user") ?? getDefaultUserId();
|
|
18
|
+
const engine = await getEngine({ db, noEmbeddings });
|
|
19
|
+
const count = await engine.count(userId);
|
|
20
|
+
output({ userId, count }, format);
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
run
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
optionalOption
|
|
3
|
+
} from "./chunk-US6CEDMU.js";
|
|
4
|
+
import {
|
|
5
|
+
getEngine
|
|
6
|
+
} from "./chunk-47SU2YUJ.js";
|
|
7
|
+
import {
|
|
8
|
+
output
|
|
9
|
+
} from "./chunk-ET6TNQOJ.js";
|
|
10
|
+
import {
|
|
11
|
+
getDefaultUserId
|
|
12
|
+
} from "./chunk-WYHEAKPC.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/count.ts
|
|
15
|
+
async function run(options) {
|
|
16
|
+
const { args, format, db, noEmbeddings } = options;
|
|
17
|
+
const userId = optionalOption(args, "--user") ?? getDefaultUserId();
|
|
18
|
+
const engine = await getEngine({ db, noEmbeddings });
|
|
19
|
+
const count = await engine.count(userId);
|
|
20
|
+
output({ userId, count }, format);
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
run
|
|
24
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasFlag,
|
|
3
|
+
optionalOption
|
|
4
|
+
} from "./chunk-US6CEDMU.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveDbPath
|
|
7
|
+
} from "./chunk-47SU2YUJ.js";
|
|
8
|
+
import {
|
|
9
|
+
output,
|
|
10
|
+
outputError
|
|
11
|
+
} from "./chunk-ET6TNQOJ.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveCanonicalUserId
|
|
14
|
+
} from "./chunk-WYHEAKPC.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/duplicates.ts
|
|
17
|
+
var PREVIEW_LIMIT = 200;
|
|
18
|
+
var MAX_GROUPS_IN_TEXT = 20;
|
|
19
|
+
function scanDuplicates(db, canonicalUserId, limit) {
|
|
20
|
+
const groups = db.prepare(
|
|
21
|
+
`SELECT
|
|
22
|
+
content,
|
|
23
|
+
memory_type AS memoryType,
|
|
24
|
+
COUNT(*) AS totalRows,
|
|
25
|
+
COUNT(DISTINCT user_id) AS distinctUsers,
|
|
26
|
+
GROUP_CONCAT(DISTINCT user_id) AS users
|
|
27
|
+
FROM memories
|
|
28
|
+
GROUP BY content, memory_type
|
|
29
|
+
HAVING totalRows > 1
|
|
30
|
+
ORDER BY totalRows DESC
|
|
31
|
+
LIMIT ?`
|
|
32
|
+
).all(limit);
|
|
33
|
+
const memberStmt = db.prepare(
|
|
34
|
+
`SELECT
|
|
35
|
+
memory_id AS memoryId,
|
|
36
|
+
user_id AS userId,
|
|
37
|
+
namespace,
|
|
38
|
+
learned_at AS learnedAt,
|
|
39
|
+
use_count AS useCount,
|
|
40
|
+
success_count AS successCount
|
|
41
|
+
FROM memories
|
|
42
|
+
WHERE content = ? AND memory_type = ?
|
|
43
|
+
ORDER BY learned_at DESC`
|
|
44
|
+
);
|
|
45
|
+
return groups.map((g) => {
|
|
46
|
+
const members = memberStmt.all(g.content, g.memoryType);
|
|
47
|
+
const sorted = [...members].sort((a, b) => scoreMember(b, canonicalUserId) - scoreMember(a, canonicalUserId));
|
|
48
|
+
return {
|
|
49
|
+
content: g.content,
|
|
50
|
+
memoryType: g.memoryType,
|
|
51
|
+
totalRows: g.totalRows,
|
|
52
|
+
distinctUsers: g.distinctUsers,
|
|
53
|
+
users: (g.users ?? "").split(","),
|
|
54
|
+
members,
|
|
55
|
+
recommendedKeep: sorted[0]?.memoryId ?? null
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function scoreMember(row, canonicalUserId) {
|
|
60
|
+
let score = 0;
|
|
61
|
+
if (row.userId === canonicalUserId) score += 1e6;
|
|
62
|
+
score += (row.successCount ?? 0) * 100;
|
|
63
|
+
score += (row.useCount ?? 0) * 10;
|
|
64
|
+
const ms = Date.parse(row.learnedAt);
|
|
65
|
+
if (!Number.isNaN(ms)) score += ms / 1e6;
|
|
66
|
+
return score;
|
|
67
|
+
}
|
|
68
|
+
async function run(options) {
|
|
69
|
+
const { args, format, db: dbOverride } = options;
|
|
70
|
+
const sub = args[0];
|
|
71
|
+
if (sub !== "report") {
|
|
72
|
+
outputError(
|
|
73
|
+
"Usage: memrosetta duplicates report [--format json|text] [--limit <n>] [--canonical <user>]",
|
|
74
|
+
format
|
|
75
|
+
);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const sliced = args.slice(1);
|
|
80
|
+
const limitRaw = optionalOption(sliced, "--limit");
|
|
81
|
+
const limit = limitRaw ? Math.max(1, parseInt(limitRaw, 10)) : PREVIEW_LIMIT;
|
|
82
|
+
const verbose = hasFlag(sliced, "--verbose");
|
|
83
|
+
const canonicalOverride = optionalOption(sliced, "--canonical");
|
|
84
|
+
const canonicalUserId = resolveCanonicalUserId(canonicalOverride ?? null);
|
|
85
|
+
const dbPath = resolveDbPath(dbOverride);
|
|
86
|
+
const { default: Database } = await import("better-sqlite3");
|
|
87
|
+
const db = new Database(dbPath, { readonly: true });
|
|
88
|
+
try {
|
|
89
|
+
const groups = scanDuplicates(db, canonicalUserId, limit);
|
|
90
|
+
const totalGroups = groups.length;
|
|
91
|
+
const crossUserGroups = groups.filter((g) => g.distinctUsers > 1).length;
|
|
92
|
+
const totalDuplicateRows = groups.reduce((sum, g) => sum + g.totalRows, 0);
|
|
93
|
+
if (format === "text") {
|
|
94
|
+
printText(groups, canonicalUserId, {
|
|
95
|
+
totalGroups,
|
|
96
|
+
crossUserGroups,
|
|
97
|
+
totalDuplicateRows,
|
|
98
|
+
verbose
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
output(
|
|
103
|
+
{
|
|
104
|
+
canonicalUserId,
|
|
105
|
+
totalGroups,
|
|
106
|
+
crossUserGroups,
|
|
107
|
+
totalDuplicateRows,
|
|
108
|
+
groups
|
|
109
|
+
},
|
|
110
|
+
format
|
|
111
|
+
);
|
|
112
|
+
} finally {
|
|
113
|
+
db.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function printText(groups, canonicalUserId, summary) {
|
|
117
|
+
const lines = [];
|
|
118
|
+
lines.push(`Duplicate audit (canonical='${canonicalUserId}')`);
|
|
119
|
+
lines.push("=".repeat(60));
|
|
120
|
+
lines.push(` duplicate groups : ${summary.totalGroups}`);
|
|
121
|
+
lines.push(` cross-user groups : ${summary.crossUserGroups}`);
|
|
122
|
+
lines.push(` total duplicate rows : ${summary.totalDuplicateRows}`);
|
|
123
|
+
lines.push("");
|
|
124
|
+
const shown = groups.slice(0, summary.verbose ? groups.length : MAX_GROUPS_IN_TEXT);
|
|
125
|
+
for (const group of shown) {
|
|
126
|
+
const excerpt = group.content.length > 80 ? group.content.slice(0, 80) + "\u2026" : group.content;
|
|
127
|
+
lines.push(`- [${group.memoryType}] rows=${group.totalRows} users=${group.distinctUsers}`);
|
|
128
|
+
lines.push(` content: ${excerpt}`);
|
|
129
|
+
lines.push(` users: ${group.users.join(", ")}`);
|
|
130
|
+
if (group.recommendedKeep) {
|
|
131
|
+
lines.push(` recommended keep: ${group.recommendedKeep}`);
|
|
132
|
+
}
|
|
133
|
+
if (summary.verbose) {
|
|
134
|
+
for (const m of group.members) {
|
|
135
|
+
lines.push(
|
|
136
|
+
` - ${m.memoryId} user=${m.userId} use=${m.useCount} success=${m.successCount} learned=${m.learnedAt}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
lines.push("");
|
|
141
|
+
}
|
|
142
|
+
if (!summary.verbose && groups.length > MAX_GROUPS_IN_TEXT) {
|
|
143
|
+
lines.push(`(+${groups.length - MAX_GROUPS_IN_TEXT} more groups \u2014 pass --verbose to list all)`);
|
|
144
|
+
}
|
|
145
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
run
|
|
149
|
+
};
|