@getrift/rift 0.1.0-beta.13 → 0.1.0-beta.15

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.
Files changed (62) hide show
  1. package/README.md +35 -9
  2. package/dist/src/capture/observability.d.ts +36 -0
  3. package/dist/src/capture/observability.d.ts.map +1 -1
  4. package/dist/src/capture/observability.js +42 -4
  5. package/dist/src/capture/observability.js.map +1 -1
  6. package/dist/src/cli/commands/capture.d.ts.map +1 -1
  7. package/dist/src/cli/commands/capture.js +25 -0
  8. package/dist/src/cli/commands/capture.js.map +1 -1
  9. package/dist/src/cli/commands/doctor.d.ts +6 -0
  10. package/dist/src/cli/commands/doctor.d.ts.map +1 -0
  11. package/dist/src/cli/commands/doctor.js +183 -0
  12. package/dist/src/cli/commands/doctor.js.map +1 -0
  13. package/dist/src/cli/commands/menubar.d.ts +30 -0
  14. package/dist/src/cli/commands/menubar.d.ts.map +1 -0
  15. package/dist/src/cli/commands/menubar.js +180 -0
  16. package/dist/src/cli/commands/menubar.js.map +1 -0
  17. package/dist/src/cli/commands/onboard.d.ts.map +1 -1
  18. package/dist/src/cli/commands/onboard.js +71 -31
  19. package/dist/src/cli/commands/onboard.js.map +1 -1
  20. package/dist/src/cli/commands/status.d.ts +9 -7
  21. package/dist/src/cli/commands/status.d.ts.map +1 -1
  22. package/dist/src/cli/commands/status.js +29 -10
  23. package/dist/src/cli/commands/status.js.map +1 -1
  24. package/dist/src/cli/commands/update.d.ts +3 -0
  25. package/dist/src/cli/commands/update.d.ts.map +1 -1
  26. package/dist/src/cli/commands/update.js +19 -0
  27. package/dist/src/cli/commands/update.js.map +1 -1
  28. package/dist/src/cli/index.d.ts.map +1 -1
  29. package/dist/src/cli/index.js +4 -0
  30. package/dist/src/cli/index.js.map +1 -1
  31. package/dist/src/cli/postinstall-menubar.d.ts +22 -0
  32. package/dist/src/cli/postinstall-menubar.d.ts.map +1 -0
  33. package/dist/src/cli/postinstall-menubar.js +39 -0
  34. package/dist/src/cli/postinstall-menubar.js.map +1 -0
  35. package/dist/src/cli/status/friend-header.d.ts.map +1 -1
  36. package/dist/src/cli/status/friend-header.js +59 -4
  37. package/dist/src/cli/status/friend-header.js.map +1 -1
  38. package/dist/src/diagnostics/doctor.d.ts +106 -0
  39. package/dist/src/diagnostics/doctor.d.ts.map +1 -0
  40. package/dist/src/diagnostics/doctor.js +282 -0
  41. package/dist/src/diagnostics/doctor.js.map +1 -0
  42. package/dist/src/diagnostics/notify.d.ts +90 -0
  43. package/dist/src/diagnostics/notify.d.ts.map +1 -0
  44. package/dist/src/diagnostics/notify.js +177 -0
  45. package/dist/src/diagnostics/notify.js.map +1 -0
  46. package/dist/src/diagnostics/repair-prompt.d.ts +49 -0
  47. package/dist/src/diagnostics/repair-prompt.d.ts.map +1 -0
  48. package/dist/src/diagnostics/repair-prompt.js +198 -0
  49. package/dist/src/diagnostics/repair-prompt.js.map +1 -0
  50. package/dist/src/main.js +45 -4
  51. package/dist/src/main.js.map +1 -1
  52. package/dist/src/observability/version-check.d.ts +1 -0
  53. package/dist/src/observability/version-check.d.ts.map +1 -1
  54. package/dist/src/observability/version-check.js +2 -1
  55. package/dist/src/observability/version-check.js.map +1 -1
  56. package/dist/src/server/routes/friend-status.d.ts +25 -0
  57. package/dist/src/server/routes/friend-status.d.ts.map +1 -1
  58. package/dist/src/server/routes/friend-status.js +6 -2
  59. package/dist/src/server/routes/friend-status.js.map +1 -1
  60. package/operator/swiftbar/render-menu.py +444 -0
  61. package/operator/swiftbar/rift.10s.sh +152 -0
  62. package/package.json +3 -1
