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/README.md +1 -1
- package/dist/agent.js +7 -7
- package/dist/cli.js +54 -63
- package/dist/crm.d.ts +6 -7
- package/dist/crm.js +175 -98
- package/dist/health.js +1 -1
- package/dist/icloud-discovery.js +7 -7
- package/dist/init-check.js +13 -17
- package/dist/installer.js +79 -57
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +22 -6
- package/dist/message-loop.js +3 -3
- package/dist/progress.js +24 -24
- package/dist/router.js +1 -1
- package/dist/rply-client.d.ts +98 -0
- package/dist/rply-client.js +79 -0
- package/dist/sms-gateway.js +2 -2
- package/dist/tools.js +161 -12
- package/dist/tui.d.ts +6 -0
- package/dist/tui.js +93 -6
- package/dist/tunnel.js +4 -4
- package/dist/ui/app.js +10 -13
- package/dist/ui/progress.js +24 -24
- package/dist/ui/theme.d.ts +2 -2
- package/dist/ui/theme.js +42 -41
- package/package.json +1 -1
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
|
-
// ───
|
|
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.
|
|
176
|
+
out(` ${C.faint('handled')}`);
|
|
90
177
|
for (const item of items.slice(0, max))
|
|
91
|
-
out(` ${C.
|
|
178
|
+
out(` ${C.faint('·')} ${C.dim(item)}`);
|
|
92
179
|
if (items.length > max)
|
|
93
|
-
out(` ${C.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
52
|
+
// silent — caller handles missing network
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
const hostname = getHostname();
|
|
56
56
|
if (!hostname) {
|
|
57
|
-
|
|
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(`
|
|
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(`
|
|
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: "#
|
|
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: "#
|
|
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: "#
|
|
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: "#
|
|
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
|
-
|
|
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' : '#
|
|
44
|
-
}), _jsx(Text, { children: ' ' })
|
|
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.
|
|
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 >
|
|
153
|
-
log(` ${C.
|
|
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
|
};
|
package/dist/ui/progress.js
CHANGED
|
@@ -5,30 +5,30 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { showProgress, hideProgress } from './app.js';
|
|
7
7
|
const LABEL = {
|
|
8
|
-
search_all_messages: '
|
|
9
|
-
get_conversation: '
|
|
10
|
-
profile_contact: '
|
|
11
|
-
get_messages: '
|
|
12
|
-
get_unanswered_messages: '
|
|
13
|
-
get_screen_time: '
|
|
14
|
-
get_browsing: '
|
|
15
|
-
get_calls: 'checking
|
|
16
|
-
scan_sources: '
|
|
17
|
-
lookup_contact: '
|
|
18
|
-
get_email_summary: '
|
|
19
|
-
get_git_activity: '
|
|
20
|
-
get_recent_files: '
|
|
21
|
-
get_shell_history: '
|
|
22
|
-
get_notes: 'reading
|
|
23
|
-
get_claude_history: '
|
|
24
|
-
get_chatgpt_history: '
|
|
25
|
-
get_interests_for_plans: '
|
|
26
|
-
get_calendar: '
|
|
27
|
-
get_reminders: 'checking
|
|
28
|
-
get_notifications: '
|
|
29
|
-
discover_platforms: '
|
|
30
|
-
generate_archetype: '
|
|
31
|
-
search_emails: '
|
|
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 = [];
|
package/dist/ui/theme.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Design System — "Midnight Console"
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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;
|