life-pulse 2.3.2 → 2.3.4
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/dist/agent.js +10 -11
- package/dist/cli.js +9 -5
- package/dist/crm.js +22 -11
- package/dist/tui.js +14 -16
- package/dist/ui/app.js +2 -2
- package/dist/ui/progress.js +2 -2
- package/dist/ui/theme.js +1 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -348,18 +348,17 @@ progress, onCard) {
|
|
|
348
348
|
// User prompt
|
|
349
349
|
const prompt = `Run the executor protocol. One shot. Tell me what actually matters.
|
|
350
350
|
|
|
351
|
-
1. SCAN
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
- For unknown contacts: search_emails(sender=NAME) before labeling unknown
|
|
351
|
+
1. SCAN — call these in parallel:
|
|
352
|
+
get_claude_history(days=3), get_chatgpt_history, scan_sources, get_calendar(days_ahead=7), get_unanswered_messages, get_messages(days=7, limit=100), get_calls(missed_only=true)
|
|
353
|
+
For unknown contacts: search_emails(sender=NAME) before labeling unknown.
|
|
355
354
|
|
|
356
|
-
2.
|
|
355
|
+
2. STREAM — as SOON as you identify an item, dispatch a worker AND present it:
|
|
356
|
+
- Dispatch a Task worker to investigate (draft reply, gather context, etc.)
|
|
357
|
+
- Immediately call ask_user with your best recommendation. Don't wait for other workers.
|
|
358
|
+
- ${name} picks inline while other workers run in parallel.
|
|
359
|
+
- CRITICAL: present the FIRST card within 30 seconds. Don't wait until all scanning is done.
|
|
357
360
|
|
|
358
|
-
3.
|
|
359
|
-
|
|
360
|
-
4. For each decision, present via ask_user. ${name} picks inline.
|
|
361
|
-
|
|
362
|
-
5. SYNTHESIZE into final briefing.
|
|
361
|
+
3. SYNTHESIZE — after all cards are presented, output final JSON.
|
|
363
362
|
|
|
364
363
|
Output JSON (no markdown, no code fences):
|
|
365
364
|
{
|
|
@@ -374,7 +373,7 @@ Output JSON (no markdown, no code fences):
|
|
|
374
373
|
|
|
375
374
|
RULES:
|
|
376
375
|
- Promises/blockers: 2-3 options. First = recommendation. Description = draft text.
|
|
377
|
-
- Present each decision via ask_user
|
|
376
|
+
- Present each decision via ask_user THE MOMENT you have enough context. Never batch.
|
|
378
377
|
- Bumps: 1-2 options. Alpha: plain strings.
|
|
379
378
|
- HARD LIMIT: 4 promises, 4 blockers, 3 bumps, 3 alpha, 3 handled.
|
|
380
379
|
- Empty sections = fine. Short briefing = gift.`;
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { saveState, saveDecisions } from './state.js';
|
|
|
6
6
|
// ProgressRenderer unused — removed
|
|
7
7
|
import { InkProgress } from './ui/progress.js';
|
|
8
8
|
import { addTodo, resolveTodos, pruneOld } from './todo.js';
|
|
9
|
-
import { renderIntro, renderReveal, renderCRMList, pickCard, renderSection, renderSectionHeader, renderHandled, renderDivider, renderBrief, renderArchetype, Spinner, destroyUI, MAG, CYN, RED, AMB, MID, DIM } from './tui.js';
|
|
9
|
+
import { renderIntro, renderReveal, renderCRMList, renderCRMContact, pickCard, renderSection, renderSectionHeader, renderHandled, renderDivider, renderBrief, renderArchetype, Spinner, destroyUI, MAG, CYN, RED, AMB, MID, DIM } from './tui.js';
|
|
10
10
|
import { needsDiscovery, discoverPlatforms, savePlatforms } from './platforms.js';
|
|
11
11
|
import { generateArchetype } from './archetype.js';
|
|
12
12
|
import { runPermissionFlow, getMissingPermissions } from './permissions.js';
|
|
@@ -97,22 +97,26 @@ async function showCRM(apiKey, opts) {
|
|
|
97
97
|
innerCircle,
|
|
98
98
|
aboutToBreak,
|
|
99
99
|
});
|
|
100
|
-
renderCRMList(crm.threads);
|
|
101
|
-
// Enrich in background (for agent context + brief)
|
|
102
|
-
opts?.spinner?.start('understanding the room');
|
|
103
100
|
const calendarContext = opts?.calendarContext ?? '';
|
|
104
101
|
const hour = new Date().getHours();
|
|
105
102
|
const timeOfDay = hour < 12 ? 'morning' : hour < 17 ? 'afternoon' : 'evening';
|
|
103
|
+
// Stream contacts as they enrich — each name + relationship appears live
|
|
106
104
|
let brief = '';
|
|
107
105
|
const enriched = [];
|
|
106
|
+
const enrichedNames = new Set();
|
|
108
107
|
for await (const t of streamEnrichedCRM(crm, apiKey, {
|
|
109
108
|
calendarContext,
|
|
110
109
|
timeOfDay,
|
|
111
110
|
onBrief: (b) => { brief = b; },
|
|
112
111
|
})) {
|
|
113
112
|
enriched.push(t);
|
|
113
|
+
enrichedNames.add(t.name);
|
|
114
|
+
await renderCRMContact(t);
|
|
114
115
|
}
|
|
115
|
-
|
|
116
|
+
// Show remaining contacts (not enriched) — just names
|
|
117
|
+
const remaining = crm.threads.filter(t => !enrichedNames.has(t.name)).slice(0, 5);
|
|
118
|
+
if (remaining.length)
|
|
119
|
+
renderCRMList(remaining);
|
|
116
120
|
if (brief)
|
|
117
121
|
await renderBrief(brief);
|
|
118
122
|
saveContactSummaries(enriched);
|
package/dist/crm.js
CHANGED
|
@@ -406,33 +406,30 @@ STYLE GUIDE:
|
|
|
406
406
|
${contactBlocks}`,
|
|
407
407
|
}],
|
|
408
408
|
});
|
|
409
|
-
|
|
410
|
-
const fullText = response.content
|
|
411
|
-
.filter(b => b.type === 'text')
|
|
412
|
-
.map(b => b.text)
|
|
413
|
-
.join('');
|
|
409
|
+
// Stream text deltas — yield contacts the moment each block completes
|
|
414
410
|
let section = 'preamble';
|
|
415
411
|
const briefLines = [];
|
|
416
|
-
|
|
412
|
+
let lineBuf = '';
|
|
413
|
+
const processLine = (rawLine) => {
|
|
417
414
|
const line = rawLine.trim();
|
|
418
415
|
if (/^CONTACTS:?$/i.test(line)) {
|
|
419
416
|
section = 'contacts';
|
|
420
|
-
|
|
417
|
+
return;
|
|
421
418
|
}
|
|
422
419
|
if (/^BRIEF:?$/i.test(line)) {
|
|
423
420
|
flushContact();
|
|
424
421
|
section = 'brief';
|
|
425
|
-
|
|
422
|
+
return;
|
|
426
423
|
}
|
|
427
424
|
const bracketMatch = line.match(/^\[([^\]]+)\]/);
|
|
428
425
|
if (bracketMatch && (section === 'contacts' || section === 'preamble')) {
|
|
429
426
|
flushContact();
|
|
430
427
|
curName = bracketMatch[1].trim();
|
|
431
428
|
section = 'contacts';
|
|
432
|
-
|
|
429
|
+
return;
|
|
433
430
|
}
|
|
434
431
|
if (!line)
|
|
435
|
-
|
|
432
|
+
return;
|
|
436
433
|
if (section === 'contacts' || section === 'preamble') {
|
|
437
434
|
if (line.startsWith('Priority:')) {
|
|
438
435
|
curPriority = line.replace('Priority:', '').trim().toLowerCase();
|
|
@@ -444,7 +441,21 @@ ${contactBlocks}`,
|
|
|
444
441
|
else if (section === 'brief') {
|
|
445
442
|
briefLines.push(line);
|
|
446
443
|
}
|
|
444
|
+
};
|
|
445
|
+
for await (const event of stream) {
|
|
446
|
+
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
|
|
447
|
+
lineBuf += event.delta.text;
|
|
448
|
+
const parts = lineBuf.split('\n');
|
|
449
|
+
lineBuf = parts.pop() || '';
|
|
450
|
+
for (const line of parts)
|
|
451
|
+
processLine(line);
|
|
452
|
+
while (ready.length)
|
|
453
|
+
yield ready.shift();
|
|
454
|
+
}
|
|
447
455
|
}
|
|
456
|
+
// Flush remaining buffer
|
|
457
|
+
if (lineBuf.trim())
|
|
458
|
+
processLine(lineBuf);
|
|
448
459
|
flushContact();
|
|
449
460
|
while (ready.length)
|
|
450
461
|
yield ready.shift();
|
|
@@ -452,7 +463,7 @@ ${contactBlocks}`,
|
|
|
452
463
|
opts.onBrief(briefLines.join(' '));
|
|
453
464
|
}
|
|
454
465
|
catch (err) {
|
|
455
|
-
|
|
466
|
+
// silent — contacts still show without enrichment
|
|
456
467
|
}
|
|
457
468
|
for (const t of top) {
|
|
458
469
|
if (!yielded.has(t.name) && t.recentSnippets?.length)
|
package/dist/tui.js
CHANGED
|
@@ -39,19 +39,21 @@ async function typewrite(text, style, delay = 30) {
|
|
|
39
39
|
await _sleep(delay);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
async function
|
|
42
|
+
async function fetchLocationWeather() {
|
|
43
43
|
try {
|
|
44
44
|
const ac = new AbortController();
|
|
45
|
-
const t = setTimeout(() => ac.abort(),
|
|
46
|
-
const r = await fetch(`https://wttr.in
|
|
45
|
+
const t = setTimeout(() => ac.abort(), 3000);
|
|
46
|
+
const r = await fetch(`https://wttr.in/?format=%l|%t|%C`, { signal: ac.signal, headers: { 'User-Agent': 'life-pulse' } });
|
|
47
47
|
clearTimeout(t);
|
|
48
48
|
const raw = (await r.text()).trim();
|
|
49
|
-
const [temp, cond] = raw.split('|');
|
|
50
|
-
if (!temp || !cond)
|
|
49
|
+
const [loc, temp, cond] = raw.split('|');
|
|
50
|
+
if (!loc || !temp || !cond)
|
|
51
51
|
return null;
|
|
52
|
+
// loc comes back as "City, Region, Country" — take just the city
|
|
53
|
+
const city = loc.split(',')[0].trim();
|
|
52
54
|
const deg = temp.replace(/[+\s]/g, '').replace(/°[CF]/, '°');
|
|
53
55
|
const sky = cond.trim().toLowerCase();
|
|
54
|
-
return `${deg} and ${sky}
|
|
56
|
+
return { city, weather: `${deg} and ${sky}` };
|
|
55
57
|
}
|
|
56
58
|
catch {
|
|
57
59
|
return null;
|
|
@@ -118,10 +120,8 @@ function renderScanField(cols, rows) {
|
|
|
118
120
|
}
|
|
119
121
|
export async function renderIntro(name) {
|
|
120
122
|
const firstName = name?.split(' ')[0] || name;
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
// Fire weather fetch while animation runs
|
|
124
|
-
const weatherP = city ? fetchWeather(city) : Promise.resolve(null);
|
|
123
|
+
// Fire IP-based location + weather fetch while animation runs
|
|
124
|
+
const locP = fetchLocationWeather();
|
|
125
125
|
const cols = process.stdout.columns || 80;
|
|
126
126
|
const rows = process.stdout.rows || 24;
|
|
127
127
|
// Push old content out of scrollback
|
|
@@ -144,12 +144,10 @@ export async function renderIntro(name) {
|
|
|
144
144
|
await typewrite(hey, G1, 35);
|
|
145
145
|
process.stdout.write('\n');
|
|
146
146
|
await _sleep(250);
|
|
147
|
-
// "I see you're in
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
: city ? `I see you're in ${city}.` : '';
|
|
152
|
-
if (locLine) {
|
|
147
|
+
// "I see you're in Short Hills. 42° and cloudy."
|
|
148
|
+
const loc = await locP;
|
|
149
|
+
if (loc) {
|
|
150
|
+
const locLine = `I see you're in ${loc.city}. ${weatherRemark(loc.weather)}`;
|
|
153
151
|
process.stdout.write(' ');
|
|
154
152
|
await typewrite(locLine, G2, 20);
|
|
155
153
|
process.stdout.write('\n');
|
package/dist/ui/app.js
CHANGED
|
@@ -25,13 +25,13 @@ const Bar = ({ done, total }) => {
|
|
|
25
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
|
-
const spin = (off = 0) => BAR_CHARS[(frame + off) % BAR_CHARS.length];
|
|
28
|
+
const spin = (off = 0) => BAR_CHARS[(Math.floor((frame + off) / 5)) % BAR_CHARS.length];
|
|
29
29
|
if (progress.thinking && !progress.items.length && !progress.total) {
|
|
30
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: "#3b4261", children: spin() }), ' ', _jsx(Text, { color: "#565f89", children: phase === 'scanning' ? 'scanned' : 'putting it together' })] })) : 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 || [];
|
package/dist/ui/progress.js
CHANGED
|
@@ -43,14 +43,14 @@ export class InkProgress {
|
|
|
43
43
|
const workersDone = this.workers.filter(w => w.done).length;
|
|
44
44
|
const activeWorkers = this.workers.filter(w => !w.done);
|
|
45
45
|
if (this.workers.length > 0) {
|
|
46
|
-
showProgress(activeWorkers.length > 0 ? 'working' : '
|
|
46
|
+
showProgress(activeWorkers.length > 0 ? 'working' : 'finishing up', workersDone, this.workers.length, activeWorkers.map(w => ({
|
|
47
47
|
label: w.label,
|
|
48
48
|
tool: w.tool ? this.s(w.tool) : undefined,
|
|
49
49
|
})), this._thinking);
|
|
50
50
|
}
|
|
51
51
|
else if (this.tools.length > 0) {
|
|
52
52
|
const active = this.tools.filter(t => !t.done);
|
|
53
|
-
showProgress(toolsDone === this.tools.length ? '
|
|
53
|
+
showProgress(toolsDone === this.tools.length ? 'finishing up' : 'scanning', toolsDone, this.tools.length, active.map(t => ({ label: this.s(t.name) })), this._thinking);
|
|
54
54
|
}
|
|
55
55
|
else if (this._thinking) {
|
|
56
56
|
showProgress('', 0, 0, [], true);
|
package/dist/ui/theme.js
CHANGED
|
@@ -51,7 +51,7 @@ export const W = () => Math.min(process.stdout.columns || 80, 80);
|
|
|
51
51
|
export const INNER = () => W() - 4;
|
|
52
52
|
// Spinner frames
|
|
53
53
|
export const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
54
|
-
export const BAR_CHARS = ['
|
|
54
|
+
export const BAR_CHARS = ['✶', '*', '✢'];
|
|
55
55
|
// Progress label map — personality, not developer console
|
|
56
56
|
export const TOOL_LABEL = {
|
|
57
57
|
search_all_messages: 'reading the room',
|