life-pulse 2.3.11 → 2.4.0
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/cli.js +6 -193
- package/dist/installer.js +57 -1
- package/dist/ui/theme.js +16 -17
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { collectAll } from './index.js';
|
|
3
2
|
import { runAgent } from './agent.js';
|
|
4
3
|
import { saveState, saveDecisions } from './state.js';
|
|
5
4
|
// ProgressRenderer unused — removed
|
|
@@ -16,18 +15,15 @@ import { buildPersonalSummary, getUserName } from './profile.js';
|
|
|
16
15
|
import { startSession, endSession, loadProgress, recordDecision, recordSurfaced, getTimeSinceLastSession, getNewDiscoveries } from './session-progress.js';
|
|
17
16
|
import { runDiscovery, formatDiscoveryReport } from './icloud-discovery.js';
|
|
18
17
|
import { getSourcesNeedingScan, getChangeSummary } from './incremental.js';
|
|
19
|
-
import { startHealthServer, stopHealthServer, isDaemonRunning,
|
|
20
|
-
// New: long-running agent harness modules
|
|
21
|
-
import { runInitCheck } from './init-check.js';
|
|
18
|
+
import { startHealthServer, stopHealthServer, isDaemonRunning, touchActivity } from './health.js';
|
|
22
19
|
import { installCrashHandlers, recordSuccess, recordCrash, checkpoint, getCrashStats } from './watchdog.js';
|
|
23
20
|
import { startMessageLoop, sendMessage } from './message-loop.js';
|
|
24
21
|
import { scanForSkills } from './skill-loader.js';
|
|
25
22
|
import { TransportManager, IMessageTransport, TelegramTransport } from './transport.js';
|
|
26
23
|
import { runInstaller } from './installer.js';
|
|
27
24
|
import { converse } from './conversation.js';
|
|
28
|
-
import { startGateway
|
|
25
|
+
import { startGateway } from './sms-gateway.js';
|
|
29
26
|
import { updateAutoKnowledge } from './knowledge.js';
|
|
30
|
-
import { startRouter, initClientRegistry } from './router.js';
|
|
31
27
|
import { findHandlesForName } from './contacts.js';
|
|
32
28
|
import { ensurePromptLayerFiles } from './prompt-layers.js';
|
|
33
29
|
import { hasTailscale, startFunnel, getHostname as getTailscaleHostname } from './tunnel.js';
|
|
@@ -262,80 +258,14 @@ async function showCRM(apiKey, opts) {
|
|
|
262
258
|
async function main() {
|
|
263
259
|
const createdPromptFiles = ensurePromptLayerFiles();
|
|
264
260
|
const jsonMode = process.argv.includes('--json');
|
|
265
|
-
const rawMode = process.argv.includes('--raw');
|
|
266
261
|
const statusMode = process.argv.includes('--status');
|
|
267
262
|
const daemonMode = process.argv.includes('--daemon');
|
|
268
|
-
const
|
|
269
|
-
const initCheckMode = process.argv.includes('--check');
|
|
270
|
-
const phoneSetupMode = process.argv.includes('--phone-setup');
|
|
271
|
-
const testSmsMode = process.argv.includes('--test-sms');
|
|
272
|
-
const pairMode = process.argv.includes('--pair') || process.argv.includes('--export-route');
|
|
273
|
-
const routerMode = process.argv.includes('--router');
|
|
274
|
-
const initClientsMode = process.argv.includes('--init-clients');
|
|
263
|
+
const pairMode = process.argv.includes('--pair');
|
|
275
264
|
// Install crash handlers early (Anthropic pattern: recover from failures)
|
|
276
265
|
installCrashHandlers((err) => {
|
|
277
266
|
console.error(chalk.red(` crash: ${err.message}`));
|
|
278
267
|
});
|
|
279
|
-
// --
|
|
280
|
-
if (initCheckMode) {
|
|
281
|
-
const status = runInitCheck();
|
|
282
|
-
console.log();
|
|
283
|
-
for (const c of status.checks) {
|
|
284
|
-
const icon = c.passed ? chalk.green('✓') : chalk.red('✗');
|
|
285
|
-
console.log(` ${icon} ${c.check}`);
|
|
286
|
-
}
|
|
287
|
-
console.log();
|
|
288
|
-
console.log(chalk.dim(` ${status.recommendation}`));
|
|
289
|
-
console.log();
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
// --router: legacy bridge mode (multi-machine relay setups)
|
|
293
|
-
if (routerMode) {
|
|
294
|
-
console.log(chalk.bold.hex('#c0caf5')(' life-pulse'));
|
|
295
|
-
console.log(chalk.dim(' routing messages'));
|
|
296
|
-
console.log();
|
|
297
|
-
const router = startRouter();
|
|
298
|
-
process.on('SIGINT', () => { router.stop(); process.exit(0); });
|
|
299
|
-
process.on('SIGTERM', () => { router.stop(); process.exit(0); });
|
|
300
|
-
await new Promise(() => { });
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
// --init-clients: create template clients.json
|
|
304
|
-
if (initClientsMode) {
|
|
305
|
-
initClientRegistry();
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
// --phone-setup: run phone number setup from installer
|
|
309
|
-
if (phoneSetupMode) {
|
|
310
|
-
await runInstaller(ANTHROPIC_KEY);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
// --test-sms: test the gateway by posting a fake inbound message
|
|
314
|
-
if (testSmsMode) {
|
|
315
|
-
const up = await isGatewayUp();
|
|
316
|
-
if (!up) {
|
|
317
|
-
console.log(chalk.red(' not running — start life-pulse first'));
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
const phone = process.argv[process.argv.indexOf('--test-sms') + 1] || '+1234567890';
|
|
321
|
-
try {
|
|
322
|
-
const resp = await fetch('http://127.0.0.1:19877/inbound', {
|
|
323
|
-
method: 'POST',
|
|
324
|
-
headers: { 'Content-Type': 'application/json' },
|
|
325
|
-
body: JSON.stringify({ phone, message: 'test message from life-pulse' }),
|
|
326
|
-
});
|
|
327
|
-
const data = await resp.json();
|
|
328
|
-
if (data.response)
|
|
329
|
-
console.log(chalk.green(` replied: ${data.response}`));
|
|
330
|
-
else
|
|
331
|
-
console.log(chalk.red(` failed: ${data.error}`));
|
|
332
|
-
}
|
|
333
|
-
catch (err) {
|
|
334
|
-
console.log(chalk.red(` couldn't connect — is life-pulse running?`));
|
|
335
|
-
}
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
// --pair / --export-route: generate Desktop/nox-route.json for NOX routing
|
|
268
|
+
// --pair: generate Desktop/nox-route.json for NOX routing
|
|
339
269
|
if (pairMode) {
|
|
340
270
|
const defaultName = getUserName() || '';
|
|
341
271
|
const detectedHost = getTailscaleHostname() || '';
|
|
@@ -381,17 +311,6 @@ async function main() {
|
|
|
381
311
|
console.log();
|
|
382
312
|
return;
|
|
383
313
|
}
|
|
384
|
-
// --health: check if daemon is running and get status
|
|
385
|
-
if (healthMode) {
|
|
386
|
-
if (isDaemonRunning()) {
|
|
387
|
-
const status = getHealthStatus();
|
|
388
|
-
console.log(JSON.stringify(status, null, 2));
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
console.log(JSON.stringify({ status: 'not_running' }));
|
|
392
|
-
}
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
314
|
// --daemon: check if already running
|
|
396
315
|
if (daemonMode && isDaemonRunning()) {
|
|
397
316
|
console.log(chalk.yellow(' already running'));
|
|
@@ -426,11 +345,6 @@ async function main() {
|
|
|
426
345
|
console.log();
|
|
427
346
|
return;
|
|
428
347
|
}
|
|
429
|
-
if (rawMode) {
|
|
430
|
-
const collected = await collectAll();
|
|
431
|
-
console.log(JSON.stringify(collected, null, 2));
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
348
|
// ── Start session tracking ──
|
|
435
349
|
const sessionProgress = startSession();
|
|
436
350
|
const timeSinceLastSession = getTimeSinceLastSession();
|
|
@@ -453,113 +367,12 @@ async function main() {
|
|
|
453
367
|
const installStateExists = existsSync(join(stateDir, 'install-state.json'));
|
|
454
368
|
const noSessionHistory = sessionProgress.totalSessions <= 1;
|
|
455
369
|
const isSetupFlag = process.argv.includes('--setup');
|
|
456
|
-
if ((noSessionHistory && !installStateExists && !
|
|
370
|
+
if ((noSessionHistory && !installStateExists && !jsonMode && !daemonMode)
|
|
457
371
|
|| isSetupFlag) {
|
|
458
372
|
// First run: launch full installer
|
|
459
373
|
await runInstaller(ANTHROPIC_KEY);
|
|
460
374
|
return;
|
|
461
375
|
}
|
|
462
|
-
if (process.argv.includes('--install')) {
|
|
463
|
-
const config = loadConfig();
|
|
464
|
-
const [hour, min] = config.briefTime.split(':').map(Number);
|
|
465
|
-
const logDir = join(homedir(), 'Library/Logs/life-pulse');
|
|
466
|
-
const agentsDir = join(homedir(), 'Library/LaunchAgents');
|
|
467
|
-
const envDir = join(homedir(), '.config/life-pulse');
|
|
468
|
-
try {
|
|
469
|
-
mkdirSync(logDir, { recursive: true });
|
|
470
|
-
}
|
|
471
|
-
catch { }
|
|
472
|
-
try {
|
|
473
|
-
mkdirSync(agentsDir, { recursive: true });
|
|
474
|
-
}
|
|
475
|
-
catch { }
|
|
476
|
-
try {
|
|
477
|
-
mkdirSync(envDir, { recursive: true });
|
|
478
|
-
}
|
|
479
|
-
catch { }
|
|
480
|
-
// Detect npx path for the installed package
|
|
481
|
-
let npxPath;
|
|
482
|
-
try {
|
|
483
|
-
npxPath = execSync('which npx', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
484
|
-
}
|
|
485
|
-
catch {
|
|
486
|
-
npxPath = '/opt/homebrew/bin/npx';
|
|
487
|
-
}
|
|
488
|
-
// Env file hint
|
|
489
|
-
const envPath = join(envDir, '.env');
|
|
490
|
-
if (!existsSync(envPath)) {
|
|
491
|
-
writeFileSync(envPath, '# life-pulse environment\n# ANTHROPIC_API_KEY=sk-ant-...\n# TELEGRAM_BOT_TOKEN=...\n');
|
|
492
|
-
console.log(chalk.dim(` Created ${envPath} — add your API key`));
|
|
493
|
-
}
|
|
494
|
-
// 1. Morning brief plist (scheduled, one-shot)
|
|
495
|
-
const morningPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
496
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
497
|
-
<plist version="1.0">
|
|
498
|
-
<dict>
|
|
499
|
-
<key>Label</key><string>com.life-pulse.morning</string>
|
|
500
|
-
<key>ProgramArguments</key>
|
|
501
|
-
<array>
|
|
502
|
-
<string>${npxPath}</string><string>life-pulse</string>
|
|
503
|
-
</array>
|
|
504
|
-
<key>StartCalendarInterval</key>
|
|
505
|
-
<dict><key>Hour</key><integer>${hour}</integer><key>Minute</key><integer>${min}</integer></dict>
|
|
506
|
-
<key>StandardOutPath</key><string>${join(logDir, 'morning.log')}</string>
|
|
507
|
-
<key>StandardErrorPath</key><string>${join(logDir, 'morning-err.log')}</string>
|
|
508
|
-
<key>EnvironmentVariables</key>
|
|
509
|
-
<dict>
|
|
510
|
-
<key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
511
|
-
<key>HOME</key><string>${homedir()}</string>
|
|
512
|
-
</dict>
|
|
513
|
-
</dict>
|
|
514
|
-
</plist>`;
|
|
515
|
-
// 2. Daemon plist (KeepAlive — auto-restart on crash)
|
|
516
|
-
const daemonPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
517
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
518
|
-
<plist version="1.0">
|
|
519
|
-
<dict>
|
|
520
|
-
<key>Label</key><string>com.life-pulse.daemon</string>
|
|
521
|
-
<key>ProgramArguments</key>
|
|
522
|
-
<array>
|
|
523
|
-
<string>${npxPath}</string><string>life-pulse</string><string>--daemon</string>
|
|
524
|
-
</array>
|
|
525
|
-
<key>RunAtLoad</key><true/>
|
|
526
|
-
<key>KeepAlive</key>
|
|
527
|
-
<dict>
|
|
528
|
-
<key>SuccessfulExit</key><false/>
|
|
529
|
-
</dict>
|
|
530
|
-
<key>ThrottleInterval</key><integer>30</integer>
|
|
531
|
-
<key>StandardOutPath</key><string>${join(logDir, 'daemon.log')}</string>
|
|
532
|
-
<key>StandardErrorPath</key><string>${join(logDir, 'daemon-err.log')}</string>
|
|
533
|
-
<key>EnvironmentVariables</key>
|
|
534
|
-
<dict>
|
|
535
|
-
<key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
536
|
-
<key>HOME</key><string>${homedir()}</string>
|
|
537
|
-
</dict>
|
|
538
|
-
</dict>
|
|
539
|
-
</plist>`;
|
|
540
|
-
writeFileSync(join(agentsDir, 'com.life-pulse.morning.plist'), morningPlist);
|
|
541
|
-
writeFileSync(join(agentsDir, 'com.life-pulse.daemon.plist'), daemonPlist);
|
|
542
|
-
// Prefer direct NOX -> this Mac flow: ensure tailscale endpoint is configured.
|
|
543
|
-
let noxEndpoint = null;
|
|
544
|
-
if (hasTailscale()) {
|
|
545
|
-
noxEndpoint = startFunnel(19877);
|
|
546
|
-
if (!noxEndpoint) {
|
|
547
|
-
const host = getTailscaleHostname();
|
|
548
|
-
if (host)
|
|
549
|
-
noxEndpoint = `https://${host}`;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
console.log(chalk.dim(' running in the background'));
|
|
553
|
-
console.log(chalk.dim(` morning brief at ${config.briefTime}`));
|
|
554
|
-
if (noxEndpoint) {
|
|
555
|
-
console.log(chalk.dim(` nox endpoint ${noxEndpoint}`));
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
console.log(chalk.dim(' nox endpoint unavailable — tailscale needed'));
|
|
559
|
-
}
|
|
560
|
-
console.log();
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
376
|
if (!ANTHROPIC_KEY) {
|
|
564
377
|
console.log(chalk.red('\n\n missing API key\n'));
|
|
565
378
|
process.exit(1);
|
|
@@ -592,7 +405,7 @@ async function main() {
|
|
|
592
405
|
let crmShown = false;
|
|
593
406
|
const setupMode = process.argv.includes('--setup');
|
|
594
407
|
const isFirstRun = sessionProgress.totalSessions === 1;
|
|
595
|
-
if ((needsDiscovery() || setupMode || isFirstRun) && !
|
|
408
|
+
if ((needsDiscovery() || setupMode || isFirstRun) && !jsonMode) {
|
|
596
409
|
// Welcome message for first run
|
|
597
410
|
if (isFirstRun && interactive) {
|
|
598
411
|
console.log();
|
package/dist/installer.js
CHANGED
|
@@ -88,6 +88,52 @@ const PLATFORM_HINTS = {
|
|
|
88
88
|
function platformHint(platform) {
|
|
89
89
|
return PLATFORM_HINTS[platform] || '';
|
|
90
90
|
}
|
|
91
|
+
// ─── Tailscale Auto-Install ─────────────────────────────────────
|
|
92
|
+
function hasBrew() {
|
|
93
|
+
try {
|
|
94
|
+
execSync('which brew', { stdio: 'pipe', timeout: 5000 });
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function installTailscale(spinner) {
|
|
102
|
+
if (hasBrew()) {
|
|
103
|
+
spinner?.start('installing tailscale via homebrew');
|
|
104
|
+
try {
|
|
105
|
+
execSync('brew install --cask tailscale', { stdio: 'pipe', timeout: 120_000 });
|
|
106
|
+
spinner?.stop();
|
|
107
|
+
// Launch the app so the CLI helper is available
|
|
108
|
+
try {
|
|
109
|
+
execSync('open -a Tailscale', { stdio: 'pipe', timeout: 10_000 });
|
|
110
|
+
}
|
|
111
|
+
catch { }
|
|
112
|
+
// Give it a moment to register the CLI
|
|
113
|
+
execSync('sleep 2');
|
|
114
|
+
if (hasTailscale()) {
|
|
115
|
+
console.log(DIM(' tailscale installed via homebrew'));
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
spinner?.stop();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Fallback: open Mac App Store page for Tailscale
|
|
124
|
+
spinner?.start('opening tailscale download');
|
|
125
|
+
try {
|
|
126
|
+
execSync('open "https://apps.apple.com/app/tailscale/id1475387142"', { stdio: 'pipe', timeout: 5000 });
|
|
127
|
+
spinner?.stop();
|
|
128
|
+
console.log(DIM(' opened Tailscale in App Store'));
|
|
129
|
+
console.log(DIM(' install it, then run setup again'));
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
spinner?.stop();
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
91
137
|
// ─── Main Installer Flow ────────────────────────────────────────
|
|
92
138
|
export async function runInstaller(apiKey) {
|
|
93
139
|
const state = loadState();
|
|
@@ -276,6 +322,15 @@ export async function runInstaller(apiKey) {
|
|
|
276
322
|
const gw = startGateway(apiKey);
|
|
277
323
|
setTimeout(() => gw.stop(), 60_000);
|
|
278
324
|
}
|
|
325
|
+
// Auto-install Tailscale if missing
|
|
326
|
+
if (!hasTailscale()) {
|
|
327
|
+
console.log(DIM(' tailscale not found — installing'));
|
|
328
|
+
const installed = await installTailscale(spinner);
|
|
329
|
+
if (!installed) {
|
|
330
|
+
console.log(HD(' couldn\'t install tailscale automatically'));
|
|
331
|
+
console.log(DIM(' install manually: tailscale.com/download'));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
279
334
|
if (hasTailscale()) {
|
|
280
335
|
const hostname = getTailscaleHostname();
|
|
281
336
|
if (hostname) {
|
|
@@ -290,7 +345,8 @@ export async function runInstaller(apiKey) {
|
|
|
290
345
|
console.log(DIM(` ${state.funnelUrl}`));
|
|
291
346
|
}
|
|
292
347
|
else {
|
|
293
|
-
console.log(HD('
|
|
348
|
+
console.log(HD(' tailscale installed but not logged in'));
|
|
349
|
+
console.log(DIM(' run: tailscale login'));
|
|
294
350
|
}
|
|
295
351
|
}
|
|
296
352
|
else {
|
package/dist/ui/theme.js
CHANGED
|
@@ -7,24 +7,23 @@
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
// ─── Palette ──────────────────────────────────────
|
|
9
9
|
export const C = {
|
|
10
|
-
// Brand —
|
|
11
|
-
accent: chalk.hex('#
|
|
12
|
-
brand: chalk.bold.hex('#
|
|
13
|
-
// Semantic —
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
info: chalk.hex('#a9b1d6'),
|
|
10
|
+
// Brand — deep purple accent
|
|
11
|
+
accent: chalk.hex('#9d7cd8'),
|
|
12
|
+
brand: chalk.bold.hex('#9d7cd8'),
|
|
13
|
+
// Semantic — purple-tinted grayscale
|
|
14
|
+
ok: chalk.hex('#b4a4d6'),
|
|
15
|
+
warn: chalk.hex('#8a7aab'),
|
|
16
|
+
err: chalk.bold.hex('#b4a4d6'),
|
|
17
|
+
info: chalk.hex('#8a7aab'),
|
|
19
18
|
// Text hierarchy (bright → invisible)
|
|
20
|
-
hd: chalk.bold.hex('#
|
|
21
|
-
heading: chalk.bold.hex('#
|
|
22
|
-
bright: chalk.hex('#
|
|
23
|
-
text: chalk.hex('#
|
|
24
|
-
mid: chalk.hex('#
|
|
25
|
-
dim: chalk.hex('#
|
|
26
|
-
faint: chalk.hex('#
|
|
27
|
-
rule: chalk.hex('#
|
|
19
|
+
hd: chalk.bold.hex('#b4a4d6'),
|
|
20
|
+
heading: chalk.bold.hex('#b4a4d6'),
|
|
21
|
+
bright: chalk.hex('#b4a4d6'),
|
|
22
|
+
text: chalk.hex('#8a7aab'),
|
|
23
|
+
mid: chalk.hex('#635d80'),
|
|
24
|
+
dim: chalk.hex('#4a4466'),
|
|
25
|
+
faint: chalk.hex('#332e4a'),
|
|
26
|
+
rule: chalk.hex('#231f36'),
|
|
28
27
|
};
|
|
29
28
|
// Lowercase alias (backward compat)
|
|
30
29
|
export const c = C;
|