@cachly-dev/mcp-server 0.10.47 → 0.10.49

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 (309) hide show
  1. package/.github/workflows/eas-build-android.yml +58 -0
  2. package/.github/workflows/eas-update.yml +55 -0
  3. package/STRATEGY.md +209 -0
  4. package/apps/travel-chaos-organizer/.env.example +49 -0
  5. package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
  6. package/apps/travel-chaos-organizer/Makefile +57 -0
  7. package/apps/travel-chaos-organizer/README.md +159 -0
  8. package/apps/travel-chaos-organizer/backend/.env.example +10 -0
  9. package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
  10. package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
  11. package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
  12. package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
  13. package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
  14. package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
  15. package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
  16. package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
  17. package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
  18. package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
  19. package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
  20. package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
  21. package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
  22. package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
  23. package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
  24. package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
  25. package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
  26. package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
  27. package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
  28. package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
  29. package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
  30. package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
  31. package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
  32. package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
  33. package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
  34. package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
  35. package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
  36. package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
  37. package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
  38. package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
  39. package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
  40. package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
  41. package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
  42. package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
  43. package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
  44. package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
  45. package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
  46. package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
  47. package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
  48. package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
  49. package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
  50. package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
  51. package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
  52. package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
  53. package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
  54. package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
  55. package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
  56. package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
  57. package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
  58. package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
  59. package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
  60. package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
  61. package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
  62. package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
  63. package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
  64. package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
  65. package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
  66. package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
  67. package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
  68. package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
  69. package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
  70. package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
  71. package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
  72. package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
  73. package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
  74. package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
  75. package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
  76. package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
  77. package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
  78. package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
  79. package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
  80. package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
  81. package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
  82. package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
  83. package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
  84. package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
  85. package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
  86. package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
  87. package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
  88. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
  89. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
  90. package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
  91. package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
  92. package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
  93. package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
  94. package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
  95. package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
  96. package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
  97. package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
  98. package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
  99. package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
  100. package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
  101. package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
  102. package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
  103. package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
  104. package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
  105. package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
  106. package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
  107. package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
  108. package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
  109. package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
  110. package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
  111. package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
  112. package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
  113. package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
  114. package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
  115. package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
  116. package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
  117. package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
  118. package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
  119. package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
  120. package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
  121. package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
  122. package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
  123. package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
  124. package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
  125. package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
  126. package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
  127. package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
  128. package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
  129. package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
  130. package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
  131. package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
  132. package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
  133. package/apps/travel-chaos-organizer/frontend/package-lock.json +13037 -0
  134. package/apps/travel-chaos-organizer/frontend/package.json +44 -0
  135. package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
  136. package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
  137. package/apps/travel-chaos-organizer/landing/index.html +773 -0
  138. package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
  139. package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
  140. package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
  141. package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
  142. package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
  143. package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
  144. package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
  145. package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
  146. package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
  147. package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
  148. package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
  149. package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
  150. package/dist/packages/telegram-notify/client.d.ts +19 -0
  151. package/dist/packages/telegram-notify/client.js +75 -0
  152. package/dist/packages/telegram-notify/client.js.map +1 -0
  153. package/dist/src/__tests__/auth.test.js.map +1 -0
  154. package/dist/{__tests__ → src/__tests__}/brain-flow.test.js +143 -2
  155. package/dist/src/__tests__/brain-flow.test.js.map +1 -0
  156. package/dist/src/__tests__/confidence.test.js.map +1 -0
  157. package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
  158. package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
  159. package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
  160. package/dist/src/__tests__/e2e/config.js.map +1 -0
  161. package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
  162. package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
  163. package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
  164. package/dist/{__tests__ → src/__tests__}/handlers-unit.test.js +1 -1
  165. package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
  166. package/dist/src/__tests__/namespace.test.js.map +1 -0
  167. package/dist/src/auth.js.map +1 -0
  168. package/dist/src/ckg.js.map +1 -0
  169. package/dist/src/confidence.js.map +1 -0
  170. package/dist/src/embeddings.js.map +1 -0
  171. package/dist/src/handlers/advanced.js.map +1 -0
  172. package/dist/{handlers → src/handlers}/brain.js +135 -31
  173. package/dist/src/handlers/brain.js.map +1 -0
  174. package/dist/src/handlers/cache.js.map +1 -0
  175. package/dist/{handlers → src/handlers}/context.js +9 -4
  176. package/dist/src/handlers/context.js.map +1 -0
  177. package/dist/{handlers → src/handlers}/fedbrain.d.ts +6 -0
  178. package/dist/{handlers → src/handlers}/fedbrain.js +15 -2
  179. package/dist/src/handlers/fedbrain.js.map +1 -0
  180. package/dist/src/handlers/instances.js.map +1 -0
  181. package/dist/{handlers → src/handlers}/roadmap.js +8 -4
  182. package/dist/src/handlers/roadmap.js.map +1 -0
  183. package/dist/src/handlers/syndicate.js.map +1 -0
  184. package/dist/src/handlers/tco.d.ts +16 -0
  185. package/dist/src/handlers/tco.js +303 -0
  186. package/dist/src/handlers/tco.js.map +1 -0
  187. package/dist/{handlers → src/handlers}/team.js +1 -1
  188. package/dist/src/handlers/team.js.map +1 -0
  189. package/dist/{index.js → src/index.js} +145 -97
  190. package/dist/src/index.js.map +1 -0
  191. package/dist/src/namespace.js.map +1 -0
  192. package/dist/src/notifier.d.ts +5 -0
  193. package/dist/src/notifier.js +6 -0
  194. package/dist/src/notifier.js.map +1 -0
  195. package/dist/{search.js → src/search.js} +3 -3
  196. package/dist/src/search.js.map +1 -0
  197. package/dist/src/search.test.js.map +1 -0
  198. package/dist/{tools.d.ts → src/tools.d.ts} +196 -0
  199. package/dist/{tools.js → src/tools.js} +178 -0
  200. package/dist/src/tools.js.map +1 -0
  201. package/dist/src/utils.js.map +1 -0
  202. package/package.json +3 -2
  203. package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
  204. package/packages/telegram-notify/client.ts +88 -0
  205. package/packages/telegram-notify/emojis.json +22 -0
  206. package/packages/telegram-notify/pyproject.toml +17 -0
  207. package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
  208. package/packages/telegram-notify/telegram_notify/client.py +87 -0
  209. package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
  210. package/scripts/generate-icons.js +28 -0
  211. package/smithery.yaml +21 -10
  212. package/src/__tests__/brain-flow.test.ts +176 -2
  213. package/src/__tests__/handlers-unit.test.ts +1 -1
  214. package/src/handlers/brain.ts +139 -32
  215. package/src/handlers/context.ts +9 -4
  216. package/src/handlers/fedbrain.ts +17 -2
  217. package/src/handlers/roadmap.ts +8 -4
  218. package/src/handlers/tco.ts +347 -0
  219. package/src/handlers/team.ts +1 -1
  220. package/src/index.ts +137 -147
  221. package/src/notifier.ts +5 -0
  222. package/src/search.ts +3 -3
  223. package/src/tools.ts +191 -0
  224. package/tsconfig.json +2 -3
  225. package/dist/__tests__/auth.test.js.map +0 -1
  226. package/dist/__tests__/brain-flow.test.js.map +0 -1
  227. package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
  228. package/dist/__tests__/brain-predict-failures.test.js +0 -339
  229. package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
  230. package/dist/__tests__/confidence.test.js.map +0 -1
  231. package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
  232. package/dist/__tests__/e2e/api-health.test.js.map +0 -1
  233. package/dist/__tests__/e2e/cli.test.js.map +0 -1
  234. package/dist/__tests__/e2e/config.js.map +0 -1
  235. package/dist/__tests__/e2e/funnel.test.js.map +0 -1
  236. package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
  237. package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
  238. package/dist/__tests__/handlers-unit.test.js.map +0 -1
  239. package/dist/__tests__/namespace.test.js.map +0 -1
  240. package/dist/auth.js.map +0 -1
  241. package/dist/ckg.js.map +0 -1
  242. package/dist/confidence.js.map +0 -1
  243. package/dist/embeddings.js.map +0 -1
  244. package/dist/handlers/advanced.js.map +0 -1
  245. package/dist/handlers/brain.js.map +0 -1
  246. package/dist/handlers/cache.js.map +0 -1
  247. package/dist/handlers/context.js.map +0 -1
  248. package/dist/handlers/fedbrain.js.map +0 -1
  249. package/dist/handlers/instances.js.map +0 -1
  250. package/dist/handlers/roadmap.js.map +0 -1
  251. package/dist/handlers/syndicate.js.map +0 -1
  252. package/dist/handlers/team.js.map +0 -1
  253. package/dist/index.js.map +0 -1
  254. package/dist/namespace.js.map +0 -1
  255. package/dist/search.js.map +0 -1
  256. package/dist/search.test.js.map +0 -1
  257. package/dist/tools.js.map +0 -1
  258. package/dist/utils.js.map +0 -1
  259. package/src/__tests__/brain-predict-failures.test.ts +0 -430
  260. /package/dist/{__tests__ → src/__tests__}/auth.test.d.ts +0 -0
  261. /package/dist/{__tests__ → src/__tests__}/auth.test.js +0 -0
  262. /package/dist/{__tests__ → src/__tests__}/brain-flow.test.d.ts +0 -0
  263. /package/dist/{__tests__ → src/__tests__}/confidence.test.d.ts +0 -0
  264. /package/dist/{__tests__ → src/__tests__}/confidence.test.js +0 -0
  265. /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.d.ts +0 -0
  266. /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.js +0 -0
  267. /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.d.ts +0 -0
  268. /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.js +0 -0
  269. /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.d.ts +0 -0
  270. /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.js +0 -0
  271. /package/dist/{__tests__ → src/__tests__}/e2e/config.d.ts +0 -0
  272. /package/dist/{__tests__ → src/__tests__}/e2e/config.js +0 -0
  273. /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.d.ts +0 -0
  274. /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.js +0 -0
  275. /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
  276. /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.js +0 -0
  277. /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.d.ts +0 -0
  278. /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.js +0 -0
  279. /package/dist/{__tests__ → src/__tests__}/handlers-unit.test.d.ts +0 -0
  280. /package/dist/{__tests__ → src/__tests__}/namespace.test.d.ts +0 -0
  281. /package/dist/{__tests__ → src/__tests__}/namespace.test.js +0 -0
  282. /package/dist/{auth.d.ts → src/auth.d.ts} +0 -0
  283. /package/dist/{auth.js → src/auth.js} +0 -0
  284. /package/dist/{ckg.d.ts → src/ckg.d.ts} +0 -0
  285. /package/dist/{ckg.js → src/ckg.js} +0 -0
  286. /package/dist/{confidence.d.ts → src/confidence.d.ts} +0 -0
  287. /package/dist/{confidence.js → src/confidence.js} +0 -0
  288. /package/dist/{embeddings.d.ts → src/embeddings.d.ts} +0 -0
  289. /package/dist/{embeddings.js → src/embeddings.js} +0 -0
  290. /package/dist/{handlers → src/handlers}/advanced.d.ts +0 -0
  291. /package/dist/{handlers → src/handlers}/advanced.js +0 -0
  292. /package/dist/{handlers → src/handlers}/brain.d.ts +0 -0
  293. /package/dist/{handlers → src/handlers}/cache.d.ts +0 -0
  294. /package/dist/{handlers → src/handlers}/cache.js +0 -0
  295. /package/dist/{handlers → src/handlers}/context.d.ts +0 -0
  296. /package/dist/{handlers → src/handlers}/instances.d.ts +0 -0
  297. /package/dist/{handlers → src/handlers}/instances.js +0 -0
  298. /package/dist/{handlers → src/handlers}/roadmap.d.ts +0 -0
  299. /package/dist/{handlers → src/handlers}/syndicate.d.ts +0 -0
  300. /package/dist/{handlers → src/handlers}/syndicate.js +0 -0
  301. /package/dist/{handlers → src/handlers}/team.d.ts +0 -0
  302. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  303. /package/dist/{namespace.d.ts → src/namespace.d.ts} +0 -0
  304. /package/dist/{namespace.js → src/namespace.js} +0 -0
  305. /package/dist/{search.d.ts → src/search.d.ts} +0 -0
  306. /package/dist/{search.test.d.ts → src/search.test.d.ts} +0 -0
  307. /package/dist/{search.test.js → src/search.test.js} +0 -0
  308. /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
  309. /package/dist/{utils.js → src/utils.js} +0 -0
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { jwtExpiryMs, checkJwt, handleApiError } from './auth.js';
3
+ import { handleTcoTool, TCO_TOOL_NAMES } from './handlers/tco.js';
4
+ import { notify } from './notifier.js';
3
5
  import { readFile } from 'node:fs/promises';
