life-pulse 2.3.9 → 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/dist/tunnel.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Tailscale Funnel — expose local port as HTTPS to the internet.
3
3
  *
4
- * Exposes health server or imwebserver for remote access/monitoring.
5
- * No Twilio — messages go through rply-mac-server's imwebserver (local).
4
+ * Used to expose the local NOX gateway (:19877) for inbound tool calls.
6
5
  *
7
6
  */
8
7
  /** Check if tailscale CLI is available */
package/dist/tunnel.js CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Tailscale Funnel — expose local port as HTTPS to the internet.
3
3
  *
4
- * Exposes health server or imwebserver for remote access/monitoring.
5
- * No Twilio — messages go through rply-mac-server's imwebserver (local).
4
+ * Used to expose the local NOX gateway (:19877) for inbound tool calls.
6
5
  *
7
6
  */
8
7
  import { execSync, spawn } from 'child_process';
package/dist/ui/app.d.ts CHANGED
@@ -21,10 +21,11 @@ export declare function initInk(seedLines?: string[]): void;
21
21
  export declare function destroy(): void;
22
22
  export declare function log(text: string): void;
23
23
  export declare function gap(): void;
24
+ export declare function pinLine(text: string): void;
24
25
  export declare function showSpinner(label: string): void;
25
26
  export declare function updateSpinner(label: string): void;
26
27
  export declare function hideSpinner(doneMsg?: string): void;
27
- export declare function pickCard(card: CardData, num: number): Promise<string>;
28
+ export declare function pickCard(card: CardData, num: number, total?: number): Promise<string>;
28
29
  export declare function showProgress(phase: string, done: number, total: number, items: {
29
30
  label: string;
30
31
  tool?: string;
package/dist/ui/app.js CHANGED
@@ -13,10 +13,18 @@ let _update = null;
13
13
  let _cardResolve = null;
14
14
  let _enterResolve = null;
15
15
  const INIT = {
16
+ stickyLines: [],
16
17
  lines: [], spinner: null,
17
- card: null, cardNum: 0, cardSel: 0,
18
+ card: null, cardNum: 0, cardTotal: null, cardSel: 0,
18
19
  progress: null,
19
20
  };
21
+ const MAX_LOG_LINES = 24;
22
+ function appendLines(prev, next) {
23
+ const merged = [...prev, ...next];
24
+ if (merged.length <= MAX_LOG_LINES)
25
+ return merged;
26
+ return merged.slice(merged.length - MAX_LOG_LINES);
27
+ }
20
28
  // ─── Components ───────────────────────────────────
21
29
  const SpinnerLine = ({ label, frame }) => (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#565f89", children: BAR_CHARS[Math.floor(frame / 3) % BAR_CHARS.length] }), ' ', _jsx(Text, { color: "#565f89", children: label })] }));
22
30
  const BAR_W = 20;
@@ -28,18 +36,24 @@ const Bar = ({ done, total }) => {
28
36
  const ProgressView = ({ progress, frame }) => {
29
37
  const spin = (off = 0) => BAR_CHARS[(Math.floor((frame + off) / 5)) % BAR_CHARS.length];
30
38
  if (progress.thinking && !progress.items.length && !progress.total) {
31
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { children: [' ', _jsx(Text, { color: "#3b4261", children: spin() }), ' ', _jsx(Text, { color: "#565f89", children: "thinking" })] })] }));
39
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { children: [' ', _jsx(Text, { color: "#3b4261", children: spin() }), ' ', _jsx(Text, { color: "#565f89", children: progress.phase || 'assembling briefing' })] })] }));
32
40
  }
33
41
  const { phase, done, total, items } = progress;
34
42
  const allDone = done === total && total > 0;
