@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.
- package/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
package/src/cli/serve.ts
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nimbus serve -- Headless HTTP API Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes the Nimbus agent loop as a REST + SSE API designed for
|
|
5
|
+
* consumption by the Web UI, IDE extensions, and third-party integrations.
|
|
6
|
+
*
|
|
7
|
+
* Endpoints:
|
|
8
|
+
* POST /api/chat -- Send a message, receive SSE streaming response
|
|
9
|
+
* POST /api/run -- Non-interactive single prompt (JSON response)
|
|
10
|
+
* GET /api/sessions -- List all sessions
|
|
11
|
+
* GET /api/session/:id -- Session details + conversation messages
|
|
12
|
+
* POST /api/session/:id -- Continue an existing session (SSE streaming)
|
|
13
|
+
* GET /api/health -- Health check
|
|
14
|
+
* GET /api/openapi.json -- OpenAPI 3.1 specification
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* nimbus serve # localhost:4200
|
|
18
|
+
* nimbus serve --port 8080 # custom port
|
|
19
|
+
* nimbus serve --host 0.0.0.0 # bind to all interfaces
|
|
20
|
+
* nimbus serve --auth admin:secret # enable HTTP Basic Auth
|
|
21
|
+
*
|
|
22
|
+
* @module cli/serve
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { Elysia } from 'elysia';
|
|
26
|
+
import { cors } from '@elysiajs/cors';
|
|
27
|
+
import { initApp } from '../app';
|
|
28
|
+
import { runAgentLoop, type ToolCallInfo } from '../agent/loop';
|
|
29
|
+
import { defaultToolRegistry, type ToolResult } from '../tools/schemas/types';
|
|
30
|
+
import { standardTools } from '../tools/schemas/standard';
|
|
31
|
+
import { devopsTools } from '../tools/schemas/devops';
|
|
32
|
+
import { SessionManager } from '../sessions/manager';
|
|
33
|
+
import { saveConversation, getConversation } from '../state/conversations';
|
|
34
|
+
import { shareSession, getSharedSession, listShares } from '../sharing/sync';
|
|
35
|
+
import { ContextManager } from '../agent/context-manager';
|
|
36
|
+
import { getOpenAPISpec } from './openapi-spec';
|
|
37
|
+
import { createAuthMiddleware } from './serve-auth';
|
|
38
|
+
import type { LLMMessage } from '../llm/types';
|
|
39
|
+
import type { AgentMode } from '../agent/system-prompt';
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Public Types
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** Options for the `nimbus serve` command. */
|
|
46
|
+
export interface ServeOptions {
|
|
47
|
+
/** Port to listen on (default: 4200). */
|
|
48
|
+
port?: number;
|
|
49
|
+
/** Hostname to bind to (default: 'localhost'). */
|
|
50
|
+
host?: string;
|
|
51
|
+
/** HTTP Basic Auth credentials in "user:pass" format. */
|
|
52
|
+
auth?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Helpers
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Ensure the default tool registry is populated.
|
|
61
|
+
* Idempotent -- skips tools that are already registered.
|
|
62
|
+
*/
|
|
63
|
+
function ensureToolsRegistered(): void {
|
|
64
|
+
if (defaultToolRegistry.size > 0) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const tool of [...standardTools, ...devopsTools]) {
|
|
68
|
+
try {
|
|
69
|
+
defaultToolRegistry.register(tool);
|
|
70
|
+
} catch {
|
|
71
|
+
// Already registered -- skip.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate and narrow a mode string to the AgentMode union.
|
|
78
|
+
*/
|
|
79
|
+
function parseMode(mode: string | undefined): AgentMode {
|
|
80
|
+
if (mode === 'plan' || mode === 'build' || mode === 'deploy') {
|
|
81
|
+
return mode;
|
|
82
|
+
}
|
|
83
|
+
return 'build';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Create an SSE-formatted ReadableStream that runs the agent loop and
|
|
88
|
+
* emits events for text, tool calls, completion, and errors.
|
|
89
|
+
*
|
|
90
|
+
* SSE event types:
|
|
91
|
+
* session -- { id, mode }
|
|
92
|
+
* text -- { content }
|
|
93
|
+
* tool_start -- { id, name, input? }
|
|
94
|
+
* tool_end -- { id, name, output?, isError }
|
|
95
|
+
* done -- { turns, usage, cost }
|
|
96
|
+
* error -- { message }
|
|
97
|
+
*/
|
|
98
|
+
function createAgentSSEStream(
|
|
99
|
+
userMessage: string,
|
|
100
|
+
history: LLMMessage[],
|
|
101
|
+
sessionId: string,
|
|
102
|
+
mode: AgentMode,
|
|
103
|
+
model: string | undefined,
|
|
104
|
+
router: any,
|
|
105
|
+
contextManager: ContextManager,
|
|
106
|
+
sessionManager: SessionManager
|
|
107
|
+
): ReadableStream<Uint8Array> {
|
|
108
|
+
return new ReadableStream<Uint8Array>({
|
|
109
|
+
async start(controller) {
|
|
110
|
+
const encoder = new TextEncoder();
|
|
111
|
+
|
|
112
|
+
const send = (event: string, data: unknown): void => {
|
|
113
|
+
const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
114
|
+
controller.enqueue(encoder.encode(payload));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
send('session', { id: sessionId, mode });
|
|
119
|
+
|
|
120
|
+
const result = await runAgentLoop(userMessage, history, {
|
|
121
|
+
router,
|
|
122
|
+
toolRegistry: defaultToolRegistry,
|
|
123
|
+
mode,
|
|
124
|
+
model,
|
|
125
|
+
sessionId,
|
|
126
|
+
onText: (text: string) => {
|
|
127
|
+
send('text', { content: text });
|
|
128
|
+
},
|
|
129
|
+
onToolCallStart: (toolCall: ToolCallInfo) => {
|
|
130
|
+
send('tool_start', {
|
|
131
|
+
id: toolCall.id,
|
|
132
|
+
name: toolCall.name,
|
|
133
|
+
input: toolCall.input,
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
onToolCallEnd: (toolCall: ToolCallInfo, toolResult: ToolResult) => {
|
|
137
|
+
send('tool_end', {
|
|
138
|
+
id: toolCall.id,
|
|
139
|
+
name: toolCall.name,
|
|
140
|
+
output:
|
|
141
|
+
typeof toolResult.output === 'string'
|
|
142
|
+
? toolResult.output.slice(0, 5000)
|
|
143
|
+
: toolResult.output,
|
|
144
|
+
isError: toolResult.isError,
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Persist conversation
|
|
150
|
+
saveConversation(sessionId, userMessage.slice(0, 100), result.messages, model);
|
|
151
|
+
|
|
152
|
+
// Update session stats
|
|
153
|
+
sessionManager.updateSession(sessionId, {
|
|
154
|
+
tokenCount: result.usage.totalTokens,
|
|
155
|
+
costUSD: result.totalCost,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
send('done', {
|
|
159
|
+
turns: result.turns,
|
|
160
|
+
usage: result.usage,
|
|
161
|
+
cost: result.totalCost,
|
|
162
|
+
});
|
|
163
|
+
} catch (error: unknown) {
|
|
164
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
165
|
+
send('error', { message: msg });
|
|
166
|
+
} finally {
|
|
167
|
+
controller.close();
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Wrap a ReadableStream as an SSE Response with proper headers.
|
|
175
|
+
*/
|
|
176
|
+
function sseResponse(stream: ReadableStream<Uint8Array>): Response {
|
|
177
|
+
return new Response(stream, {
|
|
178
|
+
headers: {
|
|
179
|
+
'Content-Type': 'text/event-stream',
|
|
180
|
+
'Cache-Control': 'no-cache',
|
|
181
|
+
Connection: 'keep-alive',
|
|
182
|
+
'Access-Control-Allow-Origin': '*',
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Main Command
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Start the Nimbus headless API server.
|
|
193
|
+
*
|
|
194
|
+
* Initializes the app context (DB + LLM router), registers tools, sets up
|
|
195
|
+
* all API routes via Elysia, and starts listening.
|
|
196
|
+
*/
|
|
197
|
+
export async function serveCommand(options: ServeOptions): Promise<void> {
|
|
198
|
+
const port = options.port ?? 4200;
|
|
199
|
+
const host = options.host ?? 'localhost';
|
|
200
|
+
|
|
201
|
+
// ------------------------------------------------------------------
|
|
202
|
+
// Initialize core systems
|
|
203
|
+
// ------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
const { router } = await initApp();
|
|
206
|
+
const sessionManager = SessionManager.getInstance();
|
|
207
|
+
const contextManager = new ContextManager();
|
|
208
|
+
|
|
209
|
+
ensureToolsRegistered();
|
|
210
|
+
|
|
211
|
+
// ------------------------------------------------------------------
|
|
212
|
+
// Build Elysia app
|
|
213
|
+
// ------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
const app = new Elysia().use(
|
|
216
|
+
cors({
|
|
217
|
+
origin: true,
|
|
218
|
+
methods: ['GET', 'POST', 'OPTIONS'],
|
|
219
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Optional HTTP Basic Auth
|
|
224
|
+
if (options.auth) {
|
|
225
|
+
const colonIdx = options.auth.indexOf(':');
|
|
226
|
+
if (colonIdx > 0) {
|
|
227
|
+
const user = options.auth.slice(0, colonIdx);
|
|
228
|
+
const pass = options.auth.slice(colonIdx + 1);
|
|
229
|
+
app.onBeforeHandle(createAuthMiddleware({ username: user, password: pass }));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ------------------------------------------------------------------
|
|
234
|
+
// GET /api/health
|
|
235
|
+
// ------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
app.get('/api/health', () => ({
|
|
238
|
+
status: 'ok' as const,
|
|
239
|
+
version: '0.2.0',
|
|
240
|
+
uptime: process.uptime(),
|
|
241
|
+
db: true,
|
|
242
|
+
llm: true,
|
|
243
|
+
}));
|
|
244
|
+
|
|
245
|
+
// ------------------------------------------------------------------
|
|
246
|
+
// GET /api/openapi.json
|
|
247
|
+
// ------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
app.get('/api/openapi.json', () => getOpenAPISpec());
|
|
250
|
+
|
|
251
|
+
// ------------------------------------------------------------------
|
|
252
|
+
// GET /api/sessions
|
|
253
|
+
// ------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
app.get('/api/sessions', () => ({
|
|
256
|
+
sessions: sessionManager.list(),
|
|
257
|
+
}));
|
|
258
|
+
|
|
259
|
+
// ------------------------------------------------------------------
|
|
260
|
+
// GET /api/session/:id
|
|
261
|
+
// ------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
app.get('/api/session/:id', ({ params }: { params: { id: string } }) => {
|
|
264
|
+
const session = sessionManager.get(params.id);
|
|
265
|
+
if (!session) {
|
|
266
|
+
return new Response(JSON.stringify({ error: 'Session not found' }), {
|
|
267
|
+
status: 404,
|
|
268
|
+
headers: { 'Content-Type': 'application/json' },
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const conversation = getConversation(params.id);
|
|
273
|
+
return {
|
|
274
|
+
session,
|
|
275
|
+
messages: conversation?.messages ?? [],
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// ------------------------------------------------------------------
|
|
280
|
+
// POST /api/chat -- SSE streaming chat
|
|
281
|
+
// ------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
app.post('/api/chat', async ctx => {
|
|
284
|
+
const body = ctx.body as { message: string; sessionId?: string; model?: string; mode?: string };
|
|
285
|
+
const mode = parseMode(body.mode);
|
|
286
|
+
|
|
287
|
+
// Get or create session
|
|
288
|
+
let sessionId = body.sessionId;
|
|
289
|
+
let session = sessionId ? sessionManager.get(sessionId) : null;
|
|
290
|
+
|
|
291
|
+
if (!session) {
|
|
292
|
+
session = sessionManager.create({
|
|
293
|
+
name: `API Session ${new Date().toISOString().slice(0, 16)}`,
|
|
294
|
+
mode,
|
|
295
|
+
model: body.model,
|
|
296
|
+
});
|
|
297
|
+
sessionId = session.id;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Load existing conversation history
|
|
301
|
+
const existing = getConversation(sessionId!);
|
|
302
|
+
const history: LLMMessage[] = existing?.messages ?? [];
|
|
303
|
+
|
|
304
|
+
const stream = createAgentSSEStream(
|
|
305
|
+
body.message,
|
|
306
|
+
history,
|
|
307
|
+
sessionId!,
|
|
308
|
+
mode,
|
|
309
|
+
body.model,
|
|
310
|
+
router,
|
|
311
|
+
contextManager,
|
|
312
|
+
sessionManager
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return sseResponse(stream);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ------------------------------------------------------------------
|
|
319
|
+
// POST /api/run -- Non-interactive single prompt
|
|
320
|
+
// ------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
app.post('/api/run', async ctx => {
|
|
323
|
+
const body = ctx.body as { prompt: string; model?: string; mode?: string };
|
|
324
|
+
const mode = parseMode(body.mode);
|
|
325
|
+
|
|
326
|
+
const session = sessionManager.create({
|
|
327
|
+
name: `Run: ${body.prompt.slice(0, 50)}`,
|
|
328
|
+
mode,
|
|
329
|
+
model: body.model,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const result = await runAgentLoop(body.prompt, [], {
|
|
334
|
+
router,
|
|
335
|
+
toolRegistry: defaultToolRegistry,
|
|
336
|
+
mode,
|
|
337
|
+
model: body.model,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Persist conversation and mark session complete
|
|
341
|
+
saveConversation(session.id, body.prompt.slice(0, 100), result.messages, body.model);
|
|
342
|
+
sessionManager.complete(session.id);
|
|
343
|
+
|
|
344
|
+
// Extract final assistant message
|
|
345
|
+
const lastAssistant = [...result.messages].reverse().find(m => m.role === 'assistant');
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
sessionId: session.id,
|
|
349
|
+
response: lastAssistant?.content ?? '',
|
|
350
|
+
turns: result.turns,
|
|
351
|
+
usage: result.usage,
|
|
352
|
+
cost: result.totalCost,
|
|
353
|
+
};
|
|
354
|
+
} catch (error: unknown) {
|
|
355
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
356
|
+
sessionManager.complete(session.id);
|
|
357
|
+
return new Response(JSON.stringify({ error: msg }), {
|
|
358
|
+
status: 500,
|
|
359
|
+
headers: { 'Content-Type': 'application/json' },
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// ------------------------------------------------------------------
|
|
365
|
+
// POST /api/session/:id -- Continue existing session (SSE)
|
|
366
|
+
// ------------------------------------------------------------------
|
|
367
|
+
|
|
368
|
+
app.post('/api/session/:id', async ctx => {
|
|
369
|
+
const params = ctx.params as { id: string };
|
|
370
|
+
const body = ctx.body as { message: string; model?: string };
|
|
371
|
+
const session = sessionManager.get(params.id);
|
|
372
|
+
if (!session) {
|
|
373
|
+
return new Response(JSON.stringify({ error: 'Session not found' }), {
|
|
374
|
+
status: 404,
|
|
375
|
+
headers: { 'Content-Type': 'application/json' },
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const existing = getConversation(params.id);
|
|
380
|
+
const history: LLMMessage[] = existing?.messages ?? [];
|
|
381
|
+
const mode = parseMode(session.mode);
|
|
382
|
+
|
|
383
|
+
const stream = createAgentSSEStream(
|
|
384
|
+
body.message,
|
|
385
|
+
history,
|
|
386
|
+
params.id,
|
|
387
|
+
mode,
|
|
388
|
+
body.model ?? session.model,
|
|
389
|
+
router,
|
|
390
|
+
contextManager,
|
|
391
|
+
sessionManager
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
return sseResponse(stream);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// ------------------------------------------------------------------
|
|
398
|
+
// POST /api/share -- Share a session
|
|
399
|
+
// ------------------------------------------------------------------
|
|
400
|
+
|
|
401
|
+
app.post('/api/share', ctx => {
|
|
402
|
+
const body = ctx.body as { sessionId: string; isLive?: boolean; ttlDays?: number };
|
|
403
|
+
const shared = shareSession(body.sessionId, {
|
|
404
|
+
isLive: body.isLive,
|
|
405
|
+
ttlDays: body.ttlDays,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (!shared) {
|
|
409
|
+
return new Response(JSON.stringify({ error: 'Session not found' }), {
|
|
410
|
+
status: 404,
|
|
411
|
+
headers: { 'Content-Type': 'application/json' },
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
shareId: shared.id,
|
|
417
|
+
url: `http://${host}:${port}/nimbus/share/${shared.id}`,
|
|
418
|
+
expiresAt: shared.expiresAt,
|
|
419
|
+
isLive: shared.isLive,
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// ------------------------------------------------------------------
|
|
424
|
+
// GET /api/share/:id -- Get shared session
|
|
425
|
+
// ------------------------------------------------------------------
|
|
426
|
+
|
|
427
|
+
app.get('/api/share/:id', ({ params }: { params: { id: string } }) => {
|
|
428
|
+
const shared = getSharedSession(params.id);
|
|
429
|
+
if (!shared) {
|
|
430
|
+
return new Response(JSON.stringify({ error: 'Shared session not found or expired' }), {
|
|
431
|
+
status: 404,
|
|
432
|
+
headers: { 'Content-Type': 'application/json' },
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return shared;
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// ------------------------------------------------------------------
|
|
439
|
+
// GET /api/shares -- List all shares
|
|
440
|
+
// ------------------------------------------------------------------
|
|
441
|
+
|
|
442
|
+
app.get('/api/shares', () => ({
|
|
443
|
+
shares: listShares(),
|
|
444
|
+
}));
|
|
445
|
+
|
|
446
|
+
// ------------------------------------------------------------------
|
|
447
|
+
// Start listening
|
|
448
|
+
// ------------------------------------------------------------------
|
|
449
|
+
|
|
450
|
+
app.listen({ port, hostname: host });
|
|
451
|
+
|
|
452
|
+
console.log(`
|
|
453
|
+
Nimbus API Server
|
|
454
|
+
─────────────────────────────
|
|
455
|
+
Local: http://${host}:${port}
|
|
456
|
+
Health: http://${host}:${port}/api/health
|
|
457
|
+
OpenAPI: http://${host}:${port}/api/openapi.json
|
|
458
|
+
${options.auth ? ' Auth: HTTP Basic Auth enabled' : ' Auth: None (use --auth user:pass to enable)'}
|
|
459
|
+
|
|
460
|
+
Press Ctrl+C to stop.
|
|
461
|
+
`);
|
|
462
|
+
}
|
package/src/cli/web.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nimbus web -- Start the API server and open the Web UI
|
|
3
|
+
*
|
|
4
|
+
* Convenience command that:
|
|
5
|
+
* 1. Starts `nimbus serve` on the configured port
|
|
6
|
+
* 2. Opens the astron-landing Web UI in the default browser
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* nimbus web # serve on 4200, open http://localhost:6001/nimbus
|
|
10
|
+
* nimbus web --port 8080 # custom serve port
|
|
11
|
+
* nimbus web --ui-url https://app.example.com/nimbus # custom Web UI URL
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from 'node:child_process';
|
|
15
|
+
import { serveCommand } from './serve';
|
|
16
|
+
|
|
17
|
+
export interface WebOptions {
|
|
18
|
+
/** Port for nimbus serve (default: 4200). */
|
|
19
|
+
port?: number;
|
|
20
|
+
/** Hostname for nimbus serve (default: 'localhost'). */
|
|
21
|
+
host?: string;
|
|
22
|
+
/** HTTP Basic Auth credentials. */
|
|
23
|
+
auth?: string;
|
|
24
|
+
/** URL of the Web UI (default: http://localhost:6001/nimbus). */
|
|
25
|
+
uiUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Open a URL in the default browser (cross-platform).
|
|
30
|
+
*/
|
|
31
|
+
async function openBrowser(url: string): Promise<void> {
|
|
32
|
+
const { platform } = process;
|
|
33
|
+
const cmd =
|
|
34
|
+
platform === 'darwin'
|
|
35
|
+
? ['open', url]
|
|
36
|
+
: platform === 'win32'
|
|
37
|
+
? ['cmd', '/c', 'start', url]
|
|
38
|
+
: ['xdg-open', url];
|
|
39
|
+
|
|
40
|
+
const proc = spawn(cmd[0], cmd.slice(1), { stdio: 'ignore', detached: true });
|
|
41
|
+
proc.unref();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run the web command: start serve and open browser.
|
|
46
|
+
*/
|
|
47
|
+
export async function webCommand(options: WebOptions): Promise<void> {
|
|
48
|
+
const port = options.port ?? 4200;
|
|
49
|
+
const uiUrl = options.uiUrl ?? 'http://localhost:6001/nimbus';
|
|
50
|
+
|
|
51
|
+
console.log(`Starting Nimbus API server on port ${port}...`);
|
|
52
|
+
console.log(`Opening Web UI at ${uiUrl}\n`);
|
|
53
|
+
|
|
54
|
+
// Open browser after a short delay to let the server start
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
openBrowser(uiUrl).catch(() => {
|
|
57
|
+
console.log(`Could not open browser. Please visit: ${uiUrl}`);
|
|
58
|
+
});
|
|
59
|
+
}, 1500);
|
|
60
|
+
|
|
61
|
+
// Start the server (this blocks)
|
|
62
|
+
await serveCommand({
|
|
63
|
+
port,
|
|
64
|
+
host: options.host,
|
|
65
|
+
auth: options.auth,
|
|
66
|
+
});
|
|
67
|
+
}
|