truememory-mirror 1.0.0 → 1.0.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/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.0';
12
- const DEFAULT_API_URL = 'https://truememory-mirror.vercel.app';
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
- TrueMemory Mirror v${VERSION}
76
+ ${BANNER}
77
+ ${DIM}v${VERSION}${R}
56
78
 
57
- Usage: npx truememory-mirror [options]
79
+ ${WHITE}usage:${R} npx truememory-mirror [options]
58
80
 
59
- Options:
60
- --limit <n> Max sessions to analyze (default: 500)
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 Output raw profile JSON instead of opening browser
63
- --no-open Don't open browser automatically
64
- --delete <id> Delete a profile by ID
65
- --api-url <url> API endpoint (default: ${DEFAULT_API_URL})
66
- -h, --help Show this 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 = 25) {
102
+ function progressBar(pct, width = 30) {
81
103
  const filled = Math.round(pct * width);
82
104
  const empty = width - filled;
83
- return '█'.repeat(filled) + '░'.repeat(empty);
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(` Profile ${id} deleted.\n`);
118
+ write(` ${G}✓${R} profile ${id} deleted\n`);
97
119
  } else {
98
- write(` ✗ Failed to delete profile ${id} (${resp.status})\n`);
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(`\n TrueMemory Mirror v${VERSION}\n\n`);
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
- // Step 1: Scan
115
- write(' Scanning for AI conversations...\n');
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(` Parsing Claude Code sessions (${files.length} found)...\n`);
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
- claudeSessions.push(session);
131
- }
132
- } catch {
133
- // skip unparseable sessions
134
- }
149
+ if (session.messages.length > 0) claudeSessions.push(session);
150
+ } catch {}
135
151
  }
136
- write(` ✓ Claude Code — ${claudeSessions.length} conversations (~/.claude/)\n`);
152
+ clearLine();
153
+ write(` ${G}✓${R} claude code ${WHITE}${claudeSessions.length}${R} conversations\n`);
137
154
  } else {
138
- write('Claude Code — not found\n');
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(` Parsing Codex CLI sessions (${files.length} found)...\n`);
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
- codexSessions.push(session);
151
- }
152
- } catch {
153
- // skip unparseable sessions
154
- }
165
+ if (session.messages.length > 0) codexSessions.push(session);
166
+ } catch {}
155
167
  }
156
- write(` ✓ Codex CLI — ${codexSessions.length} conversations (~/.codex/)\n`);
168
+ clearLine();
169
+ write(` ${G}✓${R} codex cli ${WHITE}${codexSessions.length}${R} conversations\n`);
157
170
  } else {
158
- write('Codex CLI — not found\n');
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('\n No AI conversations found. Make sure you have Claude Code or Codex CLI installed.\n\n');
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
- // Warn if too few messages
168
- const totalMessages = claudeSessions.reduce((s, c) => s + c.messages.length, 0)
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
- // Step 4: Sanitize & unify
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
- // Step 5: Submit
199
- write(' Uploading & extracting identity traits...\n');
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 ✗ Upload failed: ${err.message}\n\n`);
205
+ write(`\n ✗ upload failed: ${err.message}\n\n`);
206
206
  process.exit(1);
207
207
  }
208
208
 
209
- // Step 6: Poll for status
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 statusText = '';
215
-
216
- if (status.status === 'queued') {
217
- statusText = `Position ${status.queue_position || '?'} in queue`;
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
- statusText = 'Building profile';
219
+ detail = `${DIM}building profile${R}`;
225
220
  } else if (status.status === 'predicting') {
226
- statusText = 'Running scenario predictions';
221
+ detail = `${DIM}running predictions${R}`;
227
222
  }
228
223
 
229
224
  clearLine();
230
- write(` ${bar} ${Math.round(pct * 100)}% ${statusText}`);
225
+ write(` ${bar} ${WHITE}${Math.round(pct * 100)}%${R} ${detail}`);
231
226
  });
232
227
  } catch (err) {
233
- write(`\n\n ✗ Extraction failed: ${err.message}\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)} 100%\n\n`);
239
- write(` Your identity mirror is ready.\n`);
240
- write(` ${result.url}\n\n`);
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 Error: ${err.message}\n\n`);
243
+ process.stderr.write(`\n error: ${err.message}\n\n`);
249
244
  process.exit(1);
250
245
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "truememory-mirror",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "See yourself through your AI conversations",
5
5
  "bin": {
6
6
  "truememory-mirror": "./bin/mirror.js"
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;