4
6
  import { execSync } from 'node:child_process';
5
7
  import { join } from 'node:path';
@@ -37,11 +39,24 @@ const _isMain = process.argv[1] != null &&
37
39
  * ── Auth & Status ────────────────────────────────────────────────────────────
38
40
  * • get_api_status – check API health + JWT auth info (Keycloak)
39
41
  *
42
+ * ── Travel Chaos Organizer (TCO) ────────────────────────────────────────────
43
+ * • tco_list_trips – list all trips
44
+ * • tco_create_trip – create a new trip
45
+ * • tco_get_timeline – get timeline items for a trip
46
+ * • tco_delete_trip – delete a trip
47
+ * • tco_inbox_list – list Chaos Inbox items
48
+ * • tco_inbox_assign – assign inbox item to a trip
49
+ * • tco_inbox_reject – reject/discard an inbox item
50
+ * • tco_parse_url – fetch a URL and parse with Ollama
51
+ * • tco_import_email – import raw email text
52
+ * (Requires TCO_API_URL env var; auth forwarded from CACHLY_JWT — same Keycloak realm)
53
+ *
40
54
  * Configuration (env vars):
41
55
  * CACHLY_API_URL – default https://api.cachly.dev
42
56
  * CACHLY_JWT – your JWT (Keycloak access token)
