@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,1137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for Kubernetes operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { k8sClient } from '../../clients';
|
|
8
|
+
import { ui } from '../../wizard/ui';
|
|
9
|
+
import { confirmWithResourceName } from '../../wizard/approval';
|
|
10
|
+
import { showDestructionCostWarning } from '../../utils/cost-warning';
|
|
11
|
+
import { historyManager } from '../../history';
|
|
12
|
+
|
|
13
|
+
export interface K8sCommandOptions {
|
|
14
|
+
namespace?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
labels?: Record<string, string>;
|
|
17
|
+
output?: 'json' | 'yaml' | 'wide';
|
|
18
|
+
container?: string;
|
|
19
|
+
tail?: number;
|
|
20
|
+
since?: string;
|
|
21
|
+
force?: boolean;
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
yes?: boolean;
|
|
24
|
+
kubeconfig?: string;
|
|
25
|
+
context?: string;
|
|
26
|
+
type?: 'strategic' | 'merge' | 'json';
|
|
27
|
+
overwrite?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get Kubernetes resources
|
|
32
|
+
*/
|
|
33
|
+
export async function k8sGetCommand(
|
|
34
|
+
resource: string,
|
|
35
|
+
options: K8sCommandOptions = {}
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
ui.header(`Kubernetes Get ${resource}`);
|
|
38
|
+
|
|
39
|
+
if (options.namespace) {
|
|
40
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
41
|
+
} else {
|
|
42
|
+
ui.info('Namespace: all');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ui.startSpinner({ message: `Getting ${resource}...` });
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const available = await k8sClient.isAvailable();
|
|
49
|
+
if (!available) {
|
|
50
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
51
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const result = await k8sClient.get(resource, {
|
|
56
|
+
namespace: options.namespace,
|
|
57
|
+
name: options.name,
|
|
58
|
+
labels: options.labels,
|
|
59
|
+
output: options.output,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (result.success) {
|
|
63
|
+
ui.stopSpinnerSuccess(`Found ${result.items.length} ${resource}`);
|
|
64
|
+
|
|
65
|
+
if (result.items.length > 0) {
|
|
66
|
+
// Display as table
|
|
67
|
+
ui.table({
|
|
68
|
+
columns: [
|
|
69
|
+
{ key: 'name', header: 'Name' },
|
|
70
|
+
{ key: 'namespace', header: 'Namespace' },
|
|
71
|
+
{ key: 'kind', header: 'Kind' },
|
|
72
|
+
{ key: 'labels', header: 'Labels' },
|
|
73
|
+
],
|
|
74
|
+
data: result.items.map(item => ({
|
|
75
|
+
name: item.metadata.name,
|
|
76
|
+
namespace: item.metadata.namespace || 'default',
|
|
77
|
+
kind: item.kind,
|
|
78
|
+
labels:
|
|
79
|
+
Object.entries(item.metadata.labels || {})
|
|
80
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
81
|
+
.join(', ') || '-',
|
|
82
|
+
})),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
ui.stopSpinnerFail(`Failed to get ${resource}`);
|
|
87
|
+
if (result.error) {
|
|
88
|
+
ui.error(result.error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (error: any) {
|
|
92
|
+
ui.stopSpinnerFail(`Error getting ${resource}`);
|
|
93
|
+
ui.error(error.message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Apply Kubernetes manifests
|
|
99
|
+
*/
|
|
100
|
+
export async function k8sApplyCommand(
|
|
101
|
+
manifests: string,
|
|
102
|
+
options: K8sCommandOptions = {}
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
ui.header('Kubernetes Apply');
|
|
105
|
+
|
|
106
|
+
ui.startSpinner({ message: 'Applying manifests...' });
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const available = await k8sClient.isAvailable();
|
|
110
|
+
if (!available) {
|
|
111
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
112
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result = await k8sClient.apply(manifests, {
|
|
117
|
+
namespace: options.namespace,
|
|
118
|
+
dryRun: options.dryRun,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (result.success) {
|
|
122
|
+
ui.stopSpinnerSuccess('Manifests applied successfully');
|
|
123
|
+
|
|
124
|
+
if (result.created && result.created.length > 0) {
|
|
125
|
+
ui.info('Created:');
|
|
126
|
+
result.created.forEach(r => ui.info(` - ${r}`));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (result.configured && result.configured.length > 0) {
|
|
130
|
+
ui.info('Configured:');
|
|
131
|
+
result.configured.forEach(r => ui.info(` - ${r}`));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (result.output) {
|
|
135
|
+
ui.box({ title: 'Output', content: result.output });
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
ui.stopSpinnerFail('Failed to apply manifests');
|
|
139
|
+
if (result.error) {
|
|
140
|
+
ui.error(result.error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch (error: any) {
|
|
144
|
+
ui.stopSpinnerFail('Error applying manifests');
|
|
145
|
+
ui.error(error.message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Delete Kubernetes resources
|
|
151
|
+
*/
|
|
152
|
+
export async function k8sDeleteCommand(
|
|
153
|
+
resource: string,
|
|
154
|
+
name: string,
|
|
155
|
+
options: K8sCommandOptions = {}
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
ui.header(`Kubernetes Delete ${resource}/${name}`);
|
|
158
|
+
|
|
159
|
+
if (options.namespace) {
|
|
160
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Show destructive operation warning with resource count and estimated impact
|
|
164
|
+
ui.newLine();
|
|
165
|
+
ui.warning(`Destructive operation: deleting ${resource}/${name}`);
|
|
166
|
+
ui.print(` ${ui.color('Resource:', 'yellow')} ${resource}`);
|
|
167
|
+
ui.print(` ${ui.color('Name:', 'yellow')} ${name}`);
|
|
168
|
+
ui.print(` ${ui.color('Namespace:', 'yellow')} ${options.namespace || 'default'}`);
|
|
169
|
+
ui.print(` ${ui.color('Resources affected:', 'yellow')} 1 ${resource}`);
|
|
170
|
+
ui.print(
|
|
171
|
+
` ${ui.color('Impact:', 'red')} This will permanently remove the ${resource} and any dependent resources.`
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Show cost warning before destructive operation
|
|
175
|
+
await showDestructionCostWarning(process.cwd());
|
|
176
|
+
|
|
177
|
+
// Require type-name-to-delete confirmation for destructive operations
|
|
178
|
+
if (!options.force && !options.dryRun && !options.yes) {
|
|
179
|
+
const confirmed = await confirmWithResourceName(name, resource);
|
|
180
|
+
if (!confirmed) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
ui.startSpinner({ message: `Deleting ${resource}/${name}...` });
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const available = await k8sClient.isAvailable();
|
|
189
|
+
if (!available) {
|
|
190
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
191
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const result = await k8sClient.delete(resource, name, {
|
|
196
|
+
namespace: options.namespace,
|
|
197
|
+
force: options.force,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (result.success) {
|
|
201
|
+
ui.stopSpinnerSuccess(`Deleted ${resource}/${name}`);
|
|
202
|
+
|
|
203
|
+
if (result.deleted && result.deleted.length > 0) {
|
|
204
|
+
ui.info('Deleted:');
|
|
205
|
+
result.deleted.forEach(r => ui.info(` - ${r}`));
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
ui.stopSpinnerFail(`Failed to delete ${resource}/${name}`);
|
|
209
|
+
if (result.error) {
|
|
210
|
+
ui.error(result.error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch (error: any) {
|
|
214
|
+
ui.stopSpinnerFail(`Error deleting ${resource}/${name}`);
|
|
215
|
+
ui.error(error.message);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get pod logs
|
|
221
|
+
*/
|
|
222
|
+
export async function k8sLogsCommand(
|
|
223
|
+
podName: string,
|
|
224
|
+
options: K8sCommandOptions = {}
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
ui.header(`Kubernetes Logs - ${podName}`);
|
|
227
|
+
|
|
228
|
+
if (options.namespace) {
|
|
229
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
230
|
+
}
|
|
231
|
+
if (options.container) {
|
|
232
|
+
ui.info(`Container: ${options.container}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
ui.startSpinner({ message: `Fetching logs from ${podName}...` });
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const available = await k8sClient.isAvailable();
|
|
239
|
+
if (!available) {
|
|
240
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
241
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const result = await k8sClient.logs(podName, {
|
|
246
|
+
namespace: options.namespace,
|
|
247
|
+
container: options.container,
|
|
248
|
+
tail: options.tail,
|
|
249
|
+
since: options.since,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (result.success) {
|
|
253
|
+
ui.stopSpinnerSuccess('Logs retrieved');
|
|
254
|
+
console.log(result.logs);
|
|
255
|
+
} else {
|
|
256
|
+
ui.stopSpinnerFail('Failed to get logs');
|
|
257
|
+
if (result.error) {
|
|
258
|
+
ui.error(result.error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} catch (error: any) {
|
|
262
|
+
ui.stopSpinnerFail('Error getting logs');
|
|
263
|
+
ui.error(error.message);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Describe Kubernetes resource
|
|
269
|
+
*/
|
|
270
|
+
export async function k8sDescribeCommand(
|
|
271
|
+
resource: string,
|
|
272
|
+
name: string,
|
|
273
|
+
options: K8sCommandOptions = {}
|
|
274
|
+
): Promise<void> {
|
|
275
|
+
ui.header(`Kubernetes Describe ${resource}/${name}`);
|
|
276
|
+
|
|
277
|
+
if (options.namespace) {
|
|
278
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
ui.startSpinner({ message: `Describing ${resource}/${name}...` });
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const available = await k8sClient.isAvailable();
|
|
285
|
+
if (!available) {
|
|
286
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
287
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const result = await k8sClient.describe(resource, name, {
|
|
292
|
+
namespace: options.namespace,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (result.success) {
|
|
296
|
+
ui.stopSpinnerSuccess(`Described ${resource}/${name}`);
|
|
297
|
+
console.log(result.output);
|
|
298
|
+
} else {
|
|
299
|
+
ui.stopSpinnerFail(`Failed to describe ${resource}/${name}`);
|
|
300
|
+
if (result.error) {
|
|
301
|
+
ui.error(result.error);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch (error: any) {
|
|
305
|
+
ui.stopSpinnerFail(`Error describing ${resource}/${name}`);
|
|
306
|
+
ui.error(error.message);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Scale deployment/replicaset
|
|
312
|
+
*/
|
|
313
|
+
export async function k8sScaleCommand(
|
|
314
|
+
resource: string,
|
|
315
|
+
name: string,
|
|
316
|
+
replicas: number,
|
|
317
|
+
options: K8sCommandOptions = {}
|
|
318
|
+
): Promise<void> {
|
|
319
|
+
ui.header(`Kubernetes Scale ${resource}/${name}`);
|
|
320
|
+
|
|
321
|
+
ui.info(`Scaling to ${replicas} replicas`);
|
|
322
|
+
if (options.namespace) {
|
|
323
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
ui.startSpinner({ message: `Scaling ${resource}/${name} to ${replicas}...` });
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const available = await k8sClient.isAvailable();
|
|
330
|
+
if (!available) {
|
|
331
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
332
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const result = await k8sClient.scale(resource, name, replicas, {
|
|
337
|
+
namespace: options.namespace,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
if (result.success) {
|
|
341
|
+
ui.stopSpinnerSuccess(`Scaled ${resource}/${name} to ${replicas} replicas`);
|
|
342
|
+
if (result.output) {
|
|
343
|
+
ui.box({ title: 'Output', content: result.output });
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
ui.stopSpinnerFail(`Failed to scale ${resource}/${name}`);
|
|
347
|
+
if (result.error) {
|
|
348
|
+
ui.error(result.error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} catch (error: any) {
|
|
352
|
+
ui.stopSpinnerFail(`Error scaling ${resource}/${name}`);
|
|
353
|
+
ui.error(error.message);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Execute a command in a pod
|
|
359
|
+
*/
|
|
360
|
+
export async function k8sExecCommand(
|
|
361
|
+
pod: string,
|
|
362
|
+
command: string[],
|
|
363
|
+
options: K8sCommandOptions = {}
|
|
364
|
+
): Promise<void> {
|
|
365
|
+
ui.header(`Kubernetes Exec - ${pod}`);
|
|
366
|
+
|
|
367
|
+
if (options.namespace) {
|
|
368
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
369
|
+
}
|
|
370
|
+
if (options.container) {
|
|
371
|
+
ui.info(`Container: ${options.container}`);
|
|
372
|
+
}
|
|
373
|
+
ui.info(`Command: ${command.join(' ')}`);
|
|
374
|
+
|
|
375
|
+
ui.startSpinner({ message: `Executing in ${pod}...` });
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const available = await k8sClient.isAvailable();
|
|
379
|
+
if (!available) {
|
|
380
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
381
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const result = await k8sClient.exec(pod, command, {
|
|
386
|
+
namespace: options.namespace,
|
|
387
|
+
container: options.container,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (result.success) {
|
|
391
|
+
ui.stopSpinnerSuccess('Command executed');
|
|
392
|
+
if (result.output) {
|
|
393
|
+
console.log(result.output);
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
ui.stopSpinnerFail('Command failed');
|
|
397
|
+
if (result.error) {
|
|
398
|
+
ui.error(result.error);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
} catch (error: any) {
|
|
402
|
+
ui.stopSpinnerFail('Error executing command');
|
|
403
|
+
ui.error(error.message);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Manage rollouts for a resource
|
|
409
|
+
*/
|
|
410
|
+
export async function k8sRolloutCommand(
|
|
411
|
+
resource: string,
|
|
412
|
+
name: string,
|
|
413
|
+
action: 'status' | 'history' | 'restart' | 'undo' | 'pause' | 'resume',
|
|
414
|
+
options: K8sCommandOptions = {}
|
|
415
|
+
): Promise<void> {
|
|
416
|
+
ui.header(`Kubernetes Rollout ${action} - ${resource}/${name}`);
|
|
417
|
+
|
|
418
|
+
if (options.namespace) {
|
|
419
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
ui.startSpinner({ message: `Running rollout ${action}...` });
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const available = await k8sClient.isAvailable();
|
|
426
|
+
if (!available) {
|
|
427
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
428
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const result = await k8sClient.rollout(resource, name, action, {
|
|
433
|
+
namespace: options.namespace,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (result.success) {
|
|
437
|
+
ui.stopSpinnerSuccess(`Rollout ${action} complete`);
|
|
438
|
+
if (result.output) {
|
|
439
|
+
console.log(result.output);
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
ui.stopSpinnerFail(`Rollout ${action} failed`);
|
|
443
|
+
if (result.error) {
|
|
444
|
+
ui.error(result.error);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
} catch (error: any) {
|
|
448
|
+
ui.stopSpinnerFail(`Error during rollout ${action}`);
|
|
449
|
+
ui.error(error.message);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Get Kubernetes events
|
|
455
|
+
*/
|
|
456
|
+
export async function k8sEventsCommand(options: K8sCommandOptions = {}): Promise<void> {
|
|
457
|
+
ui.header('Kubernetes Events');
|
|
458
|
+
|
|
459
|
+
if (options.namespace) {
|
|
460
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
461
|
+
} else {
|
|
462
|
+
ui.info('Namespace: all');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
ui.startSpinner({ message: 'Fetching events...' });
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
const available = await k8sClient.isAvailable();
|
|
469
|
+
if (!available) {
|
|
470
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
471
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const result = await k8sClient.events({
|
|
476
|
+
namespace: options.namespace,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
if (result.success) {
|
|
480
|
+
ui.stopSpinnerSuccess(`Found ${result.events.length} events`);
|
|
481
|
+
|
|
482
|
+
if (result.events.length > 0) {
|
|
483
|
+
ui.table({
|
|
484
|
+
columns: [
|
|
485
|
+
{ key: 'type', header: 'Type' },
|
|
486
|
+
{ key: 'reason', header: 'Reason' },
|
|
487
|
+
{ key: 'object', header: 'Object' },
|
|
488
|
+
{ key: 'message', header: 'Message' },
|
|
489
|
+
{ key: 'age', header: 'Age' },
|
|
490
|
+
],
|
|
491
|
+
data: result.events.map(event => ({
|
|
492
|
+
type: event.type || '-',
|
|
493
|
+
reason: event.reason || '-',
|
|
494
|
+
object: event.object || '-',
|
|
495
|
+
message: event.message || '-',
|
|
496
|
+
age: event.age || '-',
|
|
497
|
+
})),
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
ui.stopSpinnerFail('Failed to get events');
|
|
502
|
+
if (result.error) {
|
|
503
|
+
ui.error(result.error);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} catch (error: any) {
|
|
507
|
+
ui.stopSpinnerFail('Error getting events');
|
|
508
|
+
ui.error(error.message);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Forward local ports to a pod
|
|
514
|
+
*/
|
|
515
|
+
export async function k8sPortForwardCommand(
|
|
516
|
+
pod: string,
|
|
517
|
+
ports: string,
|
|
518
|
+
options: K8sCommandOptions = {}
|
|
519
|
+
): Promise<void> {
|
|
520
|
+
ui.header(`Kubernetes Port-Forward - ${pod}`);
|
|
521
|
+
|
|
522
|
+
ui.info(`Port mapping: ${ports}`);
|
|
523
|
+
if (options.namespace) {
|
|
524
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
ui.startSpinner({ message: `Starting port-forward to ${pod}...` });
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
const available = await k8sClient.isAvailable();
|
|
531
|
+
if (!available) {
|
|
532
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
533
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const result = await k8sClient.portForward(pod, ports, {
|
|
538
|
+
namespace: options.namespace,
|
|
539
|
+
kubeconfig: options.kubeconfig,
|
|
540
|
+
context: options.context,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
if (result.success) {
|
|
544
|
+
ui.stopSpinnerSuccess(`Port-forward established for ${pod}`);
|
|
545
|
+
if (result.output) {
|
|
546
|
+
console.log(result.output);
|
|
547
|
+
}
|
|
548
|
+
} else {
|
|
549
|
+
ui.stopSpinnerFail(`Failed to start port-forward to ${pod}`);
|
|
550
|
+
if (result.error) {
|
|
551
|
+
ui.error(result.error);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} catch (error: any) {
|
|
555
|
+
ui.stopSpinnerFail(`Error starting port-forward`);
|
|
556
|
+
ui.error(error.message);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Manage Kubernetes namespaces (list, create, delete)
|
|
562
|
+
*/
|
|
563
|
+
export async function k8sNamespaceCommand(
|
|
564
|
+
action: 'list' | 'create' | 'delete',
|
|
565
|
+
name: string | undefined,
|
|
566
|
+
options: K8sCommandOptions = {}
|
|
567
|
+
): Promise<void> {
|
|
568
|
+
const kubeconfig = options.kubeconfig;
|
|
569
|
+
const context = options.context;
|
|
570
|
+
|
|
571
|
+
if (action === 'list') {
|
|
572
|
+
ui.header('Kubernetes Namespaces');
|
|
573
|
+
ui.startSpinner({ message: 'Fetching namespaces...' });
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
const available = await k8sClient.isAvailable();
|
|
577
|
+
if (!available) {
|
|
578
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
579
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const result = await k8sClient.listNamespaces({ kubeconfig, context });
|
|
584
|
+
|
|
585
|
+
if (result.success) {
|
|
586
|
+
ui.stopSpinnerSuccess(`Found ${result.namespaces.length} namespace(s)`);
|
|
587
|
+
if (result.namespaces.length > 0) {
|
|
588
|
+
ui.table({
|
|
589
|
+
columns: [{ key: 'name', header: 'Name' }],
|
|
590
|
+
data: result.namespaces.map(ns => ({ name: ns })),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
ui.stopSpinnerFail('Failed to list namespaces');
|
|
595
|
+
if (result.error) {
|
|
596
|
+
ui.error(result.error);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
} catch (error: any) {
|
|
600
|
+
ui.stopSpinnerFail('Error listing namespaces');
|
|
601
|
+
ui.error(error.message);
|
|
602
|
+
}
|
|
603
|
+
} else if (action === 'create') {
|
|
604
|
+
if (!name) {
|
|
605
|
+
ui.error('Usage: nimbus k8s namespace create <name>');
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
ui.header(`Kubernetes Create Namespace - ${name}`);
|
|
609
|
+
ui.startSpinner({ message: `Creating namespace ${name}...` });
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const available = await k8sClient.isAvailable();
|
|
613
|
+
if (!available) {
|
|
614
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
615
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const result = await k8sClient.createNamespace(name, { kubeconfig, context });
|
|
620
|
+
|
|
621
|
+
if (result.success) {
|
|
622
|
+
ui.stopSpinnerSuccess(`Namespace ${name} created`);
|
|
623
|
+
if (result.output) {
|
|
624
|
+
ui.box({ title: 'Output', content: result.output });
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
ui.stopSpinnerFail(`Failed to create namespace ${name}`);
|
|
628
|
+
if (result.error) {
|
|
629
|
+
ui.error(result.error);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
} catch (error: any) {
|
|
633
|
+
ui.stopSpinnerFail('Error creating namespace');
|
|
634
|
+
ui.error(error.message);
|
|
635
|
+
}
|
|
636
|
+
} else if (action === 'delete') {
|
|
637
|
+
if (!name) {
|
|
638
|
+
ui.error('Usage: nimbus k8s namespace delete <name>');
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
ui.header(`Kubernetes Delete Namespace - ${name}`);
|
|
642
|
+
|
|
643
|
+
ui.newLine();
|
|
644
|
+
ui.warning(`Destructive operation: deleting namespace ${name}`);
|
|
645
|
+
ui.print(` ${ui.color('Namespace:', 'yellow')} ${name}`);
|
|
646
|
+
ui.print(
|
|
647
|
+
` ${ui.color('Impact:', 'red')} This will permanently remove the namespace and all resources within it.`
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
await showDestructionCostWarning(process.cwd());
|
|
651
|
+
|
|
652
|
+
if (!options.force && !options.yes) {
|
|
653
|
+
const confirmed = await confirmWithResourceName(name, 'namespace');
|
|
654
|
+
if (!confirmed) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
ui.startSpinner({ message: `Deleting namespace ${name}...` });
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
const available = await k8sClient.isAvailable();
|
|
663
|
+
if (!available) {
|
|
664
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
665
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const result = await k8sClient.deleteNamespace(name, { kubeconfig, context });
|
|
670
|
+
|
|
671
|
+
if (result.success) {
|
|
672
|
+
ui.stopSpinnerSuccess(`Namespace ${name} deleted`);
|
|
673
|
+
if (result.output) {
|
|
674
|
+
ui.box({ title: 'Output', content: result.output });
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
ui.stopSpinnerFail(`Failed to delete namespace ${name}`);
|
|
678
|
+
if (result.error) {
|
|
679
|
+
ui.error(result.error);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
} catch (error: any) {
|
|
683
|
+
ui.stopSpinnerFail('Error deleting namespace');
|
|
684
|
+
ui.error(error.message);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Show resource usage for pods or nodes
|
|
691
|
+
*/
|
|
692
|
+
export async function k8sTopCommand(
|
|
693
|
+
resourceType: 'pods' | 'nodes',
|
|
694
|
+
options: K8sCommandOptions = {}
|
|
695
|
+
): Promise<void> {
|
|
696
|
+
ui.header(`Kubernetes Top ${resourceType}`);
|
|
697
|
+
|
|
698
|
+
if (resourceType === 'pods' && options.namespace) {
|
|
699
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
ui.startSpinner({ message: `Fetching ${resourceType} resource usage...` });
|
|
703
|
+
|
|
704
|
+
try {
|
|
705
|
+
const available = await k8sClient.isAvailable();
|
|
706
|
+
if (!available) {
|
|
707
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
708
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const result = await k8sClient.top(resourceType, {
|
|
713
|
+
namespace: options.namespace,
|
|
714
|
+
kubeconfig: options.kubeconfig,
|
|
715
|
+
context: options.context,
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
if (result.success) {
|
|
719
|
+
ui.stopSpinnerSuccess(`Resource usage for ${resourceType}`);
|
|
720
|
+
|
|
721
|
+
if (result.items && result.items.length > 0) {
|
|
722
|
+
const columns =
|
|
723
|
+
resourceType === 'pods'
|
|
724
|
+
? [
|
|
725
|
+
{ key: 'name', header: 'Name' },
|
|
726
|
+
{ key: 'namespace', header: 'Namespace' },
|
|
727
|
+
{ key: 'cpu', header: 'CPU' },
|
|
728
|
+
{ key: 'memory', header: 'Memory' },
|
|
729
|
+
]
|
|
730
|
+
: [
|
|
731
|
+
{ key: 'name', header: 'Name' },
|
|
732
|
+
{ key: 'cpu', header: 'CPU' },
|
|
733
|
+
{ key: 'memory', header: 'Memory' },
|
|
734
|
+
];
|
|
735
|
+
|
|
736
|
+
ui.table({ columns, data: result.items });
|
|
737
|
+
} else if (result.output) {
|
|
738
|
+
console.log(result.output);
|
|
739
|
+
}
|
|
740
|
+
} else {
|
|
741
|
+
ui.stopSpinnerFail(`Failed to get ${resourceType} usage`);
|
|
742
|
+
if (result.error) {
|
|
743
|
+
ui.error(result.error);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
} catch (error: any) {
|
|
747
|
+
ui.stopSpinnerFail(`Error getting ${resourceType} usage`);
|
|
748
|
+
ui.error(error.message);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Patch a Kubernetes resource
|
|
754
|
+
*/
|
|
755
|
+
export async function k8sPatchCommand(
|
|
756
|
+
resource: string,
|
|
757
|
+
name: string,
|
|
758
|
+
patch: string,
|
|
759
|
+
options: K8sCommandOptions = {}
|
|
760
|
+
): Promise<void> {
|
|
761
|
+
ui.header(`Kubernetes Patch ${resource}/${name}`);
|
|
762
|
+
|
|
763
|
+
if (options.namespace) {
|
|
764
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
765
|
+
}
|
|
766
|
+
ui.info(`Patch type: ${options.type || 'strategic'}`);
|
|
767
|
+
|
|
768
|
+
ui.startSpinner({ message: `Patching ${resource}/${name}...` });
|
|
769
|
+
|
|
770
|
+
try {
|
|
771
|
+
const available = await k8sClient.isAvailable();
|
|
772
|
+
if (!available) {
|
|
773
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
774
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const result = await k8sClient.patch(resource, name, patch, {
|
|
779
|
+
namespace: options.namespace,
|
|
780
|
+
type: options.type,
|
|
781
|
+
kubeconfig: options.kubeconfig,
|
|
782
|
+
context: options.context,
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
if (result.success) {
|
|
786
|
+
ui.stopSpinnerSuccess(`Patched ${resource}/${name}`);
|
|
787
|
+
if (result.output) {
|
|
788
|
+
ui.box({ title: 'Output', content: result.output });
|
|
789
|
+
}
|
|
790
|
+
} else {
|
|
791
|
+
ui.stopSpinnerFail(`Failed to patch ${resource}/${name}`);
|
|
792
|
+
if (result.error) {
|
|
793
|
+
ui.error(result.error);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
} catch (error: any) {
|
|
797
|
+
ui.stopSpinnerFail(`Error patching ${resource}/${name}`);
|
|
798
|
+
ui.error(error.message);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Label a Kubernetes resource
|
|
804
|
+
*/
|
|
805
|
+
export async function k8sLabelCommand(
|
|
806
|
+
resource: string,
|
|
807
|
+
name: string,
|
|
808
|
+
labels: Record<string, string>,
|
|
809
|
+
options: K8sCommandOptions = {}
|
|
810
|
+
): Promise<void> {
|
|
811
|
+
ui.header(`Kubernetes Label ${resource}/${name}`);
|
|
812
|
+
|
|
813
|
+
const labelStr = Object.entries(labels)
|
|
814
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
815
|
+
.join(', ');
|
|
816
|
+
ui.info(`Labels: ${labelStr}`);
|
|
817
|
+
|
|
818
|
+
if (options.namespace) {
|
|
819
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
ui.startSpinner({ message: `Labeling ${resource}/${name}...` });
|
|
823
|
+
|
|
824
|
+
try {
|
|
825
|
+
const available = await k8sClient.isAvailable();
|
|
826
|
+
if (!available) {
|
|
827
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
828
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const result = await k8sClient.label(resource, name, labels, {
|
|
833
|
+
namespace: options.namespace,
|
|
834
|
+
overwrite: options.overwrite,
|
|
835
|
+
kubeconfig: options.kubeconfig,
|
|
836
|
+
context: options.context,
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
if (result.success) {
|
|
840
|
+
ui.stopSpinnerSuccess(`Labeled ${resource}/${name}`);
|
|
841
|
+
if (result.output) {
|
|
842
|
+
ui.box({ title: 'Output', content: result.output });
|
|
843
|
+
}
|
|
844
|
+
} else {
|
|
845
|
+
ui.stopSpinnerFail(`Failed to label ${resource}/${name}`);
|
|
846
|
+
if (result.error) {
|
|
847
|
+
ui.error(result.error);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
} catch (error: any) {
|
|
851
|
+
ui.stopSpinnerFail(`Error labeling ${resource}/${name}`);
|
|
852
|
+
ui.error(error.message);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Annotate a Kubernetes resource
|
|
858
|
+
*/
|
|
859
|
+
export async function k8sAnnotateCommand(
|
|
860
|
+
resource: string,
|
|
861
|
+
name: string,
|
|
862
|
+
annotations: Record<string, string>,
|
|
863
|
+
options: K8sCommandOptions = {}
|
|
864
|
+
): Promise<void> {
|
|
865
|
+
ui.header(`Kubernetes Annotate ${resource}/${name}`);
|
|
866
|
+
|
|
867
|
+
const annotationStr = Object.entries(annotations)
|
|
868
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
869
|
+
.join(', ');
|
|
870
|
+
ui.info(`Annotations: ${annotationStr}`);
|
|
871
|
+
|
|
872
|
+
if (options.namespace) {
|
|
873
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
ui.startSpinner({ message: `Annotating ${resource}/${name}...` });
|
|
877
|
+
|
|
878
|
+
try {
|
|
879
|
+
const available = await k8sClient.isAvailable();
|
|
880
|
+
if (!available) {
|
|
881
|
+
ui.stopSpinnerFail('Kubernetes Tools Service not available');
|
|
882
|
+
ui.error('Please ensure the Kubernetes Tools Service is running.');
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const result = await k8sClient.annotate(resource, name, annotations, {
|
|
887
|
+
namespace: options.namespace,
|
|
888
|
+
overwrite: options.overwrite,
|
|
889
|
+
kubeconfig: options.kubeconfig,
|
|
890
|
+
context: options.context,
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
if (result.success) {
|
|
894
|
+
ui.stopSpinnerSuccess(`Annotated ${resource}/${name}`);
|
|
895
|
+
if (result.output) {
|
|
896
|
+
ui.box({ title: 'Output', content: result.output });
|
|
897
|
+
}
|
|
898
|
+
} else {
|
|
899
|
+
ui.stopSpinnerFail(`Failed to annotate ${resource}/${name}`);
|
|
900
|
+
if (result.error) {
|
|
901
|
+
ui.error(result.error);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
} catch (error: any) {
|
|
905
|
+
ui.stopSpinnerFail(`Error annotating ${resource}/${name}`);
|
|
906
|
+
ui.error(error.message);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Main k8s command router
|
|
912
|
+
*/
|
|
913
|
+
export async function k8sCommand(subcommand: string, args: string[]): Promise<void> {
|
|
914
|
+
const options: K8sCommandOptions = {};
|
|
915
|
+
|
|
916
|
+
// Extract positional args and options
|
|
917
|
+
const positionalArgs: string[] = [];
|
|
918
|
+
|
|
919
|
+
for (let i = 0; i < args.length; i++) {
|
|
920
|
+
const arg = args[i];
|
|
921
|
+
if (arg === '-n' || arg === '--namespace') {
|
|
922
|
+
options.namespace = args[++i];
|
|
923
|
+
} else if (arg === '-c' || arg === '--container') {
|
|
924
|
+
options.container = args[++i];
|
|
925
|
+
} else if (arg === '--tail') {
|
|
926
|
+
options.tail = parseInt(args[++i], 10);
|
|
927
|
+
} else if (arg === '--since') {
|
|
928
|
+
options.since = args[++i];
|
|
929
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
930
|
+
options.output = args[++i] as 'json' | 'yaml' | 'wide';
|
|
931
|
+
} else if (arg === '-f' || arg === '--force') {
|
|
932
|
+
options.force = true;
|
|
933
|
+
} else if (arg === '--dry-run') {
|
|
934
|
+
options.dryRun = true;
|
|
935
|
+
} else if (arg === '--yes' || arg === '-y') {
|
|
936
|
+
options.yes = true;
|
|
937
|
+
} else if (arg.startsWith('-l') || arg === '--labels') {
|
|
938
|
+
const labelArg = arg.startsWith('-l=') ? arg.slice(3) : args[++i];
|
|
939
|
+
options.labels = options.labels || {};
|
|
940
|
+
const [key, value] = labelArg.split('=');
|
|
941
|
+
options.labels[key] = value;
|
|
942
|
+
} else if (arg === '--kubeconfig') {
|
|
943
|
+
options.kubeconfig = args[++i];
|
|
944
|
+
} else if (arg === '--context') {
|
|
945
|
+
options.context = args[++i];
|
|
946
|
+
} else if (arg === '--type') {
|
|
947
|
+
options.type = args[++i] as 'strategic' | 'merge' | 'json';
|
|
948
|
+
} else if (arg === '--overwrite') {
|
|
949
|
+
options.overwrite = true;
|
|
950
|
+
} else if (!arg.startsWith('-')) {
|
|
951
|
+
positionalArgs.push(arg);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const startTime = Date.now();
|
|
956
|
+
const entry = historyManager.addEntry('k8s', [subcommand, ...args]);
|
|
957
|
+
|
|
958
|
+
try {
|
|
959
|
+
switch (subcommand) {
|
|
960
|
+
case 'get':
|
|
961
|
+
if (positionalArgs.length < 1) {
|
|
962
|
+
ui.error('Usage: nimbus k8s get <resource> [name]');
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
options.name = positionalArgs[1];
|
|
966
|
+
await k8sGetCommand(positionalArgs[0], options);
|
|
967
|
+
break;
|
|
968
|
+
case 'apply':
|
|
969
|
+
if (positionalArgs.length < 1) {
|
|
970
|
+
ui.error('Usage: nimbus k8s apply <manifest-file-or-yaml>');
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
await k8sApplyCommand(positionalArgs[0], options);
|
|
974
|
+
break;
|
|
975
|
+
case 'delete':
|
|
976
|
+
if (positionalArgs.length < 2) {
|
|
977
|
+
ui.error('Usage: nimbus k8s delete <resource> <name>');
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
await k8sDeleteCommand(positionalArgs[0], positionalArgs[1], options);
|
|
981
|
+
break;
|
|
982
|
+
case 'logs':
|
|
983
|
+
if (positionalArgs.length < 1) {
|
|
984
|
+
ui.error('Usage: nimbus k8s logs <pod-name>');
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
await k8sLogsCommand(positionalArgs[0], options);
|
|
988
|
+
break;
|
|
989
|
+
case 'describe':
|
|
990
|
+
if (positionalArgs.length < 2) {
|
|
991
|
+
ui.error('Usage: nimbus k8s describe <resource> <name>');
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
await k8sDescribeCommand(positionalArgs[0], positionalArgs[1], options);
|
|
995
|
+
break;
|
|
996
|
+
case 'scale':
|
|
997
|
+
if (positionalArgs.length < 3) {
|
|
998
|
+
ui.error('Usage: nimbus k8s scale <resource> <name> <replicas>');
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
await k8sScaleCommand(
|
|
1002
|
+
positionalArgs[0],
|
|
1003
|
+
positionalArgs[1],
|
|
1004
|
+
parseInt(positionalArgs[2], 10),
|
|
1005
|
+
options
|
|
1006
|
+
);
|
|
1007
|
+
break;
|
|
1008
|
+
case 'exec': {
|
|
1009
|
+
if (positionalArgs.length < 1) {
|
|
1010
|
+
ui.error('Usage: nimbus k8s exec <pod> -- <command...>');
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
// Everything after '--' is the command
|
|
1014
|
+
const dashIdx = args.indexOf('--');
|
|
1015
|
+
const execCmd = dashIdx >= 0 ? args.slice(dashIdx + 1) : positionalArgs.slice(1);
|
|
1016
|
+
if (execCmd.length === 0) {
|
|
1017
|
+
ui.error('Usage: nimbus k8s exec <pod> -- <command...>');
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
await k8sExecCommand(positionalArgs[0], execCmd, options);
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
case 'rollout': {
|
|
1024
|
+
if (positionalArgs.length < 2) {
|
|
1025
|
+
ui.error('Usage: nimbus k8s rollout <action> <resource>/<name>');
|
|
1026
|
+
ui.info('Actions: status, history, restart, undo, pause, resume');
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
const rolloutAction = positionalArgs[0] as
|
|
1030
|
+
| 'status'
|
|
1031
|
+
| 'history'
|
|
1032
|
+
| 'restart'
|
|
1033
|
+
| 'undo'
|
|
1034
|
+
| 'pause'
|
|
1035
|
+
| 'resume';
|
|
1036
|
+
const resourceParts = positionalArgs[1].split('/');
|
|
1037
|
+
const rolloutResource = resourceParts.length > 1 ? resourceParts[0] : 'deployment';
|
|
1038
|
+
const rolloutName = resourceParts.length > 1 ? resourceParts[1] : resourceParts[0];
|
|
1039
|
+
await k8sRolloutCommand(rolloutResource, rolloutName, rolloutAction, options);
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
case 'events':
|
|
1043
|
+
await k8sEventsCommand(options);
|
|
1044
|
+
break;
|
|
1045
|
+
case 'generate': {
|
|
1046
|
+
const type = positionalArgs[0] as string | undefined;
|
|
1047
|
+
const { generateK8sCommand } = await import('../generate-k8s');
|
|
1048
|
+
await generateK8sCommand({ workloadType: type as any });
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
case 'port-forward': {
|
|
1052
|
+
if (positionalArgs.length < 2) {
|
|
1053
|
+
ui.error('Usage: nimbus k8s port-forward <pod> <local-port>:<remote-port>');
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
await k8sPortForwardCommand(positionalArgs[0], positionalArgs[1], options);
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
case 'namespace':
|
|
1060
|
+
case 'ns': {
|
|
1061
|
+
const nsAction = (positionalArgs[0] as 'list' | 'create' | 'delete') || 'list';
|
|
1062
|
+
const nsName = positionalArgs[1];
|
|
1063
|
+
if ((nsAction === 'create' || nsAction === 'delete') && !nsName) {
|
|
1064
|
+
ui.error(`Usage: nimbus k8s namespace ${nsAction} <name>`);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
await k8sNamespaceCommand(nsAction, nsName, options);
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1070
|
+
case 'top': {
|
|
1071
|
+
const topResource = (positionalArgs[0] as 'pods' | 'nodes') || 'pods';
|
|
1072
|
+
if (topResource !== 'pods' && topResource !== 'nodes') {
|
|
1073
|
+
ui.error('Usage: nimbus k8s top <pods|nodes>');
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
await k8sTopCommand(topResource, options);
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
case 'patch': {
|
|
1080
|
+
if (positionalArgs.length < 3) {
|
|
1081
|
+
ui.error('Usage: nimbus k8s patch <resource> <name> <patch-json>');
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
await k8sPatchCommand(positionalArgs[0], positionalArgs[1], positionalArgs[2], options);
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
case 'label': {
|
|
1088
|
+
if (positionalArgs.length < 3) {
|
|
1089
|
+
ui.error('Usage: nimbus k8s label <resource> <name> <key=value> [key=value...]');
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
const labelPairs = positionalArgs.slice(2);
|
|
1093
|
+
const labelMap: Record<string, string> = {};
|
|
1094
|
+
for (const pair of labelPairs) {
|
|
1095
|
+
const eqIdx = pair.indexOf('=');
|
|
1096
|
+
if (eqIdx < 0) {
|
|
1097
|
+
ui.error(`Invalid label format: ${pair}. Expected key=value`);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
labelMap[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
|
|
1101
|
+
}
|
|
1102
|
+
await k8sLabelCommand(positionalArgs[0], positionalArgs[1], labelMap, options);
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
case 'annotate': {
|
|
1106
|
+
if (positionalArgs.length < 3) {
|
|
1107
|
+
ui.error('Usage: nimbus k8s annotate <resource> <name> <key=value> [key=value...]');
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const annotationPairs = positionalArgs.slice(2);
|
|
1111
|
+
const annotationMap: Record<string, string> = {};
|
|
1112
|
+
for (const pair of annotationPairs) {
|
|
1113
|
+
const eqIdx = pair.indexOf('=');
|
|
1114
|
+
if (eqIdx < 0) {
|
|
1115
|
+
ui.error(`Invalid annotation format: ${pair}. Expected key=value`);
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
annotationMap[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
|
|
1119
|
+
}
|
|
1120
|
+
await k8sAnnotateCommand(positionalArgs[0], positionalArgs[1], annotationMap, options);
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
default:
|
|
1124
|
+
ui.error(`Unknown k8s subcommand: ${subcommand}`);
|
|
1125
|
+
ui.info(
|
|
1126
|
+
'Available commands: get, apply, delete, logs, describe, scale, exec, rollout, events, generate, port-forward, namespace, top, patch, label, annotate'
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
historyManager.completeEntry(entry.id, 'success', Date.now() - startTime);
|
|
1131
|
+
} catch (error: any) {
|
|
1132
|
+
historyManager.completeEntry(entry.id, 'failure', Date.now() - startTime, {
|
|
1133
|
+
error: error.message,
|
|
1134
|
+
});
|
|
1135
|
+
throw error;
|
|
1136
|
+
}
|
|
1137
|
+
}
|