@cachly-dev/mcp-server 0.10.56 β 0.10.57
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/.github/workflows/eas-build-android.yml +58 -0
- package/.github/workflows/eas-update.yml +55 -0
- package/.github/workflows/publish.yml +2 -2
- package/BENCH.md +91 -0
- package/CHANGELOG.md +44 -0
- package/README.md +6 -34
- package/STRATEGY.md +228 -0
- package/apps/travel-chaos-organizer/.env.example +49 -0
- package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
- package/apps/travel-chaos-organizer/Makefile +57 -0
- package/apps/travel-chaos-organizer/README.md +159 -0
- package/apps/travel-chaos-organizer/backend/.env.example +10 -0
- package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
- package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
- package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
- package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
- package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
- package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
- package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
- package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
- package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
- package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
- package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
- package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
- package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
- package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
- package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
- package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
- package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
- package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
- package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
- package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
- package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
- package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
- package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
- package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
- package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
- package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
- package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
- package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
- package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
- package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
- package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
- package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
- package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
- package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
- package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
- package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
- package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
- package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
- package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
- package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
- package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
- package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
- package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
- package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
- package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
- package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
- package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
- package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
- package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
- package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
- package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
- package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
- package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
- package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
- package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
- package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
- package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
- package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
- package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
- package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
- package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
- package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
- package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
- package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
- package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
- package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
- package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
- package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
- package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
- package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
- package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
- package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
- package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
- package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
- package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
- package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
- package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
- package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
- package/apps/travel-chaos-organizer/frontend/package-lock.json +13221 -0
- package/apps/travel-chaos-organizer/frontend/package.json +44 -0
- package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
- package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
- package/apps/travel-chaos-organizer/landing/index.html +773 -0
- package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
- package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
- package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
- package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
- package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
- package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
- package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
- package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
- package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
- package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
- package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
- package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
- package/dist/packages/telegram-notify/client.d.ts +19 -0
- package/dist/packages/telegram-notify/client.js +75 -0
- package/dist/packages/telegram-notify/client.js.map +1 -0
- package/dist/src/__tests__/auth.test.js.map +1 -0
- package/dist/{__tests__ β src/__tests__}/brain-flow.test.js +141 -0
- package/dist/src/__tests__/brain-flow.test.js.map +1 -0
- package/dist/src/__tests__/confidence.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
- package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
- package/dist/src/__tests__/e2e/config.js.map +1 -0
- package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
- package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
- package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
- package/dist/{__tests__ β src/__tests__}/handlers-unit.test.js +0 -2
- package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
- package/dist/src/__tests__/namespace.test.js.map +1 -0
- package/dist/src/__tests__/rerank.test.js +115 -0
- package/dist/src/__tests__/rerank.test.js.map +1 -0
- package/dist/src/auth.js.map +1 -0
- package/dist/src/bench/cachly-bench.d.ts +35 -0
- package/dist/src/bench/cachly-bench.js +182 -0
- package/dist/src/bench/cachly-bench.js.map +1 -0
- package/dist/src/bench/fixtures.d.ts +37 -0
- package/dist/src/bench/fixtures.js +225 -0
- package/dist/src/bench/fixtures.js.map +1 -0
- package/dist/src/ckg.js.map +1 -0
- package/dist/src/confidence.js.map +1 -0
- package/dist/src/embeddings.js.map +1 -0
- package/dist/src/handlers/advanced.js.map +1 -0
- package/dist/{handlers β src/handlers}/brain.js +335 -193
- package/dist/src/handlers/brain.js.map +1 -0
- package/dist/src/handlers/cache.js.map +1 -0
- package/dist/{handlers β src/handlers}/context.js +9 -4
- package/dist/src/handlers/context.js.map +1 -0
- package/dist/{handlers β src/handlers}/fedbrain.d.ts +6 -0
- package/dist/{handlers β src/handlers}/fedbrain.js +19 -7
- package/dist/src/handlers/fedbrain.js.map +1 -0
- package/dist/src/handlers/instances.js.map +1 -0
- package/dist/{handlers β src/handlers}/roadmap.js +7 -3
- package/dist/src/handlers/roadmap.js.map +1 -0
- package/dist/src/handlers/syndicate.js.map +1 -0
- package/dist/src/handlers/tco.d.ts +16 -0
- package/dist/src/handlers/tco.js +303 -0
- package/dist/src/handlers/tco.js.map +1 -0
- package/dist/{handlers β src/handlers}/team.js +57 -7
- package/dist/src/handlers/team.js.map +1 -0
- package/dist/{index.js β src/index.js} +209 -431
- package/dist/src/index.js.map +1 -0
- package/dist/src/namespace.js.map +1 -0
- package/dist/src/notifier.d.ts +5 -0
- package/dist/src/notifier.js +6 -0
- package/dist/src/notifier.js.map +1 -0
- package/dist/src/rerank.d.ts +55 -0
- package/dist/src/rerank.js +111 -0
- package/dist/src/rerank.js.map +1 -0
- package/dist/src/search.js.map +1 -0
- package/dist/src/search.test.d.ts +1 -0
- package/dist/src/search.test.js.map +1 -0
- package/dist/{tools.d.ts β src/tools.d.ts} +226 -0
- package/dist/{tools.js β src/tools.js} +196 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +8 -7
- package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
- package/packages/telegram-notify/client.ts +88 -0
- package/packages/telegram-notify/emojis.json +22 -0
- package/packages/telegram-notify/pyproject.toml +17 -0
- package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
- package/packages/telegram-notify/telegram_notify/client.py +87 -0
- package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
- package/scripts/generate-icons.js +28 -0
- package/server.json +3 -3
- package/smithery.yaml +27 -16
- package/src/__tests__/brain-flow.test.ts +174 -0
- package/src/__tests__/handlers-unit.test.ts +1 -2
- package/src/__tests__/rerank.test.ts +130 -0
- package/src/bench/cachly-bench.ts +217 -0
- package/src/bench/fixtures.ts +256 -0
- package/src/handlers/brain.ts +345 -197
- package/src/handlers/context.ts +9 -4
- package/src/handlers/fedbrain.ts +21 -7
- package/src/handlers/roadmap.ts +7 -3
- package/src/handlers/tco.ts +347 -0
- package/src/handlers/team.ts +67 -7
- package/src/index.ts +224 -418
- package/src/notifier.ts +5 -0
- package/src/rerank.ts +148 -0
- package/src/tools.ts +210 -0
- package/tsconfig.json +2 -3
- package/dist/__tests__/auth.test.js.map +0 -1
- package/dist/__tests__/brain-flow.test.js.map +0 -1
- package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
- package/dist/__tests__/brain-predict-failures.test.js +0 -339
- package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
- package/dist/__tests__/confidence.test.js.map +0 -1
- package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
- package/dist/__tests__/e2e/api-health.test.js.map +0 -1
- package/dist/__tests__/e2e/cli.test.js.map +0 -1
- package/dist/__tests__/e2e/config.js.map +0 -1
- package/dist/__tests__/e2e/funnel.test.js.map +0 -1
- package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
- package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
- package/dist/__tests__/handlers-unit.test.js.map +0 -1
- package/dist/__tests__/namespace.test.js.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/ckg.js.map +0 -1
- package/dist/confidence.js.map +0 -1
- package/dist/embeddings.js.map +0 -1
- package/dist/handlers/advanced.js.map +0 -1
- package/dist/handlers/brain.js.map +0 -1
- package/dist/handlers/cache.js.map +0 -1
- package/dist/handlers/context.js.map +0 -1
- package/dist/handlers/fedbrain.js.map +0 -1
- package/dist/handlers/instances.js.map +0 -1
- package/dist/handlers/roadmap.js.map +0 -1
- package/dist/handlers/syndicate.js.map +0 -1
- package/dist/handlers/team.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/namespace.js.map +0 -1
- package/dist/search.js.map +0 -1
- package/dist/search.test.js.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/src/__tests__/brain-predict-failures.test.ts +0 -430
- /package/dist/{__tests__ β src/__tests__}/auth.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/auth.test.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/brain-flow.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/confidence.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/confidence.test.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/api-auth.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/api-auth.test.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/api-health.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/api-health.test.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/cli.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/cli.test.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/config.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/config.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/funnel.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/funnel.test.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/mcp-tools.test.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/provisioning.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/e2e/provisioning.test.js +0 -0
- /package/dist/{__tests__ β src/__tests__}/handlers-unit.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/namespace.test.d.ts +0 -0
- /package/dist/{__tests__ β src/__tests__}/namespace.test.js +0 -0
- /package/dist/{search.test.d.ts β src/__tests__/rerank.test.d.ts} +0 -0
- /package/dist/{auth.d.ts β src/auth.d.ts} +0 -0
- /package/dist/{auth.js β src/auth.js} +0 -0
- /package/dist/{ckg.d.ts β src/ckg.d.ts} +0 -0
- /package/dist/{ckg.js β src/ckg.js} +0 -0
- /package/dist/{confidence.d.ts β src/confidence.d.ts} +0 -0
- /package/dist/{confidence.js β src/confidence.js} +0 -0
- /package/dist/{embeddings.d.ts β src/embeddings.d.ts} +0 -0
- /package/dist/{embeddings.js β src/embeddings.js} +0 -0
- /package/dist/{handlers β src/handlers}/advanced.d.ts +0 -0
- /package/dist/{handlers β src/handlers}/advanced.js +0 -0
- /package/dist/{handlers β src/handlers}/brain.d.ts +0 -0
- /package/dist/{handlers β src/handlers}/cache.d.ts +0 -0
- /package/dist/{handlers β src/handlers}/cache.js +0 -0
- /package/dist/{handlers β src/handlers}/context.d.ts +0 -0
- /package/dist/{handlers β src/handlers}/instances.d.ts +0 -0
- /package/dist/{handlers β src/handlers}/instances.js +0 -0
- /package/dist/{handlers β src/handlers}/roadmap.d.ts +0 -0
- /package/dist/{handlers β src/handlers}/syndicate.d.ts +0 -0
- /package/dist/{handlers β src/handlers}/syndicate.js +0 -0
- /package/dist/{handlers β src/handlers}/team.d.ts +0 -0
- /package/dist/{index.d.ts β src/index.d.ts} +0 -0
- /package/dist/{namespace.d.ts β src/namespace.d.ts} +0 -0
- /package/dist/{namespace.js β src/namespace.js} +0 -0
- /package/dist/{search.d.ts β src/search.d.ts} +0 -0
- /package/dist/{search.js β src/search.js} +0 -0
- /package/dist/{search.test.js β src/search.test.js} +0 -0
- /package/dist/{utils.d.ts β src/utils.d.ts} +0 -0
- /package/dist/{utils.js β src/utils.js} +0 -0
package/src/handlers/context.ts
CHANGED
|
@@ -64,13 +64,18 @@ export async function handleContextTool(
|
|
|
64
64
|
if (ttl && ttl > 0) {
|
|
65
65
|
body['expires_at'] = new Date(Date.now() + ttl * 1000).toISOString();
|
|
66
66
|
}
|
|
67
|
-
await fetch(`${vectorUrl}/entries`, {
|
|
67
|
+
const res = await fetch(`${vectorUrl}/entries`, {
|
|
68
68
|
method: 'POST',
|
|
69
69
|
headers: { 'Content-Type': 'application/json' },
|
|
70
70
|
body: JSON.stringify(body),
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
signal: AbortSignal.timeout(8000),
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
process.stderr.write(`[cachly] semantic index skipped (HTTP ${res.status}) β smart_recall on this context will be keyword-only\n`);
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
// Embedding is optional β keyword recall still works. Log so it's debuggable.
|
|
78
|
+
process.stderr.write(`[cachly] semantic index failed: ${e instanceof Error ? e.message : String(e)} β smart_recall on this context will be keyword-only\n`);
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
|
package/src/handlers/fedbrain.ts
CHANGED
|
@@ -9,6 +9,9 @@ import { keywordSearch, tokenize } from '../search.js';
|
|
|
9
9
|
import { safeJsonParse } from '../utils.js';
|
|
10
10
|
import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
|
|
11
11
|
|
|
12
|
+
// Last brain_from_git category counts β set after each run so index.ts can include them in telemetry
|
|
13
|
+
export let _lastBrainFromGitCounts: { fixes: number; features: number; refactors: number; total: number } | null = null;
|
|
14
|
+
|
|
12
15
|
// Git concurrency semaphore (for brain_from_git parallel workers)
|
|
13
16
|
let _gitSemCount = 0;
|
|
14
17
|
const _GIT_SEM_MAX = 10;
|
|
@@ -363,8 +366,6 @@ export async function handleFedbrainTool(
|
|
|
363
366
|
|
|
364
367
|
if (hooksArr.includes('ci')) {
|
|
365
368
|
lines.push(`### GitHub Actions CI outcome hook`);
|
|
366
|
-
lines.push(`**Setup:** add a repo secret \`CACHLY_CACHE_TOKEN\` = your instance's cache token`);
|
|
367
|
-
lines.push(`(find it via \`get_connection_string\` β the token in the cache URL).`);
|
|
368
369
|
lines.push(`**Add at the end of each job** (after build/test steps):`);
|
|
369
370
|
lines.push('```yaml');
|
|
370
371
|
lines.push(`- name: cachly CLS β record CI outcome`);
|
|
@@ -372,14 +373,15 @@ export async function handleFedbrainTool(
|
|
|
372
373
|
lines.push(` run: |`);
|
|
373
374
|
lines.push(` node -e "`);
|
|
374
375
|
lines.push(` const r=require('https');`);
|
|
375
|
-
lines.push(` const d=JSON.stringify({source:'ci_outcome',payload:{`);
|
|
376
|
+
lines.push(` const d=JSON.stringify({instance_id:'${instance_id}',source:'ci_outcome',payload:{`);
|
|
376
377
|
lines.push(` status:'\${{ job.status }}',prev_status:'unknown',job:'\${{ github.job }}',`);
|
|
377
378
|
lines.push(` context:'github-actions run \${{ github.run_number }}'}});`);
|
|
378
|
-
lines.push(` r.request({hostname:'api.cachly.dev',path:'/v1/
|
|
379
|
-
lines.push(` headers:{'Content-Type':'application/json','
|
|
379
|
+
lines.push(` r.request({hostname:'api.cachly.dev',path:'/api/v1/cls/ingest',method:'POST',`);
|
|
380
|
+
lines.push(` headers:{'Content-Type':'application/json','Authorization':'Bearer $CACHLY_JWT',`);
|
|
381
|
+
lines.push(` 'Content-Length':d.length}},()=>{}).end(d);`);
|
|
380
382
|
lines.push(` " 2>/dev/null || true`);
|
|
381
383
|
lines.push(` env:`);
|
|
382
|
-
lines.push(`
|
|
384
|
+
lines.push(` CACHLY_JWT: \${{ secrets.CACHLY_JWT }}`);
|
|
383
385
|
lines.push('```');
|
|
384
386
|
lines.push('');
|
|
385
387
|
}
|
|
@@ -1013,11 +1015,16 @@ export async function handleFedbrainTool(
|
|
|
1013
1015
|
// Only store if no existing lesson for this topic (avoid overwriting higher-confidence lessons)
|
|
1014
1016
|
const existing = await redis.get(`cachly:lesson:best:${topic}`);
|
|
1015
1017
|
if (!existing) {
|
|
1016
|
-
|
|
1018
|
+
// Auto-inferred git lessons get a 90-day TTL β they self-refresh on the next
|
|
1019
|
+
// brain_from_git run, and shouldn't pin memory forever if the repo goes stale.
|
|
1020
|
+
await redis.set(`cachly:lesson:best:${topic}`, JSON.stringify(lessonObj), 'EX', 90 * 86400);
|
|
1017
1021
|
await redis.rpush(`cachly:lessons:${topic}`, JSON.stringify(lessonObj));
|
|
1022
|
+
await redis.ltrim(`cachly:lessons:${topic}`, -100, -1);
|
|
1023
|
+
await redis.expire(`cachly:lessons:${topic}`, 90 * 86400);
|
|
1018
1024
|
}
|
|
1019
1025
|
await redis.rpush('cachly:lessons:brain_from_git:all', JSON.stringify({ topic, sha: commit.sha.slice(0, 8), subject: commit.subject.slice(0, 60) }));
|
|
1020
1026
|
await redis.ltrim('cachly:lessons:brain_from_git:all', -500, -1);
|
|
1027
|
+
await redis.expire('cachly:lessons:brain_from_git:all', 90 * 86400);
|
|
1021
1028
|
|
|
1022
1029
|
// Update CKG
|
|
1023
1030
|
const conceptId = ckgSlug(topic);
|
|
@@ -1034,6 +1041,13 @@ export async function handleFedbrainTool(
|
|
|
1034
1041
|
|
|
1035
1042
|
const categoryBreakdown = [...categoryCount.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => ` β’ **${k}** (${v})`).join('\n');
|
|
1036
1043
|
|
|
1044
|
+
_lastBrainFromGitCounts = {
|
|
1045
|
+
fixes: categoryCount.get('fix') ?? 0,
|
|
1046
|
+
features: categoryCount.get('feat') ?? 0,
|
|
1047
|
+
refactors: categoryCount.get('refactor') ?? 0,
|
|
1048
|
+
total: ingested,
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1037
1051
|
return [
|
|
1038
1052
|
`π **brain_from_git: ${repoDir}**`,
|
|
1039
1053
|
isIncremental ? `π Incremental mode β only new commits since \`${lastSha.slice(0, 8)}\` were processed` : '',
|
package/src/handlers/roadmap.ts
CHANGED
|
@@ -157,9 +157,12 @@ export async function handleRoadmapTool(
|
|
|
157
157
|
const PRIORITY_ORDER: Record<string, number> = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
158
158
|
const PRIORITY_ICON: Record<string, string> = { critical: 'π΄', high: 'π ', medium: 'π‘', low: 'π΅' };
|
|
159
159
|
const items = Object.values(all)
|
|
160
|
-
.
|
|
160
|
+
.flatMap(v => {
|
|
161
|
+
const item = safeJsonParse<Record<string, unknown> | null>(v as string, null);
|
|
162
|
+
return item ? [item] : [];
|
|
163
|
+
})
|
|
161
164
|
.filter(i => i.status === 'in-progress' || i.status === 'planned')
|
|
162
|
-
.filter(i => !filterTag || (i.tags
|
|
165
|
+
.filter(i => !filterTag || (Array.isArray(i.tags) ? i.tags : []).includes(filterTag));
|
|
163
166
|
if (items.length === 0) return 'π **No open roadmap items!** All tasks are done (or use `roadmap_list` to check).';
|
|
164
167
|
// in-progress first, then by priority
|
|
165
168
|
items.sort((a, b) => {
|
|
@@ -169,7 +172,8 @@ export async function handleRoadmapTool(
|
|
|
169
172
|
});
|
|
170
173
|
const next = items[0];
|
|
171
174
|
const remaining = items.length - 1;
|
|
172
|
-
const
|
|
175
|
+
const nextTags = Array.isArray(next.tags) ? next.tags as string[] : [];
|
|
176
|
+
const tags = nextTags.length ? `\nTags: ${nextTags.join(', ')}` : '';
|
|
173
177
|
const milestone = next.milestone ? `\nMilestone: ${next.milestone}` : '';
|
|
174
178
|
const notes = next.notes ? `\nNotes: ${(next.notes as string).split('\n').pop()?.slice(0, 120)}` : '';
|
|
175
179
|
return [
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Travel Chaos Organizer β MCP bridge.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the TCO REST API as MCP tools so any Claude instance with cachly-mcp
|
|
5
|
+
* configured can manage travel plans through natural language.
|
|
6
|
+
*
|
|
7
|
+
* Auth: TCO and Cachly share the same Keycloak realm, so the Cachly JWT
|
|
8
|
+
* (CACHLY_JWT env var) is forwarded as-is to the TCO backend β no extra
|
|
9
|
+
* credentials needed.
|
|
10
|
+
*
|
|
11
|
+
* Config:
|
|
12
|
+
* TCO_API_URL β base URL of the TCO FastAPI backend (e.g. http://localhost:8000)
|
|
13
|
+
* If not set, all tools return a configuration error.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const TCO_URL = process.env.TCO_API_URL ?? '';
|
|
17
|
+
|
|
18
|
+
export const TCO_TOOL_NAMES = new Set([
|
|
19
|
+
'tco_list_trips',
|
|
20
|
+
'tco_create_trip',
|
|
21
|
+
'tco_get_timeline',
|
|
22
|
+
'tco_delete_trip',
|
|
23
|
+
'tco_inbox_list',
|
|
24
|
+
'tco_inbox_assign',
|
|
25
|
+
'tco_inbox_reject',
|
|
26
|
+
'tco_parse_url',
|
|
27
|
+
'tco_import_email',
|
|
28
|
+
'tco_parse_text',
|
|
29
|
+
'tco_cache_stats',
|
|
30
|
+
'tco_provision_cache',
|
|
31
|
+
'tco_telegram_status',
|
|
32
|
+
'tco_telegram_send',
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
type TcoFetch = (path: string, jwt: string, init?: RequestInit) => Promise<unknown>;
|
|
36
|
+
|
|
37
|
+
async function tcoFetch(path: string, jwt: string, init: RequestInit = {}): Promise<unknown> {
|
|
38
|
+
if (!TCO_URL) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
'TCO_API_URL is not configured. Add `TCO_API_URL=http://<your-tco-host>:8000` to your environment.'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const res = await fetch(`${TCO_URL}${path}`, {
|
|
44
|
+
...init,
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${jwt}`,
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
...(init.headers ?? {}),
|
|
49
|
+
},
|
|
50
|
+
signal: AbortSignal.timeout(20_000),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const text = await res.text().catch(() => '');
|
|
55
|
+
throw new Error(`TCO API ${res.status}: ${text}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ct = res.headers.get('content-type') ?? '';
|
|
59
|
+
if (res.status === 204 || !ct.includes('application/json')) return null;
|
|
60
|
+
return res.json();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function noConfig(): string {
|
|
64
|
+
return (
|
|
65
|
+
'βοΈ **TCO not configured.**\n\n' +
|
|
66
|
+
'Set `TCO_API_URL=http://<host>:8000` in your environment and restart cachly-mcp.\n' +
|
|
67
|
+
'See `apps/travel-chaos-organizer/README.md` for setup instructions.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function fmt(data: unknown): string {
|
|
72
|
+
return JSON.stringify(data, null, 2);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function handleTcoTool(
|
|
76
|
+
name: string,
|
|
77
|
+
args: Record<string, unknown>,
|
|
78
|
+
jwt: string,
|
|
79
|
+
): Promise<string | null> {
|
|
80
|
+
if (!TCO_TOOL_NAMES.has(name)) return null;
|
|
81
|
+
if (!TCO_URL) return noConfig();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
switch (name) {
|
|
85
|
+
|
|
86
|
+
// ββ List all trips βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
87
|
+
case 'tco_list_trips': {
|
|
88
|
+
const trips = await tcoFetch('/api/v1/trips', jwt);
|
|
89
|
+
const list = trips as Array<Record<string, unknown>>;
|
|
90
|
+
if (!list.length) return 'π No trips yet. Use `tco_create_trip` to get started.';
|
|
91
|
+
const rows = list.map((t) => {
|
|
92
|
+
const dates = t.start_date
|
|
93
|
+
? `${t.start_date}${t.end_date ? ` β ${t.end_date}` : ''}`
|
|
94
|
+
: 'no dates';
|
|
95
|
+
return `β’ **${t.name}** \`${t.id}\` β ${dates}`;
|
|
96
|
+
});
|
|
97
|
+
return `πΊοΈ **Your trips (${list.length}):**\n\n${rows.join('\n')}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ββ Create a trip ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
101
|
+
case 'tco_create_trip': {
|
|
102
|
+
const { name: tripName, description = null, start_date = null, end_date = null } = args as {
|
|
103
|
+
name: string; description?: string | null; start_date?: string | null; end_date?: string | null;
|
|
104
|
+
};
|
|
105
|
+
const trip = await tcoFetch('/api/v1/trips', jwt, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
body: JSON.stringify({ name: tripName, description, start_date, end_date }),
|
|
108
|
+
});
|
|
109
|
+
const t = trip as Record<string, unknown>;
|
|
110
|
+
return `β
Trip **${t.name}** created (id: \`${t.id}\`)`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ββ Get timeline for a trip ββββββββββββββββββββββββββββββββββββββββββ
|
|
114
|
+
case 'tco_get_timeline': {
|
|
115
|
+
const { trip_id } = args as { trip_id: string };
|
|
116
|
+
const items = await tcoFetch(`/api/v1/trips/${trip_id}/items`, jwt);
|
|
117
|
+
const list = items as Array<Record<string, unknown>>;
|
|
118
|
+
if (!list.length) return `π Trip \`${trip_id}\` has no timeline items yet. Upload a document to get started.`;
|
|
119
|
+
|
|
120
|
+
const TYPE_ICONS: Record<string, string> = {
|
|
121
|
+
flight: 'βοΈ', train: 'π', bus: 'π', hotel: 'π¨',
|
|
122
|
+
rental_car: 'π', activity: 'π‘', transfer: 'π', document: 'π', other: 'π',
|
|
123
|
+
};
|
|
124
|
+
const rows = list.map((i) => {
|
|
125
|
+
const icon = TYPE_ICONS[i.type as string] ?? 'π';
|
|
126
|
+
const time = i.event_at ? ` Β· ${(i.event_at as string).slice(0, 16).replace('T', ' ')}` : '';
|
|
127
|
+
const ref = i.booking_ref ? ` Β· #${i.booking_ref}` : '';
|
|
128
|
+
const provider = i.provider ? ` Β· ${i.provider}` : '';
|
|
129
|
+
return `${icon} **${i.title}**${time}${provider}${ref}`;
|
|
130
|
+
});
|
|
131
|
+
return `π
**Timeline (${list.length} items):**\n\n${rows.join('\n')}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ββ Delete a trip ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
135
|
+
case 'tco_delete_trip': {
|
|
136
|
+
const { trip_id } = args as { trip_id: string };
|
|
137
|
+
await tcoFetch(`/api/v1/trips/${trip_id}`, jwt, { method: 'DELETE' });
|
|
138
|
+
return `ποΈ Trip \`${trip_id}\` deleted.`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ββ Inbox: list pending items ββββββββββββββββββββββββββββββββββββββββ
|
|
142
|
+
case 'tco_inbox_list': {
|
|
143
|
+
const status = (args.status as string | undefined) ?? 'pending';
|
|
144
|
+
const items = await tcoFetch(`/api/v1/inbox?status_filter=${status}`, jwt);
|
|
145
|
+
const list = items as Array<Record<string, unknown>>;
|
|
146
|
+
if (!list.length) return `π Chaos inbox is empty (status: ${status}).`;
|
|
147
|
+
const rows = list.map((i) => {
|
|
148
|
+
const pd = i.parsed_data as Record<string, unknown> | null;
|
|
149
|
+
const title = (pd?.title as string | undefined) ?? '(no title)';
|
|
150
|
+
const src = (i.source as string | undefined) ?? 'unknown source';
|
|
151
|
+
return `β’ **${title}** \`${i.id}\` β from ${src}`;
|
|
152
|
+
});
|
|
153
|
+
return `π₯ **Inbox (${list.length} ${status}):**\n\n${rows.join('\n')}\n\nUse \`tco_inbox_assign\` to move to a trip.`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ββ Inbox: assign item to trip βββββββββββββββββββββββββββββββββββββββ
|
|
157
|
+
case 'tco_inbox_assign': {
|
|
158
|
+
const { inbox_id, trip_id, type = 'other' } = args as {
|
|
159
|
+
inbox_id: string; trip_id: string; type?: string;
|
|
160
|
+
};
|
|
161
|
+
const result = await tcoFetch(`/api/v1/inbox/${inbox_id}/assign`, jwt, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
body: JSON.stringify({ trip_id, type }),
|
|
164
|
+
});
|
|
165
|
+
const r = result as Record<string, unknown>;
|
|
166
|
+
return `β
Inbox item assigned to trip. New timeline item id: \`${r.trip_item_id}\``;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ββ Inbox: reject / delete item ββββββββββββββββββββββββββββββββββββββ
|
|
170
|
+
case 'tco_inbox_reject': {
|
|
171
|
+
const { inbox_id } = args as { inbox_id: string };
|
|
172
|
+
await tcoFetch(`/api/v1/inbox/${inbox_id}`, jwt, { method: 'DELETE' });
|
|
173
|
+
return `ποΈ Inbox item \`${inbox_id}\` rejected and removed.`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ββ Parse a public URL into a trip βββββββββββββββββββββββββββββββββββ
|
|
177
|
+
case 'tco_parse_url': {
|
|
178
|
+
const { url, trip_id } = args as { url: string; trip_id?: string };
|
|
179
|
+
const body: Record<string, string> = { url };
|
|
180
|
+
if (trip_id) body.trip_id = trip_id;
|
|
181
|
+
const result = await tcoFetch('/api/v1/parse/url', jwt, {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
body: JSON.stringify(body),
|
|
184
|
+
});
|
|
185
|
+
const r = result as Record<string, unknown>;
|
|
186
|
+
const confidence = typeof r.confidence === 'number' ? ` (confidence: ${Math.round(r.confidence * 100)}%)` : '';
|
|
187
|
+
const dest = trip_id ? `trip \`${trip_id}\`` : 'chaos inbox';
|
|
188
|
+
return `β
URL parsed and saved to ${dest}${confidence}.\n\nParsed: ${fmt(r.parsed_data ?? r)}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ββ Import email text into inbox or trip βββββββββββββββββββββββββββββ
|
|
192
|
+
case 'tco_import_email': {
|
|
193
|
+
const { raw_email, trip_id } = args as { raw_email: string; trip_id?: string };
|
|
194
|
+
const body: Record<string, string> = { raw_email };
|
|
195
|
+
if (trip_id) body.trip_id = trip_id;
|
|
196
|
+
const result = await tcoFetch('/api/v1/mail/import', jwt, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
body: JSON.stringify(body),
|
|
199
|
+
});
|
|
200
|
+
const r = result as Record<string, unknown>;
|
|
201
|
+
const dest = trip_id ? `trip \`${trip_id}\`` : 'chaos inbox';
|
|
202
|
+
return `β
Email imported to ${dest}. Inbox id: \`${r.inbox_id ?? '?'}\``;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ββ Parse raw text via Ollama (Cachly-cached) ββββββββββββββββββββββββ
|
|
206
|
+
case 'tco_parse_text': {
|
|
207
|
+
const { text, trip_id } = args as { text: string; trip_id?: string };
|
|
208
|
+
const params = new URLSearchParams();
|
|
209
|
+
params.set('raw_text', text);
|
|
210
|
+
if (trip_id) params.set('trip_id', trip_id);
|
|
211
|
+
const result = await tcoFetch('/api/v1/parse/text', jwt, {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
body: params.toString(),
|
|
214
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
215
|
+
});
|
|
216
|
+
const r = result as Record<string, unknown>;
|
|
217
|
+
const parsed = r.parsed as Record<string, unknown> | undefined;
|
|
218
|
+
const cached = (r as Record<string, unknown>).cached;
|
|
219
|
+
const dest = trip_id ? `trip \`${trip_id}\`` : 'Chaos Inbox';
|
|
220
|
+
const cacheNote = cached ? ' β‘ **(Cache Hit β kein AI-Call)**' : '';
|
|
221
|
+
const confidence = typeof parsed?.confidence === 'number'
|
|
222
|
+
? ` (confidence: ${Math.round((parsed.confidence as number) * 100)}%)`
|
|
223
|
+
: '';
|
|
224
|
+
return `β
Text geparst und in ${dest} gespeichert${confidence}${cacheNote}.\n\nParsed: ${fmt(parsed ?? r)}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ββ Cachly Redis cache stats βββββββββββββββββββββββββββββββββββββββββ
|
|
228
|
+
case 'tco_cache_stats': {
|
|
229
|
+
const stats = await tcoFetch('/api/v1/cache/stats', jwt) as Record<string, unknown>;
|
|
230
|
+
if (!stats.configured) {
|
|
231
|
+
return (
|
|
232
|
+
'π **Cachly Redis ist nicht verbunden.**\n\n' +
|
|
233
|
+
'Setze `CACHLY_REDIS_URL=redis://<host>:6379/0` in der TCO `.env` und deploye neu.\n' +
|
|
234
|
+
'Nutze `tco_provision_cache` um eine kostenlose Cachly-Instanz zu erstellen.'
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const hits = stats.hits as number;
|
|
238
|
+
const misses = stats.misses as number;
|
|
239
|
+
const total = hits + misses;
|
|
240
|
+
const hitPct = total > 0 ? Math.round((hits / total) * 100) : 0;
|
|
241
|
+
const saved = hits > 0
|
|
242
|
+
? `\n\nπ‘ **~${hits} AI-Calls eingespart** (jeder Cache Hit = kein Ollama-Aufruf).`
|
|
243
|
+
: '';
|
|
244
|
+
return (
|
|
245
|
+
`β
**Cachly Redis β aktiv**\n\n` +
|
|
246
|
+
`| Metric | Wert |\n|--------|------|\n` +
|
|
247
|
+
`| Cache Hits | **${hits}** |\n` +
|
|
248
|
+
`| Cache Misses | ${misses} |\n` +
|
|
249
|
+
`| Hit Rate | **${hitPct}%** |\n` +
|
|
250
|
+
`| Gecachte Dokumente | ${stats.key_count} |\n` +
|
|
251
|
+
`| TTL | ${stats.ttl_days} Tage |` +
|
|
252
|
+
saved
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ββ Provision a Cachly free instance for TCO βββββββββββββββββββββββββ
|
|
257
|
+
case 'tco_provision_cache': {
|
|
258
|
+
const CACHLY_API = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
|
|
259
|
+
const res = await fetch(`${CACHLY_API}/api/v1/instances`, {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: {
|
|
262
|
+
Authorization: `Bearer ${jwt}`,
|
|
263
|
+
'Content-Type': 'application/json',
|
|
264
|
+
},
|
|
265
|
+
body: JSON.stringify({ name: 'tco-cache', tier: 'free' }),
|
|
266
|
+
signal: AbortSignal.timeout(30_000),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
const txt = await res.text().catch(() => '');
|
|
271
|
+
// Instance may already exist β try to fetch it
|
|
272
|
+
if (res.status === 409 || txt.includes('already')) {
|
|
273
|
+
const listRes = await fetch(`${CACHLY_API}/api/v1/instances`, {
|
|
274
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
275
|
+
});
|
|
276
|
+
if (listRes.ok) {
|
|
277
|
+
const list = await listRes.json() as Array<Record<string, unknown>>;
|
|
278
|
+
const existing = list.find((i) => i.name === 'tco-cache');
|
|
279
|
+
if (existing) {
|
|
280
|
+
return (
|
|
281
|
+
`βΉοΈ **Instanz "tco-cache" existiert bereits.**\n\n` +
|
|
282
|
+
`Redis URL: \`${existing.redis_url ?? existing.connection_string ?? '(siehe Cachly Dashboard)'}\`\n\n` +
|
|
283
|
+
`Setze in der TCO \`.env\`:\n\`\`\`\nCACHLY_REDIS_URL=${existing.redis_url ?? existing.connection_string}\n\`\`\`\n` +
|
|
284
|
+
`Dann: \`bash scripts/deploy.sh\``
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
throw new Error(`Cachly API ${res.status}: ${txt}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const instance = await res.json() as Record<string, unknown>;
|
|
293
|
+
const redisUrl = instance.redis_url ?? instance.connection_string ?? '';
|
|
294
|
+
return (
|
|
295
|
+
`π **Cachly Redis Instanz erstellt!**\n\n` +
|
|
296
|
+
`Name: \`tco-cache\` (Free Tier, 25 MB)\n` +
|
|
297
|
+
`Redis URL: \`${redisUrl}\`\n\n` +
|
|
298
|
+
`**Setup (2 Schritte):**\n` +
|
|
299
|
+
`1. FΓΌge zur TCO \`.env\` hinzu:\n\`\`\`\nCACHLY_REDIS_URL=${redisUrl}\n\`\`\`\n` +
|
|
300
|
+
`2. Neu deployen:\n\`\`\`bash\nbash scripts/deploy.sh\n\`\`\`\n\n` +
|
|
301
|
+
`Ab sofort werden alle Ollama Parse-Ergebnisse gecacht β kein zweiter AI-Call fΓΌr dasselbe Dokument.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ββ Telegram: check linked status ββββββββββββββββββββββββββββββββββββ
|
|
306
|
+
case 'tco_telegram_status': {
|
|
307
|
+
const me = await tcoFetch('/api/v1/users/me', jwt) as Record<string, unknown>;
|
|
308
|
+
const linked = me.telegram_linked === true;
|
|
309
|
+
if (linked) {
|
|
310
|
+
return (
|
|
311
|
+
`β
**Telegram ist verknΓΌpft.**\n\n` +
|
|
312
|
+
`Plan: ${me.plan === 'pro' ? 'β¦ Pro' : 'Free'}\n\n` +
|
|
313
|
+
`Du kannst mit \`tco_telegram_send\` direkt Nachrichten an den User senden.`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return (
|
|
317
|
+
`π΄ **Telegram nicht verknΓΌpft.**\n\n` +
|
|
318
|
+
`Der User muss in der TCO-App unter *Einstellungen β Mit Telegram verknΓΌpfen* einen PIN generieren ` +
|
|
319
|
+
`und diesen an @TCOBot senden (\`/link <PIN>\`).`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ββ Telegram: send message to user βββββββββββββββββββββββββββββββββββ
|
|
324
|
+
case 'tco_telegram_send': {
|
|
325
|
+
const { message } = args as { message: string };
|
|
326
|
+
const me = await tcoFetch('/api/v1/users/me', jwt) as Record<string, unknown>;
|
|
327
|
+
if (!me.telegram_linked) {
|
|
328
|
+
return (
|
|
329
|
+
`π΄ **Telegram nicht verknΓΌpft** β Nachricht wurde nicht gesendet.\n\n` +
|
|
330
|
+
`Nutze \`tco_telegram_status\` fΓΌr Details zur VerknΓΌpfung.`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
await tcoFetch('/api/v1/users/telegram-notify', jwt, {
|
|
334
|
+
method: 'POST',
|
|
335
|
+
body: JSON.stringify({ message }),
|
|
336
|
+
});
|
|
337
|
+
return `β
Telegram-Nachricht gesendet:\n\n> ${message.slice(0, 200)}${message.length > 200 ? 'β¦' : ''}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
default:
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
345
|
+
return `β TCO error: ${msg}`;
|
|
346
|
+
}
|
|
347
|
+
}
|
package/src/handlers/team.ts
CHANGED
|
@@ -11,7 +11,7 @@ type GetConnection = (instanceId: string) => Promise<Redis>;
|
|
|
11
11
|
type ApiFetch = <T>(path: string, options?: RequestInit) => Promise<T>;
|
|
12
12
|
|
|
13
13
|
export const TEAM_TOOL_NAMES = new Set([
|
|
14
|
-
'sync_file_changes', 'team_learn', 'team_recall', 'team_synthesize', 'memory_crystalize',
|
|
14
|
+
'sync_file_changes', 'team_learn', 'team_confirm', 'team_recall', 'team_synthesize', 'memory_crystalize',
|
|
15
15
|
'brain_doctor', 'recall_at', 'trace_dependency', 'global_learn', 'global_recall',
|
|
16
16
|
'publish_lesson', 'import_public_brain', 'setup_ai_memory',
|
|
17
17
|
]);
|
|
@@ -109,9 +109,7 @@ export async function handleTeamTool(
|
|
|
109
109
|
file_paths: file_paths ?? [],
|
|
110
110
|
commands: commands ?? [],
|
|
111
111
|
tags: [...(tags ?? []), 'team'],
|
|
112
|
-
|
|
113
|
-
verified_at: outcome === 'success' ? new Date().toISOString() : undefined,
|
|
114
|
-
confidence: 1.0,
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
115
113
|
recall_count: 0,
|
|
116
114
|
version: 2,
|
|
117
115
|
};
|
|
@@ -126,6 +124,59 @@ export async function handleTeamTool(
|
|
|
126
124
|
return `β
Team lesson stored by **${author}**: \`${topic}\` (${outcome})\nπ‘ ${what_worked.slice(0, 120)}`;
|
|
127
125
|
}
|
|
128
126
|
|
|
127
|
+
// ββ team_confirm β knowledge governance (Phase 3) βββββββββββββββββββββββββ
|
|
128
|
+
// A human reviewer endorses a stored lesson. The endorsement raises the
|
|
129
|
+
// lesson's recall ranking (see src/rerank.ts), so trusted, reviewed knowledge
|
|
130
|
+
// surfaces above unreviewed auto-learned entries.
|
|
131
|
+
case 'team_confirm': {
|
|
132
|
+
const { instance_id, topic, reviewer, level = 'peer', note = '' } = args as {
|
|
133
|
+
instance_id: string; topic: string; reviewer: string;
|
|
134
|
+
level?: 'senior' | 'peer'; note?: string;
|
|
135
|
+
};
|
|
136
|
+
if (!instance_id) return 'β instance_id required';
|
|
137
|
+
if (!topic || !reviewer) return 'β Required: topic, reviewer';
|
|
138
|
+
const reviewLevel = level === 'senior' ? 'senior' : 'peer';
|
|
139
|
+
|
|
140
|
+
const redis = await getConnection(instance_id);
|
|
141
|
+
const key = `cachly:lesson:best:${topic}`;
|
|
142
|
+
const raw = await redis.get(key);
|
|
143
|
+
const lesson = safeJsonParse<Record<string, unknown> | null>(raw, null);
|
|
144
|
+
if (!lesson) {
|
|
145
|
+
return `π No best-solution lesson found for \`${topic}\`.\n\nA lesson must exist (store one with \`team_learn\` / \`learn_from_attempts\`) before it can be confirmed.`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Track distinct reviewers so endorsements can't be inflated by one person.
|
|
149
|
+
const endorsersKey = `cachly:lesson:endorsers:${topic}`;
|
|
150
|
+
const added = await redis.sadd(endorsersKey, reviewer);
|
|
151
|
+
await redis.expire(endorsersKey, 365 * 86400);
|
|
152
|
+
const endorsements = await redis.scard(endorsersKey);
|
|
153
|
+
|
|
154
|
+
// Senior beats peer; never downgrade an existing senior review to peer.
|
|
155
|
+
const prevLevel = (lesson.review_level as string) ?? '';
|
|
156
|
+
const effectiveLevel = prevLevel === 'senior' ? 'senior' : reviewLevel;
|
|
157
|
+
|
|
158
|
+
const updated = {
|
|
159
|
+
...lesson,
|
|
160
|
+
reviewed_by: reviewer,
|
|
161
|
+
review_level: effectiveLevel,
|
|
162
|
+
endorsements,
|
|
163
|
+
reviewed_at: new Date().toISOString(),
|
|
164
|
+
};
|
|
165
|
+
await redis.set(key, JSON.stringify(updated));
|
|
166
|
+
|
|
167
|
+
// Append an immutable review record for the audit trail.
|
|
168
|
+
const reviewLog = {
|
|
169
|
+
reviewer, level: reviewLevel, note: note.slice(0, 280), ts: new Date().toISOString(),
|
|
170
|
+
};
|
|
171
|
+
const logKey = `cachly:lesson:reviews:${topic}`;
|
|
172
|
+
await redis.rpush(logKey, JSON.stringify(reviewLog));
|
|
173
|
+
await redis.ltrim(logKey, -50, -1);
|
|
174
|
+
await redis.expire(logKey, 365 * 86400);
|
|
175
|
+
|
|
176
|
+
const badge = effectiveLevel === 'senior' ? 'π‘οΈ senior-reviewed' : 'βοΈ peer-reviewed';
|
|
177
|
+
return `${badge} β \`${topic}\` confirmed by **${reviewer}** (${endorsements} distinct endorsement${endorsements === 1 ? '' : 's'}).\nπ This lesson now ranks higher in \`smart_recall\` / \`team_recall\`.${note ? `\nπ ${note.slice(0, 200)}` : ''}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
129
180
|
// ββ team_recall βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
130
181
|
case 'team_recall': {
|
|
131
182
|
const { instance_id, topic, author, limit = 10 } = args as {
|
|
@@ -148,6 +199,7 @@ export async function handleTeamTool(
|
|
|
148
199
|
topic: string; outcome: string; what_worked: string;
|
|
149
200
|
ts: string; severity?: string; recall_count?: number;
|
|
150
201
|
author?: string; tags?: string[];
|
|
202
|
+
reviewed_by?: string; review_level?: string; endorsements?: number;
|
|
151
203
|
};
|
|
152
204
|
let lessons: TeamLesson[] = [];
|
|
153
205
|
if (lessonKeys.length > 0) {
|
|
@@ -171,8 +223,10 @@ export async function handleTeamTool(
|
|
|
171
223
|
lessons = lessons.filter(l => l.author?.toLowerCase().includes(a));
|
|
172
224
|
}
|
|
173
225
|
|
|
174
|
-
// Sort by recall_count desc
|
|
175
|
-
|
|
226
|
+
// Sort: reviewed lessons first (senior > peer), then by recall_count desc.
|
|
227
|
+
const reviewRank = (l: TeamLesson) => l.reviewed_by ? (l.review_level === 'senior' ? 2 : 1) : 0;
|
|
228
|
+
lessons.sort((a, b) =>
|
|
229
|
+
(reviewRank(b) - reviewRank(a)) || ((b.recall_count ?? 0) - (a.recall_count ?? 0)));
|
|
176
230
|
lessons = lessons.slice(0, limit);
|
|
177
231
|
|
|
178
232
|
if (lessons.length === 0) {
|
|
@@ -185,11 +239,17 @@ export async function handleTeamTool(
|
|
|
185
239
|
for (const l of lessons) {
|
|
186
240
|
const emoji = l.outcome === 'success' ? 'β
' : l.outcome === 'partial' ? 'β οΈ' : 'β';
|
|
187
241
|
const sev = l.severity === 'critical' ? 'π΄ ' : l.severity === 'major' ? 'π‘ ' : '';
|
|
242
|
+
const reviewBadge = l.reviewed_by
|
|
243
|
+
? (l.review_level === 'senior' ? ' π‘οΈ' : ' βοΈ')
|
|
244
|
+
: '';
|
|
188
245
|
const authorStr = l.author ? ` Β· _by ${l.author}_` : '';
|
|
246
|
+
const reviewStr = l.reviewed_by
|
|
247
|
+
? ` Β· reviewed by ${l.reviewed_by}${(l.endorsements ?? 0) > 1 ? ` +${(l.endorsements ?? 0) - 1}` : ''}`
|
|
248
|
+
: '';
|
|
189
249
|
const recallStr = (l.recall_count ?? 0) > 0 ? ` Β· recalled ${l.recall_count}Γ` : '';
|
|
190
250
|
const ago = Math.round((Date.now() - new Date(l.ts).getTime()) / 86400000);
|
|
191
251
|
const agoStr = ago === 0 ? 'today' : ago === 1 ? 'yesterday' : `${ago}d ago`;
|
|
192
|
-
lines.push(`${emoji} ${sev}**\`${l.topic}\`**${authorStr}${recallStr} Β· ${agoStr}`);
|
|
252
|
+
lines.push(`${emoji} ${sev}**\`${l.topic}\`**${reviewBadge}${authorStr}${reviewStr}${recallStr} Β· ${agoStr}`);
|
|
193
253
|
lines.push(` ${l.what_worked.slice(0, 120)}`);
|
|
194
254
|
lines.push('');
|
|
195
255
|
}
|