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 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 buildCRM();
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
- * LLM-powered analysis. Sends collected data → gets back a personal briefing.
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
- * LLM-powered analysis. Sends collected data → gets back a personal briefing.
2
+ * Analysis types for the life-pulse agent.
3
3
  */
4
- import { buildDeltaContext } from './state.js';
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 {};