@build-astron-co/nimbus 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/bin/nimbus +26 -10
  2. package/bin/nimbus.cmd +41 -0
  3. package/bin/nimbus.mjs +70 -0
  4. package/completions/nimbus.bash +38 -0
  5. package/completions/nimbus.fish +48 -0
  6. package/completions/nimbus.zsh +81 -0
  7. package/dist/src/agent/compaction-agent.js +215 -0
  8. package/dist/src/agent/context-manager.js +385 -0
  9. package/dist/src/agent/context.js +322 -0
  10. package/dist/src/agent/deploy-preview.js +395 -0
  11. package/dist/src/agent/expand-files.js +95 -0
  12. package/dist/src/agent/index.js +18 -0
  13. package/dist/src/agent/loop.js +1535 -0
  14. package/dist/src/agent/modes.js +347 -0
  15. package/dist/src/agent/permissions.js +396 -0
  16. package/dist/src/agent/subagents/base.js +67 -0
  17. package/dist/src/agent/subagents/cost.js +45 -0
  18. package/dist/src/agent/subagents/explore.js +36 -0
  19. package/dist/src/agent/subagents/general.js +41 -0
  20. package/dist/src/agent/subagents/index.js +88 -0
  21. package/dist/src/agent/subagents/infra.js +52 -0
  22. package/dist/src/agent/subagents/security.js +60 -0
  23. package/dist/src/agent/system-prompt.js +860 -0
  24. package/dist/src/app.js +152 -0
  25. package/dist/src/audit/activity-log.js +209 -0
  26. package/dist/src/audit/compliance-checker.js +419 -0
  27. package/dist/src/audit/cost-tracker.js +231 -0
  28. package/dist/src/audit/index.js +10 -0
  29. package/dist/src/audit/security-scanner.js +490 -0
  30. package/dist/src/auth/guard.js +64 -0
  31. package/dist/src/auth/index.js +19 -0
  32. package/dist/src/auth/keychain.js +79 -0
  33. package/dist/src/auth/oauth.js +389 -0
  34. package/dist/src/auth/providers.js +415 -0
  35. package/dist/src/auth/sso.js +87 -0
  36. package/dist/src/auth/store.js +424 -0
  37. package/dist/src/auth/types.js +5 -0
  38. package/dist/src/cli/index.js +8 -0
  39. package/dist/src/cli/init.js +1048 -0
  40. package/dist/src/cli/openapi-spec.js +346 -0
  41. package/dist/src/cli/run.js +505 -0
  42. package/dist/src/cli/serve-auth.js +56 -0
  43. package/dist/src/cli/serve.js +432 -0
  44. package/dist/src/cli/web.js +50 -0
  45. package/dist/src/cli.js +1574 -0
  46. package/dist/src/clients/core-engine-client.js +156 -0
  47. package/dist/src/clients/enterprise-client.js +246 -0
  48. package/dist/src/clients/generator-client.js +219 -0
  49. package/dist/src/clients/git-client.js +367 -0
  50. package/dist/src/clients/github-client.js +229 -0
  51. package/dist/src/clients/helm-client.js +299 -0
  52. package/dist/src/clients/index.js +18 -0
  53. package/dist/src/clients/k8s-client.js +270 -0
  54. package/dist/src/clients/llm-client.js +119 -0
  55. package/dist/src/clients/rest-client.js +104 -0
  56. package/dist/src/clients/service-discovery.js +35 -0
  57. package/dist/src/clients/terraform-client.js +302 -0
  58. package/dist/src/clients/tools-client.js +1227 -0
  59. package/dist/src/clients/ws-client.js +93 -0
  60. package/dist/src/commands/alias.js +91 -0
  61. package/dist/src/commands/analyze/index.js +313 -0
  62. package/dist/src/commands/apply/helm.js +375 -0
  63. package/dist/src/commands/apply/index.js +176 -0
  64. package/dist/src/commands/apply/k8s.js +350 -0
  65. package/dist/src/commands/apply/terraform.js +465 -0
  66. package/dist/src/commands/ask.js +137 -0
  67. package/dist/src/commands/audit/index.js +322 -0
  68. package/dist/src/commands/auth-cloud.js +345 -0
  69. package/dist/src/commands/auth-list.js +112 -0
  70. package/dist/src/commands/auth-profile.js +104 -0
  71. package/dist/src/commands/auth-refresh.js +161 -0
  72. package/dist/src/commands/auth-status.js +122 -0
  73. package/dist/src/commands/aws/ec2.js +402 -0
  74. package/dist/src/commands/aws/iam.js +304 -0
  75. package/dist/src/commands/aws/index.js +108 -0
  76. package/dist/src/commands/aws/lambda.js +317 -0
  77. package/dist/src/commands/aws/rds.js +345 -0
  78. package/dist/src/commands/aws/s3.js +346 -0
  79. package/dist/src/commands/aws/vpc.js +302 -0
  80. package/dist/src/commands/aws-discover.js +413 -0
  81. package/dist/src/commands/aws-terraform.js +618 -0
  82. package/dist/src/commands/azure/aks.js +305 -0
  83. package/dist/src/commands/azure/functions.js +200 -0
  84. package/dist/src/commands/azure/index.js +93 -0
  85. package/dist/src/commands/azure/storage.js +378 -0
  86. package/dist/src/commands/azure/vm.js +291 -0
  87. package/dist/src/commands/billing/index.js +224 -0
  88. package/dist/src/commands/chat.js +259 -0
  89. package/dist/src/commands/completions.js +255 -0
  90. package/dist/src/commands/config.js +291 -0
  91. package/dist/src/commands/cost/cloud-cost-estimator.js +211 -0
  92. package/dist/src/commands/cost/estimator.js +73 -0
  93. package/dist/src/commands/cost/index.js +625 -0
  94. package/dist/src/commands/cost/parsers/terraform.js +234 -0
  95. package/dist/src/commands/cost/parsers/types.js +4 -0
  96. package/dist/src/commands/cost/pricing/aws.js +501 -0
  97. package/dist/src/commands/cost/pricing/azure.js +462 -0
  98. package/dist/src/commands/cost/pricing/gcp.js +359 -0
  99. package/dist/src/commands/cost/pricing/index.js +24 -0
  100. package/dist/src/commands/demo.js +196 -0
  101. package/dist/src/commands/deploy.js +215 -0
  102. package/dist/src/commands/doctor.js +1291 -0
  103. package/dist/src/commands/drift/index.js +674 -0
  104. package/dist/src/commands/explain.js +235 -0
  105. package/dist/src/commands/export.js +120 -0
  106. package/dist/src/commands/feedback.js +319 -0
  107. package/dist/src/commands/fix.js +263 -0
  108. package/dist/src/commands/fs/index.js +338 -0
  109. package/dist/src/commands/gcp/compute.js +266 -0
  110. package/dist/src/commands/gcp/functions.js +221 -0
  111. package/dist/src/commands/gcp/gke.js +357 -0
  112. package/dist/src/commands/gcp/iam.js +295 -0
  113. package/dist/src/commands/gcp/index.js +105 -0
  114. package/dist/src/commands/gcp/storage.js +232 -0
  115. package/dist/src/commands/generate-helm.js +1026 -0
  116. package/dist/src/commands/generate-k8s.js +1263 -0
  117. package/dist/src/commands/generate-terraform.js +1058 -0
  118. package/dist/src/commands/gh/index.js +663 -0
  119. package/dist/src/commands/git/index.js +1208 -0
  120. package/dist/src/commands/helm/index.js +985 -0
  121. package/dist/src/commands/help.js +639 -0
  122. package/dist/src/commands/history.js +120 -0
  123. package/dist/src/commands/import.js +782 -0
  124. package/dist/src/commands/incident.js +144 -0
  125. package/dist/src/commands/index.js +109 -0
  126. package/dist/src/commands/init.js +955 -0
  127. package/dist/src/commands/k8s/index.js +979 -0
  128. package/dist/src/commands/login.js +588 -0
  129. package/dist/src/commands/logout.js +61 -0
  130. package/dist/src/commands/logs.js +160 -0
  131. package/dist/src/commands/onboarding.js +382 -0
  132. package/dist/src/commands/pipeline.js +153 -0
  133. package/dist/src/commands/plan/display.js +216 -0
  134. package/dist/src/commands/plan/index.js +525 -0
  135. package/dist/src/commands/plugin.js +325 -0
  136. package/dist/src/commands/preview.js +356 -0
  137. package/dist/src/commands/profile.js +297 -0
  138. package/dist/src/commands/questionnaire.js +1021 -0
  139. package/dist/src/commands/resume.js +35 -0
  140. package/dist/src/commands/rollback.js +259 -0
  141. package/dist/src/commands/rollout.js +74 -0
  142. package/dist/src/commands/runbook.js +307 -0
  143. package/dist/src/commands/schedule.js +202 -0
  144. package/dist/src/commands/status.js +213 -0
  145. package/dist/src/commands/team/index.js +309 -0
  146. package/dist/src/commands/team-context.js +200 -0
  147. package/dist/src/commands/template.js +204 -0
  148. package/dist/src/commands/tf/index.js +989 -0
  149. package/dist/src/commands/upgrade.js +515 -0
  150. package/dist/src/commands/usage/index.js +118 -0
  151. package/dist/src/commands/version.js +145 -0
  152. package/dist/src/commands/watch.js +127 -0
  153. package/dist/src/compat/index.js +2 -0
  154. package/dist/src/compat/runtime.js +10 -0
  155. package/dist/src/compat/sqlite.js +144 -0
  156. package/dist/src/config/index.js +6 -0
  157. package/dist/src/config/manager.js +469 -0
  158. package/dist/src/config/mode-store.js +57 -0
  159. package/dist/src/config/profiles.js +66 -0
  160. package/dist/src/config/safety-policy.js +251 -0
  161. package/dist/src/config/schema.js +107 -0
  162. package/dist/src/config/types.js +311 -0
  163. package/dist/src/config/workspace-state.js +38 -0
  164. package/dist/src/context/context-db.js +138 -0
  165. package/dist/src/demo/index.js +295 -0
  166. package/dist/src/demo/scenarios/full-journey.js +226 -0
  167. package/dist/src/demo/scenarios/getting-started.js +124 -0
  168. package/dist/src/demo/scenarios/helm-release.js +334 -0
  169. package/dist/src/demo/scenarios/k8s-deployment.js +190 -0
  170. package/dist/src/demo/scenarios/terraform-vpc.js +167 -0
  171. package/dist/src/demo/types.js +6 -0
  172. package/dist/src/engine/cost-estimator.js +334 -0
  173. package/dist/src/engine/diagram-generator.js +192 -0
  174. package/dist/src/engine/drift-detector.js +688 -0
  175. package/dist/src/engine/executor.js +832 -0
  176. package/dist/src/engine/index.js +39 -0
  177. package/dist/src/engine/orchestrator.js +436 -0
  178. package/dist/src/engine/planner.js +616 -0
  179. package/dist/src/engine/safety.js +609 -0
  180. package/dist/src/engine/verifier.js +664 -0
  181. package/dist/src/enterprise/audit.js +241 -0
  182. package/dist/src/enterprise/auth.js +189 -0
  183. package/dist/src/enterprise/billing.js +512 -0
  184. package/dist/src/enterprise/index.js +16 -0
  185. package/dist/src/enterprise/teams.js +315 -0
  186. package/dist/src/generator/best-practices.js +1375 -0
  187. package/dist/src/generator/helm.js +495 -0
  188. package/dist/src/generator/index.js +11 -0
  189. package/dist/src/generator/intent-parser.js +420 -0
  190. package/dist/src/generator/kubernetes.js +773 -0
  191. package/dist/src/generator/terraform.js +1472 -0
  192. package/dist/src/history/index.js +6 -0
  193. package/dist/src/history/manager.js +199 -0
  194. package/dist/src/history/types.js +6 -0
  195. package/dist/src/hooks/config.js +318 -0
  196. package/dist/src/hooks/engine.js +317 -0
  197. package/dist/src/hooks/index.js +2 -0
  198. package/dist/src/llm/auth-bridge.js +157 -0
  199. package/dist/src/llm/circuit-breaker.js +116 -0
  200. package/dist/src/llm/config-loader.js +172 -0
  201. package/dist/src/llm/cost-calculator.js +137 -0
  202. package/dist/src/llm/index.js +7 -0
  203. package/dist/src/llm/model-aliases.js +99 -0
  204. package/dist/src/llm/provider-registry.js +57 -0
  205. package/dist/src/llm/providers/anthropic.js +430 -0
  206. package/dist/src/llm/providers/bedrock.js +409 -0
  207. package/dist/src/llm/providers/google.js +344 -0
  208. package/dist/src/llm/providers/ollama.js +661 -0
  209. package/dist/src/llm/providers/openai-compatible.js +289 -0
  210. package/dist/src/llm/providers/openai.js +284 -0
  211. package/dist/src/llm/providers/openrouter.js +293 -0
  212. package/dist/src/llm/router.js +844 -0
  213. package/dist/src/llm/types.js +69 -0
  214. package/dist/src/lsp/client.js +239 -0
  215. package/dist/src/lsp/languages.js +95 -0
  216. package/dist/src/lsp/manager.js +243 -0
  217. package/dist/src/mcp/client.js +289 -0
  218. package/dist/src/mcp/index.js +5 -0
  219. package/dist/src/mcp/manager.js +113 -0
  220. package/dist/src/nimbus.js +212 -0
  221. package/dist/src/plugins/index.js +13 -0
  222. package/dist/src/plugins/loader.js +280 -0
  223. package/dist/src/plugins/manager.js +282 -0
  224. package/dist/src/plugins/types.js +23 -0
  225. package/dist/src/scanners/cicd-scanner.js +230 -0
  226. package/dist/src/scanners/cloud-scanner.js +415 -0
  227. package/dist/src/scanners/framework-scanner.js +430 -0
  228. package/dist/src/scanners/iac-scanner.js +350 -0
  229. package/dist/src/scanners/index.js +454 -0
  230. package/dist/src/scanners/language-scanner.js +258 -0
  231. package/dist/src/scanners/package-manager-scanner.js +252 -0
  232. package/dist/src/scanners/types.js +6 -0
  233. package/dist/src/sessions/manager.js +395 -0
  234. package/dist/src/sessions/types.js +4 -0
  235. package/dist/src/sharing/sync.js +238 -0
  236. package/dist/src/sharing/viewer.js +131 -0
  237. package/dist/src/snapshots/index.js +1 -0
  238. package/dist/src/snapshots/manager.js +432 -0
  239. package/dist/src/state/artifacts.js +94 -0
  240. package/dist/src/state/audit.js +73 -0
  241. package/dist/src/state/billing.js +126 -0
  242. package/dist/src/state/checkpoints.js +81 -0
  243. package/dist/src/state/config.js +58 -0
  244. package/dist/src/state/conversations.js +7 -0
  245. package/dist/src/state/credentials.js +96 -0
  246. package/dist/src/state/db.js +53 -0
  247. package/dist/src/state/index.js +23 -0
  248. package/dist/src/state/messages.js +76 -0
  249. package/dist/src/state/projects.js +92 -0
  250. package/dist/src/state/schema.js +233 -0
  251. package/dist/src/state/sessions.js +79 -0
  252. package/dist/src/state/teams.js +131 -0
  253. package/dist/src/telemetry.js +91 -0
  254. package/dist/src/tools/aws-ops.js +747 -0
  255. package/dist/src/tools/azure-ops.js +491 -0
  256. package/dist/src/tools/file-ops.js +451 -0
  257. package/dist/src/tools/gcp-ops.js +559 -0
  258. package/dist/src/tools/git-ops.js +557 -0
  259. package/dist/src/tools/github-ops.js +460 -0
  260. package/dist/src/tools/helm-ops.js +634 -0
  261. package/dist/src/tools/index.js +16 -0
  262. package/dist/src/tools/k8s-ops.js +579 -0
  263. package/dist/src/tools/schemas/converter.js +129 -0
  264. package/dist/src/tools/schemas/devops.js +3319 -0
  265. package/dist/src/tools/schemas/index.js +19 -0
  266. package/dist/src/tools/schemas/standard.js +966 -0
  267. package/dist/src/tools/schemas/types.js +409 -0
  268. package/dist/src/tools/spawn-exec.js +109 -0
  269. package/dist/src/tools/terraform-ops.js +627 -0
  270. package/dist/src/types/config.js +1 -0
  271. package/dist/src/types/drift.js +4 -0
  272. package/dist/src/types/enterprise.js +5 -0
  273. package/dist/src/types/index.js +14 -0
  274. package/dist/src/types/plan.js +1 -0
  275. package/dist/src/types/request.js +1 -0
  276. package/dist/src/types/response.js +1 -0
  277. package/dist/src/types/service.js +1 -0
  278. package/dist/src/ui/App.js +1672 -0
  279. package/dist/src/ui/DeployPreview.js +60 -0
  280. package/dist/src/ui/FileDiffModal.js +108 -0
  281. package/dist/src/ui/Header.js +46 -0
  282. package/dist/src/ui/HelpModal.js +9 -0
  283. package/dist/src/ui/InputBox.js +408 -0
  284. package/dist/src/ui/MessageList.js +795 -0
  285. package/dist/src/ui/PermissionPrompt.js +72 -0
  286. package/dist/src/ui/StatusBar.js +109 -0
  287. package/dist/src/ui/TerminalPane.js +31 -0
  288. package/dist/src/ui/ToolCallDisplay.js +303 -0
  289. package/dist/src/ui/TreePane.js +83 -0
  290. package/dist/src/ui/chat-ui.js +721 -0
  291. package/dist/src/ui/index.js +11 -0
  292. package/dist/src/ui/ink/index.js +1325 -0
  293. package/dist/src/ui/streaming.js +137 -0
  294. package/dist/src/ui/theme.js +78 -0
  295. package/dist/src/ui/types.js +7 -0
  296. package/dist/src/utils/analytics.js +61 -0
  297. package/dist/src/utils/cost-warning.js +25 -0
  298. package/dist/src/utils/env.js +42 -0
  299. package/dist/src/utils/errors.js +54 -0
  300. package/dist/src/utils/event-bus.js +22 -0
  301. package/dist/src/utils/index.js +16 -0
  302. package/dist/src/utils/logger.js +150 -0
  303. package/dist/src/utils/rate-limiter.js +90 -0
  304. package/dist/src/utils/service-auth.js +36 -0
  305. package/dist/src/utils/validation.js +39 -0
  306. package/dist/src/version.js +3 -0
  307. package/dist/src/watcher/index.js +192 -0
  308. package/dist/src/wizard/approval.js +275 -0
  309. package/dist/src/wizard/index.js +13 -0
  310. package/dist/src/wizard/prompts.js +273 -0
  311. package/dist/src/wizard/types.js +4 -0
  312. package/dist/src/wizard/ui.js +453 -0
  313. package/dist/src/wizard/wizard.js +227 -0
  314. package/package.json +31 -23
  315. package/src/__tests__/alias.test.ts +133 -0
  316. package/src/__tests__/app.test.ts +1 -1
  317. package/src/__tests__/audit.test.ts +1 -1
  318. package/src/__tests__/circuit-breaker.test.ts +1 -1
  319. package/src/__tests__/cli-run.test.ts +237 -1
  320. package/src/__tests__/compat-sqlite.test.ts +68 -0
  321. package/src/__tests__/context-manager.test.ts +131 -1
  322. package/src/__tests__/context.test.ts +1 -1
  323. package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
  324. package/src/__tests__/doctor.test.ts +48 -0
  325. package/src/__tests__/enterprise.test.ts +1 -1
  326. package/src/__tests__/export.test.ts +236 -0
  327. package/src/__tests__/gap-11-18-20.test.ts +958 -0
  328. package/src/__tests__/generator.test.ts +1 -1
  329. package/src/__tests__/helm-streaming.test.ts +127 -0
  330. package/src/__tests__/hooks.test.ts +1 -1
  331. package/src/__tests__/incident.test.ts +179 -0
  332. package/src/__tests__/init.test.ts +55 -4
  333. package/src/__tests__/intent-parser.test.ts +1 -1
  334. package/src/__tests__/llm-router.test.ts +1 -1
  335. package/src/__tests__/logs.test.ts +107 -0
  336. package/src/__tests__/loop-errors.test.ts +244 -0
  337. package/src/__tests__/lsp.test.ts +1 -1
  338. package/src/__tests__/modes.test.ts +1 -1
  339. package/src/__tests__/perf-optimizations.test.ts +847 -0
  340. package/src/__tests__/permissions.test.ts +1 -1
  341. package/src/__tests__/pipeline.test.ts +50 -0
  342. package/src/__tests__/polish-phase3.test.ts +340 -0
  343. package/src/__tests__/profile.test.ts +237 -0
  344. package/src/__tests__/rollback.test.ts +83 -0
  345. package/src/__tests__/runbook.test.ts +219 -0
  346. package/src/__tests__/schedule.test.ts +206 -0
  347. package/src/__tests__/serve.test.ts +1 -1
  348. package/src/__tests__/sessions.test.ts +96 -1
  349. package/src/__tests__/sharing.test.ts +53 -1
  350. package/src/__tests__/snapshots.test.ts +1 -1
  351. package/src/__tests__/standalone-migration.test.ts +199 -0
  352. package/src/__tests__/state-db.test.ts +1 -1
  353. package/src/__tests__/status.test.ts +158 -0
  354. package/src/__tests__/stream-with-tools.test.ts +71 -25
  355. package/src/__tests__/subagents.test.ts +1 -1
  356. package/src/__tests__/system-prompt.test.ts +82 -3
  357. package/src/__tests__/terminal-gap-v2.test.ts +395 -0
  358. package/src/__tests__/terminal-parity.test.ts +393 -0
  359. package/src/__tests__/tf-apply.test.ts +187 -0
  360. package/src/__tests__/tool-converter.test.ts +1 -1
  361. package/src/__tests__/tool-schemas.test.ts +209 -4
  362. package/src/__tests__/tools.test.ts +4 -3
  363. package/src/__tests__/version-json.test.ts +184 -0
  364. package/src/__tests__/version.test.ts +1 -1
  365. package/src/__tests__/watch.test.ts +129 -0
  366. package/src/agent/compaction-agent.ts +40 -1
  367. package/src/agent/context-manager.ts +67 -3
  368. package/src/agent/deploy-preview.ts +62 -1
  369. package/src/agent/expand-files.ts +108 -0
  370. package/src/agent/loop.ts +1312 -31
  371. package/src/agent/permissions.ts +51 -4
  372. package/src/agent/system-prompt.ts +573 -19
  373. package/src/app.ts +58 -0
  374. package/src/audit/security-scanner.ts +45 -0
  375. package/src/auth/keychain.ts +82 -0
  376. package/src/auth/oauth.ts +15 -5
  377. package/src/cli/init.ts +378 -5
  378. package/src/cli/run.ts +407 -16
  379. package/src/cli/serve.ts +78 -1
  380. package/src/cli/web.ts +10 -6
  381. package/src/cli.ts +312 -1
  382. package/src/clients/service-discovery.ts +30 -25
  383. package/src/commands/alias.ts +100 -0
  384. package/src/commands/audit/index.ts +121 -2
  385. package/src/commands/auth-cloud.ts +113 -0
  386. package/src/commands/auth-refresh.ts +187 -0
  387. package/src/commands/aws-discover.ts +144 -251
  388. package/src/commands/aws-terraform.ts +68 -118
  389. package/src/commands/chat.ts +9 -3
  390. package/src/commands/completions.ts +268 -0
  391. package/src/commands/config.ts +26 -0
  392. package/src/commands/cost/index.ts +218 -2
  393. package/src/commands/deploy.ts +260 -0
  394. package/src/commands/doctor.ts +744 -152
  395. package/src/commands/drift/index.ts +371 -23
  396. package/src/commands/export.ts +146 -0
  397. package/src/commands/generate-k8s.ts +9 -61
  398. package/src/commands/generate-terraform.ts +191 -449
  399. package/src/commands/help.ts +212 -36
  400. package/src/commands/history.ts +8 -1
  401. package/src/commands/incident.ts +166 -0
  402. package/src/commands/init.ts +5 -0
  403. package/src/commands/login.ts +86 -1
  404. package/src/commands/logs.ts +167 -0
  405. package/src/commands/onboarding.ts +211 -34
  406. package/src/commands/pipeline.ts +186 -0
  407. package/src/commands/plugin.ts +398 -0
  408. package/src/commands/profile.ts +342 -0
  409. package/src/commands/questionnaire.ts +0 -98
  410. package/src/commands/resume.ts +26 -34
  411. package/src/commands/rollback.ts +315 -0
  412. package/src/commands/rollout.ts +88 -0
  413. package/src/commands/runbook.ts +346 -0
  414. package/src/commands/schedule.ts +236 -0
  415. package/src/commands/status.ts +252 -0
  416. package/src/commands/team-context.ts +220 -0
  417. package/src/commands/template.ts +58 -57
  418. package/src/commands/tf/index.ts +70 -11
  419. package/src/commands/upgrade.ts +57 -0
  420. package/src/commands/version.ts +54 -50
  421. package/src/commands/watch.ts +153 -0
  422. package/src/compat/runtime.ts +1 -1
  423. package/src/compat/sqlite.ts +75 -5
  424. package/src/config/mode-store.ts +62 -0
  425. package/src/config/profiles.ts +84 -0
  426. package/src/config/types.ts +83 -1
  427. package/src/config/workspace-state.ts +53 -0
  428. package/src/engine/cost-estimator.ts +52 -10
  429. package/src/engine/executor.ts +33 -2
  430. package/src/engine/planner.ts +68 -1
  431. package/src/generator/terraform.ts +8 -0
  432. package/src/history/manager.ts +2 -74
  433. package/src/hooks/engine.ts +5 -4
  434. package/src/llm/cost-calculator.ts +2 -2
  435. package/src/llm/providers/anthropic.ts +50 -21
  436. package/src/llm/router.ts +76 -7
  437. package/src/lsp/languages.ts +3 -0
  438. package/src/lsp/manager.ts +21 -5
  439. package/src/nimbus.ts +37 -18
  440. package/src/sessions/manager.ts +108 -1
  441. package/src/sharing/sync.ts +4 -0
  442. package/src/sharing/viewer.ts +66 -0
  443. package/src/tools/file-ops.ts +22 -0
  444. package/src/tools/schemas/devops.ts +3007 -117
  445. package/src/tools/schemas/standard.ts +5 -1
  446. package/src/tools/schemas/types.ts +31 -1
  447. package/src/tools/spawn-exec.ts +148 -0
  448. package/src/ui/App.tsx +1183 -66
  449. package/src/ui/DeployPreview.tsx +62 -57
  450. package/src/ui/FileDiffModal.tsx +162 -0
  451. package/src/ui/Header.tsx +87 -24
  452. package/src/ui/HelpModal.tsx +57 -0
  453. package/src/ui/InputBox.tsx +163 -10
  454. package/src/ui/MessageList.tsx +487 -40
  455. package/src/ui/PermissionPrompt.tsx +17 -5
  456. package/src/ui/StatusBar.tsx +122 -3
  457. package/src/ui/TerminalPane.tsx +84 -0
  458. package/src/ui/ToolCallDisplay.tsx +252 -18
  459. package/src/ui/TreePane.tsx +132 -0
  460. package/src/ui/chat-ui.ts +41 -44
  461. package/src/ui/ink/index.ts +771 -38
  462. package/src/ui/streaming.ts +1 -1
  463. package/src/ui/theme.ts +104 -0
  464. package/src/ui/types.ts +18 -0
  465. package/src/version.ts +1 -1
  466. package/src/watcher/index.ts +66 -15
  467. package/src/wizard/types.ts +1 -0
  468. package/src/wizard/ui.ts +1 -1
  469. package/tsconfig.json +2 -2
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Streaming Text Display
3
+ *
4
+ * Utilities for displaying streaming LLM responses in the terminal
5
+ */
6
+ import { ui } from '../wizard/ui';
7
+ /**
8
+ * StreamingDisplay handles real-time text output from LLM responses
9
+ */
10
+ export class StreamingDisplay {
11
+ options;
12
+ buffer = '';
13
+ lineStarted = false;
14
+ cursorInterval;
15
+ constructor(options = {}) {
16
+ this.options = {
17
+ prefix: options.prefix || '',
18
+ prefixColor: options.prefixColor || 'blue',
19
+ showCursor: options.showCursor ?? true,
20
+ cursorChar: options.cursorChar || '|',
21
+ };
22
+ }
23
+ /**
24
+ * Start a new streaming response
25
+ */
26
+ start() {
27
+ this.buffer = '';
28
+ this.lineStarted = false;
29
+ // Print the prefix
30
+ if (this.options.prefix) {
31
+ ui.write(ui.color(this.options.prefix, this.options.prefixColor));
32
+ }
33
+ this.lineStarted = true;
34
+ // Start cursor blink if enabled
35
+ if (this.options.showCursor) {
36
+ this.startCursor();
37
+ }
38
+ }
39
+ /**
40
+ * Append text to the streaming display
41
+ */
42
+ append(text) {
43
+ // Stop cursor while writing
44
+ this.stopCursor();
45
+ // Handle newlines in the text
46
+ const lines = text.split('\n');
47
+ for (let i = 0; i < lines.length; i++) {
48
+ const line = lines[i];
49
+ if (i > 0) {
50
+ // This is after a newline
51
+ ui.print(''); // End current line
52
+ this.lineStarted = false;
53
+ }
54
+ if (line) {
55
+ if (!this.lineStarted) {
56
+ // Add indentation for continuation lines
57
+ ui.write(' '); // Indent to align with prefix
58
+ this.lineStarted = true;
59
+ }
60
+ ui.write(line);
61
+ }
62
+ this.buffer += (i > 0 ? '\n' : '') + line;
63
+ }
64
+ // Restart cursor
65
+ if (this.options.showCursor) {
66
+ this.startCursor();
67
+ }
68
+ }
69
+ /**
70
+ * Complete the streaming display
71
+ */
72
+ complete() {
73
+ this.stopCursor();
74
+ // Ensure we end on a new line
75
+ if (this.lineStarted) {
76
+ ui.print('');
77
+ }
78
+ return this.buffer;
79
+ }
80
+ /**
81
+ * Handle an error during streaming
82
+ */
83
+ error(message) {
84
+ this.stopCursor();
85
+ if (this.lineStarted) {
86
+ ui.print('');
87
+ }
88
+ ui.error(message);
89
+ }
90
+ startCursor() {
91
+ if (this.cursorInterval) {
92
+ return;
93
+ }
94
+ let visible = true;
95
+ this.cursorInterval = setInterval(() => {
96
+ if (visible) {
97
+ ui.write(ui.dim(this.options.cursorChar));
98
+ }
99
+ else {
100
+ // Backspace to remove cursor
101
+ ui.write('\b \b');
102
+ }
103
+ visible = !visible;
104
+ }, 500);
105
+ }
106
+ stopCursor() {
107
+ if (this.cursorInterval) {
108
+ clearInterval(this.cursorInterval);
109
+ this.cursorInterval = undefined;
110
+ // Clear any remaining cursor character
111
+ ui.write('\b \b');
112
+ }
113
+ }
114
+ }
115
+ /**
116
+ * Simple streaming helper for one-off usage
117
+ */
118
+ export async function displayStreaming(generator, options = {}) {
119
+ const display = new StreamingDisplay(options);
120
+ display.start();
121
+ try {
122
+ for await (const chunk of generator) {
123
+ if (chunk.type === 'content' && chunk.content) {
124
+ display.append(chunk.content);
125
+ }
126
+ else if (chunk.type === 'error') {
127
+ display.error(chunk.message || 'Unknown error');
128
+ return '';
129
+ }
130
+ }
131
+ return display.complete();
132
+ }
133
+ catch (error) {
134
+ display.error(error instanceof Error ? error.message : 'Streaming failed');
135
+ return '';
136
+ }
137
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Theme system for Nimbus TUI.
3
+ *
4
+ * Provides named color themes (dark, light) that can be switched at runtime.
5
+ * Components should import `activeTheme` and use its color properties instead
6
+ * of hardcoding color strings.
7
+ *
8
+ * Theme preference is persisted to ~/.nimbus/config.yaml.
9
+ */
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ import * as os from 'node:os';
13
+ // ---------------------------------------------------------------------------
14
+ // Built-in themes
15
+ // ---------------------------------------------------------------------------
16
+ export const THEMES = {
17
+ dark: {
18
+ name: 'dark',
19
+ primary: 'cyan',
20
+ secondary: 'blue',
21
+ success: 'green',
22
+ warning: 'yellow',
23
+ error: 'red',
24
+ muted: 'gray',
25
+ border: 'gray',
26
+ },
27
+ light: {
28
+ name: 'light',
29
+ primary: 'blue',
30
+ secondary: 'magenta',
31
+ success: 'green',
32
+ warning: 'yellow',
33
+ error: 'red',
34
+ muted: 'gray',
35
+ border: 'white',
36
+ },
37
+ };
38
+ // ---------------------------------------------------------------------------
39
+ // Active theme (mutable singleton)
40
+ // ---------------------------------------------------------------------------
41
+ export let activeTheme = THEMES.dark;
42
+ /**
43
+ * Switch the active theme by name.
44
+ * Falls back to `dark` if the name is not recognized.
45
+ * Persists the chosen theme name to ~/.nimbus/config.yaml.
46
+ */
47
+ export function setTheme(name) {
48
+ activeTheme = THEMES[name] ?? THEMES.dark;
49
+ // Persist to config file
50
+ try {
51
+ const configDir = path.join(os.homedir(), '.nimbus');
52
+ const configFile = path.join(configDir, 'config.yaml');
53
+ fs.mkdirSync(configDir, { recursive: true });
54
+ let config = '';
55
+ try {
56
+ config = fs.readFileSync(configFile, 'utf-8');
57
+ }
58
+ catch {
59
+ /* new file */
60
+ }
61
+ if (config.includes('theme:')) {
62
+ config = config.replace(/^theme:.*$/m, `theme: ${name}`);
63
+ }
64
+ else {
65
+ config = config ? `${config.trim()}\ntheme: ${name}\n` : `theme: ${name}\n`;
66
+ }
67
+ fs.writeFileSync(configFile, config, 'utf-8');
68
+ }
69
+ catch {
70
+ /* ignore FS errors */
71
+ }
72
+ }
73
+ /**
74
+ * List all available theme names.
75
+ */
76
+ export function listThemes() {
77
+ return Object.keys(THEMES);
78
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shared types for Nimbus TUI components.
3
+ *
4
+ * These interfaces are consumed by every Ink component in the UI layer and by
5
+ * the orchestration logic that feeds data into the rendering tree.
6
+ */
7
+ export {};
@@ -0,0 +1,61 @@
1
+ /**
2
+ * PostHog Analytics Integration
3
+ *
4
+ * Lightweight analytics wrapper that sends events to PostHog.
5
+ * Operates as a complete no-op when POSTHOG_API_KEY is not set.
6
+ */
7
+ export class Analytics {
8
+ apiKey;
9
+ host;
10
+ constructor() {
11
+ this.apiKey = process.env.POSTHOG_API_KEY;
12
+ this.host = process.env.POSTHOG_HOST || 'https://app.posthog.com';
13
+ }
14
+ get enabled() {
15
+ return !!this.apiKey;
16
+ }
17
+ async trackEvent(event, properties) {
18
+ if (!this.enabled) {
19
+ return;
20
+ }
21
+ try {
22
+ await fetch(`${this.host}/capture/`, {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/json' },
25
+ body: JSON.stringify({
26
+ api_key: this.apiKey,
27
+ event,
28
+ properties: {
29
+ ...properties,
30
+ timestamp: new Date().toISOString(),
31
+ },
32
+ distinct_id: properties?.userId || 'anonymous',
33
+ }),
34
+ });
35
+ }
36
+ catch {
37
+ // Fire-and-forget -- never throw from analytics
38
+ }
39
+ }
40
+ async identifyUser(userId, properties) {
41
+ if (!this.enabled) {
42
+ return;
43
+ }
44
+ try {
45
+ await fetch(`${this.host}/capture/`, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({
49
+ api_key: this.apiKey,
50
+ event: '$identify',
51
+ distinct_id: userId,
52
+ properties,
53
+ }),
54
+ });
55
+ }
56
+ catch {
57
+ // Fire-and-forget
58
+ }
59
+ }
60
+ }
61
+ export const analytics = new Analytics();
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Cost Warning Utility
3
+ *
4
+ * Shows informational cost warnings before destructive operations.
5
+ * Best-effort: silently skips on any failure.
6
+ */
7
+ import { ui } from '../wizard/ui';
8
+ import { CostEstimator } from '../commands/cost/estimator';
9
+ /**
10
+ * Show an informational cost warning before a destructive operation.
11
+ * Calls CostEstimator.estimateDirectory() and displays the estimated
12
+ * monthly cost impact as a negative value (savings from destroying).
13
+ * Wrapped in try/catch — never throws.
14
+ */
15
+ export async function showDestructionCostWarning(directory) {
16
+ try {
17
+ const estimate = await CostEstimator.estimateDirectory(directory);
18
+ if (estimate.totalMonthlyCost > 0) {
19
+ ui.warning(`Estimated monthly cost impact: -$${estimate.totalMonthlyCost.toFixed(2)}/month`);
20
+ }
21
+ }
22
+ catch {
23
+ // Best-effort — silently skip if estimation fails
24
+ }
25
+ }
@@ -0,0 +1,42 @@
1
+ import { ConfigurationError } from './errors';
2
+ export function getEnv(key, defaultValue) {
3
+ const value = process.env[key];
4
+ if (value === undefined) {
5
+ if (defaultValue !== undefined) {
6
+ return defaultValue;
7
+ }
8
+ throw new ConfigurationError(`Environment variable ${key} is required but not set`, 'nimbus', {
9
+ key,
10
+ });
11
+ }
12
+ return value;
13
+ }
14
+ export function getEnvOptional(key) {
15
+ return process.env[key];
16
+ }
17
+ export function getEnvNumber(key, defaultValue) {
18
+ const value = process.env[key];
19
+ if (value === undefined) {
20
+ if (defaultValue !== undefined) {
21
+ return defaultValue;
22
+ }
23
+ throw new ConfigurationError(`Environment variable ${key} is required but not set`, 'nimbus', {
24
+ key,
25
+ });
26
+ }
27
+ const parsed = parseInt(value, 10);
28
+ if (isNaN(parsed)) {
29
+ throw new ConfigurationError(`Environment variable ${key} must be a valid number`, 'nimbus', {
30
+ key,
31
+ value,
32
+ });
33
+ }
34
+ return parsed;
35
+ }
36
+ export function getEnvBoolean(key, defaultValue = false) {
37
+ const value = process.env[key];
38
+ if (value === undefined) {
39
+ return defaultValue;
40
+ }
41
+ return value.toLowerCase() === 'true' || value === '1';
42
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Base Nimbus Error
3
+ */
4
+ export class NimbusError extends Error {
5
+ code;
6
+ service;
7
+ timestamp;
8
+ details;
9
+ constructor(message, code, service, details) {
10
+ super(message);
11
+ this.name = 'NimbusError';
12
+ this.code = code;
13
+ this.service = service;
14
+ this.timestamp = new Date().toISOString();
15
+ this.details = details;
16
+ }
17
+ toJSON() {
18
+ return {
19
+ code: this.code,
20
+ message: this.message,
21
+ service: this.service,
22
+ timestamp: this.timestamp,
23
+ details: this.details,
24
+ stack: this.stack,
25
+ };
26
+ }
27
+ }
28
+ export class ValidationError extends NimbusError {
29
+ constructor(message, service, details) {
30
+ super(message, 'VALIDATION_ERROR', service, details);
31
+ this.name = 'ValidationError';
32
+ }
33
+ }
34
+ export class ServiceUnavailableError extends NimbusError {
35
+ constructor(serviceName, details) {
36
+ super(`Service ${serviceName} is unavailable`, 'SERVICE_UNAVAILABLE', 'nimbus', details);
37
+ this.name = 'ServiceUnavailableError';
38
+ }
39
+ }
40
+ export class TimeoutError extends NimbusError {
41
+ constructor(operation, service, timeoutMs) {
42
+ super(`Operation ${operation} timed out after ${timeoutMs}ms`, 'TIMEOUT_ERROR', service, {
43
+ operation,
44
+ timeoutMs,
45
+ });
46
+ this.name = 'TimeoutError';
47
+ }
48
+ }
49
+ export class ConfigurationError extends NimbusError {
50
+ constructor(message, service, details) {
51
+ super(message, 'CONFIGURATION_ERROR', service, details);
52
+ this.name = 'ConfigurationError';
53
+ }
54
+ }
@@ -0,0 +1,22 @@
1
+ import { EventEmitter } from 'node:events';
2
+ class NimbusEventBus {
3
+ emitter = new EventEmitter();
4
+ constructor() {
5
+ this.emitter.setMaxListeners(50);
6
+ }
7
+ publish(event) {
8
+ this.emitter.emit(event.type, event);
9
+ this.emitter.emit('*', event);
10
+ }
11
+ subscribe(eventType, handler) {
12
+ this.emitter.on(eventType, handler);
13
+ return () => this.emitter.off(eventType, handler);
14
+ }
15
+ once(eventType, handler) {
16
+ this.emitter.once(eventType, handler);
17
+ }
18
+ removeAllListeners(eventType) {
19
+ this.emitter.removeAllListeners(eventType);
20
+ }
21
+ }
22
+ export const eventBus = new NimbusEventBus();
@@ -0,0 +1,16 @@
1
+ // Logger
2
+ export { logger, Logger } from './logger';
3
+ // Errors
4
+ export * from './errors';
5
+ // Validation
6
+ export * from './validation';
7
+ // Environment helpers
8
+ export * from './env';
9
+ // Service authentication
10
+ export * from './service-auth';
11
+ // Rate limiting
12
+ export * from './rate-limiter';
13
+ // Event bus
14
+ export * from './event-bus';
15
+ // Analytics (PostHog)
16
+ export * from './analytics';
@@ -0,0 +1,150 @@
1
+ const SENSITIVE_KEYS = new Set([
2
+ 'password',
3
+ 'passwordhash',
4
+ 'password_hash',
5
+ 'secret',
6
+ 'secretkey',
7
+ 'secret_key',
8
+ 'clientsecret',
9
+ 'client_secret',
10
+ 'token',
11
+ 'apikey',
12
+ 'api_key',
13
+ 'api-key',
14
+ 'authorization',
15
+ 'credentials',
16
+ 'credential',
17
+ 'accesstoken',
18
+ 'access_token',
19
+ 'refreshtoken',
20
+ 'refresh_token',
21
+ 'privatekey',
22
+ 'private_key',
23
+ 'signingkey',
24
+ 'signing_key',
25
+ 'encryptionkey',
26
+ 'encryption_key',
27
+ 'bearer',
28
+ 'auth',
29
+ 'authtoken',
30
+ 'auth_token',
31
+ 'sessiontoken',
32
+ 'session_token',
33
+ 'jwt',
34
+ 'jwttoken',
35
+ 'jwt_token',
36
+ ]);
37
+ const REDACTED = '[REDACTED]';
38
+ function sanitize(value, seen = new WeakSet()) {
39
+ if (value === null || value === undefined) {
40
+ return value;
41
+ }
42
+ if (typeof value === 'bigint') {
43
+ return value.toString();
44
+ }
45
+ if (typeof value !== 'object') {
46
+ return value;
47
+ }
48
+ const obj = value;
49
+ if (seen.has(obj)) {
50
+ return '[Circular]';
51
+ }
52
+ seen.add(obj);
53
+ if (obj instanceof Date) {
54
+ return obj.toISOString();
55
+ }
56
+ if (obj instanceof Map) {
57
+ return sanitize(Object.fromEntries(obj), seen);
58
+ }
59
+ if (obj instanceof Set) {
60
+ return sanitize([...obj], seen);
61
+ }
62
+ if (Array.isArray(obj)) {
63
+ return obj.map(item => sanitize(item, seen));
64
+ }
65
+ const sanitized = {};
66
+ for (const key of Object.keys(obj)) {
67
+ if (SENSITIVE_KEYS.has(key.toLowerCase())) {
68
+ sanitized[key] = REDACTED;
69
+ }
70
+ else {
71
+ sanitized[key] = sanitize(obj[key], seen);
72
+ }
73
+ }
74
+ return sanitized;
75
+ }
76
+ // Patterns to redact in error messages/stacks (connection strings, URLs with creds, etc.)
77
+ const SENSITIVE_PATTERNS = [
78
+ /(?<=:\/\/[^:]+:)[^@]+(?=@)/g, // URL password: protocol://user:PASSWORD@host
79
+ /(?<=password[=:])\s*\S+/gi, // password=VALUE or password: VALUE
80
+ /(?<=secret[=:])\s*\S+/gi, // secret=VALUE or secret: VALUE
81
+ /(?<=token[=:])\s*\S+/gi, // token=VALUE or token: VALUE
82
+ /(?<=apikey[=:])\s*\S+/gi, // apiKey=VALUE or apikey: VALUE
83
+ /(?<=authorization[=:])\s*\S+/gi, // authorization=VALUE
84
+ ];
85
+ function sanitizeString(str) {
86
+ let result = str;
87
+ for (const pattern of SENSITIVE_PATTERNS) {
88
+ result = result.replace(pattern, REDACTED);
89
+ }
90
+ return result;
91
+ }
92
+ function safeContext(context) {
93
+ if (context instanceof Error) {
94
+ const errorText = context.stack ?? context.message;
95
+ return sanitizeString(errorText);
96
+ }
97
+ return JSON.stringify(sanitize(context));
98
+ }
99
+ /**
100
+ * Simple logger implementation
101
+ */
102
+ class Logger {
103
+ level;
104
+ levels = ['debug', 'info', 'warn', 'error'];
105
+ constructor(level = 'info') {
106
+ this.level = level;
107
+ }
108
+ shouldLog(level) {
109
+ return this.levels.indexOf(level) >= this.levels.indexOf(this.level);
110
+ }
111
+ debug(message, context) {
112
+ if (this.shouldLog('debug')) {
113
+ const line = this.format('debug', message, context);
114
+ console.log(line);
115
+ }
116
+ }
117
+ info(message, context) {
118
+ if (this.shouldLog('info')) {
119
+ const line = this.format('info', message, context);
120
+ console.log(line);
121
+ }
122
+ }
123
+ warn(message, context) {
124
+ if (this.shouldLog('warn')) {
125
+ const line = this.format('warn', message, context);
126
+ console.warn(line);
127
+ }
128
+ }
129
+ error(message, context) {
130
+ if (this.shouldLog('error')) {
131
+ const line = this.format('error', message, context);
132
+ console.error(line);
133
+ }
134
+ }
135
+ setLevel(level) {
136
+ this.level = level;
137
+ }
138
+ format(level, message, context) {
139
+ const timestamp = new Date().toISOString();
140
+ const levelStr = level.toUpperCase().padEnd(5);
141
+ if (context === undefined) {
142
+ return `[${timestamp}] [${levelStr}] ${message}`;
143
+ }
144
+ return `[${timestamp}] [${levelStr}] ${message} ${safeContext(context)}`;
145
+ }
146
+ }
147
+ // Export singleton instance
148
+ export const logger = new Logger(process.env.LOG_LEVEL || 'info');
149
+ // Export Logger class for testing
150
+ export { Logger };
@@ -0,0 +1,90 @@
1
+ import { logger } from './logger';
2
+ export class SimpleRateLimiter {
3
+ requestsPerMinute;
4
+ burstSize;
5
+ buckets;
6
+ cleanupInterval;
7
+ constructor(options) {
8
+ this.requestsPerMinute = options.requestsPerMinute;
9
+ this.burstSize = options.burstSize || options.requestsPerMinute;
10
+ this.buckets = new Map();
11
+ this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);
12
+ if (this.cleanupInterval &&
13
+ typeof this.cleanupInterval === 'object' &&
14
+ 'unref' in this.cleanupInterval) {
15
+ this.cleanupInterval.unref();
16
+ }
17
+ }
18
+ tryAcquire(clientId = 'default') {
19
+ const now = Date.now();
20
+ let bucket = this.buckets.get(clientId);
21
+ if (!bucket) {
22
+ bucket = { tokens: this.burstSize, lastRefill: now };
23
+ this.buckets.set(clientId, bucket);
24
+ }
25
+ const elapsed = now - bucket.lastRefill;
26
+ const refill = (elapsed / 60_000) * this.requestsPerMinute;
27
+ bucket.tokens = Math.min(this.burstSize, bucket.tokens + refill);
28
+ bucket.lastRefill = now;
29
+ if (bucket.tokens >= 1) {
30
+ bucket.tokens -= 1;
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+ getRemainingRequests(clientId = 'default') {
36
+ const bucket = this.buckets.get(clientId);
37
+ if (!bucket) {
38
+ return this.burstSize;
39
+ }
40
+ const elapsed = Date.now() - bucket.lastRefill;
41
+ const refill = (elapsed / 60_000) * this.requestsPerMinute;
42
+ return Math.min(this.burstSize, Math.floor(bucket.tokens + refill));
43
+ }
44
+ cleanup() {
45
+ const cutoff = Date.now() - 120_000;
46
+ for (const [clientId, bucket] of this.buckets) {
47
+ if (bucket.lastRefill < cutoff) {
48
+ this.buckets.delete(clientId);
49
+ }
50
+ }
51
+ }
52
+ destroy() {
53
+ if (this.cleanupInterval) {
54
+ clearInterval(this.cleanupInterval);
55
+ this.cleanupInterval = null;
56
+ }
57
+ }
58
+ }
59
+ function getClientIp(req) {
60
+ const forwarded = req.headers.get('x-forwarded-for');
61
+ if (forwarded) {
62
+ return forwarded.split(',')[0].trim();
63
+ }
64
+ const realIp = req.headers.get('x-real-ip');
65
+ if (realIp) {
66
+ return realIp;
67
+ }
68
+ return 'unknown';
69
+ }
70
+ export function rateLimitMiddleware(limiter) {
71
+ return (req) => {
72
+ const url = new URL(req.url);
73
+ if (url.pathname === '/health') {
74
+ return null;
75
+ }
76
+ const clientIp = getClientIp(req);
77
+ if (!limiter.tryAcquire(clientIp)) {
78
+ logger.warn(`Rate limited client ${clientIp} on ${url.pathname}`);
79
+ const retryAfter = Math.ceil(60 / limiter.getRemainingRequests(clientIp) || 60);
80
+ return new Response(JSON.stringify({ success: false, error: 'Too Many Requests' }), {
81
+ status: 429,
82
+ headers: {
83
+ 'Content-Type': 'application/json',
84
+ 'Retry-After': String(retryAfter),
85
+ },
86
+ });
87
+ }
88
+ return null;
89
+ };
90
+ }