@@ -0,0 +1,282 @@
1
+ /**
2
+ * True only when `iso` parses to a timestamp at/after the daemon's
3
+ * current start. Historical errors that predate the running process are
4
+ * not current and must not be reported as live failures — a healthy
5
+ * respawn should clear them. Mirrors `friend-header.isCurrentUptime`.
6
+ */
7
+ function isCurrentUptime(iso, daemonStartMs) {
8
+ if (!iso)
9
+ return false;
10
+ const ts = Date.parse(iso);
11
+ if (Number.isNaN(ts))
12
+ return false;
13
+ return ts >= daemonStartMs;
14
+ }
15
+ /**
16
+ * Translate internal error-class names into copy a friend can act on.
17
+ * Falls back to the raw class so a new/unknown value still surfaces.
18
+ * Kept in sync with `friend-header.humanizeErrorClass`.
19
+ */
20
+ function humanizeErrorClass(reason) {
21
+ if (!reason)
22
+ return "unknown";
23
+ switch (reason) {
24
+ case "schema_mismatch":
25
+ return "index format changed by an update";
26
+ case "lock_conflict":
27
+ return "index busy (a concurrent write held the lock)";
28
+ case "provider_400":
29
+ return "Voyage rejected the request (often an invalid or revoked key)";
30
+ case "network":
31
+ return "a network error reaching Voyage";
32
+ default:
33
+ return reason;
34
+ }
35
+ }
36
+ /**
37
+ * Build the single issue for a null `/status/friend`, tailored to *why*
38
+ * the read failed so the next action actually fixes the problem.
39
+ */
40
+ function unreachableIssue(reason) {
41
+ switch (reason) {
42
+ case "config_missing":
43
+ return {
44
+ kind: "config_missing",
45
+ severity: "broken",
46
+ title: "Rift is not set up on this Mac yet",
47
+ detail: "Rift has no config file, so the command does not know how to " +
48
+ "reach the daemon. This usually means onboarding never finished.",
49
+ nextAction: "Set it up: rift onboard",
50
+ daemonObservable: false,
51
+ };
52
+ case "not_authenticated":
53
+ return {
54
+ kind: "daemon_auth_failed",
55
+ severity: "broken",
56
+ title: "Rift has no access token on this Mac",
57
+ detail: "The daemon may be running, but this command has no token to " +
58
+ "authenticate with it, so it cannot read Rift's status.",
59
+ nextAction: "Issue a token: rift token issue",
60
+ daemonObservable: false,
61
+ };
62
+ case "auth_failed":
63
+ return {
64
+ kind: "daemon_auth_failed",
65
+ severity: "broken",
66
+ title: "Rift's daemon rejected this command's token",
67
+ detail: "The daemon is reachable but refused the token (it may be stale " +
68
+ "or from a different install). This is an authentication problem, " +
69
+ "not a crashed daemon — restarting will not fix it.",
70
+ nextAction: "Re-issue a token: rift token issue",
71
+ daemonObservable: false,
72
+ };
73
+ case "connection_refused":
74
+ case "unknown":
75
+ default:
76
+ return {
77
+ kind: "daemon_unreachable",
78
+ severity: "broken",
79
+ title: "Rift's background engine is not responding",
80
+ detail: "The Rift daemon is the always-on process that captures sessions, " +
81
+ "keeps the search index fresh, and answers your AI tools. Right now " +
82
+ "nothing can reach it, so capture and memory are paused.",
83
+ nextAction: "Restart it: launchctl kickstart -k gui/$UID/com.getrift.daemon",
84
+ daemonObservable: false,
85
+ };
86
+ }
87
+ }
88
+ /**
89
+ * Diagnose Rift health from the same inputs `rift status` uses. Pure:
90
+ * given identical inputs it always returns the same report.
91
+ */
92
+ export function diagnose(input) {
93
+ const { status, localSignals, now } = input;
94
+ const issues = [];
95
+ // No payload is a root failure, but the *reason* changes the fix:
96
+ // a down daemon needs a restart, an auth failure needs a fresh token,
97
+ // and a missing config needs onboarding. Reporting all three as
98
+ // "restart the daemon" is exactly the misdirection Slice B removes.
99
+ // All are CLI-side diagnoses (the in-process daemon caller always has a
100
+ // payload), so none are daemon-observable — a dead/unauthable daemon
101
+ // cannot notify about itself; that is the Slice D watchdog's job.
102
+ if (!status) {
103
+ const issue = unreachableIssue(input.unreachableReason ?? "connection_refused");
104
+ return { healthy: false, primary: issue, issues: [issue] };
105
+ }
106
+ const daemonStartMs = now - status.daemon.uptime_seconds * 1000;
107
+ // 1. Voyage key missing — without it nothing can be embedded or
108
+ // searched. Highest-priority broken state.
109
+ if (!status.daemon.voyage_key_present) {
110
+ issues.push({
111
+ kind: "voyage_key_missing",
112
+ severity: "broken",
113
+ title: "Voyage search key is missing",
114
+ detail: "Rift uses a Voyage key to turn your text into searchable meaning. " +
115
+ "Without it, nothing new can be indexed and search will not work.",
116
+ nextAction: "Re-add the key: rift onboard --reconfigure-voyage",
117
+ daemonObservable: true,
118
+ });
119
+ }
120
+ // 2. Codex auth expired — auto-capture triage runs through the
121
+ // locally-authenticated codex CLI. Expired login halts capture.
122
+ if (status.codex.last_preflight_ok === false) {
123
+ issues.push({
124
+ kind: "codex_auth_expired",
125
+ severity: "broken",
126
+ title: "Codex login has expired",
127
+ detail: "Rift reads your new Claude Code / Codex sessions through the codex " +
128
+ "CLI. Its login has expired, so automatic capture cannot run and new " +
129
+ "conversations are not being saved.",
130
+ nextAction: "Sign back in: codex login (then run: rift capture)",
131
+ daemonObservable: true,
132
+ });
133
+ }
134
+ // 3. Voyage embedding errors during this uptime — the key is present
135
+ // but Voyage is rejecting/dropping calls (often an invalid key or
136
+ // a network problem). Recency-gated so a prior process's error
137
+ // does not haunt a healthy respawn.
138
+ if (isCurrentUptime(status.voyage.last_error_at, daemonStartMs)) {
139
+ issues.push({
140
+ kind: "voyage_embed_errors",
141
+ severity: "broken",
142
+ fingerprint: `voyage_embed_errors:${status.voyage.last_error_reason ?? "unknown"}`,
143
+ title: "Search indexing is failing at the Voyage step",
144
+ detail: "Rift could not turn recent text into searchable meaning: " +
145
+ `${humanizeErrorClass(status.voyage.last_error_reason)}. New content ` +
146
+ "may not be findable until this clears.",
147
+ nextAction: "Inspect with rift status, then check the Voyage key with " +
148
+ "rift onboard --reconfigure-voyage if errors mention the key",
149
+ daemonObservable: true,
150
+ });
151
+ }
152
+ // 4. Index write errors during this uptime — text embedded fine, but
153
+ // the LanceDB write failed (schema mismatch, lock, IO). Distinct
154
+ // from Voyage: "embedded" does not imply "searchable".
155
+ if (isCurrentUptime(status.index.last_error_at, daemonStartMs)) {
156
+ issues.push({
157
+ kind: "index_write_errors",
158
+ severity: "broken",
159
+ fingerprint: `index_write_errors:${status.index.last_error_reason ?? "unknown"}`,
160
+ title: "Search index writes are failing",
161
+ detail: "Rift embedded recent text but could not write it to the search " +
162
+ `index: ${humanizeErrorClass(status.index.last_error_reason)}. ` +
163
+ "Recently captured items may not be searchable yet.",
164
+ nextAction: "Inspect with rift status, then try: rift reindex",
165
+ daemonObservable: true,
166
+ });
167
+ }
168
+ // 5. Capture run failed during this uptime — the auto-capture loop
169
+ // itself threw (distinct from a Codex preflight failure, which is
170
+ // reported above with a more specific action).
171
+ if (status.capture.last_errors > 0 &&
172
+ isCurrentUptime(status.capture.last_run_at, daemonStartMs)) {
173
+ issues.push({
174
+ kind: "capture_failed",
175
+ severity: "broken",
176
+ title: "The last capture run reported errors",
177
+ detail: "Rift's automatic capture ran but hit errors while saving sessions, " +
178
+ "so some recent conversations may not have been stored.",
179
+ nextAction: "Re-run it and watch the output: rift capture",
180
+ daemonObservable: true,
181
+ });
182
+ }
183
+ // 5b. Scheduled capture is failing, but a more recent manual `rift
184
+ // capture` succeeded. The honest middle state: capture works right
185
+ // now (so this is NOT broken), but the daemon's own loop is still
186
+ // failing and must not be laundered green by the manual success. It
187
+ // clears itself once the daemon records a healthy run. CLI-side
188
+ // reconciliation, so not daemon-observable — the daemon never sees
189
+ // the manual run, and its own failures already notify via
190
+ // capture_failed/codex_auth_expired while they are the newest row.
191
+ if (status.capture.last_daemon_failed === true &&
192
+ isCurrentUptime(status.capture.last_daemon_run_at ?? null, daemonStartMs) &&
193
+ status.capture.last_run_trigger != null &&
194
+ status.capture.last_run_trigger !== "daemon" &&
195
+ status.capture.last_run_ok === true &&
196
+ status.capture.last_errors === 0) {
197
+ issues.push({
198
+ kind: "capture_daemon_stale",
199
+ severity: "warning",
200
+ title: "Automatic capture last failed — recent capture is working",
201
+ detail: "Your most recent capture ran fine, but the daemon's last scheduled " +
202
+ "capture failed. Capture works right now; the automatic loop will " +
203
+ "re-check on its next scheduled run.",
204
+ // Capture is working, so the calm, friend-facing action is to do
205
+ // nothing: the next scheduled tick re-checks and clears this on its
206
+ // own. Deliberately NO `launchctl kickstart` here — that operator
207
+ // machinery is too scary for a recovery path where nothing is broken
208
+ // right now. (A friend-safe `rift daemon restart` is a future affordance.)
209
+ nextAction: "Nothing to do — the next automatic capture will re-check and clear " +
210
+ "this on its own.",
211
+ daemonObservable: false,
212
+ });
213
+ }
214
+ // 6. Inbox import errors — user-side: a dropped export was rejected
215
+ // (malformed JSON, corrupt zip, unsupported source). The fix is to
216
+ // re-drop a fresh export, not to repair the engine. Warning.
217
+ if (status.inbox &&
218
+ isCurrentUptime(status.inbox.last_error_at, daemonStartMs)) {
219
+ issues.push({
220
+ kind: "inbox_import_errors",
221
+ severity: "warning",
222
+ title: "A dropped import could not be read",
223
+ detail: "Rift tried to import a file from your inbox folder but could not " +
224
+ `read it (${status.inbox.last_error_reason ?? "unknown reason"}). ` +
225
+ "This is usually a corrupt or unsupported export, not a Rift fault.",
226
+ nextAction: "Re-export and drop a fresh archive under data/inbox/",
227
+ // User-side; the daemon observes it but the plan scopes
228
+ // notifications to engine failures, not rejected user input.
229
+ daemonObservable: false,
230
+ });
231
+ }
232
+ // 7. MCP not installed for one or more clients — degraded reach, not a
233
+ // broken engine. Warning.
234
+ const missing = localSignals.mcpClients
235
+ .filter((c) => !c.installed)
236
+ .map((c) => c.client);
237
+ if (missing.length > 0) {
238
+ issues.push({
239
+ kind: "mcp_not_installed",
240
+ severity: "warning",
241
+ title: `Rift is not connected to ${missing.join(", ")}`,
242
+ detail: "MCP is the connection that lets your AI tools ask Rift for memory. " +
243
+ `These tools are not wired up yet: ${missing.join(", ")}.`,
244
+ nextAction: `Connect one: rift mcp install --client=${missing[0]}`,
245
+ daemonObservable: false,
246
+ });
247
+ }
248
+ // 8. Quarantined oversized sessions — silent data loss, but a graceful
249
+ // skip rather than an error. Warning.
250
+ if (status.capture.last_quarantined > 0) {
251
+ const n = status.capture.last_quarantined;
252
+ issues.push({
253
+ kind: "capture_quarantined",
254
+ severity: "warning",
255
+ title: `${n} session${n === 1 ? "" : "s"} skipped for being too large`,
256
+ detail: "Rift skipped some oversized sessions during capture instead of " +
257
+ "saving them. They are not lost on disk, but they are not in your " +
258
+ "memory yet.",
259
+ nextAction: "Raise capture.codex_cli.max_session_bytes in config.json and " +
260
+ "restart, or accept the gap",
261
+ daemonObservable: false,
262
+ });
263
+ }
264
+ // 9. Update available — informational nudge, lowest priority.
265
+ if (status.update.available && status.update.latest_beta) {
266
+ issues.push({
267
+ kind: "update_available",
268
+ severity: "warning",
269
+ title: `A newer Rift is available (${status.update.installed} → ${status.update.latest_beta})`,
270
+ detail: "A newer beta build is published.",
271
+ nextAction: "Update now: rift update",
272
+ daemonObservable: false,
273
+ });
274
+ }
275
+ const healthy = !issues.some((i) => i.severity === "broken");
276
+ // Lead with the most severe issue: the first `broken` if any, else the
277
+ // first issue. Broken issues are already pushed ahead of warnings, so
278
+ // this also keeps `primary` stable if the positional order ever shifts.
279
+ const primary = issues.find((i) => i.severity === "broken") ?? issues[0] ?? null;
280
+ return { healthy, primary, issues };
281
+ }
282
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/diagnostics/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import type { DoctorIssue, DoctorIssueKind } from "./doctor.js";
2
+ /** Re-notify cadence for an issue that stays unresolved. */
3
+ export declare const RENOTIFY_INTERVAL_MS: number;
4
+ export interface NotificationPayload {
5
+ /** macOS notification title — always the same friend-facing banner. */
6
+ title: string;
7
+ /** One-line subtitle naming the failing subsystem. */
8
+ subtitle: string;
9
+ /** Body: the issue's next action, so the notification is actionable. */
10
+ message: string;
11
+ }
12
+ /** Per-subsystem dedup record. */
13
+ export interface NotifyRecord {
14
+ last_notified_at: string;
15
+ /**
16
+ * The cause fingerprint last notified for this kind (e.g.
17
+ * `voyage_embed_errors:provider_400`). When the live issue's
18
+ * fingerprint differs, the cause changed and the friend should be
19
+ * re-notified immediately — the repair action/risk is now different —
20
+ * rather than staying silent for the rest of the 24h window. Optional
21
+ * for backward compatibility with state written before fingerprinting.
22
+ */
23
+ fingerprint?: string;
24
+ }
25
+ /**
26
+ * Persisted dedup state. Presence of a kind means it is currently in a
27
+ * failing state and was last notified at `last_notified_at`. Absence
28
+ * means resolved-or-never — so the next occurrence notifies fresh.
29
+ */
30
+ export interface NotifyState {
31
+ active: Partial<Record<DoctorIssueKind, NotifyRecord>>;
32
+ }
33
+ export interface PlanNotificationsInput {
34
+ /** All diagnosed issues (the full report list, any severity). */
35
+ issues: DoctorIssue[];
36
+ /** Previously persisted dedup state. */
37
+ state: NotifyState;
38
+ now: number;
39
+ }
40
+ export interface PlanNotificationsResult {
41
+ /** Payloads to actually send this tick (already rate-limited). */
42
+ notifications: NotificationPayload[];
43
+ /** State to persist after sending. */
44
+ nextState: NotifyState;
45
+ }
46
+ /**
47
+ * Build the friend-facing notification for an issue. Pure. The title is
48
+ * intentionally constant ("Rift needs attention") so the friend learns
49
+ * to recognise it; the subtitle and body carry the specifics.
50
+ */
51
+ export declare function buildNotificationPayload(issue: DoctorIssue): NotificationPayload;
52
+ /**
53
+ * Decide which notifications to send this tick and what state to persist.
54
+ * Pure: no IO, no clock except the injected `now`.
55
+ *
56
+ * Only `daemonObservable` issues are eligible — the daemon must not
57
+ * notify about things it cannot itself observe (e.g. a dead daemon, or
58
+ * user-side rejected imports).
59
+ */
60
+ export declare function planNotifications(input: PlanNotificationsInput): PlanNotificationsResult;
61
+ /** Absolute path to the dedup-state file under the data dir. */
62
+ export declare function notifyStatePath(dataDir: string): string;
63
+ /** Read dedup state; returns empty state on any read/parse failure. */
64
+ export declare function readNotifyState(dataDir: string): NotifyState;
65
+ /** Persist dedup state best-effort; swallows IO errors. */
66
+ export declare function writeNotifyState(dataDir: string, state: NotifyState): void;
67
+ /**
68
+ * Send a single notification via macOS `osascript`. Best-effort and
69
+ * fire-and-forget — any failure (non-macOS, osascript missing, user has
70
+ * notifications disabled) is swallowed so capture is never affected.
71
+ *
72
+ * Strings are passed as argv to `execFile` (not interpolated into a
73
+ * shell), and AppleScript string literals are escaped, so notification
74
+ * text cannot inject shell or AppleScript.
75
+ */
76
+ export declare function sendNotification(payload: NotificationPayload, opts?: {
77
+ platform?: NodeJS.Platform;
78
+ }): void;
79
+ /** Escape a string into an AppleScript double-quoted literal. */
80
+ export declare function asAppleScriptString(value: string): string;
81
+ /**
82
+ * End-to-end daemon helper: given the diagnosed issues, send any due
83
+ * notifications and persist updated dedup state. Best-effort throughout
84
+ * — wired into the capture loop, so it must never throw.
85
+ */
86
+ export declare function notifyDaemonFailures(dataDir: string, issues: DoctorIssue[], opts?: {
87
+ now?: number;
88
+ platform?: NodeJS.Platform;
89
+ }): void;
90
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../../src/diagnostics/notify.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhE,4DAA4D;AAC5D,eAAO,MAAM,oBAAoB,QAAsB,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,kCAAkC;AAClC,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC;CACxD;AAOD,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,wCAAwC;IACxC,KAAK,EAAE,WAAW,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,kEAAkE;IAClE,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,sCAAsC;IACtC,SAAS,EAAE,WAAW,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,WAAW,GACjB,mBAAmB,CAMrB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,GAC5B,uBAAuB,CA+CzB;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAW5D;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAQ1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,mBAAmB,EAC5B,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAO,GACxC,IAAI,CAcN;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,EAAE,EACrB,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAO,GACtD,IAAI,CAmBN"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Native failure notifications for daemon-observed problems.
3
+ *
4
+ * A running daemon can see capture failures, expired Codex login, an
5
+ * invalid/failing Voyage key, and index-write failures. When one of
6
+ * those crosses into a failing state, the friend should get one calm
7
+ * macOS notification that points at the repair surface — not a wall of
8
+ * logs, and not a notification on every poll.
9
+ *
10
+ * What this module does NOT cover: a fully dead daemon cannot notify
11
+ * about itself. That needs a separate watchdog and is explicitly Slice D
12
+ * (see docs/feedback/2026-05-20-friend-onboarding-status-surface.md).
13
+ *
14
+ * Rate-limiting rules (from the status-surface plan):
15
+ * - Notify on state transition, not every poll.
16
+ * - Do not repeat the same unresolved issue more than once per day.
17
+ * - Notify again after the issue resolves and later recurs.
18
+ *
19
+ * The planning logic (`planNotifications`) is pure and fully tested; the
20
+ * IO (reading/writing dedup state, sending via osascript) is thin and
21
+ * best-effort so a notification failure can never break capture.
22
+ */
23
+ import { execFile } from "node:child_process";
24
+ import fs from "node:fs";
25
+ import path from "node:path";
26
+ /** Re-notify cadence for an issue that stays unresolved. */
27
+ export const RENOTIFY_INTERVAL_MS = 24 * 60 * 60 * 1000;
28
+ /** Cause identity for an issue: explicit fingerprint, else its kind. */
29
+ function issueFingerprint(issue) {
30
+ return issue.fingerprint ?? issue.kind;
31
+ }
32
+ /**
33
+ * Build the friend-facing notification for an issue. Pure. The title is
34
+ * intentionally constant ("Rift needs attention") so the friend learns
35
+ * to recognise it; the subtitle and body carry the specifics.
36
+ */
37
+ export function buildNotificationPayload(issue) {
38
+ return {
39
+ title: "Rift needs attention",
40
+ subtitle: issue.title,
41
+ message: issue.nextAction,
42
+ };
43
+ }
44
+ /**
45
+ * Decide which notifications to send this tick and what state to persist.
46
+ * Pure: no IO, no clock except the injected `now`.
47
+ *
48
+ * Only `daemonObservable` issues are eligible — the daemon must not
49
+ * notify about things it cannot itself observe (e.g. a dead daemon, or
50
+ * user-side rejected imports).
51
+ */
52
+ export function planNotifications(input) {
53
+ const { issues, state, now } = input;
54
+ const eligible = issues.filter((i) => i.daemonObservable);
55
+ const eligibleKinds = new Set(eligible.map((i) => i.kind));
56
+ const nextActive = {};
57
+ const notifications = [];
58
+ for (const issue of eligible) {
59
+ const prior = state.active[issue.kind];
60
+ const fingerprint = issueFingerprint(issue);
61
+ const notify = () => {
62
+ notifications.push(buildNotificationPayload(issue));
63
+ nextActive[issue.kind] = {
64
+ last_notified_at: new Date(now).toISOString(),
65
+ fingerprint,
66
+ };
67
+ };
68
+ if (!prior) {
69
+ // New transition into failure → notify.
70
+ notify();
71
+ continue;
72
+ }
73
+ // The cause changed under the same subsystem (e.g. a Voyage network
74
+ // blip became a provider rejection). The action and risk differ, so
75
+ // treat it as a fresh failure rather than a still-unresolved repeat.
76
+ if ((prior.fingerprint ?? issue.kind) !== fingerprint) {
77
+ notify();
78
+ continue;
79
+ }
80
+ const priorMs = Date.parse(prior.last_notified_at);
81
+ const stale = Number.isNaN(priorMs) || now - priorMs >= RENOTIFY_INTERVAL_MS;
82
+ if (stale) {
83
+ // Still failing, same cause, a day later → re-notify once.
84
+ notify();
85
+ }
86
+ else {
87
+ // Already notified within the window → stay quiet, keep record.
88
+ nextActive[issue.kind] = prior;
89
+ }
90
+ }
91
+ // Anything previously active but no longer present has resolved; it is
92
+ // simply absent from `nextActive`, so a later recurrence notifies fresh.
93
+ void eligibleKinds;
94
+ return { notifications, nextState: { active: nextActive } };
95
+ }
96
+ /** Absolute path to the dedup-state file under the data dir. */
97
+ export function notifyStatePath(dataDir) {
98
+ return path.join(dataDir, "observability", "notifications.json");
99
+ }
100
+ /** Read dedup state; returns empty state on any read/parse failure. */
101
+ export function readNotifyState(dataDir) {
102
+ try {
103
+ const raw = fs.readFileSync(notifyStatePath(dataDir), "utf8");
104
+ const parsed = JSON.parse(raw);
105
+ if (parsed && typeof parsed === "object" && parsed.active) {
106
+ return { active: parsed.active };
107
+ }
108
+ }
109
+ catch {
110
+ // fall through
111
+ }
112
+ return { active: {} };
113
+ }
114
+ /** Persist dedup state best-effort; swallows IO errors. */
115
+ export function writeNotifyState(dataDir, state) {
116
+ try {
117
+ const file = notifyStatePath(dataDir);
118
+ fs.mkdirSync(path.dirname(file), { recursive: true });
119
+ fs.writeFileSync(file, JSON.stringify(state, null, 2) + "\n", "utf8");
120
+ }
121
+ catch {
122
+ // best-effort: a failed write just means we may re-notify next tick.
123
+ }
124
+ }
125
+ /**
126
+ * Send a single notification via macOS `osascript`. Best-effort and
127
+ * fire-and-forget — any failure (non-macOS, osascript missing, user has
128
+ * notifications disabled) is swallowed so capture is never affected.
129
+ *
130
+ * Strings are passed as argv to `execFile` (not interpolated into a
131
+ * shell), and AppleScript string literals are escaped, so notification
132
+ * text cannot inject shell or AppleScript.
133
+ */
134
+ export function sendNotification(payload, opts = {}) {
135
+ const platform = opts.platform ?? process.platform;
136
+ if (platform !== "darwin")
137
+ return;
138
+ const script = `display notification ${asAppleScriptString(payload.message)} ` +
139
+ `with title ${asAppleScriptString(payload.title)} ` +
140
+ `subtitle ${asAppleScriptString(payload.subtitle)}`;
141
+ try {
142
+ execFile("osascript", ["-e", script], () => {
143
+ // ignore stdout/stderr/exit — best-effort.
144
+ });
145
+ }
146
+ catch {
147
+ // ignore spawn errors entirely.
148
+ }
149
+ }
150
+ /** Escape a string into an AppleScript double-quoted literal. */
151
+ export function asAppleScriptString(value) {
152
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
153
+ }
154
+ /**
155
+ * End-to-end daemon helper: given the diagnosed issues, send any due
156
+ * notifications and persist updated dedup state. Best-effort throughout
157
+ * — wired into the capture loop, so it must never throw.
158
+ */
159
+ export function notifyDaemonFailures(dataDir, issues, opts = {}) {
160
+ try {
161
+ const now = opts.now ?? Date.now();
162
+ const state = readNotifyState(dataDir);
163
+ const { notifications, nextState } = planNotifications({
164
+ issues,
165
+ state,
166
+ now,
167
+ });
168
+ for (const payload of notifications) {
169
+ sendNotification(payload, opts.platform ? { platform: opts.platform } : {});
170
+ }
171
+ writeNotifyState(dataDir, nextState);
172
+ }
173
+ catch {
174
+ // best-effort: never let notification logic break the daemon.
175
+ }
176
+ }
177
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../../../src/diagnostics/notify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Redacted repair-prompt generator for `rift doctor --copy-prompt`.
3
+ *
4
+ * The output is designed to be pasted straight into Claude or Codex and
5
+ * be safe to do so. It carries enough state to debug Rift but never
6
+ * leaks a secret, and it forbids destructive repair so an over-eager
7
+ * assistant cannot wipe the user's memory.
8
+ *
9
+ * Redaction contract (see the friend-onboarding status-surface plan):
10
+ * - Never include full Voyage keys, auth tokens, bearer tokens, or raw
11
+ * private conversation content.
12
+ * - Voyage keys appear only as a last-4 fingerprint, and only when one
13
+ * is known locally.
14
+ * - Local paths are allowed only when they help repair (data + log
15
+ * locations), because they are not secrets and the assistant needs
16
+ * them to inspect the right folder.
17
+ *
18
+ * Safety contract:
19
+ * - Tell the assistant to inspect before changing anything.
20
+ * - Prefer commands that inspect/repair over commands that reset.
21
+ * - Explicitly forbid destructive resets and
22
+ * `rift uninstall --purge-data`.
23
+ *
24
+ * This module is pure (no IO). Centralizing prompt generation here —
25
+ * rather than in the SwiftBar script or the CLI command — keeps
26
+ * redaction, status interpretation, and future issue classes in one
27
+ * place (Slice C's menu bar will call this same generator).
28
+ */
29
+ import type { FriendStatusPayload } from "../server/routes/friend-status.js";
30
+ import type { DoctorLocalSignals, DoctorReport } from "./doctor.js";
31
+ export type RepairTarget = "claude" | "codex";
32
+ export interface RepairPromptInput {
33
+ report: DoctorReport;
34
+ status: FriendStatusPayload | null;
35
+ localSignals: DoctorLocalSignals;
36
+ }
37
+ /**
38
+ * Commands the assistant must never run from this prompt. Kept as data
39
+ * so the test suite can assert every one is named in the rendered
40
+ * "Constraints" block.
41
+ */
42
+ export declare const FORBIDDEN_COMMANDS: readonly string[];
43
+ /**
44
+ * Build the repair prompt. `target` only changes which assistant the
45
+ * prompt addresses; the state and safety rules are identical so neither
46
+ * tool can be talked into something the other forbids.
47
+ */
48
+ export declare function buildRepairPrompt(input: RepairPromptInput, target: RepairTarget): string;
49
+ //# sourceMappingURL=repair-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair-prompt.d.ts","sourceRoot":"","sources":["../../../src/diagnostics/repair-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,kBAAkB,CAAC;CAClC;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAI/C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,YAAY,GACnB,MAAM,CAoDR"}