@getrift/rift 0.1.0-beta.2 → 0.1.0-beta.20

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 (386) hide show
  1. package/README.md +35 -9
  2. package/dist/src/auth/keychain.d.ts +9 -0
  3. package/dist/src/auth/keychain.d.ts.map +1 -1
  4. package/dist/src/auth/keychain.js +37 -0
  5. package/dist/src/auth/keychain.js.map +1 -1
  6. package/dist/src/capture/auto-capture.d.ts +7 -0
  7. package/dist/src/capture/auto-capture.d.ts.map +1 -1
  8. package/dist/src/capture/auto-capture.js +82 -15
  9. package/dist/src/capture/auto-capture.js.map +1 -1
  10. package/dist/src/capture/auto-repair.d.ts +110 -0
  11. package/dist/src/capture/auto-repair.d.ts.map +1 -0
  12. package/dist/src/capture/auto-repair.js +269 -0
  13. package/dist/src/capture/auto-repair.js.map +1 -0
  14. package/dist/src/capture/codex-cli-triage-provider.d.ts.map +1 -1
  15. package/dist/src/capture/codex-cli-triage-provider.js +4 -3
  16. package/dist/src/capture/codex-cli-triage-provider.js.map +1 -1
  17. package/dist/src/capture/observability.d.ts +42 -0
  18. package/dist/src/capture/observability.d.ts.map +1 -1
  19. package/dist/src/capture/observability.js +45 -4
  20. package/dist/src/capture/observability.js.map +1 -1
  21. package/dist/src/capture/recover-quarantine.d.ts +260 -0
  22. package/dist/src/capture/recover-quarantine.d.ts.map +1 -0
  23. package/dist/src/capture/recover-quarantine.js +522 -0
  24. package/dist/src/capture/recover-quarantine.js.map +1 -0
  25. package/dist/src/cli/commands/backfill.d.ts.map +1 -1
  26. package/dist/src/cli/commands/backfill.js +5 -2
  27. package/dist/src/cli/commands/backfill.js.map +1 -1
  28. package/dist/src/cli/commands/capture-recover.d.ts +40 -0
  29. package/dist/src/cli/commands/capture-recover.d.ts.map +1 -0
  30. package/dist/src/cli/commands/capture-recover.js +184 -0
  31. package/dist/src/cli/commands/capture-recover.js.map +1 -0
  32. package/dist/src/cli/commands/capture.d.ts.map +1 -1
  33. package/dist/src/cli/commands/capture.js +96 -5
  34. package/dist/src/cli/commands/capture.js.map +1 -1
  35. package/dist/src/cli/commands/doctor.d.ts +6 -0
  36. package/dist/src/cli/commands/doctor.d.ts.map +1 -0
  37. package/dist/src/cli/commands/doctor.js +242 -0
  38. package/dist/src/cli/commands/doctor.js.map +1 -0
  39. package/dist/src/cli/commands/feedback.d.ts +12 -0
  40. package/dist/src/cli/commands/feedback.d.ts.map +1 -1
  41. package/dist/src/cli/commands/feedback.js +93 -4
  42. package/dist/src/cli/commands/feedback.js.map +1 -1
  43. package/dist/src/cli/commands/mcp-install.js +5 -2
  44. package/dist/src/cli/commands/mcp-install.js.map +1 -1
  45. package/dist/src/cli/commands/menubar.d.ts +30 -0
  46. package/dist/src/cli/commands/menubar.d.ts.map +1 -0
  47. package/dist/src/cli/commands/menubar.js +180 -0
  48. package/dist/src/cli/commands/menubar.js.map +1 -0
  49. package/dist/src/cli/commands/onboard.d.ts +129 -0
  50. package/dist/src/cli/commands/onboard.d.ts.map +1 -1
  51. package/dist/src/cli/commands/onboard.js +752 -171
  52. package/dist/src/cli/commands/onboard.js.map +1 -1
  53. package/dist/src/cli/commands/rebuild.d.ts.map +1 -1
  54. package/dist/src/cli/commands/rebuild.js +6 -3
  55. package/dist/src/cli/commands/rebuild.js.map +1 -1
  56. package/dist/src/cli/commands/reconcile.d.ts.map +1 -1
  57. package/dist/src/cli/commands/reconcile.js +12 -0
  58. package/dist/src/cli/commands/reconcile.js.map +1 -1
  59. package/dist/src/cli/commands/review.d.ts.map +1 -1
  60. package/dist/src/cli/commands/review.js +22 -7
  61. package/dist/src/cli/commands/review.js.map +1 -1
  62. package/dist/src/cli/commands/search.d.ts +2 -0
  63. package/dist/src/cli/commands/search.d.ts.map +1 -1
  64. package/dist/src/cli/commands/search.js +34 -4
  65. package/dist/src/cli/commands/search.js.map +1 -1
  66. package/dist/src/cli/commands/status.d.ts +9 -7
  67. package/dist/src/cli/commands/status.d.ts.map +1 -1
  68. package/dist/src/cli/commands/status.js +113 -12
  69. package/dist/src/cli/commands/status.js.map +1 -1
  70. package/dist/src/cli/commands/token-issue.d.ts.map +1 -1
  71. package/dist/src/cli/commands/token-issue.js +9 -1
  72. package/dist/src/cli/commands/token-issue.js.map +1 -1
  73. package/dist/src/cli/commands/triage.d.ts.map +1 -1
  74. package/dist/src/cli/commands/triage.js +7 -5
  75. package/dist/src/cli/commands/triage.js.map +1 -1
  76. package/dist/src/cli/commands/update.d.ts +80 -0
  77. package/dist/src/cli/commands/update.d.ts.map +1 -0
  78. package/dist/src/cli/commands/update.js +378 -0
  79. package/dist/src/cli/commands/update.js.map +1 -0
  80. package/dist/src/cli/default-config-path.d.ts +15 -0
  81. package/dist/src/cli/default-config-path.d.ts.map +1 -0
  82. package/dist/src/cli/default-config-path.js +27 -0
  83. package/dist/src/cli/default-config-path.js.map +1 -0
  84. package/dist/src/cli/feedback/feedback-config.d.ts +46 -0
  85. package/dist/src/cli/feedback/feedback-config.d.ts.map +1 -1
  86. package/dist/src/cli/feedback/feedback-config.js +130 -4
  87. package/dist/src/cli/feedback/feedback-config.js.map +1 -1
  88. package/dist/src/cli/feedback/feedback-history.d.ts +7 -0
  89. package/dist/src/cli/feedback/feedback-history.d.ts.map +1 -1
  90. package/dist/src/cli/feedback/feedback-history.js +39 -9
  91. package/dist/src/cli/feedback/feedback-history.js.map +1 -1
  92. package/dist/src/cli/feedback/feedback-payload.d.ts +22 -1
  93. package/dist/src/cli/feedback/feedback-payload.d.ts.map +1 -1
  94. package/dist/src/cli/feedback/feedback-payload.js.map +1 -1
  95. package/dist/src/cli/feedback/feedback-relay.d.ts +2 -2
  96. package/dist/src/cli/feedback/feedback-relay.d.ts.map +1 -1
  97. package/dist/src/cli/feedback/feedback-relay.js.map +1 -1
  98. package/dist/src/cli/feedback/invite.d.ts +17 -0
  99. package/dist/src/cli/feedback/invite.d.ts.map +1 -0
  100. package/dist/src/cli/feedback/invite.js +67 -0
  101. package/dist/src/cli/feedback/invite.js.map +1 -0
  102. package/dist/src/cli/feedback/relay-secret-store.d.ts +32 -0
  103. package/dist/src/cli/feedback/relay-secret-store.d.ts.map +1 -0
  104. package/dist/src/cli/feedback/relay-secret-store.js +137 -0
  105. package/dist/src/cli/feedback/relay-secret-store.js.map +1 -0
  106. package/dist/src/cli/http-client.d.ts +93 -1
  107. package/dist/src/cli/http-client.d.ts.map +1 -1
  108. package/dist/src/cli/http-client.js +254 -6
  109. package/dist/src/cli/http-client.js.map +1 -1
  110. package/dist/src/cli/index.d.ts.map +1 -1
  111. package/dist/src/cli/index.js +29 -6
  112. package/dist/src/cli/index.js.map +1 -1
  113. package/dist/src/cli/postinstall-menubar.d.ts +22 -0
  114. package/dist/src/cli/postinstall-menubar.d.ts.map +1 -0
  115. package/dist/src/cli/postinstall-menubar.js +39 -0
  116. package/dist/src/cli/postinstall-menubar.js.map +1 -0
  117. package/dist/src/cli/status/friend-header.d.ts +8 -1
  118. package/dist/src/cli/status/friend-header.d.ts.map +1 -1
  119. package/dist/src/cli/status/friend-header.js +334 -26
  120. package/dist/src/cli/status/friend-header.js.map +1 -1
  121. package/dist/src/cli/ui.d.ts +47 -0
  122. package/dist/src/cli/ui.d.ts.map +1 -0
  123. package/dist/src/cli/ui.js +166 -0
  124. package/dist/src/cli/ui.js.map +1 -0
  125. package/dist/src/config/schema.d.ts +79 -0
  126. package/dist/src/config/schema.d.ts.map +1 -1
  127. package/dist/src/config/schema.js +44 -0
  128. package/dist/src/config/schema.js.map +1 -1
  129. package/dist/src/diagnostics/codex-preflight.d.ts +33 -0
  130. package/dist/src/diagnostics/codex-preflight.d.ts.map +1 -0
  131. package/dist/src/diagnostics/codex-preflight.js +75 -0
  132. package/dist/src/diagnostics/codex-preflight.js.map +1 -0
  133. package/dist/src/diagnostics/doctor.d.ts +106 -0
  134. package/dist/src/diagnostics/doctor.d.ts.map +1 -0
  135. package/dist/src/diagnostics/doctor.js +334 -0
  136. package/dist/src/diagnostics/doctor.js.map +1 -0
  137. package/dist/src/diagnostics/notify.d.ts +90 -0
  138. package/dist/src/diagnostics/notify.d.ts.map +1 -0
  139. package/dist/src/diagnostics/notify.js +177 -0
  140. package/dist/src/diagnostics/notify.js.map +1 -0
  141. package/dist/src/diagnostics/repair-prompt.d.ts +49 -0
  142. package/dist/src/diagnostics/repair-prompt.d.ts.map +1 -0
  143. package/dist/src/diagnostics/repair-prompt.js +223 -0
  144. package/dist/src/diagnostics/repair-prompt.js.map +1 -0
  145. package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts +2 -0
  146. package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts.map +1 -0
  147. package/dist/src/ingestion/inbox-core/conversation-fingerprint.js +27 -0
  148. package/dist/src/ingestion/inbox-core/conversation-fingerprint.js.map +1 -0
  149. package/dist/src/ingestion/inbox-core/conversation-key.d.ts +2 -0
  150. package/dist/src/ingestion/inbox-core/conversation-key.d.ts.map +1 -0
  151. package/dist/src/ingestion/inbox-core/conversation-key.js +31 -0
  152. package/dist/src/ingestion/inbox-core/conversation-key.js.map +1 -0
  153. package/dist/src/ingestion/inbox-core/extensions.d.ts +3 -0
  154. package/dist/src/ingestion/inbox-core/extensions.d.ts.map +1 -0
  155. package/dist/src/ingestion/inbox-core/extensions.js +16 -0
  156. package/dist/src/ingestion/inbox-core/extensions.js.map +1 -0
  157. package/dist/src/ingestion/inbox-core/idempotency.d.ts +2 -0
  158. package/dist/src/ingestion/inbox-core/idempotency.d.ts.map +1 -0
  159. package/dist/src/ingestion/inbox-core/idempotency.js +22 -0
  160. package/dist/src/ingestion/inbox-core/idempotency.js.map +1 -0
  161. package/dist/src/ingestion/inbox-core/index.d.ts +20 -0
  162. package/dist/src/ingestion/inbox-core/index.d.ts.map +1 -0
  163. package/dist/src/ingestion/inbox-core/index.js +20 -0
  164. package/dist/src/ingestion/inbox-core/index.js.map +1 -0
  165. package/dist/src/ingestion/inbox-core/source-detection.d.ts +2 -0
  166. package/dist/src/ingestion/inbox-core/source-detection.d.ts.map +1 -0
  167. package/dist/src/ingestion/inbox-core/source-detection.js +23 -0
  168. package/dist/src/ingestion/inbox-core/source-detection.js.map +1 -0
  169. package/dist/src/ingestion/inbox-core/source-sniffer.d.ts +11 -0
  170. package/dist/src/ingestion/inbox-core/source-sniffer.d.ts.map +1 -0
  171. package/dist/src/ingestion/inbox-core/source-sniffer.js +69 -0
  172. package/dist/src/ingestion/inbox-core/source-sniffer.js.map +1 -0
  173. package/dist/src/ingestion/inbox-core/zip-sniffer.d.ts +70 -0
  174. package/dist/src/ingestion/inbox-core/zip-sniffer.d.ts.map +1 -0
  175. package/dist/src/ingestion/inbox-core/zip-sniffer.js +161 -0
  176. package/dist/src/ingestion/inbox-core/zip-sniffer.js.map +1 -0
  177. package/dist/src/ingestion/inbox-watcher.d.ts.map +1 -1
  178. package/dist/src/ingestion/inbox-watcher.js +34 -50
  179. package/dist/src/ingestion/inbox-watcher.js.map +1 -1
  180. package/dist/src/ingestion/indexer.d.ts +7 -0
  181. package/dist/src/ingestion/indexer.d.ts.map +1 -1
  182. package/dist/src/ingestion/indexer.js +36 -2
  183. package/dist/src/ingestion/indexer.js.map +1 -1
  184. package/dist/src/ingestion/metadata-extraction.d.ts +8 -5
  185. package/dist/src/ingestion/metadata-extraction.d.ts.map +1 -1
  186. package/dist/src/ingestion/metadata-extraction.js +24 -5
  187. package/dist/src/ingestion/metadata-extraction.js.map +1 -1
  188. package/dist/src/ingestion/skip-quarantine.d.ts +10 -0
  189. package/dist/src/ingestion/skip-quarantine.d.ts.map +1 -0
  190. package/dist/src/ingestion/skip-quarantine.js +35 -0
  191. package/dist/src/ingestion/skip-quarantine.js.map +1 -0
  192. package/dist/src/jobs/handlers/compact.d.ts.map +1 -1
  193. package/dist/src/jobs/handlers/compact.js +30 -4
  194. package/dist/src/jobs/handlers/compact.js.map +1 -1
  195. package/dist/src/jobs/handlers/dedupe-conversations.d.ts +134 -0
  196. package/dist/src/jobs/handlers/dedupe-conversations.d.ts.map +1 -0
  197. package/dist/src/jobs/handlers/dedupe-conversations.js +371 -0
  198. package/dist/src/jobs/handlers/dedupe-conversations.js.map +1 -0
  199. package/dist/src/jobs/handlers/ingest.d.ts.map +1 -1
  200. package/dist/src/jobs/handlers/ingest.js +295 -41
  201. package/dist/src/jobs/handlers/ingest.js.map +1 -1
  202. package/dist/src/jobs/handlers/reconcile.d.ts +28 -0
  203. package/dist/src/jobs/handlers/reconcile.d.ts.map +1 -1
  204. package/dist/src/jobs/handlers/reconcile.js +145 -19
  205. package/dist/src/jobs/handlers/reconcile.js.map +1 -1
  206. package/dist/src/jobs/handlers/reindex.d.ts.map +1 -1
  207. package/dist/src/jobs/handlers/reindex.js +13 -2
  208. package/dist/src/jobs/handlers/reindex.js.map +1 -1
  209. package/dist/src/jobs/handlers/save.d.ts.map +1 -1
  210. package/dist/src/jobs/handlers/save.js +57 -3
  211. package/dist/src/jobs/handlers/save.js.map +1 -1
  212. package/dist/src/jobs/queue.d.ts +51 -1
  213. package/dist/src/jobs/queue.d.ts.map +1 -1
  214. package/dist/src/jobs/queue.js +466 -26
  215. package/dist/src/jobs/queue.js.map +1 -1
  216. package/dist/src/jobs/worker-entry.d.ts.map +1 -1
  217. package/dist/src/jobs/worker-entry.js +35 -7
  218. package/dist/src/jobs/worker-entry.js.map +1 -1
  219. package/dist/src/jobs/worker-process.d.ts +11 -0
  220. package/dist/src/jobs/worker-process.d.ts.map +1 -1
  221. package/dist/src/jobs/worker-process.js +37 -4
  222. package/dist/src/jobs/worker-process.js.map +1 -1
  223. package/dist/src/main.js +199 -46
  224. package/dist/src/main.js.map +1 -1
  225. package/dist/src/mcp/errors.d.ts.map +1 -1
  226. package/dist/src/mcp/errors.js +20 -1
  227. package/dist/src/mcp/errors.js.map +1 -1
  228. package/dist/src/mcp/server.d.ts.map +1 -1
  229. package/dist/src/mcp/server.js +43 -3
  230. package/dist/src/mcp/server.js.map +1 -1
  231. package/dist/src/mcp/tools/context-pack.d.ts.map +1 -1
  232. package/dist/src/mcp/tools/context-pack.js +164 -23
  233. package/dist/src/mcp/tools/context-pack.js.map +1 -1
  234. package/dist/src/mcp/tools/search.d.ts +6 -2
  235. package/dist/src/mcp/tools/search.d.ts.map +1 -1
  236. package/dist/src/mcp/tools/search.js +35 -4
  237. package/dist/src/mcp/tools/search.js.map +1 -1
  238. package/dist/src/observability/embedding-events.d.ts +52 -0
  239. package/dist/src/observability/embedding-events.d.ts.map +1 -0
  240. package/dist/src/observability/embedding-events.js +149 -0
  241. package/dist/src/observability/embedding-events.js.map +1 -0
  242. package/dist/src/observability/index-events.d.ts +70 -0
  243. package/dist/src/observability/index-events.d.ts.map +1 -0
  244. package/dist/src/observability/index-events.js +148 -0
  245. package/dist/src/observability/index-events.js.map +1 -0
  246. package/dist/src/observability/onboarding-metric.d.ts +131 -0
  247. package/dist/src/observability/onboarding-metric.d.ts.map +1 -0
  248. package/dist/src/observability/onboarding-metric.js +351 -0
  249. package/dist/src/observability/onboarding-metric.js.map +1 -0
  250. package/dist/src/observability/tool-usage-stats.d.ts +77 -4
  251. package/dist/src/observability/tool-usage-stats.d.ts.map +1 -1
  252. package/dist/src/observability/tool-usage-stats.js +112 -32
  253. package/dist/src/observability/tool-usage-stats.js.map +1 -1
  254. package/dist/src/observability/tool-usage.d.ts +100 -7
  255. package/dist/src/observability/tool-usage.d.ts.map +1 -1
  256. package/dist/src/observability/tool-usage.js +196 -33
  257. package/dist/src/observability/tool-usage.js.map +1 -1
  258. package/dist/src/observability/version-check.d.ts +71 -0
  259. package/dist/src/observability/version-check.d.ts.map +1 -0
  260. package/dist/src/observability/version-check.js +198 -0
  261. package/dist/src/observability/version-check.js.map +1 -0
  262. package/dist/src/providers/basic-metadata-extraction.d.ts +60 -0
  263. package/dist/src/providers/basic-metadata-extraction.d.ts.map +1 -0
  264. package/dist/src/providers/basic-metadata-extraction.js +114 -0
  265. package/dist/src/providers/basic-metadata-extraction.js.map +1 -0
  266. package/dist/src/providers/codex-cli-metadata-extraction.d.ts +1 -0
  267. package/dist/src/providers/codex-cli-metadata-extraction.d.ts.map +1 -1
  268. package/dist/src/providers/codex-cli-metadata-extraction.js +6 -2
  269. package/dist/src/providers/codex-cli-metadata-extraction.js.map +1 -1
  270. package/dist/src/providers/codex-cli-model.d.ts +61 -0
  271. package/dist/src/providers/codex-cli-model.d.ts.map +1 -0
  272. package/dist/src/providers/codex-cli-model.js +194 -0
  273. package/dist/src/providers/codex-cli-model.js.map +1 -0
  274. package/dist/src/providers/codex-cli-runner.d.ts +39 -0
  275. package/dist/src/providers/codex-cli-runner.d.ts.map +1 -1
  276. package/dist/src/providers/codex-cli-runner.js +234 -48
  277. package/dist/src/providers/codex-cli-runner.js.map +1 -1
  278. package/dist/src/providers/conversation-generation.d.ts.map +1 -1
  279. package/dist/src/providers/conversation-generation.js +43 -6
  280. package/dist/src/providers/conversation-generation.js.map +1 -1
  281. package/dist/src/providers/ollama-embed.d.ts +2 -1
  282. package/dist/src/providers/ollama-embed.d.ts.map +1 -1
  283. package/dist/src/providers/ollama-embed.js +1 -0
  284. package/dist/src/providers/ollama-embed.js.map +1 -1
  285. package/dist/src/providers/openai-metadata-extraction.d.ts +3 -3
  286. package/dist/src/providers/openai-metadata-extraction.d.ts.map +1 -1
  287. package/dist/src/providers/openai-metadata-extraction.js +18 -3
  288. package/dist/src/providers/openai-metadata-extraction.js.map +1 -1
  289. package/dist/src/providers/placeholder-embed.d.ts +56 -0
  290. package/dist/src/providers/placeholder-embed.d.ts.map +1 -0
  291. package/dist/src/providers/placeholder-embed.js +64 -0
  292. package/dist/src/providers/placeholder-embed.js.map +1 -0
  293. package/dist/src/providers/stub.d.ts +2 -0
  294. package/dist/src/providers/stub.d.ts.map +1 -1
  295. package/dist/src/providers/stub.js +2 -0
  296. package/dist/src/providers/stub.js.map +1 -1
  297. package/dist/src/providers/types.d.ts +11 -0
  298. package/dist/src/providers/types.d.ts.map +1 -1
  299. package/dist/src/providers/voyage.d.ts +2 -1
  300. package/dist/src/providers/voyage.d.ts.map +1 -1
  301. package/dist/src/providers/voyage.js +1 -0
  302. package/dist/src/providers/voyage.js.map +1 -1
  303. package/dist/src/retrieval/compact.d.ts +116 -2
  304. package/dist/src/retrieval/compact.d.ts.map +1 -1
  305. package/dist/src/retrieval/compact.js +158 -5
  306. package/dist/src/retrieval/compact.js.map +1 -1
  307. package/dist/src/retrieval/context-pack.d.ts +114 -0
  308. package/dist/src/retrieval/context-pack.d.ts.map +1 -1
  309. package/dist/src/retrieval/context-pack.js +292 -8
  310. package/dist/src/retrieval/context-pack.js.map +1 -1
  311. package/dist/src/retrieval/current-truth.d.ts +360 -0
  312. package/dist/src/retrieval/current-truth.d.ts.map +1 -0
  313. package/dist/src/retrieval/current-truth.js +766 -0
  314. package/dist/src/retrieval/current-truth.js.map +1 -0
  315. package/dist/src/retrieval/git-state.d.ts +53 -0
  316. package/dist/src/retrieval/git-state.d.ts.map +1 -0
  317. package/dist/src/retrieval/git-state.js +174 -0
  318. package/dist/src/retrieval/git-state.js.map +1 -0
  319. package/dist/src/retrieval/lexical.d.ts.map +1 -1
  320. package/dist/src/retrieval/lexical.js +19 -3
  321. package/dist/src/retrieval/lexical.js.map +1 -1
  322. package/dist/src/retrieval/locator-boost.d.ts +37 -0
  323. package/dist/src/retrieval/locator-boost.d.ts.map +1 -0
  324. package/dist/src/retrieval/locator-boost.js +129 -0
  325. package/dist/src/retrieval/locator-boost.js.map +1 -0
  326. package/dist/src/retrieval/report-demotion.d.ts +46 -0
  327. package/dist/src/retrieval/report-demotion.d.ts.map +1 -0
  328. package/dist/src/retrieval/report-demotion.js +169 -0
  329. package/dist/src/retrieval/report-demotion.js.map +1 -0
  330. package/dist/src/retrieval/vector.d.ts.map +1 -1
  331. package/dist/src/retrieval/vector.js +11 -2
  332. package/dist/src/retrieval/vector.js.map +1 -1
  333. package/dist/src/server/app.d.ts.map +1 -1
  334. package/dist/src/server/app.js +92 -11
  335. package/dist/src/server/app.js.map +1 -1
  336. package/dist/src/server/routes/compact.d.ts.map +1 -1
  337. package/dist/src/server/routes/compact.js +4 -1
  338. package/dist/src/server/routes/compact.js.map +1 -1
  339. package/dist/src/server/routes/context.d.ts +1 -1
  340. package/dist/src/server/routes/context.d.ts.map +1 -1
  341. package/dist/src/server/routes/context.js +2 -1
  342. package/dist/src/server/routes/context.js.map +1 -1
  343. package/dist/src/server/routes/conversations-search.d.ts.map +1 -1
  344. package/dist/src/server/routes/conversations-search.js +28 -3
  345. package/dist/src/server/routes/conversations-search.js.map +1 -1
  346. package/dist/src/server/routes/enqueue.d.ts +11 -0
  347. package/dist/src/server/routes/enqueue.d.ts.map +1 -0
  348. package/dist/src/server/routes/enqueue.js +17 -0
  349. package/dist/src/server/routes/enqueue.js.map +1 -0
  350. package/dist/src/server/routes/friend-status.d.ts +339 -3
  351. package/dist/src/server/routes/friend-status.d.ts.map +1 -1
  352. package/dist/src/server/routes/friend-status.js +447 -13
  353. package/dist/src/server/routes/friend-status.js.map +1 -1
  354. package/dist/src/server/routes/ingest.d.ts.map +1 -1
  355. package/dist/src/server/routes/ingest.js +5 -2
  356. package/dist/src/server/routes/ingest.js.map +1 -1
  357. package/dist/src/server/routes/mcp-usage.d.ts +5 -4
  358. package/dist/src/server/routes/mcp-usage.d.ts.map +1 -1
  359. package/dist/src/server/routes/mcp-usage.js.map +1 -1
  360. package/dist/src/server/routes/reconcile.d.ts.map +1 -1
  361. package/dist/src/server/routes/reconcile.js +20 -1
  362. package/dist/src/server/routes/reconcile.js.map +1 -1
  363. package/dist/src/server/routes/reindex.d.ts.map +1 -1
  364. package/dist/src/server/routes/reindex.js +4 -1
  365. package/dist/src/server/routes/reindex.js.map +1 -1
  366. package/dist/src/server/routes/save.d.ts.map +1 -1
  367. package/dist/src/server/routes/save.js +4 -1
  368. package/dist/src/server/routes/save.js.map +1 -1
  369. package/dist/src/server/routes/search.d.ts +1 -1
  370. package/dist/src/server/routes/search.d.ts.map +1 -1
  371. package/dist/src/server/routes/search.js +253 -29
  372. package/dist/src/server/routes/search.js.map +1 -1
  373. package/dist/src/server/routes/triage.d.ts.map +1 -1
  374. package/dist/src/server/routes/triage.js +4 -1
  375. package/dist/src/server/routes/triage.js.map +1 -1
  376. package/dist/src/storage/rebuild.d.ts +35 -1
  377. package/dist/src/storage/rebuild.d.ts.map +1 -1
  378. package/dist/src/storage/rebuild.js +288 -64
  379. package/dist/src/storage/rebuild.js.map +1 -1
  380. package/dist/src/storage/tables.d.ts +29 -0
  381. package/dist/src/storage/tables.d.ts.map +1 -1
  382. package/dist/src/storage/tables.js +32 -1
  383. package/dist/src/storage/tables.js.map +1 -1
  384. package/operator/swiftbar/render-menu.py +524 -0
  385. package/operator/swiftbar/rift.10s.sh +176 -0
  386. package/package.json +9 -3
