@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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { logger } from './logger';
|
|
2
|
+
|
|
3
|
+
interface RateLimiterOptions {
|
|
4
|
+
requestsPerMinute: number;
|
|
5
|
+
burstSize?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ClientBucket {
|
|
9
|
+
tokens: number;
|
|
10
|
+
lastRefill: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class SimpleRateLimiter {
|
|
14
|
+
private requestsPerMinute: number;
|
|
15
|
+
private burstSize: number;
|
|
16
|
+
private buckets: Map<string, ClientBucket>;
|
|
17
|
+
private cleanupInterval: ReturnType<typeof setInterval> | null;
|
|
18
|
+
|
|
19
|
+
constructor(options: RateLimiterOptions) {
|
|
20
|
+
this.requestsPerMinute = options.requestsPerMinute;
|
|
21
|
+
this.burstSize = options.burstSize || options.requestsPerMinute;
|
|
22
|
+
this.buckets = new Map();
|
|
23
|
+
|
|
24
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);
|
|
25
|
+
if (
|
|
26
|
+
this.cleanupInterval &&
|
|
27
|
+
typeof this.cleanupInterval === 'object' &&
|
|
28
|
+
'unref' in this.cleanupInterval
|
|
29
|
+
) {
|
|
30
|
+
(this.cleanupInterval as any).unref();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
tryAcquire(clientId: string = 'default'): boolean {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
let bucket = this.buckets.get(clientId);
|
|
37
|
+
|
|
38
|
+
if (!bucket) {
|
|
39
|
+
bucket = { tokens: this.burstSize, lastRefill: now };
|
|
40
|
+
this.buckets.set(clientId, bucket);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const elapsed = now - bucket.lastRefill;
|
|
44
|
+
const refill = (elapsed / 60_000) * this.requestsPerMinute;
|
|
45
|
+
bucket.tokens = Math.min(this.burstSize, bucket.tokens + refill);
|
|
46
|
+
bucket.lastRefill = now;
|
|
47
|
+
|
|
48
|
+
if (bucket.tokens >= 1) {
|
|
49
|
+
bucket.tokens -= 1;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getRemainingRequests(clientId: string = 'default'): number {
|
|
57
|
+
const bucket = this.buckets.get(clientId);
|
|
58
|
+
if (!bucket) {
|
|
59
|
+
return this.burstSize;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const elapsed = Date.now() - bucket.lastRefill;
|
|
63
|
+
const refill = (elapsed / 60_000) * this.requestsPerMinute;
|
|
64
|
+
return Math.min(this.burstSize, Math.floor(bucket.tokens + refill));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private cleanup(): void {
|
|
68
|
+
const cutoff = Date.now() - 120_000;
|
|
69
|
+
for (const [clientId, bucket] of this.buckets) {
|
|
70
|
+
if (bucket.lastRefill < cutoff) {
|
|
71
|
+
this.buckets.delete(clientId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
destroy(): void {
|
|
77
|
+
if (this.cleanupInterval) {
|
|
78
|
+
clearInterval(this.cleanupInterval);
|
|
79
|
+
this.cleanupInterval = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getClientIp(req: Request): string {
|
|
85
|
+
const forwarded = req.headers.get('x-forwarded-for');
|
|
86
|
+
if (forwarded) {
|
|
87
|
+
return forwarded.split(',')[0].trim();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const realIp = req.headers.get('x-real-ip');
|
|
91
|
+
if (realIp) {
|
|
92
|
+
return realIp;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return 'unknown';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function rateLimitMiddleware(limiter: SimpleRateLimiter): (req: Request) => Response | null {
|
|
99
|
+
return (req: Request): Response | null => {
|
|
100
|
+
const url = new URL(req.url);
|
|
101
|
+
|
|
102
|
+
if (url.pathname === '/health') {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const clientIp = getClientIp(req);
|
|
107
|
+
if (!limiter.tryAcquire(clientIp)) {
|
|
108
|
+
logger.warn(`Rate limited client ${clientIp} on ${url.pathname}`);
|
|
109
|
+
const retryAfter = Math.ceil(60 / limiter.getRemainingRequests(clientIp) || 60);
|
|
110
|
+
return new Response(JSON.stringify({ success: false, error: 'Too Many Requests' }), {
|
|
111
|
+
status: 429,
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
'Retry-After': String(retryAfter),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { logger } from './logger';
|
|
2
|
+
|
|
3
|
+
const SERVICE_TOKEN_HEADER = 'x-internal-service-token';
|
|
4
|
+
|
|
5
|
+
export function validateServiceToken(req: Request): boolean {
|
|
6
|
+
const expectedToken = process.env.INTERNAL_SERVICE_TOKEN;
|
|
7
|
+
if (!expectedToken) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const providedToken = req.headers.get(SERVICE_TOKEN_HEADER);
|
|
12
|
+
return providedToken === expectedToken;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function serviceAuthMiddleware(req: Request): Response | null {
|
|
16
|
+
const expectedToken = process.env.INTERNAL_SERVICE_TOKEN;
|
|
17
|
+
if (!expectedToken) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const url = new URL(req.url);
|
|
22
|
+
const path = url.pathname;
|
|
23
|
+
|
|
24
|
+
if (path === '/health' || path.startsWith('/swagger') || path === '/api/openapi.json') {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!path.startsWith('/api/')) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!validateServiceToken(req)) {
|
|
33
|
+
logger.warn(`Unauthorized service request to ${path}`);
|
|
34
|
+
return Response.json(
|
|
35
|
+
{ success: false, error: 'Unauthorized: invalid or missing service token' },
|
|
36
|
+
{ status: 401 }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getServiceAuthHeaders(): Record<string, string> {
|
|
44
|
+
const token = process.env.INTERNAL_SERVICE_TOKEN;
|
|
45
|
+
if (!token) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
return { [SERVICE_TOKEN_HEADER]: token };
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ValidationError } from './errors';
|
|
2
|
+
|
|
3
|
+
export function validateRequired(value: unknown, fieldName: string, service: string): void {
|
|
4
|
+
if (value === undefined || value === null || value === '') {
|
|
5
|
+
throw new ValidationError(`Field '${fieldName}' is required`, service, { field: fieldName });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function validateString(value: unknown, fieldName: string, service: string): string {
|
|
10
|
+
if (typeof value !== 'string') {
|
|
11
|
+
throw new ValidationError(`Field '${fieldName}' must be a string`, service, {
|
|
12
|
+
field: fieldName,
|
|
13
|
+
received: typeof value,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function validateNumber(value: unknown, fieldName: string, service: string): number {
|
|
20
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
21
|
+
throw new ValidationError(`Field '${fieldName}' must be a number`, service, {
|
|
22
|
+
field: fieldName,
|
|
23
|
+
received: typeof value,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function validateBoolean(value: unknown, fieldName: string, service: string): boolean {
|
|
30
|
+
if (typeof value !== 'boolean') {
|
|
31
|
+
throw new ValidationError(`Field '${fieldName}' must be a boolean`, service, {
|
|
32
|
+
field: fieldName,
|
|
33
|
+
received: typeof value,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function validateEnum<T extends string>(
|
|
40
|
+
value: unknown,
|
|
41
|
+
fieldName: string,
|
|
42
|
+
allowedValues: T[],
|
|
43
|
+
service: string
|
|
44
|
+
): T {
|
|
45
|
+
if (typeof value !== 'string' || !allowedValues.includes(value as T)) {
|
|
46
|
+
throw new ValidationError(
|
|
47
|
+
`Field '${fieldName}' must be one of: ${allowedValues.join(', ')}`,
|
|
48
|
+
service,
|
|
49
|
+
{ field: fieldName, received: value, allowed: allowedValues }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return value as T;
|
|
53
|
+
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Watcher
|
|
3
|
+
*
|
|
4
|
+
* Watches the project directory for file changes and emits events
|
|
5
|
+
* that the agent loop can respond to (e.g., re-reading modified files).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { EventEmitter } from 'node:events';
|
|
11
|
+
|
|
12
|
+
export interface FileChangeEvent {
|
|
13
|
+
type: 'change' | 'rename';
|
|
14
|
+
path: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class FileWatcher extends EventEmitter {
|
|
19
|
+
private watcher: fs.FSWatcher | null = null;
|
|
20
|
+
private changes: FileChangeEvent[] = [];
|
|
21
|
+
private readonly maxChanges = 100;
|
|
22
|
+
private readonly ignorePatterns = [
|
|
23
|
+
'node_modules',
|
|
24
|
+
'.git',
|
|
25
|
+
'dist',
|
|
26
|
+
'coverage',
|
|
27
|
+
'.nimbus',
|
|
28
|
+
'__pycache__',
|
|
29
|
+
'.terraform',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
constructor(private readonly rootDir: string) {
|
|
33
|
+
super();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Start watching the project directory.
|
|
38
|
+
*/
|
|
39
|
+
start(): void {
|
|
40
|
+
if (this.watcher) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
this.watcher = fs.watch(this.rootDir, { recursive: true }, (eventType, filename) => {
|
|
46
|
+
if (!filename) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Skip ignored paths
|
|
51
|
+
const parts = filename.split(path.sep);
|
|
52
|
+
if (parts.some(p => this.ignorePatterns.includes(p))) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Skip hidden files
|
|
57
|
+
if (parts.some(p => p.startsWith('.') && p !== '.env')) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const event: FileChangeEvent = {
|
|
62
|
+
type: eventType as 'change' | 'rename',
|
|
63
|
+
path: path.join(this.rootDir, filename),
|
|
64
|
+
timestamp: Date.now(),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
this.changes.push(event);
|
|
68
|
+
if (this.changes.length > this.maxChanges) {
|
|
69
|
+
this.changes.shift();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.emit('change', event);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// recursive:true is only supported on macOS and Windows.
|
|
76
|
+
// On Linux, fs.watch silently ignores the flag and only watches the root dir.
|
|
77
|
+
if (process.platform === 'linux') {
|
|
78
|
+
// Watch key subdirectories individually as a workaround
|
|
79
|
+
const subdirs = ['src', 'lib', 'app', 'packages', 'services', 'test', 'tests', 'scripts'];
|
|
80
|
+
for (const sub of subdirs) {
|
|
81
|
+
const subPath = path.join(this.rootDir, sub);
|
|
82
|
+
try {
|
|
83
|
+
if (fs.existsSync(subPath) && fs.statSync(subPath).isDirectory()) {
|
|
84
|
+
fs.watch(subPath, { recursive: false }, (eventType, filename) => {
|
|
85
|
+
if (!filename) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const fullPath = path.join(subPath, filename);
|
|
89
|
+
const relParts = path.relative(this.rootDir, fullPath).split(path.sep);
|
|
90
|
+
if (relParts.some(p => this.ignorePatterns.includes(p))) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (relParts.some(p => p.startsWith('.') && p !== '.env')) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const event: FileChangeEvent = {
|
|
97
|
+
type: eventType as 'change' | 'rename',
|
|
98
|
+
path: fullPath,
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
};
|
|
101
|
+
this.changes.push(event);
|
|
102
|
+
if (this.changes.length > this.maxChanges) {
|
|
103
|
+
this.changes.shift();
|
|
104
|
+
}
|
|
105
|
+
this.emit('change', event);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
/* non-critical */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// Watching is non-critical -- some systems don't support recursive watch
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Stop watching.
|
|
120
|
+
*/
|
|
121
|
+
stop(): void {
|
|
122
|
+
if (this.watcher) {
|
|
123
|
+
this.watcher.close();
|
|
124
|
+
this.watcher = null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get recent changes since a timestamp.
|
|
130
|
+
*/
|
|
131
|
+
getChangesSince(timestamp: number): FileChangeEvent[] {
|
|
132
|
+
return this.changes.filter(c => c.timestamp > timestamp);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get a summary of recent changes for the agent context.
|
|
137
|
+
*/
|
|
138
|
+
getSummary(since?: number): string {
|
|
139
|
+
const relevant = since ? this.getChangesSince(since) : this.changes;
|
|
140
|
+
if (relevant.length === 0) {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const uniquePaths = [...new Set(relevant.map(c => c.path))];
|
|
145
|
+
const lines = uniquePaths.slice(-20).map(p => {
|
|
146
|
+
const rel = path.relative(this.rootDir, p);
|
|
147
|
+
return ` - ${rel}`;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return `Files changed externally:\n${lines.join('\n')}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Clear the change history.
|
|
155
|
+
*/
|
|
156
|
+
clearChanges(): void {
|
|
157
|
+
this.changes = [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get isWatching(): boolean {
|
|
161
|
+
return this.watcher !== null;
|
|
162
|
+
}
|
|
163
|
+
}
|