35
- 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" })] }))] }));
43
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), allDone ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [' ', _jsx(Text, { color: "#3b4261", children: spin() }), ' ', _jsx(Text, { color: "#565f89", children: phase || 'writing briefing' })] }), items.slice(0, MAX_VIS).map((item, i) => (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#3b4261", children: '·' }), ' ', _jsx(Text, { color: "#3b4261", children: item.label })] }, i)))] })) : 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 && !allDone && (_jsxs(Text, { children: [' ', _jsx(Text, { color: "#3b4261", children: spin() }), ' ', _jsx(Text, { color: "#565f89", children: "thinking" })] }))] }));
36
44
  };
37
- const CardView = ({ card, num, sel }) => {
45
+ const CardView = ({ card, num, total, sel }) => {
38
46
  const opts = card.options || [];
39
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), _jsxs(Text, { bold: true, color: "#c0caf5", children: [' ', card.title] }), card.context && _jsxs(Text, { color: "#565f89", children: [' ', card.context] }), _jsx(Text, { children: ' ' }), opts.map((opt, i) => {
47
+ const progressLabel = total && total > 0 ? `${num}/${total}` : String(num);
48
+ const selectedLabel = (opts[sel]?.label || '').trim();
49
+ const selectedAction = selectedLabel || 'continue';
50
+ const hint = opts.length <= 1
51
+ ? `[enter to ${selectedAction}]`
52
+ : `[up/down to choose · enter to ${selectedAction}]`;
53
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), _jsxs(Box, { children: [_jsx(Text, { color: "#3b4261", children: ' ┃ ' }), _jsxs(Box, { flexGrow: 1, justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "#c0caf5", children: "QUESTION" }), _jsx(Text, { color: "#565f89", children: progressLabel })] })] }), _jsxs(Box, { children: [_jsx(Text, { color: "#3b4261", children: ' ┃ ' }), _jsx(Text, { bold: true, color: "#a9b1d6", children: card.title })] }), card.context && (_jsxs(Box, { children: [_jsx(Text, { color: "#3b4261", children: ' ┃ ' }), _jsx(Text, { color: "#565f89", children: card.context })] })), opts.map((opt, i) => {
40
54
  const on = i === sel;
41
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [' ', _jsxs(Text, { color: on ? '#7aa2f7' : '#292e42', children: [on ? '' : ' ', ' '] }), _jsx(Text, { bold: on, color: on ? '#c0caf5' : '#3b4261', children: opt.label })] }), opt.description && (_jsxs(Text, { color: on ? '#565f89' : '#292e42', children: [' ', opt.description] }))] }, opt.label));
42
- }), _jsx(Text, { children: ' ' })] }));
55
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#3b4261", children: '' }), _jsx(Text, { color: on ? '#7aa2f7' : '#565f89', children: on ? '' : '○' }), _jsx(Text, { children: ' ' }), _jsx(Text, { bold: on, color: on ? '#c0caf5' : '#a9b1d6', children: opt.label })] }), opt.description && (_jsxs(Box, { children: [_jsx(Text, { color: "#3b4261", children: ' ┃ ' }), _jsx(Text, { color: on ? '#565f89' : '#3b4261', children: opt.description })] }))] }, opt.label));
56
+ }), _jsxs(Box, { children: [_jsx(Text, { color: "#3b4261", children: ' ' }), _jsx(Text, { color: "#565f89", children: hint })] }), _jsx(Text, { children: ' ' })] }));
43
57
  };
44
58
  // ─── Main App ─────────────────────────────────────
