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

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 (280) hide show
  1. package/README.md +7 -3
  2. package/dist/src/capture/auto-capture.d.ts +105 -4
  3. package/dist/src/capture/auto-capture.d.ts.map +1 -1
  4. package/dist/src/capture/auto-capture.js +313 -34
  5. package/dist/src/capture/auto-capture.js.map +1 -1
  6. package/dist/src/capture/claude-cli-triage-provider.d.ts +28 -0
  7. package/dist/src/capture/claude-cli-triage-provider.d.ts.map +1 -0
  8. package/dist/src/capture/claude-cli-triage-provider.js +88 -0
  9. package/dist/src/capture/claude-cli-triage-provider.js.map +1 -0
  10. package/dist/src/capture/codex-cli-triage-provider.d.ts.map +1 -1
  11. package/dist/src/capture/codex-cli-triage-provider.js +1 -33
  12. package/dist/src/capture/codex-cli-triage-provider.js.map +1 -1
  13. package/dist/src/capture/cursor-capture.d.ts +89 -0
  14. package/dist/src/capture/cursor-capture.d.ts.map +1 -0
  15. package/dist/src/capture/cursor-capture.js +121 -0
  16. package/dist/src/capture/cursor-capture.js.map +1 -0
  17. package/dist/src/capture/observability.d.ts +30 -0
  18. package/dist/src/capture/observability.d.ts.map +1 -1
  19. package/dist/src/capture/observability.js +29 -0
  20. package/dist/src/capture/observability.js.map +1 -1
  21. package/dist/src/capture/sources.d.ts +41 -3
  22. package/dist/src/capture/sources.d.ts.map +1 -1
  23. package/dist/src/capture/sources.js +43 -1
  24. package/dist/src/capture/sources.js.map +1 -1
  25. package/dist/src/capture/triage-classification.d.ts +69 -0
  26. package/dist/src/capture/triage-classification.d.ts.map +1 -0
  27. package/dist/src/capture/triage-classification.js +62 -0
  28. package/dist/src/capture/triage-classification.js.map +1 -0
  29. package/dist/src/capture/triage-provider-factory.d.ts +36 -0
  30. package/dist/src/capture/triage-provider-factory.d.ts.map +1 -0
  31. package/dist/src/capture/triage-provider-factory.js +55 -0
  32. package/dist/src/capture/triage-provider-factory.js.map +1 -0
  33. package/dist/src/capture/triage.d.ts +1 -1
  34. package/dist/src/capture/triage.d.ts.map +1 -1
  35. package/dist/src/capture/triage.js +8 -6
  36. package/dist/src/capture/triage.js.map +1 -1
  37. package/dist/src/cli/commands/capture.d.ts.map +1 -1
  38. package/dist/src/cli/commands/capture.js +72 -17
  39. package/dist/src/cli/commands/capture.js.map +1 -1
  40. package/dist/src/cli/commands/chunk-backfill.d.ts +13 -0
  41. package/dist/src/cli/commands/chunk-backfill.d.ts.map +1 -0
  42. package/dist/src/cli/commands/chunk-backfill.js +157 -0
  43. package/dist/src/cli/commands/chunk-backfill.js.map +1 -0
  44. package/dist/src/cli/commands/cursor-probe.d.ts +20 -0
  45. package/dist/src/cli/commands/cursor-probe.d.ts.map +1 -0
  46. package/dist/src/cli/commands/cursor-probe.js +162 -0
  47. package/dist/src/cli/commands/cursor-probe.js.map +1 -0
  48. package/dist/src/cli/commands/menubar.d.ts +50 -0
  49. package/dist/src/cli/commands/menubar.d.ts.map +1 -1
  50. package/dist/src/cli/commands/menubar.js +224 -16
  51. package/dist/src/cli/commands/menubar.js.map +1 -1
  52. package/dist/src/cli/commands/onboard.d.ts +36 -7
  53. package/dist/src/cli/commands/onboard.d.ts.map +1 -1
  54. package/dist/src/cli/commands/onboard.js +256 -53
  55. package/dist/src/cli/commands/onboard.js.map +1 -1
  56. package/dist/src/cli/commands/status.d.ts.map +1 -1
  57. package/dist/src/cli/commands/status.js +16 -0
  58. package/dist/src/cli/commands/status.js.map +1 -1
  59. package/dist/src/cli/commands/update.d.ts +34 -1
  60. package/dist/src/cli/commands/update.d.ts.map +1 -1
  61. package/dist/src/cli/commands/update.js +179 -2
  62. package/dist/src/cli/commands/update.js.map +1 -1
  63. package/dist/src/cli/index.d.ts.map +1 -1
  64. package/dist/src/cli/index.js +4 -0
  65. package/dist/src/cli/index.js.map +1 -1
  66. package/dist/src/cli/postinstall-menubar.d.ts.map +1 -1
  67. package/dist/src/cli/postinstall-menubar.js +14 -0
  68. package/dist/src/cli/postinstall-menubar.js.map +1 -1
  69. package/dist/src/cli/status/friend-header.d.ts +18 -0
  70. package/dist/src/cli/status/friend-header.d.ts.map +1 -1
  71. package/dist/src/cli/status/friend-header.js +137 -0
  72. package/dist/src/cli/status/friend-header.js.map +1 -1
  73. package/dist/src/cli/status/local-signals.d.ts +41 -0
  74. package/dist/src/cli/status/local-signals.d.ts.map +1 -1
  75. package/dist/src/cli/status/local-signals.js +48 -0
  76. package/dist/src/cli/status/local-signals.js.map +1 -1
  77. package/dist/src/config/schema.d.ts +220 -14
  78. package/dist/src/config/schema.d.ts.map +1 -1
  79. package/dist/src/config/schema.js +82 -7
  80. package/dist/src/config/schema.js.map +1 -1
  81. package/dist/src/diagnostics/claude-preflight.d.ts +34 -0
  82. package/dist/src/diagnostics/claude-preflight.d.ts.map +1 -0
  83. package/dist/src/diagnostics/claude-preflight.js +89 -0
  84. package/dist/src/diagnostics/claude-preflight.js.map +1 -0
  85. package/dist/src/diagnostics/codex-preflight.d.ts +1 -1
  86. package/dist/src/diagnostics/codex-preflight.d.ts.map +1 -1
  87. package/dist/src/diagnostics/codex-preflight.js +14 -0
  88. package/dist/src/diagnostics/codex-preflight.js.map +1 -1
  89. package/dist/src/diagnostics/doctor.d.ts +9 -1
  90. package/dist/src/diagnostics/doctor.d.ts.map +1 -1
  91. package/dist/src/diagnostics/doctor.js +57 -2
  92. package/dist/src/diagnostics/doctor.js.map +1 -1
  93. package/dist/src/ingestion/chunk-meta.d.ts +85 -0
  94. package/dist/src/ingestion/chunk-meta.d.ts.map +1 -0
  95. package/dist/src/ingestion/chunk-meta.js +167 -0
  96. package/dist/src/ingestion/chunk-meta.js.map +1 -0
  97. package/dist/src/ingestion/chunk-text.d.ts +39 -0
  98. package/dist/src/ingestion/chunk-text.d.ts.map +1 -0
  99. package/dist/src/ingestion/chunk-text.js +114 -0
  100. package/dist/src/ingestion/chunk-text.js.map +1 -0
  101. package/dist/src/ingestion/cursor/cursor-store.d.ts +177 -0
  102. package/dist/src/ingestion/cursor/cursor-store.d.ts.map +1 -0
  103. package/dist/src/ingestion/cursor/cursor-store.js +243 -0
  104. package/dist/src/ingestion/cursor/cursor-store.js.map +1 -0
  105. package/dist/src/ingestion/cursor/enrich-roots.d.ts +16 -0
  106. package/dist/src/ingestion/cursor/enrich-roots.d.ts.map +1 -0
  107. package/dist/src/ingestion/cursor/enrich-roots.js +22 -0
  108. package/dist/src/ingestion/cursor/enrich-roots.js.map +1 -0
  109. package/dist/src/ingestion/cursor/vscdb-reader.d.ts +32 -0
  110. package/dist/src/ingestion/cursor/vscdb-reader.d.ts.map +1 -0
  111. package/dist/src/ingestion/cursor/vscdb-reader.js +113 -0
  112. package/dist/src/ingestion/cursor/vscdb-reader.js.map +1 -0
  113. package/dist/src/ingestion/cursor/workspace-root.d.ts +96 -0
  114. package/dist/src/ingestion/cursor/workspace-root.d.ts.map +1 -0
  115. package/dist/src/ingestion/cursor/workspace-root.js +187 -0
  116. package/dist/src/ingestion/cursor/workspace-root.js.map +1 -0
  117. package/dist/src/ingestion/indexer.d.ts.map +1 -1
  118. package/dist/src/ingestion/indexer.js +41 -32
  119. package/dist/src/ingestion/indexer.js.map +1 -1
  120. package/dist/src/jobs/handlers/compact.d.ts.map +1 -1
  121. package/dist/src/jobs/handlers/compact.js +9 -4
  122. package/dist/src/jobs/handlers/compact.js.map +1 -1
  123. package/dist/src/jobs/handlers/ingest.d.ts.map +1 -1
  124. package/dist/src/jobs/handlers/ingest.js +60 -30
  125. package/dist/src/jobs/handlers/ingest.js.map +1 -1
  126. package/dist/src/jobs/handlers/reconcile.d.ts.map +1 -1
  127. package/dist/src/jobs/handlers/reconcile.js +128 -45
  128. package/dist/src/jobs/handlers/reconcile.js.map +1 -1
  129. package/dist/src/jobs/handlers/save.d.ts.map +1 -1
  130. package/dist/src/jobs/handlers/save.js +122 -72
  131. package/dist/src/jobs/handlers/save.js.map +1 -1
  132. package/dist/src/jobs/types.d.ts +1 -1
  133. package/dist/src/main.js +26 -15
  134. package/dist/src/main.js.map +1 -1
  135. package/dist/src/mcp/server.d.ts.map +1 -1
  136. package/dist/src/mcp/server.js +10 -3
  137. package/dist/src/mcp/server.js.map +1 -1
  138. package/dist/src/mcp/tools/context-pack.d.ts.map +1 -1
  139. package/dist/src/mcp/tools/context-pack.js +7 -1
  140. package/dist/src/mcp/tools/context-pack.js.map +1 -1
  141. package/dist/src/mcp/tools/conversations-search.d.ts +1 -1
  142. package/dist/src/mcp/tools/conversations-search.d.ts.map +1 -1
  143. package/dist/src/mcp/tools/conversations-search.js +7 -1
  144. package/dist/src/mcp/tools/conversations-search.js.map +1 -1
  145. package/dist/src/mcp/tools/evidence-feedback.d.ts +60 -0
  146. package/dist/src/mcp/tools/evidence-feedback.d.ts.map +1 -0
  147. package/dist/src/mcp/tools/evidence-feedback.js +62 -0
  148. package/dist/src/mcp/tools/evidence-feedback.js.map +1 -0
  149. package/dist/src/mcp/tools/log-outcome.d.ts +72 -0
  150. package/dist/src/mcp/tools/log-outcome.d.ts.map +1 -0
  151. package/dist/src/mcp/tools/log-outcome.js +59 -0
  152. package/dist/src/mcp/tools/log-outcome.js.map +1 -0
  153. package/dist/src/mcp/tools/open-evidence.d.ts +37 -0
  154. package/dist/src/mcp/tools/open-evidence.d.ts.map +1 -0
  155. package/dist/src/mcp/tools/open-evidence.js +72 -0
  156. package/dist/src/mcp/tools/open-evidence.js.map +1 -0
  157. package/dist/src/mcp/tools/save.d.ts +7 -2
  158. package/dist/src/mcp/tools/save.d.ts.map +1 -1
  159. package/dist/src/mcp/tools/save.js +7 -2
  160. package/dist/src/mcp/tools/save.js.map +1 -1
  161. package/dist/src/mcp/tools/search.d.ts.map +1 -1
  162. package/dist/src/mcp/tools/search.js +7 -1
  163. package/dist/src/mcp/tools/search.js.map +1 -1
  164. package/dist/src/observability/retrieval-feedback.d.ts +82 -0
  165. package/dist/src/observability/retrieval-feedback.d.ts.map +1 -0
  166. package/dist/src/observability/retrieval-feedback.js +231 -0
  167. package/dist/src/observability/retrieval-feedback.js.map +1 -0
  168. package/dist/src/observability/rift-context.d.ts.map +1 -1
  169. package/dist/src/observability/rift-context.js +3 -0
  170. package/dist/src/observability/rift-context.js.map +1 -1
  171. package/dist/src/observability/tool-usage-stats.d.ts +13 -0
  172. package/dist/src/observability/tool-usage-stats.d.ts.map +1 -1
  173. package/dist/src/observability/tool-usage-stats.js +15 -0
  174. package/dist/src/observability/tool-usage-stats.js.map +1 -1
  175. package/dist/src/observability/tool-usage.d.ts +56 -0
  176. package/dist/src/observability/tool-usage.d.ts.map +1 -1
  177. package/dist/src/observability/tool-usage.js +86 -0
  178. package/dist/src/observability/tool-usage.js.map +1 -1
  179. package/dist/src/providers/claude-cli-metadata-extraction.d.ts +47 -0
  180. package/dist/src/providers/claude-cli-metadata-extraction.d.ts.map +1 -0
  181. package/dist/src/providers/claude-cli-metadata-extraction.js +120 -0
  182. package/dist/src/providers/claude-cli-metadata-extraction.js.map +1 -0
  183. package/dist/src/providers/claude-cli-runner.d.ts +92 -0
  184. package/dist/src/providers/claude-cli-runner.d.ts.map +1 -0
  185. package/dist/src/providers/claude-cli-runner.js +598 -0
  186. package/dist/src/providers/claude-cli-runner.js.map +1 -0
  187. package/dist/src/providers/codex-cli-metadata-extraction.d.ts.map +1 -1
  188. package/dist/src/providers/codex-cli-metadata-extraction.js +1 -40
  189. package/dist/src/providers/codex-cli-metadata-extraction.js.map +1 -1
  190. package/dist/src/providers/codex-cli-runner.d.ts +7 -0
  191. package/dist/src/providers/codex-cli-runner.d.ts.map +1 -1
  192. package/dist/src/providers/codex-cli-runner.js +131 -5
  193. package/dist/src/providers/codex-cli-runner.js.map +1 -1
  194. package/dist/src/providers/conversation-generation.d.ts +10 -0
  195. package/dist/src/providers/conversation-generation.d.ts.map +1 -1
  196. package/dist/src/providers/conversation-generation.js +54 -13
  197. package/dist/src/providers/conversation-generation.js.map +1 -1
  198. package/dist/src/providers/openai-metadata-extraction.d.ts +48 -1
  199. package/dist/src/providers/openai-metadata-extraction.d.ts.map +1 -1
  200. package/dist/src/providers/openai-metadata-extraction.js +51 -2
  201. package/dist/src/providers/openai-metadata-extraction.js.map +1 -1
  202. package/dist/src/providers/types.d.ts +1 -1
  203. package/dist/src/providers/types.d.ts.map +1 -1
  204. package/dist/src/providers/types.js +4 -0
  205. package/dist/src/providers/types.js.map +1 -1
  206. package/dist/src/retrieval/compact.d.ts +81 -0
  207. package/dist/src/retrieval/compact.d.ts.map +1 -1
  208. package/dist/src/retrieval/compact.js +248 -8
  209. package/dist/src/retrieval/compact.js.map +1 -1
  210. package/dist/src/retrieval/context-pack.d.ts.map +1 -1
  211. package/dist/src/retrieval/context-pack.js +28 -14
  212. package/dist/src/retrieval/context-pack.js.map +1 -1
  213. package/dist/src/retrieval/evidence-key.d.ts +48 -0
  214. package/dist/src/retrieval/evidence-key.d.ts.map +1 -0
  215. package/dist/src/retrieval/evidence-key.js +131 -0
  216. package/dist/src/retrieval/evidence-key.js.map +1 -0
  217. package/dist/src/retrieval/group-by-parent.d.ts +38 -0
  218. package/dist/src/retrieval/group-by-parent.d.ts.map +1 -0
  219. package/dist/src/retrieval/group-by-parent.js +40 -0
  220. package/dist/src/retrieval/group-by-parent.js.map +1 -0
  221. package/dist/src/retrieval/lexical.d.ts.map +1 -1
  222. package/dist/src/retrieval/lexical.js +1 -3
  223. package/dist/src/retrieval/lexical.js.map +1 -1
  224. package/dist/src/retrieval/receipt.d.ts +57 -0
  225. package/dist/src/retrieval/receipt.d.ts.map +1 -0
  226. package/dist/src/retrieval/receipt.js +119 -0
  227. package/dist/src/retrieval/receipt.js.map +1 -0
  228. package/dist/src/retrieval/reranker.d.ts +12 -2
  229. package/dist/src/retrieval/reranker.d.ts.map +1 -1
  230. package/dist/src/retrieval/reranker.js +11 -4
  231. package/dist/src/retrieval/reranker.js.map +1 -1
  232. package/dist/src/retrieval/stitch-chunks.d.ts +73 -0
  233. package/dist/src/retrieval/stitch-chunks.d.ts.map +1 -0
  234. package/dist/src/retrieval/stitch-chunks.js +106 -0
  235. package/dist/src/retrieval/stitch-chunks.js.map +1 -0
  236. package/dist/src/server/app.d.ts.map +1 -1
  237. package/dist/src/server/app.js +17 -1
  238. package/dist/src/server/app.js.map +1 -1
  239. package/dist/src/server/routes/conversations-search.d.ts.map +1 -1
  240. package/dist/src/server/routes/conversations-search.js +12 -3
  241. package/dist/src/server/routes/conversations-search.js.map +1 -1
  242. package/dist/src/server/routes/friend-status.d.ts +44 -5
  243. package/dist/src/server/routes/friend-status.d.ts.map +1 -1
  244. package/dist/src/server/routes/friend-status.js +74 -6
  245. package/dist/src/server/routes/friend-status.js.map +1 -1
  246. package/dist/src/server/routes/mcp-usage.d.ts +9 -6
  247. package/dist/src/server/routes/mcp-usage.d.ts.map +1 -1
  248. package/dist/src/server/routes/mcp-usage.js.map +1 -1
  249. package/dist/src/server/routes/retrieval-feedback.d.ts +3 -0
  250. package/dist/src/server/routes/retrieval-feedback.d.ts.map +1 -0
  251. package/dist/src/server/routes/retrieval-feedback.js +290 -0
  252. package/dist/src/server/routes/retrieval-feedback.js.map +1 -0
  253. package/dist/src/server/routes/save.d.ts +3 -3
  254. package/dist/src/server/routes/save.d.ts.map +1 -1
  255. package/dist/src/server/routes/save.js +6 -2
  256. package/dist/src/server/routes/save.js.map +1 -1
  257. package/dist/src/server/routes/search.d.ts.map +1 -1
  258. package/dist/src/server/routes/search.js +19 -7
  259. package/dist/src/server/routes/search.js.map +1 -1
  260. package/dist/src/server/serving-marker.d.ts +85 -0
  261. package/dist/src/server/serving-marker.d.ts.map +1 -0
  262. package/dist/src/server/serving-marker.js +226 -0
  263. package/dist/src/server/serving-marker.js.map +1 -0
  264. package/dist/src/storage/chunk-backfill.d.ts +39 -0
  265. package/dist/src/storage/chunk-backfill.d.ts.map +1 -0
  266. package/dist/src/storage/chunk-backfill.js +295 -0
  267. package/dist/src/storage/chunk-backfill.js.map +1 -0
  268. package/dist/src/storage/filter.d.ts +42 -0
  269. package/dist/src/storage/filter.d.ts.map +1 -0
  270. package/dist/src/storage/filter.js +70 -0
  271. package/dist/src/storage/filter.js.map +1 -0
  272. package/dist/src/storage/rebuild.d.ts.map +1 -1
  273. package/dist/src/storage/rebuild.js +44 -27
  274. package/dist/src/storage/rebuild.js.map +1 -1
  275. package/dist/src/storage/tables.d.ts +41 -0
  276. package/dist/src/storage/tables.d.ts.map +1 -1
  277. package/dist/src/storage/tables.js +64 -1
  278. package/dist/src/storage/tables.js.map +1 -1
  279. package/operator/swiftbar/render-menu.py +57 -15
  280. package/package.json +5 -3
