@build-astron-co/nimbus 0.2.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 (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,436 @@
1
+ /**
2
+ * System Prompt Builder for the Nimbus Agentic Loop
3
+ *
4
+ * Generates the complete system prompt that is injected as the first message
5
+ * in every LLM conversation. The prompt tells the model who it is, what mode
6
+ * it is operating in, which tools are available, and how to behave.
7
+ *
8
+ * The prompt is assembled from several composable sections:
9
+ *
10
+ * 1. **Base identity** -- who Nimbus is and its core behavioral rules.
11
+ * 2. **Mode instructions** -- what the current {@link AgentMode} allows.
12
+ * 3. **Tool-use guidelines** -- general best practices for tool invocation.
13
+ * 4. **Available tools** -- a summarized list built from {@link ToolDefinition}s.
14
+ * 5. **NIMBUS.md** -- optional per-project or per-user custom instructions.
15
+ * 6. **Subagent instructions** -- constraints when running as a spawned subagent.
16
+ * 7. **Environment context** -- working directory, platform, date, git status.
17
+ *
18
+ * @module agent/system-prompt
19
+ */
20
+
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import { homedir } from 'node:os';
24
+ import { execSync } from 'node:child_process';
25
+ import type { ToolDefinition } from '../tools/schemas/types';
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Agent Mode
29
+ // ---------------------------------------------------------------------------
30
+
31
+ /**
32
+ * Agent modes that control tool availability and behavior.
33
+ *
34
+ * | Mode | Description |
35
+ * | -------- | ---------------------------------------------------------- |
36
+ * | `plan` | Read-only exploration, analysis, and proposal generation. |
37
+ * | `build` | File editing, code generation, and non-destructive DevOps. |
38
+ * | `deploy` | Full infrastructure mutation with approval gates. |
39
+ */
40
+ export type AgentMode = 'plan' | 'build' | 'deploy';
41
+
42
+ /**
43
+ * Ordered list of all agent modes from least permissive to most permissive.
44
+ * Useful for comparison and escalation logic.
45
+ */
46
+ export const AGENT_MODES: readonly AgentMode[] = ['plan', 'build', 'deploy'] as const;
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // System Prompt Options
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /**
53
+ * Options for building the system prompt via {@link buildSystemPrompt}.
54
+ */
55
+ export interface SystemPromptOptions {
56
+ /** Current agent mode -- controls which actions are permitted. */
57
+ readonly mode: AgentMode;
58
+
59
+ /** Available tools (already filtered by mode before being passed in). */
60
+ readonly tools: ToolDefinition[];
61
+
62
+ /**
63
+ * Custom instructions loaded from a `NIMBUS.md` file. When provided this
64
+ * value is used directly; when omitted the builder will attempt to
65
+ * discover and load the file automatically via {@link loadNimbusMd}.
66
+ */
67
+ readonly nimbusInstructions?: string;
68
+
69
+ /** Current working directory. Defaults to `process.cwd()`. */
70
+ readonly cwd?: string;
71
+
72
+ /**
73
+ * Active subagent name. When set, the prompt includes additional
74
+ * constraints that prevent recursive subagent spawning and encourage
75
+ * focused, scoped execution.
76
+ */
77
+ readonly activeSubagent?: string;
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Public API
82
+ // ---------------------------------------------------------------------------
83
+
84
+ /**
85
+ * Build the complete system prompt for the agentic loop.
86
+ *
87
+ * The returned string is intended to be used as the `system` message (or
88
+ * first `user`/`system` message, depending on the LLM provider) in a
89
+ * conversation with the model.
90
+ *
91
+ * @param options - Configuration that controls prompt assembly.
92
+ * @returns The fully assembled system prompt string.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * import { buildSystemPrompt } from './system-prompt';
97
+ *
98
+ * const prompt = buildSystemPrompt({
99
+ * mode: 'build',
100
+ * tools: registry.getAll(),
101
+ * cwd: '/home/user/project',
102
+ * });
103
+ * ```
104
+ */
105
+ export function buildSystemPrompt(options: SystemPromptOptions): string {
106
+ const parts: string[] = [];
107
+
108
+ // 1. Base identity
109
+ parts.push(BASE_PROMPT);
110
+
111
+ // 2. Mode-specific instructions
112
+ parts.push(getModeInstructions(options.mode));
113
+
114
+ // 3. Tool-use guidelines
115
+ parts.push(TOOL_USE_GUIDELINES);
116
+
117
+ // 4. Available tools summary
118
+ const toolsSummary = buildToolsSummary(options.tools);
119
+ if (toolsSummary) {
120
+ parts.push(toolsSummary);
121
+ }
122
+
123
+ // 5. NIMBUS.md content (if exists)
124
+ const nimbusContent = options.nimbusInstructions ?? loadNimbusMd(options.cwd);
125
+ if (nimbusContent) {
126
+ parts.push(`# Project Instructions (NIMBUS.md)\n\n${nimbusContent}`);
127
+ }
128
+
129
+ // 6. Subagent instructions (if applicable)
130
+ if (options.activeSubagent) {
131
+ parts.push(getSubagentInstructions(options.activeSubagent));
132
+ }
133
+
134
+ // 7. Environment context
135
+ parts.push(buildEnvironmentContext(options.cwd));
136
+
137
+ return parts.join('\n\n---\n\n');
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Prompt Fragments
142
+ // ---------------------------------------------------------------------------
143
+
144
+ /**
145
+ * Core identity and behavioral rules that apply regardless of mode.
146
+ * @internal
147
+ */
148
+ const BASE_PROMPT = `You are Nimbus, an AI-powered DevOps engineering agent. You help developers build, deploy, and manage cloud infrastructure through natural conversation.
149
+
150
+ You have access to tools that let you read files, edit code, run shell commands, execute Terraform/Kubernetes/Helm operations, discover cloud resources, estimate costs, and detect infrastructure drift.
151
+
152
+ You work autonomously — reading files, making edits, running commands, and iterating until the task is complete. You use tools proactively rather than asking the user to run commands themselves.
153
+
154
+ Key behaviors:
155
+ - Read files before editing them to understand the current state
156
+ - Make precise, targeted edits rather than rewriting entire files
157
+ - Run tests after making changes to verify correctness
158
+ - Show deployment previews before executing destructive infrastructure changes
159
+ - Explain what you're doing and why at each step
160
+ - If a tool call fails, analyze the error and try a different approach`;
161
+
162
+ /**
163
+ * General best-practice guidelines for tool invocation that are included in
164
+ * every prompt regardless of mode.
165
+ * @internal
166
+ */
167
+ const TOOL_USE_GUIDELINES = `# Tool-Use Guidelines
168
+
169
+ - Use the most specific tool available. Prefer \`read_file\` over \`bash cat\`, \`glob\` over \`bash find\`, \`grep\` over \`bash grep\`.
170
+ - For file edits, use \`edit_file\` for single replacements and \`multi_edit\` for multiple replacements in the same file.
171
+ - Use \`write_file\` only for creating new files or complete rewrites.
172
+ - When running bash commands, prefer specific commands over broad ones. Avoid \`rm -rf\` or other destructive patterns.
173
+ - For infrastructure operations, always run validation (terraform validate, kubectl --dry-run) before apply.
174
+ - Use \`deploy_preview\` before any destructive infrastructure change.
175
+ - Use the \`task\` tool to spawn subagents for parallel or specialized work.
176
+ - When using \`terraform\`, always run \`terraform init\` before \`plan\` or \`apply\` if not already initialized.
177
+ - For Kubernetes operations, be namespace-aware. Default to the current namespace context.`;
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Mode Instructions
181
+ // ---------------------------------------------------------------------------
182
+
183
+ /**
184
+ * Return the mode-specific instruction block for the given {@link AgentMode}.
185
+ *
186
+ * Each mode defines an explicit allow-list and deny-list so the model
187
+ * understands the boundaries of what it can do.
188
+ *
189
+ * @param mode - The active agent mode.
190
+ * @returns A markdown section describing the mode's rules.
191
+ * @internal
192
+ */
193
+ function getModeInstructions(mode: AgentMode): string {
194
+ switch (mode) {
195
+ case 'plan':
196
+ return `# Mode: PLAN
197
+
198
+ You are in Plan mode. Your role is to analyze, explore, and propose — NOT to modify.
199
+
200
+ Allowed actions:
201
+ - Read files, search code, list directories
202
+ - Analyze infrastructure configurations
203
+ - Estimate costs and detect drift
204
+ - Propose changes and create task lists
205
+ - Fetch web content for research
206
+
207
+ NOT allowed:
208
+ - Editing or creating files
209
+ - Running destructive bash commands
210
+ - Executing terraform apply, kubectl apply, helm install
211
+ - Making any state-changing operations
212
+
213
+ Focus on understanding the codebase and infrastructure, then propose a clear action plan.`;
214
+
215
+ case 'build':
216
+ return `# Mode: BUILD
217
+
218
+ You are in Build mode. You can read, edit, create files, and run non-destructive commands.
219
+
220
+ Allowed actions:
221
+ - All Plan mode actions
222
+ - Edit and create files
223
+ - Run tests and linters
224
+ - Generate Terraform/K8s/Helm configurations
225
+ - Run terraform validate, terraform fmt, terraform plan
226
+ - Run kubectl get, kubectl describe, kubectl diff
227
+
228
+ NOT allowed:
229
+ - terraform apply, terraform destroy
230
+ - kubectl apply, kubectl delete
231
+ - helm install, helm upgrade, helm uninstall
232
+ - Any infrastructure-mutating operations
233
+
234
+ Focus on building and testing changes before deploying.`;
235
+
236
+ case 'deploy':
237
+ return `# Mode: DEPLOY
238
+
239
+ You are in Deploy mode. You have full access to all tools including infrastructure mutations.
240
+
241
+ Allowed actions:
242
+ - All Build mode actions
243
+ - terraform apply, terraform destroy
244
+ - kubectl apply, kubectl delete
245
+ - helm install, helm upgrade, helm uninstall
246
+ - Cloud resource mutations
247
+
248
+ REQUIRED:
249
+ - Always run deploy_preview before any destructive operation
250
+ - Show the user what will change before executing
251
+ - Wait for explicit approval on destructive changes
252
+
253
+ Focus on safe, verified deployments with minimal blast radius.`;
254
+ }
255
+ }
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // Tools Summary
259
+ // ---------------------------------------------------------------------------
260
+
261
+ /**
262
+ * Build a markdown summary of the available tools from their definitions.
263
+ *
264
+ * Returns an empty string when the tool list is empty so callers can
265
+ * conditionally include the section.
266
+ *
267
+ * @param tools - The tool definitions to summarize.
268
+ * @returns A markdown section listing each tool, or `''` if none.
269
+ * @internal
270
+ */
271
+ function buildToolsSummary(tools: ToolDefinition[]): string {
272
+ if (tools.length === 0) {
273
+ return '';
274
+ }
275
+
276
+ const lines = tools.map(t => `- **${t.name}**: ${t.description}`);
277
+ return `# Available Tools (${tools.length})\n\n${lines.join('\n')}`;
278
+ }
279
+
280
+ // ---------------------------------------------------------------------------
281
+ // NIMBUS.md Loader
282
+ // ---------------------------------------------------------------------------
283
+
284
+ /**
285
+ * Search paths for `NIMBUS.md` files, in priority order.
286
+ * @internal
287
+ */
288
+ function getNimbusMdSearchPaths(cwd?: string): string[] {
289
+ return [
290
+ cwd ? path.join(cwd, 'NIMBUS.md') : null,
291
+ cwd ? path.join(cwd, '.nimbus', 'NIMBUS.md') : null,
292
+ path.join(homedir(), '.nimbus', 'NIMBUS.md'),
293
+ ].filter(Boolean) as string[];
294
+ }
295
+
296
+ /**
297
+ * Load `NIMBUS.md` from the project directory or the user's home directory.
298
+ *
299
+ * The search order is:
300
+ * 1. `<cwd>/NIMBUS.md`
301
+ * 2. `<cwd>/.nimbus/NIMBUS.md`
302
+ * 3. `~/.nimbus/NIMBUS.md`
303
+ *
304
+ * Returns `null` if no file is found or if all candidates are inaccessible.
305
+ *
306
+ * @param cwd - The working directory to search from. Defaults to `undefined`
307
+ * (skips cwd-relative paths).
308
+ * @returns The file contents as a string, or `null`.
309
+ */
310
+ export function loadNimbusMd(cwd?: string): string | null {
311
+ const searchPaths = getNimbusMdSearchPaths(cwd);
312
+
313
+ for (const p of searchPaths) {
314
+ try {
315
+ if (fs.existsSync(p)) {
316
+ return fs.readFileSync(p, 'utf-8');
317
+ }
318
+ } catch {
319
+ // Skip inaccessible files silently
320
+ }
321
+ }
322
+
323
+ return null;
324
+ }
325
+
326
+ // ---------------------------------------------------------------------------
327
+ // Subagent Instructions
328
+ // ---------------------------------------------------------------------------
329
+
330
+ /**
331
+ * Return the instruction block appended when the agent is running as a
332
+ * spawned subagent.
333
+ *
334
+ * Subagents are constrained to prevent recursive spawning and to keep
335
+ * execution focused on a single task.
336
+ *
337
+ * @param agentName - The name of the active subagent.
338
+ * @returns A markdown section with subagent-specific rules.
339
+ * @internal
340
+ */
341
+ function getSubagentInstructions(agentName: string): string {
342
+ return `# Subagent Mode: ${agentName}
343
+
344
+ You are running as a subagent spawned by the primary Nimbus agent. Your task is specific and focused.
345
+ - Complete the requested task and return a clear, concise result
346
+ - Do NOT spawn further subagents (no nesting)
347
+ - Stay focused on the assigned task — do not explore tangentially
348
+ - Return your findings as structured, actionable information`;
349
+ }
350
+
351
+ // ---------------------------------------------------------------------------
352
+ // Environment Context
353
+ // ---------------------------------------------------------------------------
354
+
355
+ /**
356
+ * Build a short environment-context block that gives the model awareness of
357
+ * the runtime environment (working directory, platform, date, git status).
358
+ *
359
+ * @param cwd - The working directory. Defaults to `process.cwd()`.
360
+ * @returns A markdown section with environment metadata.
361
+ * @internal
362
+ */
363
+ function buildEnvironmentContext(cwd?: string): string {
364
+ const effectiveCwd = cwd ?? process.cwd();
365
+
366
+ const parts = [
367
+ '# Environment',
368
+ `- Working directory: ${effectiveCwd}`,
369
+ `- Platform: ${process.platform}`,
370
+ `- Date: ${new Date().toISOString().split('T')[0]}`,
371
+ ];
372
+
373
+ // Check for git repo and gather context
374
+ const gitDir = path.join(effectiveCwd, '.git');
375
+ if (fs.existsSync(gitDir)) {
376
+ parts.push('- Git repository: yes');
377
+
378
+ const execOpts = {
379
+ cwd: effectiveCwd,
380
+ timeout: 1000,
381
+ encoding: 'utf-8' as const,
382
+ stdio: ['pipe', 'pipe', 'pipe'] as ['pipe', 'pipe', 'pipe'],
383
+ };
384
+
385
+ try {
386
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', execOpts).trim();
387
+ parts.push(`- Git branch: ${branch}`);
388
+ } catch {
389
+ /* ignore */
390
+ }
391
+
392
+ try {
393
+ const log = execSync('git log --oneline -5 2>/dev/null', execOpts).trim();
394
+ if (log) {
395
+ parts.push(
396
+ `- Recent commits:\n${log
397
+ .split('\n')
398
+ .map((l: string) => ` ${l}`)
399
+ .join('\n')}`
400
+ );
401
+ }
402
+ } catch {
403
+ /* ignore */
404
+ }
405
+
406
+ try {
407
+ const staged = execSync('git diff --cached --stat 2>/dev/null', execOpts).trim();
408
+ if (staged) {
409
+ parts.push(
410
+ `- Staged changes:\n${staged
411
+ .split('\n')
412
+ .map((l: string) => ` ${l}`)
413
+ .join('\n')}`
414
+ );
415
+ }
416
+ } catch {
417
+ /* ignore */
418
+ }
419
+
420
+ try {
421
+ const unstaged = execSync('git diff --stat 2>/dev/null', execOpts).trim();
422
+ if (unstaged) {
423
+ parts.push(
424
+ `- Unstaged changes:\n${unstaged
425
+ .split('\n')
426
+ .map((l: string) => ` ${l}`)
427
+ .join('\n')}`
428
+ );
429
+ }
430
+ } catch {
431
+ /* ignore */
432
+ }
433
+ }
434
+
435
+ return parts.join('\n');
436
+ }
package/src/app.ts ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * App Lifecycle
3
+ *
4
+ * Handles initialization and shutdown of the embedded Nimbus application.
5
+ * Lazily initializes the SQLite database and the LLM router.
6
+ */
7
+
8
+ import { mkdirSync, existsSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { homedir } from 'node:os';
11
+ import type { Database } from './compat/sqlite';
12
+ import type { LLMRouter } from './llm/router';
13
+
14
+ /** The resolved path to ~/.nimbus */
15
+ const NIMBUS_DIR = join(homedir(), '.nimbus');
16
+
17
+ /** Holds the initialized app context, or null if not yet initialized. */
18
+ let appContext: AppContext | null = null;
19
+
20
+ /** The shape returned by initApp(). */
21
+ export interface AppContext {
22
+ /** The bun:sqlite Database instance for local state. */
23
+ readonly db: Database;
24
+ /** The LLM router for provider-agnostic completions. */
25
+ readonly router: LLMRouter;
26
+ /** The nimbus home directory path. */
27
+ readonly nimbusDir: string;
28
+ }
29
+
30
+ /**
31
+ * Initialize the Nimbus application.
32
+ *
33
+ * - Ensures the ~/.nimbus directory exists.
34
+ * - Opens (or creates) the local SQLite database via the state module.
35
+ * - Loads config and creates the LLM router instance.
36
+ *
37
+ * This function is lazy: calling it multiple times returns the same context.
38
+ */
39
+ export async function initApp(): Promise<AppContext> {
40
+ if (appContext) {
41
+ return appContext;
42
+ }
43
+
44
+ // Ensure ~/.nimbus directory exists
45
+ if (!existsSync(NIMBUS_DIR)) {
46
+ mkdirSync(NIMBUS_DIR, { recursive: true });
47
+ }
48
+
49
+ // Initialize the SQLite database
50
+ const { getDb } = await import('./state/db');
51
+ const db = getDb();
52
+
53
+ // Load LLM config and create router
54
+ const { loadLLMConfig } = await import('./llm/config-loader');
55
+ const { LLMRouter: LLMRouterClass } = await import('./llm/router');
56
+ const llmConfig = loadLLMConfig();
57
+ const router = new LLMRouterClass(llmConfig);
58
+
59
+ // Register all built-in tools into the global registry
60
+ const { defaultToolRegistry } = await import('./tools/schemas/types');
61
+ if (defaultToolRegistry.size === 0) {
62
+ const { standardTools } = await import('./tools/schemas/standard');
63
+ const { devopsTools } = await import('./tools/schemas/devops');
64
+ for (const tool of [...standardTools, ...devopsTools]) {
65
+ try {
66
+ defaultToolRegistry.register(tool);
67
+ } catch {
68
+ /* skip duplicates */
69
+ }
70
+ }
71
+ }
72
+
73
+ // Connect MCP servers and register their tools (non-critical)
74
+ try {
75
+ const { MCPManager } = await import('./mcp/manager');
76
+ const mcpManager = new MCPManager();
77
+ await mcpManager.loadConfig(process.cwd());
78
+ if (mcpManager.serverCount > 0) {
79
+ await mcpManager.connectAll();
80
+ mcpManager.registerTools(defaultToolRegistry);
81
+ }
82
+ } catch (mcpErr) {
83
+ // MCP is non-critical — tools work fine without it, but warn the user
84
+ const msg = mcpErr instanceof Error ? mcpErr.message : String(mcpErr);
85
+ if (process.stderr.isTTY) {
86
+ process.stderr.write(
87
+ `\x1b[33m Warning: MCP server loading failed: ${msg}. External tools from .mcp.json will not be available.\x1b[0m\n`
88
+ );
89
+ }
90
+ }
91
+
92
+ appContext = { db, router, nimbusDir: NIMBUS_DIR };
93
+ return appContext;
94
+ }
95
+
96
+ /**
97
+ * Get the current app context without initializing.
98
+ * Returns null if initApp() has not been called yet.
99
+ */
100
+ export function getAppContext(): AppContext | null {
101
+ return appContext;
102
+ }
103
+
104
+ /**
105
+ * Gracefully shut down the Nimbus application.
106
+ *
107
+ * Closes the database connection and clears the cached context so that
108
+ * a subsequent call to initApp() will re-initialize from scratch.
109
+ */
110
+ export async function shutdownApp(): Promise<void> {
111
+ if (!appContext) {
112
+ return;
113
+ }
114
+
115
+ try {
116
+ appContext.db.close();
117
+ } catch {
118
+ // Database may already be closed; ignore.
119
+ }
120
+
121
+ appContext = null;
122
+ }