@@ -0,0 +1,524 @@
1
+ #!/usr/bin/env python3
2
+ """Render the friend-facing Rift SwiftBar menu.
3
+
4
+ Pure formatting: reads structured state from the environment and prints a
5
+ SwiftBar menu to stdout. No network, no clock except `now`, no CLI calls
6
+ of its own. The shell wrapper (`rift.10s.sh`) gathers the inputs and the
7
+ repair prompt always comes from `rift doctor` — this script never invents
8
+ diagnosis or hardcodes a prompt.
9
+
10
+ The menu answers one question by default: "Is Rift working?" Operator
11
+ detail (MCP counters, launchd labels, Node versions, commits) is
12
+ deliberately absent.
13
+
14
+ Inputs (all via env, all optional — missing inputs degrade gracefully):
15
+ RIFT_DOCTOR_JSON `rift doctor --json` output (the verdict + one fix).
16
+ RIFT_STATUS_JSON `rift status --json` output (capture times, clients).
17
+ RIFT_MCP_JSON `/stats/mcp-usage` body (Memory stats: today/month/all).
18
+ RIFT_HEALTH "up" | "down" — coarse /health probe, used only when
19
+ the doctor report is unavailable (CLI not resolvable).
20
+ RIFT_NODE_BIN absolute node binary, for menu actions.
21
+ RIFT_JS absolute path to the rift JS entrypoint.
22
+ RIFT_BIN absolute rift launcher, fallback when RIFT_JS is unset.
23
+ RIFT_LOG_DIR logs folder, for "Open logs".
24
+ RIFT_DATA_DIR data folder, for "Open data folder".
25
+ RIFT_ABOUT_URL About Rift URL (default https://getrift.dev/about).
26
+ RIFT_NOW_MS injected clock for tests (epoch ms); defaults to now.
27
+ """
28
+ import json
29
+ import os
30
+ import sys
31
+
32
+ CLIENT_LABELS = {
33
+ "claude-desktop": "Claude Desktop",
34
+ "claude-code": "Claude Code",
35
+ "cursor": "Cursor",
36
+ "codex": "Codex",
37
+ }
38
+
39
+ ABOUT_DEFAULT = "https://getrift.dev/about"
40
+
41
+
42
+ def env(key, default=""):
43
+ return os.environ.get(key, default)
44
+
45
+
46
+ def parse_json(raw):
47
+ raw = (raw or "").strip()
48
+ if not raw:
49
+ return None
50
+ try:
51
+ return json.loads(raw)
52
+ except Exception:
53
+ return None
54
+
55
+
56
+ def now_ms():
57
+ raw = env("RIFT_NOW_MS")
58
+ if raw:
59
+ try:
60
+ return int(raw)
61
+ except ValueError:
62
+ pass
63
+ import time
64
+
65
+ return int(time.time() * 1000)
66
+
67
+
68
+ def parse_iso_ms(iso):
69
+ """Parse an ISO-8601 timestamp to epoch ms, or None."""
70
+ if not iso:
71
+ return None
72
+ s = iso.strip()
73
+ if s.endswith("Z"):
74
+ s = s[:-1] + "+00:00"
75
+ try:
76
+ from datetime import datetime
77
+
78
+ return int(datetime.fromisoformat(s).timestamp() * 1000)
79
+ except Exception:
80
+ return None
81
+
82
+
83
+ def fmt_duration(seconds):
84
+ seconds = int(max(0, seconds))
85
+ if seconds < 60:
86
+ return f"{seconds}s"
87
+ minutes = seconds // 60
88
+ if minutes < 60:
89
+ return f"{minutes}m"
90
+ hours = minutes // 60
91
+ rem_min = minutes % 60
92
+ if hours < 24:
93
+ return f"{hours}h {rem_min}m" if rem_min else f"{hours}h"
94
+ days = hours // 24
95
+ rem_hours = hours % 24
96
+ return f"{days}d {rem_hours}h" if rem_hours else f"{days}d"
97
+
98
+
99
+ def age_ago(iso, now):
100
+ ms = parse_iso_ms(iso)
101
+ if ms is None:
102
+ return None
103
+ return fmt_duration((now - ms) / 1000) + " ago"
104
+
105
+
106
+ def time_until(iso, now):
107
+ ms = parse_iso_ms(iso)
108
+ if ms is None:
109
+ return None
110
+ delta = (ms - now) / 1000
111
+ if delta <= 0:
112
+ return "due now"
113
+ return "in " + fmt_duration(delta)
114
+
115
+
116
+ def fmt_tokens(n):
117
+ if n >= 1_000_000:
118
+ return "~{:.1f}M context tokens".format(n / 1_000_000).replace(".0M", "M")
119
+ if n >= 1_000:
120
+ return "~{}k context tokens".format(round(n / 1_000))
121
+ return f"~{n} context tokens"
122
+
123
+
124
+ # Friendly labels for the three Memory windows, mapped to their keys in the
125
+ # /stats/mcp-usage payload. Order is the render order. We deliberately skip
126
+ # `week` — three periods stay glanceable; a fourth turns the menu into a
127
+ # dashboard. A period absent from the payload is simply not rendered (we never
128
+ # fabricate a number the backend didn't report), so an older daemon that only
129
+ # emits `today` still renders cleanly.
130
+ MEMORY_PERIODS = (("Today", "today"), ("This month", "month"), ("All time", "all_time"))
131
+
132
+
133
+ def memory_window_row(label, hits, tokens):
134
+ """One '<Label>: N useful recalls · ~tokens' row, or a calm zero state."""
135
+ if hits <= 0:
136
+ return f"{label}: nothing yet"
137
+ parts = [f"{hits:,} useful recall" + ("" if hits == 1 else "s")]
138
+ if isinstance(tokens, int) and tokens > 0:
139
+ parts.append(fmt_tokens(tokens))
140
+ return f"{label}: " + " · ".join(parts)
141
+
142
+
143
+ def memory_section(mcp):
144
+ """Friendly 'Memory' section lines (header + period rows), or [].
145
+
146
+ Reads today / month / all-time recall + context-token counts straight
147
+ from /stats/mcp-usage and prints them in plain language. Returns [] when
148
+ the stats payload is missing or carries none of the expected windows, so
149
+ the menu degrades gracefully (section omitted, Rift still healthy) rather
150
+ than showing an error. A history with no recalls anywhere collapses to a
151
+ single calm line instead of three empty rows.
152
+ """
153
+ if not isinstance(mcp, dict):
154
+ return []
155
+ present = []
156
+ any_activity = False
157
+ for label, key in MEMORY_PERIODS:
158
+ window = mcp.get(key)
159
+ if not isinstance(window, dict):
160
+ continue
161
+ hits = window.get("context_hits")
162
+ if not isinstance(hits, int):
163
+ continue
164
+ tokens = window.get("context_tokens_delivered_estimate")
165
+ present.append((label, hits, tokens))
166
+ if hits > 0:
167
+ any_activity = True
168
+ if not present:
169
+ return []
170
+ # Collapse to a single calm line ONLY when every period is present and all
171
+ # are zero — that genuinely is "nothing recalled, ever". A partial payload
172
+ # (e.g. a legacy daemon emitting only `today`) must not claim all-time
173
+ # emptiness it can't see, so it falls through to the per-period rows
174
+ # ("Today: nothing yet") instead.
175
+ if not any_activity and len(present) == len(MEMORY_PERIODS):
176
+ return ["Memory", "Nothing recalled yet — Rift learns as you use it"]
177
+ return ["Memory"] + [
178
+ memory_window_row(label, hits, tokens) for label, hits, tokens in present
179
+ ]
180
+
181
+
182
+ # --- Menu-action line builders -------------------------------------------
183
+ #
184
+ # SwiftBar runs an item's `shell=` command with a stripped PATH, so every
185
+ # action uses absolute binaries. rift subcommands run through node + the
186
+ # resolved JS entrypoint (the npm `rift` symlink's `#!/usr/bin/env node`
187
+ # shebang can't resolve `node` under a stripped PATH); when only RIFT_BIN
188
+ # resolved we fall back to it directly.
189
+
190
+
191
+ def sb_param(value):
192
+ """Quote a SwiftBar parameter value so paths with spaces or SwiftBar
193
+ separators survive parsing.
194
+
195
+ SwiftBar splits an item's parameters on whitespace and uses `|` to
196
+ separate the title from its params, so any value carrying a space, tab,
197
+ `|`, or a quote must be wrapped to stay one token. We only quote when
198
+ needed, leaving simple values (and flag args like `--target=claude`)
199
+ untouched.
200
+ """
201
+ s = str(value)
202
+ if s == "" or any(ch in s for ch in " \t|\"'"):
203
+ return '"' + s.replace('"', '\\"') + '"'
204
+ return s
205
+
206
+
207
+ def rift_runnable():
208
+ node = env("RIFT_NODE_BIN")
209
+ js = env("RIFT_JS")
210
+ if node and js:
211
+ return [node, js]
212
+ rift = env("RIFT_BIN")
213
+ if rift:
214
+ return [rift]
215
+ return None
216
+
217
+
218
+ def rift_action(label, args, terminal, refresh=False):
219
+ """A menu line that runs a rift subcommand, or None if rift is unresolved."""
220
+ base = rift_runnable()
221
+ if base is None:
222
+ return None
223
+ parts = base + args
224
+ line = f"{label} | shell={sb_param(parts[0])}"
225
+ for i, p in enumerate(parts[1:], start=1):
226
+ line += f" param{i}={sb_param(p)}"
227
+ line += f" terminal={'true' if terminal else 'false'}"
228
+ if refresh:
229
+ line += " refresh=true"
230
+ return line
231
+
232
+
233
+ def open_action(label, path):
234
+ if not path:
235
+ return None
236
+ return f"{label} | shell=/usr/bin/open param1={sb_param(path)} terminal=false"
237
+
238
+
239
+ def about_line():
240
+ return f"About Rift | href={sb_param(env('RIFT_ABOUT_URL', ABOUT_DEFAULT))}"
241
+
242
+
243
+ # --- Verdict --------------------------------------------------------------
244
+
245
+
246
+ def verdict_of(doctor, health):
247
+ """healthy | warning | broken | unknown."""
248
+ if isinstance(doctor, dict) and "healthy" in doctor:
249
+ if not doctor.get("healthy"):
250
+ return "broken"
251
+ issues = doctor.get("issues") or []
252
+ if any(i.get("severity") == "warning" for i in issues):
253
+ return "warning"
254
+ return "healthy"
255
+ # No diagnosis available — fall back to the coarse health probe.
256
+ if health == "up":
257
+ return "healthy"
258
+ if health == "down":
259
+ return "broken"
260
+ return "unknown"
261
+
262
+
263
+ def has_doctor_report(doctor):
264
+ """True only when RIFT_DOCTOR_JSON parsed into a real doctor report.
265
+
266
+ Doctor-powered menu actions (`Copy fix prompt …`, `Troubleshooting`) are
267
+ gated on this, never on the rift binary merely resolving: if `rift doctor
268
+ --json` emitted nothing or failed, re-running doctor from the menu would
269
+ fail the same way, so we hide those actions and show friend-safe text.
270
+ """
271
+ return isinstance(doctor, dict) and "healthy" in doctor
272
+
273
+
274
+ TITLES = {
275
+ "healthy": "Rift | sfimage=circle.fill sfcolor=systemGreen",
276
+ "warning": "Rift | sfimage=exclamationmark.circle.fill sfcolor=systemYellow",
277
+ "broken": "Rift | sfimage=exclamationmark.triangle.fill sfcolor=systemRed",
278
+ "unknown": "Rift | sfimage=questionmark.circle.fill sfcolor=systemGray",
279
+ }
280
+
281
+
282
+ # --- Section renderers ----------------------------------------------------
283
+
284
+
285
+ def render_capture_rows(friend, now):
286
+ rows = []
287
+ capture = (friend or {}).get("capture") if isinstance(friend, dict) else None
288
+ if not isinstance(capture, dict):
289
+ return rows
290
+ last = age_ago(capture.get("last_run_at"), now)
291
+ rows.append(f"Last capture: {last}" if last else "Last capture: none yet")
292
+ nxt = time_until(capture.get("next_run_at"), now)
293
+ if nxt:
294
+ rows.append(f"Next capture: {nxt}")
295
+ return rows
296
+
297
+
298
+ def render_client_rows(status, doctor):
299
+ """Connected-client rows + connect actions for missing ones."""
300
+ rows = []
301
+ clients = status.get("mcp_clients") if isinstance(status, dict) else None
302
+ if not isinstance(clients, list):
303
+ return rows
304
+ connected = [c for c in clients if c.get("installed")]
305
+ missing = [c for c in clients if not c.get("installed")]
306
+ for c in connected:
307
+ label = CLIENT_LABELS.get(c.get("client"), c.get("client"))
308
+ rows.append(f"{label} connected | sfimage=checkmark.circle sfcolor=systemGreen")
309
+ if not connected:
310
+ rows.append("No AI tools connected yet")
311
+ for c in missing:
312
+ cid = c.get("client")
313
+ label = CLIENT_LABELS.get(cid, cid)
314
+ action = rift_action(
315
+ f"Connect {label}", ["mcp", "install", f"--client={cid}"], terminal=True
316
+ )
317
+ rows.append(action if action else f"{label} not connected")
318
+ return rows
319
+
320
+
321
+ def has_voyage_issue(doctor):
322
+ if not isinstance(doctor, dict):
323
+ return False
324
+ return any(
325
+ i.get("kind") in ("voyage_key_missing", "voyage_embed_errors", "index_write_errors")
326
+ for i in (doctor.get("issues") or [])
327
+ )
328
+
329
+
330
+ # Warning kinds whose repair a non-technical user may want an AI to walk them
331
+ # through. An explicit allowlist, not a blacklist: most warnings either carry
332
+ # their own one-click action (update_available, mcp_not_installed) or have no
333
+ # action at all (capture_daemon_stale's nextAction is literally "Nothing to
334
+ # do") — offering "Copy fix prompt" for those would promise a fix that isn't
335
+ # needed. Add a kind here only once `rift doctor --copy-prompt` produces a
336
+ # genuinely useful repair prompt for it.
337
+ REPAIRABLE_WARNINGS = {"capture_quarantined"}
338
+
339
+
340
+ def has_repairable_warning(doctor):
341
+ """A warning whose repair the user may want an AI to walk them through."""
342
+ if not isinstance(doctor, dict):
343
+ return False
344
+ return any(
345
+ i.get("severity") == "warning" and i.get("kind") in REPAIRABLE_WARNINGS
346
+ for i in (doctor.get("issues") or [])
347
+ )
348
+
349
+
350
+ def copy_prompt_actions():
351
+ """The 'Copy fix prompt for Claude/Codex' menu rows (no embedded prompt)."""
352
+ out = []
353
+ for label, target in (("Claude", "claude"), ("Codex", "codex")):
354
+ action = rift_action(
355
+ f"Copy fix prompt for {label}",
356
+ ["doctor", "--copy-prompt", f"--target={target}"],
357
+ terminal=False,
358
+ )
359
+ if action:
360
+ out.append(action)
361
+ return out
362
+
363
+
364
+ def render_advisories(doctor):
365
+ """Warning-level issues that aren't already shown as client rows."""
366
+ rows = []
367
+ if not isinstance(doctor, dict):
368
+ return rows
369
+ for issue in doctor.get("issues") or []:
370
+ if issue.get("severity") != "warning":
371
+ continue
372
+ if issue.get("kind") == "mcp_not_installed":
373
+ continue # already surfaced as a "Connect …" client row
374
+ rows.append(f"{issue.get('title', 'Heads up')}")
375
+ action = issue.get("nextAction")
376
+ if action:
377
+ rows.append(f"--{action}")
378
+ if issue.get("kind") == "update_available":
379
+ menu_action = rift_action("Update Rift", ["update"], terminal=True, refresh=True)
380
+ if menu_action:
381
+ rows.append(menu_action)
382
+ return rows
383
+
384
+
385
+ def render_footer_actions(broken, doctor_ok, offer_copy_prompt=False):
386
+ """Common Rift-owned actions. Order differs by state per the plan.
387
+
388
+ `doctor_ok` gates the doctor-powered actions (`Copy fix prompt …` and
389
+ `Troubleshooting`): they appear only when the CLI actually produced a
390
+ report, never merely because the rift binary resolved.
391
+
392
+ `offer_copy_prompt` extends the AI repair prompt to the non-broken
393
+ (warning) state: a warning with a repair action (e.g. large sessions
394
+ parked) gets the same one-click Claude/Codex help a broken state does.
395
+ """
396
+ lines = ["---"]
397
+ if broken:
398
+ if doctor_ok:
399
+ lines += copy_prompt_actions()
400
+ logs = open_action("Open logs", env("RIFT_LOG_DIR"))
401
+ if logs:
402
+ lines.append(logs)
403
+ else:
404
+ if doctor_ok and offer_copy_prompt:
405
+ lines += copy_prompt_actions()
406
+ cap = rift_action("Capture now", ["capture"], terminal=False, refresh=True)
407
+ if cap:
408
+ lines.append(cap)
409
+ st = rift_action("Open status", ["status"], terminal=True)
410
+ if st:
411
+ lines.append(st)
412
+ data = open_action("Open data folder", env("RIFT_DATA_DIR"))
413
+ if data:
414
+ lines.append(data)
415
+ lines.append("---")
416
+ lines.append(about_line())
417
+ if doctor_ok:
418
+ trouble = rift_action("Troubleshooting", ["doctor"], terminal=True)
419
+ if trouble:
420
+ lines.append(trouble)
421
+ lines.append("---")
422
+ lines.append("Refresh | refresh=true")
423
+ return lines
424
+
425
+
426
+ # --- Top-level render -----------------------------------------------------
427
+
428
+
429
+ def render_attention(doctor, health, lines):
430
+ """Render the 'needs attention' / indeterminate states.
431
+
432
+ Friend-safe by contract: the concrete repair always comes from the
433
+ doctor report (and the redacted prompt from `rift doctor --copy-prompt`,
434
+ wired into the footer). Doctor-powered actions are gated on a real report
435
+ existing — not on the rift binary resolving — so a present-but-broken CLI
436
+ never offers a fix it can't produce. We never emit launchd labels,
437
+ kickstart commands, or other operator internals.
438
+ """
439
+ doctor_ok = has_doctor_report(doctor)
440
+ broken_issues = []
441
+ if isinstance(doctor, dict):
442
+ broken_issues = [
443
+ i for i in (doctor.get("issues") or []) if i.get("severity") == "broken"
444
+ ]
445
+
446
+ if broken_issues:
447
+ # The doctor diagnosed something concrete — name it and surface the
448
+ # single repair action it chose.
449
+ lines.append("Rift needs attention")
450
+ lines.append("---")
451
+ for issue in broken_issues:
452
+ lines.append(f"✗ {issue.get('title', 'Something is wrong')}")
453
+ primary = doctor.get("primary") or broken_issues[0]
454
+ if primary.get("nextAction"):
455
+ lines.append("---")
456
+ lines.append(f"Fix: {primary['nextAction']}")
457
+ elif health == "down":
458
+ # Background engine isn't answering. Stay friend-safe: no launchctl,
459
+ # no daemon label. With no doctor report we also can't build a repair
460
+ # prompt, so say so plainly rather than offer dead doctor actions.
461
+ lines.append("Rift needs attention")
462
+ lines.append("---")
463
+ lines.append("Rift's background engine is not responding")
464
+ if not doctor_ok:
465
+ lines.append("Rift couldn't run a self-check — reopen Rift, then check below")
466
+ else:
467
+ # verdict unknown: couldn't even confirm health.
468
+ lines.append("Rift status unavailable")
469
+ lines.append("---")
470
+ lines.append("Rift couldn't reach its own tools to check")
471
+
472
+ lines += render_footer_actions(broken=True, doctor_ok=doctor_ok)
473
+ return lines
474
+
475
+
476
+ def render(doctor, status, mcp, health, now):
477
+ friend = status.get("friend") if isinstance(status, dict) else None
478
+ verdict = verdict_of(doctor, health)
479
+ lines = [TITLES[verdict], "---"]
480
+
481
+ if verdict in ("broken", "unknown"):
482
+ return render_attention(doctor, health, lines)
483
+
484
+ # healthy or warning
485
+ lines.append("Rift is working")
486
+ lines += render_capture_rows(friend, now)
487
+
488
+ mem_rows = memory_section(mcp)
489
+ if mem_rows:
490
+ lines.append("---")
491
+ lines += mem_rows
492
+
493
+ client_rows = render_client_rows(status, doctor)
494
+ if client_rows:
495
+ lines.append("---")
496
+ lines += client_rows
497
+ if not has_voyage_issue(doctor):
498
+ lines.append("Search index healthy")
499
+
500
+ advisories = render_advisories(doctor)
501
+ if advisories:
502
+ lines.append("---")
503
+ lines.append("Heads up:")
504
+ lines += advisories
505
+
506
+ lines += render_footer_actions(
507
+ broken=False,
508
+ doctor_ok=has_doctor_report(doctor),
509
+ offer_copy_prompt=has_repairable_warning(doctor),
510
+ )
511
+ return lines
512
+
513
+
514
+ def main():
515
+ doctor = parse_json(env("RIFT_DOCTOR_JSON"))
516
+ status = parse_json(env("RIFT_STATUS_JSON"))
517
+ mcp = parse_json(env("RIFT_MCP_JSON"))
518
+ health = env("RIFT_HEALTH")
519
+ for line in render(doctor, status, mcp, health, now_ms()):
520
+ print(line)
521
+
522
+
523
+ if __name__ == "__main__":
524
+ main()
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # SwiftBar plugin — Rift friend-facing heartbeat.
4
+ #
5
+ # Answers one question at a glance: "Is Rift working?" Operator telemetry
6
+ # (MCP counters, launchd labels, Node versions, commits) is intentionally
7
+ # absent — run `rift status` / `rift doctor` for that. The verdict and the
8
+ # one repair action come from `rift doctor`; the repair PROMPT is generated
9
+ # by `rift doctor --copy-prompt` (never hardcoded here), so redaction and
10
+ # diagnosis stay centralized in the CLI.
11
+ #
12
+ # Install:
13
+ # rift menubar install
14
+ #
15
+ # Overrides:
16
+ # RIFT_BASE_URL default http://127.0.0.1:3577
17
+ # RIFT_LOG_DIR default ~/Library/Application Support/Rift/logs
18
+ # RIFT_DATA_DIR default ~/Library/Application Support/Rift/data
19
+ # RIFT_ABOUT_URL default https://getrift.dev/about
20
+ #
21
+ # Reads live daemon state on every tick. Never caches.
22
+
23
+ set -u
24
+
25
+ BASE_URL="${RIFT_BASE_URL:-http://127.0.0.1:3577}"
26
+ ABOUT_URL="${RIFT_ABOUT_URL:-https://getrift.dev/about}"
27
+
28
+ # Resolve script path even when invoked via symlink, so the plugin can
29
+ # sit in SwiftBar's plugins dir and still open the repo's logs/data dirs
30
+ # and find render-menu.py next to itself.
31
+ SOURCE="${BASH_SOURCE[0]}"
32
+ while [[ -L "$SOURCE" ]]; do
33
+ DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
34
+ SOURCE="$(readlink "$SOURCE")"
35
+ [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
36
+ done
37
+ SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
38
+ RIFT_HOME_DEFAULT="${HOME}/Library/Application Support/Rift"
39
+
40
+ # Packaged (.pkg) install layout — bundled Node + app tree under Rift home,
41
+ # a bash CLI shim at ~/.rift/bin/rift, daemon logs under ~/Library/Logs/Rift.
42
+ # Detect it so the menu bar works on a BARE Mac with NO env injection: the
43
+ # `rift menubar install` env (RIFT_BIN/NODE_BIN) does NOT persist into the
44
+ # installed plugin, so the plugin must self-resolve these paths every tick.
45
+ PACKAGED_NODE="${RIFT_HOME_DEFAULT}/node/bin/node"
46
+ PACKAGED_JS="${RIFT_HOME_DEFAULT}/app/dist/src/cli/index.js"
47
+ PACKAGED_SHIM="${HOME}/.rift/bin/rift"
48
+ if [[ -f "$PACKAGED_JS" && -x "$PACKAGED_NODE" ]]; then
49
+ PACKAGED_LOG_DIR="${HOME}/Library/Logs/Rift"
50
+ else
51
+ PACKAGED_LOG_DIR=""
52
+ fi
53
+
54
+ LOG_DIR="${RIFT_LOG_DIR:-${PACKAGED_LOG_DIR:-${RIFT_HOME_DEFAULT}/logs}}"
55
+ DATA_DIR="${RIFT_DATA_DIR:-${RIFT_HOME_DEFAULT}/data}"
56
+ RENDERER="$SCRIPT_DIR/render-menu.py"
57
+
58
+ # Resolve the rift CLI. SwiftBar invokes shell commands with a stripped
59
+ # PATH, so menu actions need absolute paths end-to-end. We resolve:
60
+ # - NODE_BIN: an absolute node binary (bundled Node preferred), so actions
61
+ # don't depend on PATH and work on a Mac with no system Node.
62
+ # - RIFT_JS: the real JS entrypoint to pass as node's first arg. NEVER the
63
+ # bash shim — `node <bash-file>` fails — so we accept only a *.js target
64
+ # and prefer the packaged app entrypoint directly.
65
+ # - RIFT_BIN: an executable `rift` launcher (diagnostics + fallback). The
66
+ # packaged shim is a valid launcher when run DIRECTLY (it is not JS).
67
+ # REPO_ROOT only resolves to anything in a dev checkout; default it to empty
68
+ # so the dev-only candidate below is a harmless no-op under `set -u` on
69
+ # packaged/npm installs, where it is never set.
70
+ REPO_ROOT="${REPO_ROOT:-}"
71
+
72
+ NODE_BIN="${NODE_BIN:-}"
73
+ if [[ -z "$NODE_BIN" ]]; then
74
+ for candidate in \
75
+ "$PACKAGED_NODE" \
76
+ "/opt/homebrew/bin/node" \
77
+ "/usr/local/bin/node" \
78
+ "/usr/bin/node"; do
79
+ if [[ -x "$candidate" ]]; then
80
+ NODE_BIN="$candidate"
81
+ break
82
+ fi
83
+ done
84
+ fi
85
+
86
+ RIFT_BIN="${RIFT_BIN:-}"
87
+ if [[ -z "$RIFT_BIN" ]]; then
88
+ for candidate in \
89
+ "$PACKAGED_SHIM" \
90
+ "/opt/homebrew/bin/rift" \
91
+ "/usr/local/bin/rift" \
92
+ "$HOME/.npm-global/bin/rift" \
93
+ "$HOME/.local/bin/rift" \
94
+ "$REPO_ROOT/node_modules/.bin/rift"; do
95
+ if [[ -x "$candidate" ]]; then
96
+ RIFT_BIN="$candidate"
97
+ break
98
+ fi
99
+ done
100
+ fi
101
+
102
+ RIFT_JS="${RIFT_JS:-}"
103
+ # Packaged app entrypoint is authoritative when present.
104
+ if [[ -z "$RIFT_JS" && -f "$PACKAGED_JS" ]]; then
105
+ RIFT_JS="$PACKAGED_JS"
106
+ fi
107
+ # Otherwise follow RIFT_BIN's symlink chain to the real entrypoint — but
108
+ # only accept a *.js target, so a bash launcher (the packaged shim, or an
109
+ # on-PATH wrapper) is never handed to `node` as a script.
110
+ if [[ -z "$RIFT_JS" && -n "$RIFT_BIN" ]]; then
111
+ target="$RIFT_BIN"
112
+ while [[ -L "$target" ]]; do
113
+ link_dir="$(cd -P "$(dirname "$target")" && pwd)"
114
+ link_target="$(readlink "$target")"
115
+ if [[ "$link_target" = /* ]]; then
116
+ target="$link_target"
117
+ else
118
+ target="$link_dir/$link_target"
119
+ fi
120
+ done
121
+ if [[ -f "$target" && "$target" == *.js ]]; then
122
+ RIFT_JS="$target"
123
+ fi
124
+ fi
125
+
126
+ # Run a rift subcommand, preferring node + JS entrypoint (PATH-independent),
127
+ # falling back to the launcher. Prints stdout; swallows stderr. Returns the
128
+ # command's exit code, or 127 when rift can't be resolved at all.
129
+ run_rift() {
130
+ if [[ -n "$RIFT_JS" && -n "$NODE_BIN" ]]; then
131
+ "$NODE_BIN" "$RIFT_JS" "$@" 2>/dev/null
132
+ return $?
133
+ fi
134
+ if [[ -n "$RIFT_BIN" ]]; then
135
+ "$RIFT_BIN" "$@" 2>/dev/null
136
+ return $?
137
+ fi
138
+ return 127
139
+ }
140
+
141
+ # --- Gather state ---
142
+ # doctor --json is the canonical verdict + one fix (exits non-zero when
143
+ # broken, but still prints a valid report on stdout — capture regardless).
144
+ RIFT_DOCTOR_JSON="$(run_rift doctor --json || true)"
145
+ RIFT_STATUS_JSON="$(run_rift status --json --no-capability-map || true)"
146
+ # Memory-today is a delight metric from an unauthenticated endpoint.
147
+ RIFT_MCP_JSON="$(curl -fsS -m 2 "$BASE_URL/stats/mcp-usage" 2>/dev/null || true)"
148
+ # Coarse health probe — only consulted by the renderer when the doctor
149
+ # report is unavailable (rift CLI not resolvable on this machine).
150
+ if curl -fsS -m 2 "$BASE_URL/health" >/dev/null 2>&1; then
151
+ RIFT_HEALTH="up"
152
+ else
153
+ RIFT_HEALTH="down"
154
+ fi
155
+
156
+ export RIFT_DOCTOR_JSON RIFT_STATUS_JSON RIFT_MCP_JSON RIFT_HEALTH
157
+ export RIFT_NODE_BIN="$NODE_BIN" RIFT_JS RIFT_BIN
158
+ export RIFT_LOG_DIR="$LOG_DIR" RIFT_DATA_DIR="$DATA_DIR" RIFT_ABOUT_URL="$ABOUT_URL"
159
+
160
+ # --- Render ---
161
+ if [[ -f "$RENDERER" ]]; then
162
+ /usr/bin/python3 "$RENDERER"
163
+ else
164
+ # Renderer missing (broken install). Degrade to a minimal, honest menu
165
+ # rather than a blank one.
166
+ if [[ "$RIFT_HEALTH" == "up" ]]; then
167
+ echo "rift | sfimage=circle.fill sfcolor=systemGreen"
168
+ else
169
+ echo "rift | sfimage=exclamationmark.triangle.fill sfcolor=systemRed"
170
+ fi
171
+ echo "---"
172
+ echo "Menu renderer missing — reinstall Rift menu bar"
173
+ echo "Open logs | shell=/usr/bin/open param1=\"$LOG_DIR\" terminal=false"
174
+ echo "About Rift | href=$ABOUT_URL"
175
+ echo "Refresh | refresh=true"
176
+ fi