45
59
  const App = () => {
@@ -65,13 +79,14 @@ const App = () => {
65
79
  const s = stateRef.current;
66
80
  if (s.card && s.card.options?.length) {
67
81
  const opts = s.card.options;
82
+ const enterPressed = key.return || input === '\r' || input === '\n';
68
83
  if (key.upArrow || input === 'k') {
69
84
  setState(p => ({ ...p, cardSel: (p.cardSel - 1 + opts.length) % opts.length }));
70
85
  }
71
86
  else if (key.downArrow || input === 'j') {
72
87
  setState(p => ({ ...p, cardSel: (p.cardSel + 1) % opts.length }));
73
88
  }
74
- else if (key.return) {
89
+ else if (enterPressed || input === ' ') {
75
90
  _cardResolve?.(opts[s.cardSel].label);
76
91
  _cardResolve = null;
77
92
  }
@@ -93,13 +108,13 @@ const App = () => {
93
108
  process.exit(0);
94
109
  }
95
110
  });
96
- return (_jsxs(Box, { flexDirection: "column", children: [state.lines.map((line, i) => _jsx(Text, { children: line }, i)), state.spinner && _jsx(SpinnerLine, { label: state.spinner, frame: frame }), state.progress && (_jsx(ProgressView, { progress: state.progress, frame: frame })), state.card && state.card.options?.length ? (_jsx(CardView, { card: state.card, num: state.cardNum, sel: state.cardSel })) : null] }));
111
+ return (_jsxs(Box, { flexDirection: "column", children: [state.stickyLines.map((line, i) => _jsx(Text, { children: line }, `sticky-${i}`)), state.lines.map((line, i) => _jsx(Text, { children: line }, i)), state.spinner && _jsx(SpinnerLine, { label: state.spinner, frame: frame }), state.progress && (_jsx(ProgressView, { progress: state.progress, frame: frame })), state.card && state.card.options?.length ? (_jsx(CardView, { card: state.card, num: state.cardNum, total: state.cardTotal, sel: state.cardSel })) : null] }));
97
112
  };
98
113
  // ─── Singleton ────────────────────────────────────
99
114
  let _inst = null;
100
115
  let _started = false;
101
116
  const _origLog = console.log.bind(console);
102
- let _seedLines = [];
117
+ let _seedStickyLines = [];
103
118
  function ensureStarted() {
104
119
  if (_started)
105
120
  return;
@@ -107,27 +122,26 @@ function ensureStarted() {
107
122
  process.stdout.write('\x1B[2J\x1B[H'); // clear screen
108
123
  process.stdout.write('\x1B[?25l'); // hide cursor
109
124
  _inst = inkRender(React.createElement(App));
110
- // Seed initial lines (e.g. greeting from intro)
111
- if (_seedLines.length) {
112
- const lines = _seedLines;
113
- _seedLines = [];
125
+ // Seed pinned lines (e.g. greeting from intro)
126
+ if (_seedStickyLines.length) {
127
+ const lines = _seedStickyLines;
128
+ _seedStickyLines = [];
114
129
  // Small delay to let React mount before pushing state
115
130
  setTimeout(() => {
116
- _update?.(p => ({ ...p, lines: [...lines, ...p.lines] }));
131
+ _update?.(p => ({ ...p, stickyLines: lines }));
117
132
  }, 50);
118
133
  }
119
134
  // Redirect console.log through Ink
120
135
  console.log = (...args) => {
121
136
  const text = args.map(a => typeof a === 'string' ? a : String(a)).join(' ');
122
- for (const line of text.split('\n')) {
123
- _update?.(p => ({ ...p, lines: [...p.lines, line] }));
124
- }
137
+ const next = text.split('\n');
138
+ _update?.(p => ({ ...p, lines: appendLines(p.lines, next) }));
125
139
  };
126
140
  }
127
141
  // ─── Exports ──────────────────────────────────────
128
142
  export function initInk(seedLines) {
129
143
  if (seedLines)
130
- _seedLines = seedLines;
144
+ _seedStickyLines = seedLines;
131
145
  ensureStarted();
132
146
  }
133
147
  export function destroy() {
@@ -142,11 +156,14 @@ export function log(text) {
142
156
  _origLog(text);
143
157
  return;
144
158
  }
145
- for (const line of text.split('\n')) {
146
- _update?.(p => ({ ...p, lines: [...p.lines, line] }));
147
- }
159
+ const next = text.split('\n');
160
+ _update?.(p => ({ ...p, lines: appendLines(p.lines, next) }));
148
161
  }
149
162
  export function gap() { log(''); }
163
+ export function pinLine(text) {
164
+ ensureStarted();
165
+ _update?.(p => ({ ...p, stickyLines: [...p.stickyLines, text] }));
166
+ }
150
167
  // ── Spinner ──
151
168
  export function showSpinner(label) {
152
169
  ensureStarted();
@@ -161,7 +178,7 @@ export function hideSpinner(doneMsg) {
161
178
  log(` ${C.dim(doneMsg)}`);
162
179
  }
163
180
  // ── Card picker ──
164
- export async function pickCard(card, num) {
181
+ export async function pickCard(card, num, total) {
165
182
  ensureStarted();
166
183
  if (card.fyi || !card.options?.length) {
167
184
  log(` ${C.dim('·')} ${card.title}${card.context ? ' ' + C.dim(card.context) : ''}`);
@@ -169,13 +186,13 @@ export async function pickCard(card, num) {
169
186
  }
170
187
  return new Promise(resolve => {
171
188
  _cardResolve = (pick) => {
172
- _update?.(p => ({ ...p, card: null, cardSel: 0 }));
189
+ _update?.(p => ({ ...p, card: null, cardSel: 0, cardTotal: null }));
173
190
  const t = card.title.length > 50 ? card.title.slice(0, 49) + '…' : card.title;
174
191
  log(` ${C.dim(t)} ${C.faint('→')} ${C.bright(pick)}`);
175
192
  log('');
176
193
  resolve(pick);
177
194
  };
178
- _update?.(p => ({ ...p, card, cardNum: num, cardSel: 0 }));
195
+ _update?.(p => ({ ...p, card, cardNum: num, cardTotal: total ?? null, cardSel: 0 }));
179
196
  });
180
197
  }
181
198
  // ── Progress ──
@@ -10,6 +10,7 @@ export declare class InkProgress implements ProgressSink {
10
10
  private _thinking;
11
11
  private _paused;
12
12
  private s;
13
+ private phaseForTools;
13
14
  private paint;
14
15
  start(): void;
15
16
  thinking(): void;
@@ -5,30 +5,51 @@
5
5
  */
6
6
  import { showProgress, hideProgress } from './app.js';
7
7
  const LABEL = {
8
- search_all_messages: 'reading the room',
9
- get_conversation: 'listening in',
10
- profile_contact: 'learning who this is',
11
- get_messages: 'reading the room',
12
- get_unanswered_messages: 'finding what you missed',
13
- get_screen_time: 'tracking your attention',
14
- get_browsing: 'retracing your steps',
15
- get_calls: 'checking who called',
16
- scan_sources: 'pulling threads',
17
- lookup_contact: 'learning who this is',
18
- get_email_summary: 'going through your inbox',
19
- get_git_activity: 'seeing what you shipped',
20
- get_recent_files: 'noticing what you touched',
21
- get_shell_history: 'retracing your steps',
22
- get_notes: 'reading your thoughts',
23
- get_claude_history: 'seeing what you asked',
24
- get_chatgpt_history: 'seeing what you asked',
25
- get_interests_for_plans: 'understanding your world',
26
- get_calendar: 'looking at your day',
27
- get_reminders: 'checking what you owe yourself',
28
- get_notifications: 'catching up',
29
- discover_platforms: 'figuring out your world',
30
- generate_archetype: 'figuring out who you are',
31
- search_emails: 'digging through mail',
8
+ search_all_messages: 'messages: keyword matches',
9
+ get_conversation: 'messages: thread context',
10
+ profile_contact: 'contact profile',
11
+ get_messages: 'messages: recent threads',
12
+ get_unanswered_messages: 'messages: waiting on reply',
13
+ get_screen_time: 'screen time',
14
+ get_browsing: 'browser history',
15
+ get_calls: 'call history',
16
+ scan_sources: 'source availability',
17
+ lookup_contact: 'contact lookup',
18
+ get_email_summary: 'mail summary',
19
+ get_git_activity: 'git activity',
20
+ get_recent_files: 'recent files',
21
+ get_shell_history: 'shell history',
22
+ get_notes: 'notes',
23
+ get_claude_history: 'claude chats',
24
+ get_chatgpt_history: 'chatgpt chats',
25
+ get_interests_for_plans: 'planning signals',
26
+ get_calendar: 'calendar (next 7d)',
27
+ get_reminders: 'reminders',
28
+ get_notifications: 'notifications',
29
+ discover_platforms: 'platform detection',
30
+ generate_archetype: 'profile synthesis',
31
+ search_emails: 'mail search',
32
+ };
33
+ const TOOL_SOURCE = {
34
+ scan_sources: 'source index',
35
+ get_messages: 'messages',
36
+ get_unanswered_messages: 'messages',
37
+ get_conversation: 'messages',
38
+ search_all_messages: 'messages',
39
+ get_calendar: 'calendar',
40
+ get_calls: 'calls',
41
+ get_email_summary: 'mail',
42
+ search_emails: 'mail',
43
+ get_claude_history: 'claude',
44
+ get_chatgpt_history: 'chatgpt',
45
+ get_notes: 'notes',
46
+ get_recent_files: 'files',
47
+ get_shell_history: 'shell',
48
+ get_git_activity: 'git',
49
+ get_screen_time: 'screen time',
50
+ get_browsing: 'browser',
51
+ get_notifications: 'notifications',
52
+ get_reminders: 'reminders',
32
53
  };
33
54
  export class InkProgress {
34
55
  tools = [];
@@ -36,6 +57,15 @@ export class InkProgress {
36
57
  _thinking = false;
37
58
  _paused = false;
38
59
  s(name) { return LABEL[name] || name; }
60
+ phaseForTools(active) {
61
+ const sources = [...new Set(active.map(t => TOOL_SOURCE[t.name] || 'other'))];
62
+ if (!sources.length)
63
+ return 'scanning sources';
64
+ const lead = sources.slice(0, 2).join(' + ');
65
+ return sources.length > 2
66
+ ? `scanning ${lead} +${sources.length - 2}`
67
+ : `scanning ${lead}`;
68
+ }
39
69
  paint() {
40
70
  if (this._paused)
41
71
  return;
@@ -43,14 +73,28 @@ export class InkProgress {
43
73
  const workersDone = this.workers.filter(w => w.done).length;
44
74
  const activeWorkers = this.workers.filter(w => !w.done);
45
75
  if (this.workers.length > 0) {
46
- showProgress(activeWorkers.length > 0 ? 'working' : 'finishing up', workersDone, this.workers.length, activeWorkers.map(w => ({
47
- label: w.label,
48
- tool: w.tool ? this.s(w.tool) : undefined,
49
- })), this._thinking);
76
+ if (activeWorkers.length > 0) {
77
+ showProgress(`investigating ${activeWorkers.length} item${activeWorkers.length === 1 ? '' : 's'}`, workersDone, this.workers.length, activeWorkers.map(w => ({
78
+ label: w.label,
79
+ tool: w.tool ? this.s(w.tool) : undefined,
80
+ })), this._thinking);
81
+ }
82
+ else {
83
+ const doneLabels = this.workers
84
+ .filter(w => w.done && !w.failed)
85
+ .slice(-4)
86
+ .map(w => ({ label: w.label }));
87
+ showProgress('writing briefing', workersDone, this.workers.length, doneLabels, this._thinking);
88
+ }
50
89
  }
51
90
  else if (this.tools.length > 0) {
52
91
  const active = this.tools.filter(t => !t.done);
53
- showProgress(toolsDone === this.tools.length ? 'finishing up' : 'scanning', toolsDone, this.tools.length, active.map(t => ({ label: this.s(t.name) })), this._thinking);
92
+ if (active.length > 0) {
93
+ showProgress(this.phaseForTools(active), toolsDone, this.tools.length, active.map(t => ({ label: this.s(t.name) })), this._thinking);
94
+ }
95
+ else {
96
+ showProgress('analyzing', toolsDone, this.tools.length, [], this._thinking);
97
+ }
54
98
  }
55
99
  else if (this._thinking) {
56
100
  showProgress('', 0, 0, [], true);
@@ -71,8 +115,7 @@ export class InkProgress {
71
115
  const t = this.tools.find(t => t.name === name && !t.done);
72
116
  if (t)
73
117
  t.done = true;
74
- // Delay repaint so the label lingers on screen
75
- setTimeout(() => this.paint(), 400);
118
+ this.paint();
76
119
  }
77
120
  workerStart(id, label) {
78
121
  this._thinking = false;
@@ -114,9 +157,6 @@ export class InkProgress {
114
157
  }
115
158
  pause() { this._paused = true; hideProgress(); }
116
159
  resume() { this._paused = false; this.paint(); }
117
- stop() {
118
- // Let the final state linger so it doesn't vanish instantly
119
- setTimeout(() => hideProgress(), 800);
120
- }
160
+ stop() { hideProgress(); }
121
161
  clear() { hideProgress(); }
122
162
  }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "life-pulse",
3
- "version": "2.3.9",
3
+ "version": "2.3.11",
4
4
  "description": "macOS life diagnostic — reads local data sources, generates actionable insights",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1",
8
- "build": "tsc && chmod +x dist/cli.js",
8
+ "build": "tsc && cp src/ghostty-frames.json dist/ && chmod +x dist/cli.js",
9
9
  "dev": "tsx src/cli.ts",
10
10
  "start": "node dist/cli.js",
11
11
  "daemon": "node dist/cli.js --daemon",
12
12
  "check": "node dist/cli.js --check",
13
13
  "prepublishOnly": "npm run build",
14
- "postinstall": "node -e \"console.log('\\n life-pulse installed.\\n Run: npx life-pulse --setup\\n Or: npx life-pulse --install (daemon + morning brief)\\n')\""
14
+ "postinstall": "node -e \"const fs=require('fs');const os=require('os');const path=require('path');const base=path.join(os.homedir(),'.life-pulse');const soul=path.join(base,'SOUL.md');const memory=path.join(base,'MEMORY.md');const soulTemplate='# NOX Identity\\n\\n- You are NOX, the user\\'s always-on operator.\\n- Voice: direct, warm, sharp, no corporate language.\\n- Prefer concrete next actions over abstract advice.\\n- Never invent facts. If signal is weak, say less.\\n- Keep output compact unless explicitly asked for depth.\\n- In text replies, sound human and conversational.\\n- Prioritize reputation, promises, and momentum.\\n';const memoryTemplate='# Persistent Memory\\n\\n## Operator\\n- Name: <auto-detected at runtime>\\n\\n## Preferences\\n- Prefer terse, high-signal summaries.\\n- Focus on what needs action now.\\n\\n## Ongoing Context\\n- Keep this file updated with durable context that should persist across turns.\\n';try{fs.mkdirSync(base,{recursive:true});if(!fs.existsSync(soul))fs.writeFileSync(soul,soulTemplate,'utf-8');if(!fs.existsSync(memory))fs.writeFileSync(memory,memoryTemplate,'utf-8');}catch{}console.log('\\n life-pulse installed.\\n Run: npx life-pulse --setup\\n Or: npx life-pulse --install (daemon + morning brief)\\n')\""
15
15
  },
16
16
  "keywords": [
17
17
  "macos",
@@ -44,6 +44,7 @@
44
44
  "bplist-parser": "^0.3.2",
45
45
  "chalk": "^5.6.2",
46
46
  "dayjs": "^1.11.19",
47
+ "figlet": "^1.10.0",
47
48
  "ink": "^6.8.0",
48
49
  "react": "^19.2.4",
49
50
  "zod": "^4.3.6"