life-pulse 2.3.9 → 2.3.11
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 +19 -0
- package/dist/agent.js +14 -2
- package/dist/analyze.d.ts +1 -19
- package/dist/analyze.js +2 -128
- package/dist/cli.js +341 -167
- package/dist/collectors/calendar.js +1 -4
- package/dist/contacts.d.ts +2 -0
- package/dist/contacts.js +54 -0
- package/dist/conversation.js +3 -0
- package/dist/crm.d.ts +1 -0
- package/dist/crm.js +176 -86
- package/dist/ghostty-frames.json +1 -0
- package/dist/installer.d.ts +2 -2
- package/dist/installer.js +55 -24
- package/dist/profile.d.ts +6 -0
- package/dist/profile.js +230 -1
- package/dist/progress.d.ts +1 -0
- package/dist/progress.js +67 -31
- package/dist/prompt-layers.d.ts +17 -0
- package/dist/prompt-layers.js +113 -0
- package/dist/router.d.ts +3 -2
- package/dist/router.js +3 -2
- package/dist/session-progress.d.ts +2 -2
- package/dist/session-progress.js +2 -11
- package/dist/skill-loader.d.ts +1 -1
- package/dist/skill-loader.js +1 -1
- package/dist/sms-gateway.d.ts +6 -11
- package/dist/sms-gateway.js +14 -11
- package/dist/state.d.ts +1 -1
- package/dist/state.js +13 -17
- package/dist/tools.js +1 -3
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/dist/tui.d.ts +4 -3
- package/dist/tui.js +126 -66
- package/dist/tunnel.d.ts +1 -2
- package/dist/tunnel.js +1 -2
- package/dist/ui/app.d.ts +2 -1
- package/dist/ui/app.js +42 -25
- package/dist/ui/progress.d.ts +1 -0
- package/dist/ui/progress.js +75 -35
- package/package.json +4 -3
package/dist/installer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* White-Glove Installer — orchestrated setup for new Mac
|
|
2
|
+
* White-Glove Installer — orchestrated setup for new user Mac installations.
|
|
3
3
|
*
|
|
4
4
|
* 8 steps, ~15 minutes sitting beside the client:
|
|
5
5
|
* 1. Pre-flight (dirs, API key)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. Discovery (iCloud, Screen Time, apps, brew)
|
|
8
8
|
* 4. CRM Build (iMessage relationship map)
|
|
9
9
|
* 5. Archetype (Sonnet psychographic profile)
|
|
10
|
-
* 6.
|
|
10
|
+
* 6. NOX Link (Tailscale endpoint for inbound NOX calls)
|
|
11
11
|
* 7. Daemon Install (launchd plists)
|
|
12
12
|
* 8. First Briefing (live demo)
|
|
13
13
|
*
|
package/dist/installer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* White-Glove Installer — orchestrated setup for new Mac
|
|
2
|
+
* White-Glove Installer — orchestrated setup for new user Mac installations.
|
|
3
3
|
*
|
|
4
4
|
* 8 steps, ~15 minutes sitting beside the client:
|
|
5
5
|
* 1. Pre-flight (dirs, API key)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. Discovery (iCloud, Screen Time, apps, brew)
|
|
8
8
|
* 4. CRM Build (iMessage relationship map)
|
|
9
9
|
* 5. Archetype (Sonnet psychographic profile)
|
|
10
|
-
* 6.
|
|
10
|
+
* 6. NOX Link (Tailscale endpoint for inbound NOX calls)
|
|
11
11
|
* 7. Daemon Install (launchd plists)
|
|
12
12
|
* 8. First Briefing (live demo)
|
|
13
13
|
*
|
|
@@ -28,9 +28,11 @@ import { updateAutoKnowledge } from './knowledge.js';
|
|
|
28
28
|
import { scanForSkills } from './skill-loader.js';
|
|
29
29
|
import { hasTailscale, getHostname as getTailscaleHostname, startFunnel } from './tunnel.js';
|
|
30
30
|
import { startGateway } from './sms-gateway.js';
|
|
31
|
-
import { renderCRMList, renderBrief, Spinner, DIM, HD, MID } from './tui.js';
|
|
31
|
+
import { renderCRMList, renderBrief, pickCard, renderSectionHeader, renderSection, renderHandled, Spinner, DIM, HD, MID } from './tui.js';
|
|
32
32
|
import { saveContactSummaries } from './intelligence.js';
|
|
33
33
|
import { startSession } from './session-progress.js';
|
|
34
|
+
import { runAgent } from './agent.js';
|
|
35
|
+
import { ProgressRenderer } from './progress.js';
|
|
34
36
|
import * as rply from './rply-client.js';
|
|
35
37
|
const BASE = join(homedir(), '.life-pulse');
|
|
36
38
|
const STATE_PATH = join(BASE, 'install-state.json');
|
|
@@ -228,6 +230,25 @@ export async function runInstaller(apiKey) {
|
|
|
228
230
|
}
|
|
229
231
|
markDone(state, 'crm');
|
|
230
232
|
}
|
|
233
|
+
// ── Background: kick off agent scan while steps 5-7 run ──
|
|
234
|
+
let agentPromise = null;
|
|
235
|
+
let agentRenderer = null;
|
|
236
|
+
const cardBuffer = [];
|
|
237
|
+
let cardsLive = false;
|
|
238
|
+
let bgCardCount = 0;
|
|
239
|
+
if (apiKey && process.stdin.isTTY && !isDone(state, 'briefing')) {
|
|
240
|
+
startSession();
|
|
241
|
+
agentRenderer = new ProgressRenderer();
|
|
242
|
+
agentPromise = runAgent(apiKey, agentRenderer, async (card) => {
|
|
243
|
+
if (cardsLive) {
|
|
244
|
+
bgCardCount++;
|
|
245
|
+
return await pickCard(card, bgCardCount);
|
|
246
|
+
}
|
|
247
|
+
return new Promise((resolve) => {
|
|
248
|
+
cardBuffer.push({ card, resolve });
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
231
252
|
// ── Step 5: Archetype ──
|
|
232
253
|
stepHeader(5, 'Archetype', isDone(state, 'archetype'));
|
|
233
254
|
if (!isDone(state, 'archetype') && apiKey) {
|
|
@@ -248,8 +269,8 @@ export async function runInstaller(apiKey) {
|
|
|
248
269
|
}
|
|
249
270
|
markDone(state, 'archetype');
|
|
250
271
|
}
|
|
251
|
-
// ── Step 6:
|
|
252
|
-
stepHeader(6, '
|
|
272
|
+
// ── Step 6: NOX Link ──
|
|
273
|
+
stepHeader(6, 'NOX Link', isDone(state, 'phone'));
|
|
253
274
|
if (!isDone(state, 'phone')) {
|
|
254
275
|
if (apiKey) {
|
|
255
276
|
const gw = startGateway(apiKey);
|
|
@@ -259,12 +280,14 @@ export async function runInstaller(apiKey) {
|
|
|
259
280
|
const hostname = getTailscaleHostname();
|
|
260
281
|
if (hostname) {
|
|
261
282
|
state.funnelUrl = `http://${hostname}:19877`;
|
|
262
|
-
spinner?.start('
|
|
283
|
+
spinner?.start('connecting nox');
|
|
263
284
|
const funnelUrl = startFunnel(19877);
|
|
264
285
|
spinner?.stop();
|
|
265
286
|
if (funnelUrl)
|
|
266
287
|
state.funnelUrl = funnelUrl;
|
|
267
|
-
console.log(DIM(' connected'));
|
|
288
|
+
console.log(DIM(' nox link connected'));
|
|
289
|
+
if (state.funnelUrl)
|
|
290
|
+
console.log(DIM(` ${state.funnelUrl}`));
|
|
268
291
|
}
|
|
269
292
|
else {
|
|
270
293
|
console.log(HD(' network connection incomplete — run setup again'));
|
|
@@ -363,21 +386,26 @@ export async function runInstaller(apiKey) {
|
|
|
363
386
|
console.log();
|
|
364
387
|
console.log(DIM(' let\'s see what\'s going on...'));
|
|
365
388
|
console.log();
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
|
|
389
|
+
if (!agentPromise) {
|
|
390
|
+
startSession();
|
|
391
|
+
agentRenderer = new ProgressRenderer();
|
|
392
|
+
bgCardCount = 0;
|
|
393
|
+
agentPromise = runAgent(apiKey, agentRenderer, async (card) => {
|
|
394
|
+
bgCardCount++;
|
|
395
|
+
return await pickCard(card, bgCardCount);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
agentRenderer.start();
|
|
399
|
+
cardsLive = true;
|
|
400
|
+
// Drain any cards that arrived during steps 5-7
|
|
401
|
+
for (const { card, resolve } of cardBuffer) {
|
|
402
|
+
bgCardCount++;
|
|
403
|
+
resolve(await pickCard(card, bgCardCount));
|
|
404
|
+
}
|
|
405
|
+
cardBuffer.length = 0;
|
|
406
|
+
const analysis = await agentPromise;
|
|
407
|
+
agentRenderer.stop();
|
|
408
|
+
agentRenderer.clear();
|
|
381
409
|
// Render results
|
|
382
410
|
if (analysis.greeting) {
|
|
383
411
|
console.log(` ${MID(analysis.greeting)}`);
|
|
@@ -386,10 +414,10 @@ export async function runInstaller(apiKey) {
|
|
|
386
414
|
if (analysis.handled?.length)
|
|
387
415
|
renderHandled(analysis.handled, 5);
|
|
388
416
|
if (analysis.promises?.length) {
|
|
389
|
-
renderSectionHeader('promises',
|
|
417
|
+
renderSectionHeader('promises', HD);
|
|
390
418
|
}
|
|
391
419
|
if (analysis.alpha?.length) {
|
|
392
|
-
renderSection('alpha', analysis.alpha, '·',
|
|
420
|
+
renderSection('alpha', analysis.alpha, '·', HD, MID);
|
|
393
421
|
}
|
|
394
422
|
markDone(state, 'briefing');
|
|
395
423
|
}
|
|
@@ -405,6 +433,9 @@ export async function runInstaller(apiKey) {
|
|
|
405
433
|
console.log(DIM(` ${active.map(p => p.display_name).join(', ')}`));
|
|
406
434
|
}
|
|
407
435
|
}
|
|
436
|
+
if (state.funnelUrl) {
|
|
437
|
+
console.log(DIM(` nox endpoint: ${state.funnelUrl}`));
|
|
438
|
+
}
|
|
408
439
|
console.log(DIM(' running in the background'));
|
|
409
440
|
console.log(DIM(' you\'ll hear from me at 7:30 AM'));
|
|
410
441
|
console.log();
|
package/dist/profile.d.ts
CHANGED
|
@@ -12,8 +12,14 @@ export interface UserProfile {
|
|
|
12
12
|
topContacts: ContactTier[];
|
|
13
13
|
projects: string[];
|
|
14
14
|
platformSummary: string;
|
|
15
|
+
personalSummary?: string;
|
|
15
16
|
}
|
|
16
17
|
/** Get the real name of the current macOS user */
|
|
17
18
|
export declare function getUserName(): string;
|
|
18
19
|
/** Build full user profile from system data. Cached for session. */
|
|
19
20
|
export declare function buildProfile(): UserProfile;
|
|
21
|
+
/**
|
|
22
|
+
* Rich personal profile summary from outbound messages.
|
|
23
|
+
* Mirrors the "highlight reel" idea: personal traits, interests, and recurring patterns.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildPersonalSummary(): Promise<string>;
|
package/dist/profile.js
CHANGED
|
@@ -9,10 +9,30 @@ import { existsSync, readdirSync } from 'fs';
|
|
|
9
9
|
import { openDb, safeQuery } from './db.js';
|
|
10
10
|
import { resolveName } from './contacts.js';
|
|
11
11
|
import { loadPlatforms, buildPlatformSummary } from './platforms.js';
|
|
12
|
+
import * as rply from './rply-client.js';
|
|
12
13
|
import dayjs from 'dayjs';
|
|
13
14
|
import { APPLE_EPOCH } from './types.js';
|
|
14
15
|
let _name = null;
|
|
15
16
|
let _profile = null;
|
|
17
|
+
let _personalSummaryPromise = null;
|
|
18
|
+
const SELF_SUMMARY_MODEL = 'claude-sonnet-4-5-20250929';
|
|
19
|
+
const SELF_SUMMARY_DAYS = 7;
|
|
20
|
+
const SELF_SUMMARY_LIMIT = 1000;
|
|
21
|
+
const SELF_SUMMARY_MAX_CHARS = 120000;
|
|
22
|
+
const ATTRIBUTED_BODY_NOISE = new Set([
|
|
23
|
+
'streamtyped',
|
|
24
|
+
'NSAttributedString',
|
|
25
|
+
'NSObject',
|
|
26
|
+
'NSString',
|
|
27
|
+
'NSDictionary',
|
|
28
|
+
'NSNumber',
|
|
29
|
+
'NSArray',
|
|
30
|
+
'NSColor',
|
|
31
|
+
'NSFont',
|
|
32
|
+
'NSParagraphStyle',
|
|
33
|
+
'NSOriginalFont',
|
|
34
|
+
]);
|
|
35
|
+
const TAPBACK_RE = /^(Loved|Liked|Laughed at|Disliked|Emphasized|Questioned) (".*"|an? .+)$/i;
|
|
16
36
|
/** Get the real name of the current macOS user */
|
|
17
37
|
export function getUserName() {
|
|
18
38
|
if (_name)
|
|
@@ -24,7 +44,7 @@ export function getUserName() {
|
|
|
24
44
|
// Output format: "RealName:\n First Last"
|
|
25
45
|
const lines = raw.trim().split('\n');
|
|
26
46
|
let name = lines.length > 1 ? lines[1].trim() : lines[0].replace('RealName:', '').trim();
|
|
27
|
-
// Split CamelCase names (e.g. "
|
|
47
|
+
// Split CamelCase names (e.g. "AlexDoe" -> "Alex Doe")
|
|
28
48
|
if (name && !/\s/.test(name) && /[a-z][A-Z]/.test(name)) {
|
|
29
49
|
name = name.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
30
50
|
}
|
|
@@ -49,6 +69,58 @@ export function buildProfile() {
|
|
|
49
69
|
_profile = { name, topContacts, projects, platformSummary };
|
|
50
70
|
return _profile;
|
|
51
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Rich personal profile summary from outbound messages.
|
|
74
|
+
* Mirrors the "highlight reel" idea: personal traits, interests, and recurring patterns.
|
|
75
|
+
*/
|
|
76
|
+
export async function buildPersonalSummary() {
|
|
77
|
+
if (_profile?.personalSummary)
|
|
78
|
+
return _profile.personalSummary;
|
|
79
|
+
if (_personalSummaryPromise)
|
|
80
|
+
return _personalSummaryPromise;
|
|
81
|
+
_personalSummaryPromise = (async () => {
|
|
82
|
+
const key = process.env.ANTHROPIC_API_KEY || '';
|
|
83
|
+
if (!key)
|
|
84
|
+
return '';
|
|
85
|
+
const messages = await fetchRecentSentMessageBodies(SELF_SUMMARY_LIMIT, SELF_SUMMARY_DAYS);
|
|
86
|
+
if (messages.length < 25)
|
|
87
|
+
return '';
|
|
88
|
+
const transcript = messages.join('\n').slice(0, SELF_SUMMARY_MAX_CHARS);
|
|
89
|
+
const user = getUserName();
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
'x-api-key': key,
|
|
95
|
+
'anthropic-version': '2023-06-01',
|
|
96
|
+
'content-type': 'application/json',
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
model: SELF_SUMMARY_MODEL,
|
|
100
|
+
temperature: 0.2,
|
|
101
|
+
max_tokens: 900,
|
|
102
|
+
messages: [{
|
|
103
|
+
role: 'user',
|
|
104
|
+
content: buildSelfSummaryPrompt(transcript, user),
|
|
105
|
+
}],
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok)
|
|
109
|
+
return '';
|
|
110
|
+
const data = await res.json();
|
|
111
|
+
const summary = data.content?.[0]?.text?.trim() || '';
|
|
112
|
+
if (!summary)
|
|
113
|
+
return '';
|
|
114
|
+
if (_profile)
|
|
115
|
+
_profile.personalSummary = summary;
|
|
116
|
+
return summary;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
return _personalSummaryPromise;
|
|
123
|
+
}
|
|
52
124
|
function discoverContacts() {
|
|
53
125
|
const home = homedir();
|
|
54
126
|
const db = openDb(join(home, 'Library/Messages/chat.db'));
|
|
@@ -93,3 +165,160 @@ function discoverProjects() {
|
|
|
93
165
|
return [];
|
|
94
166
|
}
|
|
95
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Pull outbound messages (sent by the user) with iMessage's attributedBody fallback.
|
|
170
|
+
* This mirrors the SQL pattern used in the native client.
|
|
171
|
+
*/
|
|
172
|
+
async function fetchRecentSentMessageBodies(limit, inPastDays) {
|
|
173
|
+
// ── RPLY path (preferred) ──
|
|
174
|
+
if (await rply.isAvailable()) {
|
|
175
|
+
const cutoff = dayjs().subtract(inPastDays, 'day');
|
|
176
|
+
const convos = await rply.listConversations({ limit: 80 });
|
|
177
|
+
if (convos?.data?.length) {
|
|
178
|
+
const out = [];
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
for (const c of convos.data) {
|
|
181
|
+
const msgs = await rply.listMessages(c.id, { limit: 50 });
|
|
182
|
+
if (!msgs?.data?.length)
|
|
183
|
+
continue;
|
|
184
|
+
for (const m of msgs.data) {
|
|
185
|
+
if (m.direction !== 'sent')
|
|
186
|
+
continue;
|
|
187
|
+
if (!m.content?.text || m.content.type !== 'text')
|
|
188
|
+
continue;
|
|
189
|
+
if (dayjs(m.date).isBefore(cutoff))
|
|
190
|
+
continue;
|
|
191
|
+
const normalized = normalizeSentText(m.content.text);
|
|
192
|
+
if (!normalized)
|
|
193
|
+
continue;
|
|
194
|
+
const clipped = normalized.slice(0, 500);
|
|
195
|
+
if (seen.has(clipped))
|
|
196
|
+
continue;
|
|
197
|
+
seen.add(clipped);
|
|
198
|
+
out.push(clipped);
|
|
199
|
+
if (out.length >= limit)
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (out.length > 0)
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// ── SQLite fallback ──
|
|
208
|
+
const db = openDb(join(homedir(), 'Library/Messages/chat.db'));
|
|
209
|
+
if (!db)
|
|
210
|
+
return [];
|
|
211
|
+
const cutoff = (BigInt(dayjs().subtract(inPastDays, 'day').unix() - APPLE_EPOCH) * BigInt(1e9)).toString();
|
|
212
|
+
const rows = safeQuery(db, `
|
|
213
|
+
SELECT
|
|
214
|
+
m.text AS message_text,
|
|
215
|
+
m.attributedBody AS message_attributed_body
|
|
216
|
+
FROM message AS m
|
|
217
|
+
JOIN chat_message_join AS cmj ON m.ROWID = cmj.message_id
|
|
218
|
+
JOIN chat AS c ON cmj.chat_id = c.ROWID
|
|
219
|
+
WHERE m.date >= ?
|
|
220
|
+
AND m.is_from_me = 1
|
|
221
|
+
AND m.associated_message_type = 0
|
|
222
|
+
AND c.chat_identifier NOT LIKE 'urn:biz:%'
|
|
223
|
+
ORDER BY m.date DESC
|
|
224
|
+
LIMIT ?
|
|
225
|
+
`, [cutoff, limit]);
|
|
226
|
+
db.close();
|
|
227
|
+
const out = [];
|
|
228
|
+
const seen = new Set();
|
|
229
|
+
for (const row of rows) {
|
|
230
|
+
const raw = row.message_text || decodeAttributedText(row.message_attributed_body);
|
|
231
|
+
const normalized = normalizeSentText(raw);
|
|
232
|
+
if (!normalized)
|
|
233
|
+
continue;
|
|
234
|
+
const clipped = normalized.slice(0, 500);
|
|
235
|
+
if (seen.has(clipped))
|
|
236
|
+
continue;
|
|
237
|
+
seen.add(clipped);
|
|
238
|
+
out.push(clipped);
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
function decodeAttributedText(blob) {
|
|
243
|
+
if (!blob?.length)
|
|
244
|
+
return '';
|
|
245
|
+
// iMessage attributedBody is NSKeyedArchiver data; pull printable runs as fallback text.
|
|
246
|
+
const utf = blob.toString('utf8');
|
|
247
|
+
const runs = utf.match(/[\x20-\x7E]{3,}/g) || [];
|
|
248
|
+
if (!runs.length)
|
|
249
|
+
return '';
|
|
250
|
+
const candidates = runs
|
|
251
|
+
.map(s => s.trim())
|
|
252
|
+
.filter(s => s.length >= 2 && s.length <= 800)
|
|
253
|
+
.filter(s => !ATTRIBUTED_BODY_NOISE.has(s))
|
|
254
|
+
.filter(s => !/^NS[A-Za-z]+$/.test(s))
|
|
255
|
+
.filter(s => !/^[\[\]{}()<>]+$/.test(s));
|
|
256
|
+
if (!candidates.length)
|
|
257
|
+
return '';
|
|
258
|
+
candidates.sort((a, b) => scoreCandidate(b) - scoreCandidate(a));
|
|
259
|
+
return candidates[0];
|
|
260
|
+
}
|
|
261
|
+
function scoreCandidate(text) {
|
|
262
|
+
let score = text.length;
|
|
263
|
+
if (text.includes(' '))
|
|
264
|
+
score += 20;
|
|
265
|
+
if (/[A-Za-z]/.test(text))
|
|
266
|
+
score += 10;
|
|
267
|
+
if (/streamtyped|NSDictionary|NSAttributed|NSFont/i.test(text))
|
|
268
|
+
score -= 200;
|
|
269
|
+
if (/^[a-f0-9]{8,}$/i.test(text))
|
|
270
|
+
score -= 80;
|
|
271
|
+
if (/^\[cc:[a-f0-9]{8}\]/i.test(text))
|
|
272
|
+
score -= 40;
|
|
273
|
+
return score;
|
|
274
|
+
}
|
|
275
|
+
function normalizeSentText(raw) {
|
|
276
|
+
let text = raw || '';
|
|
277
|
+
if (!text)
|
|
278
|
+
return '';
|
|
279
|
+
text = text
|
|
280
|
+
.replace(/\uFFFC/g, ' ')
|
|
281
|
+
.replace(/\[cc:[a-f0-9]{8}\]\s*/ig, '')
|
|
282
|
+
.replace(/^[+>"'#*]+\s*/, '')
|
|
283
|
+
.replace(/[\u0000-\u001f\u007f]+/g, ' ')
|
|
284
|
+
.replace(/\s+/g, ' ')
|
|
285
|
+
.trim();
|
|
286
|
+
if (!text)
|
|
287
|
+
return '';
|
|
288
|
+
if (TAPBACK_RE.test(text))
|
|
289
|
+
return '';
|
|
290
|
+
return text;
|
|
291
|
+
}
|
|
292
|
+
function buildSelfSummaryPrompt(transcript, user) {
|
|
293
|
+
return `You are an expert at detecting personal patterns and characteristics from chat transcripts.
|
|
294
|
+
|
|
295
|
+
TASK — Write a rich "highlight reel" for **${user}**, focusing on their personal characteristics,
|
|
296
|
+
interests, and behaviors as revealed through their own outbound messages.
|
|
297
|
+
|
|
298
|
+
This transcript contains messages sent BY the user. Infer patterns from what they choose to say.
|
|
299
|
+
|
|
300
|
+
Identify: Places · Projects · Hobbies · Values · Recurring topics · Personality traits · Decision style
|
|
301
|
+
|
|
302
|
+
FORMAT (markdown only):
|
|
303
|
+
1) First block: a high-signal user snapshot in 4-6 sentences (dense, specific, proper nouns where available).
|
|
304
|
+
2) Then 8-12 bullets in this format:
|
|
305
|
+
<Characteristic> → <Pattern> (<helpful context grounded in message evidence>)
|
|
306
|
+
|
|
307
|
+
RULES:
|
|
308
|
+
- Speak directly to the reader as "you".
|
|
309
|
+
- Focus on PERSONAL characteristics, not relationship analysis.
|
|
310
|
+
- No corporate tone, no fluff, no invented facts.
|
|
311
|
+
- If evidence is weak, skip it.
|
|
312
|
+
- High entity density, concise writing.
|
|
313
|
+
- Never use refusal/apology boilerplate.
|
|
314
|
+
|
|
315
|
+
Voice: write like a sharp friend who knows them well and helps them see their own patterns.
|
|
316
|
+
Aim for "Huh, that's exactly me."
|
|
317
|
+
|
|
318
|
+
Conversation history:
|
|
319
|
+
<transcript>
|
|
320
|
+
${transcript}
|
|
321
|
+
</transcript>
|
|
322
|
+
|
|
323
|
+
Output only the markdown summary.`;
|
|
324
|
+
}
|
package/dist/progress.d.ts
CHANGED
package/dist/progress.js
CHANGED
|
@@ -11,30 +11,51 @@ 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: '
|
|
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: '
|
|
29
|
-
get_claude_history: '
|
|
30
|
-
get_chatgpt_history: '
|
|
31
|
-
get_interests_for_plans: '
|
|
32
|
-
get_calendar: '
|
|
33
|
-
get_reminders: '
|
|
34
|
-
get_notifications: '
|
|
35
|
-
discover_platforms: '
|
|
36
|
-
generate_archetype: '
|
|
37
|
-
search_emails: '
|
|
14
|
+
search_all_messages: 'messages: keyword matches',
|
|
15
|
+
get_conversation: 'messages: thread context',
|
|
16
|
+
profile_contact: 'contact profile',
|
|
17
|
+
get_messages: 'messages: recent threads',
|
|
18
|
+
get_unanswered_messages: 'messages: waiting on reply',
|
|
19
|
+
get_screen_time: 'screen time',
|
|
20
|
+
get_browsing: 'browser history',
|
|
21
|
+
get_calls: 'call history',
|
|
22
|
+
scan_sources: 'source availability',
|
|
23
|
+
lookup_contact: 'contact lookup',
|
|
24
|
+
get_email_summary: 'mail summary',
|
|
25
|
+
get_git_activity: 'git activity',
|
|
26
|
+
get_recent_files: 'recent files',
|
|
27
|
+
get_shell_history: 'shell history',
|
|
28
|
+
get_notes: 'notes',
|
|
29
|
+
get_claude_history: 'claude chats',
|
|
30
|
+
get_chatgpt_history: 'chatgpt chats',
|
|
31
|
+
get_interests_for_plans: 'planning signals',
|
|
32
|
+
get_calendar: 'calendar (next 7d)',
|
|
33
|
+
get_reminders: 'reminders',
|
|
34
|
+
get_notifications: 'notifications',
|
|
35
|
+
discover_platforms: 'platform detection',
|
|
36
|
+
generate_archetype: 'profile synthesis',
|
|
37
|
+
search_emails: 'mail search',
|
|
38
|
+
};
|
|
39
|
+
const TOOL_SOURCE = {
|
|
40
|
+
scan_sources: 'source index',
|
|
41
|
+
get_messages: 'messages',
|
|
42
|
+
get_unanswered_messages: 'messages',
|
|
43
|
+
get_conversation: 'messages',
|
|
44
|
+
search_all_messages: 'messages',
|
|
45
|
+
get_calendar: 'calendar',
|
|
46
|
+
get_calls: 'calls',
|
|
47
|
+
get_email_summary: 'mail',
|
|
48
|
+
search_emails: 'mail',
|
|
49
|
+
get_claude_history: 'claude',
|
|
50
|
+
get_chatgpt_history: 'chatgpt',
|
|
51
|
+
get_notes: 'notes',
|
|
52
|
+
get_recent_files: 'files',
|
|
53
|
+
get_shell_history: 'shell',
|
|
54
|
+
get_git_activity: 'git',
|
|
55
|
+
get_screen_time: 'screen time',
|
|
56
|
+
get_browsing: 'browser',
|
|
57
|
+
get_notifications: 'notifications',
|
|
58
|
+
get_reminders: 'reminders',
|
|
38
59
|
};
|
|
39
60
|
const MAX_VISIBLE = 4;
|
|
40
61
|
export class ProgressRenderer {
|
|
@@ -114,6 +135,15 @@ export class ProgressRenderer {
|
|
|
114
135
|
}
|
|
115
136
|
s(name) { return LABEL[name] || name; }
|
|
116
137
|
ms(t) { return (t / 1000).toFixed(1) + 's'; }
|
|
138
|
+
phaseForTools(active) {
|
|
139
|
+
const sources = [...new Set(active.map(t => TOOL_SOURCE[t.name] || 'other'))];
|
|
140
|
+
if (!sources.length)
|
|
141
|
+
return 'scanning sources';
|
|
142
|
+
const lead = sources.slice(0, 2).join(' + ');
|
|
143
|
+
return sources.length > 2
|
|
144
|
+
? `scanning ${lead} +${sources.length - 2}`
|
|
145
|
+
: `scanning ${lead}`;
|
|
146
|
+
}
|
|
117
147
|
spin(offset = 0) {
|
|
118
148
|
return ACCENT(FRAMES[(this.frame + offset) % FRAMES.length]);
|
|
119
149
|
}
|
|
@@ -128,11 +158,12 @@ export class ProgressRenderer {
|
|
|
128
158
|
const done = this.tools.filter(t => t.done).length;
|
|
129
159
|
const total = this.tools.length;
|
|
130
160
|
if (done === total) {
|
|
131
|
-
lines.push(` ${OK('✓')} ${chalk.dim('
|
|
161
|
+
lines.push(` ${OK('✓')} ${chalk.dim('scan complete')}`);
|
|
132
162
|
}
|
|
133
163
|
else {
|
|
134
|
-
lines.push(` ${chalk.dim('scanning')} ${this.bar(done, total)} ${chalk.white(String(done))}${chalk.dim('/')}${chalk.white(String(total))}`);
|
|
135
164
|
const active = this.tools.filter(t => !t.done);
|
|
165
|
+
const phase = this.phaseForTools(active);
|
|
166
|
+
lines.push(` ${chalk.dim(phase)} ${this.bar(done, total)} ${chalk.white(String(done))}${chalk.dim('/')}${chalk.white(String(total))}`);
|
|
136
167
|
const show = active.slice(0, MAX_VISIBLE);
|
|
137
168
|
for (let i = 0; i < show.length; i++) {
|
|
138
169
|
lines.push(` ${this.spin(i)} ${chalk.white(this.s(show[i].name))}`);
|
|
@@ -149,7 +180,8 @@ export class ProgressRenderer {
|
|
|
149
180
|
const total = this.workers.length;
|
|
150
181
|
const active = this.workers.filter(w => !w.done);
|
|
151
182
|
if (active.length > 0) {
|
|
152
|
-
|
|
183
|
+
const phase = `investigating ${active.length} item${active.length === 1 ? '' : 's'}`;
|
|
184
|
+
lines.push(` ${chalk.dim(phase)} ${this.bar(done, total)} ${chalk.white(String(done))}${chalk.dim('/')}${chalk.white(String(total))}`);
|
|
153
185
|
const show = active.slice(0, MAX_VISIBLE);
|
|
154
186
|
for (let i = 0; i < show.length; i++) {
|
|
155
187
|
const w = show[i];
|
|
@@ -161,16 +193,20 @@ export class ProgressRenderer {
|
|
|
161
193
|
}
|
|
162
194
|
}
|
|
163
195
|
else {
|
|
164
|
-
lines.push(` ${
|
|
196
|
+
lines.push(` ${this.spin()} ${chalk.white('writing briefing')}`);
|
|
197
|
+
const doneWorkers = this.workers.filter(w => w.done && !w.failed).slice(-MAX_VISIBLE);
|
|
198
|
+
for (const w of doneWorkers) {
|
|
199
|
+
lines.push(` ${chalk.dim('·')} ${chalk.dim(w.label)}`);
|
|
200
|
+
}
|
|
165
201
|
}
|
|
166
202
|
lines.push('');
|
|
167
203
|
}
|
|
168
204
|
if (this.auqWaiting) {
|
|
169
|
-
lines.push(` ${this.spin()} ${chalk.white('
|
|
205
|
+
lines.push(` ${this.spin()} ${chalk.white('awaiting your pick')}`);
|
|
170
206
|
lines.push('');
|
|
171
207
|
}
|
|
172
|
-
if (this.isThinking) {
|
|
173
|
-
lines.push(` ${this.spin()} ${chalk.white('thinking
|
|
208
|
+
if (this.isThinking && !this.workers.length) {
|
|
209
|
+
lines.push(` ${this.spin()} ${chalk.white('thinking')}`);
|
|
174
210
|
lines.push('');
|
|
175
211
|
}
|
|
176
212
|
// Clear previous render
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local identity + memory injection (OpenClaw-style).
|
|
3
|
+
*
|
|
4
|
+
* Priority order:
|
|
5
|
+
* 1) Explicit env path
|
|
6
|
+
* 2) Current working directory
|
|
7
|
+
* 3) ~/.life-pulse
|
|
8
|
+
* 4) ~/.config/life-pulse
|
|
9
|
+
*/
|
|
10
|
+
export interface PromptLayers {
|
|
11
|
+
soul: string;
|
|
12
|
+
memory: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function loadPromptLayers(): PromptLayers;
|
|
15
|
+
export declare function buildPromptLayersSection(): string;
|
|
16
|
+
/** First-run bootstrap: create default SOUL.md + MEMORY.md if missing. */
|
|
17
|
+
export declare function ensurePromptLayerFiles(): string[];
|