43
57
  * CACHLY_EMBED_PROVIDER – embedding backend: openai (default), gemini, mistral, cohere, ollama, cachly (server fallback)
44
58
  * CACHLY_EMBED_MODEL – override embedding model (optional)
59
+ * TCO_API_URL – Travel Chaos Organizer backend URL (optional, enables tco_* tools)
45
60
  */
46
61
 
47
62
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -54,23 +69,19 @@ import {
54
69
  McpError,
55
70
  } from '@modelcontextprotocol/sdk/types.js';
56
71
  import { Redis } from 'ioredis';
57
- import { AsyncLocalStorage } from 'node:async_hooks';
58
72
 
59
73
  // ── Config ────────────────────────────────────────────────────────────────────
60
74
 
61
75
  const API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
62
76
  let JWT = process.env.CACHLY_JWT ?? '';
63
77
  const EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
64
- const CURRENT_VERSION = '0.10.47';
78
+ const CURRENT_VERSION = '0.10.49';
65
79
 
66
- // ── Per-request context (multi-tenant HTTP / Smithery) ───────────────────────
67
- // In stdio mode the module-level JWT/instance globals apply.
68
- // In HTTP mode, the request handler runs each request in an ALS scope that
69
- // shadows JWT + instance ID with per-user values extracted from headers.
70
- interface ReqCtx { jwt: string; instanceId: string; }
71
- const reqCtx = new AsyncLocalStorage<ReqCtx>();
72
- function getJWT(): string { return reqCtx.getStore()?.jwt || JWT; }
73
- function getDefaultInstanceId(): string { return reqCtx.getStore()?.instanceId || _defaultInstanceId; }
80
+ // Max time to wait for a freshly-provisioned instance to become "running" before
81
+ // giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
82
+ // old hard-coded 25s caused spurious "instance_not_reachable" on first run.
83
+ // Override via CACHLY_PROVISION_TIMEOUT_MS.
84
+ const PROVISION_TIMEOUT_MS = Number(process.env.CACHLY_PROVISION_TIMEOUT_MS ?? 90_000);
74
85
 
75
86
  // ── Default Instance Resolution (for Smithery & single-credential setups) ────
76
87
  // When CACHLY_BRAIN_INSTANCE_ID is set, tools can omit the instance_id parameter.
@@ -80,18 +91,15 @@ let _defaultInstanceId: string = process.env.CACHLY_BRAIN_INSTANCE_ID ?? '';
80
91
  let _defaultInstanceLastAttempt = 0;
81
92
 
