quilltap 4.10.0-dev.146 → 4.10.0-dev.173

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.
@@ -1,131 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * `quilltap memories consolidate` — fold clusters of hot memories into
5
- * digests. The CLI's database access is read-only and cannot call a model, so
6
- * this resolves the character locally and then asks the running server
7
- * (POST /api/v1/memories?action=consolidate). A dry run is rehearsed in the
8
- * server process and prints the proposed digests; a real run is queued as a
9
- * background job.
10
- */
11
-
12
- const RESET = '\x1b[0m';
13
- const BOLD = '\x1b[1m';
14
- const DIM = '\x1b[2m';
15
- const GREEN = '\x1b[32m';
16
- const YELLOW = '\x1b[33m';
17
- const CYAN = '\x1b[36m';
18
-
19
- function clip(text, n) {
20
- const flat = String(text || '').replace(/\s+/g, ' ').trim();
21
- return flat.length > n ? flat.slice(0, n - 1) + '…' : flat;
22
- }
23
-
24
- function bucketLabel(bucket) {
25
- if (bucket.kind === 'self') return 'about self';
26
- if (bucket.kind === 'other') return `about ${bucket.subjectName}`;
27
- return 'about no one';
28
- }
29
-
30
- /** Pretty-print a ConsolidationReport as cluster → members → digests. */
31
- function renderReport(report, write = (s) => process.stdout.write(s)) {
32
- const stats = report.stats || {};
33
- write(`${BOLD}Consolidation ${report.dryRun ? 'dry run' : 'report'}${RESET}` +
34
- `${report.characterName ? ` for ${report.characterName}` : ''}\n`);
35
- if (report.skippedReason) {
36
- write(`${YELLOW}Skipped: ${report.skippedReason}${RESET}\n`);
37
- return;
38
- }
39
- const clusters = report.clusters || [];
40
- clusters.forEach((cluster, idx) => {
41
- write(`\n${BOLD}Cluster ${idx + 1}${RESET} ${DIM}(${bucketLabel(cluster.bucket)}, ${cluster.clusterKind}, ${cluster.status})${RESET}\n`);
42
- if (cluster.error) write(` ${YELLOW}${cluster.error}${RESET}\n`);
43
- const standalone = new Set(cluster.keepStandalone || []);
44
- (cluster.memberIds || []).forEach((id, i) => {
45
- const tag = standalone.has(id) ? ` ${DIM}[kept standalone]${RESET}` : '';
46
- write(` ${DIM}-${RESET} ${DIM}${String(id).slice(0, 8)}${RESET} ${clip(cluster.memberContents[i], 160)}${tag}\n`);
47
- });
48
- for (const d of cluster.digests || []) {
49
- write(` ${GREEN}=>${RESET} ${CYAN}${d.action === 'update' ? 'update digest' : 'new digest'}${RESET} ` +
50
- `${DIM}(imp ${Number(d.importance).toFixed(2)}, from ${(d.memberIds || []).length})${RESET}\n`);
51
- write(` ${clip(d.content, 400)}\n`);
52
- }
53
- for (const c of cluster.contradictions || []) {
54
- write(` ${YELLOW}contradiction${RESET}: ${clip(c.note, 200)} ${DIM}(${String(c.olderId).slice(0, 8)} -> ${String(c.newerId).slice(0, 8)})${RESET}\n`);
55
- }
56
- });
57
- if (clusters.length === 0) write('\n(no clusters worth folding)\n');
58
- write(`\n${DIM}candidates ${stats.candidates ?? 0}, clusters found ${stats.clustersFound ?? 0}, ` +
59
- `attempted ${stats.clustersAttempted ?? 0}, deferred ${stats.clustersDeferred ?? 0}, ` +
60
- `${((stats.durationMs ?? 0) / 1000).toFixed(1)}s${stats.budgetExhausted ? ', time budget exhausted' : ''}${RESET}\n`);
61
- }
62
-
63
- /** Build the request body for the consolidate action from parsed CLI flags. */
64
- function buildConsolidateBody(characterId, flags) {
65
- const body = { characterId, dryRun: flags.dryRun === true };
66
- if (flags.max > 0) body.maxClustersPerRun = flags.max;
67
- if (flags.threshold >= 0) body.clusterThreshold = flags.threshold;
68
- return body;
69
- }
70
-
71
- /**
72
- * @param {object} flags parsed memories flags
73
- * @param {(flags: object) => Promise<string>} resolveCharacterId resolves --character to a UUID
74
- */
75
- async function cmdConsolidate(flags, resolveCharacterId) {
76
- if (!flags.character || flags.character === 'all') {
77
- console.error('Error: memories consolidate requires --character <name|id>.');
78
- process.exit(1);
79
- }
80
- const characterId = await resolveCharacterId(flags);
81
- if (!/^[0-9a-fA-F-]{36}$/.test(characterId)) {
82
- console.error(`Error: could not resolve --character to a UUID (got "${characterId}").`);
83
- process.exit(1);
84
- }
85
-
86
- const port = flags.port || 3000;
87
- const url = `http://localhost:${port}/api/v1/memories?action=consolidate`;
88
- const body = buildConsolidateBody(characterId, flags);
89
-
90
- if (!flags.json) {
91
- process.stderr.write(`${BOLD}${body.dryRun ? 'Rehearsing' : 'Queuing'} consolidation${RESET} via ${DIM}${url}${RESET}\n`);
92
- }
93
-
94
- let res;
95
- try {
96
- res = await fetch(url, {
97
- method: 'POST',
98
- headers: { 'Content-Type': 'application/json' },
99
- body: JSON.stringify(body),
100
- });
101
- } catch (err) {
102
- console.error(`Could not reach Quilltap server at http://localhost:${port}: ${err.message}`);
103
- console.error('Consolidation requires the running server (the model and embeddings live there).');
104
- console.error('Start it with: npm run dev');
105
- process.exit(1);
106
- }
107
-
108
- let payload = null;
109
- try {
110
- const text = await res.text();
111
- payload = text ? JSON.parse(text) : null;
112
- } catch { /* leave payload null */ }
113
-
114
- if (!res.ok) {
115
- console.error(`Error: ${(payload && payload.error) || `HTTP ${res.status}`}`);
116
- process.exit(1);
117
- }
118
-
119
- if (flags.json) {
120
- process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
121
- return;
122
- }
123
-
124
- if (body.dryRun) {
125
- renderReport(payload.report);
126
- } else {
127
- console.log(`Consolidation queued as job ${payload.jobId}. It runs in the background; watch logs/combined.log.`);
128
- }
129
- }
130
-
131
- module.exports = { cmdConsolidate, renderReport, buildConsolidateBody };