persnally 2.5.2 → 2.5.3

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/build/src/cli.js CHANGED
@@ -19,7 +19,7 @@ import { extractClaudeEvents, parseClaudeExport } from "./importers/claude.js";
19
19
  import { DEFAULT_TRANSCRIPTS_DIR, extractClaudeCodeEvents, parseClaudeCodeTranscripts, } from "./importers/claude-code.js";
20
20
  import { gitEvents, scanRepos } from "./importers/git.js";
21
21
  import { freshConversations } from "./importers/extract.js";
22
- import { autostartInstalled, installAutostart, LOG_FILE, removeAutostart, removePidFile, runningPid, startDetached, stopDaemon, writePidFile, } from "./lifecycle.js";
22
+ import { autostartInstalled, installAutostart, LOG_FILE, reloadAutostart, removeAutostart, removePidFile, runningPid, startDetached, stopDaemon, writePidFile, } from "./lifecycle.js";
23
23
  import { newEvent } from "./events.js";
24
24
  import { proseLines } from "./prose.js";
25
25
  import { analyzeVoice } from "./stylometry.js";
@@ -50,6 +50,7 @@ Usage:
50
50
  persnallyd activity Context-read engagement over time (retention pulse)
51
51
  persnallyd start [--port N] Start the daemon in the background
52
52
  persnallyd stop Stop the background daemon
53
+ persnallyd restart Restart the daemon (correctly handles autostart/launchd)
53
54
  persnallyd serve [--port N] Run the daemon in the foreground (127.0.0.1:${DEFAULT_PORT})
54
55
  persnallyd autostart [--remove] Start the daemon at login and keep it alive (macOS)
55
56
  persnallyd config set-key <key> Store the Anthropic API key (owner-only file) for the daemon
@@ -171,13 +172,8 @@ async function main() {
171
172
  console.error(`· Context hook skipped: ${e instanceof Error ? e.message : e}`);
172
173
  }
173
174
  }
174
- console.log(`\nDone${imported ? ` — ${imported} events imported` : ""}. Dashboard: http://127.0.0.1:${port}`);
175
- if (process.platform === "darwin" && process.stdout.isTTY) {
176
- try {
177
- execFileSync("open", [`http://127.0.0.1:${port}`]);
178
- }
179
- catch { /* non-fatal */ }
180
- }
175
+ console.log(`\nDone${imported ? ` — ${imported} events imported` : ""}.`);
176
+ announceDashboard(port);
181
177
  return;
182
178
  }
