@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
@@ -22,6 +22,7 @@ export interface TfCommandOptions {
22
22
  recursive?: boolean;
23
23
  diff?: boolean;
24
24
  type?: 'plan' | 'apply';
25
+ workspace?: string;
25
26
  }
26
27
 
27
28
  /**
@@ -114,28 +115,88 @@ export async function tfPlanCommand(options: TfCommandOptions = {}): Promise<voi
114
115
  }
115
116
 
116
117
  /**
117
- * Apply Terraform changes
118
+ * Apply Terraform changes — shows plan first and prompts for confirmation
119
+ * unless --auto-approve or a planFile is provided.
118
120
  */
119
121
  export async function tfApplyCommand(options: TfCommandOptions = {}): Promise<void> {
120
122
  const directory = options.directory || process.cwd();
123
+ const { confirm } = await import('../../wizard/prompts');
124
+ const os = await import('os');
125
+ const path = await import('path');
121
126
 
122
127
  ui.header('Terraform Apply');
123
128
  ui.info(`Directory: ${directory}`);
124
129
 
125
- if (!options.autoApprove && !options.planFile) {
126
- ui.warning('Running with -auto-approve flag or specify a plan file for non-interactive mode');
127
- }
128
-
129
- ui.startSpinner({ message: 'Applying Terraform changes...' });
130
-
131
130
  try {
132
131
  const available = await terraformClient.isAvailable();
133
132
  if (!available) {
134
- ui.stopSpinnerFail('Terraform Tools Service not available');
133
+ ui.error('Terraform Tools Service not available');
135
134
  ui.error('Please ensure the Terraform Tools Service is running.');
136
135
  return;
137
136
  }
138
137
 
138
+ // Select workspace if specified
139
+ if (options.workspace) {
140
+ ui.startSpinner({ message: `Selecting workspace "${options.workspace}"...` });
141
+ await terraformClient.workspace.select(options.workspace, directory);
142
+ ui.stopSpinnerSuccess(`Workspace: ${options.workspace}`);
143
+ }
144
+
145
+ // If no planFile and not auto-approving, run plan first and prompt
146
+ if (!options.planFile && !options.autoApprove) {
147
+ const tmpPlanPath = path.join(os.tmpdir(), `nimbus-tfplan-${Date.now()}.bin`);
148
+
149
+ ui.startSpinner({ message: 'Running terraform plan...' });
150
+ const planResult = await terraformClient.plan(directory, {
151
+ varFile: options.varFile,
152
+ vars: options.vars,
153
+ out: tmpPlanPath,
154
+ });
155
+
156
+ if (!planResult.success) {
157
+ ui.stopSpinnerFail('Terraform plan failed');
158
+ if (planResult.error) ui.error(planResult.error);
159
+ return;
160
+ }
161
+
162
+ if (!planResult.hasChanges) {
163
+ ui.stopSpinnerSuccess('No changes — infrastructure is up-to-date');
164
+ return;
165
+ }
166
+
167
+ ui.stopSpinnerSuccess('Plan complete');
168
+ if (planResult.output) {
169
+ ui.box({ title: 'Terraform Plan', content: planResult.output });
170
+ }
171
+
172
+ const proceed = await confirm({ message: 'Apply these changes?', defaultValue: false });
173
+ if (!proceed) {
174
+ ui.info('Apply cancelled.');
175
+ return;
176
+ }
177
+
178
+ // Apply using the saved plan file
179
+ ui.startSpinner({ message: 'Applying Terraform changes...' });
180
+ const applyResult = await terraformClient.apply(directory, {
181
+ planFile: tmpPlanPath,
182
+ varFile: options.varFile,
183
+ vars: options.vars,
184
+ });
185
+
186
+ if (applyResult.success) {
187
+ ui.stopSpinnerSuccess('Terraform apply completed successfully');
188
+ if (applyResult.output) {
189
+ ui.box({ title: 'Apply Output', content: applyResult.output });
190
+ }
191
+ } else {
192
+ ui.stopSpinnerFail('Terraform apply failed');
193
+ if (applyResult.error) ui.error(applyResult.error);
194
+ }
195
+ return;
196
+ }
197
+
198
+ // auto-approve or explicit planFile path: apply directly
199
+ ui.startSpinner({ message: 'Applying Terraform changes...' });
139
200
  const result = await terraformClient.apply(directory, {
140
201
  planFile: options.planFile,
141
202
  autoApprove: options.autoApprove,
@@ -150,9 +211,7 @@ export async function tfApplyCommand(options: TfCommandOptions = {}): Promise<vo
150
211
  }
151
212
  } else {
152
213
  ui.stopSpinnerFail('Terraform apply failed');
153
- if (result.error) {
154
- ui.error(result.error);
155
- }
214
+ if (result.error) ui.error(result.error);
156
215
  }
157
216
  } catch (error: any) {
158
217
  ui.stopSpinnerFail('Error applying Terraform changes');
@@ -24,6 +24,10 @@ export interface UpgradeOptions {
24
24
  force?: boolean;
25
25
  /** Check only, don't actually upgrade */
26
26
  check?: boolean;
27
+ /** Show what would be upgraded without actually upgrading */
28
+ dryRun?: boolean;
29
+ /** Show the changelog for the latest version */
30
+ changelog?: boolean;
27
31
  }
28
32
 
29
33
  /** Detected installation method for nimbus. */
@@ -179,6 +183,35 @@ async function fetchGitHubReleaseVersion(): Promise<string | null> {
179
183
  }
180
184
  }
181
185
 
186
+ // ---------------------------------------------------------------------------
187
+ // Changelog fetching
188
+ // ---------------------------------------------------------------------------
189
+
190
+ /**
191
+ * Fetch the raw CHANGELOG.md from GitHub. Returns the last ~20 lines on success,
192
+ * or null if unavailable.
193
+ */
194
+ async function fetchChangelog(): Promise<string | null> {
195
+ try {
196
+ const urls = [
197
+ `https://raw.githubusercontent.com/${GITHUB_REPO}/main/CHANGELOG.md`,
198
+ `https://raw.githubusercontent.com/${GITHUB_REPO}/main/CHANGELOG`,
199
+ ];
200
+ for (const url of urls) {
201
+ const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
202
+ if (response.ok) {
203
+ const text = await response.text();
204
+ const lines = text.split('\n');
205
+ // Return last 20 lines or the full content if shorter
206
+ return lines.slice(-20).join('\n');
207
+ }
208
+ }
209
+ return null;
210
+ } catch {
211
+ return null;
212
+ }
213
+ }
214
+
182
215
  // ---------------------------------------------------------------------------
183
216
  // Installation method detection
184
217
  // ---------------------------------------------------------------------------
@@ -412,6 +445,30 @@ export async function upgradeCommand(options: UpgradeOptions = {}): Promise<void
412
445
  }