82
93
  async function resolveDefaultInstanceId(): Promise<string> {
83
- // In an HTTP request scope the instance ID is fixed by the caller's header.
84
- const scopedId = reqCtx.getStore()?.instanceId;
85
- if (scopedId) return scopedId;
86
94
  if (_defaultInstanceId) return _defaultInstanceId;
87
- if (!getJWT()) return '';
95
+ if (!JWT) return '';
88
96
  // Cooldown: don't hammer the API on every tool call after a transient failure.
89
97
  const now = Date.now();
90
98
  if (_defaultInstanceLastAttempt > 0 && now - _defaultInstanceLastAttempt < 30_000) return '';
91
99
  _defaultInstanceLastAttempt = now;
92
100
  try {
93
101
  const res = await fetch(`${API_URL}/api/v1/instances`, {
94
- headers: { Authorization: `Bearer ${getJWT()}`, Accept: 'application/json' },
102
+ headers: { Authorization: `Bearer ${JWT}`, Accept: 'application/json' },
95
103
  signal: AbortSignal.timeout(5000),
96
104
  });
97
105
  if (!res.ok) return '';
@@ -180,6 +188,36 @@ async function pollDeviceFlow(flow: DeviceFlowState): Promise<'pending' | 'expir
180
188
  setEmbedJwt(apiKey); // keep embeddings.ts in sync
181
189
  _deviceFlow = null;
182
190
  sendFunnelEvent('device_flow_completed');
191
+ // Persist the API key to ~/.claude/mcp.json so subsequent MCP server restarts
192
+ // keep the token. Without this, JWT is lost on restart and brain_recall_count
193
+ // is never incremented (telemetry has no api_key → no tenant resolution).
194
+ void (async () => {
195
+ try {
196
+ const { writeFile, mkdir, readFile } = await import('node:fs/promises');
197
+ const { existsSync } = await import('node:fs');
198
+ const { resolve, dirname } = await import('node:path');
199
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
200
+ if (!home) return;
201
+ const configPath = resolve(home, '.claude', 'mcp.json');
202
+ let cfg: { mcpServers?: Record<string, { command?: string; args?: string[]; env?: Record<string, string> }> } = {};
203
+ if (existsSync(configPath)) {
204
+ try { cfg = JSON.parse(await readFile(configPath, 'utf-8')) as typeof cfg; } catch { /* corrupt — start fresh */ }
205
+ }
206
+ cfg.mcpServers ??= {};
207
+ const existing = cfg.mcpServers['cachly'];
208
+ if (existing) {
209
+ existing.env ??= {};
210
+ existing.env['CACHLY_JWT'] = apiKey;
211
+ } else {
212
+ cfg.mcpServers['cachly'] = {
213
+ command: 'npx', args: ['-y', '@cachly-dev/mcp-server@latest'],
214
+ env: { CACHLY_JWT: apiKey },
215
+ };
216
+ }
217
+ await mkdir(dirname(configPath), { recursive: true });
218
+ await writeFile(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
219
+ } catch { /* non-critical — don't break the flow on filesystem errors */ }
220
+ })();
183
221
  // Auto-provision: find or create the user's brain instance.
184
222
  _defaultInstanceLastAttempt = 0;
185
223
  await resolveDefaultInstanceId();
@@ -187,7 +225,7 @@ async function pollDeviceFlow(flow: DeviceFlowState): Promise<'pending' | 'expir
187
225
  try {
188
226
  const autoRes = await fetch(`${API_URL}/api/v1/instances/auto`, {
189
227
  method: 'POST',
190
- headers: { Authorization: `Bearer ${getJWT()}`, 'Content-Type': 'application/json' },
228
+ headers: { Authorization: `Bearer ${JWT}`, 'Content-Type': 'application/json' },
191
229
  signal: AbortSignal.timeout(12000),
192
230
  });
193
231
  if (autoRes.ok) {
@@ -202,7 +240,7 @@ async function pollDeviceFlow(flow: DeviceFlowState): Promise<'pending' | 'expir
202
240
  // Log so telemetry can detect free-tier auto-provision failures
203
241
  void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
204
242
  method: 'POST',
205
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getJWT()}` },
243
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${JWT}` },
206
244
  body: JSON.stringify({ event: 'auto_provision_failed', status: autoRes.status, version: CURRENT_VERSION }),
207
245
  signal: AbortSignal.timeout(3000),
208
246
  }).catch(() => {});
@@ -214,7 +252,7 @@ async function pollDeviceFlow(flow: DeviceFlowState): Promise<'pending' | 'expir
214
252
  if (_defaultInstanceId) {
215
253
  try {
216
254
  const checkRes = await fetch(`${API_URL}/api/v1/instances/${_defaultInstanceId}`, {
217
- headers: { Authorization: `Bearer ${getJWT()}`, Accept: 'application/json' },
255
+ headers: { Authorization: `Bearer ${JWT}`, Accept: 'application/json' },
218
256
  signal: AbortSignal.timeout(4000),
219
257
  });
220
258
  if (checkRes.ok) {
@@ -225,7 +263,7 @@ async function pollDeviceFlow(flow: DeviceFlowState): Promise<'pending' | 'expir
225
263
  while (checkInst.status === 'provisioning' && Date.now() < pDeadline) {
226
264
  await new Promise(r => setTimeout(r, 3000));
227
265
  const rr = await fetch(`${API_URL}/api/v1/instances/${_defaultInstanceId}`, {
228
- headers: { Authorization: `Bearer ${getJWT()}`, Accept: 'application/json' },
266
+ headers: { Authorization: `Bearer ${JWT}`, Accept: 'application/json' },
229
267
  signal: AbortSignal.timeout(4000),
230
268
  }).catch(() => null);
231
269
  if (rr?.ok) {
@@ -237,6 +275,29 @@ async function pollDeviceFlow(flow: DeviceFlowState): Promise<'pending' | 'expir
237
275
  }
238
276
  } catch { /* non-fatal — getConnection will handle the wait if still provisioning */ }
239
277
  }
278
+ // Persist instance_id to ~/.claude/mcp.json after it's known.
279
+ // Without this, every restart loses CACHLY_BRAIN_INSTANCE_ID → resolveDefaultInstanceId
280
+ // makes an extra API call on every startup (with 30s cooldown on failure).
281
+ if (_defaultInstanceId) {
282
+ void (async () => {
283
+ try {
284
+ const { writeFile, readFile } = await import('node:fs/promises');
285
+ const { existsSync } = await import('node:fs');
286
+ const { resolve } = await import('node:path');
287
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
288
+ if (!home) return;
289
+ const configPath = resolve(home, '.claude', 'mcp.json');
290
+ if (!existsSync(configPath)) return;
291
+ let cfg: { mcpServers?: Record<string, { env?: Record<string, string> }> } = {};
292
+ try { cfg = JSON.parse(await readFile(configPath, 'utf-8')) as typeof cfg; } catch { return; }
293
+ const entry = cfg.mcpServers?.['cachly'];
294
+ if (entry?.env) {
295
+ entry.env['CACHLY_BRAIN_INSTANCE_ID'] = _defaultInstanceId;
296
+ await writeFile(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
297
+ }
298
+ } catch { /* non-critical */ }
299
+ })();
300
+ }
240
301
  return 'done';
241
302
  }
242
303
  if (data.error === 'slow_down') flow.pollInterval = Math.min(flow.pollInterval + 2000, 15000);
@@ -296,26 +357,14 @@ async function getConnection(instance_id: string): Promise<Redis> {
296
357
  );
297
358
  }
298
359
 
299
- if (pool.has(instance_id)) {
300
- const cached = pool.get(instance_id)!;
301
- // Verify the pooled connection is still alive before reusing it.
302
- // ioredis 'error' eviction is async — a connection can be in the pool
303
- // but already internally broken (status 'reconnecting' or 'end').
304
- const status = (cached as any).status as string | undefined;
305
- if (status === 'ready' || status === 'connect') {
306
- return cached;
307
- }
308
- // Stale — evict and fall through to reconnect.
309
- pool.delete(instance_id);
310
- cached.disconnect();
311
- }
360
+ if (pool.has(instance_id)) return pool.get(instance_id)!;
312
361
 
313
- // Fetch instance, waiting up to 25 s if it is still provisioning.
362
+ // Fetch instance, waiting up to PROVISION_TIMEOUT_MS if it is still provisioning.
314
363
  // This covers the zero-friction path: device-flow auth → auto-provision → first tool call
315
364
  // all happen in quick succession and the instance isn't running yet.
316
365
  let inst = await apiFetch<Instance>(`/api/v1/instances/${instance_id}`);
317
366
  if (inst.status === 'provisioning') {
318
- const deadline = Date.now() + 25_000;
367
+ const deadline = Date.now() + PROVISION_TIMEOUT_MS;
319
368
  while (inst.status === 'provisioning' && Date.now() < deadline) {
320
369
  await new Promise(r => setTimeout(r, 3000));
321
370
  inst = await apiFetch<Instance>(`/api/v1/instances/${instance_id}`);
@@ -326,7 +375,7 @@ async function getConnection(instance_id: string): Promise<Redis> {
326
375
  // Telemetry: unreachable instance so the team can proactively investigate
327
376
  void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
328
377
  method: 'POST',
329
- headers: { 'Content-Type': 'application/json', ...(getJWT() ? { Authorization: `Bearer ${getJWT()}` } : {}) },
378
+ headers: { 'Content-Type': 'application/json', ...(JWT ? { Authorization: `Bearer ${JWT}` } : {}) },
330
379
  body: JSON.stringify({ event: 'instance_not_reachable', instance_id, status: inst.status, version: CURRENT_VERSION }),
331
380
  signal: AbortSignal.timeout(3000),
332
381
  }).catch(() => {});
@@ -379,6 +428,7 @@ async function getConnection(instance_id: string): Promise<Redis> {
379
428
  host: inst.host,
380
429
  port: inst.port,
381
430
  password: password || undefined,
431
+ commandTimeout: 8000,
382
432
  ...(tlsEnabled ? { tls: {} } : {}),
383
433
  lazyConnect: true,
384
434
  enableReadyCheck: true,
@@ -390,45 +440,14 @@ async function getConnection(instance_id: string): Promise<Redis> {
390
440
  client.on('error', evict);
391
441
  client.on('end', evict);
392
442
 
393
- // Retry up to 3 times with 2s backoff. Covers the window between K8s PodReady
394
- // and Valkey actually accepting connections (RDB/AOF load, ~1-3s on cold start).
395
- let connectErr: unknown;
396
- for (let attempt = 0; attempt < 3; attempt++) {
397
- try {
398
- if (attempt > 0) {
399
- await new Promise(r => setTimeout(r, 2000 * attempt));
400
- // Fresh client on retry — ioredis doesn't allow reconnect after explicit disconnect.
401
- client.disconnect();
402
- const retryClient = new Redis({
403
- host: inst.host,
404
- port: inst.port,
405
- password: password || undefined,
406
- ...(tlsEnabled ? { tls: {} } : {}),
407
- lazyConnect: true,
408
- enableReadyCheck: true,
409
- connectTimeout: 8000,
410
- retryStrategy: () => null,
411
- });
412
- retryClient.on('error', () => pool.delete(instance_id));
413
- retryClient.on('end', () => pool.delete(instance_id));
414
- await retryClient.connect();
415
- pool.set(instance_id, retryClient);
416
- return retryClient;
417
- }
418
- await client.connect();
419
- connectErr = undefined;
420
- break;
421
- } catch (err) {
422
- connectErr = err;
423
- }
424
- }
425
-
426
- if (connectErr) {
443
+ try {
444
+ await client.connect();
445
+ } catch (err: unknown) {
427
446
  client.disconnect();
428
- const msg = connectErr instanceof Error ? connectErr.message : String(connectErr);
447
+ const msg = err instanceof Error ? err.message : String(err);
429
448
  void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
430
449
  method: 'POST',
431
- headers: { 'Content-Type': 'application/json', ...(getJWT() ? { Authorization: `Bearer ${getJWT()}` } : {}) },
450
+ headers: { 'Content-Type': 'application/json', ...(JWT ? { Authorization: `Bearer ${JWT}` } : {}) },
432
451
  body: JSON.stringify({ event: 'redis_connect_failed', instance_id, host: inst.host, port: inst.port, error: msg, version: CURRENT_VERSION }),
433
452
  signal: AbortSignal.timeout(3000),
434
453
  }).catch(() => {});
@@ -448,14 +467,14 @@ async function getConnection(instance_id: string): Promise<Redis> {
448
467
  // ── API helper ────────────────────────────────────────────────────────────────
449
468
 
450
469
  async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
451
- checkJwt(getJWT());
470
+ checkJwt(JWT);
452
471
  const timeoutMs = Number(process.env.CACHLY_API_TIMEOUT_MS ?? 15_000);
453
472
  const res = await fetch(`${API_URL}${path}`, {
454
473
  ...options,
455
474
  signal: AbortSignal.timeout(timeoutMs),
456
475
  headers: {
457
476
  'Content-Type': 'application/json',
458
- Authorization: `Bearer ${getJWT()}`,
477
+ Authorization: `Bearer ${JWT}`,
459
478
  ...options.headers,
460
479
  },
461
480
  });
@@ -481,7 +500,7 @@ import { handleTeamTool } from './handlers/team.js';
481
500
  import { handleRoadmapTool } from './handlers/roadmap.js';
482
501
  import { handleAdvancedTool } from './handlers/advanced.js';
483
502
  import { handleSyndicateTool } from './handlers/syndicate.js';
484
- import { handleFedbrainTool } from './handlers/fedbrain.js';
503
+ import { handleFedbrainTool, _lastBrainFromGitCounts } from './handlers/fedbrain.js';
485
504
  import type { Instance } from './handlers/brain.js';
486
505
 
487
506
  // ── Tools (imported from tools.ts) ─────────────────────────────────────────
@@ -510,7 +529,7 @@ function sendFunnelEvent(event: string, extra?: Record<string, unknown>): void {
510
529
  headers: { 'Content-Type': 'application/json' },
511
530
  body: JSON.stringify({
512
531
  event, version: CURRENT_VERSION, editor: detectEditor(),
513
- ...(getJWT() ? { jwt: getJWT() } : {}),
532
+ ...(JWT ? { jwt: JWT } : {}),
514
533
  ...extra,
515
534
  }),
516
535
  signal: AbortSignal.timeout(3000),
@@ -533,7 +552,7 @@ async function sendAnonymousTelemetry(toolName: string): Promise<void> {
533
552
 
534
553
  async function handleTool(name: string, args: Record<string, unknown>): Promise<string> {
535
554
  // Guard: if no JWT, return actionable onboarding message instead of HTTP 401
536
- if (!getJWT()) {
555
+ if (!JWT) {
537
556
  void sendAnonymousTelemetry(name);
538
557
 
539
558
  // ── Zero-credential device flow ─────────────────────────────────────
@@ -613,14 +632,14 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
613
632
  // Delegate brain tools (learn, recall, session, etc.)
614
633
  const brainResult = await handleBrainTool(name, args, getConnection, apiFetch);
615
634
  if (brainResult !== null) {
616
- if (!_firstCallSuccessSent && getJWT()) {
635
+ if (!_firstCallSuccessSent && JWT) {
617
636
  _firstCallSuccessSent = true;
618
- sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? getDefaultInstanceId() ?? '' });
637
+ sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? _defaultInstanceId ?? '' });
619
638
  }
620
639
  // Per-tool telemetry — fires after every successful brain tool call.
621
640
  // api_key carries the cky_live_... token so the backend can resolve tenant + increment counters.
622
- const instanceId = (args.instance_id as string | undefined) ?? getDefaultInstanceId() ?? '';
623
- const telemetryExtra = getJWT() ? { api_key: getJWT(), instance_id: instanceId } : { instance_id: instanceId };
641
+ const instanceId = (args.instance_id as string | undefined) ?? _defaultInstanceId ?? '';
642
+ const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
624
643
  if (name === 'recall_best_solution') {
625
644
  // recall_best_solution → increments BrainRecallCount + triggers level-up logic
626
645
  sendFunnelEvent('recall_best_solution', telemetryExtra);
@@ -646,17 +665,14 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
646
665
  limit: 50,
647
666
  }, getConnection, apiFetch);
648
667
  if (gitBootstrap !== null) {
649
- sendFunnelEvent('brain_from_git', telemetryExtra);
668
+ const gitCounts = _lastBrainFromGitCounts;
669
+ sendFunnelEvent('brain_from_git', { ...telemetryExtra, ...(gitCounts ?? {}) });
650
670
  sendFunnelEvent('recall_best_solution', telemetryExtra);
651
671
  // Append bootstrap summary to the session_start briefing.
652
- const bootstrapText = typeof gitBootstrap === 'object' && gitBootstrap !== null && 'content' in gitBootstrap
653
- ? (gitBootstrap as { content: { text?: string }[] }).content.map((c) => c.text ?? '').join('\n')
654
- : String(gitBootstrap);
655
- if (typeof brainResult === 'object' && brainResult !== null && 'content' in brainResult) {
656
- (brainResult as { content: { type: string; text: string }[] }).content.push({
657
- type: 'text',
658
- text: '\n---\n' + bootstrapText,
659
- });
672
+ // brainResult is always a string here (handleBrainTool returns string for session_start).
673
+ const bootstrapText = String(gitBootstrap);
674
+ if (bootstrapText) {
675
+ return String(brainResult) + '\n---\n' + bootstrapText;
660
676
  }
661
677
  }
662
678
  } catch { /* git bootstrap errors must never break session_start */ }
@@ -671,7 +687,7 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
671
687
  const srText = typeof brainResult === 'object' && brainResult !== null && 'content' in brainResult
672
688
  ? JSON.stringify(brainResult)
673
689
  : String(brainResult ?? '');
674
- if (srText.length > 50 && !srText.includes('No lessons found') && !srText.includes('no lessons')) {
690
+ if (srText.length > 50 && !srText.includes('No lessons found') && !srText.includes('no lessons') && !srText.includes('No matches found')) {
675
691
  sendFunnelEvent('recall_best_solution', telemetryExtra);
676
692
  }
677
693
  }
@@ -681,9 +697,9 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
681
697
  // Delegate context tools (remember/recall/list/forget)
682
698
  const contextResult = await handleContextTool(name, args, getConnection, apiFetch);
683
699
  if (contextResult !== null) {
684
- if (!_firstCallSuccessSent && getJWT()) {
700
+ if (!_firstCallSuccessSent && JWT) {
685
701
  _firstCallSuccessSent = true;
686
- sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? getDefaultInstanceId() ?? '' });
702
+ sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? _defaultInstanceId ?? '' });
687
703
  }
688
704
  return contextResult;
689
705
  }
@@ -709,8 +725,8 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
709
725
  if (syndicateResult !== null) {
710
726
  // brain_predict is in syndicate handler — track telemetry here.
711
727
  if (name === 'brain_predict') {
712
- const instanceId = (args.instance_id as string | undefined) ?? getDefaultInstanceId() ?? '';
713
- const telemetryExtra = getJWT() ? { api_key: getJWT(), instance_id: instanceId } : { instance_id: instanceId };
728
+ const instanceId = (args.instance_id as string | undefined) ?? _defaultInstanceId ?? '';
729
+ const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
714
730
  sendFunnelEvent('brain_predict', telemetryExtra);
715
731
  }
716
732
  return syndicateResult;
@@ -719,16 +735,21 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
719
735
  const fedbrainResult = await handleFedbrainTool(name, args, getConnection, apiFetch);
720
736
  if (fedbrainResult !== null) {
721
737
  // brain_from_git and brain_predict_failures are in fedbrain handler.
722
- const instanceId = (args.instance_id as string | undefined) ?? getDefaultInstanceId() ?? '';
723
- const telemetryExtra = getJWT() ? { api_key: getJWT(), instance_id: instanceId } : { instance_id: instanceId };
738
+ const instanceId = (args.instance_id as string | undefined) ?? _defaultInstanceId ?? '';
739
+ const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
724
740
  if (name === 'brain_from_git') {
725
- sendFunnelEvent('brain_from_git', telemetryExtra);
741
+ const gitCounts = _lastBrainFromGitCounts;
742
+ sendFunnelEvent('brain_from_git', { ...telemetryExtra, ...(gitCounts ?? {}) });
726
743
  } else if (name === 'brain_predict_failures') {
727
744
  sendFunnelEvent('brain_predict_failures', telemetryExtra);
728
745
  }
729
746
  return fedbrainResult;
730
747
  }
731
748
 
749
+ // Delegate Travel Chaos Organizer tools (requires TCO_API_URL env var)
750
+ const tcoResult = await handleTcoTool(name, args, JWT ?? '');
751
+ if (tcoResult !== null) return tcoResult;
752
+
732
753
  switch (name) {
733
754
  // ── Instance management ──────────────────────────────────────────────
734
755
  case 'get_api_status': {
@@ -747,7 +768,7 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
747
768
  }
748
769
 
749
770
  // Check JWT / auth
750
- if (!getJWT()) {
771
+ if (!JWT) {
751
772
  return [
752
773
  `📡 **cachly API Status**`,
753
774
  ``,
@@ -762,7 +783,7 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
762
783
  // Decode JWT claims (inspection only, no verification)
763
784
  let authInfo = '❌ invalid JWT format';
764
785
  try {
765
- const parts = getJWT().split('.');
786
+ const parts = JWT.split('.');
766
787
  if (parts.length === 3) {
767
788
  const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8')) as {
768
789
  sub?: string; exp?: number; iss?: string;
@@ -795,7 +816,7 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
795
816
  const lines: string[] = ['\n\n🧠 **Brain Instances:**'];
796
817
  for (const inst of instances) {
797
818
  const badge = inst.status === 'running' ? '🟢' : inst.status === 'provisioning' ? '🟡' : '🔴';
798
- const defaultMark = inst.id === getDefaultInstanceId() ? ' ← active' : '';
819
+ const defaultMark = inst.id === _defaultInstanceId ? ' ← active' : '';
799
820
  lines.push(` ${badge} **${inst.name}** (\`${inst.id}\`) · ${inst.status}${defaultMark}`);
800
821
  if (inst.status === 'running' && inst.host) {
801
822
  // Try a quick ping on the cached connection
@@ -936,7 +957,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
936
957
  return { content: [{ type: 'text', text }] };
937
958
  } catch (err) {
938
959
  if (err instanceof McpError) throw err;
939
- throw new McpError(ErrorCode.InternalError, (err as Error).message);
960
+ const msg = (err as Error).message ?? String(err);
961
+ notify('cachly', 'tool_error', { tool: name, error: msg }).catch(() => undefined);
962
+ throw new McpError(ErrorCode.InternalError, msg);
940
963
  }
941
964
  });
942
965
 
@@ -2212,6 +2235,7 @@ if (process.argv[2] === 'setup') {
2212
2235
  const CLIENT_ID = 'cachly-cli';
2213
2236
 
2214
2237
  console.log('Step 1: Sign in to cachly (free, no credit card)\n');
2238
+ sendFunnelEvent('setup_auth_started');
2215
2239
 
2216
2240
  // Start device flow
2217
2241
  let deviceCode = '', userCode = '', verifyUri = '', pollInterval = 5000;
@@ -2235,6 +2259,7 @@ if (process.argv[2] === 'setup') {
2235
2259
  console.error('Falling back: sign in at https://cachly.dev/setup-ai and paste your API token.\n');
2236
2260
  token = await ask(' Paste API token (cky_live_...): ');
2237
2261
  if (!token) { console.error('\nToken is required. Aborting.\n'); rl.close(); process.exit(1); }
2262
+ sendFunnelEvent('setup_auth_completed');
2238
2263
  console.log('');
2239
2264
  deviceCode = ''; // mark as fallback so we skip polling
2240
2265
  }
@@ -2268,6 +2293,7 @@ if (process.argv[2] === 'setup') {
2268
2293
  if (tokenData.access_token) {
2269
2294
  token = tokenData.access_token;
2270
2295
  console.log(' \x1b[32m✓ Authorized!\x1b[0m\n');
2296
+ sendFunnelEvent('setup_auth_completed');
2271
2297
  break;
2272
2298
  }
2273
2299
  // authorization_pending = keep polling; slow_down = increase interval
@@ -2348,9 +2374,10 @@ if (process.argv[2] === 'setup') {
2348
2374
  // Newly created — poll until running or give up after 30 s.
2349
2375
  const newId = autoBody.instance_id;
2350
2376
  console.log(` ✓ created (${newId.slice(0, 8)}…)\n`);
2377
+ // 45 attempts × 3s = 135s — generous for free-tier in slow regions.
2351
2378
  process.stdout.write('⏳ Waiting for instance to start');
2352
- for (let attempt = 0; attempt < 15; attempt++) {
2353
- await new Promise(r => setTimeout(r, 2000));
2379
+ for (let attempt = 0; attempt < 45; attempt++) {
2380
+ await new Promise(r => setTimeout(r, 3000));
2354
2381
  process.stdout.write('.');
2355
2382
  try {
2356
2383
  const checkRes = await fetch(`${API_URL}/api/v1/instances/${newId}`, { headers: { Authorization: `Bearer ${token}` } });
@@ -2384,6 +2411,7 @@ if (process.argv[2] === 'setup') {
2384
2411
  console.log(` (Run with --instance-id <id> to use a different one)\n`);
2385
2412
  }
2386
2413
  console.log(`✓ Instance: ${instance.name} (${instance.id.slice(0, 8)}…)\n`);
2414
+ sendFunnelEvent('setup_instance_ready', { instance_id: instance.id });
2387
2415
 
2388
2416
  // ── Step 3: Detect editors ────────────────────────────────────────────────
2389
2417
  const cwd = process.cwd();
@@ -2629,7 +2657,7 @@ if (process.argv[2] === 'index') {
2629
2657
  const maxFiles = parseInt(flag('max-files') ?? '500', 10);
2630
2658
  const namespace = flag('namespace') ?? 'cachly:sem:code';
2631
2659
 
2632
- if (!instanceId || !getJWT()) {
2660
+ if (!instanceId || !JWT) {
2633
2661
  console.error('\n❌ CACHLY_BRAIN_INSTANCE_ID and CACHLY_JWT must be set\n');
2634
2662
  console.error(' export CACHLY_BRAIN_INSTANCE_ID=<uuid>');
2635
2663
  console.error(' export CACHLY_JWT=<cky_live_...>');
@@ -2671,7 +2699,7 @@ if (process.argv[2] === 'learn-git') {
2671
2699
  const instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
2672
2700
  const maxCommits = parseInt(flag('max-commits') ?? '50', 10);
2673
2701
 
2674
- if (!instanceId || !getJWT()) {
2702
+ if (!instanceId || !JWT) {
2675
2703
  console.error('\n❌ CACHLY_BRAIN_INSTANCE_ID and CACHLY_JWT must be set\n');
2676
2704
  console.error(' export CACHLY_BRAIN_INSTANCE_ID=<uuid>');
2677
2705
  console.error(' export CACHLY_JWT=<cky_live_...>');
@@ -2706,8 +2734,7 @@ if (process.argv[2] === 'learn-git') {
2706
2734
  // actionable message in their editor's MCP log instead of silent failures.
2707
2735
  // Skip for CLI commands that intentionally run without credentials.
2708
2736
  const _cliNoAuthCommands = ['demo', 'share', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade'];
2709
- // In multi-tenant HTTP mode (PORT set) we never need a startup JWT — per-request auth.
2710
- if (!JWT && !process.env.PORT && !_cliNoAuthCommands.includes(process.argv[2] ?? '') && !(!process.argv[2] && process.stdout.isTTY)) {
2737
+ if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '') && !(!process.argv[2] && process.stdout.isTTY)) {
2711
2738
  // No args + no JWT in a non-TTY context = running as MCP server without credentials.
2712
2739
  // Print actionable setup banner to stdout so it's captured by callers/tests.
2713
2740
  const banner =
@@ -2783,37 +2810,9 @@ const httpPort = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
2783
2810
  if (httpPort) {
2784
2811
  // ── HTTP mode (Streamable HTTP transport) ───────────────────────────────
2785
2812
  // Used for Smithery URL deployment: PORT=3000 node dist/index.js
2786
- // Multi-tenant: per-request JWT + instance ID resolved via:
2787
- // 1. Authorization: Bearer <jwt> + X-Cachly-Instance-Id: <uuid>
2788
- // 2. Smithery config: ?config=base64url({CACHLY_JWT, CACHLY_BRAIN_INSTANCE_ID, ...})
2789
- // Falls back to module-level env-derived values when neither is provided.
2790
2813
  const { createServer } = await import('node:http');
2791
2814
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
2792
2815
  await server.connect(transport);
2793
-
2794
- function extractReqCtx(req: import('node:http').IncomingMessage, url: URL): ReqCtx {
2795
- let jwtVal = '';
2796
- let instId = '';
2797
- const auth = req.headers['authorization'];
2798
- if (typeof auth === 'string' && auth.toLowerCase().startsWith('bearer ')) {
2799
- jwtVal = auth.slice(7).trim();
2800
- }
2801
- const hdrInst = req.headers['x-cachly-instance-id'];
2802
- if (typeof hdrInst === 'string') instId = hdrInst.trim();
2803
- // Smithery passes user config as a base64url-encoded JSON blob in ?config=
2804
- const cfgParam = url.searchParams.get('config');
2805
- if (cfgParam && (!jwtVal || !instId)) {
2806
- try {
2807
- const b64 = cfgParam.replace(/-/g, '+').replace(/_/g, '/');
2808
- const padded = b64 + '='.repeat((4 - b64.length % 4) % 4);
2809
- const json = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
2810
- if (!jwtVal && typeof json.CACHLY_JWT === 'string') jwtVal = json.CACHLY_JWT;
2811
- if (!instId && typeof json.CACHLY_BRAIN_INSTANCE_ID === 'string') instId = json.CACHLY_BRAIN_INSTANCE_ID;
2812
- } catch { /* malformed config — ignore, fall back to globals */ }
2813
- }
2814
- return { jwt: jwtVal, instanceId: instId };
2815
- }
2816
-
2817
2816
  const httpServer = createServer((req, res) => {
2818
2817
  const url = new URL(req.url ?? '/', `http://localhost:${httpPort}`);
2819
2818
  if (url.pathname === '/health') {
@@ -2821,8 +2820,6 @@ if (httpPort) {
2821
2820
  res.end(JSON.stringify({
2822
2821
  status: 'ok',
2823
2822
  version: CURRENT_VERSION,
2824
- transport: 'streamable-http',
2825
- multiTenant: true,
2826
2823
  zeroResults: {
2827
2824
  total: zeroResultsTotal,
2828
2825
  last10: ZERO_RESULTS_LOG.slice(-10).map(e => ({ query: e.query, ts: new Date(e.ts).toISOString() })),
@@ -2831,16 +2828,7 @@ if (httpPort) {
2831
2828
  return;
2832
2829
  }
2833
2830
  if (url.pathname === '/mcp' || url.pathname === '/') {
2834
- const ctx = extractReqCtx(req, url);
2835
- reqCtx.run(ctx, () => {
2836
- transport.handleRequest(req, res).catch((err) => {
2837
- process.stderr.write(`cachly-mcp HTTP handler error: ${(err as Error).message}\n`);
2838
- if (!res.headersSent) {
2839
- res.writeHead(500, { 'Content-Type': 'application/json' });
2840
- res.end(JSON.stringify({ error: 'internal_error' }));
2841
- }
2842
- });
2843
- });
2831
+ transport.handleRequest(req, res);
2844
2832
  return;
2845
2833
  }
2846
2834
  res.writeHead(404);
@@ -2855,3 +2843,5 @@ if (httpPort) {
2855
2843
  await server.connect(transport);
2856
2844
  }
2857
2845
 
2846
+ notify('cachly', 'startup', { version: CURRENT_VERSION, mode: process.env.MCP_HTTP_PORT ? 'http' : 'stdio' }).catch(() => undefined);
2847
+
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Re-exports from the shared telegram-notify package.
3
+ * Canonical implementation: packages/telegram-notify/client.ts
4
+ */
5
+ export { notify, formatMessage } from '../packages/telegram-notify/client.js';
package/src/search.ts CHANGED
@@ -384,7 +384,7 @@ function hindiLightStem(word: string): string {
384
384
  * настройки → настройк (genitive plural)
385
385
  */
386
386
  function russianLightStem(word: string): string {
387
- let w = word;
387
+ const w = word;
388
388
  // Longest first to avoid partial stripping
389
389
  // Verb infinitives / gerunds
390
390
  if (w.endsWith('ывание') && w.length > 7) return w.slice(0, -6);
@@ -427,7 +427,7 @@ function russianLightStem(word: string): string {
427
427
  * yüklemek → yükle (-mek/-mak infinitive)
428
428
  */
429
429
  function turkishLightStem(word: string): string {
430
- let w = word;
430
+ const w = word;
431
431
  // Suffixes longest-first
432
432
  if (w.endsWith('lardaki') && w.length > 7) return w.slice(0, -7);
433
433
  if (w.endsWith('lerdeki') && w.length > 7) return w.slice(0, -7);
@@ -475,7 +475,7 @@ function turkishLightStem(word: string): string {
475
475
  * সমস্যাটি → সমস্যা (-টি singular marker)
476
476
  */
477
477
  function bengaliLightStem(word: string): string {
478
- let w = word;
478
+ const w = word;
479
479
  // Plural / collective markers (longest first)
480
480
  if (w.endsWith('\u0997\u09c1\u09b2\u09cb') && w.length > 5) return w.slice(0, -4); // গুলো (-gulo plural)
481
481
  if (w.endsWith('\u0997\u09c1\u09b2\u09bf') && w.length > 5) return w.slice(0, -4); // গুলি (-guli plural)