life-pulse 2.3.10 → 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 +336 -158
- 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/README.md
CHANGED
|
@@ -76,6 +76,7 @@ life-pulse --setup # Run first-time setup flow
|
|
|
76
76
|
life-pulse --status # Show session progress info
|
|
77
77
|
life-pulse --daemon # Run as background daemon
|
|
78
78
|
life-pulse --health # Check daemon health (JSON)
|
|
79
|
+
life-pulse --pair # Create Desktop/nox-route.json for NOX routing
|
|
79
80
|
life-pulse --raw # Output raw collected data
|
|
80
81
|
life-pulse --json # Output analysis as JSON
|
|
81
82
|
life-pulse --legacy # Use single-shot LLM mode
|
|
@@ -90,10 +91,28 @@ export ANTHROPIC_API_KEY="sk-ant-..."
|
|
|
90
91
|
|
|
91
92
|
# Optional: OpenRouter for legacy mode
|
|
92
93
|
export OPENROUTER_API_KEY="..."
|
|
94
|
+
|
|
95
|
+
# Optional: end-of-brief text target (E.164 preferred)
|
|
96
|
+
export LIFE_PULSE_BRIEF_SMS_PHONE="+14155551234"
|
|
93
97
|
```
|
|
94
98
|
|
|
95
99
|
Store in `~/.config/life-pulse/.env` for persistence.
|
|
96
100
|
|
|
101
|
+
## Identity + Memory Injection
|
|
102
|
+
|
|
103
|
+
OpenClaw-style prompt layers are supported. On every turn, life-pulse will load:
|
|
104
|
+
|
|
105
|
+
- `SOUL.md` (identity/instructions)
|
|
106
|
+
- `MEMORY.md` (persistent context)
|
|
107
|
+
|
|
108
|
+
Defaults are auto-created at first install/run in `~/.life-pulse/`.
|
|
109
|
+
|
|
110
|
+
Search order is:
|
|
111
|
+
1. `LIFE_PULSE_SOUL_PATH` / `LIFE_PULSE_MEMORY_PATH`
|
|
112
|
+
2. current working directory
|
|
113
|
+
3. `~/.life-pulse/`
|
|
114
|
+
4. `~/.config/life-pulse/`
|
|
115
|
+
|
|
97
116
|
## Data Sources
|
|
98
117
|
|
|
99
118
|
life-pulse reads from local macOS databases (requires Full Disk Access):
|
package/dist/agent.js
CHANGED
|
@@ -15,11 +15,12 @@ import { TOOLS, executeTool } from './tools.js';
|
|
|
15
15
|
import { buildDeltaContext } from './state.js';
|
|
16
16
|
import { buildTodoContext } from './todo.js';
|
|
17
17
|
import { buildSessionContext } from './session-progress.js';
|
|
18
|
-
import { getUserName, buildProfile } from './profile.js';
|
|
18
|
+
import { getUserName, buildProfile, buildPersonalSummary } from './profile.js';
|
|
19
19
|
import { buildCRM, buildCRMContext } from './crm.js';
|
|
20
20
|
import { ask } from './auq.js';
|
|
21
21
|
import { loadPlatforms } from './platforms.js';
|
|
22
22
|
import { buildRemoteMcpServers } from './mcp-registry.js';
|
|
23
|
+
import { buildPromptLayersSection } from './prompt-layers.js';
|
|
23
24
|
const MAX_TURNS = 30;
|
|
24
25
|
const ORCHESTRATOR_MODEL = 'claude-opus-4-6';
|
|
25
26
|
// ─── JSON Schema → Zod conversion for tool params ───────────────
|
|
@@ -99,11 +100,18 @@ async function buildOrchestratorPrompt() {
|
|
|
99
100
|
const todos = buildTodoContext();
|
|
100
101
|
const profile = buildProfile();
|
|
101
102
|
const name = profile.name;
|
|
102
|
-
const crm = await
|
|
103
|
+
const [crm, personalSummary] = await Promise.all([
|
|
104
|
+
buildCRM(),
|
|
105
|
+
buildPersonalSummary(),
|
|
106
|
+
]);
|
|
103
107
|
const crmContext = buildCRMContext(crm);
|
|
104
108
|
const projectSection = profile.projects.length > 0
|
|
105
109
|
? `\n- Active projects: ${profile.projects.join(', ')}`
|
|
106
110
|
: '';
|
|
111
|
+
const personalSummarySection = personalSummary
|
|
112
|
+
? `\nPERSONAL PATTERNS (from ${name}'s own outbound messages):\n${personalSummary}`
|
|
113
|
+
: '';
|
|
114
|
+
const identityMemory = buildPromptLayersSection();
|
|
107
115
|
return `EXECUTIVE OPERATING SYSTEM — ${name.toUpperCase()}
|
|
108
116
|
|
|
109
117
|
You are the best executive assistant on earth. You know everything about this person — every message they've sent, every meeting they've had, every commitment they've made, every app they use, every relationship they maintain. You've been with them long enough to know who matters, what's slipping, and what they'd want to know before they know they want to know it.
|
|
@@ -116,6 +124,8 @@ WHO THIS IS:
|
|
|
116
124
|
Someone running at a pace where the failure mode isn't laziness — it's physics. More active commitments than any human brain can hold. The most important thing they need to do today is buried under 40 things screaming for attention. Your job is to dig it out.
|
|
117
125
|
- Uses iMessage as primary communication${projectSection}
|
|
118
126
|
${profile.platformSummary ? '\nPLATFORMS:\n' + profile.platformSummary : ''}
|
|
127
|
+
${personalSummarySection}
|
|
128
|
+
${identityMemory}
|
|
119
129
|
|
|
120
130
|
═══ WHAT YOU KNOW ═══
|
|
121
131
|
You know everyone in this person's life — who they are, how they're connected, what they owe each other, and the current state of every open thread. You understand the unspoken dynamics: who's waiting, who's frustrated, who's drifting, who would be thrilled to hear from them.
|
|
@@ -258,11 +268,13 @@ ${delta ? '\n' + delta : ''}${todos ? '\n' + todos : ''}`;
|
|
|
258
268
|
}
|
|
259
269
|
function buildWorkerPrompt() {
|
|
260
270
|
const name = getUserName();
|
|
271
|
+
const identityMemory = buildPromptLayersSection();
|
|
261
272
|
return `You are an execution worker for the Executive Operating System. You don't investigate and report — you DO THE WORK. ${name} says yes or no. That's it.
|
|
262
273
|
|
|
263
274
|
You have full capabilities: search messages, emails, contacts, web, files, shell. Use everything.
|
|
264
275
|
|
|
265
276
|
Produce READY-TO-USE output. Not suggestions. Not "you could consider." Actual drafts, exact numbers, real timelines. Have a take.
|
|
277
|
+
${identityMemory}
|
|
266
278
|
|
|
267
279
|
INVESTIGATION RULES:
|
|
268
280
|
- Be THOROUGH. Search for related keywords across ALL conversations, not just one thread.
|
package/dist/analyze.d.ts
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Analysis types for the life-pulse agent.
|
|
3
3
|
*/
|
|
4
4
|
export type DecisionOption = {
|
|
5
5
|
label: string;
|
|
6
6
|
description: string;
|
|
7
7
|
};
|
|
8
|
-
export type Decision = {
|
|
9
|
-
title: string;
|
|
10
|
-
urgency: 'now' | 'today' | 'this_week';
|
|
11
|
-
options?: DecisionOption[];
|
|
12
|
-
fyi?: boolean;
|
|
13
|
-
context?: string;
|
|
14
|
-
category?: string;
|
|
15
|
-
who?: string;
|
|
16
|
-
};
|
|
17
8
|
export type PromiseItem = {
|
|
18
9
|
title: string;
|
|
19
10
|
urgency: 'now' | 'today' | 'this_week';
|
|
@@ -42,13 +33,4 @@ export type Analysis = {
|
|
|
42
33
|
bumps?: BumpItem[];
|
|
43
34
|
alpha?: string[];
|
|
44
35
|
resolved_todos?: string[];
|
|
45
|
-
decisions?: Decision[];
|
|
46
|
-
upcoming?: string[];
|
|
47
|
-
intel?: string[];
|
|
48
|
-
right_now?: string[];
|
|
49
|
-
today?: string[];
|
|
50
|
-
this_week?: string[];
|
|
51
|
-
noticed?: string[];
|
|
52
|
-
heads_up?: string[];
|
|
53
36
|
};
|
|
54
|
-
export declare function analyzeWithLLM(collectedData: Record<string, unknown>, apiKey: string): Promise<Analysis>;
|
package/dist/analyze.js
CHANGED
|
@@ -1,130 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Analysis types for the life-pulse agent.
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
import { getUserName, buildProfile } from './profile.js';
|
|
6
|
-
const API_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
|
7
|
-
const MODEL = 'google/gemini-2.5-flash';
|
|
8
|
-
function buildPersona() {
|
|
9
|
-
const profile = buildProfile();
|
|
10
|
-
const name = profile.name;
|
|
11
|
-
const lines = [`WHO YOU'RE BRIEFING:\n${name}.`];
|
|
12
|
-
if (profile.topContacts.length > 0) {
|
|
13
|
-
lines.push(`\n${name.toUpperCase()}'S KEY CONTACTS (auto-detected by message volume, last 30 days):`);
|
|
14
|
-
const byTier = { T1: [], T2: [], T3: [], T4: [] };
|
|
15
|
-
for (const c of profile.topContacts) {
|
|
16
|
-
byTier[c.tier].push(`${c.name} (${c.msgs30d} msgs)`);
|
|
17
|
-
}
|
|
18
|
-
if (byTier.T1.length)
|
|
19
|
-
lines.push(`- Inner circle (very high volume): ${byTier.T1.join(', ')}`);
|
|
20
|
-
if (byTier.T2.length)
|
|
21
|
-
lines.push(`- Close contacts: ${byTier.T2.join(', ')}`);
|
|
22
|
-
if (byTier.T3.length)
|
|
23
|
-
lines.push(`- Regular contacts: ${byTier.T3.join(', ')}`);
|
|
24
|
-
if (byTier.T4.length)
|
|
25
|
-
lines.push(`- Occasional contacts: ${byTier.T4.join(', ')}`);
|
|
26
|
-
}
|
|
27
|
-
if (profile.projects.length > 0) {
|
|
28
|
-
lines.push(`\nACTIVE PROJECTS: ${profile.projects.join(', ')}`);
|
|
29
|
-
}
|
|
30
|
-
return lines.join('\n');
|
|
31
|
-
}
|
|
32
|
-
function buildSystemPrompt() {
|
|
33
|
-
const name = getUserName();
|
|
34
|
-
return `You are ${name}'s telepathic executive assistant. You know them better than they know themselves. You have access to 30 days of their digital life.
|
|
35
|
-
|
|
36
|
-
${buildPersona()}
|
|
37
|
-
|
|
38
|
-
YOUR DATA SOURCES (what you're looking at):
|
|
39
|
-
- iMessage conversations (full threads with timestamps)
|
|
40
|
-
- Phone call history (who, when, duration)
|
|
41
|
-
- Calendar events (upcoming commitments)
|
|
42
|
-
- Screen time (app usage, categories, daily totals)
|
|
43
|
-
- Safari + Chrome browsing history (URLs, search queries, timestamps)
|
|
44
|
-
- Shell/terminal history (commands run, what's being built)
|
|
45
|
-
- Apple Mail
|
|
46
|
-
- Apple Notes
|
|
47
|
-
- Notifications
|
|
48
|
-
- Recently opened files
|
|
49
|
-
- Find My (device locations)
|
|
50
|
-
- Installed applications
|
|
51
|
-
|
|
52
|
-
RULES:
|
|
53
|
-
- ALWAYS use people's NAMES. You know who everyone is. Never show phone numbers or handles.
|
|
54
|
-
- Be INSANELY specific. Not "respond to messages" but "Text [name] back about dinner — they asked you yesterday."
|
|
55
|
-
- EXPONENTIAL TIME WEIGHTING: Last 24 hours = CRITICAL. Last 7 days = important context. Last 30 days = background patterns. Weight urgency accordingly.
|
|
56
|
-
- Cross-reference EVERYTHING. Searches reveal thinking. Browsing reveals research. Messages reveal commitments. Screen time reveals where attention actually goes. Shell history reveals what's being built and what's broken.
|
|
57
|
-
- Use the contact tier data to prioritize — inner circle contacts get immediate attention, occasional contacts can wait.
|
|
58
|
-
- ONLY surface things you're CERTAIN about from the data. Wrong suggestions = you get turned off. Accuracy > coverage.
|
|
59
|
-
- No counting. No statistics. No "you sent X messages." They care about WHAT needs doing, not HOW MUCH was done.
|
|
60
|
-
- No generic advice. No "consider" or "you might want to." If something needs doing, say DO IT.
|
|
61
|
-
- Talk like a trusted friend with perfect memory. Not a report generator.
|
|
62
|
-
- Surface contradictions: researching sleep but browsing at 2am? Say it.
|
|
63
|
-
- Don't moralize about screen time or social media.
|
|
64
|
-
|
|
65
|
-
OUTPUT FORMAT — return ONLY this JSON (no markdown, no code fences):
|
|
66
|
-
{
|
|
67
|
-
"greeting": "One sentence. What their last day/week has been like, in plain human language.",
|
|
68
|
-
"right_now": [
|
|
69
|
-
"Most urgent action with specific names and context — things from the last 24 hours that need immediate attention"
|
|
70
|
-
],
|
|
71
|
-
"today": [
|
|
72
|
-
"Things to handle today — messages to respond to, follow-ups, commitments"
|
|
73
|
-
],
|
|
74
|
-
"this_week": [
|
|
75
|
-
"Things from the broader week/month that shouldn't be forgotten"
|
|
76
|
-
],
|
|
77
|
-
"heads_up": [
|
|
78
|
-
"Upcoming commitments, follow-ups promised, things from group chats that might be forgotten"
|
|
79
|
-
],
|
|
80
|
-
"noticed": [
|
|
81
|
-
"Patterns you noticed — health, relationships, work habits, the stuff a great EA would whisper. Only say things you're CONFIDENT about from the data."
|
|
82
|
-
]
|
|
83
|
-
}`;
|
|
84
|
-
}
|
|
85
|
-
export async function analyzeWithLLM(collectedData, apiKey) {
|
|
86
|
-
const contextStr = JSON.stringify(collectedData, null, 1);
|
|
87
|
-
const trimmed = contextStr.length > 200_000
|
|
88
|
-
? contextStr.slice(0, 200_000) + '\n...(truncated)'
|
|
89
|
-
: contextStr;
|
|
90
|
-
const deltaContext = buildDeltaContext();
|
|
91
|
-
const name = getUserName();
|
|
92
|
-
const userContent = `Analyze ${name}'s life right now. Tell them exactly what to do, starting with the most urgent.
|
|
93
|
-
|
|
94
|
-
Data from the last 30 days (most recent first — prioritize accordingly):
|
|
95
|
-
|
|
96
|
-
${trimmed}${deltaContext ? '\n\n' + deltaContext : ''}`;
|
|
97
|
-
const response = await fetch(API_URL, {
|
|
98
|
-
method: 'POST',
|
|
99
|
-
headers: {
|
|
100
|
-
'Content-Type': 'application/json',
|
|
101
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
102
|
-
},
|
|
103
|
-
body: JSON.stringify({
|
|
104
|
-
model: MODEL,
|
|
105
|
-
messages: [
|
|
106
|
-
{ role: 'system', content: buildSystemPrompt() },
|
|
107
|
-
{ role: 'user', content: userContent }
|
|
108
|
-
],
|
|
109
|
-
temperature: 0.3,
|
|
110
|
-
max_tokens: 4000,
|
|
111
|
-
})
|
|
112
|
-
});
|
|
113
|
-
if (!response.ok) {
|
|
114
|
-
const err = await response.text();
|
|
115
|
-
throw new Error(`API error ${response.status}: ${err}`);
|
|
116
|
-
}
|
|
117
|
-
const json = await response.json();
|
|
118
|
-
const content = json.choices?.[0]?.message?.content || '';
|
|
119
|
-
const cleaned = content.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
|
|
120
|
-
try {
|
|
121
|
-
return JSON.parse(cleaned);
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
return {
|
|
125
|
-
greeting: content.slice(0, 300),
|
|
126
|
-
right_now: [], today: [], this_week: [],
|
|
127
|
-
noticed: [], heads_up: []
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
4
|
+
export {};
|