413
446
  console.log('');
414
447
 
448
+ // --dry-run: show what would be upgraded without upgrading
449
+ if (options.dryRun) {
450
+ console.log(
451
+ `${yellow('Dry run:')} Current: ${bold(VERSION)} → Latest: ${bold(green(latestVersion))}`
452
+ );
453
+ console.log(dim(` Source: ${source === 'github' ? 'GitHub Releases' : 'npm registry'}`));
454
+ console.log('');
455
+ console.log(dim('No changes were made. Remove --dry-run to perform the upgrade.'));
456
+ return;
457
+ }
458
+
459
+ // --changelog: fetch and display the changelog
460
+ if (options.changelog) {
461
+ console.log(bold(`Changelog for nimbus ${latestVersion}:`));
462
+ console.log('');
463
+ const cl = await fetchChangelog();
464
+ if (cl) {
465
+ console.log(cl);
466
+ } else {
467
+ console.log(dim(`Changelog not available. View releases at: https://github.com/${GITHUB_REPO}/releases`));
468
+ }
469
+ return;
470
+ }
471
+
415
472
  if (options.check) {
416
473
  return;
417
474
  }
@@ -66,52 +66,45 @@ function getBunVersion(): string | undefined {
66
66
  }
67
67
 
68
68
  /**
69
- * Fetch component versions from services (for verbose mode)
69
+ * Fetch DevOps CLI tool versions (for verbose mode)
70
70
  */
