this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: add strict TypeScript/ESLint/Prettier toolchain

- Add strict tsconfig.json with all strict flags enabled
- Add ESLint flat config with strictTypeChecked rules
- Add Prettier config (120 width, semi, single quotes)
- Add package.json scripts: typecheck, lint, format, check, fix
- Update CLAUDE.md with subagent handoff protocol
- Fix all TypeScript and ESLint errors across codebase
- Update health check test to handle both running/stopped services

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice 518d83f4 06af883d

+705 -322
+1
.beads/issues.jsonl
··· 25 25 {"id":"assistant-pqh.2","title":"Telegram bot setup with Telegraf (src/bot.ts)","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-11T13:44:26.327934Z","updated_at":"2025-12-11T14:22:36.187044Z","closed_at":"2025-12-11T14:22:36.187044Z","dependencies":[{"issue_id":"assistant-pqh.2","depends_on_id":"assistant-pqh","type":"parent-child","created_at":"2025-12-11T13:44:26.328387Z","created_by":"daemon"}]} 26 26 {"id":"assistant-pqh.3","title":"Secret token verification","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-11T13:44:27.745088Z","updated_at":"2025-12-11T14:23:32.170307Z","closed_at":"2025-12-11T14:23:32.170307Z","dependencies":[{"issue_id":"assistant-pqh.3","depends_on_id":"assistant-pqh","type":"parent-child","created_at":"2025-12-11T13:44:27.745547Z","created_by":"daemon"}]} 27 27 {"id":"assistant-pqh.4","title":"Basic message flow to Letta and back","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-11T13:44:28.893703Z","updated_at":"2025-12-11T14:22:37.312394Z","closed_at":"2025-12-11T14:22:37.312394Z","dependencies":[{"issue_id":"assistant-pqh.4","depends_on_id":"assistant-pqh","type":"parent-child","created_at":"2025-12-11T13:44:28.894172Z","created_by":"daemon"}]} 28 + {"id":"assistant-r3f","title":"Set up TypeScript, ESLint, Prettier toolchain","description":"","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-11T14:25:35.643117Z","updated_at":"2025-12-11T14:34:19.722101Z","closed_at":"2025-12-11T14:34:19.722101Z"} 28 29 {"id":"assistant-swt","title":"M3: Tone + Detection","description":"","status":"open","priority":1,"issue_type":"epic","created_at":"2025-12-11T13:43:37.059037Z","updated_at":"2025-12-11T13:43:37.059037Z","dependencies":[{"issue_id":"assistant-swt","depends_on_id":"assistant-nno","type":"blocks","created_at":"2025-12-11T13:43:51.868818Z","created_by":"daemon"}]} 29 30 {"id":"assistant-swt.1","title":"Overwhelm detection (src/detect.ts)","description":"","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-11T13:44:50.906825Z","updated_at":"2025-12-11T13:44:50.906825Z","dependencies":[{"issue_id":"assistant-swt.1","depends_on_id":"assistant-swt","type":"parent-child","created_at":"2025-12-11T13:44:50.90734Z","created_by":"daemon"}]} 30 31 {"id":"assistant-swt.2","title":"Self-bullying detection (src/detect.ts)","description":"","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-11T13:44:51.940846Z","updated_at":"2025-12-11T13:44:51.940846Z","dependencies":[{"issue_id":"assistant-swt.2","depends_on_id":"assistant-swt","type":"parent-child","created_at":"2025-12-11T13:44:51.941304Z","created_by":"daemon"}]}
+6
.prettierignore
··· 1 + node_modules 2 + dist 3 + bun.lock 4 + *.md 5 + .beads 6 + history
+11
.prettierrc
··· 1 + { 2 + "printWidth": 120, 3 + "semi": true, 4 + "singleQuote": true, 5 + "trailingComma": "es5", 6 + "tabWidth": 2, 7 + "useTabs": false, 8 + "bracketSpacing": true, 9 + "arrowParens": "always", 10 + "endOfLine": "lf" 11 + }
+47
CLAUDE.md
··· 108 108 109 109 --- 110 110 111 + ## Code Quality (MANDATORY) 112 + 113 + **CRITICAL**: All code changes MUST pass these checks before completion: 114 + 115 + ```bash 116 + bun run check # Runs all checks: typecheck, lint, format, test 117 + ``` 118 + 119 + ### Individual Commands 120 + 121 + ```bash 122 + bun run typecheck # TypeScript strict checking with tsgo 123 + bun run lint # ESLint with strict rules 124 + bun run format:check # Prettier format verification 125 + bun test # Run all tests 126 + ``` 127 + 128 + ### Auto-fix Commands 129 + 130 + ```bash 131 + bun run fix # Auto-fix lint and format issues 132 + bun run lint:fix # Fix ESLint issues only 133 + bun run format # Fix Prettier issues only 134 + ``` 135 + 136 + ### Subagent Handoff Protocol 137 + 138 + **Before completing ANY task**, subagents MUST: 139 + 140 + 1. Run `bun run check` and ensure ALL checks pass 141 + 2. Fix any errors found (use `bun run fix` for auto-fixable issues) 142 + 3. Re-run `bun run check` to verify fixes 143 + 4. Only mark task complete when all checks pass 144 + 145 + **If checks fail and cannot be fixed:** 146 + - Document the specific errors in the task completion message 147 + - Do NOT mark task as complete 148 + - Escalate to orchestrator for resolution 149 + 150 + ### Configuration Files 151 + 152 + - `tsconfig.json` - Strict TypeScript (all strict flags enabled) 153 + - `eslint.config.js` - ESLint flat config with typescript-eslint strict 154 + - `.prettierrc` - Prettier (120 chars, semicolons, single quotes) 155 + 156 + --- 157 + 111 158 ## Bun Runtime 112 159 113 160 Default to using Bun instead of Node.js.
+226
bun.lock
··· 11 11 "telegraf": "^4.16.3", 12 12 }, 13 13 "devDependencies": { 14 + "@eslint/js": "^9.39.1", 14 15 "@types/bun": "latest", 15 16 "@typescript/native-preview": "^7.0.0-dev.20251211.1", 17 + "eslint": "^9.39.1", 18 + "eslint-config-prettier": "^10.1.8", 19 + "eslint-plugin-prettier": "^5.5.4", 20 + "prettier": "^3.7.4", 21 + "typescript-eslint": "^8.49.0", 16 22 }, 17 23 }, 18 24 }, ··· 25 31 26 32 "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="], 27 33 34 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], 35 + 36 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 37 + 38 + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], 39 + 40 + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], 41 + 42 + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], 43 + 44 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], 45 + 46 + "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], 47 + 48 + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], 49 + 50 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], 51 + 52 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 53 + 54 + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], 55 + 56 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 57 + 58 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 59 + 28 60 "@letta-ai/letta-client": ["@letta-ai/letta-client@1.3.3", "", {}, "sha512-1pSmkmeuXAN9Lq8PXDO2YSLK0q6O39zh2BHlulVhtc8P3aNxAJF7XBteiAhL2hzb49wF2zv0mi2Uikr67CTUCw=="], 29 61 30 62 "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], 31 63 64 + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], 65 + 32 66 "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], 33 67 34 68 "@telegraf/types": ["@telegraf/types@7.1.0", "", {}, "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw=="], 35 69 36 70 "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], 37 71 72 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 73 + 74 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 75 + 38 76 "@types/node": ["@types/node@25.0.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew=="], 39 77 78 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.49.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/type-utils": "8.49.0", "@typescript-eslint/utils": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A=="], 79 + 80 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.49.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA=="], 81 + 82 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.49.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.49.0", "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g=="], 83 + 84 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0" } }, "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg=="], 85 + 86 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.49.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA=="], 87 + 88 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg=="], 89 + 90 + "@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="], 91 + 92 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.49.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.49.0", "@typescript-eslint/tsconfig-utils": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA=="], 93 + 94 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.49.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA=="], 95 + 96 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA=="], 97 + 40 98 "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20251211.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251211.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251211.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251211.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251211.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251211.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251211.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251211.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-RXuRj/zn2tWrria1eea1mzOVmUjNHdOZsxlcnXLy2BjXil+ncgdMFARWryeXP2+NPmGTwC+ERJ5YAuwU8n4nlg=="], 41 99 42 100 "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20251211.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0TSLj8s2M1eQXnQV0+DMFCnJF4vqNobTaeKzMpR8oHOsD2az93knOUixsZk0Nyf3jYzgszDakNXhp0K3fzWWAw=="], ··· 57 115 58 116 "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], 59 117 118 + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 119 + 120 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 121 + 60 122 "ai": ["ai@5.0.110", "", { "dependencies": { "@ai-sdk/gateway": "2.0.19", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZBq+5bvef4e5qoIG4U6NJ1UpCPWGjuaWERHXbHu2T2ND3c02nJ2zlnjm+N6zAAplQPxwqm7Sb16mrRX5uQNWtQ=="], 61 123 124 + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 125 + 126 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 127 + 128 + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 129 + 130 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 131 + 132 + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 133 + 62 134 "buffer-alloc": ["buffer-alloc@1.2.0", "", { "dependencies": { "buffer-alloc-unsafe": "^1.1.0", "buffer-fill": "^1.0.0" } }, "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow=="], 63 135 64 136 "buffer-alloc-unsafe": ["buffer-alloc-unsafe@1.1.0", "", {}, "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="], ··· 67 139 68 140 "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], 69 141 142 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 143 + 144 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 145 + 146 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 147 + 148 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 149 + 150 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 151 + 152 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 153 + 70 154 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 71 155 156 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 157 + 158 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 159 + 160 + "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], 161 + 162 + "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], 163 + 164 + "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.4", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg=="], 165 + 166 + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 167 + 168 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 169 + 170 + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 171 + 172 + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 173 + 174 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 175 + 176 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 177 + 178 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 179 + 72 180 "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], 73 181 74 182 "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], 75 183 184 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 185 + 186 + "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], 187 + 188 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 189 + 190 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 191 + 192 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 193 + 194 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 195 + 196 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 197 + 198 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 199 + 200 + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 201 + 202 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 203 + 204 + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 205 + 206 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 207 + 208 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 209 + 210 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 211 + 212 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 213 + 214 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 215 + 216 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 217 + 218 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 219 + 220 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 221 + 222 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 223 + 76 224 "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], 77 225 226 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 227 + 228 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 229 + 230 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 231 + 232 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 233 + 234 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 235 + 236 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 237 + 238 + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 239 + 78 240 "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], 79 241 80 242 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 81 243 244 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 245 + 82 246 "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], 247 + 248 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 249 + 250 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 251 + 252 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 83 253 84 254 "p-timeout": ["p-timeout@4.1.0", "", {}, "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw=="], 85 255 256 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 257 + 258 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 259 + 260 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 261 + 262 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 263 + 264 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 265 + 266 + "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="], 267 + 268 + "prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="], 269 + 270 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 271 + 272 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 273 + 86 274 "safe-compare": ["safe-compare@1.1.4", "", { "dependencies": { "buffer-alloc": "^1.2.0" } }, "sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ=="], 87 275 88 276 "sandwich-stream": ["sandwich-stream@2.0.2", "", {}, "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ=="], 89 277 278 + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 279 + 280 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 281 + 282 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 283 + 284 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 285 + 286 + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 287 + 288 + "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], 289 + 90 290 "telegraf": ["telegraf@4.16.3", "", { "dependencies": { "@telegraf/types": "^7.1.0", "abort-controller": "^3.0.0", "debug": "^4.3.4", "mri": "^1.2.0", "node-fetch": "^2.7.0", "p-timeout": "^4.1.0", "safe-compare": "^1.1.4", "sandwich-stream": "^2.0.2" }, "bin": { "telegraf": "lib/cli.mjs" } }, "sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w=="], 291 + 292 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 91 293 92 294 "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], 93 295 296 + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 297 + 298 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 299 + 300 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 301 + 302 + "typescript-eslint": ["typescript-eslint@8.49.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.49.0", "@typescript-eslint/parser": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg=="], 303 + 94 304 "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 305 + 306 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 95 307 96 308 "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], 97 309 98 310 "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], 99 311 312 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 313 + 314 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 315 + 316 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 317 + 100 318 "zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="], 319 + 320 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 321 + 322 + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 323 + 324 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 325 + 326 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 101 327 } 102 328 }
+50
eslint.config.js
··· 1 + import eslint from '@eslint/js'; 2 + import tseslint from 'typescript-eslint'; 3 + import prettierConfig from 'eslint-config-prettier'; 4 + import prettierPlugin from 'eslint-plugin-prettier'; 5 + 6 + export default tseslint.config( 7 + eslint.configs.recommended, 8 + ...tseslint.configs.strictTypeChecked, 9 + ...tseslint.configs.stylisticTypeChecked, 10 + prettierConfig, 11 + { 12 + languageOptions: { 13 + parserOptions: { 14 + projectService: true, 15 + tsconfigRootDir: import.meta.dirname, 16 + }, 17 + }, 18 + plugins: { 19 + prettier: prettierPlugin, 20 + }, 21 + rules: { 22 + // Prettier integration 23 + 'prettier/prettier': 'error', 24 + 25 + // TypeScript strict rules 26 + '@typescript-eslint/no-explicit-any': 'error', 27 + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 28 + '@typescript-eslint/explicit-function-return-type': 'error', 29 + '@typescript-eslint/explicit-module-boundary-types': 'error', 30 + '@typescript-eslint/no-floating-promises': 'error', 31 + '@typescript-eslint/no-misused-promises': 'error', 32 + '@typescript-eslint/await-thenable': 'error', 33 + '@typescript-eslint/no-unnecessary-condition': 'error', 34 + '@typescript-eslint/prefer-nullish-coalescing': 'error', 35 + '@typescript-eslint/prefer-optional-chain': 'error', 36 + '@typescript-eslint/strict-boolean-expressions': 'error', 37 + 38 + // General best practices 39 + 'no-console': 'off', // Allow console for server-side code 40 + 'eqeqeq': ['error', 'always'], 41 + 'curly': ['error', 'all'], 42 + 'no-throw-literal': 'error', 43 + 'prefer-const': 'error', 44 + 'no-var': 'error', 45 + }, 46 + }, 47 + { 48 + ignores: ['node_modules/**', 'dist/**', '**/*.js', '**/*.d.ts'], 49 + } 50 + );
history/.gitkeep

This is a binary file and will not be displayed.

+19 -1
package.json
··· 3 3 "module": "index.ts", 4 4 "type": "module", 5 5 "private": true, 6 + "scripts": { 7 + "dev": "bun --hot src/index.ts", 8 + "start": "bun src/index.ts", 9 + "typecheck": "tsgo --noEmit", 10 + "lint": "eslint src/", 11 + "lint:fix": "eslint src/ --fix", 12 + "format": "prettier --write src/", 13 + "format:check": "prettier --check src/", 14 + "test": "bun test", 15 + "check": "bun run typecheck && bun run lint && bun run format:check && bun test", 16 + "fix": "bun run lint:fix && bun run format" 17 + }, 6 18 "devDependencies": { 19 + "@eslint/js": "^9.39.1", 7 20 "@types/bun": "latest", 8 - "@typescript/native-preview": "^7.0.0-dev.20251211.1" 21 + "@typescript/native-preview": "^7.0.0-dev.20251211.1", 22 + "eslint": "^9.39.1", 23 + "eslint-config-prettier": "^10.1.8", 24 + "eslint-plugin-prettier": "^5.5.4", 25 + "prettier": "^3.7.4", 26 + "typescript-eslint": "^8.49.0" 9 27 }, 10 28 "dependencies": { 11 29 "@ai-sdk/anthropic": "^2.0.54",
+81 -63
src/bot.ts
··· 8 8 * - Error handling and user-friendly responses 9 9 */ 10 10 11 - import { Telegraf, type Context } from "telegraf"; 12 - import type { Update } from "telegraf/types"; 13 - import { config } from "./config"; 14 - import { getLettaClient } from "./letta"; 11 + import { Telegraf, type Context } from 'telegraf'; 12 + import type { Update } from 'telegraf/types'; 13 + import { config } from './config'; 14 + import { getLettaClient } from './letta'; 15 15 16 16 /** 17 17 * Create Telegraf bot instance ··· 34 34 * @param username - Telegram username (for logging/debugging) 35 35 * @returns Agent ID 36 36 */ 37 - async function getOrCreateAgentForUser( 38 - userId: number, 39 - username?: string 40 - ): Promise<string> { 37 + async function getOrCreateAgentForUser(userId: number, username?: string): Promise<string> { 41 38 // Check if we already have an agent for this user 42 39 const existingAgentId = userAgentMap.get(userId); 43 - if (existingAgentId) { 44 - console.log(`Using existing agent ${existingAgentId} for user ${userId}`); 40 + if (existingAgentId !== undefined) { 41 + console.log(`Using existing agent ${existingAgentId} for user ${userId.toString()}`); 45 42 return existingAgentId; 46 43 } 47 44 ··· 49 46 const client = getLettaClient(); 50 47 51 48 try { 52 - console.log(`Creating new agent for user ${userId} (${username || "unknown"})`); 49 + const usernameOrUnknown = username ?? 'unknown'; 50 + console.log(`Creating new agent for user ${userId.toString()} (${usernameOrUnknown})`); 53 51 54 52 // Create agent with basic configuration 55 53 // For M1, we'll use a simple configuration. Later milestones will customize this. 56 54 // Note: The model string should be in the format "provider/model-name" 57 55 // The Letta server should be configured with the anthropic-proxy as a provider 58 56 const agentState = await client.agents.create({ 59 - name: `user-${userId}-${username || "unknown"}`, 60 - description: `ADHD support agent for Telegram user ${userId}`, 57 + name: `user-${userId.toString()}-${usernameOrUnknown}`, 58 + description: `ADHD support agent for Telegram user ${userId.toString()}`, 61 59 // Using Claude Opus 4.5 (should be configured in Letta server) 62 - model: "anthropic/claude-opus-4-5-20251101", 63 - embedding: "openai/text-embedding-ada-002", 60 + model: 'anthropic/claude-opus-4-5-20251101', 61 + embedding: 'openai/text-embedding-ada-002', 64 62 // Memory blocks with system prompt for ADHD support 65 63 memory_blocks: [ 66 64 { 67 - label: "persona", 65 + label: 'persona', 68 66 value: `You are a helpful ADHD support assistant. You help users with: 69 67 - Task management and breaking down complex tasks 70 68 - Time management and scheduling ··· 77 75 ], 78 76 }); 79 77 80 - console.log(`Created agent ${agentState.id} for user ${userId}`); 78 + console.log(`Created agent ${agentState.id} for user ${userId.toString()}`); 81 79 82 80 // Store the mapping 83 81 userAgentMap.set(userId, agentState.id); 84 82 85 83 return agentState.id; 86 - } catch (error: any) { 87 - console.error(`Failed to create agent for user ${userId}:`, error); 88 - throw new Error( 89 - `Failed to create agent: ${error.message || "Unknown error"}` 90 - ); 84 + } catch (err: unknown) { 85 + const errorMessage = err instanceof Error ? err.message : 'Unknown error'; 86 + console.error(`Failed to create agent for user ${userId.toString()}:`, err); 87 + throw new Error(`Failed to create agent: ${errorMessage}`); 91 88 } 92 89 } 93 90 ··· 98 95 * @param message - User message text 99 96 * @returns Agent response text 100 97 */ 101 - async function sendMessageToAgent( 102 - agentId: string, 103 - message: string 104 - ): Promise<string> { 98 + async function sendMessageToAgent(agentId: string, message: string): Promise<string> { 105 99 const client = getLettaClient(); 106 100 107 101 try { ··· 113 107 114 108 // Extract the assistant's response from the messages 115 109 // The response contains an array of messages, we want the assistant's reply 116 - const assistantMessages = response.messages.filter( 117 - (msg) => msg.message_type === "assistant_message" 118 - ); 110 + const assistantMessages = response.messages.filter((msg) => msg.message_type === 'assistant_message'); 119 111 120 112 if (assistantMessages.length === 0) { 121 - console.warn("No assistant message in response:", response); 113 + console.warn('No assistant message in response:', response); 122 114 return "I'm sorry, I didn't generate a response. Please try again."; 123 115 } 124 116 125 117 // Get the last assistant message 126 118 const lastMessage = assistantMessages[assistantMessages.length - 1]; 127 119 120 + // Check if lastMessage exists 121 + if (!lastMessage) { 122 + console.warn('No assistant message found in response:', response); 123 + return "I'm sorry, I didn't generate a response. Please try again."; 124 + } 125 + 126 + // Type guard to check if message is AssistantMessage 127 + if (lastMessage.message_type !== 'assistant_message') { 128 + console.warn('Last message is not an assistant message:', lastMessage); 129 + return "I'm sorry, I received an unexpected response format."; 130 + } 131 + 132 + // Now TypeScript knows lastMessage is AssistantMessage, which has content 128 133 // Extract text content from the message 129 134 // AssistantMessage has a 'content' field which can be a string or array 130 - if (typeof lastMessage.content === "string") { 135 + if (typeof lastMessage.content === 'string') { 131 136 return lastMessage.content; 132 137 } 133 138 134 139 // If content is an array, join text parts 135 140 if (Array.isArray(lastMessage.content)) { 136 - const textParts = lastMessage.content 137 - .filter((part: any) => part.type === "text") 138 - .map((part: any) => part.text); 139 - return textParts.join("\n") || "I'm sorry, I couldn't process that message."; 141 + interface TextPart { 142 + type: string; 143 + text: string; 144 + } 145 + 146 + const isTextPart = (part: unknown): part is TextPart => { 147 + return typeof part === 'object' && part !== null && 'type' in part && 'text' in part && part.type === 'text'; 148 + }; 149 + 150 + const textParts = lastMessage.content.filter(isTextPart).map((part) => part.text); 151 + const joinedText = textParts.join('\n'); 152 + return joinedText !== '' ? joinedText : "I'm sorry, I couldn't process that message."; 140 153 } 141 154 142 - console.warn("Unexpected message format:", lastMessage); 155 + console.warn('Unexpected message format:', lastMessage); 143 156 return "I'm sorry, I received an unexpected response format."; 144 - } catch (error: any) { 145 - console.error(`Failed to send message to agent ${agentId}:`, error); 146 - throw new Error( 147 - `Failed to get response from agent: ${error.message || "Unknown error"}` 148 - ); 157 + } catch (err: unknown) { 158 + const errorMessage = err instanceof Error ? err.message : 'Unknown error'; 159 + console.error(`Failed to send message to agent ${agentId}:`, err); 160 + throw new Error(`Failed to get response from agent: ${errorMessage}`); 149 161 } 150 162 } 151 163 152 164 /** 153 165 * Handle /start command 154 166 */ 155 - bot.command("start", async (ctx: Context) => { 167 + bot.command('start', async (ctx: Context) => { 156 168 const welcomeMessage = `Welcome to the ADHD Support Agent! 157 169 158 170 I'm here to help you with: ··· 172 184 /** 173 185 * Handle /help command 174 186 */ 175 - bot.command("help", async (ctx: Context) => { 187 + bot.command('help', async (ctx: Context) => { 176 188 const helpMessage = `Available commands: 177 189 178 190 /start - Show welcome message ··· 186 198 /** 187 199 * Handle text messages 188 200 */ 189 - bot.on("message", async (ctx: Context) => { 201 + bot.on('message', async (ctx: Context) => { 190 202 // Only handle text messages (ignore photos, videos, etc. for M1) 191 - if (!ctx.message || !("text" in ctx.message)) { 203 + if (!ctx.message || !('text' in ctx.message)) { 192 204 return; 193 205 } 194 206 195 207 const messageText = ctx.message.text; 208 + 209 + // Check if ctx.from exists (it should always exist for messages, but TypeScript requires the check) 210 + if (!ctx.from) { 211 + console.error('Message received without sender information'); 212 + return; 213 + } 214 + 196 215 const userId = ctx.from.id; 197 216 const username = ctx.from.username; 198 217 199 218 // Skip if it's a command (already handled by command handlers) 200 - if (messageText.startsWith("/")) { 219 + if (messageText.startsWith('/')) { 201 220 return; 202 221 } 203 222 204 223 try { 205 224 // Show typing indicator while processing 206 - await ctx.sendChatAction("typing"); 225 + await ctx.sendChatAction('typing'); 207 226 208 227 // Get or create agent for this user 209 228 const agentId = await getOrCreateAgentForUser(userId, username); ··· 213 232 214 233 // Reply to user 215 234 await ctx.reply(response); 216 - } catch (error: any) { 217 - console.error("Error handling message:", error); 235 + } catch (err: unknown) { 236 + console.error('Error handling message:', err); 218 237 219 238 // Send user-friendly error message 220 - const errorMessage = 221 - "I'm sorry, I encountered an error processing your message. Please try again later."; 239 + const errorMessage = "I'm sorry, I encountered an error processing your message. Please try again later."; 222 240 223 - await ctx.reply(errorMessage).catch((replyError) => { 224 - console.error("Failed to send error message to user:", replyError); 241 + await ctx.reply(errorMessage).catch((replyError: unknown) => { 242 + console.error('Failed to send error message to user:', replyError); 225 243 }); 226 244 } 227 245 }); ··· 235 253 try { 236 254 await bot.handleUpdate(update); 237 255 } catch (error) { 238 - console.error("Error in handleUpdate:", error); 256 + console.error('Error in handleUpdate:', error); 239 257 throw error; 240 258 } 241 259 } ··· 247 265 * Should NOT be called in production webhook mode. 248 266 */ 249 267 export async function startPolling(): Promise<void> { 250 - console.log("Starting Telegram bot in polling mode..."); 268 + console.log('Starting Telegram bot in polling mode...'); 251 269 252 270 try { 253 271 await bot.launch(); 254 - console.log("Bot is running in polling mode"); 272 + console.log('Bot is running in polling mode'); 255 273 256 274 // Enable graceful stop 257 - process.once("SIGINT", () => { 258 - console.log("SIGINT received, stopping bot..."); 259 - bot.stop("SIGINT"); 275 + process.once('SIGINT', () => { 276 + console.log('SIGINT received, stopping bot...'); 277 + bot.stop('SIGINT'); 260 278 }); 261 - process.once("SIGTERM", () => { 262 - console.log("SIGTERM received, stopping bot..."); 263 - bot.stop("SIGTERM"); 279 + process.once('SIGTERM', () => { 280 + console.log('SIGTERM received, stopping bot...'); 281 + bot.stop('SIGTERM'); 264 282 }); 265 283 } catch (error) { 266 - console.error("Failed to start polling:", error); 284 + console.error('Failed to start polling:', error); 267 285 throw error; 268 286 } 269 287 }
+18 -17
src/config.example.ts
··· 5 5 * Run with: bun run src/config.example.ts 6 6 */ 7 7 8 - import { config, validateConfig, isWebhookMode, isAnthropicProxyReady } from "./config"; 8 + import { config, validateConfig, isWebhookMode, isAnthropicProxyReady } from './config'; 9 9 10 - console.log("=== ADHD Support Agent Configuration ===\n"); 10 + console.log('=== ADHD Support Agent Configuration ===\n'); 11 11 12 12 // Validate configuration at startup 13 13 try { 14 14 validateConfig(); 15 - console.log("✅ Configuration is valid\n"); 16 - } catch (error: any) { 17 - console.error("❌ Configuration error:", error.message); 15 + console.log('✅ Configuration is valid\n'); 16 + } catch (error: unknown) { 17 + const message = error instanceof Error ? error.message : String(error); 18 + console.error('❌ Configuration error:', message); 18 19 process.exit(1); 19 20 } 20 21 21 22 // Display configuration 22 - console.log("Server Configuration:"); 23 - console.log(` PORT: ${config.PORT}`); 23 + console.log('Server Configuration:'); 24 + console.log(` PORT: ${config.PORT.toString()}`); 24 25 console.log(` DB_PATH: ${config.DB_PATH}`); 25 26 console.log(); 26 27 27 - console.log("Letta Configuration:"); 28 + console.log('Letta Configuration:'); 28 29 console.log(` LETTA_BASE_URL: ${config.LETTA_BASE_URL}`); 29 30 console.log(); 30 31 31 - console.log("Telegram Configuration:"); 32 + console.log('Telegram Configuration:'); 32 33 console.log(` TELEGRAM_BOT_TOKEN: ${config.TELEGRAM_BOT_TOKEN.slice(0, 10)}...`); 33 - console.log(` Mode: ${isWebhookMode() ? "Webhook" : "Polling"}`); 34 + console.log(` Mode: ${isWebhookMode() ? 'Webhook' : 'Polling'}`); 34 35 if (isWebhookMode()) { 35 36 console.log(` TELEGRAM_WEBHOOK_URL: ${config.TELEGRAM_WEBHOOK_URL}`); 36 37 } 37 38 console.log(); 38 39 39 - console.log("Anthropic Proxy Configuration:"); 40 + console.log('Anthropic Proxy Configuration:'); 40 41 console.log(` ANTHROPIC_PROXY_URL: ${config.ANTHROPIC_PROXY_URL}`); 41 - console.log(` Status: ${isAnthropicProxyReady() ? "Ready" : "Needs OAuth setup"}`); 42 + console.log(` Status: ${isAnthropicProxyReady() ? 'Ready' : 'Needs OAuth setup'}`); 42 43 console.log(); 43 44 44 - console.log("OpenAI Configuration:"); 45 + console.log('OpenAI Configuration:'); 45 46 console.log(` OPENAI_API_KEY: ${config.OPENAI_API_KEY.slice(0, 10)}...`); 46 47 console.log(); 47 48 48 49 // Example: Conditional logic based on configuration 49 50 if (!isAnthropicProxyReady()) { 50 - console.warn("⚠️ Warning: Anthropic proxy is not configured."); 51 - console.warn(" Please complete OAuth flow to set ANTHROPIC_PROXY_SESSION_ID"); 51 + console.warn('⚠️ Warning: Anthropic proxy is not configured.'); 52 + console.warn(' Please complete OAuth flow to set ANTHROPIC_PROXY_SESSION_ID'); 52 53 } 53 54 54 55 if (!isWebhookMode()) { 55 - console.warn("⚠️ Warning: Running in polling mode (development only)."); 56 - console.warn(" Set TELEGRAM_WEBHOOK_URL and TELEGRAM_WEBHOOK_SECRET_TOKEN for production."); 56 + console.warn('⚠️ Warning: Running in polling mode (development only).'); 57 + console.warn(' Set TELEGRAM_WEBHOOK_URL and TELEGRAM_WEBHOOK_SECRET_TOKEN for production.'); 57 58 }
+43 -38
src/config.test.ts
··· 1 - import { test, expect } from "bun:test"; 1 + import { test, expect } from 'bun:test'; 2 2 3 3 /** 4 4 * Config module tests ··· 8 8 * current environment, so we test the validation functions instead. 9 9 */ 10 10 11 - test("validateConfig accepts valid URLs", () => { 11 + test('validateConfig accepts valid URLs', () => { 12 12 // Create a mock config with valid values 13 13 const mockConfig = { 14 14 PORT: 3000, 15 - LETTA_BASE_URL: "http://localhost:8283", 16 - TELEGRAM_BOT_TOKEN: "test_token", 17 - TELEGRAM_WEBHOOK_URL: "", 18 - TELEGRAM_WEBHOOK_SECRET_TOKEN: "", 19 - ANTHROPIC_PROXY_URL: "http://localhost:4001/v1", 20 - ANTHROPIC_PROXY_SESSION_SECRET: "test_secret", 21 - ANTHROPIC_PROXY_SESSION_ID: "", 22 - OPENAI_API_KEY: "test_key", 23 - DB_PATH: "./data/assistant.db", 15 + LETTA_BASE_URL: 'http://localhost:8283', 16 + TELEGRAM_BOT_TOKEN: 'test_token', 17 + TELEGRAM_WEBHOOK_URL: '', 18 + TELEGRAM_WEBHOOK_SECRET_TOKEN: '', 19 + ANTHROPIC_PROXY_URL: 'http://localhost:4001/v1', 20 + ANTHROPIC_PROXY_SESSION_SECRET: 'test_secret', 21 + ANTHROPIC_PROXY_SESSION_ID: '', 22 + OPENAI_API_KEY: 'test_key', 23 + DB_PATH: './data/assistant.db', 24 24 }; 25 25 26 26 // Should not throw with valid config ··· 31 31 }).not.toThrow(); 32 32 }); 33 33 34 - test("URL validation rejects invalid URLs", () => { 35 - expect(() => new URL("not-a-url")).toThrow(); 36 - expect(() => new URL("")).toThrow(); 34 + test('URL validation rejects invalid URLs', () => { 35 + expect(() => new URL('not-a-url')).toThrow(); 36 + expect(() => new URL('')).toThrow(); 37 37 }); 38 38 39 - test("PORT validation accepts valid ports", () => { 39 + test('PORT validation accepts valid ports', () => { 40 40 const validPorts = [1, 3000, 8080, 65535]; 41 41 for (const port of validPorts) { 42 42 expect(port >= 1 && port <= 65535).toBe(true); 43 43 } 44 44 }); 45 45 46 - test("PORT validation rejects invalid ports", () => { 46 + test('PORT validation rejects invalid ports', () => { 47 47 const invalidPorts = [0, -1, 70000, 100000]; 48 48 for (const port of invalidPorts) { 49 49 expect(port >= 1 && port <= 65535).toBe(false); 50 50 } 51 51 }); 52 52 53 - test("webhook mode detection works correctly", () => { 53 + test('webhook mode detection works correctly', () => { 54 54 // Both set = webhook mode 55 - const webhook1 = { url: "https://example.com", secret: "token" }; 56 - expect(webhook1.url !== "" && webhook1.secret !== "").toBe(true); 55 + const webhook1 = { url: 'https://example.com', secret: 'token' }; 56 + expect(webhook1.url !== '' && webhook1.secret !== '').toBe(true); 57 57 58 58 // Both empty = polling mode 59 - const webhook2 = { url: "", secret: "" }; 60 - expect(webhook2.url !== "" && webhook2.secret !== "").toBe(false); 59 + const webhook2 = { url: '', secret: '' }; 60 + expect(webhook2.url !== '' && webhook2.secret !== '').toBe(false); 61 61 62 62 // Only one set = invalid (should be caught by validateConfig) 63 - const webhook3 = { url: "https://example.com", secret: "" }; 64 - const hasUrl = webhook3.url !== ""; 65 - const hasSecret = webhook3.secret !== ""; 63 + const webhook3 = { url: 'https://example.com', secret: '' }; 64 + const hasUrl = webhook3.url !== ''; 65 + const hasSecret = webhook3.secret !== ''; 66 66 expect(hasUrl === hasSecret).toBe(false); // Should trigger validation error 67 67 }); 68 68 69 - test("session ID detection works correctly", () => { 70 - expect("session_123" !== "").toBe(true); 71 - expect("" !== "").toBe(false); 69 + test('session ID detection works correctly', () => { 70 + // Test the logic that checks if a session ID is present 71 + const checkSessionIdPresent = (sessionId: string): boolean => { 72 + return sessionId !== ''; 73 + }; 74 + 75 + expect(checkSessionIdPresent('session_123')).toBe(true); 76 + expect(checkSessionIdPresent('')).toBe(false); 72 77 }); 73 78 74 - test("number parsing works correctly", () => { 75 - expect(Number("3000")).toBe(3000); 76 - expect(Number("8080")).toBe(8080); 77 - expect(isNaN(Number("not-a-number"))).toBe(true); 78 - expect(isNaN(Number(""))).toBe(false); // Empty string becomes 0 79 + test('number parsing works correctly', () => { 80 + expect(Number('3000')).toBe(3000); 81 + expect(Number('8080')).toBe(8080); 82 + expect(isNaN(Number('not-a-number'))).toBe(true); 83 + expect(isNaN(Number(''))).toBe(false); // Empty string becomes 0 79 84 }); 80 85 81 - test("default values work correctly", () => { 82 - const getValue = (envValue: string | undefined, defaultValue: string) => { 83 - return envValue || defaultValue; 86 + test('default values work correctly', () => { 87 + const getValue = (envValue: string | undefined, defaultValue: string): string => { 88 + return envValue !== undefined && envValue !== '' ? envValue : defaultValue; 84 89 }; 85 90 86 - expect(getValue(undefined, "default")).toBe("default"); 87 - expect(getValue("", "default")).toBe("default"); 88 - expect(getValue("custom", "default")).toBe("custom"); 91 + expect(getValue(undefined, 'default')).toBe('default'); 92 + expect(getValue('', 'default')).toBe('default'); 93 + expect(getValue('custom', 'default')).toBe('custom'); 89 94 });
+24 -22
src/config.ts
··· 10 10 */ 11 11 function requireEnv(name: string): string { 12 12 const value = process.env[name]; 13 - if (!value) { 13 + if (value === undefined || value === '') { 14 14 throw new Error(`Missing required environment variable: ${name}`); 15 15 } 16 16 return value; ··· 20 20 * Get an optional environment variable with a default value 21 21 */ 22 22 function optionalEnv(name: string, defaultValue: string): string { 23 - return process.env[name] || defaultValue; 23 + const value = process.env[name]; 24 + return value !== undefined && value !== '' ? value : defaultValue; 24 25 } 25 26 26 27 /** ··· 28 29 */ 29 30 function numberEnv(name: string, defaultValue: number): number { 30 31 const value = process.env[name]; 31 - if (!value) return defaultValue; 32 + if (value === undefined || value === '') { 33 + return defaultValue; 34 + } 32 35 const parsed = Number(value); 33 36 if (isNaN(parsed)) { 34 37 throw new Error(`Environment variable ${name} must be a number, got: ${value}`); ··· 44 47 */ 45 48 export const config = { 46 49 // === Server === 47 - PORT: numberEnv("PORT", 3000), 50 + PORT: numberEnv('PORT', 3000), 48 51 49 52 // === Letta === 50 - LETTA_BASE_URL: requireEnv("LETTA_BASE_URL"), 53 + LETTA_BASE_URL: requireEnv('LETTA_BASE_URL'), 51 54 52 55 // === Telegram === 53 - TELEGRAM_BOT_TOKEN: requireEnv("TELEGRAM_BOT_TOKEN"), 54 - TELEGRAM_WEBHOOK_URL: optionalEnv("TELEGRAM_WEBHOOK_URL", ""), 55 - TELEGRAM_WEBHOOK_SECRET_TOKEN: optionalEnv("TELEGRAM_WEBHOOK_SECRET_TOKEN", ""), 56 + TELEGRAM_BOT_TOKEN: requireEnv('TELEGRAM_BOT_TOKEN'), 57 + TELEGRAM_WEBHOOK_URL: optionalEnv('TELEGRAM_WEBHOOK_URL', ''), 58 + TELEGRAM_WEBHOOK_SECRET_TOKEN: optionalEnv('TELEGRAM_WEBHOOK_SECRET_TOKEN', ''), 56 59 57 60 // === Anthropic Proxy === 58 - ANTHROPIC_PROXY_URL: requireEnv("ANTHROPIC_PROXY_URL"), 59 - ANTHROPIC_PROXY_SESSION_SECRET: requireEnv("ANTHROPIC_PROXY_SESSION_SECRET"), 60 - ANTHROPIC_PROXY_SESSION_ID: optionalEnv("ANTHROPIC_PROXY_SESSION_ID", ""), 61 + ANTHROPIC_PROXY_URL: requireEnv('ANTHROPIC_PROXY_URL'), 62 + ANTHROPIC_PROXY_SESSION_SECRET: requireEnv('ANTHROPIC_PROXY_SESSION_SECRET'), 63 + ANTHROPIC_PROXY_SESSION_ID: optionalEnv('ANTHROPIC_PROXY_SESSION_ID', ''), 61 64 62 65 // === OpenAI (embeddings only) === 63 - OPENAI_API_KEY: requireEnv("OPENAI_API_KEY"), 66 + OPENAI_API_KEY: requireEnv('OPENAI_API_KEY'), 64 67 65 68 // === Database === 66 - DB_PATH: optionalEnv("DB_PATH", "./data/assistant.db"), 69 + DB_PATH: optionalEnv('DB_PATH', './data/assistant.db'), 67 70 } as const; 68 71 69 72 /** ··· 74 77 */ 75 78 export function validateConfig(): void { 76 79 // Webhook URL and secret token must both be present or both be empty 77 - const hasWebhookUrl = config.TELEGRAM_WEBHOOK_URL !== ""; 78 - const hasWebhookSecret = config.TELEGRAM_WEBHOOK_SECRET_TOKEN !== ""; 80 + const hasWebhookUrl = config.TELEGRAM_WEBHOOK_URL !== ''; 81 + const hasWebhookSecret = config.TELEGRAM_WEBHOOK_SECRET_TOKEN !== ''; 79 82 80 83 if (hasWebhookUrl !== hasWebhookSecret) { 81 84 throw new Error( 82 - "TELEGRAM_WEBHOOK_URL and TELEGRAM_WEBHOOK_SECRET_TOKEN must both be set or both be empty. " + 83 - "If both are empty, the bot will run in polling mode (dev only)." 85 + 'TELEGRAM_WEBHOOK_URL and TELEGRAM_WEBHOOK_SECRET_TOKEN must both be set or both be empty. ' + 86 + 'If both are empty, the bot will run in polling mode (dev only).' 84 87 ); 85 88 } 86 89 ··· 108 111 // Warn if session ID is missing (needed for Anthropic proxy to work) 109 112 if (!config.ANTHROPIC_PROXY_SESSION_ID) { 110 113 console.warn( 111 - "⚠️ ANTHROPIC_PROXY_SESSION_ID is not set. " + 112 - "The Anthropic proxy will not work until OAuth flow is completed." 114 + '⚠️ ANTHROPIC_PROXY_SESSION_ID is not set. ' + 'The Anthropic proxy will not work until OAuth flow is completed.' 113 115 ); 114 116 } 115 117 116 118 // Port validation 117 119 if (config.PORT < 1 || config.PORT > 65535) { 118 - throw new Error(`PORT must be between 1 and 65535, got: ${config.PORT}`); 120 + throw new Error(`PORT must be between 1 and 65535, got: ${config.PORT.toString()}`); 119 121 } 120 122 } 121 123 ··· 123 125 * Determine if the bot should run in webhook mode or polling mode 124 126 */ 125 127 export function isWebhookMode(): boolean { 126 - return config.TELEGRAM_WEBHOOK_URL !== "" && config.TELEGRAM_WEBHOOK_SECRET_TOKEN !== ""; 128 + return config.TELEGRAM_WEBHOOK_URL !== '' && config.TELEGRAM_WEBHOOK_SECRET_TOKEN !== ''; 127 129 } 128 130 129 131 /** 130 132 * Determine if the Anthropic proxy is configured and ready 131 133 */ 132 134 export function isAnthropicProxyReady(): boolean { 133 - return config.ANTHROPIC_PROXY_SESSION_ID !== ""; 135 + return config.ANTHROPIC_PROXY_SESSION_ID !== ''; 134 136 }
+38 -32
src/health.integration.test.ts
··· 5 5 * correctly with the config module and handles various scenarios. 6 6 */ 7 7 8 - import { test, expect, describe } from "bun:test"; 9 - import { healthCheck, simpleHealthCheck, type HealthCheckResult } from "./health"; 10 - import { config } from "./config"; 8 + import { test, expect, describe } from 'bun:test'; 9 + import { healthCheck, simpleHealthCheck, type HealthCheckResult } from './health'; 10 + import { config } from './config'; 11 11 12 - describe("Health Check Integration", () => { 13 - test("healthCheck uses config values", async () => { 12 + describe('Health Check Integration', () => { 13 + test('healthCheck uses config values', async () => { 14 14 // Verify that healthCheck is using the config module 15 15 expect(config.LETTA_BASE_URL).toBeDefined(); 16 16 expect(config.ANTHROPIC_PROXY_URL).toBeDefined(); ··· 20 20 expect(response.status).toBeOneOf([200, 503]); 21 21 }); 22 22 23 - test("healthCheck returns proper Response object", async () => { 23 + test('healthCheck returns proper Response object', async () => { 24 24 const response = await healthCheck(); 25 25 26 26 // Verify it's a valid Response 27 27 expect(response).toBeInstanceOf(Response); 28 - expect(response.headers.get("Content-Type")).toBe("application/json"); 28 + expect(response.headers.get('Content-Type')).toBe('application/json'); 29 29 30 30 // Verify JSON is parseable 31 31 const body = await response.json(); 32 32 expect(body).toBeDefined(); 33 33 }); 34 34 35 - test("healthCheck has all required checks", async () => { 35 + test('healthCheck has all required checks', async () => { 36 36 const response = await healthCheck(); 37 37 const body = (await response.json()) as HealthCheckResult; 38 38 39 39 // Verify structure 40 - expect(body).toHaveProperty("healthy"); 41 - expect(body).toHaveProperty("checks"); 42 - expect(body.checks).toHaveProperty("db"); 43 - expect(body.checks).toHaveProperty("letta"); 44 - expect(body.checks).toHaveProperty("proxy"); 40 + expect(body).toHaveProperty('healthy'); 41 + expect(body).toHaveProperty('checks'); 42 + expect(body.checks).toHaveProperty('db'); 43 + expect(body.checks).toHaveProperty('letta'); 44 + expect(body.checks).toHaveProperty('proxy'); 45 45 46 46 // Verify types 47 - expect(typeof body.healthy).toBe("boolean"); 48 - expect(typeof body.checks.db).toBe("boolean"); 49 - expect(typeof body.checks.letta).toBe("boolean"); 50 - expect(typeof body.checks.proxy).toBe("boolean"); 47 + expect(typeof body.healthy).toBe('boolean'); 48 + expect(typeof body.checks.db).toBe('boolean'); 49 + expect(typeof body.checks.letta).toBe('boolean'); 50 + expect(typeof body.checks.proxy).toBe('boolean'); 51 51 }); 52 52 53 - test("healthCheck returns 503 when services are down", async () => { 54 - // In test environment, Letta and Proxy won't be running 53 + test('healthCheck status matches healthy field', async () => { 54 + // This integration test verifies consistency between status code and healthy field 55 + // Services may or may not be running depending on the environment 55 56 const response = await healthCheck(); 56 57 const body = (await response.json()) as HealthCheckResult; 57 58 58 - // Should be unhealthy since services aren't running 59 - expect(response.status).toBe(503); 60 - expect(body.healthy).toBe(false); 61 - 62 - // DB should be healthy (optional in M0) 59 + // DB should always be healthy in M0 (check is skipped) 63 60 expect(body.checks.db).toBe(true); 64 61 65 - // Letta and Proxy should be unhealthy (not running) 66 - expect(body.checks.letta).toBe(false); 67 - expect(body.checks.proxy).toBe(false); 62 + // Status code should be consistent with healthy field 63 + if (body.healthy) { 64 + expect(response.status).toBe(200); 65 + // All checks should be true when healthy 66 + expect(body.checks.letta).toBe(true); 67 + expect(body.checks.proxy).toBe(true); 68 + } else { 69 + expect(response.status).toBe(503); 70 + // At least one check should be false when unhealthy 71 + const anyFailed = !body.checks.letta || !body.checks.proxy; 72 + expect(anyFailed).toBe(true); 73 + } 68 74 }); 69 75 70 - test("simpleHealthCheck always returns 200", () => { 76 + test('simpleHealthCheck always returns 200', () => { 71 77 const response = simpleHealthCheck(); 72 78 73 79 expect(response.status).toBe(200); 74 - expect(response.headers.get("Content-Type")).toBe("application/json"); 80 + expect(response.headers.get('Content-Type')).toBe('application/json'); 75 81 }); 76 82 77 - test("simpleHealthCheck returns minimal structure", async () => { 83 + test('simpleHealthCheck returns minimal structure', async () => { 78 84 const response = simpleHealthCheck(); 79 - const body = await response.json(); 85 + const body = (await response.json()) as { healthy: boolean; checks: { server: boolean }; message: string }; 80 86 81 87 expect(body.healthy).toBe(true); 82 88 expect(body.checks.server).toBe(true); 83 - expect(body.message).toContain("M0"); 89 + expect(body.message).toContain('M0'); 84 90 }); 85 91 });
+11 -11
src/health.test.ts
··· 2 2 * Tests for health check functionality 3 3 */ 4 4 5 - import { test, expect, describe } from "bun:test"; 6 - import { simpleHealthCheck } from "./health"; 5 + import { test, expect, describe } from 'bun:test'; 6 + import { simpleHealthCheck } from './health'; 7 7 8 - describe("Health Check", () => { 9 - test("simpleHealthCheck returns 200 and healthy status", async () => { 8 + describe('Health Check', () => { 9 + test('simpleHealthCheck returns 200 and healthy status', async () => { 10 10 const response = simpleHealthCheck(); 11 11 12 12 expect(response.status).toBe(200); 13 - expect(response.headers.get("Content-Type")).toBe("application/json"); 13 + expect(response.headers.get('Content-Type')).toBe('application/json'); 14 14 15 - const body = await response.json(); 15 + const body = (await response.json()) as { healthy: boolean; checks: { server: boolean } }; 16 16 expect(body.healthy).toBe(true); 17 17 expect(body.checks.server).toBe(true); 18 18 }); 19 19 20 - test("simpleHealthCheck returns valid JSON", async () => { 20 + test('simpleHealthCheck returns valid JSON', async () => { 21 21 const response = simpleHealthCheck(); 22 - const body = await response.json(); 22 + const body = (await response.json()) as { healthy: boolean; checks: Record<string, boolean> }; 23 23 24 - expect(body).toHaveProperty("healthy"); 25 - expect(body).toHaveProperty("checks"); 26 - expect(typeof body.healthy).toBe("boolean"); 24 + expect(body).toHaveProperty('healthy'); 25 + expect(body).toHaveProperty('checks'); 26 + expect(typeof body.healthy).toBe('boolean'); 27 27 }); 28 28 });
+11 -30
src/health.ts
··· 9 9 * Returns 200 if all services are healthy, 503 if any are down. 10 10 */ 11 11 12 - import { config } from "./config"; 12 + import { config } from './config'; 13 13 14 14 export interface HealthCheckResult { 15 15 healthy: boolean; ··· 34 34 35 35 // DB: Optional for M0 (database module doesn't exist yet) 36 36 // Will be enabled in M2 when src/db/index.ts exists 37 - try { 38 - // Check if database module exists and is importable 39 - const dbModule = await import("./db/index.js").catch(() => null); 40 - if (dbModule?.sqlite) { 41 - // Use underlying bun:sqlite directly for simple ping 42 - dbModule.sqlite.query("SELECT 1").get(); 43 - checks.db = true; 44 - } else { 45 - // Database module not yet implemented, skip check 46 - // In M0 this is expected and not a failure 47 - checks.db = true; 48 - } 49 - } catch (error) { 50 - // Database check failed - only log in production 51 - // In M0/development, this is expected 52 - if (process.env.NODE_ENV === "production") { 53 - console.error("Database health check failed:", error); 54 - } 55 - // For M0, we treat missing DB as non-critical 56 - checks.db = true; 57 - } 37 + // For now, we skip the DB check entirely - it will be implemented in M2 38 + checks.db = true; 58 39 59 40 // Letta: Check health endpoint (fast, doesn't query agents) 60 41 try { 61 42 const res = await fetch(`${config.LETTA_BASE_URL}/v1/health/`, { 62 - method: "GET", 43 + method: 'GET', 63 44 signal: AbortSignal.timeout(5000), // 5s timeout 64 45 }); 65 46 checks.letta = res.ok; 66 47 } catch (error) { 67 - console.error("Letta health check failed:", error); 48 + console.error('Letta health check failed:', error); 68 49 checks.letta = false; 69 50 } 70 51 71 52 // Proxy: Check health endpoint 72 53 try { 73 - const proxyHealthUrl = config.ANTHROPIC_PROXY_URL.replace("/v1", "/health"); 54 + const proxyHealthUrl = config.ANTHROPIC_PROXY_URL.replace('/v1', '/health'); 74 55 const res = await fetch(proxyHealthUrl, { 75 - method: "GET", 56 + method: 'GET', 76 57 signal: AbortSignal.timeout(5000), // 5s timeout 77 58 }); 78 59 checks.proxy = res.ok; 79 60 } catch (error) { 80 - console.error("Proxy health check failed:", error); 61 + console.error('Proxy health check failed:', error); 81 62 checks.proxy = false; 82 63 } 83 64 ··· 92 73 return new Response(JSON.stringify(result), { 93 74 status: healthy ? 200 : 503, 94 75 headers: { 95 - "Content-Type": "application/json", 76 + 'Content-Type': 'application/json', 96 77 }, 97 78 }); 98 79 } ··· 110 91 checks: { 111 92 server: true, 112 93 }, 113 - message: "Server is running (M0 - basic health check)", 94 + message: 'Server is running (M0 - basic health check)', 114 95 }), 115 96 { 116 97 status: 200, 117 98 headers: { 118 - "Content-Type": "application/json", 99 + 'Content-Type': 'application/json', 119 100 }, 120 101 } 121 102 );
+10 -10
src/index.example.ts
··· 5 5 * when it's ready to be created. 6 6 */ 7 7 8 - import { config } from "./config"; 9 - import { healthCheck, simpleHealthCheck } from "./health"; 8 + import { config } from './config'; 9 + import { healthCheck, simpleHealthCheck } from './health'; 10 10 11 11 // Example 1: Use full health check (M0+) 12 12 Bun.serve({ 13 13 port: config.PORT, 14 - fetch: async (req) => { 14 + fetch: async (req): Promise<Response> => { 15 15 const url = new URL(req.url); 16 16 17 - if (url.pathname === "/health") { 17 + if (url.pathname === '/health') { 18 18 // Use full health check with Letta + Proxy checks 19 19 return await healthCheck(); 20 20 } 21 21 22 - return new Response("Not Found", { status: 404 }); 22 + return new Response('Not Found', { status: 404 }); 23 23 }, 24 24 }); 25 25 26 26 // Example 2: Use simple health check (M0 only, before dependencies are ready) 27 27 Bun.serve({ 28 28 port: config.PORT, 29 - fetch: async (req) => { 29 + fetch: (req): Response => { 30 30 const url = new URL(req.url); 31 31 32 - if (url.pathname === "/health") { 32 + if (url.pathname === '/health') { 33 33 // Use simple health check that only verifies server is running 34 34 return simpleHealthCheck(); 35 35 } 36 36 37 - return new Response("Not Found", { status: 404 }); 37 + return new Response('Not Found', { status: 404 }); 38 38 }, 39 39 }); 40 40 41 - console.log(`Server running on http://localhost:${config.PORT}`); 42 - console.log(`Health check available at http://localhost:${config.PORT}/health`); 41 + console.log(`Server running on http://localhost:${config.PORT.toString()}`); 42 + console.log(`Health check available at http://localhost:${config.PORT.toString()}/health`);
+43 -55
src/index.ts
··· 9 9 * Uses Bun.serve() for high-performance HTTP handling. 10 10 */ 11 11 12 - import { config, isWebhookMode } from "./config"; 13 - import { healthCheck, simpleHealthCheck } from "./health"; 14 - import { initializeLetta } from "./letta"; 15 - import { handleUpdate, startPolling } from "./bot"; 16 - import type { Update } from "telegraf/types"; 12 + import { config, isWebhookMode } from './config'; 13 + import { healthCheck, simpleHealthCheck } from './health'; 14 + import { initializeLetta } from './letta'; 15 + import { handleUpdate, startPolling } from './bot'; 16 + import type { Update } from 'telegraf/types'; 17 17 18 18 /** 19 19 * Main server handler using Bun.serve() 20 20 */ 21 - async function main() { 22 - console.log("Starting ADHD Support Agent..."); 21 + async function main(): Promise<void> { 22 + console.log('Starting ADHD Support Agent...'); 23 23 24 24 // Initialize Letta before starting the server 25 25 try { 26 26 await initializeLetta(); 27 27 } catch (error) { 28 - console.error("Failed to initialize Letta:", error); 29 - console.error("Server will start, but bot functionality may be limited."); 28 + console.error('Failed to initialize Letta:', error); 29 + console.error('Server will start, but bot functionality may be limited.'); 30 30 } 31 31 32 32 // Start the HTTP server 33 - const server = Bun.serve({ 33 + Bun.serve({ 34 34 port: config.PORT, 35 35 async fetch(req) { 36 36 const url = new URL(req.url); 37 37 const path = url.pathname; 38 38 39 39 // GET /health - Full health check 40 - if (path === "/health" && req.method === "GET") { 40 + if (path === '/health' && req.method === 'GET') { 41 41 return await healthCheck(); 42 42 } 43 43 44 44 // GET /healthz - Simple health check (k8s liveness probe) 45 - if (path === "/healthz" && req.method === "GET") { 45 + if (path === '/healthz' && req.method === 'GET') { 46 46 return simpleHealthCheck(); 47 47 } 48 48 49 49 // POST /webhook - Telegram webhook endpoint 50 - if (path === "/webhook" && req.method === "POST") { 50 + if (path === '/webhook' && req.method === 'POST') { 51 51 // Verify the secret token 52 - const token = req.headers.get("X-Telegram-Bot-Api-Secret-Token"); 53 - if (!token || token !== config.TELEGRAM_WEBHOOK_SECRET_TOKEN) { 54 - console.warn("Webhook request with invalid or missing secret token"); 55 - return new Response( 56 - JSON.stringify({ error: "Unauthorized" }), 57 - { 58 - status: 401, 59 - headers: { "Content-Type": "application/json" }, 60 - } 61 - ); 52 + const token = req.headers.get('X-Telegram-Bot-Api-Secret-Token'); 53 + if (token === null || token !== config.TELEGRAM_WEBHOOK_SECRET_TOKEN) { 54 + console.warn('Webhook request with invalid or missing secret token'); 55 + return new Response(JSON.stringify({ error: 'Unauthorized' }), { 56 + status: 401, 57 + headers: { 'Content-Type': 'application/json' }, 58 + }); 62 59 } 63 60 64 61 // Parse the Telegram update 65 62 let update: Update; 66 63 try { 67 - update = await req.json(); 64 + update = (await req.json()) as Update; 68 65 } catch (error) { 69 - console.error("Failed to parse webhook body:", error); 70 - return new Response( 71 - JSON.stringify({ error: "Invalid JSON" }), 72 - { 73 - status: 400, 74 - headers: { "Content-Type": "application/json" }, 75 - } 76 - ); 66 + console.error('Failed to parse webhook body:', error); 67 + return new Response(JSON.stringify({ error: 'Invalid JSON' }), { 68 + status: 400, 69 + headers: { 'Content-Type': 'application/json' }, 70 + }); 77 71 } 78 72 79 73 // Handle the update (fire and forget - Telegram expects quick response) 80 - handleUpdate(update).catch((error) => { 81 - console.error("Error handling update:", error); 74 + handleUpdate(update).catch((error: unknown) => { 75 + console.error('Error handling update:', error); 82 76 }); 83 77 84 78 // Return 200 OK immediately 85 - return new Response( 86 - JSON.stringify({ ok: true }), 87 - { 88 - status: 200, 89 - headers: { "Content-Type": "application/json" }, 90 - } 91 - ); 79 + return new Response(JSON.stringify({ ok: true }), { 80 + status: 200, 81 + headers: { 'Content-Type': 'application/json' }, 82 + }); 92 83 } 93 84 94 85 // 404 for unknown routes 95 - return new Response( 96 - JSON.stringify({ error: "Not Found" }), 97 - { 98 - status: 404, 99 - headers: { "Content-Type": "application/json" }, 100 - } 101 - ); 86 + return new Response(JSON.stringify({ error: 'Not Found' }), { 87 + status: 404, 88 + headers: { 'Content-Type': 'application/json' }, 89 + }); 102 90 }, 103 91 }); 104 92 105 - console.log(`Server listening on http://localhost:${config.PORT}`); 93 + console.log(`Server listening on http://localhost:${config.PORT.toString()}`); 106 94 107 95 // Start bot in appropriate mode 108 96 if (isWebhookMode()) { 109 97 console.log(`Webhook mode enabled: ${config.TELEGRAM_WEBHOOK_URL}`); 110 98 } else { 111 - console.log("Webhook mode disabled, starting polling for development..."); 99 + console.log('Webhook mode disabled, starting polling for development...'); 112 100 try { 113 101 await startPolling(); 114 102 } catch (error) { 115 - console.error("Failed to start polling mode:", error); 116 - console.error("Bot will not receive messages in polling mode."); 103 + console.error('Failed to start polling mode:', error); 104 + console.error('Bot will not receive messages in polling mode.'); 117 105 } 118 106 } 119 107 120 - console.log("ADHD Support Agent is ready!"); 108 + console.log('ADHD Support Agent is ready!'); 121 109 } 122 110 123 111 // Start the server 124 - main().catch((error) => { 125 - console.error("Fatal error starting server:", error); 112 + main().catch((error: unknown) => { 113 + console.error('Fatal error starting server:', error); 126 114 process.exit(1); 127 115 });
+5 -5
src/letta.test.ts
··· 2 2 * Tests for Letta client module 3 3 */ 4 4 5 - import { test, expect, describe } from "bun:test"; 6 - import { getLettaClient } from "./letta"; 5 + import { test, expect, describe } from 'bun:test'; 6 + import { getLettaClient } from './letta'; 7 7 8 - describe("Letta client", () => { 9 - test("getLettaClient returns a Letta instance", () => { 8 + describe('Letta client', () => { 9 + test('getLettaClient returns a Letta instance', () => { 10 10 const client = getLettaClient(); 11 11 expect(client).toBeDefined(); 12 12 expect(client.agents).toBeDefined(); ··· 14 14 expect(client.tools).toBeDefined(); 15 15 }); 16 16 17 - test("getLettaClient returns the same instance (singleton)", () => { 17 + test('getLettaClient returns the same instance (singleton)', () => { 18 18 const client1 = getLettaClient(); 19 19 const client2 = getLettaClient(); 20 20 expect(client1).toBe(client2);
+31 -30
src/letta.ts
··· 7 7 * - Agent creation (placeholder for M1) 8 8 */ 9 9 10 - import { Letta } from "@letta-ai/letta-client"; 11 - import { config } from "./config"; 10 + import { Letta } from '@letta-ai/letta-client'; 11 + import { config } from './config'; 12 12 13 13 /** 14 14 * Singleton Letta client instance ··· 19 19 * Get or create the Letta client singleton 20 20 */ 21 21 export function getLettaClient(): Letta { 22 - if (!lettaClient) { 23 - lettaClient = new Letta({ 24 - baseUrl: config.LETTA_BASE_URL, 25 - }); 26 - } 22 + lettaClient ??= new Letta({ 23 + baseURL: config.LETTA_BASE_URL, 24 + }); 27 25 return lettaClient; 28 26 } 29 27 ··· 49 47 const client = getLettaClient(); 50 48 51 49 try { 52 - console.log("Verifying Letta connectivity..."); 50 + console.log('Verifying Letta connectivity...'); 53 51 54 52 // List available models to verify connectivity 55 - const models = await client.models.list(); 56 - console.log(`Letta is accessible. Found ${models.llm_models?.length || 0} LLM models and ${models.embedding_models?.length || 0} embedding models.`); 53 + const llmModels = await client.models.list(); 54 + const embeddingModels = await client.models.embeddings.list(); 55 + console.log( 56 + `Letta is accessible. Found ${llmModels.length.toString()} LLM models and ${embeddingModels.length.toString()} embedding models.` 57 + ); 57 58 58 59 // Log available Anthropic models 59 - const anthropicModels = models.llm_models?.filter((m: any) => 60 - m.provider_type === "anthropic" || m.provider_name?.includes("anthropic") 61 - ) || []; 60 + const anthropicModels = llmModels.filter( 61 + (m) => m.provider_type === 'anthropic' || (m.provider_name?.includes('anthropic') ?? false) 62 + ); 62 63 63 64 if (anthropicModels.length > 0) { 64 - console.log(`Found ${anthropicModels.length} Anthropic model(s):`); 65 - anthropicModels.forEach((m: any) => { 66 - console.log(` - ${m.handle || m.name}`); 65 + console.log(`Found ${anthropicModels.length.toString()} Anthropic model(s):`); 66 + anthropicModels.forEach((m) => { 67 + console.log(` - ${m.handle ?? m.name}`); 67 68 }); 68 69 } else { 69 70 console.warn( 70 - "⚠️ No Anthropic models found. " + 71 - "Make sure the anthropic-proxy is configured as a provider in Letta server. " + 72 - "This may need to be done via Letta's admin interface or configuration." 71 + '⚠️ No Anthropic models found. ' + 72 + 'Make sure the anthropic-proxy is configured as a provider in Letta server. ' + 73 + "This may need to be done via Letta's admin interface or configuration." 73 74 ); 74 75 } 75 76 76 - return "Letta connectivity verified"; 77 - } catch (error: any) { 78 - console.error("Failed to verify Letta connectivity:", error); 77 + return 'Letta connectivity verified'; 78 + } catch (error: unknown) { 79 + console.error('Failed to verify Letta connectivity:', error); 79 80 throw error; 80 81 } 81 82 } ··· 88 89 * 89 90 * @returns Agent ID once implemented, null for now 90 91 */ 91 - export async function getOrCreateAgent(): Promise<string | null> { 92 - console.log("getOrCreateAgent() called - placeholder for M1 implementation"); 93 - console.log("Agent creation will be implemented in milestone M1"); 94 - return null; 92 + export function getOrCreateAgent(): Promise<string | null> { 93 + console.log('getOrCreateAgent() called - placeholder for M1 implementation'); 94 + console.log('Agent creation will be implemented in milestone M1'); 95 + return Promise.resolve(null); 95 96 } 96 97 97 98 /** ··· 102 103 * - Eventually will create/get the agent (M1) 103 104 */ 104 105 export async function initializeLetta(): Promise<void> { 105 - console.log("Initializing Letta..."); 106 + console.log('Initializing Letta...'); 106 107 107 108 // Get client (creates singleton) 108 - const client = getLettaClient(); 109 + getLettaClient(); 109 110 console.log(`Letta client initialized (base URL: ${config.LETTA_BASE_URL})`); 110 111 111 112 // Ensure provider exists 112 113 try { 113 114 await ensureProvider(); 114 115 } catch (error) { 115 - console.error("Failed to ensure provider during initialization:", error); 116 + console.error('Failed to ensure provider during initialization:', error); 116 117 throw error; 117 118 } 118 119 119 120 // Placeholder: agent creation will be added in M1 120 - console.log("Letta initialization complete (agent creation deferred to M1)"); 121 + console.log('Letta initialization complete (agent creation deferred to M1)'); 121 122 }
+30 -8
tsconfig.json
··· 6 6 "module": "Preserve", 7 7 "moduleDetection": "force", 8 8 "jsx": "react-jsx", 9 - "allowJs": true, 9 + "allowJs": false, 10 10 11 11 // Bundler mode 12 12 "moduleResolution": "bundler", ··· 14 14 "verbatimModuleSyntax": true, 15 15 "noEmit": true, 16 16 17 - // Best practices 17 + // Strict type checking - ALL enabled 18 18 "strict": true, 19 - "skipLibCheck": true, 19 + "noImplicitAny": true, 20 + "strictNullChecks": true, 21 + "strictFunctionTypes": true, 22 + "strictBindCallApply": true, 23 + "strictPropertyInitialization": true, 24 + "noImplicitThis": true, 25 + "useUnknownInCatchVariables": true, 26 + "alwaysStrict": true, 27 + 28 + // Additional strict checks - ALL enabled 29 + "noUnusedLocals": true, 30 + "noUnusedParameters": true, 31 + "exactOptionalPropertyTypes": true, 32 + "noImplicitReturns": true, 20 33 "noFallthroughCasesInSwitch": true, 21 34 "noUncheckedIndexedAccess": true, 22 35 "noImplicitOverride": true, 36 + "noPropertyAccessFromIndexSignature": true, 23 37 24 - // Some stricter flags (disabled by default) 25 - "noUnusedLocals": false, 26 - "noUnusedParameters": false, 27 - "noPropertyAccessFromIndexSignature": false 28 - } 38 + // Module and interop 39 + "skipLibCheck": true, 40 + "esModuleInterop": true, 41 + "allowSyntheticDefaultImports": true, 42 + "forceConsistentCasingInFileNames": true, 43 + "resolveJsonModule": true, 44 + "isolatedModules": true, 45 + 46 + // Bun types 47 + "types": ["bun-types"] 48 + }, 49 + "include": ["src/**/*.ts"], 50 + "exclude": ["node_modules"] 29 51 }