@@ -34,10 +34,10 @@ import { sanitizeProjectLabel } from "../../runtime/legacy-name-guard.js";
34
34
  import { validateVoyageKey } from "../../onboarding/voyage-validate.js";
35
35
  import { writeEnvFile } from "../../onboarding/env-file.js";
36
36
  import { kickstartDaemon, waitForHealth, } from "../../onboarding/daemon-control.js";
37
- import { runAutoCapture, resolveCodexCaptureDeps, } from "../../capture/auto-capture.js";
37
+ import { runAutoCapture, resolveCodexCaptureDeps, resolveCursorCaptureDeps, } from "../../capture/auto-capture.js";
38
38
  import { appendAutoCaptureRunRecord, buildAutoCaptureRunRecord, } from "../../capture/observability.js";
39
- import { DEFAULT_AUTO_CAPTURE_SOURCES } from "../../capture/sources.js";
40
- import { CodexCliTriageProvider } from "../../capture/codex-cli-triage-provider.js";
39
+ import { DEFAULT_AUTO_CAPTURE_SOURCES, CURSOR_CAPTURE_SOURCE, } from "../../capture/sources.js";
40
+ import { createTriageProvider, resolveTriageProviderName, setConfiguredTriageProviderName, triageWorkerLabel, } from "../../capture/triage-provider-factory.js";
41
41
  import { discoverClaudeCodeSessions } from "../../ingestion/parsers/claude-code-jsonl.js";
