life-pulse 2.3.0 → 2.3.2
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 +4 -4
- package/dist/cli.js +51 -60
- package/dist/health.js +1 -1
- package/dist/icloud-discovery.js +7 -7
- package/dist/init-check.js +13 -17
- package/dist/installer.js +41 -63
- package/dist/progress.js +24 -24
- package/dist/router.js +1 -1
- package/dist/sms-gateway.js +2 -2
- package/dist/tui.d.ts +6 -0
- package/dist/tui.js +158 -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/README.md
CHANGED
package/dist/agent.js
CHANGED
|
@@ -147,26 +147,26 @@ One briefing. Organized by how much damage it does if ignored — not by app, no
|
|
|
147
147
|
|
|
148
148
|
Chase down every thread. Be relentless. Don't surface the obvious stuff they'd find on their own — find the things about to fall through the cracks that they don't even know about yet.
|
|
149
149
|
|
|
150
|
-
1. PROMISES
|
|
150
|
+
1. PROMISES
|
|
151
151
|
Things ${name} said they'd do that are about to break or already have. This is the most important section. Always.
|
|
152
152
|
Scan every outbound message for commitment language: "I'll send," "let me get back to you," "I'll loop in," "by Friday," "will do." Cross-reference against what was actually done. The gap = the briefing.
|
|
153
153
|
Reputation compounds. Every dropped commitment is a withdrawal from an account that's very hard to refill.
|
|
154
154
|
For each: who they promised, what specifically (not "follow up with Sarah" → "Send Sarah the revised deck she asked for Tuesday"), when it was due, and the one move to close it. Tight enough to do in 2 minutes or delegate in one sentence.
|
|
155
155
|
|
|
156
|
-
2. BLOCKERS
|
|
156
|
+
2. BLOCKERS
|
|
157
157
|
People quietly waiting on ${name}.
|
|
158
158
|
Find everywhere they're the bottleneck. Approvals sitting untouched. Questions asked that they never saw. Docs shared "for review" that really mean "I can't move until you respond."
|
|
159
159
|
Nobody says "you're blocking me" out loud. They just wait and quietly start resenting it. Find those moments before they fester.
|
|
160
160
|
For each: who's stuck, what they need, how long they've been waiting. Then make the call — yes, no, or delegate — with a one-line reason. Don't present options. Recommend.
|
|
161
161
|
|
|
162
|
-
3. BUMPS
|
|
162
|
+
3. BUMPS
|
|
163
163
|
Relationships worth a touch. Offense, not defense.
|
|
164
164
|
Conversations that went cold but shouldn't have. Intros offered but never made. Threads where someone shared something interesting and a reply would strengthen the relationship.
|
|
165
165
|
The question: "Who would be pleasantly surprised to hear from ${name} today?"
|
|
166
166
|
These aren't obligations — they're compound interest. A "congrats on the launch" text. A "saw this and thought of you" forward. That coffee they said they'd grab three weeks ago.
|
|
167
167
|
For each: who, why now, and a suggested message they can send as-is or edit in 10 seconds.
|
|
168
168
|
|
|
169
|
-
4. ALPHA
|
|
169
|
+
4. ALPHA
|
|
170
170
|
Moves they'd be pissed they missed.
|
|
171
171
|
Based on everything you know — what should they be doing that they haven't thought of?
|
|
172
172
|
A competitor just shipped something relevant. Someone in their network just raised a round. Two people they know should know each other but don't. Something they do every week that you could just handle for them.
|
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 {
|
|
9
|
+
import { renderIntro, renderReveal, renderCRMList, 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';
|
|
@@ -88,11 +88,18 @@ async function showCRM(apiKey, opts) {
|
|
|
88
88
|
opts?.spinner?.stop();
|
|
89
89
|
return false;
|
|
90
90
|
}
|
|
91
|
-
// Names appear INSTANTLY — no API call, just DB data
|
|
92
91
|
opts?.spinner?.stop();
|
|
92
|
+
// The reveal — silence, then the numbers drop
|
|
93
|
+
const innerCircle = crm.threads.filter(t => t.threadTemp === 'hot' || t.threadTemp === 'warm' || t.msgs30d > 20).length;
|
|
94
|
+
const aboutToBreak = crm.threads.filter(t => t.waitingOnYou).length;
|
|
95
|
+
await renderReveal({
|
|
96
|
+
conversations: crm.threads.length,
|
|
97
|
+
innerCircle,
|
|
98
|
+
aboutToBreak,
|
|
99
|
+
});
|
|
93
100
|
renderCRMList(crm.threads);
|
|
94
101
|
// Enrich in background (for agent context + brief)
|
|
95
|
-
opts?.spinner?.start('
|
|
102
|
+
opts?.spinner?.start('understanding the room');
|
|
96
103
|
const calendarContext = opts?.calendarContext ?? '';
|
|
97
104
|
const hour = new Date().getHours();
|
|
98
105
|
const timeOfDay = hour < 12 ? 'morning' : hour < 17 ? 'afternoon' : 'evening';
|
|
@@ -132,23 +139,19 @@ async function main() {
|
|
|
132
139
|
if (initCheckMode) {
|
|
133
140
|
const status = runInitCheck();
|
|
134
141
|
console.log();
|
|
135
|
-
console.log(chalk.bold(' PRE-FLIGHT CHECK'));
|
|
136
|
-
console.log();
|
|
137
142
|
for (const c of status.checks) {
|
|
138
143
|
const icon = c.passed ? chalk.green('✓') : chalk.red('✗');
|
|
139
|
-
|
|
140
|
-
console.log(` ${icon} ${c.check}${detail}`);
|
|
144
|
+
console.log(` ${icon} ${c.check}`);
|
|
141
145
|
}
|
|
142
146
|
console.log();
|
|
143
|
-
console.log(chalk.dim(` Mode: ${status.mode}`));
|
|
144
147
|
console.log(chalk.dim(` ${status.recommendation}`));
|
|
145
148
|
console.log();
|
|
146
149
|
return;
|
|
147
150
|
}
|
|
148
151
|
// --router: run message router on YOUR Mac (routes to client Mac Minis)
|
|
149
152
|
if (routerMode) {
|
|
150
|
-
console.log(chalk.bold.hex('#
|
|
151
|
-
console.log(chalk.dim(' routing messages
|
|
153
|
+
console.log(chalk.bold.hex('#c0caf5')(' life-pulse'));
|
|
154
|
+
console.log(chalk.dim(' routing messages'));
|
|
152
155
|
console.log();
|
|
153
156
|
const router = startRouter();
|
|
154
157
|
process.on('SIGINT', () => { router.stop(); process.exit(0); });
|
|
@@ -170,7 +173,7 @@ async function main() {
|
|
|
170
173
|
if (testSmsMode) {
|
|
171
174
|
const up = await isGatewayUp();
|
|
172
175
|
if (!up) {
|
|
173
|
-
console.log(chalk.red('
|
|
176
|
+
console.log(chalk.red(' not running — start life-pulse first'));
|
|
174
177
|
return;
|
|
175
178
|
}
|
|
176
179
|
const phone = process.argv[process.argv.indexOf('--test-sms') + 1] || '+1234567890';
|
|
@@ -178,15 +181,16 @@ async function main() {
|
|
|
178
181
|
const resp = await fetch('http://127.0.0.1:19877/inbound', {
|
|
179
182
|
method: 'POST',
|
|
180
183
|
headers: { 'Content-Type': 'application/json' },
|
|
181
|
-
body: JSON.stringify({ phone, message: 'test message from life-pulse
|
|
184
|
+
body: JSON.stringify({ phone, message: 'test message from life-pulse' }),
|
|
182
185
|
});
|
|
183
186
|
const data = await resp.json();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
if (data.response)
|
|
188
|
+
console.log(chalk.green(` replied: ${data.response}`));
|
|
189
|
+
else
|
|
190
|
+
console.log(chalk.red(` failed: ${data.error}`));
|
|
187
191
|
}
|
|
188
192
|
catch (err) {
|
|
189
|
-
console.log(chalk.red(`
|
|
193
|
+
console.log(chalk.red(` couldn't connect — is life-pulse running?`));
|
|
190
194
|
}
|
|
191
195
|
return;
|
|
192
196
|
}
|
|
@@ -203,8 +207,7 @@ async function main() {
|
|
|
203
207
|
}
|
|
204
208
|
// --daemon: check if already running
|
|
205
209
|
if (daemonMode && isDaemonRunning()) {
|
|
206
|
-
console.log(chalk.yellow('
|
|
207
|
-
console.log(chalk.dim(' use --health to check status'));
|
|
210
|
+
console.log(chalk.yellow(' already running'));
|
|
208
211
|
return;
|
|
209
212
|
}
|
|
210
213
|
// --status: show session progress info and exit
|
|
@@ -212,32 +215,26 @@ async function main() {
|
|
|
212
215
|
const progress = loadProgress();
|
|
213
216
|
const timeSince = getTimeSinceLastSession();
|
|
214
217
|
console.log();
|
|
215
|
-
console.log(chalk.
|
|
216
|
-
console.log();
|
|
217
|
-
console.log(` Sessions: ${progress.totalSessions}`);
|
|
218
|
-
console.log(` First run: ${dayjs(progress.firstRun).format('MMM D, YYYY')}`);
|
|
219
|
-
console.log(` Last session: ${timeSince.readable}`);
|
|
220
|
-
console.log();
|
|
218
|
+
console.log(chalk.dim(` since ${dayjs(progress.firstRun).format('MMM D, YYYY')} · ${progress.totalSessions} sessions`));
|
|
219
|
+
console.log(chalk.dim(` last check-in ${timeSince.readable}`));
|
|
221
220
|
const discoveries = getNewDiscoveries();
|
|
222
221
|
if (discoveries.length) {
|
|
223
|
-
console.log(
|
|
222
|
+
console.log();
|
|
224
223
|
for (const d of discoveries.slice(0, 5)) {
|
|
225
|
-
console.log(`
|
|
224
|
+
console.log(chalk.dim(` ${d.name}`));
|
|
226
225
|
}
|
|
227
226
|
}
|
|
228
|
-
// Skills summary
|
|
229
227
|
const skills = scanForSkills();
|
|
230
|
-
|
|
231
|
-
|
|
228
|
+
if (skills.active.length) {
|
|
229
|
+
console.log(chalk.dim(` ${skills.active.length} skills`));
|
|
230
|
+
}
|
|
232
231
|
const crashes = getCrashStats();
|
|
233
232
|
if (crashes.consecutive > 0) {
|
|
234
|
-
console.log(chalk.yellow(`
|
|
233
|
+
console.log(chalk.yellow(` something's wrong — ${crashes.consecutive} failures in a row`));
|
|
235
234
|
}
|
|
236
235
|
const missing = getMissingPermissions();
|
|
237
236
|
if (missing.length) {
|
|
238
|
-
console.log();
|
|
239
|
-
console.log(` Missing permissions: ${missing.map(m => m.label).join(', ')}`);
|
|
240
|
-
console.log(chalk.dim(' Run with --setup to configure'));
|
|
237
|
+
console.log(chalk.dim(` missing: ${missing.map(m => m.label).join(', ')}`));
|
|
241
238
|
}
|
|
242
239
|
console.log();
|
|
243
240
|
return;
|
|
@@ -354,17 +351,13 @@ async function main() {
|
|
|
354
351
|
</plist>`;
|
|
355
352
|
writeFileSync(join(agentsDir, 'com.life-pulse.morning.plist'), morningPlist);
|
|
356
353
|
writeFileSync(join(agentsDir, 'com.life-pulse.daemon.plist'), daemonPlist);
|
|
357
|
-
console.log(chalk.
|
|
358
|
-
console.log(chalk.dim(`
|
|
359
|
-
console.log(chalk.dim(` Daemon: KeepAlive (auto-restart on crash)`));
|
|
354
|
+
console.log(chalk.dim(' running in the background'));
|
|
355
|
+
console.log(chalk.dim(` morning brief at ${config.briefTime}`));
|
|
360
356
|
console.log();
|
|
361
|
-
console.log(chalk.dim(' To activate:'));
|
|
362
|
-
console.log(chalk.dim(' launchctl load ~/Library/LaunchAgents/com.life-pulse.daemon.plist'));
|
|
363
|
-
console.log(chalk.dim(' launchctl load ~/Library/LaunchAgents/com.life-pulse.morning.plist'));
|
|
364
357
|
return;
|
|
365
358
|
}
|
|
366
359
|
if (!key && !ANTHROPIC_KEY) {
|
|
367
|
-
console.log(chalk.red('\n\n
|
|
360
|
+
console.log(chalk.red('\n\n missing API key\n'));
|
|
368
361
|
process.exit(1);
|
|
369
362
|
}
|
|
370
363
|
const interactive = process.stdin.isTTY && !jsonMode && !legacyMode;
|
|
@@ -377,8 +370,7 @@ async function main() {
|
|
|
377
370
|
}
|
|
378
371
|
catch { }
|
|
379
372
|
if (interactive) {
|
|
380
|
-
|
|
381
|
-
renderContextLine();
|
|
373
|
+
await renderIntro(userName);
|
|
382
374
|
}
|
|
383
375
|
const spinner = interactive ? new Spinner() : undefined;
|
|
384
376
|
// ── First-run: permissions → discovery → archetype ──
|
|
@@ -389,7 +381,7 @@ async function main() {
|
|
|
389
381
|
// Welcome message for first run
|
|
390
382
|
if (isFirstRun && interactive) {
|
|
391
383
|
console.log();
|
|
392
|
-
console.log(chalk.bold.hex('#
|
|
384
|
+
console.log(chalk.bold.hex('#c0caf5')(' Welcome to life-pulse.'));
|
|
393
385
|
console.log(chalk.dim(' Let me learn about your setup...\n'));
|
|
394
386
|
}
|
|
395
387
|
else if (timeSinceLastSession.days > 7 && interactive) {
|
|
@@ -397,10 +389,10 @@ async function main() {
|
|
|
397
389
|
}
|
|
398
390
|
if (process.stdin.isTTY)
|
|
399
391
|
await runPermissionFlow();
|
|
400
|
-
spinner?.start('
|
|
392
|
+
spinner?.start('learning your world');
|
|
401
393
|
const platformProfile = discoverPlatforms();
|
|
402
394
|
// iCloud + local app discovery
|
|
403
|
-
spinner?.update('
|
|
395
|
+
spinner?.update('looking deeper');
|
|
404
396
|
const discoveredApps = runDiscovery();
|
|
405
397
|
if (discoveredApps.length && interactive) {
|
|
406
398
|
spinner?.stop();
|
|
@@ -409,7 +401,7 @@ async function main() {
|
|
|
409
401
|
console.log();
|
|
410
402
|
}
|
|
411
403
|
// Skill discovery: map found apps → activatable skills
|
|
412
|
-
spinner?.update('
|
|
404
|
+
spinner?.update('seeing what you use');
|
|
413
405
|
const skills = scanForSkills();
|
|
414
406
|
if (skills.newlyFound.length && interactive) {
|
|
415
407
|
spinner?.stop();
|
|
@@ -428,12 +420,12 @@ async function main() {
|
|
|
428
420
|
const toolCount = platformProfile.cliTools.length;
|
|
429
421
|
// CRM drip-feeds before archetype — contacts stream in one by one
|
|
430
422
|
if (interactive) {
|
|
431
|
-
spinner?.update('
|
|
423
|
+
spinner?.update('getting to know you');
|
|
432
424
|
const calCtx = await calendarP;
|
|
433
425
|
crmShown = await showCRM(ANTHROPIC_KEY, { calendarContext: calCtx, spinner });
|
|
434
426
|
}
|
|
435
427
|
if (ANTHROPIC_KEY) {
|
|
436
|
-
spinner?.start('
|
|
428
|
+
spinner?.start('figuring out who you are');
|
|
437
429
|
try {
|
|
438
430
|
const archetype = await generateArchetype(platformProfile, ANTHROPIC_KEY);
|
|
439
431
|
spinner?.stop();
|
|
@@ -459,10 +451,10 @@ async function main() {
|
|
|
459
451
|
if (legacyMode) {
|
|
460
452
|
spinner?.stop();
|
|
461
453
|
if (!jsonMode)
|
|
462
|
-
process.stdout.write(chalk.dim('\n
|
|
454
|
+
process.stdout.write(chalk.dim('\n pulling threads...'));
|
|
463
455
|
const collected = await collectAll();
|
|
464
456
|
if (!jsonMode)
|
|
465
|
-
process.stdout.write(chalk.dim(` ${collected.sources.length} sources
|
|
457
|
+
process.stdout.write(chalk.dim(` ${collected.sources.length} sources... thinking`));
|
|
466
458
|
const analysis = await analyzeWithLLM(collected.data, key);
|
|
467
459
|
if (jsonMode) {
|
|
468
460
|
console.log(JSON.stringify({ collected, analysis }, null, 2));
|
|
@@ -475,12 +467,12 @@ async function main() {
|
|
|
475
467
|
// Agent mode (default): multi-turn investigation with Anthropic
|
|
476
468
|
if (!ANTHROPIC_KEY) {
|
|
477
469
|
spinner?.stop();
|
|
478
|
-
console.log(chalk.red('\n\n
|
|
470
|
+
console.log(chalk.red('\n\n missing API key\n'));
|
|
479
471
|
process.exit(1);
|
|
480
472
|
}
|
|
481
473
|
// ── CRM: show relationship map before agent runs ──
|
|
482
474
|
if (interactive && !crmShown) {
|
|
483
|
-
spinner?.start('
|
|
475
|
+
spinner?.start('getting to know you');
|
|
484
476
|
const calCtx = await calendarP;
|
|
485
477
|
await showCRM(ANTHROPIC_KEY, { calendarContext: calCtx, spinner });
|
|
486
478
|
}
|
|
@@ -608,7 +600,7 @@ async function main() {
|
|
|
608
600
|
...(analysis.decisions || []),
|
|
609
601
|
].some((d) => d.options?.length);
|
|
610
602
|
if (!hasActionable && !(analysis.alpha?.length)) {
|
|
611
|
-
console.log(
|
|
603
|
+
console.log(DIM(' All clear — nothing needs you right now.'));
|
|
612
604
|
console.log();
|
|
613
605
|
}
|
|
614
606
|
renderDivider();
|
|
@@ -626,7 +618,7 @@ async function main() {
|
|
|
626
618
|
const tgToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
627
619
|
if (tgToken) {
|
|
628
620
|
transports.register(new TelegramTransport(tgToken));
|
|
629
|
-
|
|
621
|
+
// silent — Telegram just works
|
|
630
622
|
}
|
|
631
623
|
// Start transport layer for inbound messages
|
|
632
624
|
transports.startAll(async (inbound) => {
|
|
@@ -660,7 +652,7 @@ async function main() {
|
|
|
660
652
|
return result.response;
|
|
661
653
|
}
|
|
662
654
|
catch (err) {
|
|
663
|
-
process.stderr.write(`
|
|
655
|
+
process.stderr.write(` couldn't reply to ${contactName}\n`);
|
|
664
656
|
}
|
|
665
657
|
}
|
|
666
658
|
return null;
|
|
@@ -673,16 +665,15 @@ async function main() {
|
|
|
673
665
|
recordCrash(err.message, 'message-loop');
|
|
674
666
|
},
|
|
675
667
|
});
|
|
676
|
-
|
|
677
|
-
|
|
668
|
+
if (!daemonMode) {
|
|
669
|
+
console.log(chalk.dim(' listening'));
|
|
670
|
+
console.log();
|
|
671
|
+
}
|
|
678
672
|
// Start health server for daemon mode
|
|
679
673
|
let gw = null;
|
|
680
674
|
if (daemonMode) {
|
|
681
675
|
startHealthServer();
|
|
682
|
-
console.log(chalk.dim(` Health check: http://127.0.0.1:19876/health`));
|
|
683
|
-
// Start inbound gateway (rply-mac-server pushes messages here via Tailscale)
|
|
684
676
|
gw = startGateway(ANTHROPIC_KEY);
|
|
685
|
-
console.log(chalk.dim(` Gateway: :${gw.port} (rply-mac-server → Tailscale → here)`));
|
|
686
677
|
}
|
|
687
678
|
// Generate knowledge bases from CRM on startup
|
|
688
679
|
try {
|
package/dist/health.js
CHANGED
|
@@ -45,7 +45,7 @@ export function startHealthServer() {
|
|
|
45
45
|
healthServer.on('error', (e) => {
|
|
46
46
|
if (e.code === 'EADDRINUSE') {
|
|
47
47
|
// Another instance is running
|
|
48
|
-
console.error('
|
|
48
|
+
console.error(' already running');
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
}
|
package/dist/icloud-discovery.js
CHANGED
|
@@ -373,27 +373,27 @@ export function formatDiscoveryReport(discoveries) {
|
|
|
373
373
|
if (!discoveries.length)
|
|
374
374
|
return '';
|
|
375
375
|
const lines = [];
|
|
376
|
-
lines.push('
|
|
376
|
+
lines.push(' here\'s what I found:\n');
|
|
377
377
|
// Group by category
|
|
378
378
|
const noteApps = discoveries.filter(d => ['Obsidian', 'Bear', 'Logseq', 'Apple Notes', 'Craft', 'iA Writer', 'Ulysses'].includes(d.app));
|
|
379
379
|
const taskApps = discoveries.filter(d => ['Things 3', 'OmniFocus', 'Todoist'].includes(d.app));
|
|
380
380
|
const otherApps = discoveries.filter(d => !noteApps.includes(d) && !taskApps.includes(d));
|
|
381
381
|
if (noteApps.length) {
|
|
382
|
-
lines.push('
|
|
382
|
+
lines.push(' notes & writing');
|
|
383
383
|
for (const app of noteApps) {
|
|
384
|
-
const skill = app.skillId ? '
|
|
384
|
+
const skill = app.skillId ? ' — skill available' : '';
|
|
385
385
|
lines.push(` ${app.app}${skill}`);
|
|
386
386
|
}
|
|
387
387
|
}
|
|
388
388
|
if (taskApps.length) {
|
|
389
|
-
lines.push('
|
|
389
|
+
lines.push(' task management');
|
|
390
390
|
for (const app of taskApps) {
|
|
391
|
-
const skill = app.skillId ? '
|
|
391
|
+
const skill = app.skillId ? ' — skill available' : '';
|
|
392
392
|
lines.push(` ${app.app}${skill}`);
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
395
|
if (otherApps.length) {
|
|
396
|
-
lines.push('
|
|
396
|
+
lines.push(' other');
|
|
397
397
|
for (const app of otherApps) {
|
|
398
398
|
lines.push(` ${app.app}`);
|
|
399
399
|
}
|
|
@@ -401,7 +401,7 @@ export function formatDiscoveryReport(discoveries) {
|
|
|
401
401
|
const skillCount = discoveries.filter(d => d.skillId).length;
|
|
402
402
|
if (skillCount > 0) {
|
|
403
403
|
lines.push('');
|
|
404
|
-
lines.push(`
|
|
404
|
+
lines.push(` ${skillCount} skill${skillCount === 1 ? '' : 's'} available`);
|
|
405
405
|
}
|
|
406
406
|
return lines.join('\n');
|
|
407
407
|
}
|
package/dist/init-check.js
CHANGED
|
@@ -20,7 +20,7 @@ import { hasRequiredPermissions, getMissingPermissions } from './permissions.js'
|
|
|
20
20
|
import { loadProgress, getTimeSinceLastSession } from './session-progress.js';
|
|
21
21
|
import { getCrashStats } from './watchdog.js';
|
|
22
22
|
import { getSkills } from './skill-loader.js';
|
|
23
|
-
import { hasTailscale
|
|
23
|
+
import { hasTailscale } from './tunnel.js';
|
|
24
24
|
const home = homedir();
|
|
25
25
|
/** Run all pre-flight checks. Returns readiness status. */
|
|
26
26
|
export function runInitCheck() {
|
|
@@ -62,32 +62,29 @@ export function runInitCheck() {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
checks.push({
|
|
65
|
-
check: '
|
|
65
|
+
check: 'messages',
|
|
66
66
|
passed: msgOk,
|
|
67
|
-
detail: msgOk ? undefined : '
|
|
67
|
+
detail: msgOk ? undefined : 'can\'t read messages — needs Full Disk Access',
|
|
68
68
|
});
|
|
69
69
|
// 4. API key available
|
|
70
70
|
const hasKey = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENROUTER_API_KEY);
|
|
71
71
|
checks.push({
|
|
72
|
-
check: '
|
|
72
|
+
check: 'intelligence',
|
|
73
73
|
passed: hasKey,
|
|
74
|
-
detail: hasKey ? undefined : 'set ANTHROPIC_API_KEY in ~/.config/life-pulse/.env',
|
|
75
74
|
});
|
|
76
75
|
// 5. Session progress integrity
|
|
77
76
|
const progress = loadProgress();
|
|
78
77
|
const progressOk = progress.version === 2;
|
|
79
78
|
checks.push({
|
|
80
|
-
check: '
|
|
79
|
+
check: 'memory',
|
|
81
80
|
passed: progressOk,
|
|
82
|
-
detail: progressOk ? `${progress.totalSessions} sessions` : 'needs migration',
|
|
83
81
|
});
|
|
84
82
|
// 6. Crash state
|
|
85
83
|
const crashes = getCrashStats();
|
|
86
84
|
const crashOk = crashes.consecutive === 0;
|
|
87
85
|
checks.push({
|
|
88
|
-
check: '
|
|
86
|
+
check: 'stability',
|
|
89
87
|
passed: crashOk,
|
|
90
|
-
detail: crashOk ? undefined : `${crashes.consecutive} consecutive failures`,
|
|
91
88
|
});
|
|
92
89
|
// 7. Skills loaded
|
|
93
90
|
const skills = getSkills();
|
|
@@ -95,7 +92,6 @@ export function runInitCheck() {
|
|
|
95
92
|
checks.push({
|
|
96
93
|
check: 'skills',
|
|
97
94
|
passed: activeSkills > 0,
|
|
98
|
-
detail: `${activeSkills} active`,
|
|
99
95
|
});
|
|
100
96
|
// 8. Inbound gateway (port 19877)
|
|
101
97
|
let gwOk = false;
|
|
@@ -107,16 +103,14 @@ export function runInitCheck() {
|
|
|
107
103
|
}
|
|
108
104
|
catch { }
|
|
109
105
|
checks.push({
|
|
110
|
-
check: '
|
|
106
|
+
check: 'messaging',
|
|
111
107
|
passed: gwOk,
|
|
112
|
-
detail: gwOk ? 'listening' : 'not running — start daemon',
|
|
113
108
|
});
|
|
114
109
|
// 9. Tailscale installed
|
|
115
110
|
const tsOk = hasTailscale();
|
|
116
111
|
checks.push({
|
|
117
|
-
check: '
|
|
112
|
+
check: 'network',
|
|
118
113
|
passed: tsOk,
|
|
119
|
-
detail: tsOk ? (isFunnelActive() ? 'funnel active' : 'installed, funnel not active') : 'not installed',
|
|
120
114
|
});
|
|
121
115
|
// Determine mode
|
|
122
116
|
const timeSince = getTimeSinceLastSession();
|
|
@@ -134,12 +128,14 @@ export function runInitCheck() {
|
|
|
134
128
|
const allPassed = checks.every(c => c.passed);
|
|
135
129
|
if (allPassed) {
|
|
136
130
|
recommendation = mode === 'first_run'
|
|
137
|
-
? '
|
|
138
|
-
:
|
|
131
|
+
? 'ready for setup'
|
|
132
|
+
: 'everything looks good';
|
|
139
133
|
}
|
|
140
134
|
else {
|
|
141
135
|
const failing = checks.filter(c => !c.passed);
|
|
142
|
-
recommendation =
|
|
136
|
+
recommendation = failing.length === 1
|
|
137
|
+
? `${failing[0].check} needs attention`
|
|
138
|
+
: `${failing.length} things need attention`;
|
|
143
139
|
}
|
|
144
140
|
return {
|
|
145
141
|
ready: allPassed || (permOk && hasKey), // Can still run in degraded mode
|