@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,505 @@
1
+ /**
2
+ * Non-Interactive CLI Mode
3
+ *
4
+ * Runs the Nimbus agent with a prompt from the command line.
5
+ * Outputs results to stdout and exits.
6
+ *
7
+ * Usage:
8
+ * nimbus run "deploy the staging environment"
9
+ * nimbus run "fix the failing tests" --auto-approve
10
+ * echo "analyze this repo" | nimbus run --stdin
11
+ * nimbus run "estimate costs" --format json --model anthropic/claude-haiku-4-5
12
+ * nimbus run --schema # print the JSON output schema and exit
13
+ */
14
+ import { runAgentLoop } from '../agent/loop';
15
+ import { createPermissionState, checkPermission } from '../agent/permissions';
16
+ import { defaultToolRegistry } from '../tools/schemas/types';
17
+ import { standardTools } from '../tools/schemas/standard';
18
+ import { devopsTools } from '../tools/schemas/devops';
19
+ import { expandFileReferences } from '../agent/expand-files';
20
+ /**
21
+ * Parse `nimbus run` CLI arguments.
22
+ */
23
+ export function parseRunArgs(args) {
24
+ let prompt = '';
25
+ let format = 'text';
26
+ let autoApprove = false;
27
+ let stdin = false;
28
+ let stdinJson = false;
29
+ let model;
30
+ let mode = 'build';
31
+ let maxTurns = 50;
32
+ let timeout;
33
+ let rawToolOutput = false;
34
+ let schema = false;
35
+ let dryRun = false;
36
+ let exitOnError = true; // C5: default true for CI/CD POSIX convention
37
+ let context;
38
+ let workspace;
39
+ let namespace;
40
+ let notify;
41
+ let notifySlack;
42
+ let budget;
43
+ const positional = [];
44
+ for (let i = 0; i < args.length; i++) {
45
+ const arg = args[i];
46
+ switch (arg) {
47
+ case '--format':
48
+ format = (args[++i] ?? 'text');
49
+ break;
50
+ case '--json':
51
+ format = 'json';
52
+ break;
53
+ case '--auto-approve':
54
+ case '-y':
55
+ case '--non-interactive':
56
+ autoApprove = true;
57
+ break;
58
+ case '--stdin':
59
+ stdin = true;
60
+ break;
61
+ case '--stdin-json':
62
+ stdinJson = true;
63
+ stdin = true; // also read stdin
64
+ break;
65
+ case '--model':
66
+ model = args[++i];
67
+ break;
68
+ case '--mode':
69
+ mode = (args[++i] ?? 'build');
70
+ break;
71
+ case '--max-turns':
72
+ maxTurns = parseInt(args[++i] ?? '50', 10);
73
+ break;
74
+ case '--timeout':
75
+ // G13: timeout in seconds, converted to ms
76
+ timeout = parseInt(args[++i] ?? '0', 10) * 1000;
77
+ break;
78
+ case '--raw-tool-output':
79
+ // G15: print last tool output as JSON
80
+ rawToolOutput = true;
81
+ break;
82
+ case '--schema':
83
+ // G23: print the JSON output schema and exit
84
+ schema = true;
85
+ break;
86
+ case '--json-schema':
87
+ // GAP-16: print the stable JSON output schema and exit
88
+ schema = true;
89
+ break;
90
+ case '--dry-run':
91
+ // M1: dry-run forces plan mode — no mutations allowed
92
+ dryRun = true;
93
+ mode = 'plan';
94
+ break;
95
+ case '--exit-code-on-error':
96
+ // H3: exit with code 1 on failure
97
+ exitOnError = true;
98
+ break;
99
+ case '--no-exit-on-error':
100
+ // C5: legacy scripts can opt out of exit-on-error
101
+ exitOnError = false;
102
+ break;
103
+ case '--context':
104
+ // H3: kubectl context
105
+ context = args[++i];
106
+ break;
107
+ case '--workspace':
108
+ // H3: terraform workspace
109
+ workspace = args[++i];
110
+ break;
111
+ case '--namespace':
112
+ case '-n':
113
+ // H3: kubernetes namespace
114
+ namespace = args[++i];
115
+ break;
116
+ case '--notify':
117
+ // H3: webhook URL
118
+ notify = args[++i];
119
+ break;
120
+ case '--notify-slack':
121
+ // H3: slack webhook
122
+ notifySlack = args[++i];
123
+ break;
124
+ case '--budget':
125
+ // G16: cost budget in USD
126
+ budget = parseFloat(args[++i] ?? '0');
127
+ break;
128
+ default:
129
+ if (!arg.startsWith('-')) {
130
+ positional.push(arg);
131
+ }
132
+ break;
133
+ }
134
+ }
135
+ prompt = positional.join(' ');
136
+ return { prompt, format, autoApprove, stdin, stdinJson, model, mode, maxTurns, timeout, rawToolOutput, schema, dryRun, exitOnError, context, workspace, namespace, notify, notifySlack, budget };
137
+ }
138
+ /**
139
+ * Execute a non-interactive run.
140
+ */
141
+ export async function executeRun(router, options) {
142
+ // G23 / GAP-16: --schema / --json-schema flag: print the JSON output schema and exit immediately
143
+ if (options.schema) {
144
+ const schema = {
145
+ type: 'object',
146
+ properties: {
147
+ success: { type: 'boolean', description: 'Whether the agent completed without error' },
148
+ output: { type: 'string', description: 'Final text response from the agent' },
149
+ cost: { type: 'number', description: 'Total cost in USD' },
150
+ turns: { type: 'number', description: 'Number of LLM turns taken' },
151
+ toolCalls: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, success: { type: 'boolean' }, durationMs: { type: 'number' } } } },
152
+ errors: { type: 'array', items: { type: 'string' }, description: 'Any error messages encountered' },
153
+ },
154
+ required: ['success', 'output', 'cost', 'turns', 'toolCalls', 'errors'],
155
+ };
156
+ process.stdout.write(JSON.stringify(schema, null, 2) + '\n');
157
+ return {
158
+ success: true,
159
+ output: '',
160
+ turns: 0,
161
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
162
+ cost: 0,
163
+ interrupted: false,
164
+ };
165
+ }
166
+ // H3: Inject context/workspace/namespace into environment before agent loop
167
+ if (options.context)
168
+ process.env.KUBECTL_CONTEXT = options.context;
169
+ if (options.workspace)
170
+ process.env.TF_WORKSPACE = options.workspace;
171
+ if (options.namespace)
172
+ process.env.K8S_NAMESPACE = options.namespace;
173
+ // Get prompt from stdin if requested
174
+ let prompt = options.prompt;
175
+ if (options.stdin && !prompt) {
176
+ const stdinContent = await readStdin();
177
+ // L5: --stdin-json support: parse stdin as { prompt, mode, model, autoApprove, maxTurns }
178
+ if (options.stdinJson && stdinContent) {
179
+ try {
180
+ const config = JSON.parse(stdinContent);
181
+ if (typeof config.prompt === 'string')
182
+ prompt = config.prompt;
183
+ if (config.mode === 'plan' || config.mode === 'build' || config.mode === 'deploy') {
184
+ options = { ...options, mode: config.mode };
185
+ }
186
+ if (typeof config.model === 'string')
187
+ options = { ...options, model: config.model };
188
+ if (typeof config.autoApprove === 'boolean')
189
+ options = { ...options, autoApprove: config.autoApprove };
190
+ if (typeof config.maxTurns === 'number')
191
+ options = { ...options, maxTurns: config.maxTurns };
192
+ }
193
+ catch {
194
+ // If JSON parse fails, treat stdin as raw prompt text
195
+ prompt = stdinContent;
196
+ }
197
+ }
198
+ else {
199
+ prompt = stdinContent;
200
+ }
201
+ }
202
+ // Expand @file references in the prompt
203
+ prompt = expandFileReferences(prompt, process.cwd());
204
+ if (!prompt) {
205
+ return {
206
+ success: false,
207
+ output: 'Error: No prompt provided. Usage: nimbus run "your prompt"',
208
+ turns: 0,
209
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
210
+ cost: 0,
211
+ interrupted: false,
212
+ };
213
+ }
214
+ // Set up tool registry
215
+ const registry = defaultToolRegistry;
216
+ if (registry.size === 0) {
217
+ // Register all built-in tools
218
+ for (const tool of [...standardTools, ...devopsTools]) {
219
+ try {
220
+ registry.register(tool);
221
+ }
222
+ catch {
223
+ /* skip duplicates */
224
+ }
225
+ }
226
+ }
227
+ // Set up permission state
228
+ const permissionState = createPermissionState();
229
+ // Collect output
230
+ const outputParts = [];
231
+ const tableRows = [];
232
+ // G13: Set up timeout AbortController if --timeout was specified
233
+ let timeoutAbortController;
234
+ let timeoutHandle;
235
+ if (options.timeout && options.timeout > 0) {
236
+ timeoutAbortController = new AbortController();
237
+ timeoutHandle = setTimeout(() => {
238
+ timeoutAbortController.abort();
239
+ if (options.format === 'text') {
240
+ process.stderr.write(`\n[Timeout: agent loop aborted after ${options.timeout / 1000}s]\n`);
241
+ }
242
+ }, options.timeout);
243
+ }
244
+ // G15: Track last tool output for --raw-tool-output
245
+ let lastToolName = '';
246
+ let lastToolOutput = '';
247
+ // G23: Track all tool calls for structured JSON output
248
+ const allToolCalls = [];
249
+ // H1: Discover infra context for CI/CD pipelines (best-effort, non-blocking)
250
+ let infraContext;
251
+ try {
252
+ const { discoverInfraContext } = await import('../cli/init');
253
+ infraContext = await discoverInfraContext(process.cwd());
254
+ }
255
+ catch { /* non-critical */ }
256
+ // Run the agent loop
257
+ const result = await runAgentLoop(prompt, [], {
258
+ router,
259
+ toolRegistry: registry,
260
+ mode: options.mode,
261
+ maxTurns: options.maxTurns,
262
+ model: options.model,
263
+ cwd: process.cwd(),
264
+ signal: timeoutAbortController?.signal,
265
+ dryRun: options.dryRun,
266
+ costBudgetUSD: options.budget,
267
+ infraContext,
268
+ onText: text => {
269
+ outputParts.push(text);
270
+ if (options.format === 'text') {
271
+ process.stdout.write(text);
272
+ }
273
+ },
274
+ onToolCallStart: toolCall => {
275
+ if (options.format === 'text') {
276
+ process.stderr.write(`\n[Tool: ${toolCall.name}]\n`);
277
+ }
278
+ },
279
+ onToolCallEnd: (toolCall, result) => {
280
+ if (options.format === 'text' && result.isError) {
281
+ process.stderr.write(`[Error: ${result.error}]\n`);
282
+ }
283
+ if (options.format === 'table') {
284
+ tableRows.push({
285
+ tool: toolCall.name,
286
+ status: result.isError ? 'error' : 'ok',
287
+ output: (result.output ?? result.error ?? '').slice(0, 80),
288
+ });
289
+ }
290
+ // G15: track last tool output
291
+ lastToolName = toolCall.name;
292
+ lastToolOutput = result.isError ? (result.error ?? '') : (result.output ?? '');
293
+ // G23: accumulate all tool calls for structured JSON output
294
+ allToolCalls.push({
295
+ name: toolCall.name,
296
+ input: toolCall.input && typeof toolCall.input === 'object'
297
+ ? toolCall.input
298
+ : {},
299
+ output: result.isError ? (result.error ?? '') : (result.output ?? ''),
300
+ isError: result.isError ?? false,
301
+ });
302
+ },
303
+ checkPermission: async (tool, input) => {
304
+ if (options.autoApprove) {
305
+ return 'allow';
306
+ }
307
+ const decision = checkPermission(tool, input, permissionState);
308
+ if (decision === 'ask') {
309
+ // In non-interactive mode without --auto-approve, deny by default
310
+ return 'deny';
311
+ }
312
+ return decision;
313
+ },
314
+ });
315
+ // G13: Clear the timeout timer if we finished before it fired
316
+ if (timeoutHandle) {
317
+ clearTimeout(timeoutHandle);
318
+ }
319
+ const output = outputParts.join('');
320
+ // G15: --raw-tool-output: print last tool call as JSON to stdout
321
+ if (options.rawToolOutput && lastToolName) {
322
+ console.log(JSON.stringify({ tool: lastToolName, output: lastToolOutput }));
323
+ return {
324
+ success: !result.interrupted,
325
+ output,
326
+ turns: result.turns,
327
+ usage: result.usage,
328
+ cost: result.totalCost,
329
+ interrupted: result.interrupted,
330
+ };
331
+ }
332
+ // Format output
333
+ if (options.format === 'json') {
334
+ // GAP-16 / G23: structured JSON output matching the stable RunJsonOutput schema
335
+ // L2: Extract terraform plan summary from tool outputs for CI-friendly output
336
+ let planSummary;
337
+ const tfPlanCall = allToolCalls.find(tc => tc.name === 'terraform' && tc.output && /Plan:/.test(tc.output));
338
+ if (tfPlanCall?.output) {
339
+ const planLine = tfPlanCall.output.match(/Plan:\s*(\d+)\s*to add,\s*(\d+)\s*to change,\s*(\d+)\s*to destroy/i);
340
+ if (planLine) {
341
+ planSummary = {
342
+ toAdd: parseInt(planLine[1]),
343
+ toChange: parseInt(planLine[2]),
344
+ toDestroy: parseInt(planLine[3]),
345
+ raw: planLine[0],
346
+ };
347
+ }
348
+ }
349
+ const jsonResult = {
350
+ success: !result.interrupted,
351
+ output,
352
+ cost: result.totalCost ?? 0,
353
+ turns: result.turns ?? 0,
354
+ toolCalls: allToolCalls.map(tc => ({
355
+ name: tc.name,
356
+ success: !tc.isError,
357
+ durationMs: 0,
358
+ })),
359
+ errors: allToolCalls.filter(tc => tc.isError).map(tc => tc.output).filter(Boolean),
360
+ ...(planSummary ? { planSummary } : {}),
361
+ };
362
+ // M3: Build devops_summary from tool calls
363
+ const DEVOPS_TOOL_NAMES = new Set([
364
+ 'terraform', 'kubectl', 'helm', 'aws', 'gcloud', 'az',
365
+ 'docker', 'secrets', 'cicd', 'monitor', 'gitops', 'cloud_action',
366
+ 'logs', 'certs', 'mesh', 'cfn', 'k8s_rbac', 'generate_infra',
367
+ 'kubectl_context', 'helm_values', 'cost_estimate', 'cloud_discover',
368
+ ]);
369
+ const toolsUsed = [...new Set(allToolCalls.map(tc => tc.name))];
370
+ const devopsToolsUsed = toolsUsed.filter(t => DEVOPS_TOOL_NAMES.has(t));
371
+ if (devopsToolsUsed.length > 0) {
372
+ jsonResult.devops_summary = {
373
+ tools_used: devopsToolsUsed,
374
+ tool_call_count: allToolCalls.length,
375
+ devops_tool_count: allToolCalls.filter(tc => DEVOPS_TOOL_NAMES.has(tc.name)).length,
376
+ };
377
+ }
378
+ // M1: Add infraContext and toolsUsed fields
379
+ jsonResult.toolsUsed = toolsUsed;
380
+ if (infraContext) {
381
+ jsonResult.infraContext = {
382
+ terraformWorkspace: infraContext.terraformWorkspace,
383
+ kubectlContext: infraContext.kubectlContext,
384
+ awsAccount: infraContext.awsAccount,
385
+ };
386
+ }
387
+ console.log(JSON.stringify(jsonResult, null, 2));
388
+ }
389
+ else if (options.format === 'text') {
390
+ // Text was already streamed above
391
+ console.log(''); // Final newline
392
+ }
393
+ else if (options.format === 'table') {
394
+ // ASCII table of tool calls
395
+ const COL_TOOL = 30;
396
+ const COL_STATUS = 6;
397
+ const COL_OUTPUT = 80;
398
+ const divider = `${'-'.repeat(COL_TOOL + 2)}+${'-'.repeat(COL_STATUS + 2)}+${'-'.repeat(COL_OUTPUT + 2)}`;
399
+ const pad = (s, n) => s.slice(0, n).padEnd(n);
400
+ console.log(divider);
401
+ console.log(`| ${pad('Tool', COL_TOOL)} | ${pad('Status', COL_STATUS)} | ${pad('Output', COL_OUTPUT)} |`);
402
+ console.log(divider);
403
+ for (const row of tableRows) {
404
+ console.log(`| ${pad(row.tool, COL_TOOL)} | ${pad(row.status, COL_STATUS)} | ${pad(row.output, COL_OUTPUT)} |`);
405
+ }
406
+ console.log(divider);
407
+ console.log('');
408
+ // Also print final text output
409
+ if (output)
410
+ process.stdout.write(output + '\n');
411
+ }
412
+ const runResult = {
413
+ success: !result.interrupted,
414
+ output,
415
+ turns: result.turns,
416
+ usage: result.usage,
417
+ cost: result.totalCost,
418
+ interrupted: result.interrupted,
419
+ };
420
+ // H3: Fire webhook notifications after run completes
421
+ const duration = Date.now(); // approximate; real duration would need a start time
422
+ const notifyPayload = {
423
+ success: runResult.success,
424
+ output: runResult.output.slice(0, 2000), // truncate for webhook
425
+ cost: runResult.cost,
426
+ duration,
427
+ };
428
+ if (options.notify) {
429
+ try {
430
+ await fetch(options.notify, {
431
+ method: 'POST',
432
+ headers: { 'Content-Type': 'application/json' },
433
+ body: JSON.stringify(notifyPayload),
434
+ signal: AbortSignal.timeout(10_000),
435
+ });
436
+ }
437
+ catch {
438
+ // Webhook failure is non-fatal
439
+ process.stderr.write(`[Warning: notification webhook failed]\n`);
440
+ }
441
+ }
442
+ if (options.notifySlack) {
443
+ try {
444
+ const slackPayload = {
445
+ text: runResult.success
446
+ ? `:white_check_mark: *Nimbus run succeeded*\n${runResult.output.slice(0, 500)}`
447
+ : `:x: *Nimbus run failed*\n${runResult.output.slice(0, 500)}`,
448
+ attachments: [
449
+ {
450
+ fields: [
451
+ { title: 'Cost', value: `$${runResult.cost.toFixed(4)}`, short: true },
452
+ { title: 'Turns', value: String(runResult.turns), short: true },
453
+ ],
454
+ },
455
+ ],
456
+ };
457
+ await fetch(options.notifySlack, {
458
+ method: 'POST',
459
+ headers: { 'Content-Type': 'application/json' },
460
+ body: JSON.stringify(slackPayload),
461
+ signal: AbortSignal.timeout(10_000),
462
+ });
463
+ }
464
+ catch {
465
+ process.stderr.write(`[Warning: Slack notification failed]\n`);
466
+ }
467
+ }
468
+ // H3: Exit with code 1 if the run failed and --exit-code-on-error is set
469
+ if (options.exitOnError && !runResult.success) {
470
+ process.exit(1);
471
+ }
472
+ return runResult;
473
+ }
474
+ /**
475
+ * Read all input from stdin.
476
+ */
477
+ async function readStdin() {
478
+ // If stdin is a TTY (no pipe), resolve immediately with empty string
479
+ if (process.stdin.isTTY) {
480
+ return '';
481
+ }
482
+ const chunks = [];
483
+ return new Promise((resolve, reject) => {
484
+ // 30-second timeout for stdin reads to prevent hanging
485
+ const timeout = setTimeout(() => {
486
+ process.stdin.removeAllListeners();
487
+ resolve(Buffer.concat(chunks).toString('utf-8').trim());
488
+ }, 30_000);
489
+ process.stdin.on('data', chunk => chunks.push(Buffer.from(chunk)));
490
+ process.stdin.on('end', () => {
491
+ clearTimeout(timeout);
492
+ resolve(Buffer.concat(chunks).toString('utf-8').trim());
493
+ });
494
+ process.stdin.on('error', err => {
495
+ clearTimeout(timeout);
496
+ // On error, use whatever we've collected so far
497
+ if (chunks.length > 0) {
498
+ resolve(Buffer.concat(chunks).toString('utf-8').trim());
499
+ }
500
+ else {
501
+ reject(err);
502
+ }
503
+ });
504
+ });
505
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * HTTP Basic Auth Middleware for `nimbus serve`
3
+ *
4
+ * Provides optional HTTP Basic Authentication for the headless API server.
5
+ * Disabled by default for local development; enabled via `--auth user:pass`.
6
+ *
7
+ * Unauthenticated endpoints (always bypassed):
8
+ * - GET /api/health
9
+ * - GET /api/openapi.json
10
+ * - OPTIONS (CORS preflight)
11
+ *
12
+ * @module cli/serve-auth
13
+ */
14
+ // ---------------------------------------------------------------------------
15
+ // Paths that are always public (no auth required)
16
+ // ---------------------------------------------------------------------------
17
+ const PUBLIC_PATHS = new Set(['/api/health', '/api/openapi.json']);
18
+ // ---------------------------------------------------------------------------
19
+ // Middleware Factory
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Create an Elysia-compatible `onBeforeHandle` function that enforces
23
+ * HTTP Basic Authentication on protected endpoints.
24
+ *
25
+ * @param options - The username and password to validate against.
26
+ * @returns A handler that short-circuits with 401 when credentials are
27
+ * missing or invalid, or `undefined` to let the request through.
28
+ */
29
+ export function createAuthMiddleware(options) {
30
+ const expectedToken = btoa(`${options.username}:${options.password}`);
31
+ return ({ request, set }) => {
32
+ const url = new URL(request.url);
33
+ // Skip auth for public endpoints
34
+ if (PUBLIC_PATHS.has(url.pathname)) {
35
+ return undefined;
36
+ }
37
+ // Skip auth for CORS preflight requests
38
+ if (request.method === 'OPTIONS') {
39
+ return undefined;
40
+ }
41
+ const authHeader = request.headers.get('Authorization');
42
+ if (!authHeader) {
43
+ set.status = 401;
44
+ set.headers = { 'WWW-Authenticate': 'Basic realm="Nimbus API"' };
45
+ return { error: 'Authentication required' };
46
+ }
47
+ const [scheme, token] = authHeader.split(' ');
48
+ if (scheme !== 'Basic' || token !== expectedToken) {
49
+ set.status = 401;
50
+ set.headers = { 'WWW-Authenticate': 'Basic realm="Nimbus API"' };
51
+ return { error: 'Invalid credentials' };
52
+ }
53
+ // Credentials valid -- proceed
54
+ return undefined;
55
+ };
56
+ }