42
42
  import { discoverCodexSessions } from "../../ingestion/parsers/codex-jsonl.js";
43
43
  import { sniffInboxSource } from "../../ingestion/inbox-core/source-sniffer.js";
@@ -89,11 +89,12 @@ export function makeOnboardCommand() {
89
89
  .option("--reconfigure-voyage", "Recovery flow: replace the Voyage key only", false)
90
90
  .option("--yes", "Accept all defaults (non-interactive)", false)
91
91
  .option("--skip-capture", "Skip the post-setup capture pass (test-only)", false)
92
- .option("--no-codex-capture", "Skip the Codex CLI preflight + disable the capture pass for this run")
92
+ .option("--no-codex-capture", "Skip the triage-worker preflight + disable the capture pass for this run")
93
93
  .option("--with-claude-hook", "Install the Rift policy hook into Claude Code without prompting", false)
94
94
  .option("--no-claude-hook", "Skip the Claude Code policy-hook prompt entirely")
95
95
  .option("--enable-codex-enrichment", "Opt into Codex AI metadata enrichment (default: AI-free import + keyword search)", false)
96
- .option("--enable-capture", "Opt into scheduled chat capture + its Codex preflight (default: capture off, zero Codex calls)", false)
96
+ .option("--enable-claude-enrichment", "Opt into Claude Code AI metadata enrichment (digests/compaction stay off until Claude digest support ships; mutually exclusive with --enable-codex-enrichment)", false)
97
+ .option("--enable-capture", "Opt into scheduled chat capture + its triage-worker preflight (Codex by default, or Claude Code via capture.triage.provider; default: capture off, zero AI-worker calls)", false)
97
98
  .action(async (opts, cmd) => {
98
99
  const globalOpts = cmd.optsWithGlobals();
99
100
  try {
@@ -119,6 +120,13 @@ export async function runOnboard(opts, globalOpts) {
119
120
  terminal: !!process.stdin.isTTY,
120
121
  });
121
122
  try {
123
+ // Fail fast, before any config write: the two enrichment opt-ins pick
124
+ // different metadata workers (Codex vs Claude), so passing both is an
125
+ // ambiguous request, not a mergeable one. Reject it up front rather than
126
+ // letting last-writer-wins silently decide which provider you got.
127
+ if (opts.enableCodexEnrichment === true && opts.enableClaudeEnrichment === true) {
128
+ throw new CliError("Pass only one of --enable-codex-enrichment or --enable-claude-enrichment — they select different metadata workers.", "validation");
129
+ }
122
130
  if (opts.reconfigureVoyage) {
123
131
  await reconfigureVoyageFlow(opts, globalOpts, rl);
124
132
  return;
@@ -152,6 +160,7 @@ export async function runOnboard(opts, globalOpts) {
152
160
  // AI-free import + keyword search (zero Codex calls). Only persisted when
153
161
  // the user explicitly passed --enable-codex-enrichment.
154
162
  applyCodexEnrichmentOptIn(opts.enableCodexEnrichment, globalOpts.config, say);
163
+ applyClaudeEnrichmentOptIn(opts.enableClaudeEnrichment, globalOpts.config, say);
155
164
  // Step 2d — optional opt-in to scheduled chat capture. Default off, so a
156
165
  // fresh install makes zero Codex calls. Auto-capture embeds each saved
157
166
  // conversation, so it requires a Voyage key; opting in without one would
@@ -190,7 +199,7 @@ export async function runOnboard(opts, globalOpts) {
190
199
  throw new CliError(`Voyage smoke failed after daemon kickstart: ${refresh.reason}`, "server_error");
191
200
  }
192
201
  }
193
- // Step 3 — Codex CLI preflight (capture lane only).
202
+ // Step 3 — triage-worker preflight (capture lane only).
194
203
  //
195
204
  // Capture is OFF by default (trust-first): a fresh install makes zero
196
205
  // Codex calls. The preflight below performs a real Codex triage call,
@@ -205,6 +214,27 @@ export async function runOnboard(opts, globalOpts) {
205
214
  // `--no-codex-capture` skips the preflight even when capture was enabled
206
215
  // (e.g. enable the daemon schedule but skip the one-shot).
207
216
  let captureDisabled = false;
217
+ // `codexUnavailable` is set ONLY when capture was enabled and the live
218
+ // Codex preflight actually failed — i.e. scheduled capture is configured
219
+ // on but cannot run until Codex is fixed. `--no-codex-capture` is NOT this
220
+ // case: it only skips the onboarding one-shot, leaving the daemon's
221
+ // scheduled capture to run normally. Keeping the two apart is what lets the
222
+ // import-prompt and summary copy stay honest instead of either over- or
223
+ // under-promising automatic capture.
224
+ let codexUnavailable = false;
225
+ // Resolve the configured triage worker so the preflight probes (and labels)
226
+ // the worker the daemon will actually use, and record it process-wide so
227
+ // the first-capture pass below inherits the same selection. Unreadable
228
+ // config falls back to codex_cli — byte-for-byte the prior default.
229
+ let triageWorkerName = "codex_cli";
230
+ try {
231
+ triageWorkerName = resolveTriageProviderName(loadConfig(path.resolve(globalOpts.config)));
232
+ }
233
+ catch {
234
+ triageWorkerName = "codex_cli";
235
+ }
236
+ setConfiguredTriageProviderName(triageWorkerName);
237
+ const workerLabel = triageWorkerLabel(triageWorkerName);
208
238
  if (!captureEnabled) {
209
239
  ui.step("skip", "Chat access", captureRequested
210
240
  ? "capture needs a Voyage key — skipped"
@@ -216,21 +246,48 @@ export async function runOnboard(opts, globalOpts) {
216
246
  captureDisabled = true;
217
247
  }
218
248
  else {
219
- const codexSpin = new ui.Spinner("Chat access").start();
220
- const codexOk = await codexPreflight();
221
- if (codexOk) {
222
- codexSpin.succeed("Chat access", "ready · Codex CLI");
249
+ const preflightSpin = new ui.Spinner("Chat access").start();
250
+ const preflightOk = await triagePreflight(triageWorkerName);
251
+ if (preflightOk) {
252
+ preflightSpin.succeed("Chat access", `ready · ${workerLabel}`);
223
253
  }
224
254
  else {
225
- codexSpin.fail("Chat access", "not ready · Codex CLI — auto-import off");
226
- ui.note("To enable later: run `codex login`, then re-run `rift onboard`.");
255
+ preflightSpin.fail("Chat access", `not ready · ${workerLabel} — auto-import off`);
256
+ ui.note(triageWorkerName === "claude_code"
257
+ ? "To enable later: run `claude` once and sign in, then re-run `rift onboard`."
258
+ : "To enable later: run `codex login`, then re-run `rift onboard`.");
227
259
  captureDisabled = true;
260
+ codexUnavailable = true;
228
261
  }
229
262
  }
263
+ // Will the daemon's scheduled auto-capture actually run going forward?
264
+ // Configured on (requested + Voyage key) AND Codex available. This is the
265
+ // signal the import prompt and the summary line must use — NOT bare
266
+ // `captureEnabled`, which is only "configured on" and stays true even after
267
+ // a failed Codex preflight already told the user auto-import is off.
268
+ const autoCaptureWillRun = captureEnabled && !codexUnavailable;
230
269
  // Step 4 — discover sessions.
231
270
  const claudeSessions = safeDiscover(() => discoverClaudeCodeSessions(path.join(os.homedir(), ".claude")));
232
271
  const codexSessions = safeDiscover(() => discoverCodexSessions());
233
272
  ui.step("ok", "Chat history", `${claudeSessions} Claude Code · ${codexSessions} Codex CLI`);
273
+ // Set the expectation honestly up front: auto-capture covers the CLI agents,
274
+ // not every AI app on the Mac. The Claude/ChatGPT desktop + web apps keep
275
+ // their history server-side, so those chats arrive via export → rift import.
276
+ // `ui.detail` (legible, not dimmed) because this is trust-critical copy.
277
+ //
278
+ // The CLI-agent line is gated on `autoCaptureWillRun`: "captured
279
+ // automatically" is only true once capture is enabled AND the worker
280
+ // preflight passed. On a default/off or not-ready install (we just printed
281
+ // "capture off"/"not ready" above) we describe ELIGIBILITY instead, so the
282
+ // block never contradicts the gate it sits under.
283
+ const cliAgentsLine = autoCaptureWillRun
284
+ ? " • Claude Code + Codex CLI — captured automatically\n"
285
+ : " • Claude Code + Codex CLI — eligible for auto-capture once capture is enabled and chat access is ready\n";
286
+ ui.detail("Auto-capture covers your terminal AI agents — not every AI app on your Mac:\n" +
287
+ cliAgentsLine +
288
+ " • Cursor — only if you turn it on (add cursor_composer to capture.sources)\n" +
289
+ " • Claude Desktop / ChatGPT / web chats — NOT automatic; export them, then:\n" +
290
+ " rift import <file> --source claude_web (or chatgpt_web)");
234
291
  // Step 5 — beta opt-in (stay connected: news, pricing, feedback).
235
292
  const feedback = await collectFeedbackPreference(opts, dataDir, rl);
236
293
  if (feedback.email) {
@@ -246,6 +303,7 @@ export async function runOnboard(opts, globalOpts) {
246
303
  await maybeInstallClaudeCodeHook(opts, rl);
247
304
  // Step 6 + 7 — watermark current sessions + run one capture pass.
248
305
  let captureSaved = 0;
306
+ let captureRan = false;
249
307
  if (opts.skipCapture) {
250
308
  ui.step("skip", "Chat import", "skipped (--skip-capture)");
251
309
  }
@@ -262,6 +320,7 @@ export async function runOnboard(opts, globalOpts) {
262
320
  ui.step("skip", "Chat import", "skipped (capture not enabled)");
263
321
  }
264
322
  else {
323
+ captureRan = true;
265
324
  const captureResult = await runFirstCapturePass(globalOpts.config, dataDir);
266
325
  captureSaved = captureResult.saved;
267
326
  // Per A-1.1/C-1.3: a token-issuance failure during capture-pass
@@ -275,12 +334,18 @@ export async function runOnboard(opts, globalOpts) {
275
334
  }
276
335
  // Step 8 — optional export import.
277
336
  let importSucceeded = false;
337
+ let importAttempted = false;
278
338
  if (isOff(opts, "importExport", "noImportExport")) {
279
339
  ui.step("skip", "File import", "skipped (--no-import-export)");
280
340
  }
281
341
  else {
282
- const importPath = await collectImportPath(opts, rl);
342
+ const importPath = await collectImportPath(opts, rl, {
343
+ willRun: autoCaptureWillRun,
344
+ configured: captureEnabled,
345
+ workerLabel,
346
+ });
283
347
  if (importPath) {
348
+ importAttempted = true;
284
349
  const outcome = await runImport(importPath, globalOpts.config);
285
350
  if (outcome.kind === "imported" || outcome.kind === "duplicate") {
286
351
  importSucceeded = true;
@@ -291,30 +356,46 @@ export async function runOnboard(opts, globalOpts) {
291
356
  }
292
357
  }
293
358
  // Step 9 — first-recall verification.
294
- // Onboarding declares "complete" only when (a) capture or import landed
295
- // user data this run, AND (b) a recall query against the daemon returns
296
- // at least one hit. Without (a), there is nothing to recall calling
297
- // it "complete" because /search returned 0 rows would be misleading.
359
+ // A recall check only makes sense once this run actually landed user data.
360
+ // When nothing was imported or captured (the default, deliberate path), we
361
+ // skip the search check rather than start a spinner and FAIL itfailing a
362
+ // check the user opted out of is what made a clean setup feel broken.
298
363
  const ingestedAny = captureSaved > 0 || importSucceeded;
299
- const recallSpin = new ui.Spinner("Search check").start();
300
- const recall = ingestedAny
301
- ? await firstRecallCheck(globalOpts.config)
302
- : { ok: false, reason: "no user data was captured or imported during onboarding" };
303
- if (recall.ok && recall.hits > 0) {
304
- recallSpin.succeed("Search check", `found ${recall.hits} result(s)`);
305
- }
306
- else if (recall.ok) {
307
- // 0 hits is NOT success — decideOnboardOutcome treats it as incomplete.
308
- recallSpin.warn("Search check", "no results yet — index is fresh, try a query shortly");
364
+ const ingestAttempted = captureRan || importAttempted;
365
+ let recall;
366
+ if (!ingestedAny) {
367
+ ui.step("skip", "Search check", "nothing imported yet skipped");
368
+ recall = { ok: false, reason: "nothing imported yet" };
309
369
  }
310
370
  else {
311
- recallSpin.fail("Search check", recall.reason);
371
+ const recallSpin = new ui.Spinner("Search check").start();
372
+ recall = await firstRecallCheck(globalOpts.config);
373
+ if (recall.ok && recall.hits > 0) {
374
+ recallSpin.succeed("Search check", `found ${recall.hits} result(s)`);
375
+ }
376
+ else if (recall.ok) {
377
+ recallSpin.warn("Search check", "no results yet — index is fresh, try a query shortly");
378
+ }
379
+ else {
380
+ recallSpin.fail("Search check", recall.reason);
381
+ }
312
382
  }
313
383
  // Step 10 — next-action card.
314
- const card = decideOnboardOutcome({ ingestedAny, recall });
384
+ const card = decideOnboardOutcome({ ingestedAny, recall, ingestAttempted });
315
385
  card.push(ui.pc.dim(last4 !== null
316
386
  ? `Voyage: key valid (last 4 …${last4})${safeLabel ? ` · label ${safeLabel}` : ""}`
317
387
  : "Search: keyword-only (no embedding key) — add one later for semantic search"));
388
+ // One-line "what's on/off" so the friend leaves knowing whether Rift keeps
389
+ // itself fed. Three honest states: ready (configured + worker available),
390
+ // configured-but-not-ready (preflight failed — don't claim it's running),
391
+ // and off (the default). Capture is off by default (zero-egress default).
392
+ card.push(ui.pc.dim(autoCaptureWillRun
393
+ ? "Auto-capture: on — new chats are captured on a schedule."
394
+ : captureEnabled
395
+ ? `Auto-capture: configured on, but not ready — ${workerLabel} isn't available (${triageWorkerName === "claude_code"
396
+ ? "run `claude` and sign in"
397
+ : "run `codex login`"}).`
398
+ : "Auto-capture: off — enable with: rift onboard --enable-capture"));
318
399
  ui.box(card);
319
400
  ui.line("");
320
401
  }
@@ -446,8 +527,10 @@ export function persistVoyageLabel(configPath, label) {
446
527
  * Persist the Codex AI-metadata-enrichment opt-in into config.json. Default
447
528
  * onboarding leaves `enrichment.ai_metadata = false` (AI-free import + keyword
448
529
  * search, zero Codex calls). When the user passes `--enable-codex-enrichment`,
449
- * this flips it to true so the daemon/worker select the Codex-backed extractor.
450
- * Backs the file up first, mirroring `persistVoyageLabel`.
530
+ * this flips it to true AND pins `enrichment.metadata.provider = "codex_cli"` so
531
+ * the daemon/worker select the Codex-backed extractor overriding any prior
532
+ * `claude_code` pin rather than silently leaving Claude selected. Backs the file
533
+ * up first, mirroring `persistVoyageLabel`.
451
534
  *
452
535
  * Exported for orchestrator-level tests; `runOnboard` is the only production
453
536
  * caller. Returns true when the flag was set and persisted, false otherwise.
@@ -465,6 +548,55 @@ export function applyCodexEnrichmentOptIn(enable, configPath, emit) {
465
548
  const parsed = JSON.parse(raw);
466
549
  const enrichment = parsed["enrichment"] ?? {};
467
550
  enrichment["ai_metadata"] = true;
551
+ // Pin the metadata worker to Codex explicitly. Setting only `ai_metadata`
552
+ // would leave a pre-existing `metadata.provider = "claude_code"` in place, so
553
+ // "use Codex" would silently keep routing metadata through Claude (and keep
554
+ // digests off). Codex opt-in must win the provider selection, not just the gate.
555
+ const metadata = enrichment["metadata"] ?? {};
556
+ metadata["provider"] = "codex_cli";
557
+ enrichment["metadata"] = metadata;
558
+ parsed["enrichment"] = enrichment;
559
+ const tmp = `${absolute}.tmp.${process.pid}`;
560
+ fs.writeFileSync(tmp, JSON.stringify(parsed, null, 2) + "\n", {
561
+ encoding: "utf8",
562
+ mode: 0o644,
563
+ });
564
+ fs.renameSync(tmp, absolute);
565
+ emit("Codex AI metadata enrichment enabled (enrichment.ai_metadata = true, metadata.provider = codex_cli).");
566
+ return true;
567
+ }
568
+ /**
569
+ * Persist the Claude-Code AI-metadata-enrichment opt-in into config.json. Like
570
+ * `applyCodexEnrichmentOptIn` it flips `enrichment.ai_metadata = true`, but it
571
+ * ALSO pins `enrichment.metadata.provider = "claude_code"` so the daemon/worker
572
+ * route metadata through the `claude` CLI instead of Codex. Backs the file up
573
+ * first, mirroring the Codex opt-in. Callers must reject the combination of
574
+ * both enrichment flags before reaching here (see `runOnboard`).
575
+ *
576
+ * Discloses the one capability this does NOT turn on: there is no Claude digest
577
+ * worker yet and we will not silently borrow Codex for it, so digests/
578
+ * compaction stay off until a dedicated digest-provider slice ships. Saying so
579
+ * here prevents a silent capability loss for a user who switches to Claude.
580
+ *
581
+ * Exported for orchestrator-level tests; `runOnboard` is the only production
582
+ * caller. Returns true when the flag was set and persisted, false otherwise.
583
+ */
584
+ export function applyClaudeEnrichmentOptIn(enable, configPath, emit) {
585
+ if (enable !== true)
586
+ return false;
587
+ const absolute = path.resolve(configPath);
588
+ if (!fs.existsSync(absolute)) {
589
+ throw new CliError(`Cannot persist enrichment opt-in: config not found at ${absolute}`, "validation");
590
+ }
591
+ const raw = fs.readFileSync(absolute, "utf8");
592
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
593
+ fs.writeFileSync(`${absolute}.bak.${stamp}`, raw, { encoding: "utf8", mode: 0o600 });
594
+ const parsed = JSON.parse(raw);
595
+ const enrichment = parsed["enrichment"] ?? {};
596
+ enrichment["ai_metadata"] = true;
597
+ const metadata = enrichment["metadata"] ?? {};
598
+ metadata["provider"] = "claude_code";
599
+ enrichment["metadata"] = metadata;
468
600
  parsed["enrichment"] = enrichment;
469
601
  const tmp = `${absolute}.tmp.${process.pid}`;
470
602
  fs.writeFileSync(tmp, JSON.stringify(parsed, null, 2) + "\n", {
@@ -472,7 +604,7 @@ export function applyCodexEnrichmentOptIn(enable, configPath, emit) {
472
604
  mode: 0o644,
473
605
  });
474
606
  fs.renameSync(tmp, absolute);
475
- emit("Codex AI metadata enrichment enabled (enrichment.ai_metadata = true).");
607
+ emit("Claude metadata enrichment enabled. Digests/compaction stay off until Claude digest support ships.");
476
608
  return true;
477
609
  }
478
610
  /**
@@ -704,12 +836,18 @@ export function describeTokenFailure(reason) {
704
836
  }
705
837
  return "re-run `rift onboard`. If the failure repeats, run `rift feedback --kind=broke --with-status` to ship a diagnostic bundle.";
706
838
  }
707
- // ----- Step 3: Codex preflight -----
708
- async function codexPreflight() {
839
+ // ----- Step 3: triage-worker preflight -----
840
+ /**
841
+ * Live triage probe for the CONFIGURED worker (Codex CLI by default, or Claude
842
+ * Code when `capture.triage.provider = claude_code`). Routing through the
843
+ * factory — not a hardcoded CodexCliTriageProvider — is what stops a Claude
844
+ * Code user who deliberately picked `claude_code` from being failed by a Codex
845
+ * probe they never asked for.
846
+ */
847
+ async function triagePreflight(name) {
709
848
  try {
710
- await new CodexCliTriageProvider({ timeoutMs: 120_000 }).triage({
849
+ await createTriageProvider(name, { timeoutMs: 120_000 }).triage({
711
850
  content: "User: Rift onboarding preflight.\n\nAssistant: Ready.",
712
- source: "codex_cli",
713
851
  });
714
852
  return true;
715
853
  }
@@ -763,7 +901,17 @@ export async function collectFeedbackPreference(opts, dataDir, rl, store = macRe
763
901
  !opts.enableFeedbackRelay &&
764
902
  !opts.yes &&
765
903
  process.stdin.isTTY) {
766
- const pasted = (await ask(rl, "Paste a Rift invite code to enable feedback (Enter to skip): ")).trim();
904
+ // Most friends will NOT have an invite code make that the obvious,
905
+ // pressure-free default so the bare prompt doesn't read as "something is
906
+ // missing". An invite only turns on the direct feedback relay; the email
907
+ // ask below (and `rift feedback`) work without it.
908
+ ui.detail([
909
+ "Feedback channel (optional)",
910
+ "• Have an invite code from Clem? Paste it to turn on the direct feedback relay.",
911
+ "• No code? Just press Enter — this is optional, and most people skip it.",
912
+ "• Either way you can send feedback any time with `rift feedback`.",
913
+ ].join("\n"));
914
+ const pasted = (await ask(rl, "Invite code from Clem (or press Enter to skip): ")).trim();
767
915
  if (pasted) {
768
916
  try {
769
917
  const invite = parseInvite(pasted);
@@ -1029,15 +1177,38 @@ export async function runFirstCapturePass(configPath, dataDir) {
1029
1177
  const token = tokenResult.token;
1030
1178
  const client = createHttpClient({ baseUrl, token });
1031
1179
  let codexCaptureDeps = {};
1180
+ let cursorCaptureDeps = {};
1181
+ // Onboarding is a config-driven capture path, so it must honor the Cursor
1182
+ // opt-in exactly like the daemon: if `cursor_composer` is in
1183
+ // `config.capture.sources`, this first pass authorizes Cursor discovery +
1184
+ // persistence WITHOUT the env flags (which exist only for ad-hoc dogfood
1185
+ // runs). Without this, onboarding would record cursor_composer in its ledger
1186
+ // row yet runAutoCapture would treat Cursor as disabled — capturing nothing.
1187
+ let cursorConfigEnabled = false;
1032
1188
  let enabledSources = DEFAULT_AUTO_CAPTURE_SOURCES;
1189
+ // Worker name for the honest preflight-failed warning below (defaults to the
1190
+ // codex_cli label until config resolves).
1191
+ let captureWorkerLabel = triageWorkerLabel("codex_cli");
1033
1192
  try {
1034
1193
  const { loadConfig } = await import("../../config/loader.js");
1035
1194
  const cfg = loadConfig(configPath);
1195
+ // Route this first capture pass through the configured triage worker —
1196
+ // runAutoCapture builds its provider from the process-wide configured name,
1197
+ // so without this a claude_code user's onboarding pass would silently fall
1198
+ // back to Codex. (Step 3 already set it; re-assert here so this function is
1199
+ // correct independent of call order.)
1200
+ const captureWorkerName = resolveTriageProviderName(cfg);
1201
+ setConfiguredTriageProviderName(captureWorkerName);
1202
+ captureWorkerLabel = triageWorkerLabel(captureWorkerName);
1036
1203
  codexCaptureDeps = resolveCodexCaptureDeps(cfg);
1204
+ cursorCaptureDeps = resolveCursorCaptureDeps(cfg);
1205
+ cursorConfigEnabled = cfg.capture.sources.includes(CURSOR_CAPTURE_SOURCE);
1037
1206
  enabledSources = cfg.capture.sources;
1038
1207
  }
1039
1208
  catch {
1040
1209
  codexCaptureDeps = {};
1210
+ cursorCaptureDeps = {};
1211
+ cursorConfigEnabled = false;
1041
1212
  }
1042
1213
  const startedAt = new Date();
1043
1214
  const captureSpin = new ui.Spinner("Chat import").start();
@@ -1052,6 +1223,8 @@ export async function runFirstCapturePass(configPath, dataDir) {
1052
1223
  // ledger that says one thing while capture did another.
1053
1224
  sources: enabledSources,
1054
1225
  ...codexCaptureDeps,
1226
+ ...cursorCaptureDeps,
1227
+ cursorConfigEnabled,
1055
1228
  saveFn: async (saveOpts) => {
1056
1229
  const { data } = await client.post("/save", {
1057
1230
  source: saveOpts.source,
@@ -1094,12 +1267,12 @@ export async function runFirstCapturePass(configPath, dataDir) {
1094
1267
  : String(observabilityErr)}\n`);
1095
1268
  }
1096
1269
  const base = `${report.saved} added · ${report.review} to review`;
1097
- // A preflight failure (Codex health probe) carries 0 per-session errors,
1098
- // so an `errors > 0` check alone would `succeed()` on a run where triage
1099
- // never ran — telling a friend everything's fine when it isn't. Treat a
1100
- // failed probe as a warning too, and surface why.
1270
+ // A preflight failure (triage-worker health probe) carries 0 per-session
1271
+ // errors, so an `errors > 0` check alone would `succeed()` on a run where
1272
+ // triage never ran — telling a friend everything's fine when it isn't.
1273
+ // Treat a failed probe as a warning too, and surface why.
1101
1274
  if (!report.preflight_ok) {
1102
- captureSpin.warn("Chat import", `${base} · Codex preflight failed${report.preflight_error ? ` (${report.preflight_error})` : ""}`);
1275
+ captureSpin.warn("Chat import", `${base} · ${captureWorkerLabel} preflight failed${report.preflight_error ? ` (${report.preflight_error})` : ""}`);
1103
1276
  }
1104
1277
  else if (report.errors > 0) {
1105
1278
  captureSpin.warn("Chat import", `${base} · ${report.errors} error(s)`);
@@ -1115,7 +1288,7 @@ export async function runFirstCapturePass(configPath, dataDir) {
1115
1288
  }
1116
1289
  }
1117
1290
  // ----- Step 8: import -----
1118
- async function collectImportPath(opts, rl) {
1291
+ async function collectImportPath(opts, rl, capture) {
1119
1292
  // opts.importExport is `string | false | undefined` — Commander writes
1120
1293
  // `false` when `--no-import-export` was passed, but the caller already
1121
1294
  // short-circuits in that case. Only a non-empty string is a real path.
@@ -1129,11 +1302,23 @@ async function collectImportPath(opts, rl) {
1129
1302
  // user who has never exported a chat archive shouldn't hit a bare
1130
1303
  // "Drop a path" prompt. The source-sniffer below still fail-fasts a
1131
1304
  // non-ChatGPT export, but the friend learns the boundary earlier here.
1305
+ //
1306
+ // The "skipping is fine" reassurance MUST match what will actually happen.
1307
+ // Auto-capture is OFF by default, AND can be configured-on-but-not-running
1308
+ // when the triage-worker preflight failed — promising "new chats get captured
1309
+ // automatically" in either case is the exact trust break this flow removes.
1310
+ // Only promise it when capture will genuinely run; otherwise point at the
1311
+ // real next step (fix the triage worker if configured, or enable it if off).
1312
+ const whySkip = capture.willRun
1313
+ ? "skipping is fine — new chats get captured automatically."
1314
+ : capture.configured
1315
+ ? `skipping is fine — import later with \`rift import\` (auto-capture is on but won't run until ${capture.workerLabel} is ready — see above).`
1316
+ : "skipping is fine — import later with `rift import`, or turn on auto-capture with `rift onboard --enable-capture`.";
1132
1317
  ui.detail([
1133
1318
  "Import past chats (optional)",
1134
1319
  "• What: the file path to a ChatGPT data export (a .zip or .json on your Mac), e.g. ~/Downloads/chatgpt-export.zip.",
1135
1320
  "• Where: chatgpt.com → Settings → Data controls → Export data, then unzip nothing — just drop the downloaded file's path here.",
1136
- "• Why: this seeds Rift with your existing ChatGPT history so search works on day one; skipping is fine — new chats get captured automatically.",
1321
+ `• Why: this seeds Rift with your existing ChatGPT history so search works on day one; ${whySkip}`,
1137
1322
  "• Other tools: Claude/Grok/Gemini exports aren't imported here. Run them later with: rift import <path> --source claude_web|grok_web|gemini_web",
1138
1323
  ].join("\n"));
1139
1324
  const answer = (await ask(rl, "Path to a ChatGPT export to import now (or `skip`): ", "skip")).trim();
@@ -1196,14 +1381,22 @@ async function runImport(filePath, configPath) {
1196
1381
  return { kind: "imported", jobId: resp.job_id };
1197
1382
  }
1198
1383
  /**
1199
- * Pure outcome decision for the next-action card. Onboarding is only
1200
- * "complete" when this run actually ingested user data AND a recall
1201
- * query against the freshly-ingested data returned at least one hit.
1202
- * Smoke is non-indexing (see daemonRefreshFlow), so any hit is real
1203
- * user data, not a leftover probe.
1384
+ * Pure outcome decision for the next-action card.
1385
+ *
1386
+ * "Complete + verified" needs both: this run ingested user data AND a recall
1387
+ * query returned a hit (smoke is non-indexing see daemonRefreshFlow so a
1388
+ * hit is real user data, not a leftover probe).
1389
+ *
1390
+ * But an EMPTY archive is not a failure. By default capture is OFF (zero-Codex
1391
+ * trust default) and import is optional, so a friend who simply presses Enter
1392
+ * past the import prompt has finished a valid setup — they just have nothing to
1393
+ * recall yet. That case (`ingestAttempted === false`) is reported as DONE, not
1394
+ * "incomplete". We only flag a retry when an ingest was actually attempted and
1395
+ * still added nothing.
1204
1396
  */
1205
1397
  export function decideOnboardOutcome(input) {
1206
1398
  const { ingestedAny, recall } = input;
1399
+ const ingestAttempted = input.ingestAttempted ?? false;
1207
1400
  if (ingestedAny && recall.ok && recall.hits > 0) {
1208
1401
  return [
1209
1402
  `Setup complete — recall returned ${recall.hits} result(s) from your data.`,
@@ -1211,10 +1404,19 @@ export function decideOnboardOutcome(input) {
1211
1404
  ];
1212
1405
  }
1213
1406
  if (!ingestedAny) {
1407
+ if (!ingestAttempted) {
1408
+ // Deliberate empty setup — a finished, valid state, not a failure.
1409
+ return [
1410
+ "Setup complete — nothing imported yet.",
1411
+ "Import later: rift import <path> --source <name>",
1412
+ "Next: rift mcp install --client=claude-desktop",
1413
+ ];
1414
+ }
1415
+ // An ingest ran but landed nothing — honest, calm retry guidance.
1214
1416
  return [
1215
- "Setup incompleteno user data was captured or imported during onboarding.",
1216
- "Next: drop an export at the prompt above and re-run rift onboard,",
1217
- " or run: rift import <path> --source <name>",
1417
+ "Setup readybut capture/import added nothing this run.",
1418
+ "Try: rift import <path> --source <name>",
1419
+ "If it repeats: rift feedback --kind=broke --with-status",
1218
1420
  ];
1219
1421
  }
1220
1422
  if (recall.ok) {
@@ -1377,7 +1579,8 @@ function say(line) {
1377
1579
  function sayPrivacyContract() {
1378
1580
  ui.detail([
1379
1581
  "• Conversation content stays local (LanceDB + raw transcripts).",
1380
- "• Content snippets leave the machine for embedding only (Voyage AI).",
1582
+ "• Indexed conversation text and your search queries leave the machine",
1583
+ " for embedding only when a Voyage key is configured (Voyage AI).",
1381
1584
  "• The Voyage key sits in ~/.rift.env (mode 0600), is sent only to Voyage,",
1382
1585
  " and is never logged or sent to Clem.",
1383
1586
  "• Feedback is stored locally as JSONL. Relay is opt-in: explicit notes only,",