truememory-mirror 1.0.1 → 1.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/bin/mirror.js +72 -77
- package/package.json +1 -1
- package/src/unified.js +0 -2
package/bin/mirror.js
CHANGED
|
@@ -8,8 +8,29 @@ import { unifyMessages } from '../src/unified.js';
|
|
|
8
8
|
import { submitForExtraction, pollStatus } from '../src/api-client.js';
|
|
9
9
|
import { exec } from 'child_process';
|
|
10
10
|
|
|
11
|
-
const VERSION = '1.0.
|
|
12
|
-
const DEFAULT_API_URL = 'https://
|
|
11
|
+
const VERSION = '1.0.1';
|
|
12
|
+
const DEFAULT_API_URL = 'https://mirror.truememory.net';
|
|
13
|
+
|
|
14
|
+
const G = '\x1b[32m';
|
|
15
|
+
const DIM = '\x1b[2m';
|
|
16
|
+
const BOLD = '\x1b[1m';
|
|
17
|
+
const R = '\x1b[0m';
|
|
18
|
+
const WHITE = '\x1b[37m';
|
|
19
|
+
|
|
20
|
+
const BANNER = `
|
|
21
|
+
${G}${BOLD} ███ ███ ██ ██████ ██████ ██████ ██████${R}
|
|
22
|
+
${G}${BOLD} ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██${R}
|
|
23
|
+
${G}${BOLD} ██ ████ ██ ██ ██████ ██████ ██ ██ ██████${R}
|
|
24
|
+
${G}${BOLD} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██${R}
|
|
25
|
+
${G}${BOLD} ██ ██ ██ ██ ██ ██ ██ ██████ ██ ██${R}
|
|
26
|
+
${DIM}${G} ──────────────────────────────────────────────────${R}
|
|
27
|
+
${DIM}${G} ██ ██ ██ ██ ██ ██ ██ ██████ ██ ██${R}
|
|
28
|
+
${DIM}${G} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██${R}
|
|
29
|
+
${DIM}${G} ██ ████ ██ ██ ██████ ██████ ██ ██ ██████${R}
|
|
30
|
+
${DIM}${G} ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██${R}
|
|
31
|
+
${DIM}${G} ███ ███ ██ ██████ ██████ ██████ ██████${R}
|
|
32
|
+
${DIM} ▓ truememory ▓${R}
|
|
33
|
+
`;
|
|
13
34
|
|
|
14
35
|
function parseArgs(argv) {
|
|
15
36
|
const args = {
|
|
@@ -52,18 +73,19 @@ function parseArgs(argv) {
|
|
|
52
73
|
|
|
53
74
|
function printHelp() {
|
|
54
75
|
write(`
|
|
55
|
-
|
|
76
|
+
${BANNER}
|
|
77
|
+
${DIM}v${VERSION}${R}
|
|
56
78
|
|
|
57
|
-
|
|
79
|
+
${WHITE}usage:${R} npx truememory-mirror [options]
|
|
58
80
|
|
|
59
|
-
|
|
60
|
-
--limit <n>
|
|
81
|
+
${WHITE}options:${R}
|
|
82
|
+
--limit <n> max sessions to analyze (default: 500)
|
|
61
83
|
--source <type> claude, codex, or all (default: all)
|
|
62
|
-
--json
|
|
63
|
-
--no-open
|
|
64
|
-
--delete <id>
|
|
65
|
-
--api-url <url> API endpoint
|
|
66
|
-
-h, --help
|
|
84
|
+
--json output raw profile JSON
|
|
85
|
+
--no-open don't open browser automatically
|
|
86
|
+
--delete <id> delete a profile by ID
|
|
87
|
+
--api-url <url> custom API endpoint
|
|
88
|
+
-h, --help show this help
|
|
67
89
|
`);
|
|
68
90
|
}
|
|
69
91
|
|
|
@@ -77,10 +99,10 @@ function clearLine() {
|
|
|
77
99
|
if (!_jsonMode) process.stderr.write('\r\x1b[K');
|
|
78
100
|
}
|
|
79
101
|
|
|
80
|
-
function progressBar(pct, width =
|
|
102
|
+
function progressBar(pct, width = 30) {
|
|
81
103
|
const filled = Math.round(pct * width);
|
|
82
104
|
const empty = width - filled;
|
|
83
|
-
return '█'.repeat(filled)
|
|
105
|
+
return `${G}${'█'.repeat(filled)}${DIM}${'░'.repeat(empty)}${R}`;
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
function openBrowser(url) {
|
|
@@ -93,94 +115,72 @@ function openBrowser(url) {
|
|
|
93
115
|
async function handleDelete(id, apiUrl) {
|
|
94
116
|
const resp = await fetch(`${apiUrl}/api/profile/${id}`, { method: 'DELETE' });
|
|
95
117
|
if (resp.ok) {
|
|
96
|
-
write(`
|
|
118
|
+
write(` ${G}✓${R} profile ${id} deleted\n`);
|
|
97
119
|
} else {
|
|
98
|
-
write(` ✗
|
|
120
|
+
write(` ✗ failed to delete profile ${id} (${resp.status})\n`);
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
async function main() {
|
|
103
125
|
const args = parseArgs(process.argv);
|
|
104
|
-
|
|
105
126
|
_jsonMode = args.json;
|
|
106
127
|
|
|
107
|
-
write(
|
|
128
|
+
if (!_jsonMode) write(BANNER + '\n');
|
|
108
129
|
|
|
109
130
|
if (args.delete) {
|
|
110
131
|
await handleDelete(args.delete, args.apiUrl);
|
|
111
132
|
return;
|
|
112
133
|
}
|
|
113
134
|
|
|
114
|
-
//
|
|
115
|
-
write(
|
|
135
|
+
// --- Scan ---
|
|
136
|
+
write(` ${DIM}scanning for conversations...${R}\n`);
|
|
116
137
|
const sources = await scan(args.source);
|
|
117
138
|
|
|
118
139
|
let claudeSessions = [];
|
|
119
140
|
let codexSessions = [];
|
|
120
141
|
|
|
121
|
-
// Step 2: Parse Claude Code
|
|
122
142
|
if (sources.claude_code) {
|
|
123
143
|
const files = sources.claude_code.sessions;
|
|
124
|
-
write(`
|
|
144
|
+
write(` ${DIM}parsing claude code...${R} ${files.length} files`);
|
|
125
145
|
const parseLimit = Math.min(files.length, args.limit);
|
|
126
146
|
for (let i = 0; i < parseLimit; i++) {
|
|
127
147
|
try {
|
|
128
148
|
const session = await parseClaudeCodeSession(files[i].path);
|
|
129
|
-
if (session.messages.length > 0)
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
// skip unparseable sessions
|
|
134
|
-
}
|
|
149
|
+
if (session.messages.length > 0) claudeSessions.push(session);
|
|
150
|
+
} catch {}
|
|
135
151
|
}
|
|
136
|
-
|
|
152
|
+
clearLine();
|
|
153
|
+
write(` ${G}✓${R} claude code ${WHITE}${claudeSessions.length}${R} conversations\n`);
|
|
137
154
|
} else {
|
|
138
|
-
write(
|
|
155
|
+
write(` ${DIM}– claude code not found${R}\n`);
|
|
139
156
|
}
|
|
140
157
|
|
|
141
|
-
// Step 3: Parse Codex
|
|
142
158
|
if (sources.codex) {
|
|
143
159
|
const files = sources.codex.sessions;
|
|
144
|
-
write(`
|
|
160
|
+
write(` ${DIM}parsing codex cli...${R} ${files.length} files`);
|
|
145
161
|
const parseLimit = Math.min(files.length, args.limit);
|
|
146
162
|
for (let i = 0; i < parseLimit; i++) {
|
|
147
163
|
try {
|
|
148
164
|
const session = await parseCodexSession(files[i].path);
|
|
149
|
-
if (session.messages.length > 0)
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
} catch {
|
|
153
|
-
// skip unparseable sessions
|
|
154
|
-
}
|
|
165
|
+
if (session.messages.length > 0) codexSessions.push(session);
|
|
166
|
+
} catch {}
|
|
155
167
|
}
|
|
156
|
-
|
|
168
|
+
clearLine();
|
|
169
|
+
write(` ${G}✓${R} codex cli ${WHITE}${codexSessions.length}${R} conversations\n`);
|
|
157
170
|
} else {
|
|
158
|
-
write(
|
|
171
|
+
write(` ${DIM}– codex cli not found${R}\n`);
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
const totalSessions = claudeSessions.length + codexSessions.length;
|
|
162
175
|
if (totalSessions === 0) {
|
|
163
|
-
write(
|
|
176
|
+
write(`\n no AI conversations found.\n make sure you have Claude Code or Codex CLI installed.\n\n`);
|
|
164
177
|
process.exit(1);
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
+ codexSessions.reduce((s, c) => s + c.messages.length, 0);
|
|
170
|
-
|
|
171
|
-
if (totalMessages < 50) {
|
|
172
|
-
write(`\n ⚠ Found only ${totalMessages} messages. For a meaningful profile,\n`);
|
|
173
|
-
write(' we recommend 100+ conversations. Results may be sparse.\n');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
write(` ${'─'.repeat(40)}\n`);
|
|
177
|
-
write(` Total: ${totalSessions} conversations`);
|
|
178
|
-
if (totalSessions > args.limit) {
|
|
179
|
-
write(` (using most recent ${args.limit})`);
|
|
180
|
-
}
|
|
181
|
-
write('\n\n');
|
|
180
|
+
write(` ${DIM}────────────────────────────────────${R}\n`);
|
|
181
|
+
write(` ${WHITE}${totalSessions}${R} conversations total\n\n`);
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// --- Sanitize & unify ---
|
|
184
184
|
for (const session of claudeSessions) {
|
|
185
185
|
session.messages = sanitizeMessages(session.messages);
|
|
186
186
|
}
|
|
@@ -195,49 +195,44 @@ async function main() {
|
|
|
195
195
|
return;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
//
|
|
199
|
-
write(
|
|
198
|
+
// --- Extract ---
|
|
199
|
+
write(` ${DIM}extracting identity profile...${R}\n`);
|
|
200
200
|
|
|
201
201
|
let result;
|
|
202
202
|
try {
|
|
203
203
|
result = await submitForExtraction(payload, args.apiUrl);
|
|
204
204
|
} catch (err) {
|
|
205
|
-
write(`\n ✗
|
|
205
|
+
write(`\n ✗ upload failed: ${err.message}\n\n`);
|
|
206
206
|
process.exit(1);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
//
|
|
209
|
+
// --- Poll ---
|
|
210
210
|
try {
|
|
211
211
|
await pollStatus(result.profile_id, args.apiUrl, (status) => {
|
|
212
212
|
const pct = status.progress || 0;
|
|
213
213
|
const bar = progressBar(pct);
|
|
214
|
-
let
|
|
215
|
-
|
|
216
|
-
if (status.status === '
|
|
217
|
-
|
|
218
|
-
} else if (status.status === 'extracting') {
|
|
219
|
-
statusText = `Extracting traits`;
|
|
220
|
-
if (status.claims_found) {
|
|
221
|
-
statusText += ` (${status.claims_found} claims found)`;
|
|
222
|
-
}
|
|
214
|
+
let detail = '';
|
|
215
|
+
|
|
216
|
+
if (status.status === 'extracting' && status.claims_found) {
|
|
217
|
+
detail = `${DIM}${status.claims_found} claims found${R}`;
|
|
223
218
|
} else if (status.status === 'consolidating') {
|
|
224
|
-
|
|
219
|
+
detail = `${DIM}building profile${R}`;
|
|
225
220
|
} else if (status.status === 'predicting') {
|
|
226
|
-
|
|
221
|
+
detail = `${DIM}running predictions${R}`;
|
|
227
222
|
}
|
|
228
223
|
|
|
229
224
|
clearLine();
|
|
230
|
-
write(` ${bar}
|
|
225
|
+
write(` ${bar} ${WHITE}${Math.round(pct * 100)}%${R} ${detail}`);
|
|
231
226
|
});
|
|
232
227
|
} catch (err) {
|
|
233
|
-
write(`\n\n ✗
|
|
228
|
+
write(`\n\n ✗ extraction failed: ${err.message}\n\n`);
|
|
234
229
|
process.exit(1);
|
|
235
230
|
}
|
|
236
231
|
|
|
237
232
|
clearLine();
|
|
238
|
-
write(` ${progressBar(1.0)}
|
|
239
|
-
write(`
|
|
240
|
-
write(`
|
|
233
|
+
write(` ${progressBar(1.0)} ${WHITE}100%${R}\n\n`);
|
|
234
|
+
write(` ${G}✓${R} your mirror is ready\n`);
|
|
235
|
+
write(` ${G}→${R} ${result.url}\n\n`);
|
|
241
236
|
|
|
242
237
|
if (!args.noOpen) {
|
|
243
238
|
openBrowser(result.url);
|
|
@@ -245,6 +240,6 @@ async function main() {
|
|
|
245
240
|
}
|
|
246
241
|
|
|
247
242
|
main().catch(err => {
|
|
248
|
-
process.stderr.write(`\n
|
|
243
|
+
process.stderr.write(`\n error: ${err.message}\n\n`);
|
|
249
244
|
process.exit(1);
|
|
250
245
|
});
|
package/package.json
CHANGED
package/src/unified.js
CHANGED
|
@@ -11,12 +11,10 @@ export function unifyMessages(claudeSessions, codexSessions, limit = 500) {
|
|
|
11
11
|
|
|
12
12
|
let selected = allSessions.slice(0, limit);
|
|
13
13
|
|
|
14
|
-
// Size guard: trim oldest sessions until payload fits
|
|
15
14
|
let payload = buildPayload(selected);
|
|
16
15
|
while (JSON.stringify(payload).length > MAX_PAYLOAD_BYTES && selected.length > 10) {
|
|
17
16
|
selected = selected.slice(0, Math.floor(selected.length * 0.8));
|
|
18
17
|
payload = buildPayload(selected);
|
|
19
|
-
process.stderr.write(` Payload too large, trimmed to ${selected.length} sessions\n`);
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
return payload;
|