183
179
  case "scope": {
@@ -483,19 +479,43 @@ async function main() {
483
479
  const existing = runningPid();
484
480
  if (existing)
485
481
  return die(`daemon already running (pid ${existing})`);
486
- const pid = await startDetached(process.argv[1], parsePort(args));
487
- console.log(`persnallyd started (pid ${pid}). Dashboard: http://127.0.0.1:${parsePort(args)}`);
482
+ const port = parsePort(args);
483
+ const pid = await startDetached(process.argv[1], port);
484
+ console.log(`persnallyd started (pid ${pid}).`);
485
+ announceDashboard(port);
488
486
  console.log(`Logs: ${LOG_FILE}`);
489
487
  return;
490
488
  }
491
489
  case "stop": {
492
490
  if (autostartInstalled()) {
493
- console.error("Note: autostart is installed — launchd will restart the daemon. Use `persnallyd autostart --remove` to stop it permanently.");
491
+ console.error("Note: autostart is installed — launchd will respawn the daemon. To restart cleanly use `persnallyd restart`; to stop it for good use `persnallyd autostart --remove`.");
494
492
  }
495
493
  const pid = await stopDaemon();
496
494
  console.log(pid ? `Stopped daemon (pid ${pid}).` : "Daemon was not running.");
497
495
  return;
498
496
  }
497
+ case "restart": {
498
+ const port = parsePort(args);
499
+ if (autostartInstalled()) {
500
+ // launchd owns the lifecycle — a plain stop just gets respawned. Reload the
501
+ // job so it comes back on the current install (also heals a drifted plist path).
502
+ const health = await reloadAutostart(process.argv[1], port);
503
+ if (health) {
504
+ console.log(`Restarted via launchd — daemon up on v${health.version}.`);
505
+ announceDashboard(port);
506
+ }
507
+ else {
508
+ console.log("Reloaded autostart; daemon is still coming up — check: persnallyd status");
509
+ }
510
+ }
511
+ else {
512
+ await stopDaemon();
513
+ const pid = await startDetached(process.argv[1], port);
514
+ console.log(`persnallyd restarted (pid ${pid}).`);
515
+ announceDashboard(port);
516
+ }
517
+ return;
518
+ }
499
519
  case "autostart": {
500
520
  if (args[0] === "--remove") {
501
521
  console.log(removeAutostart() ? "Autostart removed; daemon stopped." : "Autostart was not installed.");
@@ -507,6 +527,7 @@ async function main() {
507
527
  console.log(`Stopped existing daemon (pid ${stopped}) — launchd takes over.`);
508
528
  const plist = installAutostart(process.argv[1], parsePort(args));
509
529
  console.log(`Autostart installed (${plist}). The daemon now runs at login and restarts if it exits.`);
530
+ announceDashboard(parsePort(args), false); // launchd brings it up async — show the link, don't open a not-yet-ready page
510
531
  return;
511
532
  }
512
533
  case "serve": {
@@ -553,6 +574,17 @@ function summarize(payload) {
553
574
  const s = JSON.stringify(payload);
554
575
  return s.length > 80 ? s.slice(0, 77) + "..." : s;
555
576
  }
577
+ /** Print the dashboard URL and, when run interactively on macOS, open it. */
578
+ function announceDashboard(port, open = true) {
579
+ const url = `http://127.0.0.1:${port}`;
580
+ console.log(`Dashboard: ${url}`);
581
+ if (open && process.platform === "darwin" && process.stdout.isTTY) {
582
+ try {
583
+ execFileSync("open", [url]);
584
+ }
585
+ catch { /* non-fatal — the link is printed above */ }
586
+ }
587
+ }
556
588
  function die(msg) {
557
589
  console.error(msg);
558
590
  process.exit(1);
@@ -12,3 +12,13 @@ export declare function stopDaemon(): Promise<number | null>;
12
12
  export declare function autostartInstalled(): boolean;
13
13
  export declare function installAutostart(cliPath: string, port: number): string;
14
14
  export declare function removeAutostart(): boolean;
15
+ /**
16
+ * Reload the launchd job so the daemon restarts on the currently-installed build.
17
+ * `unload` then `load` — a plain `load` can't replace an already-loaded job, which
18
+ * is how a plist path silently drifts from the running process. Rewriting from the
19
+ * caller's cliPath also heals that drift. Returns the new daemon's /health once it
20
+ * answers, or null if it didn't come up in time.
21
+ */
22
+ export declare function reloadAutostart(cliPath: string, port: number): Promise<{
23
+ version: string;
24
+ } | null>;
@@ -118,6 +118,27 @@ export function removeAutostart() {
118
118
  rmSync(PLIST_PATH);
119
119
  return true;
120
120
  }
121
+ /**
122
+ * Reload the launchd job so the daemon restarts on the currently-installed build.
123
+ * `unload` then `load` — a plain `load` can't replace an already-loaded job, which
124
+ * is how a plist path silently drifts from the running process. Rewriting from the
125
+ * caller's cliPath also heals that drift. Returns the new daemon's /health once it
126
+ * answers, or null if it didn't come up in time.
127
+ */
128
+ export async function reloadAutostart(cliPath, port) {
129
+ removeAutostart();
130
+ installAutostart(cliPath, port);
131
+ for (let i = 0; i < 30; i++) {
132
+ await sleep(100);
133
+ try {
134
+ const r = await fetch(`http://127.0.0.1:${port}/health`);
135
+ if (r.ok)
136
+ return (await r.json());
137
+ }
138
+ catch { /* launchd hasn't brought it up yet */ }
139
+ }
140
+ return null;
141
+ }
121
142
  function sleep(ms) {
122
143
  return new Promise((r) => setTimeout(r, ms));
123
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "persnally",
3
- "version": "2.5.2",
3
+ "version": "2.5.3",
4
4
  "license": "FSL-1.1-MIT",
5
5
  "description": "Your own context engine — local-first, across every AI. So every AI finally knows you.",
6
6
  "type": "module",