@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.
- package/README.md +35 -9
- package/dist/src/auth/keychain.d.ts +9 -0
- package/dist/src/auth/keychain.d.ts.map +1 -1
- package/dist/src/auth/keychain.js +37 -0
- package/dist/src/auth/keychain.js.map +1 -1
- package/dist/src/capture/auto-capture.d.ts +7 -0
- package/dist/src/capture/auto-capture.d.ts.map +1 -1
- package/dist/src/capture/auto-capture.js +82 -15
- package/dist/src/capture/auto-capture.js.map +1 -1
- package/dist/src/capture/auto-repair.d.ts +110 -0
- package/dist/src/capture/auto-repair.d.ts.map +1 -0
- package/dist/src/capture/auto-repair.js +269 -0
- package/dist/src/capture/auto-repair.js.map +1 -0
- package/dist/src/capture/codex-cli-triage-provider.d.ts.map +1 -1
- package/dist/src/capture/codex-cli-triage-provider.js +4 -3
- package/dist/src/capture/codex-cli-triage-provider.js.map +1 -1
- package/dist/src/capture/observability.d.ts +42 -0
- package/dist/src/capture/observability.d.ts.map +1 -1
- package/dist/src/capture/observability.js +45 -4
- package/dist/src/capture/observability.js.map +1 -1
- package/dist/src/capture/recover-quarantine.d.ts +260 -0
- package/dist/src/capture/recover-quarantine.d.ts.map +1 -0
- package/dist/src/capture/recover-quarantine.js +522 -0
- package/dist/src/capture/recover-quarantine.js.map +1 -0
- package/dist/src/cli/commands/backfill.d.ts.map +1 -1
- package/dist/src/cli/commands/backfill.js +5 -2
- package/dist/src/cli/commands/backfill.js.map +1 -1
- package/dist/src/cli/commands/capture-recover.d.ts +40 -0
- package/dist/src/cli/commands/capture-recover.d.ts.map +1 -0
- package/dist/src/cli/commands/capture-recover.js +184 -0
- package/dist/src/cli/commands/capture-recover.js.map +1 -0
- package/dist/src/cli/commands/capture.d.ts.map +1 -1
- package/dist/src/cli/commands/capture.js +96 -5
- package/dist/src/cli/commands/capture.js.map +1 -1
- package/dist/src/cli/commands/doctor.d.ts +6 -0
- package/dist/src/cli/commands/doctor.d.ts.map +1 -0
- package/dist/src/cli/commands/doctor.js +242 -0
- package/dist/src/cli/commands/doctor.js.map +1 -0
- package/dist/src/cli/commands/feedback.d.ts +12 -0
- package/dist/src/cli/commands/feedback.d.ts.map +1 -1
- package/dist/src/cli/commands/feedback.js +93 -4
- package/dist/src/cli/commands/feedback.js.map +1 -1
- package/dist/src/cli/commands/mcp-install.js +5 -2
- package/dist/src/cli/commands/mcp-install.js.map +1 -1
- package/dist/src/cli/commands/menubar.d.ts +30 -0
- package/dist/src/cli/commands/menubar.d.ts.map +1 -0
- package/dist/src/cli/commands/menubar.js +180 -0
- package/dist/src/cli/commands/menubar.js.map +1 -0
- package/dist/src/cli/commands/onboard.d.ts +129 -0
- package/dist/src/cli/commands/onboard.d.ts.map +1 -1
- package/dist/src/cli/commands/onboard.js +752 -171
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/commands/rebuild.d.ts.map +1 -1
- package/dist/src/cli/commands/rebuild.js +6 -3
- package/dist/src/cli/commands/rebuild.js.map +1 -1
- package/dist/src/cli/commands/reconcile.d.ts.map +1 -1
- package/dist/src/cli/commands/reconcile.js +12 -0
- package/dist/src/cli/commands/reconcile.js.map +1 -1
- package/dist/src/cli/commands/review.d.ts.map +1 -1
- package/dist/src/cli/commands/review.js +22 -7
- package/dist/src/cli/commands/review.js.map +1 -1
- package/dist/src/cli/commands/search.d.ts +2 -0
- package/dist/src/cli/commands/search.d.ts.map +1 -1
- package/dist/src/cli/commands/search.js +34 -4
- package/dist/src/cli/commands/search.js.map +1 -1
- package/dist/src/cli/commands/status.d.ts +9 -7
- package/dist/src/cli/commands/status.d.ts.map +1 -1
- package/dist/src/cli/commands/status.js +113 -12
- package/dist/src/cli/commands/status.js.map +1 -1
- package/dist/src/cli/commands/token-issue.d.ts.map +1 -1
- package/dist/src/cli/commands/token-issue.js +9 -1
- package/dist/src/cli/commands/token-issue.js.map +1 -1
- package/dist/src/cli/commands/triage.d.ts.map +1 -1
- package/dist/src/cli/commands/triage.js +7 -5
- package/dist/src/cli/commands/triage.js.map +1 -1
- package/dist/src/cli/commands/update.d.ts +80 -0
- package/dist/src/cli/commands/update.d.ts.map +1 -0
- package/dist/src/cli/commands/update.js +378 -0
- package/dist/src/cli/commands/update.js.map +1 -0
- package/dist/src/cli/default-config-path.d.ts +15 -0
- package/dist/src/cli/default-config-path.d.ts.map +1 -0
- package/dist/src/cli/default-config-path.js +27 -0
- package/dist/src/cli/default-config-path.js.map +1 -0
- package/dist/src/cli/feedback/feedback-config.d.ts +46 -0
- package/dist/src/cli/feedback/feedback-config.d.ts.map +1 -1
- package/dist/src/cli/feedback/feedback-config.js +130 -4
- package/dist/src/cli/feedback/feedback-config.js.map +1 -1
- package/dist/src/cli/feedback/feedback-history.d.ts +7 -0
- package/dist/src/cli/feedback/feedback-history.d.ts.map +1 -1
- package/dist/src/cli/feedback/feedback-history.js +39 -9
- package/dist/src/cli/feedback/feedback-history.js.map +1 -1
- package/dist/src/cli/feedback/feedback-payload.d.ts +22 -1
- package/dist/src/cli/feedback/feedback-payload.d.ts.map +1 -1
- package/dist/src/cli/feedback/feedback-payload.js.map +1 -1
- package/dist/src/cli/feedback/feedback-relay.d.ts +2 -2
- package/dist/src/cli/feedback/feedback-relay.d.ts.map +1 -1
- package/dist/src/cli/feedback/feedback-relay.js.map +1 -1
- package/dist/src/cli/feedback/invite.d.ts +17 -0
- package/dist/src/cli/feedback/invite.d.ts.map +1 -0
- package/dist/src/cli/feedback/invite.js +67 -0
- package/dist/src/cli/feedback/invite.js.map +1 -0
- package/dist/src/cli/feedback/relay-secret-store.d.ts +32 -0
- package/dist/src/cli/feedback/relay-secret-store.d.ts.map +1 -0
- package/dist/src/cli/feedback/relay-secret-store.js +137 -0
- package/dist/src/cli/feedback/relay-secret-store.js.map +1 -0
- package/dist/src/cli/http-client.d.ts +93 -1
- package/dist/src/cli/http-client.d.ts.map +1 -1
- package/dist/src/cli/http-client.js +254 -6
- package/dist/src/cli/http-client.js.map +1 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +29 -6
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/postinstall-menubar.d.ts +22 -0
- package/dist/src/cli/postinstall-menubar.d.ts.map +1 -0
- package/dist/src/cli/postinstall-menubar.js +39 -0
- package/dist/src/cli/postinstall-menubar.js.map +1 -0
- package/dist/src/cli/status/friend-header.d.ts +8 -1
- package/dist/src/cli/status/friend-header.d.ts.map +1 -1
- package/dist/src/cli/status/friend-header.js +334 -26
- package/dist/src/cli/status/friend-header.js.map +1 -1
- package/dist/src/cli/ui.d.ts +47 -0
- package/dist/src/cli/ui.d.ts.map +1 -0
- package/dist/src/cli/ui.js +166 -0
- package/dist/src/cli/ui.js.map +1 -0
- package/dist/src/config/schema.d.ts +79 -0
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +44 -0
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/diagnostics/codex-preflight.d.ts +33 -0
- package/dist/src/diagnostics/codex-preflight.d.ts.map +1 -0
- package/dist/src/diagnostics/codex-preflight.js +75 -0
- package/dist/src/diagnostics/codex-preflight.js.map +1 -0
- package/dist/src/diagnostics/doctor.d.ts +106 -0
- package/dist/src/diagnostics/doctor.d.ts.map +1 -0
- package/dist/src/diagnostics/doctor.js +334 -0
- package/dist/src/diagnostics/doctor.js.map +1 -0
- package/dist/src/diagnostics/notify.d.ts +90 -0
- package/dist/src/diagnostics/notify.d.ts.map +1 -0
- package/dist/src/diagnostics/notify.js +177 -0
- package/dist/src/diagnostics/notify.js.map +1 -0
- package/dist/src/diagnostics/repair-prompt.d.ts +49 -0
- package/dist/src/diagnostics/repair-prompt.d.ts.map +1 -0
- package/dist/src/diagnostics/repair-prompt.js +223 -0
- package/dist/src/diagnostics/repair-prompt.js.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.js +27 -0
- package/dist/src/ingestion/inbox-core/conversation-fingerprint.js.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-key.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/conversation-key.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/conversation-key.js +31 -0
- package/dist/src/ingestion/inbox-core/conversation-key.js.map +1 -0
- package/dist/src/ingestion/inbox-core/extensions.d.ts +3 -0
- package/dist/src/ingestion/inbox-core/extensions.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/extensions.js +16 -0
- package/dist/src/ingestion/inbox-core/extensions.js.map +1 -0
- package/dist/src/ingestion/inbox-core/idempotency.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/idempotency.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/idempotency.js +22 -0
- package/dist/src/ingestion/inbox-core/idempotency.js.map +1 -0
- package/dist/src/ingestion/inbox-core/index.d.ts +20 -0
- package/dist/src/ingestion/inbox-core/index.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/index.js +20 -0
- package/dist/src/ingestion/inbox-core/index.js.map +1 -0
- package/dist/src/ingestion/inbox-core/source-detection.d.ts +2 -0
- package/dist/src/ingestion/inbox-core/source-detection.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/source-detection.js +23 -0
- package/dist/src/ingestion/inbox-core/source-detection.js.map +1 -0
- package/dist/src/ingestion/inbox-core/source-sniffer.d.ts +11 -0
- package/dist/src/ingestion/inbox-core/source-sniffer.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/source-sniffer.js +69 -0
- package/dist/src/ingestion/inbox-core/source-sniffer.js.map +1 -0
- package/dist/src/ingestion/inbox-core/zip-sniffer.d.ts +70 -0
- package/dist/src/ingestion/inbox-core/zip-sniffer.d.ts.map +1 -0
- package/dist/src/ingestion/inbox-core/zip-sniffer.js +161 -0
- package/dist/src/ingestion/inbox-core/zip-sniffer.js.map +1 -0
- package/dist/src/ingestion/inbox-watcher.d.ts.map +1 -1
- package/dist/src/ingestion/inbox-watcher.js +34 -50
- package/dist/src/ingestion/inbox-watcher.js.map +1 -1
- package/dist/src/ingestion/indexer.d.ts +7 -0
- package/dist/src/ingestion/indexer.d.ts.map +1 -1
- package/dist/src/ingestion/indexer.js +36 -2
- package/dist/src/ingestion/indexer.js.map +1 -1
- package/dist/src/ingestion/metadata-extraction.d.ts +8 -5
- package/dist/src/ingestion/metadata-extraction.d.ts.map +1 -1
- package/dist/src/ingestion/metadata-extraction.js +24 -5
- package/dist/src/ingestion/metadata-extraction.js.map +1 -1
- package/dist/src/ingestion/skip-quarantine.d.ts +10 -0
- package/dist/src/ingestion/skip-quarantine.d.ts.map +1 -0
- package/dist/src/ingestion/skip-quarantine.js +35 -0
- package/dist/src/ingestion/skip-quarantine.js.map +1 -0
- package/dist/src/jobs/handlers/compact.d.ts.map +1 -1
- package/dist/src/jobs/handlers/compact.js +30 -4
- package/dist/src/jobs/handlers/compact.js.map +1 -1
- package/dist/src/jobs/handlers/dedupe-conversations.d.ts +134 -0
- package/dist/src/jobs/handlers/dedupe-conversations.d.ts.map +1 -0
- package/dist/src/jobs/handlers/dedupe-conversations.js +371 -0
- package/dist/src/jobs/handlers/dedupe-conversations.js.map +1 -0
- package/dist/src/jobs/handlers/ingest.d.ts.map +1 -1
- package/dist/src/jobs/handlers/ingest.js +295 -41
- package/dist/src/jobs/handlers/ingest.js.map +1 -1
- package/dist/src/jobs/handlers/reconcile.d.ts +28 -0
- package/dist/src/jobs/handlers/reconcile.d.ts.map +1 -1
- package/dist/src/jobs/handlers/reconcile.js +145 -19
- package/dist/src/jobs/handlers/reconcile.js.map +1 -1
- package/dist/src/jobs/handlers/reindex.d.ts.map +1 -1
- package/dist/src/jobs/handlers/reindex.js +13 -2
- package/dist/src/jobs/handlers/reindex.js.map +1 -1
- package/dist/src/jobs/handlers/save.d.ts.map +1 -1
- package/dist/src/jobs/handlers/save.js +57 -3
- package/dist/src/jobs/handlers/save.js.map +1 -1
- package/dist/src/jobs/queue.d.ts +51 -1
- package/dist/src/jobs/queue.d.ts.map +1 -1
- package/dist/src/jobs/queue.js +466 -26
- package/dist/src/jobs/queue.js.map +1 -1
- package/dist/src/jobs/worker-entry.d.ts.map +1 -1
- package/dist/src/jobs/worker-entry.js +35 -7
- package/dist/src/jobs/worker-entry.js.map +1 -1
- package/dist/src/jobs/worker-process.d.ts +11 -0
- package/dist/src/jobs/worker-process.d.ts.map +1 -1
- package/dist/src/jobs/worker-process.js +37 -4
- package/dist/src/jobs/worker-process.js.map +1 -1
- package/dist/src/main.js +199 -46
- package/dist/src/main.js.map +1 -1
- package/dist/src/mcp/errors.d.ts.map +1 -1
- package/dist/src/mcp/errors.js +20 -1
- package/dist/src/mcp/errors.js.map +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +43 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/context-pack.d.ts.map +1 -1
- package/dist/src/mcp/tools/context-pack.js +164 -23
- package/dist/src/mcp/tools/context-pack.js.map +1 -1
- package/dist/src/mcp/tools/search.d.ts +6 -2
- package/dist/src/mcp/tools/search.d.ts.map +1 -1
- package/dist/src/mcp/tools/search.js +35 -4
- package/dist/src/mcp/tools/search.js.map +1 -1
- package/dist/src/observability/embedding-events.d.ts +52 -0
- package/dist/src/observability/embedding-events.d.ts.map +1 -0
- package/dist/src/observability/embedding-events.js +149 -0
- package/dist/src/observability/embedding-events.js.map +1 -0
- package/dist/src/observability/index-events.d.ts +70 -0
- package/dist/src/observability/index-events.d.ts.map +1 -0
- package/dist/src/observability/index-events.js +148 -0
- package/dist/src/observability/index-events.js.map +1 -0
- package/dist/src/observability/onboarding-metric.d.ts +131 -0
- package/dist/src/observability/onboarding-metric.d.ts.map +1 -0
- package/dist/src/observability/onboarding-metric.js +351 -0
- package/dist/src/observability/onboarding-metric.js.map +1 -0
- package/dist/src/observability/tool-usage-stats.d.ts +77 -4
- package/dist/src/observability/tool-usage-stats.d.ts.map +1 -1
- package/dist/src/observability/tool-usage-stats.js +112 -32
- package/dist/src/observability/tool-usage-stats.js.map +1 -1
- package/dist/src/observability/tool-usage.d.ts +100 -7
- package/dist/src/observability/tool-usage.d.ts.map +1 -1
- package/dist/src/observability/tool-usage.js +196 -33
- package/dist/src/observability/tool-usage.js.map +1 -1
- package/dist/src/observability/version-check.d.ts +71 -0
- package/dist/src/observability/version-check.d.ts.map +1 -0
- package/dist/src/observability/version-check.js +198 -0
- package/dist/src/observability/version-check.js.map +1 -0
- package/dist/src/providers/basic-metadata-extraction.d.ts +60 -0
- package/dist/src/providers/basic-metadata-extraction.d.ts.map +1 -0
- package/dist/src/providers/basic-metadata-extraction.js +114 -0
- package/dist/src/providers/basic-metadata-extraction.js.map +1 -0
- package/dist/src/providers/codex-cli-metadata-extraction.d.ts +1 -0
- package/dist/src/providers/codex-cli-metadata-extraction.d.ts.map +1 -1
- package/dist/src/providers/codex-cli-metadata-extraction.js +6 -2
- package/dist/src/providers/codex-cli-metadata-extraction.js.map +1 -1
- package/dist/src/providers/codex-cli-model.d.ts +61 -0
- package/dist/src/providers/codex-cli-model.d.ts.map +1 -0
- package/dist/src/providers/codex-cli-model.js +194 -0
- package/dist/src/providers/codex-cli-model.js.map +1 -0
- package/dist/src/providers/codex-cli-runner.d.ts +39 -0
- package/dist/src/providers/codex-cli-runner.d.ts.map +1 -1
- package/dist/src/providers/codex-cli-runner.js +234 -48
- package/dist/src/providers/codex-cli-runner.js.map +1 -1
- package/dist/src/providers/conversation-generation.d.ts.map +1 -1
- package/dist/src/providers/conversation-generation.js +43 -6
- package/dist/src/providers/conversation-generation.js.map +1 -1
- package/dist/src/providers/ollama-embed.d.ts +2 -1
- package/dist/src/providers/ollama-embed.d.ts.map +1 -1
- package/dist/src/providers/ollama-embed.js +1 -0
- package/dist/src/providers/ollama-embed.js.map +1 -1
- package/dist/src/providers/openai-metadata-extraction.d.ts +3 -3
- package/dist/src/providers/openai-metadata-extraction.d.ts.map +1 -1
- package/dist/src/providers/openai-metadata-extraction.js +18 -3
- package/dist/src/providers/openai-metadata-extraction.js.map +1 -1
- package/dist/src/providers/placeholder-embed.d.ts +56 -0
- package/dist/src/providers/placeholder-embed.d.ts.map +1 -0
- package/dist/src/providers/placeholder-embed.js +64 -0
- package/dist/src/providers/placeholder-embed.js.map +1 -0
- package/dist/src/providers/stub.d.ts +2 -0
- package/dist/src/providers/stub.d.ts.map +1 -1
- package/dist/src/providers/stub.js +2 -0
- package/dist/src/providers/stub.js.map +1 -1
- package/dist/src/providers/types.d.ts +11 -0
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/providers/voyage.d.ts +2 -1
- package/dist/src/providers/voyage.d.ts.map +1 -1
- package/dist/src/providers/voyage.js +1 -0
- package/dist/src/providers/voyage.js.map +1 -1
- package/dist/src/retrieval/compact.d.ts +116 -2
- package/dist/src/retrieval/compact.d.ts.map +1 -1
- package/dist/src/retrieval/compact.js +158 -5
- package/dist/src/retrieval/compact.js.map +1 -1
- package/dist/src/retrieval/context-pack.d.ts +114 -0
- package/dist/src/retrieval/context-pack.d.ts.map +1 -1
- package/dist/src/retrieval/context-pack.js +292 -8
- package/dist/src/retrieval/context-pack.js.map +1 -1
- package/dist/src/retrieval/current-truth.d.ts +360 -0
- package/dist/src/retrieval/current-truth.d.ts.map +1 -0
- package/dist/src/retrieval/current-truth.js +766 -0
- package/dist/src/retrieval/current-truth.js.map +1 -0
- package/dist/src/retrieval/git-state.d.ts +53 -0
- package/dist/src/retrieval/git-state.d.ts.map +1 -0
- package/dist/src/retrieval/git-state.js +174 -0
- package/dist/src/retrieval/git-state.js.map +1 -0
- package/dist/src/retrieval/lexical.d.ts.map +1 -1
- package/dist/src/retrieval/lexical.js +19 -3
- package/dist/src/retrieval/lexical.js.map +1 -1
- package/dist/src/retrieval/locator-boost.d.ts +37 -0
- package/dist/src/retrieval/locator-boost.d.ts.map +1 -0
- package/dist/src/retrieval/locator-boost.js +129 -0
- package/dist/src/retrieval/locator-boost.js.map +1 -0
- package/dist/src/retrieval/report-demotion.d.ts +46 -0
- package/dist/src/retrieval/report-demotion.d.ts.map +1 -0
- package/dist/src/retrieval/report-demotion.js +169 -0
- package/dist/src/retrieval/report-demotion.js.map +1 -0
- package/dist/src/retrieval/vector.d.ts.map +1 -1
- package/dist/src/retrieval/vector.js +11 -2
- package/dist/src/retrieval/vector.js.map +1 -1
- package/dist/src/server/app.d.ts.map +1 -1
- package/dist/src/server/app.js +92 -11
- package/dist/src/server/app.js.map +1 -1
- package/dist/src/server/routes/compact.d.ts.map +1 -1
- package/dist/src/server/routes/compact.js +4 -1
- package/dist/src/server/routes/compact.js.map +1 -1
- package/dist/src/server/routes/context.d.ts +1 -1
- package/dist/src/server/routes/context.d.ts.map +1 -1
- package/dist/src/server/routes/context.js +2 -1
- package/dist/src/server/routes/context.js.map +1 -1
- package/dist/src/server/routes/conversations-search.d.ts.map +1 -1
- package/dist/src/server/routes/conversations-search.js +28 -3
- package/dist/src/server/routes/conversations-search.js.map +1 -1
- package/dist/src/server/routes/enqueue.d.ts +11 -0
- package/dist/src/server/routes/enqueue.d.ts.map +1 -0
- package/dist/src/server/routes/enqueue.js +17 -0
- package/dist/src/server/routes/enqueue.js.map +1 -0
- package/dist/src/server/routes/friend-status.d.ts +339 -3
- package/dist/src/server/routes/friend-status.d.ts.map +1 -1
- package/dist/src/server/routes/friend-status.js +447 -13
- package/dist/src/server/routes/friend-status.js.map +1 -1
- package/dist/src/server/routes/ingest.d.ts.map +1 -1
- package/dist/src/server/routes/ingest.js +5 -2
- package/dist/src/server/routes/ingest.js.map +1 -1
- package/dist/src/server/routes/mcp-usage.d.ts +5 -4
- package/dist/src/server/routes/mcp-usage.d.ts.map +1 -1
- package/dist/src/server/routes/mcp-usage.js.map +1 -1
- package/dist/src/server/routes/reconcile.d.ts.map +1 -1
- package/dist/src/server/routes/reconcile.js +20 -1
- package/dist/src/server/routes/reconcile.js.map +1 -1
- package/dist/src/server/routes/reindex.d.ts.map +1 -1
- package/dist/src/server/routes/reindex.js +4 -1
- package/dist/src/server/routes/reindex.js.map +1 -1
- package/dist/src/server/routes/save.d.ts.map +1 -1
- package/dist/src/server/routes/save.js +4 -1
- package/dist/src/server/routes/save.js.map +1 -1
- package/dist/src/server/routes/search.d.ts +1 -1
- package/dist/src/server/routes/search.d.ts.map +1 -1
- package/dist/src/server/routes/search.js +253 -29
- package/dist/src/server/routes/search.js.map +1 -1
- package/dist/src/server/routes/triage.d.ts.map +1 -1
- package/dist/src/server/routes/triage.js +4 -1
- package/dist/src/server/routes/triage.js.map +1 -1
- package/dist/src/storage/rebuild.d.ts +35 -1
- package/dist/src/storage/rebuild.d.ts.map +1 -1
- package/dist/src/storage/rebuild.js +288 -64
- package/dist/src/storage/rebuild.js.map +1 -1
- package/dist/src/storage/tables.d.ts +29 -0
- package/dist/src/storage/tables.d.ts.map +1 -1
- package/dist/src/storage/tables.js +32 -1
- package/dist/src/storage/tables.js.map +1 -1
- package/operator/swiftbar/render-menu.py +524 -0
- package/operator/swiftbar/rift.10s.sh +176 -0
- 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
|