71
- async function fetchComponentVersions(): Promise<Record<string, string>> {
72
- const components: Record<string, string> = {};
73
- const services = [
74
- { name: 'core-engine', url: process.env.CORE_ENGINE_URL || 'http://localhost:3001' },
75
- { name: 'llm-service', url: process.env.LLM_SERVICE_URL || 'http://localhost:3002' },
76
- {
77
- name: 'generator-service',
78
- url: process.env.GENERATOR_SERVICE_URL || 'http://localhost:3003',
79
- },
80
- { name: 'terraform-tools', url: process.env.TERRAFORM_TOOLS_URL || 'http://localhost:3006' },
81
- { name: 'k8s-tools', url: process.env.K8S_TOOLS_URL || 'http://localhost:3007' },
82
- { name: 'helm-tools', url: process.env.HELM_TOOLS_URL || 'http://localhost:3008' },
71
+ async function fetchDevOpsVersions(): Promise<Record<string, string>> {
72
+ const { execFileSync } = await import('child_process');
73
+ const tools = [
74
+ { name: 'terraform', args: ['version', '-json'] },
75
+ { name: 'kubectl', args: ['version', '--client', '--output=json'] },
76
+ { name: 'helm', args: ['version', '--short'] },
77
+ { name: 'aws', args: ['--version'] },
78
+ { name: 'gcloud', args: ['version'] },
79
+ { name: 'az', args: ['version'] },
83
80
  ];
84
81
 
85
- const fetchWithTimeout = async (url: string, timeout = 2000): Promise<Response> => {
86
- const controller = new AbortController();
87
- const timeoutId = setTimeout(() => controller.abort(), timeout);
82
+ const versions: Record<string, string> = {};
83
+ for (const tool of tools) {
88
84
  try {
89
- const response = await fetch(url, { signal: controller.signal });
90
- clearTimeout(timeoutId);
91
- return response;
92
- } catch {
93
- clearTimeout(timeoutId);
94
- throw new Error('Timeout');
95
- }
96
- };
97
-
98
- await Promise.all(
99
- services.map(async service => {
85
+ const output = execFileSync(tool.name, tool.args, {
86
+ encoding: 'utf-8',
87
+ timeout: 5000,
88
+ stdio: ['pipe', 'pipe', 'pipe'],
89
+ });
90
+ // Try to parse version string from JSON output
91
+ let version = 'installed';
100
92
  try {
101
- const response = await fetchWithTimeout(`${service.url}/health`);
102
- if (response.ok) {
103
- const data = (await response.json()) as { version?: string; status?: string };
104
- components[service.name] = data.version || 'running';
105
- } else {
106
- components[service.name] = 'unavailable';
107
- }
93
+ const parsed = JSON.parse(output);
94
+ version =
95
+ parsed.terraform_version ||
96
+ parsed.clientVersion?.gitVersion ||
97
+ 'installed';
108
98
  } catch {
109
- components[service.name] = 'unavailable';
99
+ const match = output.match(/[\d]+\.[\d]+\.[\d]+/);
100
+ if (match) version = `v${match[0]}`;
110
101
  }
111
- })
112
- );
113
-
114
- return components;
102
+ versions[tool.name] = version;
103
+ } catch {
104
+ versions[tool.name] = 'not installed';
105
+ }
106
+ }
107
+ return versions;
115
108
  }
116
109
 
117
110
  /**
@@ -120,25 +113,35 @@ async function fetchComponentVersions(): Promise<Record<string, string>> {
120
113
  export async function versionCommand(options: VersionOptions = {}): Promise<void> {
121
114
  logger.debug('Running version command', { options });
122
115
 
116
+ const cliVersion = getCliVersion();
123
117
  const versionInfo: VersionInfo = {
124
- cli: getCliVersion(),
118
+ cli: cliVersion,
125
119
  node: process.version,
126
120
  bun: getBunVersion(),
127
121
  platform: process.platform,
128
122
  arch: process.arch,
129
123
  };
130
124
 
131
- // Fetch component versions in verbose mode
125
+ // Fetch DevOps tool versions in verbose mode
132
126
  if (options.verbose) {
133
- ui.startSpinner({ message: 'Fetching component versions...' });
134
- versionInfo.components = await fetchComponentVersions();
127
+ ui.startSpinner({ message: 'Checking DevOps tool versions...' });
128
+ versionInfo.components = await fetchDevOpsVersions();
135
129
  ui.stopSpinnerSuccess('');
136
130
  }
137
131
 
138
- // JSON output
132
+ // JSON output — expose `version` field (alias for `cli`) for L3 compatibility
139
133
  if (options.json) {
140
- console.log(JSON.stringify(versionInfo, null, 2));
141
- return;
134
+ const jsonOutput: Record<string, unknown> = {
135
+ version: cliVersion,
136
+ node: versionInfo.node,
137
+ platform: versionInfo.platform,
138
+ arch: versionInfo.arch,
139
+ cli: versionInfo.cli,
140
+ };
141
+ if (versionInfo.bun) jsonOutput.bun = versionInfo.bun;
142
+ if (versionInfo.components) jsonOutput.components = versionInfo.components;
143
+ console.log(JSON.stringify(jsonOutput, null, 2));
144
+ process.exit(0);
142
145
  }
143
146
 
144
147
  // Human-readable output
@@ -156,11 +159,12 @@ export async function versionCommand(options: VersionOptions = {}): Promise<void
156
159
 
157
160
  if (versionInfo.components) {
158
161
  ui.newLine();
159
- ui.print(`Components:`);
162
+ ui.print(`DevOps Tools:`);
160
163
  for (const [name, version] of Object.entries(versionInfo.components)) {
161
- const status =
162
- version === 'unavailable' ? ui.color(version, 'red') : ui.color(version, 'green');
163
- ui.print(` ${name.padEnd(18)} ${status}`);
164
+ const isInstalled = version !== 'not installed';
165
+ const icon = isInstalled ? '[+]' : '[-]';
166
+ const color = isInstalled ? 'green' : 'red';
167
+ ui.print(` ${ui.color(icon, color)} ${name.padEnd(12)} ${version}`);
164
168
  }
165
169
  }
166
170
  }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * nimbus watch — Watch files and trigger agent runs on change (M3)
3
+ *
4
+ * Usage:
5
+ * nimbus watch "*.tf" --run "validate the terraform changes"
6
+ * nimbus watch "src/**" --run "run tests" --debounce 2000
7
+ */
8
+
9
+ import { basename } from 'node:path';
10
+ import { FSWatcher, watch as fsWatch } from 'node:fs';
11
+
12
+ export interface WatchOptions {
13
+ /** Glob pattern to watch (e.g. "*.tf") */
14
+ glob: string;
15
+ /** Agent prompt to run on change */
16
+ run?: string;
17
+ /** Debounce delay in ms (default 500) */
18
+ debounce?: number;
19
+ /** Auto-approve agent runs */
20
+ autoApprove?: boolean;
21
+ /** Maximum number of runs before stopping (0 = unlimited) */
22
+ maxRuns?: number;
23
+ }
24
+
25
+ /**
26
+ * Simple glob pattern matching without external dependencies.
27
+ * Supports: *.ext, *.*, prefix/**, prefix/name.ext
28
+ */
29
+ function matchGlob(filename: string, pattern: string): boolean {
30
+ // Normalize separators
31
+ const f = filename.replace(/\\/g, '/');
32
+ const p = pattern.replace(/\\/g, '/');
33
+
34
+ // Convert glob pattern to regex
35
+ const regexStr = p
36
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars (but not * and ?)
37
+ .replace(/\*\*/g, '__GLOBSTAR__')
38
+ .replace(/\*/g, '[^/]*')
39
+ .replace(/\?/g, '[^/]')
40
+ .replace(/__GLOBSTAR__/g, '.*');
41
+
42
+ const re = new RegExp(`^${regexStr}$`);
43
+ // Also match against basename for simple patterns like "*.tf"
44
+ const base = basename(f);
45
+ return re.test(f) || (!/\//.test(p) && re.test(base));
46
+ }
47
+
48
+ /**
49
+ * Start watching files matching the glob pattern.
50
+ * On change, triggers an agent run with the configured prompt.
51
+ */
52
+ export async function watchCommand(options: WatchOptions): Promise<void> {
53
+ const { glob, run, debounce = 500, autoApprove = false, maxRuns = 0 } = options;
54
+
55
+ if (!glob) {
56
+ console.error('Usage: nimbus watch <glob> --run "prompt"');
57
+ process.exit(1);
58
+ }
59
+
60
+ if (!run) {
61
+ console.error('Error: --run "prompt" is required for watch command');
62
+ process.exit(1);
63
+ }
64
+
65
+ const cwd = process.cwd();
66
+ console.log(`Watching ${glob} in ${cwd}...`);
67
+ console.log(`On change, will run: "${run}"`);
68
+ console.log('Press Ctrl+C to stop.\n');
69
+
70
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
71
+ let runCount = 0;
72
+ let isRunning = false;
73
+
74
+ async function triggerRun(changedFile: string): Promise<void> {
75
+ if (isRunning) {
76
+ return; // Skip concurrent runs
77
+ }
78
+ if (maxRuns > 0 && runCount >= maxRuns) {
79
+ console.log(`\nMaximum runs (${maxRuns}) reached. Stopping watcher.`);
80
+ process.exit(0);
81
+ }
82
+
83
+ runCount++;
84
+ isRunning = true;
85
+ const prompt = `${run}\n\nChanged file: ${changedFile}`;
86
+
87
+ try {
88
+ console.log(`\n[${new Date().toLocaleTimeString()}] Change detected: ${changedFile}`);
89
+ console.log(`Running: nimbus run "${run}"\n`);
90
+
91
+ const { initApp } = await import('../app');
92
+ const { executeRun } = await import('../cli/run');
93
+ const { router } = await initApp();
94
+ await executeRun(router, {
95
+ prompt,
96
+ format: 'text',
97
+ autoApprove,
98
+ stdin: false,
99
+ stdinJson: false,
100
+ mode: 'build',
101
+ maxTurns: 10,
102
+ rawToolOutput: false,
103
+ schema: false,
104
+ dryRun: false,
105
+ exitOnError: false,
106
+ });
107
+ } catch (error) {
108
+ console.error(`Run failed: ${error instanceof Error ? error.message : String(error)}`);
109
+ } finally {
110
+ isRunning = false;
111
+ }
112
+ }
113
+
114
+ function onChange(filename: string | null): void {
115
+ if (!filename) return;
116
+
117
+ // Check if the file matches the glob pattern
118
+ if (!matchGlob(filename, glob)) return;
119
+
120
+ // Debounce rapid successive changes
121
+ if (debounceTimer) clearTimeout(debounceTimer);
122
+ debounceTimer = setTimeout(() => {
123
+ debounceTimer = null;
124
+ triggerRun(filename).catch(err => {
125
+ console.error('Watch trigger error:', err);
126
+ });
127
+ }, debounce);
128
+ }
129
+
130
+ let watcher: FSWatcher;
131
+ try {
132
+ watcher = fsWatch(cwd, { recursive: true }, (_eventType, filename) => {
133
+ onChange(filename);
134
+ });
135
+ } catch (error) {
136
+ console.error(`Failed to start watcher: ${error instanceof Error ? error.message : String(error)}`);
137
+ process.exit(1);
138
+ }
139
+
140
+ // Handle shutdown gracefully
141
+ process.on('SIGINT', () => {
142
+ console.log('\nWatcher stopped.');
143
+ watcher.close();
144
+ process.exit(0);
145
+ });
146
+ process.on('SIGTERM', () => {
147
+ watcher.close();
148
+ process.exit(0);
149
+ });
150
+
151
+ // Keep process alive
152
+ await new Promise<void>(() => { /* intentionally never resolves */ });
153
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  /** Whether the current runtime is Bun. */
9
- export const isBun = typeof globalThis.Bun !== 'undefined';
9
+ export const isBun = typeof (globalThis as any).Bun !== 'undefined';
10
10
 
11
11
  /** Whether the current runtime is Node.js (without Bun). */
12
12
  export const isNode = !isBun && typeof process !== 'undefined' && !!process.versions?.node;
@@ -79,11 +79,81 @@ if (isBun) {
79
79
  }
80
80
  _Impl = BetterSqlite3;
81
81
  } catch {
82
- throw new Error(
83
- 'Nimbus requires either the Bun runtime (bun:sqlite) or the better-sqlite3 package.\n' +
84
- 'Install better-sqlite3: npm install better-sqlite3\n' +
85
- 'Or install Bun: curl -fsSL https://bun.sh/install | bash'
86
- );
82
+ // G1: Path 3 — sql.js fallback (pure WASM, no native build required)
83
+ try {
84
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
85
+ const initSqlJs = require('sql.js');
86
+ process.stderr.write(
87
+ '[nimbus] WARNING: Running in in-memory SQLite mode (no persistence).\n' +
88
+ '[nimbus] Install better-sqlite3 for full functionality: npm install better-sqlite3\n'
89
+ );
90
+
91
+ let sqlJsDb: { run(sql: string, params?: unknown[]): void; prepare(sql: string): unknown; close(): void } | null = null;
92
+
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ const getSqlJsDb = (): any => {
95
+ if (!sqlJsDb) throw new Error('sql.js database not yet initialised — call new Database() first');
96
+ return sqlJsDb;
97
+ };
98
+
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ class SqlJsDatabase {
101
+ constructor(_path: string) {
102
+ // Synchronously initialise — sql.js init is async but we use a trick:
103
+ // initSqlJs() returns a promise; we store a sentinel and resolve lazily.
104
+ // For simplicity, initialise synchronously with the default WASM bundle.
105
+ try {
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ const SQL = (initSqlJs as any)({ locateFile: () => '' });
108
+ if (SQL && SQL.then) {
109
+ // async — best-effort; DB will be ready after first await
110
+ SQL.then((s: { Database: new () => typeof sqlJsDb }) => { sqlJsDb = new s.Database(); });
111
+ } else {
112
+ sqlJsDb = new (SQL as { Database: new () => typeof sqlJsDb }).Database();
113
+ }
114
+ } catch {
115
+ // fallback: leave sqlJsDb null, operations will throw with clear message
116
+ }
117
+ }
118
+ exec(sql: string): void { getSqlJsDb().run(sql); }
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ prepare(sql: string): any {
121
+ const db = getSqlJsDb();
122
+ const stmt = db.prepare(sql);
123
+ return {
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ run: (...params: unknown[]) => { stmt.run(params as any[]); return {}; },
126
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
+ get: (...params: unknown[]) => { stmt.bind(params as any[]); return stmt.step() ? stmt.getAsObject() : undefined; },
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
+ all: (...params: unknown[]) => { const rows: unknown[] = []; stmt.bind(params as any[]); while (stmt.step()) rows.push(stmt.getAsObject()); return rows; },
130
+ finalize: () => stmt.free(),
131
+ };
132
+ }
133
+ close(): void { getSqlJsDb().close(); sqlJsDb = null; }
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
+ transaction<T>(fn: (...args: any[]) => T): (...args: any[]) => T {
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ return (...args: any[]) => {
138
+ getSqlJsDb().run('BEGIN');
139
+ try { const r = fn(...args); getSqlJsDb().run('COMMIT'); return r; }
140
+ catch (e) { try { getSqlJsDb().run('ROLLBACK'); } catch { /* ignore */ } throw e; }
141
+ };
142
+ }
143
+ run(sql: string, params?: unknown[]): unknown {
144
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ return params ? getSqlJsDb().run(sql, params as any[]) : getSqlJsDb().run(sql);
146
+ }
147
+ query(sql: string) { return this.prepare(sql); }
148
+ }
149
+ _Impl = SqlJsDatabase;
150
+ } catch {
151
+ throw new Error(
152
+ 'Nimbus requires either the Bun runtime (bun:sqlite) or the better-sqlite3 package.\n' +
153
+ 'Install better-sqlite3: npm install better-sqlite3\n' +
154
+ 'Or install Bun: curl -fsSL https://bun.sh/install | bash'
155
+ );
156
+ }
87
157
  }
88
158
  }
89
159
 
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Mode persistence per working directory.
3
+ *
4
+ * Saves and loads the agent mode (plan/build/deploy) for each project directory
5
+ * using a JSON file at ~/.nimbus/mode-config.json.
6
+ */
7
+
8
+ import type { AgentMode } from '../agent/system-prompt';
9
+
10
+ const MODE_CACHE = new Map<string, AgentMode>();
11
+
12
+ function getConfigPath(): string {
13
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';
14
+ return `${home}/.nimbus/mode-config.json`;
15
+ }
16
+
17
+ function loadConfig(): Record<string, AgentMode> {
18
+ try {
19
+ const { readFileSync } = require('node:fs') as typeof import('node:fs');
20
+ const raw = readFileSync(getConfigPath(), 'utf-8');
21
+ return JSON.parse(raw) as Record<string, AgentMode>;
22
+ } catch {
23
+ return {};
24
+ }
25
+ }
26
+
27
+ function saveConfig(data: Record<string, AgentMode>): void {
28
+ try {
29
+ const { writeFileSync, mkdirSync } = require('node:fs') as typeof import('node:fs');
30
+ const { dirname } = require('node:path') as typeof import('node:path');
31
+ const configPath = getConfigPath();
32
+ mkdirSync(dirname(configPath), { recursive: true });
33
+ writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');
34
+ } catch {
35
+ // Non-critical — silently ignore
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Load the saved agent mode for the given working directory.
41
+ * Returns null if no mode has been saved for this directory.
42
+ */
43
+ export function loadModeForCwd(cwd: string): AgentMode | null {
44
+ if (MODE_CACHE.has(cwd)) return MODE_CACHE.get(cwd) ?? null;
45
+ const config = loadConfig();
46
+ const mode = config[cwd] as AgentMode | undefined;
47
+ if (mode && ['plan', 'build', 'deploy'].includes(mode)) {
48
+ MODE_CACHE.set(cwd, mode);
49
+ return mode;
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Save the agent mode for the given working directory.
56
+ */
57
+ export function saveModeForCwd(cwd: string, mode: AgentMode): void {
58
+ MODE_CACHE.set(cwd, mode);
59
+ const config = loadConfig();
60
+ config[cwd] = mode;
61
+ saveConfig(config);
62
+ }