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/installer.js
CHANGED
|
@@ -19,7 +19,6 @@ import { join } from 'path';
|
|
|
19
19
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
20
20
|
import { execSync } from 'child_process';
|
|
21
21
|
import { createInterface } from 'readline';
|
|
22
|
-
import chalk from 'chalk';
|
|
23
22
|
import { runPermissionFlow, hasRequiredPermissions } from './permissions.js';
|
|
24
23
|
import { discoverPlatforms, savePlatforms } from './platforms.js';
|
|
25
24
|
import { runDiscovery, formatDiscoveryReport } from './icloud-discovery.js';
|
|
@@ -29,9 +28,10 @@ import { updateAutoKnowledge } from './knowledge.js';
|
|
|
29
28
|
import { scanForSkills } from './skill-loader.js';
|
|
30
29
|
import { hasTailscale, getHostname as getTailscaleHostname, startFunnel } from './tunnel.js';
|
|
31
30
|
import { startGateway } from './sms-gateway.js';
|
|
32
|
-
import { renderCRMList, renderBrief, Spinner,
|
|
31
|
+
import { renderCRMList, renderBrief, Spinner, DIM, HD, MID } from './tui.js';
|
|
33
32
|
import { saveContactSummaries } from './intelligence.js';
|
|
34
33
|
import { startSession } from './session-progress.js';
|
|
34
|
+
import * as rply from './rply-client.js';
|
|
35
35
|
const BASE = join(homedir(), '.life-pulse');
|
|
36
36
|
const STATE_PATH = join(BASE, 'install-state.json');
|
|
37
37
|
function loadState() {
|
|
@@ -61,9 +61,8 @@ function isDone(state, step) {
|
|
|
61
61
|
}
|
|
62
62
|
// ─── TUI Helpers ────────────────────────────────────────────────
|
|
63
63
|
function stepHeader(num, title, done) {
|
|
64
|
-
const icon = done ? GRN('✓') : chalk.bold.hex('#7AA2F7')(`${num}`);
|
|
65
64
|
const label = done ? DIM(title) : HD(title);
|
|
66
|
-
console.log(`\n ${
|
|
65
|
+
console.log(`\n ${label}`);
|
|
67
66
|
}
|
|
68
67
|
async function prompt(question) {
|
|
69
68
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -74,14 +73,24 @@ async function prompt(question) {
|
|
|
74
73
|
});
|
|
75
74
|
});
|
|
76
75
|
}
|
|
76
|
+
// ─── Platform Hints ─────────────────────────────────────────────
|
|
77
|
+
const PLATFORM_HINTS = {
|
|
78
|
+
slack: 'sign into slack.com in Safari',
|
|
79
|
+
x: 'sign into x.com in Safari',
|
|
80
|
+
email: 'sign into gmail.com in Safari',
|
|
81
|
+
instagram: 'sign into instagram.com in Safari',
|
|
82
|
+
linkedin: 'sign into linkedin.com in Safari',
|
|
83
|
+
whatsApp: 'sign into web.whatsapp.com in Safari',
|
|
84
|
+
telegram: 'sign into web.telegram.org in Safari',
|
|
85
|
+
};
|
|
86
|
+
function platformHint(platform) {
|
|
87
|
+
return PLATFORM_HINTS[platform] || '';
|
|
88
|
+
}
|
|
77
89
|
// ─── Main Installer Flow ────────────────────────────────────────
|
|
78
90
|
export async function runInstaller(apiKey) {
|
|
79
91
|
const state = loadState();
|
|
80
92
|
const spinner = process.stdin.isTTY ? new Spinner() : undefined;
|
|
81
93
|
console.log();
|
|
82
|
-
console.log(chalk.bold.hex('#7AA2F7')(' life-pulse'));
|
|
83
|
-
console.log(DIM(' personal AI operating system'));
|
|
84
|
-
console.log();
|
|
85
94
|
// ── Step 1: Pre-flight ──
|
|
86
95
|
stepHeader(1, 'Pre-flight', isDone(state, 'preflight'));
|
|
87
96
|
if (!isDone(state, 'preflight')) {
|
|
@@ -107,15 +116,15 @@ export async function runInstaller(apiKey) {
|
|
|
107
116
|
process.env.ANTHROPIC_API_KEY = key;
|
|
108
117
|
apiKey = key;
|
|
109
118
|
state.apiKeySet = true;
|
|
110
|
-
console.log(
|
|
119
|
+
console.log(DIM(' API key saved'));
|
|
111
120
|
}
|
|
112
121
|
else {
|
|
113
|
-
console.log(
|
|
122
|
+
console.log(DIM(' no API key — some features will be limited'));
|
|
114
123
|
}
|
|
115
124
|
}
|
|
116
125
|
else {
|
|
117
126
|
state.apiKeySet = true;
|
|
118
|
-
console.log(
|
|
127
|
+
console.log(DIM(' API key configured'));
|
|
119
128
|
}
|
|
120
129
|
markDone(state, 'preflight');
|
|
121
130
|
}
|
|
@@ -125,13 +134,13 @@ export async function runInstaller(apiKey) {
|
|
|
125
134
|
if (process.stdin.isTTY) {
|
|
126
135
|
const allGranted = await runPermissionFlow();
|
|
127
136
|
if (allGranted) {
|
|
128
|
-
console.log(
|
|
137
|
+
console.log(DIM(' all permissions granted'));
|
|
129
138
|
}
|
|
130
139
|
else if (hasRequiredPermissions()) {
|
|
131
140
|
console.log(DIM(' required permissions granted (some optional skipped)'));
|
|
132
141
|
}
|
|
133
142
|
else {
|
|
134
|
-
console.log(
|
|
143
|
+
console.log(HD(' some required permissions missing — re-run to retry'));
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
markDone(state, 'permissions');
|
|
@@ -139,9 +148,9 @@ export async function runInstaller(apiKey) {
|
|
|
139
148
|
// ── Step 3: Discovery ──
|
|
140
149
|
stepHeader(3, 'Discovery', isDone(state, 'discovery'));
|
|
141
150
|
if (!isDone(state, 'discovery')) {
|
|
142
|
-
spinner?.start('
|
|
151
|
+
spinner?.start('learning your world');
|
|
143
152
|
const platformProfile = discoverPlatforms();
|
|
144
|
-
spinner?.update('
|
|
153
|
+
spinner?.update('looking deeper');
|
|
145
154
|
const discoveredApps = runDiscovery();
|
|
146
155
|
spinner?.stop();
|
|
147
156
|
if (discoveredApps.length) {
|
|
@@ -162,23 +171,45 @@ export async function runInstaller(apiKey) {
|
|
|
162
171
|
if (skills.active.length) {
|
|
163
172
|
console.log(DIM(` ${skills.active.length} skills activated`));
|
|
164
173
|
}
|
|
174
|
+
// RPLY platform connectivity
|
|
175
|
+
const rplyStatus = await rply.status();
|
|
176
|
+
if (rplyStatus) {
|
|
177
|
+
const active = rplyStatus.platforms.filter(p => p.is_available && p.is_enabled);
|
|
178
|
+
const inactive = rplyStatus.platforms.filter(p => !p.is_available || !p.is_enabled);
|
|
179
|
+
console.log();
|
|
180
|
+
for (const p of active) {
|
|
181
|
+
console.log(` ${HD(p.display_name)}`);
|
|
182
|
+
}
|
|
183
|
+
for (const p of inactive) {
|
|
184
|
+
const hint = platformHint(p.platform);
|
|
185
|
+
console.log(` ${DIM('○')} ${DIM(p.display_name)}${hint ? ' ' + DIM(hint) : ''}`);
|
|
186
|
+
}
|
|
187
|
+
if (inactive.length) {
|
|
188
|
+
console.log();
|
|
189
|
+
console.log(DIM(' sign into these to connect them'));
|
|
190
|
+
console.log(DIM(' run setup again after'));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
console.log(DIM(' iMessage only — more platforms available'));
|
|
195
|
+
}
|
|
165
196
|
savePlatforms(platformProfile);
|
|
166
197
|
markDone(state, 'discovery');
|
|
167
198
|
}
|
|
168
199
|
// ── Step 4: CRM Build ──
|
|
169
200
|
stepHeader(4, 'Relationship Map', isDone(state, 'crm'));
|
|
170
201
|
if (!isDone(state, 'crm')) {
|
|
171
|
-
spinner?.start('
|
|
172
|
-
const crm = buildCRM();
|
|
202
|
+
spinner?.start('reading the room');
|
|
203
|
+
const crm = await buildCRM();
|
|
173
204
|
spinner?.stop();
|
|
174
205
|
if (crm.threads.length) {
|
|
175
206
|
renderCRMList(crm.threads);
|
|
176
207
|
// Knowledge base generation
|
|
177
|
-
const knowledgeCount = updateAutoKnowledge(crm);
|
|
208
|
+
const knowledgeCount = await updateAutoKnowledge(crm);
|
|
178
209
|
console.log(DIM(` ${knowledgeCount} contact knowledge bases created`));
|
|
179
210
|
// Enrichment
|
|
180
211
|
if (apiKey) {
|
|
181
|
-
spinner?.start('
|
|
212
|
+
spinner?.start('understanding the room');
|
|
182
213
|
let brief = '';
|
|
183
214
|
const enriched = [];
|
|
184
215
|
for await (const t of streamEnrichedCRM(crm, apiKey, {
|
|
@@ -200,7 +231,7 @@ export async function runInstaller(apiKey) {
|
|
|
200
231
|
// ── Step 5: Archetype ──
|
|
201
232
|
stepHeader(5, 'Archetype', isDone(state, 'archetype'));
|
|
202
233
|
if (!isDone(state, 'archetype') && apiKey) {
|
|
203
|
-
spinner?.start('
|
|
234
|
+
spinner?.start('figuring out who you are');
|
|
204
235
|
try {
|
|
205
236
|
const platforms = discoverPlatforms(); // re-read (may have been saved)
|
|
206
237
|
const archetype = await generateArchetype(platforms, apiKey);
|
|
@@ -213,48 +244,40 @@ export async function runInstaller(apiKey) {
|
|
|
213
244
|
}
|
|
214
245
|
catch (err) {
|
|
215
246
|
spinner?.stop();
|
|
216
|
-
console.log(
|
|
247
|
+
console.log(DIM(` archetype generation failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
217
248
|
}
|
|
218
249
|
markDone(state, 'archetype');
|
|
219
250
|
}
|
|
220
|
-
// ── Step 6: Messaging
|
|
221
|
-
stepHeader(6, 'Messaging
|
|
251
|
+
// ── Step 6: Messaging ──
|
|
252
|
+
stepHeader(6, 'Messaging', isDone(state, 'phone'));
|
|
222
253
|
if (!isDone(state, 'phone')) {
|
|
223
|
-
// Start the inbound gateway (rply-mac-server pushes messages here)
|
|
224
254
|
if (apiKey) {
|
|
225
255
|
const gw = startGateway(apiKey);
|
|
226
|
-
console.log(GRN(` gateway listening on :${gw.port}`));
|
|
227
|
-
// Stop after installer finishes (daemon will restart it)
|
|
228
256
|
setTimeout(() => gw.stop(), 60_000);
|
|
229
257
|
}
|
|
230
|
-
// Tailscale — required for rply-mac-server to reach this Mac
|
|
231
258
|
if (hasTailscale()) {
|
|
232
259
|
const hostname = getTailscaleHostname();
|
|
233
260
|
if (hostname) {
|
|
234
261
|
state.funnelUrl = `http://${hostname}:19877`;
|
|
235
|
-
|
|
236
|
-
console.log(DIM(` rply-mac-server → ${state.funnelUrl}/inbound`));
|
|
237
|
-
// Optionally expose via Funnel for HTTPS
|
|
238
|
-
spinner?.start('setting up tunnel');
|
|
262
|
+
spinner?.start('opening the connection');
|
|
239
263
|
const funnelUrl = startFunnel(19877);
|
|
240
264
|
spinner?.stop();
|
|
241
|
-
if (funnelUrl)
|
|
265
|
+
if (funnelUrl)
|
|
242
266
|
state.funnelUrl = funnelUrl;
|
|
243
|
-
|
|
244
|
-
}
|
|
267
|
+
console.log(DIM(' connected'));
|
|
245
268
|
}
|
|
246
269
|
else {
|
|
247
|
-
console.log(
|
|
270
|
+
console.log(HD(' network connection incomplete — run setup again'));
|
|
248
271
|
}
|
|
249
272
|
}
|
|
250
273
|
else {
|
|
251
|
-
console.log(
|
|
252
|
-
console.log(DIM('
|
|
274
|
+
console.log(HD(' missing network layer — messages won\'t reach this machine'));
|
|
275
|
+
console.log(DIM(' tailscale.com/download'));
|
|
253
276
|
}
|
|
254
277
|
markDone(state, 'phone');
|
|
255
278
|
}
|
|
256
279
|
// ── Step 7: Daemon Install ──
|
|
257
|
-
stepHeader(7, '
|
|
280
|
+
stepHeader(7, 'Background', isDone(state, 'daemon'));
|
|
258
281
|
if (!isDone(state, 'daemon')) {
|
|
259
282
|
const logDir = join(homedir(), 'Library/Logs/life-pulse');
|
|
260
283
|
const agentsDir = join(homedir(), 'Library/LaunchAgents');
|
|
@@ -320,35 +343,31 @@ export async function runInstaller(apiKey) {
|
|
|
320
343
|
execSync('launchctl load ~/Library/LaunchAgents/com.life-pulse.daemon.plist', {
|
|
321
344
|
stdio: 'pipe', timeout: 5000,
|
|
322
345
|
});
|
|
323
|
-
console.log(GRN(' daemon installed and started'));
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
console.log(DIM(' plists written — load manually:'));
|
|
327
|
-
console.log(DIM(' launchctl load ~/Library/LaunchAgents/com.life-pulse.daemon.plist'));
|
|
328
346
|
}
|
|
347
|
+
catch { }
|
|
329
348
|
// Load morning
|
|
330
349
|
try {
|
|
331
350
|
execSync('launchctl load ~/Library/LaunchAgents/com.life-pulse.morning.plist', {
|
|
332
351
|
stdio: 'pipe', timeout: 5000,
|
|
333
352
|
});
|
|
334
|
-
console.log(GRN(' morning brief: 7:30 AM daily'));
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
console.log(DIM(' launchctl load ~/Library/LaunchAgents/com.life-pulse.morning.plist'));
|
|
338
353
|
}
|
|
354
|
+
catch { }
|
|
355
|
+
console.log(DIM(' always on'));
|
|
356
|
+
console.log(DIM(' morning brief at 7:30 AM'));
|
|
339
357
|
markDone(state, 'daemon');
|
|
340
358
|
}
|
|
341
359
|
// ── Step 8: First Briefing ──
|
|
342
360
|
stepHeader(8, 'First Briefing', isDone(state, 'briefing'));
|
|
343
361
|
if (!isDone(state, 'briefing') && apiKey && process.stdin.isTTY) {
|
|
344
362
|
console.log();
|
|
345
|
-
console.log(
|
|
363
|
+
console.log();
|
|
364
|
+
console.log(DIM(' let\'s see what\'s going on...'));
|
|
346
365
|
console.log();
|
|
347
366
|
// Start a session for the briefing
|
|
348
367
|
startSession();
|
|
349
368
|
// Import and run the agent directly
|
|
350
369
|
const { runAgent } = await import('./agent.js');
|
|
351
|
-
const { pickCard, renderSectionHeader, renderSection, renderHandled,
|
|
370
|
+
const { pickCard, renderSectionHeader, renderSection, renderHandled, HD: tHD, DIM: tDIM, MID: tMID } = await import('./tui.js');
|
|
352
371
|
const { ProgressRenderer } = await import('./progress.js');
|
|
353
372
|
const renderer = new ProgressRenderer();
|
|
354
373
|
renderer.start();
|
|
@@ -367,23 +386,26 @@ export async function runInstaller(apiKey) {
|
|
|
367
386
|
if (analysis.handled?.length)
|
|
368
387
|
renderHandled(analysis.handled, 5);
|
|
369
388
|
if (analysis.promises?.length) {
|
|
370
|
-
renderSectionHeader('
|
|
389
|
+
renderSectionHeader('promises', tHD);
|
|
371
390
|
}
|
|
372
391
|
if (analysis.alpha?.length) {
|
|
373
|
-
renderSection('
|
|
392
|
+
renderSection('alpha', analysis.alpha, '·', tHD, tMID);
|
|
374
393
|
}
|
|
375
394
|
markDone(state, 'briefing');
|
|
376
395
|
}
|
|
377
396
|
// ── Summary ──
|
|
378
397
|
console.log();
|
|
379
|
-
console.log(
|
|
398
|
+
console.log(HD(' You\'re all set.'));
|
|
380
399
|
console.log();
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
400
|
+
// Show active platform count
|
|
401
|
+
const finalStatus = await rply.status();
|
|
402
|
+
if (finalStatus) {
|
|
403
|
+
const active = finalStatus.platforms.filter(p => p.is_available && p.is_enabled);
|
|
404
|
+
if (active.length) {
|
|
405
|
+
console.log(DIM(` ${active.map(p => p.display_name).join(', ')}`));
|
|
406
|
+
}
|
|
384
407
|
}
|
|
385
|
-
console.log(DIM('
|
|
386
|
-
console.log(DIM('
|
|
387
|
-
console.log(DIM(' Run --check to verify, --status for stats'));
|
|
408
|
+
console.log(DIM(' running in the background'));
|
|
409
|
+
console.log(DIM(' you\'ll hear from me at 7:30 AM'));
|
|
388
410
|
console.log();
|
|
389
411
|
}
|
package/dist/knowledge.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare function loadKnowledge(contactName: string): string;
|
|
|
16
16
|
/** Get the knowledge directory for a contact (creates if needed) */
|
|
17
17
|
export declare function getKnowledgeDir(contactName: string): string;
|
|
18
18
|
/** Regenerate auto knowledge for all CRM contacts */
|
|
19
|
-
export declare function updateAutoKnowledge(crm: CRM): number
|
|
19
|
+
export declare function updateAutoKnowledge(crm: CRM): Promise<number>;
|
|
20
20
|
interface ConversationTurn {
|
|
21
21
|
role: 'user' | 'assistant';
|
|
22
22
|
content: string;
|
package/dist/knowledge.js
CHANGED
|
@@ -14,6 +14,7 @@ import { homedir } from 'os';
|
|
|
14
14
|
import { join } from 'path';
|
|
15
15
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'fs';
|
|
16
16
|
import { openDb, safeQuery } from './db.js';
|
|
17
|
+
import * as rply from './rply-client.js';
|
|
17
18
|
const BASE = join(homedir(), '.life-pulse');
|
|
18
19
|
const KNOWLEDGE_DIR = join(BASE, 'knowledge');
|
|
19
20
|
const CONVERSATIONS_DIR = join(BASE, 'conversations');
|
|
@@ -93,8 +94,23 @@ function writeRelationship(dir, thread) {
|
|
|
93
94
|
lines.push(`Waiting on them since ${thread.waitingSince}`);
|
|
94
95
|
writeFileSync(join(dir, 'relationship.txt'), lines.join('\n'), 'utf-8');
|
|
95
96
|
}
|
|
96
|
-
/** Update recent-threads.txt
|
|
97
|
-
function writeRecentThreads(dir, handle) {
|
|
97
|
+
/** Update recent-threads.txt — RPLY first, SQLite fallback */
|
|
98
|
+
async function writeRecentThreads(dir, handle) {
|
|
99
|
+
// ── RPLY path ──
|
|
100
|
+
if (await rply.isAvailable()) {
|
|
101
|
+
const msgs = await rply.listMessages(handle, { limit: MAX_RECENT_MSGS });
|
|
102
|
+
if (msgs?.data.length) {
|
|
103
|
+
const lines = msgs.data
|
|
104
|
+
.filter(m => m.content?.type === 'text' && m.content.text)
|
|
105
|
+
.reverse() // oldest first
|
|
106
|
+
.map(m => `${m.direction === 'sent' ? '→' : '←'} ${m.content.text.slice(0, 200)}`);
|
|
107
|
+
if (lines.length) {
|
|
108
|
+
writeFileSync(join(dir, 'recent-threads.txt'), lines.join('\n'), 'utf-8');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ── SQLite fallback ──
|
|
98
114
|
const db = openDb(join(homedir(), 'Library/Messages/chat.db'));
|
|
99
115
|
if (!db)
|
|
100
116
|
return;
|
|
@@ -116,8 +132,8 @@ function writeRecentThreads(dir, handle) {
|
|
|
116
132
|
if (!rows.length)
|
|
117
133
|
return;
|
|
118
134
|
const lines = rows.reverse().map(r => {
|
|
119
|
-
const
|
|
120
|
-
return `${
|
|
135
|
+
const d = r.is_from_me ? '→' : '←';
|
|
136
|
+
return `${d} ${(r.text || '').slice(0, 200)}`;
|
|
121
137
|
});
|
|
122
138
|
writeFileSync(join(dir, 'recent-threads.txt'), lines.join('\n'), 'utf-8');
|
|
123
139
|
}
|
|
@@ -126,7 +142,7 @@ function writeRecentThreads(dir, handle) {
|
|
|
126
142
|
}
|
|
127
143
|
}
|
|
128
144
|
/** Regenerate auto knowledge for all CRM contacts */
|
|
129
|
-
export function updateAutoKnowledge(crm) {
|
|
145
|
+
export async function updateAutoKnowledge(crm) {
|
|
130
146
|
mkdirSync(KNOWLEDGE_DIR, { recursive: true });
|
|
131
147
|
let count = 0;
|
|
132
148
|
for (const thread of crm.threads) {
|
|
@@ -134,7 +150,7 @@ export function updateAutoKnowledge(crm) {
|
|
|
134
150
|
continue;
|
|
135
151
|
const dir = getKnowledgeDir(thread.name);
|
|
136
152
|
writeRelationship(dir, thread);
|
|
137
|
-
writeRecentThreads(dir, thread.handle);
|
|
153
|
+
await writeRecentThreads(dir, thread.handle);
|
|
138
154
|
count++;
|
|
139
155
|
}
|
|
140
156
|
return count;
|
package/dist/message-loop.js
CHANGED
|
@@ -129,7 +129,7 @@ function fetchNewMessages() {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
/** Resolve a handle to a contact name */
|
|
132
|
-
function resolveHandle(handle) {
|
|
132
|
+
async function resolveHandle(handle) {
|
|
133
133
|
try {
|
|
134
134
|
const profile = buildProfile();
|
|
135
135
|
const lower = handle.toLowerCase();
|
|
@@ -141,7 +141,7 @@ function resolveHandle(handle) {
|
|
|
141
141
|
catch { }
|
|
142
142
|
// Fallback: use CRM for better resolution
|
|
143
143
|
try {
|
|
144
|
-
const crm = buildCRM();
|
|
144
|
+
const crm = await buildCRM();
|
|
145
145
|
for (const t of crm.threads) {
|
|
146
146
|
if (t.handle === handle) {
|
|
147
147
|
const tier = t.msgs30d > 50 ? 'T1' : t.msgs30d > 15 ? 'T2' : t.msgs30d > 5 ? 'T3' : 'T4';
|
|
@@ -191,7 +191,7 @@ async function pollCycle(config) {
|
|
|
191
191
|
skippedCount++;
|
|
192
192
|
continue;
|
|
193
193
|
}
|
|
194
|
-
const { name, tier } = resolveHandle(msg.handle);
|
|
194
|
+
const { name, tier } = await resolveHandle(msg.handle);
|
|
195
195
|
// Only respond to configured tiers
|
|
196
196
|
if (!config.respondTiers.includes(tier)) {
|
|
197
197
|
processed.add(msg.rowid);
|
package/dist/progress.js
CHANGED
|
@@ -11,30 +11,30 @@ const STRUCTURE = chalk.hex('#3B3B3B');
|
|
|
11
11
|
const OK = chalk.hex('#98C379');
|
|
12
12
|
const FAIL = chalk.hex('#E06C75');
|
|
13
13
|
const LABEL = {
|
|
14
|
-
search_all_messages: '
|
|
15
|
-
get_conversation: '
|
|
16
|
-
profile_contact: '
|
|
17
|
-
get_messages: '
|
|
18
|
-
get_unanswered_messages: '
|
|
19
|
-
get_screen_time: '
|
|
20
|
-
get_browsing: '
|
|
21
|
-
get_calls: 'checking
|
|
22
|
-
scan_sources: '
|
|
23
|
-
lookup_contact: '
|
|
24
|
-
get_email_summary: '
|
|
25
|
-
get_git_activity: '
|
|
26
|
-
get_recent_files: '
|
|
27
|
-
get_shell_history: '
|
|
28
|
-
get_notes: 'reading
|
|
29
|
-
get_claude_history: '
|
|
30
|
-
get_chatgpt_history: '
|
|
31
|
-
get_interests_for_plans: '
|
|
32
|
-
get_calendar: '
|
|
33
|
-
get_reminders: 'checking
|
|
34
|
-
get_notifications: '
|
|
35
|
-
discover_platforms: '
|
|
36
|
-
generate_archetype: '
|
|
37
|
-
search_emails: '
|
|
14
|
+
search_all_messages: 'reading the room',
|
|
15
|
+
get_conversation: 'listening in',
|
|
16
|
+
profile_contact: 'learning who this is',
|
|
17
|
+
get_messages: 'reading the room',
|
|
18
|
+
get_unanswered_messages: 'finding what you missed',
|
|
19
|
+
get_screen_time: 'tracking your attention',
|
|
20
|
+
get_browsing: 'retracing your steps',
|
|
21
|
+
get_calls: 'checking who called',
|
|
22
|
+
scan_sources: 'pulling threads',
|
|
23
|
+
lookup_contact: 'learning who this is',
|
|
24
|
+
get_email_summary: 'going through your inbox',
|
|
25
|
+
get_git_activity: 'seeing what you shipped',
|
|
26
|
+
get_recent_files: 'noticing what you touched',
|
|
27
|
+
get_shell_history: 'retracing your steps',
|
|
28
|
+
get_notes: 'reading your thoughts',
|
|
29
|
+
get_claude_history: 'seeing what you asked',
|
|
30
|
+
get_chatgpt_history: 'seeing what you asked',
|
|
31
|
+
get_interests_for_plans: 'understanding your world',
|
|
32
|
+
get_calendar: 'looking at your day',
|
|
33
|
+
get_reminders: 'checking what you owe yourself',
|
|
34
|
+
get_notifications: 'catching up',
|
|
35
|
+
discover_platforms: 'figuring out your world',
|
|
36
|
+
generate_archetype: 'figuring out who you are',
|
|
37
|
+
search_emails: 'digging through mail',
|
|
38
38
|
};
|
|
39
39
|
const MAX_VISIBLE = 4;
|
|
40
40
|
export class ProgressRenderer {
|
package/dist/router.js
CHANGED
|
@@ -146,7 +146,7 @@ const stats = {
|
|
|
146
146
|
lastForwarded: null,
|
|
147
147
|
};
|
|
148
148
|
function log(msg) {
|
|
149
|
-
process.stderr.write(`
|
|
149
|
+
process.stderr.write(` ${msg}\n`);
|
|
150
150
|
}
|
|
151
151
|
// ─── Poll Cycle ───────────────────────────────────────────────
|
|
152
152
|
async function poll() {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPLY API client — localhost:19851
|
|
3
|
+
*
|
|
4
|
+
* Unified messaging API. When available, replaces direct SQLite access
|
|
5
|
+
* to iMessage/chat.db, removing the Full Disk Access requirement.
|
|
6
|
+
*/
|
|
7
|
+
export interface RPLYConversation {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
platform: string;
|
|
11
|
+
last_message_date: string;
|
|
12
|
+
unread_count: number;
|
|
13
|
+
is_group_chat: boolean;
|
|
14
|
+
is_never_reply: boolean;
|
|
15
|
+
is_dismissed: boolean;
|
|
16
|
+
priority?: string;
|
|
17
|
+
participants: RPLYParticipant[];
|
|
18
|
+
reply?: RPLYReply;
|
|
19
|
+
messages_load_state: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RPLYParticipant {
|
|
22
|
+
display_name: string;
|
|
23
|
+
full_name: string;
|
|
24
|
+
identifier: string;
|
|
25
|
+
platform: string;
|
|
26
|
+
}
|
|
27
|
+
export interface RPLYReply {
|
|
28
|
+
id: string;
|
|
29
|
+
text: string;
|
|
30
|
+
original_text: string;
|
|
31
|
+
classification_reason: string;
|
|
32
|
+
reply_date: string;
|
|
33
|
+
was_edited: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface RPLYMessage {
|
|
36
|
+
direction: 'sent' | 'received';
|
|
37
|
+
date: string;
|
|
38
|
+
content: {
|
|
39
|
+
type: string;
|
|
40
|
+
text: string;
|
|
41
|
+
};
|
|
42
|
+
id?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface RPLYContact {
|
|
45
|
+
display_name: string;
|
|
46
|
+
full_name: string;
|
|
47
|
+
identifier: string;
|
|
48
|
+
platform: string;
|
|
49
|
+
}
|
|
50
|
+
export interface RPLYSearchResult {
|
|
51
|
+
message: RPLYMessage & {
|
|
52
|
+
id: string;
|
|
53
|
+
};
|
|
54
|
+
conversation_id: string;
|
|
55
|
+
}
|
|
56
|
+
export interface RPLYStatus {
|
|
57
|
+
conversation_count: number;
|
|
58
|
+
version: string;
|
|
59
|
+
is_loading: boolean;
|
|
60
|
+
platforms: {
|
|
61
|
+
platform: string;
|
|
62
|
+
display_name: string;
|
|
63
|
+
is_available: boolean;
|
|
64
|
+
is_enabled: boolean;
|
|
65
|
+
}[];
|
|
66
|
+
}
|
|
67
|
+
interface Paginated<T> {
|
|
68
|
+
data: T[];
|
|
69
|
+
total: number;
|
|
70
|
+
limit: number;
|
|
71
|
+
offset: number;
|
|
72
|
+
}
|
|
73
|
+
export declare function isAvailable(): Promise<boolean>;
|
|
74
|
+
export declare function resetCache(): void;
|
|
75
|
+
export declare function status(): Promise<RPLYStatus | null>;
|
|
76
|
+
export declare function listConversations(opts?: {
|
|
77
|
+
limit?: number;
|
|
78
|
+
offset?: number;
|
|
79
|
+
category?: string;
|
|
80
|
+
platform?: string;
|
|
81
|
+
}): Promise<Paginated<RPLYConversation> | null>;
|
|
82
|
+
export declare function getConversation(id: string): Promise<(RPLYConversation & {
|
|
83
|
+
messages: RPLYMessage[];
|
|
84
|
+
}) | null>;
|
|
85
|
+
export declare function listMessages(conversationId: string, opts?: {
|
|
86
|
+
limit?: number;
|
|
87
|
+
offset?: number;
|
|
88
|
+
}): Promise<Paginated<RPLYMessage> | null>;
|
|
89
|
+
export declare function searchConversations(q: string): Promise<RPLYConversation[] | null>;
|
|
90
|
+
export declare function searchMessages(q: string, opts?: {
|
|
91
|
+
limit?: number;
|
|
92
|
+
}): Promise<RPLYSearchResult[] | null>;
|
|
93
|
+
export declare function listContacts(opts?: {
|
|
94
|
+
limit?: number;
|
|
95
|
+
offset?: number;
|
|
96
|
+
}): Promise<Paginated<RPLYContact> | null>;
|
|
97
|
+
export declare function searchContacts(q: string): Promise<RPLYContact[] | null>;
|
|
98
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPLY API client — localhost:19851
|
|
3
|
+
*
|
|
4
|
+
* Unified messaging API. When available, replaces direct SQLite access
|
|
5
|
+
* to iMessage/chat.db, removing the Full Disk Access requirement.
|
|
6
|
+
*/
|
|
7
|
+
const BASE = 'http://localhost:19851';
|
|
8
|
+
const TIMEOUT = 5_000;
|
|
9
|
+
// ─── Core ────────────────────────────────────────────────────
|
|
10
|
+
let _available = null;
|
|
11
|
+
async function get(path) {
|
|
12
|
+
try {
|
|
13
|
+
const r = await fetch(`${BASE}${path}`, { signal: AbortSignal.timeout(TIMEOUT) });
|
|
14
|
+
if (!r.ok)
|
|
15
|
+
return null;
|
|
16
|
+
return await r.json();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function isAvailable() {
|
|
23
|
+
if (_available !== null)
|
|
24
|
+
return _available;
|
|
25
|
+
const s = await get('/v1/status');
|
|
26
|
+
_available = s !== null && !s.is_loading;
|
|
27
|
+
return _available;
|
|
28
|
+
}
|
|
29
|
+
export function resetCache() { _available = null; }
|
|
30
|
+
// ─── Endpoints ───────────────────────────────────────────────
|
|
31
|
+
export async function status() {
|
|
32
|
+
return get('/v1/status');
|
|
33
|
+
}
|
|
34
|
+
export async function listConversations(opts) {
|
|
35
|
+
const p = new URLSearchParams();
|
|
36
|
+
if (opts?.limit)
|
|
37
|
+
p.set('limit', String(opts.limit));
|
|
38
|
+
if (opts?.offset)
|
|
39
|
+
p.set('offset', String(opts.offset));
|
|
40
|
+
if (opts?.category)
|
|
41
|
+
p.set('category', opts.category);
|
|
42
|
+
if (opts?.platform)
|
|
43
|
+
p.set('platform', opts.platform);
|
|
44
|
+
const qs = p.toString();
|
|
45
|
+
return get(`/v1/conversations${qs ? '?' + qs : ''}`);
|
|
46
|
+
}
|
|
47
|
+
export async function getConversation(id) {
|
|
48
|
+
return get(`/v1/conversations/${encodeURIComponent(id)}`);
|
|
49
|
+
}
|
|
50
|
+
export async function listMessages(conversationId, opts) {
|
|
51
|
+
const p = new URLSearchParams();
|
|
52
|
+
if (opts?.limit)
|
|
53
|
+
p.set('limit', String(opts.limit));
|
|
54
|
+
if (opts?.offset)
|
|
55
|
+
p.set('offset', String(opts.offset));
|
|
56
|
+
const qs = p.toString();
|
|
57
|
+
return get(`/v1/conversations/${encodeURIComponent(conversationId)}/messages${qs ? '?' + qs : ''}`);
|
|
58
|
+
}
|
|
59
|
+
export async function searchConversations(q) {
|
|
60
|
+
return get(`/v1/conversations/search?q=${encodeURIComponent(q)}`);
|
|
61
|
+
}
|
|
62
|
+
export async function searchMessages(q, opts) {
|
|
63
|
+
const p = new URLSearchParams({ q });
|
|
64
|
+
if (opts?.limit)
|
|
65
|
+
p.set('limit', String(opts.limit));
|
|
66
|
+
return get(`/v1/messages/search?${p}`);
|
|
67
|
+
}
|
|
68
|
+
export async function listContacts(opts) {
|
|
69
|
+
const p = new URLSearchParams();
|
|
70
|
+
if (opts?.limit)
|
|
71
|
+
p.set('limit', String(opts.limit));
|
|
72
|
+
if (opts?.offset)
|
|
73
|
+
p.set('offset', String(opts.offset));
|
|
74
|
+
const qs = p.toString();
|
|
75
|
+
return get(`/v1/contacts${qs ? '?' + qs : ''}`);
|
|
76
|
+
}
|
|
77
|
+
export async function searchContacts(q) {
|
|
78
|
+
return get(`/v1/contacts/search?q=${encodeURIComponent(q)}`);
|
|
79
|
+
}
|
package/dist/sms-gateway.js
CHANGED
|
@@ -87,7 +87,7 @@ async function handleInbound(req, res, apiKey) {
|
|
|
87
87
|
catch (err) {
|
|
88
88
|
stats.errors++;
|
|
89
89
|
const msg = err instanceof Error ? err.message : String(err);
|
|
90
|
-
process.stderr.write(`
|
|
90
|
+
process.stderr.write(` couldn't reply to ${contactName}\n`);
|
|
91
91
|
respond(res, 500, { error: 'conversation failed' });
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -122,7 +122,7 @@ export function startGateway(apiKey) {
|
|
|
122
122
|
respond(res, 404, { error: 'not found' });
|
|
123
123
|
});
|
|
124
124
|
server.listen(GATEWAY_PORT, '0.0.0.0', () => {
|
|
125
|
-
|
|
125
|
+
// silent startup — no infrastructure noise
|
|
126
126
|
});
|
|
127
127
|
return {
|
|
128
128
|
port: GATEWAY_PORT,
|