@hasna/mementos 0.8.0 → 0.10.0
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/README.md +10 -0
- package/dist/cli/index.js +787 -4
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/session-jobs.d.ts +49 -0
- package/dist/db/session-jobs.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/lib/open-sessions-connector.d.ts +60 -0
- package/dist/lib/open-sessions-connector.d.ts.map +1 -0
- package/dist/lib/session-auto-resolve.d.ts +28 -0
- package/dist/lib/session-auto-resolve.d.ts.map +1 -0
- package/dist/lib/session-processor.d.ts +38 -0
- package/dist/lib/session-processor.d.ts.map +1 -0
- package/dist/lib/session-queue.d.ts +28 -0
- package/dist/lib/session-queue.d.ts.map +1 -0
- package/dist/mcp/index.js +439 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +518 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2520,7 +2520,30 @@ var init_database = __esm(() => {
|
|
|
2520
2520
|
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_type ON webhook_hooks(type);
|
|
2521
2521
|
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_enabled ON webhook_hooks(enabled);
|
|
2522
2522
|
INSERT OR IGNORE INTO _migrations (id) VALUES (10);
|
|
2523
|
-
|
|
2523
|
+
`,
|
|
2524
|
+
`
|
|
2525
|
+
CREATE TABLE IF NOT EXISTS session_memory_jobs (
|
|
2526
|
+
id TEXT PRIMARY KEY,
|
|
2527
|
+
session_id TEXT NOT NULL,
|
|
2528
|
+
agent_id TEXT,
|
|
2529
|
+
project_id TEXT,
|
|
2530
|
+
source TEXT NOT NULL DEFAULT 'manual' CHECK(source IN ('claude-code','codex','manual','open-sessions')),
|
|
2531
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','processing','completed','failed')),
|
|
2532
|
+
transcript TEXT NOT NULL,
|
|
2533
|
+
chunk_count INTEGER NOT NULL DEFAULT 0,
|
|
2534
|
+
memories_extracted INTEGER NOT NULL DEFAULT 0,
|
|
2535
|
+
error TEXT,
|
|
2536
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
2537
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2538
|
+
started_at TEXT,
|
|
2539
|
+
completed_at TEXT
|
|
2540
|
+
);
|
|
2541
|
+
CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_status ON session_memory_jobs(status);
|
|
2542
|
+
CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_agent ON session_memory_jobs(agent_id);
|
|
2543
|
+
CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_project ON session_memory_jobs(project_id);
|
|
2544
|
+
CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_session ON session_memory_jobs(session_id);
|
|
2545
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (13);
|
|
2546
|
+
`
|
|
2524
2547
|
];
|
|
2525
2548
|
});
|
|
2526
2549
|
|
|
@@ -6345,6 +6368,408 @@ var init_synthesis2 = __esm(() => {
|
|
|
6345
6368
|
init_metrics();
|
|
6346
6369
|
});
|
|
6347
6370
|
|
|
6371
|
+
// src/db/session-jobs.ts
|
|
6372
|
+
var exports_session_jobs = {};
|
|
6373
|
+
__export(exports_session_jobs, {
|
|
6374
|
+
updateSessionJob: () => updateSessionJob,
|
|
6375
|
+
listSessionJobs: () => listSessionJobs,
|
|
6376
|
+
getSessionJob: () => getSessionJob,
|
|
6377
|
+
getNextPendingJob: () => getNextPendingJob,
|
|
6378
|
+
createSessionJob: () => createSessionJob
|
|
6379
|
+
});
|
|
6380
|
+
function parseJobRow(row) {
|
|
6381
|
+
return {
|
|
6382
|
+
id: row["id"],
|
|
6383
|
+
session_id: row["session_id"],
|
|
6384
|
+
agent_id: row["agent_id"] || null,
|
|
6385
|
+
project_id: row["project_id"] || null,
|
|
6386
|
+
source: row["source"],
|
|
6387
|
+
status: row["status"],
|
|
6388
|
+
transcript: row["transcript"],
|
|
6389
|
+
chunk_count: row["chunk_count"],
|
|
6390
|
+
memories_extracted: row["memories_extracted"],
|
|
6391
|
+
error: row["error"] || null,
|
|
6392
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
6393
|
+
created_at: row["created_at"],
|
|
6394
|
+
started_at: row["started_at"] || null,
|
|
6395
|
+
completed_at: row["completed_at"] || null
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6398
|
+
function createSessionJob(input, db) {
|
|
6399
|
+
const d = db || getDatabase();
|
|
6400
|
+
const id = uuid();
|
|
6401
|
+
const timestamp = now();
|
|
6402
|
+
const source = input.source ?? "manual";
|
|
6403
|
+
const metadata = JSON.stringify(input.metadata ?? {});
|
|
6404
|
+
d.run(`INSERT INTO session_memory_jobs
|
|
6405
|
+
(id, session_id, agent_id, project_id, source, status, transcript, chunk_count, memories_extracted, metadata, created_at)
|
|
6406
|
+
VALUES (?, ?, ?, ?, ?, 'pending', ?, 0, 0, ?, ?)`, [
|
|
6407
|
+
id,
|
|
6408
|
+
input.session_id,
|
|
6409
|
+
input.agent_id ?? null,
|
|
6410
|
+
input.project_id ?? null,
|
|
6411
|
+
source,
|
|
6412
|
+
input.transcript,
|
|
6413
|
+
metadata,
|
|
6414
|
+
timestamp
|
|
6415
|
+
]);
|
|
6416
|
+
return getSessionJob(id, d);
|
|
6417
|
+
}
|
|
6418
|
+
function getSessionJob(id, db) {
|
|
6419
|
+
const d = db || getDatabase();
|
|
6420
|
+
const row = d.query("SELECT * FROM session_memory_jobs WHERE id = ?").get(id);
|
|
6421
|
+
if (!row)
|
|
6422
|
+
return null;
|
|
6423
|
+
return parseJobRow(row);
|
|
6424
|
+
}
|
|
6425
|
+
function listSessionJobs(filter, db) {
|
|
6426
|
+
const d = db || getDatabase();
|
|
6427
|
+
const conditions = [];
|
|
6428
|
+
const params = [];
|
|
6429
|
+
if (filter?.agent_id) {
|
|
6430
|
+
conditions.push("agent_id = ?");
|
|
6431
|
+
params.push(filter.agent_id);
|
|
6432
|
+
}
|
|
6433
|
+
if (filter?.project_id) {
|
|
6434
|
+
conditions.push("project_id = ?");
|
|
6435
|
+
params.push(filter.project_id);
|
|
6436
|
+
}
|
|
6437
|
+
if (filter?.status) {
|
|
6438
|
+
conditions.push("status = ?");
|
|
6439
|
+
params.push(filter.status);
|
|
6440
|
+
}
|
|
6441
|
+
if (filter?.session_id) {
|
|
6442
|
+
conditions.push("session_id = ?");
|
|
6443
|
+
params.push(filter.session_id);
|
|
6444
|
+
}
|
|
6445
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
6446
|
+
const limit = filter?.limit ?? 20;
|
|
6447
|
+
const offset = filter?.offset ?? 0;
|
|
6448
|
+
const rows = d.query(`SELECT * FROM session_memory_jobs ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
6449
|
+
return rows.map(parseJobRow);
|
|
6450
|
+
}
|
|
6451
|
+
function updateSessionJob(id, updates, db) {
|
|
6452
|
+
const d = db || getDatabase();
|
|
6453
|
+
const setClauses = [];
|
|
6454
|
+
const params = [];
|
|
6455
|
+
if (updates.status !== undefined) {
|
|
6456
|
+
setClauses.push("status = ?");
|
|
6457
|
+
params.push(updates.status);
|
|
6458
|
+
}
|
|
6459
|
+
if (updates.chunk_count !== undefined) {
|
|
6460
|
+
setClauses.push("chunk_count = ?");
|
|
6461
|
+
params.push(updates.chunk_count);
|
|
6462
|
+
}
|
|
6463
|
+
if (updates.memories_extracted !== undefined) {
|
|
6464
|
+
setClauses.push("memories_extracted = ?");
|
|
6465
|
+
params.push(updates.memories_extracted);
|
|
6466
|
+
}
|
|
6467
|
+
if ("error" in updates) {
|
|
6468
|
+
setClauses.push("error = ?");
|
|
6469
|
+
params.push(updates.error ?? null);
|
|
6470
|
+
}
|
|
6471
|
+
if ("started_at" in updates) {
|
|
6472
|
+
setClauses.push("started_at = ?");
|
|
6473
|
+
params.push(updates.started_at ?? null);
|
|
6474
|
+
}
|
|
6475
|
+
if ("completed_at" in updates) {
|
|
6476
|
+
setClauses.push("completed_at = ?");
|
|
6477
|
+
params.push(updates.completed_at ?? null);
|
|
6478
|
+
}
|
|
6479
|
+
if (setClauses.length === 0)
|
|
6480
|
+
return getSessionJob(id, d);
|
|
6481
|
+
params.push(id);
|
|
6482
|
+
d.run(`UPDATE session_memory_jobs SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
6483
|
+
return getSessionJob(id, d);
|
|
6484
|
+
}
|
|
6485
|
+
function getNextPendingJob(db) {
|
|
6486
|
+
const d = db || getDatabase();
|
|
6487
|
+
const row = d.query("SELECT * FROM session_memory_jobs WHERE status = 'pending' ORDER BY created_at ASC LIMIT 1").get();
|
|
6488
|
+
if (!row)
|
|
6489
|
+
return null;
|
|
6490
|
+
return parseJobRow(row);
|
|
6491
|
+
}
|
|
6492
|
+
var init_session_jobs = __esm(() => {
|
|
6493
|
+
init_database();
|
|
6494
|
+
});
|
|
6495
|
+
|
|
6496
|
+
// src/lib/session-processor.ts
|
|
6497
|
+
function chunkTranscript(transcript, chunkSize = 2000, overlap = 200) {
|
|
6498
|
+
if (!transcript || transcript.length === 0)
|
|
6499
|
+
return [];
|
|
6500
|
+
if (transcript.length <= chunkSize)
|
|
6501
|
+
return [transcript];
|
|
6502
|
+
const chunks = [];
|
|
6503
|
+
let start = 0;
|
|
6504
|
+
while (start < transcript.length) {
|
|
6505
|
+
const end = Math.min(start + chunkSize, transcript.length);
|
|
6506
|
+
chunks.push(transcript.slice(start, end));
|
|
6507
|
+
if (end === transcript.length)
|
|
6508
|
+
break;
|
|
6509
|
+
start += chunkSize - overlap;
|
|
6510
|
+
}
|
|
6511
|
+
return chunks;
|
|
6512
|
+
}
|
|
6513
|
+
async function extractMemoriesFromChunk(chunk, context, db) {
|
|
6514
|
+
const provider = providerRegistry.getAvailable();
|
|
6515
|
+
if (!provider)
|
|
6516
|
+
return 0;
|
|
6517
|
+
try {
|
|
6518
|
+
const extracted = await provider.extractMemories(SESSION_EXTRACTION_USER_TEMPLATE(chunk, context.sessionId), {
|
|
6519
|
+
sessionId: context.sessionId,
|
|
6520
|
+
agentId: context.agentId,
|
|
6521
|
+
projectId: context.projectId
|
|
6522
|
+
});
|
|
6523
|
+
let savedCount = 0;
|
|
6524
|
+
const sourceTag = context.source ? `source:${context.source}` : "source:manual";
|
|
6525
|
+
for (const memory of extracted) {
|
|
6526
|
+
if (!memory.content || !memory.content.trim())
|
|
6527
|
+
continue;
|
|
6528
|
+
try {
|
|
6529
|
+
createMemory({
|
|
6530
|
+
key: memory.content.slice(0, 120).replace(/\s+/g, "-").toLowerCase(),
|
|
6531
|
+
value: memory.content,
|
|
6532
|
+
category: memory.category,
|
|
6533
|
+
scope: memory.suggestedScope ?? "shared",
|
|
6534
|
+
importance: memory.importance,
|
|
6535
|
+
tags: [
|
|
6536
|
+
...memory.tags,
|
|
6537
|
+
"session-extracted",
|
|
6538
|
+
sourceTag,
|
|
6539
|
+
`session:${context.sessionId}`
|
|
6540
|
+
],
|
|
6541
|
+
source: "auto",
|
|
6542
|
+
agent_id: context.agentId,
|
|
6543
|
+
project_id: context.projectId,
|
|
6544
|
+
session_id: context.sessionId,
|
|
6545
|
+
metadata: {
|
|
6546
|
+
auto_extracted: true,
|
|
6547
|
+
session_source: context.source ?? "manual",
|
|
6548
|
+
extracted_at: new Date().toISOString(),
|
|
6549
|
+
reasoning: memory.reasoning
|
|
6550
|
+
}
|
|
6551
|
+
}, "merge", db);
|
|
6552
|
+
savedCount++;
|
|
6553
|
+
} catch {}
|
|
6554
|
+
}
|
|
6555
|
+
return savedCount;
|
|
6556
|
+
} catch {
|
|
6557
|
+
try {
|
|
6558
|
+
const fallbacks = providerRegistry.getFallbacks();
|
|
6559
|
+
for (const fallback of fallbacks) {
|
|
6560
|
+
try {
|
|
6561
|
+
const extracted = await fallback.extractMemories(SESSION_EXTRACTION_USER_TEMPLATE(chunk, context.sessionId), {
|
|
6562
|
+
sessionId: context.sessionId,
|
|
6563
|
+
agentId: context.agentId,
|
|
6564
|
+
projectId: context.projectId
|
|
6565
|
+
});
|
|
6566
|
+
let savedCount = 0;
|
|
6567
|
+
const sourceTag = context.source ? `source:${context.source}` : "source:manual";
|
|
6568
|
+
for (const memory of extracted) {
|
|
6569
|
+
if (!memory.content || !memory.content.trim())
|
|
6570
|
+
continue;
|
|
6571
|
+
try {
|
|
6572
|
+
createMemory({
|
|
6573
|
+
key: memory.content.slice(0, 120).replace(/\s+/g, "-").toLowerCase(),
|
|
6574
|
+
value: memory.content,
|
|
6575
|
+
category: memory.category,
|
|
6576
|
+
scope: memory.suggestedScope ?? "shared",
|
|
6577
|
+
importance: memory.importance,
|
|
6578
|
+
tags: [
|
|
6579
|
+
...memory.tags,
|
|
6580
|
+
"session-extracted",
|
|
6581
|
+
sourceTag,
|
|
6582
|
+
`session:${context.sessionId}`
|
|
6583
|
+
],
|
|
6584
|
+
source: "auto",
|
|
6585
|
+
agent_id: context.agentId,
|
|
6586
|
+
project_id: context.projectId,
|
|
6587
|
+
session_id: context.sessionId,
|
|
6588
|
+
metadata: {
|
|
6589
|
+
auto_extracted: true,
|
|
6590
|
+
session_source: context.source ?? "manual",
|
|
6591
|
+
extracted_at: new Date().toISOString(),
|
|
6592
|
+
reasoning: memory.reasoning
|
|
6593
|
+
}
|
|
6594
|
+
}, "merge", db);
|
|
6595
|
+
savedCount++;
|
|
6596
|
+
} catch {}
|
|
6597
|
+
}
|
|
6598
|
+
if (savedCount > 0)
|
|
6599
|
+
return savedCount;
|
|
6600
|
+
} catch {
|
|
6601
|
+
continue;
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6604
|
+
} catch {}
|
|
6605
|
+
return 0;
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
async function processSessionJob(jobId, db) {
|
|
6609
|
+
const result = {
|
|
6610
|
+
jobId,
|
|
6611
|
+
chunksProcessed: 0,
|
|
6612
|
+
memoriesExtracted: 0,
|
|
6613
|
+
errors: []
|
|
6614
|
+
};
|
|
6615
|
+
let job;
|
|
6616
|
+
try {
|
|
6617
|
+
job = getSessionJob(jobId, db);
|
|
6618
|
+
if (!job) {
|
|
6619
|
+
result.errors.push(`Job not found: ${jobId}`);
|
|
6620
|
+
return result;
|
|
6621
|
+
}
|
|
6622
|
+
} catch (e) {
|
|
6623
|
+
result.errors.push(`Failed to fetch job: ${String(e)}`);
|
|
6624
|
+
return result;
|
|
6625
|
+
}
|
|
6626
|
+
try {
|
|
6627
|
+
updateSessionJob(jobId, { status: "processing", started_at: new Date().toISOString() }, db);
|
|
6628
|
+
} catch (e) {
|
|
6629
|
+
result.errors.push(`Failed to mark job as processing: ${String(e)}`);
|
|
6630
|
+
return result;
|
|
6631
|
+
}
|
|
6632
|
+
const chunks = chunkTranscript(job.transcript);
|
|
6633
|
+
try {
|
|
6634
|
+
updateSessionJob(jobId, { chunk_count: chunks.length }, db);
|
|
6635
|
+
} catch {}
|
|
6636
|
+
let totalMemories = 0;
|
|
6637
|
+
for (let i = 0;i < chunks.length; i++) {
|
|
6638
|
+
const chunk = chunks[i];
|
|
6639
|
+
try {
|
|
6640
|
+
const count = await extractMemoriesFromChunk(chunk, {
|
|
6641
|
+
sessionId: job.session_id,
|
|
6642
|
+
agentId: job.agent_id ?? undefined,
|
|
6643
|
+
projectId: job.project_id ?? undefined,
|
|
6644
|
+
source: job.source
|
|
6645
|
+
}, db);
|
|
6646
|
+
totalMemories += count;
|
|
6647
|
+
result.chunksProcessed++;
|
|
6648
|
+
} catch (e) {
|
|
6649
|
+
result.errors.push(`Chunk ${i} failed: ${String(e)}`);
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
result.memoriesExtracted = totalMemories;
|
|
6653
|
+
try {
|
|
6654
|
+
if (result.errors.length > 0 && result.chunksProcessed === 0) {
|
|
6655
|
+
updateSessionJob(jobId, {
|
|
6656
|
+
status: "failed",
|
|
6657
|
+
error: result.errors.join("; "),
|
|
6658
|
+
completed_at: new Date().toISOString(),
|
|
6659
|
+
memories_extracted: totalMemories,
|
|
6660
|
+
chunk_count: chunks.length
|
|
6661
|
+
}, db);
|
|
6662
|
+
} else {
|
|
6663
|
+
updateSessionJob(jobId, {
|
|
6664
|
+
status: "completed",
|
|
6665
|
+
completed_at: new Date().toISOString(),
|
|
6666
|
+
memories_extracted: totalMemories,
|
|
6667
|
+
chunk_count: chunks.length
|
|
6668
|
+
}, db);
|
|
6669
|
+
}
|
|
6670
|
+
} catch (e) {
|
|
6671
|
+
result.errors.push(`Failed to update job status: ${String(e)}`);
|
|
6672
|
+
}
|
|
6673
|
+
return result;
|
|
6674
|
+
}
|
|
6675
|
+
var SESSION_EXTRACTION_USER_TEMPLATE = (chunk, sessionId) => `Extract memories from this session chunk (session: ${sessionId}):
|
|
6676
|
+
|
|
6677
|
+
${chunk}
|
|
6678
|
+
|
|
6679
|
+
Return JSON array: [{"key": "...", "value": "...", "category": "knowledge|fact|preference|history", "importance": 1-10, "tags": [...]}]`;
|
|
6680
|
+
var init_session_processor = __esm(() => {
|
|
6681
|
+
init_memories();
|
|
6682
|
+
init_registry();
|
|
6683
|
+
init_session_jobs();
|
|
6684
|
+
});
|
|
6685
|
+
|
|
6686
|
+
// src/lib/session-queue.ts
|
|
6687
|
+
var exports_session_queue = {};
|
|
6688
|
+
__export(exports_session_queue, {
|
|
6689
|
+
startSessionQueueWorker: () => startSessionQueueWorker,
|
|
6690
|
+
getSessionQueueStats: () => getSessionQueueStats,
|
|
6691
|
+
enqueueSessionJob: () => enqueueSessionJob
|
|
6692
|
+
});
|
|
6693
|
+
function enqueueSessionJob(jobId) {
|
|
6694
|
+
_pendingQueue.add(jobId);
|
|
6695
|
+
if (!_isProcessing) {
|
|
6696
|
+
_processNext();
|
|
6697
|
+
}
|
|
6698
|
+
}
|
|
6699
|
+
function getSessionQueueStats() {
|
|
6700
|
+
try {
|
|
6701
|
+
const db = getDatabase();
|
|
6702
|
+
const rows = db.query("SELECT status, COUNT(*) as count FROM session_memory_jobs GROUP BY status").all();
|
|
6703
|
+
const stats = {
|
|
6704
|
+
pending: 0,
|
|
6705
|
+
processing: 0,
|
|
6706
|
+
completed: 0,
|
|
6707
|
+
failed: 0
|
|
6708
|
+
};
|
|
6709
|
+
for (const row of rows) {
|
|
6710
|
+
if (row.status === "pending")
|
|
6711
|
+
stats.pending = row.count;
|
|
6712
|
+
else if (row.status === "processing")
|
|
6713
|
+
stats.processing = row.count;
|
|
6714
|
+
else if (row.status === "completed")
|
|
6715
|
+
stats.completed = row.count;
|
|
6716
|
+
else if (row.status === "failed")
|
|
6717
|
+
stats.failed = row.count;
|
|
6718
|
+
}
|
|
6719
|
+
return stats;
|
|
6720
|
+
} catch {
|
|
6721
|
+
return {
|
|
6722
|
+
pending: _pendingQueue.size,
|
|
6723
|
+
processing: _isProcessing ? 1 : 0,
|
|
6724
|
+
completed: 0,
|
|
6725
|
+
failed: 0
|
|
6726
|
+
};
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6729
|
+
function startSessionQueueWorker() {
|
|
6730
|
+
if (_workerStarted)
|
|
6731
|
+
return;
|
|
6732
|
+
_workerStarted = true;
|
|
6733
|
+
setInterval(() => {
|
|
6734
|
+
_processNext();
|
|
6735
|
+
}, 5000);
|
|
6736
|
+
}
|
|
6737
|
+
async function _processNext() {
|
|
6738
|
+
if (_isProcessing)
|
|
6739
|
+
return;
|
|
6740
|
+
let jobId;
|
|
6741
|
+
if (_pendingQueue.size > 0) {
|
|
6742
|
+
jobId = [..._pendingQueue][0];
|
|
6743
|
+
_pendingQueue.delete(jobId);
|
|
6744
|
+
} else {
|
|
6745
|
+
try {
|
|
6746
|
+
const job = getNextPendingJob();
|
|
6747
|
+
if (job)
|
|
6748
|
+
jobId = job.id;
|
|
6749
|
+
} catch {
|
|
6750
|
+
return;
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
if (!jobId)
|
|
6754
|
+
return;
|
|
6755
|
+
_isProcessing = true;
|
|
6756
|
+
try {
|
|
6757
|
+
await processSessionJob(jobId);
|
|
6758
|
+
} catch {} finally {
|
|
6759
|
+
_isProcessing = false;
|
|
6760
|
+
if (_pendingQueue.size > 0) {
|
|
6761
|
+
_processNext();
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
var _pendingQueue, _isProcessing = false, _workerStarted = false;
|
|
6766
|
+
var init_session_queue = __esm(() => {
|
|
6767
|
+
init_database();
|
|
6768
|
+
init_session_jobs();
|
|
6769
|
+
init_session_processor();
|
|
6770
|
+
_pendingQueue = new Set;
|
|
6771
|
+
});
|
|
6772
|
+
|
|
6348
6773
|
// node_modules/commander/esm.mjs
|
|
6349
6774
|
var import__ = __toESM(require_commander(), 1);
|
|
6350
6775
|
var {
|
|
@@ -7018,6 +7443,236 @@ function resolveKeyOrId(keyOrId, opts, globalOpts) {
|
|
|
7018
7443
|
return null;
|
|
7019
7444
|
}
|
|
7020
7445
|
program2.name("mementos").description("Universal memory system for AI agents").version(getPackageVersion()).option("--project <path>", "Project path for scoping").option("--json", "Output as JSON").option("--format <fmt>", "Output format: compact, json, csv, yaml").option("--agent <name>", "Agent name or ID").option("--session <id>", "Session ID");
|
|
7446
|
+
program2.command("init").description("One-command setup: register MCP, install stop hook, configure auto-start").action(async () => {
|
|
7447
|
+
const { platform } = process;
|
|
7448
|
+
const home = homedir2();
|
|
7449
|
+
const isMac = platform === "darwin";
|
|
7450
|
+
console.log("");
|
|
7451
|
+
console.log(chalk.bold(" mementos \u2014 setting up your memory layer"));
|
|
7452
|
+
console.log("");
|
|
7453
|
+
async function run(cmd) {
|
|
7454
|
+
try {
|
|
7455
|
+
const proc = Bun.spawn(cmd, {
|
|
7456
|
+
stdout: "pipe",
|
|
7457
|
+
stderr: "pipe"
|
|
7458
|
+
});
|
|
7459
|
+
const [out, err, exitCode] = await Promise.all([
|
|
7460
|
+
new Response(proc.stdout).text(),
|
|
7461
|
+
new Response(proc.stderr).text(),
|
|
7462
|
+
proc.exited
|
|
7463
|
+
]);
|
|
7464
|
+
return { ok: exitCode === 0, output: (out + err).trim() };
|
|
7465
|
+
} catch (e) {
|
|
7466
|
+
return { ok: false, output: e instanceof Error ? e.message : String(e) };
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
let mcpAlreadyInstalled = false;
|
|
7470
|
+
let mcpError = null;
|
|
7471
|
+
try {
|
|
7472
|
+
const list = await run(["claude", "mcp", "list"]);
|
|
7473
|
+
if (list.output.includes("mementos")) {
|
|
7474
|
+
mcpAlreadyInstalled = true;
|
|
7475
|
+
} else {
|
|
7476
|
+
const add = await run([
|
|
7477
|
+
"claude",
|
|
7478
|
+
"mcp",
|
|
7479
|
+
"add",
|
|
7480
|
+
"--transport",
|
|
7481
|
+
"stdio",
|
|
7482
|
+
"--scope",
|
|
7483
|
+
"user",
|
|
7484
|
+
"mementos",
|
|
7485
|
+
"--",
|
|
7486
|
+
"mementos-mcp"
|
|
7487
|
+
]);
|
|
7488
|
+
if (!add.ok) {
|
|
7489
|
+
mcpError = add.output || "unknown error";
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
} catch (e) {
|
|
7493
|
+
mcpError = e instanceof Error ? e.message : String(e);
|
|
7494
|
+
}
|
|
7495
|
+
if (mcpAlreadyInstalled) {
|
|
7496
|
+
console.log(chalk.dim(" \xB7 MCP already registered"));
|
|
7497
|
+
} else if (mcpError) {
|
|
7498
|
+
console.log(chalk.red(` \u2717 Failed to register MCP: ${mcpError}`));
|
|
7499
|
+
console.log(chalk.dim(" (Is Claude Code installed? Try: claude mcp add --transport stdio --scope user mementos -- mementos-mcp)"));
|
|
7500
|
+
} else {
|
|
7501
|
+
console.log(chalk.green(" \u2713 MCP server registered with Claude Code"));
|
|
7502
|
+
}
|
|
7503
|
+
const hooksDir = join3(home, ".claude", "hooks");
|
|
7504
|
+
const hookDest = join3(hooksDir, "mementos-stop-hook.ts");
|
|
7505
|
+
const settingsPath = join3(home, ".claude", "settings.json");
|
|
7506
|
+
const hookCommand = `bun ${hookDest}`;
|
|
7507
|
+
let hookAlreadyInstalled = false;
|
|
7508
|
+
let hookError = null;
|
|
7509
|
+
try {
|
|
7510
|
+
let settings = {};
|
|
7511
|
+
if (existsSync3(settingsPath)) {
|
|
7512
|
+
try {
|
|
7513
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
7514
|
+
} catch {
|
|
7515
|
+
settings = {};
|
|
7516
|
+
}
|
|
7517
|
+
}
|
|
7518
|
+
const hooksObj = settings["hooks"] || {};
|
|
7519
|
+
const stopHooks = hooksObj["Stop"] || [];
|
|
7520
|
+
const alreadyHasMementos = stopHooks.some((entry) => entry.hooks?.some((h) => h.command && h.command.includes("mementos")));
|
|
7521
|
+
if (alreadyHasMementos) {
|
|
7522
|
+
hookAlreadyInstalled = true;
|
|
7523
|
+
} else {
|
|
7524
|
+
if (!existsSync3(hooksDir)) {
|
|
7525
|
+
mkdirSync3(hooksDir, { recursive: true });
|
|
7526
|
+
}
|
|
7527
|
+
if (!existsSync3(hookDest)) {
|
|
7528
|
+
const packageDir = dirname3(dirname3(fileURLToPath(import.meta.url)));
|
|
7529
|
+
const candidatePaths = [
|
|
7530
|
+
join3(packageDir, "scripts", "hooks", "claude-stop-hook.ts"),
|
|
7531
|
+
join3(packageDir, "..", "scripts", "hooks", "claude-stop-hook.ts"),
|
|
7532
|
+
join3(home, ".bun", "install", "global", "node_modules", "@hasna", "mementos", "scripts", "hooks", "claude-stop-hook.ts")
|
|
7533
|
+
];
|
|
7534
|
+
let hookSourceFound = false;
|
|
7535
|
+
for (const src of candidatePaths) {
|
|
7536
|
+
if (existsSync3(src)) {
|
|
7537
|
+
copyFileSync(src, hookDest);
|
|
7538
|
+
hookSourceFound = true;
|
|
7539
|
+
break;
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
7542
|
+
if (!hookSourceFound) {
|
|
7543
|
+
const inlineHook = `#!/usr/bin/env bun
|
|
7544
|
+
const MEMENTOS_URL = process.env["MEMENTOS_URL"] ?? "http://localhost:19428";
|
|
7545
|
+
const MEMENTOS_AGENT = process.env["MEMENTOS_AGENT"];
|
|
7546
|
+
|
|
7547
|
+
async function main() {
|
|
7548
|
+
let stdinData = "";
|
|
7549
|
+
try {
|
|
7550
|
+
for await (const chunk of Bun.stdin.stream()) {
|
|
7551
|
+
stdinData += new TextDecoder().decode(chunk);
|
|
7552
|
+
}
|
|
7553
|
+
} catch { /* stdin may be closed */ }
|
|
7554
|
+
|
|
7555
|
+
if (!stdinData.trim()) return;
|
|
7556
|
+
|
|
7557
|
+
let payload: unknown;
|
|
7558
|
+
try { payload = JSON.parse(stdinData); } catch { return; }
|
|
7559
|
+
|
|
7560
|
+
try {
|
|
7561
|
+
await fetch(\`\${MEMENTOS_URL}/api/sessions/ingest\`, {
|
|
7562
|
+
method: "POST",
|
|
7563
|
+
headers: { "Content-Type": "application/json" },
|
|
7564
|
+
body: JSON.stringify({
|
|
7565
|
+
transcript: payload,
|
|
7566
|
+
agent: MEMENTOS_AGENT,
|
|
7567
|
+
source: "claude-stop-hook",
|
|
7568
|
+
}),
|
|
7569
|
+
signal: AbortSignal.timeout(5000),
|
|
7570
|
+
});
|
|
7571
|
+
} catch { /* server not running \u2014 silently skip */ }
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7574
|
+
main().catch(() => {});
|
|
7575
|
+
`;
|
|
7576
|
+
writeFileSync2(hookDest, inlineHook, "utf-8");
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7579
|
+
const newStopEntry = {
|
|
7580
|
+
matcher: "",
|
|
7581
|
+
hooks: [{ type: "command", command: hookCommand }]
|
|
7582
|
+
};
|
|
7583
|
+
hooksObj["Stop"] = [...stopHooks, newStopEntry];
|
|
7584
|
+
settings["hooks"] = hooksObj;
|
|
7585
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
7586
|
+
}
|
|
7587
|
+
} catch (e) {
|
|
7588
|
+
hookError = e instanceof Error ? e.message : String(e);
|
|
7589
|
+
}
|
|
7590
|
+
if (hookAlreadyInstalled) {
|
|
7591
|
+
console.log(chalk.dim(" \xB7 Stop hook already installed"));
|
|
7592
|
+
} else if (hookError) {
|
|
7593
|
+
console.log(chalk.red(` \u2717 Failed to install stop hook: ${hookError}`));
|
|
7594
|
+
} else {
|
|
7595
|
+
console.log(chalk.green(" \u2713 Stop hook installed (sessions \u2192 memories)"));
|
|
7596
|
+
}
|
|
7597
|
+
let autoStartAlreadyInstalled = false;
|
|
7598
|
+
let autoStartError = null;
|
|
7599
|
+
if (!isMac) {
|
|
7600
|
+
console.log(chalk.dim(` \xB7 Auto-start skipped (not macOS \u2014 platform: ${platform})`));
|
|
7601
|
+
} else {
|
|
7602
|
+
const plistPath = join3(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
|
|
7603
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
7604
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
7605
|
+
<plist version="1.0">
|
|
7606
|
+
<dict>
|
|
7607
|
+
<key>Label</key>
|
|
7608
|
+
<string>com.hasna.mementos</string>
|
|
7609
|
+
<key>ProgramArguments</key>
|
|
7610
|
+
<array>
|
|
7611
|
+
<string>/bin/sh</string>
|
|
7612
|
+
<string>-c</string>
|
|
7613
|
+
<string>PATH="$HOME/.bun/bin:$PATH" mementos-serve</string>
|
|
7614
|
+
</array>
|
|
7615
|
+
<key>RunAtLoad</key>
|
|
7616
|
+
<true/>
|
|
7617
|
+
<key>KeepAlive</key>
|
|
7618
|
+
<true/>
|
|
7619
|
+
<key>StandardErrorPath</key>
|
|
7620
|
+
<string>/tmp/mementos.log</string>
|
|
7621
|
+
<key>StandardOutPath</key>
|
|
7622
|
+
<string>/tmp/mementos.log</string>
|
|
7623
|
+
</dict>
|
|
7624
|
+
</plist>
|
|
7625
|
+
`;
|
|
7626
|
+
try {
|
|
7627
|
+
if (existsSync3(plistPath)) {
|
|
7628
|
+
autoStartAlreadyInstalled = true;
|
|
7629
|
+
} else {
|
|
7630
|
+
const launchAgentsDir = join3(home, "Library", "LaunchAgents");
|
|
7631
|
+
if (!existsSync3(launchAgentsDir)) {
|
|
7632
|
+
mkdirSync3(launchAgentsDir, { recursive: true });
|
|
7633
|
+
}
|
|
7634
|
+
writeFileSync2(plistPath, plistContent, "utf-8");
|
|
7635
|
+
}
|
|
7636
|
+
} catch (e) {
|
|
7637
|
+
autoStartError = e instanceof Error ? e.message : String(e);
|
|
7638
|
+
}
|
|
7639
|
+
if (autoStartAlreadyInstalled) {
|
|
7640
|
+
console.log(chalk.dim(" \xB7 Auto-start already configured"));
|
|
7641
|
+
} else if (autoStartError) {
|
|
7642
|
+
console.log(chalk.red(` \u2717 Failed to configure auto-start: ${autoStartError}`));
|
|
7643
|
+
} else {
|
|
7644
|
+
console.log(chalk.green(" \u2713 Auto-start configured (starts on login)"));
|
|
7645
|
+
}
|
|
7646
|
+
if (!autoStartAlreadyInstalled && !autoStartError) {
|
|
7647
|
+
const plistPath2 = join3(home, "Library", "LaunchAgents", "com.hasna.mementos.plist");
|
|
7648
|
+
const loadResult = await run(["launchctl", "load", plistPath2]);
|
|
7649
|
+
if (!loadResult.ok) {
|
|
7650
|
+
console.log(chalk.dim(` \xB7 launchctl load: ${loadResult.output || "already loaded"}`));
|
|
7651
|
+
}
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
let serverRunning = false;
|
|
7655
|
+
try {
|
|
7656
|
+
const res = await fetch("http://127.0.0.1:19428/api/health", {
|
|
7657
|
+
signal: AbortSignal.timeout(1500)
|
|
7658
|
+
});
|
|
7659
|
+
serverRunning = res.ok;
|
|
7660
|
+
} catch {}
|
|
7661
|
+
if (serverRunning) {
|
|
7662
|
+
console.log(chalk.green(" \u2713 Server running on http://127.0.0.1:19428"));
|
|
7663
|
+
} else {
|
|
7664
|
+
console.log(chalk.dim(" \xB7 Server not yet running \u2014 it will start automatically on next login"));
|
|
7665
|
+
console.log(chalk.dim(" (Or start it now: mementos-serve)"));
|
|
7666
|
+
}
|
|
7667
|
+
console.log("");
|
|
7668
|
+
console.log(chalk.bold(" You're all set. Restart Claude Code to activate."));
|
|
7669
|
+
console.log("");
|
|
7670
|
+
console.log(" Quick start:");
|
|
7671
|
+
console.log(` ${chalk.cyan('mementos save "my-preference" "I prefer bun over npm"')}`);
|
|
7672
|
+
console.log(` ${chalk.cyan("mementos list")}`);
|
|
7673
|
+
console.log(` ${chalk.cyan("mementos doctor")}`);
|
|
7674
|
+
console.log("");
|
|
7675
|
+
});
|
|
7021
7676
|
program2.command("save <key> <value>").description("Save a memory (create or upsert)").option("-c, --category <cat>", "Category: preference, fact, knowledge, history").option("-s, --scope <scope>", "Scope: global, shared, private").option("--importance <n>", "Importance 1-10", parseInt).option("--tags <tags>", "Comma-separated tags").option("--summary <text>", "Brief summary").option("--ttl <duration>", "Time-to-live: 30s, 5m, 2h, 1d, 1w, or milliseconds").option("--source <src>", "Source: user, agent, system, auto, imported").option("--template <name>", "Apply a template: correction, preference, decision, learning").action((key, value, opts) => {
|
|
7022
7677
|
try {
|
|
7023
7678
|
const globalOpts = program2.opts();
|
|
@@ -7696,7 +8351,7 @@ program2.command("clean").description("Remove expired memories and enforce quota
|
|
|
7696
8351
|
handleError(e);
|
|
7697
8352
|
}
|
|
7698
8353
|
});
|
|
7699
|
-
program2.command("
|
|
8354
|
+
program2.command("register-agent <name>").alias("init-agent").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").option("-p, --project <id>", "Lock agent to a project (sets active_project_id)").action((name, opts) => {
|
|
7700
8355
|
try {
|
|
7701
8356
|
const globalOpts = program2.opts();
|
|
7702
8357
|
const agent = registerAgent(name, undefined, opts.description, opts.role, opts.project);
|
|
@@ -8139,6 +8794,51 @@ program2.command("doctor").description("Diagnose common issues with the mementos
|
|
|
8139
8794
|
} catch {
|
|
8140
8795
|
checks.push({ name: "REST server", status: "warn", detail: "Could not check REST server" });
|
|
8141
8796
|
}
|
|
8797
|
+
try {
|
|
8798
|
+
const mcpProc = Bun.spawn(["claude", "mcp", "list"], { stdout: "pipe", stderr: "pipe" });
|
|
8799
|
+
const [mcpOut, , mcpExit] = await Promise.all([
|
|
8800
|
+
new Response(mcpProc.stdout).text(),
|
|
8801
|
+
new Response(mcpProc.stderr).text(),
|
|
8802
|
+
mcpProc.exited
|
|
8803
|
+
]);
|
|
8804
|
+
if (mcpExit === 0 && mcpOut.includes("mementos")) {
|
|
8805
|
+
checks.push({ name: "MCP server", status: "ok", detail: "registered with Claude Code" });
|
|
8806
|
+
} else if (mcpExit !== 0) {
|
|
8807
|
+
checks.push({ name: "MCP server", status: "warn", detail: "claude not installed or not accessible" });
|
|
8808
|
+
} else {
|
|
8809
|
+
checks.push({ name: "MCP server", status: "warn", detail: "not registered \u2192 run: mementos init" });
|
|
8810
|
+
}
|
|
8811
|
+
} catch {
|
|
8812
|
+
checks.push({ name: "MCP server", status: "warn", detail: "could not check (is claude CLI installed?)" });
|
|
8813
|
+
}
|
|
8814
|
+
try {
|
|
8815
|
+
const settingsFilePath = join3(homedir2(), ".claude", "settings.json");
|
|
8816
|
+
if (existsSync3(settingsFilePath)) {
|
|
8817
|
+
const settings = JSON.parse(readFileSync2(settingsFilePath, "utf-8"));
|
|
8818
|
+
const hooksObj = settings["hooks"] || {};
|
|
8819
|
+
const stopHooks = hooksObj["Stop"] || [];
|
|
8820
|
+
const hasMementos = stopHooks.some((e) => e.hooks?.some((h) => h.command && h.command.includes("mementos")));
|
|
8821
|
+
checks.push({
|
|
8822
|
+
name: "Stop hook",
|
|
8823
|
+
status: hasMementos ? "ok" : "warn",
|
|
8824
|
+
detail: hasMementos ? "installed (sessions \u2192 memories)" : "not installed \u2192 run: mementos init"
|
|
8825
|
+
});
|
|
8826
|
+
} else {
|
|
8827
|
+
checks.push({ name: "Stop hook", status: "warn", detail: "~/.claude/settings.json not found \u2192 run: mementos init" });
|
|
8828
|
+
}
|
|
8829
|
+
} catch {
|
|
8830
|
+
checks.push({ name: "Stop hook", status: "warn", detail: "could not check stop hook" });
|
|
8831
|
+
}
|
|
8832
|
+
if (process.platform === "darwin") {
|
|
8833
|
+
const plistFilePath = join3(homedir2(), "Library", "LaunchAgents", "com.hasna.mementos.plist");
|
|
8834
|
+
checks.push({
|
|
8835
|
+
name: "Auto-start",
|
|
8836
|
+
status: existsSync3(plistFilePath) ? "ok" : "warn",
|
|
8837
|
+
detail: existsSync3(plistFilePath) ? "configured (starts on login)" : "not configured \u2192 run: mementos init"
|
|
8838
|
+
});
|
|
8839
|
+
} else {
|
|
8840
|
+
checks.push({ name: "Auto-start", status: "ok", detail: `n/a on ${process.platform}` });
|
|
8841
|
+
}
|
|
8142
8842
|
outputDoctorResults(globalOpts, checks);
|
|
8143
8843
|
});
|
|
8144
8844
|
function outputDoctorResults(globalOpts, checks) {
|
|
@@ -8993,7 +9693,7 @@ function diffLines(oldText, newText) {
|
|
|
8993
9693
|
}
|
|
8994
9694
|
}
|
|
8995
9695
|
program2.command("completions <shell>").description("Output shell completion script (bash, zsh, fish)").action((shell) => {
|
|
8996
|
-
const commands = "save recall list update forget search stats export import clean inject context pin unpin archive versions stale doctor tail diff
|
|
9696
|
+
const commands = "init setup save recall list update forget search stats export import clean inject context pin unpin archive versions stale doctor tail diff register-agent agents projects bulk completions config backup restore report profile mcp";
|
|
8997
9697
|
const commandList = commands.split(" ");
|
|
8998
9698
|
switch (shell.toLowerCase()) {
|
|
8999
9699
|
case "bash": {
|
|
@@ -9039,7 +9739,8 @@ compdef _mementos mementos`);
|
|
|
9039
9739
|
doctor: "Check database health",
|
|
9040
9740
|
tail: "Watch recent memories",
|
|
9041
9741
|
diff: "Show memory changes",
|
|
9042
|
-
init: "
|
|
9742
|
+
init: "One-command onboarding setup (MCP + hook + auto-start)",
|
|
9743
|
+
"register-agent": "Register an agent (returns ID)",
|
|
9043
9744
|
agents: "Manage agents",
|
|
9044
9745
|
projects: "Manage projects",
|
|
9045
9746
|
bulk: "Bulk operations",
|
|
@@ -9993,4 +10694,86 @@ synthesisCmd.command("rollback <runId>").description("Roll back a synthesis run"
|
|
|
9993
10694
|
console.log(chalk.red(` Errors: ${result.errors.join(", ")}`));
|
|
9994
10695
|
}
|
|
9995
10696
|
});
|
|
10697
|
+
var sessionCmd = program2.command("session").description("Session auto-memory \u2014 ingest session transcripts for memory extraction");
|
|
10698
|
+
sessionCmd.command("ingest <transcriptFile>").description("Ingest a session transcript file for memory extraction").option("--session-id <id>", "Session ID (default: auto-generated)").option("--agent <id>", "Agent ID").option("--project <id>", "Project ID").option("--source <source>", "Source (claude-code, codex, manual, open-sessions)", "manual").action(async (transcriptFile, opts) => {
|
|
10699
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
10700
|
+
const { createSessionJob: createSessionJob2 } = await Promise.resolve().then(() => (init_session_jobs(), exports_session_jobs));
|
|
10701
|
+
const { enqueueSessionJob: enqueueSessionJob2 } = await Promise.resolve().then(() => (init_session_queue(), exports_session_queue));
|
|
10702
|
+
const transcript = readFileSync3(transcriptFile, "utf-8");
|
|
10703
|
+
const sessionId = opts.sessionId ?? `cli-${Date.now()}`;
|
|
10704
|
+
const job = createSessionJob2({
|
|
10705
|
+
session_id: sessionId,
|
|
10706
|
+
transcript,
|
|
10707
|
+
source: opts.source,
|
|
10708
|
+
agent_id: opts.agent,
|
|
10709
|
+
project_id: opts.project
|
|
10710
|
+
});
|
|
10711
|
+
enqueueSessionJob2(job.id);
|
|
10712
|
+
console.log(chalk.green(`\u2713 Session queued: ${chalk.cyan(job.id)}`));
|
|
10713
|
+
console.log(` Session: ${sessionId}`);
|
|
10714
|
+
console.log(` Length: ${transcript.length} chars`);
|
|
10715
|
+
});
|
|
10716
|
+
sessionCmd.command("status <jobId>").description("Check status of a session extraction job").action(async (jobId) => {
|
|
10717
|
+
const { getSessionJob: getSessionJob2 } = await Promise.resolve().then(() => (init_session_jobs(), exports_session_jobs));
|
|
10718
|
+
const job = getSessionJob2(jobId);
|
|
10719
|
+
if (!job) {
|
|
10720
|
+
console.error(chalk.red(`Job not found: ${jobId}`));
|
|
10721
|
+
process.exit(1);
|
|
10722
|
+
}
|
|
10723
|
+
const statusColor = job.status === "completed" ? chalk.green : job.status === "failed" ? chalk.red : chalk.yellow;
|
|
10724
|
+
console.log(`${chalk.cyan(job.id)} [${statusColor(job.status)}]`);
|
|
10725
|
+
console.log(` Session: ${job.session_id}`);
|
|
10726
|
+
console.log(` Chunks: ${job.chunk_count}`);
|
|
10727
|
+
console.log(` Extracted: ${job.memories_extracted} memories`);
|
|
10728
|
+
if (job.error)
|
|
10729
|
+
console.log(chalk.red(` Error: ${job.error}`));
|
|
10730
|
+
});
|
|
10731
|
+
sessionCmd.command("list").description("List session extraction jobs").option("--agent <id>", "Filter by agent").option("--project <id>", "Filter by project").option("--status <status>", "Filter by status").option("--limit <n>", "Max results", "20").action(async (opts) => {
|
|
10732
|
+
const { listSessionJobs: listSessionJobs2 } = await Promise.resolve().then(() => (init_session_jobs(), exports_session_jobs));
|
|
10733
|
+
const jobs = listSessionJobs2({
|
|
10734
|
+
agent_id: opts.agent,
|
|
10735
|
+
project_id: opts.project,
|
|
10736
|
+
status: opts.status,
|
|
10737
|
+
limit: parseInt(opts.limit)
|
|
10738
|
+
});
|
|
10739
|
+
if (jobs.length === 0) {
|
|
10740
|
+
console.log(chalk.gray("No session jobs found."));
|
|
10741
|
+
return;
|
|
10742
|
+
}
|
|
10743
|
+
for (const job of jobs) {
|
|
10744
|
+
const statusColor = job.status === "completed" ? chalk.green : job.status === "failed" ? chalk.red : chalk.yellow;
|
|
10745
|
+
console.log(`${chalk.cyan(job.id.slice(0, 8))} [${statusColor(job.status)}] ${job.memories_extracted} memories | ${job.created_at.slice(0, 10)}`);
|
|
10746
|
+
}
|
|
10747
|
+
});
|
|
10748
|
+
sessionCmd.command("setup-hook").description("Install mementos session hook into Claude Code or Codex").option("--claude", "Install Claude Code stop hook").option("--codex", "Install Codex session hook").option("--show", "Print hook script instead of installing").action(async (opts) => {
|
|
10749
|
+
const { resolve: resolve4 } = await import("path");
|
|
10750
|
+
const hookPath = resolve4(import.meta.dirname, "../../scripts/hooks");
|
|
10751
|
+
if (opts.claude) {
|
|
10752
|
+
const script = `${hookPath}/claude-stop-hook.ts`;
|
|
10753
|
+
if (opts.show) {
|
|
10754
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
10755
|
+
console.log(readFileSync3(script, "utf-8"));
|
|
10756
|
+
return;
|
|
10757
|
+
}
|
|
10758
|
+
console.log(chalk.bold("Claude Code stop hook installation:"));
|
|
10759
|
+
console.log("");
|
|
10760
|
+
console.log("Add to your .claude/settings.json:");
|
|
10761
|
+
console.log(chalk.cyan(JSON.stringify({
|
|
10762
|
+
hooks: {
|
|
10763
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: `bun ${script}` }] }]
|
|
10764
|
+
}
|
|
10765
|
+
}, null, 2)));
|
|
10766
|
+
console.log("");
|
|
10767
|
+
console.log(`Or run: ${chalk.cyan(`claude hooks add Stop "bun ${script}"`)}`);
|
|
10768
|
+
} else if (opts.codex) {
|
|
10769
|
+
const script = `${hookPath}/codex-stop-hook.ts`;
|
|
10770
|
+
console.log(chalk.bold("Codex session hook installation:"));
|
|
10771
|
+
console.log("");
|
|
10772
|
+
console.log("Add to ~/.codex/config.toml:");
|
|
10773
|
+
console.log(chalk.cyan(`[hooks]
|
|
10774
|
+
session_end = "bun ${script}"`));
|
|
10775
|
+
} else {
|
|
10776
|
+
console.log("Usage: mementos session setup-hook --claude | --codex");
|
|
10777
|
+
}
|
|
10778
|
+
});
|
|
9996
10779
|
program2.parse(process.argv);
|