life-pulse 2.2.2 → 2.3.1

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/tools.js CHANGED
@@ -9,6 +9,7 @@ import { execSync } from 'child_process';
9
9
  import { openDb, safeQuery } from './db.js';
10
10
  import { resolveName, buildContactMap } from './contacts.js';
11
11
  import { getUserName } from './profile.js';
12
+ import * as rply from './rply-client.js';
12
13
  import dayjs from 'dayjs';
13
14
  import { APPLE_EPOCH, CHROME_EPOCH } from './types.js';
14
15
  const home = homedir();
@@ -163,13 +164,92 @@ export const TOOLS = [
163
164
  required: []
164
165
  },
165
166
  async execute(params) {
166
- const db = openDb(join(home, 'Library/Messages/chat.db'));
167
- if (!db)
168
- return JSON.stringify({ error: 'iMessage DB not available' });
169
167
  const days = params.days || 1;
170
168
  const maxResults = params.limit || 50;
171
169
  const contactFilter = params.contact;
172
170
  const keyword = params.keyword;
171
+ // ── RPLY path ──
172
+ if (await rply.isAvailable()) {
173
+ const cutoff = dayjs().subtract(days, 'day');
174
+ const results = [];
175
+ if (keyword) {
176
+ const hits = await rply.searchMessages(keyword, { limit: maxResults * 2 });
177
+ if (hits) {
178
+ for (const h of hits) {
179
+ const d = dayjs(h.message.date);
180
+ if (d.isBefore(cutoff))
181
+ continue;
182
+ if (contactFilter) {
183
+ const conv = await rply.getConversation(h.conversation_id);
184
+ if (conv && !conv.title.toLowerCase().includes(contactFilter.toLowerCase()))
185
+ continue;
186
+ }
187
+ results.push({
188
+ from: h.message.direction === 'sent' ? getUserName() : h.conversation_id,
189
+ text: h.message.content.text.slice(0, 200),
190
+ when: d.format('ddd MMM D h:mm A'),
191
+ direction: h.message.direction,
192
+ });
193
+ if (results.length >= maxResults)
194
+ break;
195
+ }
196
+ return JSON.stringify({ count: results.length, messages: results }, null, 1);
197
+ }
198
+ }
199
+ if (contactFilter) {
200
+ const convos = await rply.searchConversations(contactFilter);
201
+ if (convos?.length) {
202
+ for (const c of convos) {
203
+ const msgs = await rply.listMessages(c.id, { limit: maxResults });
204
+ if (!msgs)
205
+ continue;
206
+ for (const m of msgs.data) {
207
+ if (dayjs(m.date).isBefore(cutoff))
208
+ continue;
209
+ if (m.content?.type !== 'text' || !m.content.text)
210
+ continue;
211
+ results.push({
212
+ from: m.direction === 'sent' ? getUserName() : c.title,
213
+ text: m.content.text.slice(0, 200),
214
+ when: dayjs(m.date).format('ddd MMM D h:mm A'),
215
+ direction: m.direction,
216
+ });
217
+ }
218
+ if (results.length >= maxResults)
219
+ break;
220
+ }
221
+ return JSON.stringify({ count: results.length, messages: results.slice(0, maxResults) }, null, 1);
222
+ }
223
+ }
224
+ // No filter: return recent messages across conversations
225
+ const convos = await rply.listConversations({ limit: 20 });
226
+ if (convos) {
227
+ for (const c of convos.data) {
228
+ const msgs = await rply.listMessages(c.id, { limit: 5 });
229
+ if (!msgs)
230
+ continue;
231
+ for (const m of msgs.data) {
232
+ if (dayjs(m.date).isBefore(cutoff))
233
+ continue;
234
+ if (m.content?.type !== 'text' || !m.content.text)
235
+ continue;
236
+ results.push({
237
+ from: m.direction === 'sent' ? getUserName() : c.title,
238
+ text: m.content.text.slice(0, 200),
239
+ when: dayjs(m.date).format('ddd MMM D h:mm A'),
240
+ direction: m.direction,
241
+ });
242
+ }
243
+ if (results.length >= maxResults)
244
+ break;
245
+ }
246
+ return JSON.stringify({ count: results.length, messages: results.slice(0, maxResults) }, null, 1);
247
+ }
248
+ }
249
+ // ── SQLite fallback ──
250
+ const db = openDb(join(home, 'Library/Messages/chat.db'));
251
+ if (!db)
252
+ return JSON.stringify({ error: 'iMessage DB not available' });
173
253
  const handles = safeQuery(db, 'SELECT ROWID, id FROM handle');
174
254
  const handleMap = new Map(handles.map(h => [h.ROWID, h.id]));
175
255
  const agoNano = (BigInt(daysAgoUnix(days) - APPLE_EPOCH) * BigInt(1e9)).toString();
@@ -215,6 +295,20 @@ export const TOOLS = [
215
295
  required: []
216
296
  },
217
297
  async execute(params) {
298
+ // ── RPLY path ──
299
+ if (await rply.isAvailable()) {
300
+ const result = await rply.listConversations({ category: 'needs_response', limit: 20 });
301
+ if (result) {
302
+ const unanswered = result.data.map(c => ({
303
+ from: c.title,
304
+ text: c.reply?.text?.slice(0, 150) || '(pending)',
305
+ when: dayjs(c.last_message_date).format('ddd MMM D h:mm A'),
306
+ platform: c.platform,
307
+ }));
308
+ return JSON.stringify({ unanswered }, null, 1);
309
+ }
310
+ }
311
+ // ── SQLite fallback ──
218
312
  const db = openDb(join(home, 'Library/Messages/chat.db'));
219
313
  if (!db)
220
314
  return JSON.stringify({ error: 'not available' });
@@ -223,7 +317,6 @@ export const TOOLS = [
223
317
  const handleMap = new Map(handles.map(h => [h.ROWID, h.id]));
224
318
  const agoNano = (BigInt(daysAgoUnix(days) - APPLE_EPOCH) * BigInt(1e9)).toString();
225
319
  const msgs = safeQuery(db, `SELECT text, date, is_from_me, handle_id FROM message WHERE date > ? ORDER BY date DESC`, [agoNano]);
226
- // Group by handle, find where last msg is from them
227
320
  const lastByHandle = new Map();
228
321
  for (const m of msgs) {
229
322
  if (!lastByHandle.has(m.handle_id))
@@ -868,6 +961,23 @@ export const TOOLS = [
868
961
  },
869
962
  async execute(params) {
870
963
  const query = params.query.toLowerCase();
964
+ // ── RPLY path ──
965
+ if (await rply.isAvailable()) {
966
+ const hits = await rply.searchContacts(query);
967
+ if (hits) {
968
+ const seen = new Set();
969
+ const results = hits.filter(c => {
970
+ if (seen.has(c.full_name))
971
+ return false;
972
+ seen.add(c.full_name);
973
+ return true;
974
+ }).slice(0, 10).map(c => ({
975
+ handle: c.identifier, name: c.full_name, platform: c.platform,
976
+ }));
977
+ return JSON.stringify({ results }, null, 1);
978
+ }
979
+ }
980
+ // ── SQLite fallback ──
871
981
  const map = buildContactMap();
872
982
  const matches = [];
873
983
  for (const [handle, name] of map) {
@@ -875,7 +985,6 @@ export const TOOLS = [
875
985
  matches.push({ handle, name });
876
986
  }
877
987
  }
878
- // Dedupe by name
879
988
  const seen = new Set();
880
989
  const unique = matches.filter(m => { if (seen.has(m.name))
881
990
  return false; seen.add(m.name); return true; });
@@ -900,12 +1009,30 @@ export const TOOLS = [
900
1009
  required: ['keywords']
901
1010
  },
902
1011
  async execute(params) {
903
- const db = openDb(join(home, 'Library/Messages/chat.db'));
904
- if (!db)
905
- return JSON.stringify({ error: 'not available' });
906
1012
  const keywords = params.keywords;
907
1013
  const days = params.days || 7;
908
1014
  const maxPer = params.limit || 20;
1015
+ const cutoff = dayjs().subtract(days, 'day');
1016
+ // ── RPLY path ──
1017
+ if (await rply.isAvailable()) {
1018
+ const results = {};
1019
+ for (const kw of keywords) {
1020
+ const hits = await rply.searchMessages(kw, { limit: maxPer * 2 });
1021
+ results[kw] = (hits || [])
1022
+ .filter(h => dayjs(h.message.date).isAfter(cutoff) && h.message.content?.text)
1023
+ .slice(0, maxPer)
1024
+ .map(h => ({
1025
+ from: h.message.direction === 'sent' ? getUserName() : h.conversation_id,
1026
+ text: h.message.content.text.slice(0, 200),
1027
+ when: dayjs(h.message.date).format('ddd MMM D h:mm A'),
1028
+ }));
1029
+ }
1030
+ return JSON.stringify(results, null, 1);
1031
+ }
1032
+ // ── SQLite fallback ──
1033
+ const db = openDb(join(home, 'Library/Messages/chat.db'));
1034
+ if (!db)
1035
+ return JSON.stringify({ error: 'not available' });
909
1036
  const handles = safeQuery(db, 'SELECT ROWID, id FROM handle');
910
1037
  const handleMap = new Map(handles.map(h => [h.ROWID, h.id]));
911
1038
  const agoNano = (BigInt(daysAgoUnix(days) - APPLE_EPOCH) * BigInt(1e9)).toString();
@@ -1177,15 +1304,37 @@ export const TOOLS = [
1177
1304
  required: ['contact']
1178
1305
  },
1179
1306
  async execute(params) {
1180
- const db = openDb(join(home, 'Library/Messages/chat.db'));
1181
- if (!db)
1182
- return JSON.stringify({ error: 'not available' });
1183
1307
  const contactName = params.contact.toLowerCase();
1184
1308
  const days = params.days || 2;
1185
1309
  const maxMsgs = params.limit || 30;
1310
+ const cutoff = dayjs().subtract(days, 'day');
1311
+ // ── RPLY path ──
1312
+ if (await rply.isAvailable()) {
1313
+ const convos = await rply.searchConversations(params.contact);
1314
+ if (convos?.length) {
1315
+ const conv = convos[0];
1316
+ const msgs = await rply.listMessages(conv.id, { limit: maxMsgs * 2 });
1317
+ if (msgs) {
1318
+ const thread = msgs.data
1319
+ .filter(m => m.content?.type === 'text' && m.content.text && dayjs(m.date).isAfter(cutoff))
1320
+ .reverse() // oldest first for thread view
1321
+ .slice(-maxMsgs)
1322
+ .map(m => ({
1323
+ from: m.direction === 'sent' ? getUserName() : conv.title,
1324
+ text: m.content.text.slice(0, 300),
1325
+ when: dayjs(m.date).format('ddd MMM D h:mm A'),
1326
+ }));
1327
+ return JSON.stringify({ contact: conv.title, thread }, null, 1);
1328
+ }
1329
+ }
1330
+ return JSON.stringify({ error: `No contact matching "${params.contact}"` });
1331
+ }
1332
+ // ── SQLite fallback ──
1333
+ const db = openDb(join(home, 'Library/Messages/chat.db'));
1334
+ if (!db)
1335
+ return JSON.stringify({ error: 'not available' });
1186
1336
  const handles = safeQuery(db, 'SELECT ROWID, id FROM handle');
1187
1337
  const handleMap = new Map(handles.map(h => [h.ROWID, h.id]));
1188
- // Find matching handle IDs
1189
1338
  const matchingHandles = handles.filter(h => {
1190
1339
  const name = resolveName(h.id);
1191
1340
  return name.toLowerCase().includes(contactName);
package/dist/tui.d.ts CHANGED
@@ -14,8 +14,14 @@ export declare const GRN: import("chalk").ChalkInstance;
14
14
  export declare const MAG: import("chalk").ChalkInstance;
15
15
  export declare const CYN: import("chalk").ChalkInstance;
16
16
  export declare const HD: import("chalk").ChalkInstance;
17
+ export declare function renderIntro(name?: string): Promise<void>;
17
18
  export declare function renderGreeting(name?: string): void;
18
19
  export declare function renderContextLine(): void;
20
+ export declare function renderReveal(stats: {
21
+ conversations: number;
22
+ innerCircle: number;
23
+ aboutToBreak: number;
24
+ }): Promise<void>;
19
25
  export declare function renderCRMList(contacts: {
20
26
  name: string;
21
27
  lastMsg: {
package/dist/tui.js CHANGED
@@ -26,7 +26,79 @@ function out(text) {
26
26
  else
27
27
  console.log(text);
28
28
  }
29
- // ─── Greeting ───
29
+ // ─── Typewriter intro ───
30
+ import chalk from 'chalk';
31
+ const _sleep = (ms) => new Promise(r => setTimeout(r, ms));
32
+ // Three-tier gold — fades like candlelight
33
+ const G1 = chalk.hex('#d4a574'); // warm amber (name)
34
+ const G2 = chalk.hex('#a8885c'); // muted gold (city + weather)
35
+ const G3 = chalk.hex('#7a6544'); // whisper (closing)
36
+ async function typewrite(text, style, delay = 30) {
37
+ for (const ch of text) {
38
+ process.stdout.write(style(ch));
39
+ await _sleep(delay);
40
+ }
41
+ }
42
+ async function fetchWeather(city) {
43
+ try {
44
+ const ac = new AbortController();
45
+ const t = setTimeout(() => ac.abort(), 2000);
46
+ const r = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=%t|%C`, { signal: ac.signal, headers: { 'User-Agent': 'life-pulse' } });
47
+ clearTimeout(t);
48
+ const raw = (await r.text()).trim();
49
+ const [temp, cond] = raw.split('|');
50
+ if (!temp || !cond)
51
+ return null;
52
+ // "+72°F" → "72°", "Sunny" → "sunny"
53
+ const deg = temp.replace(/[+\s]/g, '').replace(/°[CF]/, '°');
54
+ const sky = cond.trim().toLowerCase();
55
+ return `${deg} and ${sky}`;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ function closingLine(h) {
62
+ if (h < 5)
63
+ return "don't stay up too late.";
64
+ if (h < 12)
65
+ return 'hope you have a beautiful day.';
66
+ if (h < 17)
67
+ return "hope you're having a lovely afternoon.";
68
+ if (h < 21)
69
+ return "hope you're having a wonderful evening.";
70
+ return "don't stay up too late.";
71
+ }
72
+ export async function renderIntro(name) {
73
+ const firstName = name?.split(' ')[0] || name;
74
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
75
+ const city = tz.split('/').pop()?.replace(/_/g, ' ') || '';
76
+ // Fire weather fetch immediately — runs while we type the name
77
+ const weatherP = city ? fetchWeather(city) : Promise.resolve(null);
78
+ process.stdout.write('\x1Bc');
79
+ process.stdout.write('\n');
80
+ // Line 1: "Hey Molly."
81
+ const hey = firstName ? `Hey ${firstName}.` : 'Hey.';
82
+ process.stdout.write(' ');
83
+ await typewrite(hey, G1, 40);
84
+ process.stdout.write('\n');
85
+ await _sleep(300);
86
+ // Line 2: "Los Angeles, 68° and sunny."
87
+ const weather = await weatherP;
88
+ const line2 = weather ? `${city}, ${weather}.` : `${city}.`;
89
+ process.stdout.write(' ');
90
+ await typewrite(line2, G2, 25);
91
+ process.stdout.write('\n');
92
+ await _sleep(250);
93
+ // Line 3: "Hope you're having a wonderful evening."
94
+ const h = new Date().getHours();
95
+ process.stdout.write(' ');
96
+ await typewrite(closingLine(h), G3, 22);
97
+ process.stdout.write('\n\n');
98
+ if (USE_INK)
99
+ ink.initInk();
100
+ }
101
+ // ─── Greeting (legacy / non-interactive fallback) ───
30
102
  export function renderGreeting(name) {
31
103
  if (USE_INK) {
32
104
  ink.initInk();
@@ -49,6 +121,21 @@ export function renderContextLine() {
49
121
  out(` ${C.dim(`${days[d.getDay()]} · ${h12}:${min} ${ampm}`)}`);
50
122
  out('');
51
123
  }
124
+ // ─── Reveal (the moment after discovery) ───
125
+ export async function renderReveal(stats) {
126
+ await _sleep(600);
127
+ process.stdout.write(' ');
128
+ await typewrite(`${stats.conversations} conversations.`, G2, 22);
129
+ await _sleep(400);
130
+ process.stdout.write(' ');
131
+ await typewrite(`${stats.innerCircle} people who matter.`, G1, 25);
132
+ await _sleep(500);
133
+ if (stats.aboutToBreak > 0) {
134
+ process.stdout.write(' ');
135
+ await typewrite(`${stats.aboutToBreak} thread${stats.aboutToBreak === 1 ? '' : 's'} about to break.`, chalk.bold.hex('#c0caf5'), 28);
136
+ }
137
+ process.stdout.write('\n\n');
138
+ }
52
139
  // ─── CRM List ───
53
140
  export function renderCRMList(contacts) {
54
141
  for (const t of contacts.slice(0, 12)) {
@@ -86,11 +173,11 @@ export function renderSection(title, items, bullet, colorBold, colorNorm) {
86
173
  export function renderHandled(items, max = 5) {
87
174
  if (!items.length)
88
175
  return;
89
- out(` ${C.dim('HANDLED')}`);
176
+ out(` ${C.faint('handled')}`);
90
177
  for (const item of items.slice(0, max))
91
- out(` ${C.ok('')} ${C.dim(item)}`);
178
+ out(` ${C.faint('·')} ${C.dim(item)}`);
92
179
  if (items.length > max)
93
- out(` ${C.dim(`+${items.length - max} more`)}`);
180
+ out(` ${C.faint(`+${items.length - max} more`)}`);
94
181
  out('');
95
182
  }
96
183
  // ─── Brief ───
@@ -151,7 +238,7 @@ export async function renderCRMStream(source, spinner) {
151
238
  const gap = Math.max(2, w - 4 - name.length - ago.length);
152
239
  out(` ${C.hd(name)}${' '.repeat(gap)}${C.dim(ago)}`);
153
240
  if (t.relationship) {
154
- out(` ${C.ok(t.relationship.slice(0, w - 8))}`);
241
+ out(` ${C.mid(t.relationship.slice(0, w - 8))}`);
155
242
  }
156
243
  rendered.push(t);
157
244
  }
@@ -167,7 +254,7 @@ export async function renderCRMContact(t) {
167
254
  const gap = Math.max(2, w - 4 - name.length - ago.length);
168
255
  out(` ${C.hd(name)}${' '.repeat(gap)}${C.dim(ago)}`);
169
256
  if (t.relationship)
170
- out(` ${C.ok(t.relationship.slice(0, w - 8))}`);
257
+ out(` ${C.mid(t.relationship.slice(0, w - 8))}`);
171
258
  }
172
259
  // ─── Destroy (call at exit) ───
173
260
  export function destroyUI() {
package/dist/tunnel.js CHANGED
@@ -49,12 +49,12 @@ export function isFunnelActive() {
49
49
  export function startFunnel(port = DEFAULT_PORT) {
50
50
  state.port = port;
51
51
  if (!hasTailscale()) {
52
- process.stderr.write(' tunnel: tailscale not found\n');
52
+ // silent caller handles missing network
53
53
  return null;
54
54
  }
55
55
  const hostname = getHostname();
56
56
  if (!hostname) {
57
- process.stderr.write(' tunnel: cannot determine tailscale hostname\n');
57
+ // silent caller handles missing hostname
58
58
  return null;
59
59
  }
60
60
  // Check if already active
@@ -78,7 +78,7 @@ export function startFunnel(port = DEFAULT_PORT) {
78
78
  child.on('exit', (code) => {
79
79
  state.healthy = false;
80
80
  if (code !== 0) {
81
- process.stderr.write(` tunnel: funnel exited with code ${code}\n`);
81
+ process.stderr.write(` connection dropped\n`);
82
82
  }
83
83
  });
84
84
  // Give it a moment to establish
@@ -86,7 +86,7 @@ export function startFunnel(port = DEFAULT_PORT) {
86
86
  return state.url;
87
87
  }
88
88
  catch (err) {
89
- process.stderr.write(` tunnel: failed to start — ${err instanceof Error ? err.message : String(err)}\n`);
89
+ process.stderr.write(` connection failed\n`);
90
90
  return null;
91
91
  }
92
92
  }
package/dist/ui/app.js CHANGED
@@ -17,31 +17,28 @@ const INIT = {
17
17
  progress: null,
18
18
  };
19
19
  // ─── Components ───────────────────────────────────
20
- const SpinnerLine = ({ label, frame }) => (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#7aa2f7", children: SPIN[frame % SPIN.length] }), ' ', _jsx(Text, { color: "#565f89", children: label })] }));
20
+ const SpinnerLine = ({ label, frame }) => (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#565f89", children: SPIN[frame % SPIN.length] }), ' ', _jsx(Text, { color: "#565f89", children: label })] }));
21
21
  const BAR_W = 20;
22
22
  const MAX_VIS = 4;
23
23
  const Bar = ({ done, total }) => {
24
24
  const f = Math.round((done / Math.max(total, 1)) * BAR_W);
25
- return (_jsxs(Text, { children: [_jsx(Text, { color: "#7aa2f7", children: ''.repeat(f) }), _jsx(Text, { color: "#292e42", children: ''.repeat(BAR_W - f) }), ' ', _jsx(Text, { bold: true, color: "#c0caf5", children: done }), _jsx(Text, { color: "#565f89", children: "/" }), _jsx(Text, { bold: true, color: "#c0caf5", children: total })] }));
25
+ return (_jsxs(Text, { children: [_jsx(Text, { color: "#565f89", children: ''.repeat(f) }), _jsx(Text, { color: "#292e42", children: ''.repeat(BAR_W - f) }), ' ', _jsx(Text, { color: "#565f89", children: done }), _jsx(Text, { color: "#3b4261", children: "/" }), _jsx(Text, { color: "#565f89", children: total })] }));
26
26
  };
27
27
  const ProgressView = ({ progress, frame }) => {
28
28
  const spin = (off = 0) => BAR_CHARS[(frame + off) % BAR_CHARS.length];
29
29
  if (progress.thinking && !progress.items.length && !progress.total) {
30
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { children: [' ', _jsx(Text, { color: "#7aa2f7", children: spin() }), ' ', _jsx(Text, { color: "#c0caf5", children: "thinking\u2026" })] })] }));
30
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { children: [' ', _jsx(Text, { color: "#3b4261", children: spin() }), ' ', _jsx(Text, { color: "#565f89", children: "thinking" })] })] }));
31
31
  }
32
32
  const { phase, done, total, items } = progress;
33
33
  const allDone = done === total && total > 0;
34
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), allDone ? (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#9ece6a", children: '' }), ' ', _jsx(Text, { color: "#565f89", children: phase === 'scanning' ? 'scanned' : 'done' })] })) : total > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [' ', _jsx(Text, { color: "#565f89", children: phase || 'working' }), ' ', _jsx(Bar, { done: done, total: total })] }), items.slice(0, MAX_VIS).map((item, i) => (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#7aa2f7", children: spin(i) }), ' ', _jsx(Text, { color: "#c0caf5", children: item.label }), item.tool && _jsxs(Text, { color: "#565f89", children: [' — ', item.tool] })] }, i))), items.length > MAX_VIS && _jsxs(Text, { color: "#565f89", children: [' ', "+", items.length - MAX_VIS, " more"] })] })) : null, progress.thinking && total > 0 && (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#7aa2f7", children: spin() }), ' ', _jsx(Text, { color: "#c0caf5", children: "thinking\u2026" })] }))] }));
34
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), allDone ? (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#565f89", children: '·' }), ' ', _jsx(Text, { color: "#565f89", children: phase === 'scanning' ? 'scanned' : 'done' })] })) : total > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [' ', _jsx(Text, { color: "#565f89", children: phase || 'working' }), ' ', _jsx(Bar, { done: done, total: total })] }), items.slice(0, MAX_VIS).map((item, i) => (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#3b4261", children: spin(i) }), ' ', _jsx(Text, { color: "#565f89", children: item.label }), item.tool && _jsxs(Text, { color: "#3b4261", children: [' — ', item.tool] })] }, i))), items.length > MAX_VIS && _jsxs(Text, { color: "#3b4261", children: [' ', "+", items.length - MAX_VIS, " more"] })] })) : null, progress.thinking && total > 0 && (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#3b4261", children: spin() }), ' ', _jsx(Text, { color: "#565f89", children: "thinking" })] }))] }));
35
35
  };
36
36
  const CardView = ({ card, num, sel }) => {
37
37
  const opts = card.options || [];
38
- const cat = card.category || '';
39
- const catColor = cat === 'promise' ? '#f7768e' : cat === 'blocker' ? '#e0af68'
40
- : cat === 'bump' ? '#7dcfff' : cat === 'alpha' ? '#bb9af7' : '#565f89';
41
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { children: [' ', _jsxs(Text, { color: "#565f89", children: ['#', num] }), ' ', _jsx(Text, { bold: true, color: catColor, children: cat.toUpperCase() })] }), _jsxs(Text, { bold: true, children: [' ', card.title] }), card.context && _jsxs(Text, { color: "#565f89", children: [' ', card.context] }), _jsx(Text, { children: ' ' }), opts.map((opt, i) => {
38
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { bold: true, color: "#c0caf5", children: [' ', card.title] }), card.context && _jsxs(Text, { color: "#565f89", children: [' ', card.context] }), _jsx(Text, { children: ' ' }), opts.map((opt, i) => {
42
39
  const on = i === sel;
43
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [' ', _jsxs(Text, { color: on ? '#7aa2f7' : '#3b4261', children: [on ? '▸' : ' ', ' '] }), _jsx(Text, { bold: on, color: on ? '#c0caf5' : '#565f89', children: opt.label })] }), opt.description && on && (_jsxs(Text, { color: "#565f89", children: [' ', opt.description] }))] }, opt.label));
44
- }), _jsx(Text, { children: ' ' }), _jsxs(Text, { color: "#3b4261", children: [' ', "\u2191\u2193 navigate \u00B7 \u23CE pick"] })] }));
40
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [' ', _jsxs(Text, { color: on ? '#7aa2f7' : '#292e42', children: [on ? '▸' : ' ', ' '] }), _jsx(Text, { bold: on, color: on ? '#c0caf5' : '#3b4261', children: opt.label })] }), opt.description && (_jsxs(Text, { color: on ? '#565f89' : '#292e42', children: [' ', opt.description] }))] }, opt.label));
41
+ }), _jsx(Text, { children: ' ' })] }));
45
42
  };
46
43
  // ─── Main App ─────────────────────────────────────
47
44
  const App = () => {
@@ -137,7 +134,7 @@ export function updateSpinner(label) {
137
134
  export function hideSpinner(doneMsg) {
138
135
  _update?.(p => ({ ...p, spinner: null }));
139
136
  if (doneMsg)
140
- log(` ${C.ok('✓')} ${C.dim(doneMsg)}`);
137
+ log(` ${C.dim(doneMsg)}`);
141
138
  }
142
139
  // ── Card picker ──
143
140
  export async function pickCard(card, num) {
@@ -149,8 +146,8 @@ export async function pickCard(card, num) {
149
146
  return new Promise(resolve => {
150
147
  _cardResolve = (pick) => {
151
148
  _update?.(p => ({ ...p, card: null, cardSel: 0 }));
152
- const t = card.title.length > 44 ? card.title.slice(0, 43) + '…' : card.title;
153
- log(` ${C.ok('✓')} ${C.dim(t)} ${C.ok('→')} ${pick}`);
149
+ const t = card.title.length > 50 ? card.title.slice(0, 49) + '…' : card.title;
150
+ log(` ${C.dim(t)} ${C.faint('→')} ${C.bright(pick)}`);
154
151
  log('');
155
152
  resolve(pick);
156
153
  };
@@ -5,30 +5,30 @@
5
5
  */
6
6
  import { showProgress, hideProgress } from './app.js';
7
7
  const LABEL = {
8
- search_all_messages: 'checking messages',
9
- get_conversation: 'reading thread',
10
- profile_contact: 'looking up contact',
11
- get_messages: 'checking messages',
12
- get_unanswered_messages: 'checking unreplied texts',
13
- get_screen_time: 'checking screen time',
14
- get_browsing: 'checking browsing',
15
- get_calls: 'checking calls',
16
- scan_sources: 'scanning',
17
- lookup_contact: 'looking up contact',
18
- get_email_summary: 'checking email',
19
- get_git_activity: 'checking projects',
20
- get_recent_files: 'checking recent files',
21
- get_shell_history: 'checking history',
22
- get_notes: 'reading notes',
23
- get_claude_history: 'checking chats',
24
- get_chatgpt_history: 'checking chats',
25
- get_interests_for_plans: 'checking interests',
26
- get_calendar: 'checking calendar',
27
- get_reminders: 'checking reminders',
28
- get_notifications: 'checking notifications',
29
- discover_platforms: 'scanning apps',
30
- generate_archetype: 'building profile',
31
- search_emails: 'searching email',
8
+ search_all_messages: 'reading the room',
9
+ get_conversation: 'listening in',
10
+ profile_contact: 'learning who this is',
11
+ get_messages: 'reading the room',
12
+ get_unanswered_messages: 'finding what you missed',
13
+ get_screen_time: 'tracking your attention',
14
+ get_browsing: 'retracing your steps',
15
+ get_calls: 'checking who called',
16
+ scan_sources: 'pulling threads',
17
+ lookup_contact: 'learning who this is',
18
+ get_email_summary: 'going through your inbox',
19
+ get_git_activity: 'seeing what you shipped',
20
+ get_recent_files: 'noticing what you touched',
21
+ get_shell_history: 'retracing your steps',
22
+ get_notes: 'reading your thoughts',
23
+ get_claude_history: 'seeing what you asked',
24
+ get_chatgpt_history: 'seeing what you asked',
25
+ get_interests_for_plans: 'understanding your world',
26
+ get_calendar: 'looking at your day',
27
+ get_reminders: 'checking what you owe yourself',
28
+ get_notifications: 'catching up',
29
+ discover_platforms: 'figuring out your world',
30
+ generate_archetype: 'figuring out who you are',
31
+ search_emails: 'digging through mail',
32
32
  };
33
33
  export class InkProgress {
34
34
  tools = [];
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Design System — "Midnight Console"
3
3
  *
4
- * Tokyo Night inspired. Warm accents. NOT generic AI-slop purple.
5
- * Opinionated palette with clear text hierarchy.
4
+ * Restraint is luxury. One accent color. Everything else is grayscale.
5
+ * Category distinction comes from position, not color.
6
6
  */
7
7
  export declare const C: {
8
8
  accent: import("chalk").ChalkInstance;