@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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume Command
|
|
3
|
+
* Resume a task from its last checkpoint
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ui } from '../wizard/ui';
|
|
7
|
+
import { CoreEngineClient } from '../clients/core-engine-client';
|
|
8
|
+
|
|
9
|
+
export interface ResumeOptions {
|
|
10
|
+
taskId?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function resumeCommand(taskIdOrOptions: string | ResumeOptions = {}): Promise<void> {
|
|
14
|
+
const taskId = typeof taskIdOrOptions === 'string' ? taskIdOrOptions : taskIdOrOptions.taskId;
|
|
15
|
+
|
|
16
|
+
if (!taskId) {
|
|
17
|
+
ui.error('Task ID is required. Usage: nimbus resume <task-id>');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
ui.header('Resume Task');
|
|
22
|
+
ui.info(`Task ID: ${taskId}`);
|
|
23
|
+
ui.newLine();
|
|
24
|
+
|
|
25
|
+
const client = new CoreEngineClient();
|
|
26
|
+
|
|
27
|
+
const available = await client.isAvailable();
|
|
28
|
+
if (!available) {
|
|
29
|
+
ui.error('Core Engine service is not available.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ui.startSpinner({ message: 'Resuming task from checkpoint...' });
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const result = await client.resumeTask(taskId);
|
|
37
|
+
|
|
38
|
+
if (result.success) {
|
|
39
|
+
ui.stopSpinnerSuccess('Task resumed and completed successfully');
|
|
40
|
+
ui.newLine();
|
|
41
|
+
if (result.data) {
|
|
42
|
+
const executionResults = result.data.executionResults || [];
|
|
43
|
+
ui.print(`Steps completed: ${executionResults.length}`);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
ui.stopSpinnerFail('Resume failed');
|
|
47
|
+
ui.error(result.error || 'Unknown error');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
} catch (error: any) {
|
|
51
|
+
ui.stopSpinnerFail('Resume failed');
|
|
52
|
+
ui.error(error.message || 'Failed to resume task');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Team Commands
|
|
3
|
+
* Team collaboration CLI commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ui } from '../../wizard/ui';
|
|
7
|
+
import { teamClient } from '../../clients/enterprise-client';
|
|
8
|
+
import { getAuthStore } from '../../auth';
|
|
9
|
+
import type {
|
|
10
|
+
TeamCreateOptions,
|
|
11
|
+
TeamInviteOptions,
|
|
12
|
+
TeamMembersOptions,
|
|
13
|
+
TeamRemoveOptions,
|
|
14
|
+
TeamSwitchOptions,
|
|
15
|
+
TeamRole,
|
|
16
|
+
} from '../../types';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get current user ID from auth store
|
|
20
|
+
*/
|
|
21
|
+
function getCurrentUserId(): string {
|
|
22
|
+
const authStore = getAuthStore();
|
|
23
|
+
const auth = authStore.load();
|
|
24
|
+
const userId = auth?.identity?.github?.username;
|
|
25
|
+
if (!userId) {
|
|
26
|
+
throw new Error('Not authenticated. Run `nimbus login` first.');
|
|
27
|
+
}
|
|
28
|
+
return userId;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get current team ID from config or environment
|
|
33
|
+
*/
|
|
34
|
+
function getCurrentTeamId(): string | null {
|
|
35
|
+
return process.env.NIMBUS_TEAM_ID || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse team create options
|
|
40
|
+
*/
|
|
41
|
+
export function parseTeamCreateOptions(args: string[]): TeamCreateOptions {
|
|
42
|
+
const options: TeamCreateOptions = {};
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < args.length; i++) {
|
|
45
|
+
const arg = args[i];
|
|
46
|
+
if (arg === '--non-interactive') {
|
|
47
|
+
options.nonInteractive = true;
|
|
48
|
+
} else if (!arg.startsWith('-') && !options.name) {
|
|
49
|
+
options.name = arg;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return options;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse team invite options
|
|
58
|
+
*/
|
|
59
|
+
export function parseTeamInviteOptions(args: string[]): TeamInviteOptions {
|
|
60
|
+
const options: TeamInviteOptions = {};
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < args.length; i++) {
|
|
63
|
+
const arg = args[i];
|
|
64
|
+
if (arg === '--role' && args[i + 1]) {
|
|
65
|
+
options.role = args[++i] as TeamRole;
|
|
66
|
+
} else if (arg === '--non-interactive') {
|
|
67
|
+
options.nonInteractive = true;
|
|
68
|
+
} else if (!arg.startsWith('-') && !options.email) {
|
|
69
|
+
options.email = arg;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return options;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse team members options
|
|
78
|
+
*/
|
|
79
|
+
export function parseTeamMembersOptions(args: string[]): TeamMembersOptions {
|
|
80
|
+
const options: TeamMembersOptions = {};
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < args.length; i++) {
|
|
83
|
+
const arg = args[i];
|
|
84
|
+
if (arg === '--json') {
|
|
85
|
+
options.json = true;
|
|
86
|
+
} else if (arg === '--non-interactive') {
|
|
87
|
+
options.nonInteractive = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return options;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parse team remove options
|
|
96
|
+
*/
|
|
97
|
+
export function parseTeamRemoveOptions(args: string[]): TeamRemoveOptions {
|
|
98
|
+
const options: TeamRemoveOptions = {};
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < args.length; i++) {
|
|
101
|
+
const arg = args[i];
|
|
102
|
+
if (arg === '--force' || arg === '-f') {
|
|
103
|
+
options.force = true;
|
|
104
|
+
} else if (arg === '--non-interactive') {
|
|
105
|
+
options.nonInteractive = true;
|
|
106
|
+
} else if (!arg.startsWith('-') && !options.email) {
|
|
107
|
+
options.email = arg;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return options;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parse team switch options
|
|
116
|
+
*/
|
|
117
|
+
export function parseTeamSwitchOptions(args: string[]): TeamSwitchOptions {
|
|
118
|
+
const options: TeamSwitchOptions = {};
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < args.length; i++) {
|
|
121
|
+
const arg = args[i];
|
|
122
|
+
if (arg === '--non-interactive') {
|
|
123
|
+
options.nonInteractive = true;
|
|
124
|
+
} else if (!arg.startsWith('-') && !options.teamId) {
|
|
125
|
+
options.teamId = arg;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return options;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Team create command
|
|
134
|
+
*/
|
|
135
|
+
export async function teamCreateCommand(options: TeamCreateOptions): Promise<void> {
|
|
136
|
+
try {
|
|
137
|
+
const name = options.name;
|
|
138
|
+
if (!name) {
|
|
139
|
+
ui.error('Team name is required');
|
|
140
|
+
ui.info('Usage: nimbus team create <name>');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const userId = getCurrentUserId();
|
|
145
|
+
|
|
146
|
+
ui.startSpinner({ message: 'Creating team...' });
|
|
147
|
+
const team = await teamClient.createTeam({ name, ownerId: userId });
|
|
148
|
+
ui.stopSpinnerSuccess(`Team "${team.name}" created`);
|
|
149
|
+
|
|
150
|
+
ui.newLine();
|
|
151
|
+
ui.info(`Team ID: ${team.id}`);
|
|
152
|
+
ui.info(`To use this team, run: nimbus team switch ${team.id}`);
|
|
153
|
+
ui.info(`Or set environment variable: export NIMBUS_TEAM_ID=${team.id}`);
|
|
154
|
+
} catch (error: any) {
|
|
155
|
+
ui.stopSpinnerFail('Failed to create team');
|
|
156
|
+
ui.error(error.message);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Team invite command
|
|
162
|
+
*/
|
|
163
|
+
export async function teamInviteCommand(options: TeamInviteOptions): Promise<void> {
|
|
164
|
+
try {
|
|
165
|
+
const email = options.email;
|
|
166
|
+
if (!email) {
|
|
167
|
+
ui.error('Email is required');
|
|
168
|
+
ui.info('Usage: nimbus team invite <email> [--role member|admin|viewer]');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const teamId = getCurrentTeamId();
|
|
173
|
+
if (!teamId) {
|
|
174
|
+
ui.error('No team selected. Run `nimbus team switch <team-id>` first.');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ui.startSpinner({ message: `Inviting ${email}...` });
|
|
179
|
+
const member = await teamClient.inviteMember(teamId, {
|
|
180
|
+
email,
|
|
181
|
+
role: options.role || 'member',
|
|
182
|
+
});
|
|
183
|
+
ui.stopSpinnerSuccess(`Invited ${email} as ${member.role}`);
|
|
184
|
+
} catch (error: any) {
|
|
185
|
+
ui.stopSpinnerFail('Failed to invite member');
|
|
186
|
+
ui.error(error.message);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Team members command
|
|
192
|
+
*/
|
|
193
|
+
export async function teamMembersCommand(options: TeamMembersOptions): Promise<void> {
|
|
194
|
+
try {
|
|
195
|
+
const teamId = getCurrentTeamId();
|
|
196
|
+
if (!teamId) {
|
|
197
|
+
ui.error('No team selected. Run `nimbus team switch <team-id>` first.');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
ui.startSpinner({ message: 'Fetching members...' });
|
|
202
|
+
const members = await teamClient.listMembers(teamId);
|
|
203
|
+
ui.stopSpinnerSuccess(`Found ${members.length} members`);
|
|
204
|
+
|
|
205
|
+
if (options.json) {
|
|
206
|
+
console.log(JSON.stringify(members, null, 2));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
ui.newLine();
|
|
211
|
+
ui.table({
|
|
212
|
+
columns: [
|
|
213
|
+
{ key: 'email', header: 'Email' },
|
|
214
|
+
{ key: 'role', header: 'Role' },
|
|
215
|
+
{ key: 'joinedAt', header: 'Joined' },
|
|
216
|
+
],
|
|
217
|
+
data: members.map(m => ({
|
|
218
|
+
email: m.user?.email || m.userId,
|
|
219
|
+
role: m.role,
|
|
220
|
+
joinedAt: new Date(m.joinedAt).toLocaleDateString(),
|
|
221
|
+
})),
|
|
222
|
+
});
|
|
223
|
+
} catch (error: any) {
|
|
224
|
+
ui.stopSpinnerFail('Failed to list members');
|
|
225
|
+
ui.error(error.message);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Team remove command
|
|
231
|
+
*/
|
|
232
|
+
export async function teamRemoveCommand(options: TeamRemoveOptions): Promise<void> {
|
|
233
|
+
try {
|
|
234
|
+
const email = options.email;
|
|
235
|
+
if (!email) {
|
|
236
|
+
ui.error('Email is required');
|
|
237
|
+
ui.info('Usage: nimbus team remove <email>');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const teamId = getCurrentTeamId();
|
|
242
|
+
if (!teamId) {
|
|
243
|
+
ui.error('No team selected. Run `nimbus team switch <team-id>` first.');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// First, find the user ID by email from members list
|
|
248
|
+
const members = await teamClient.listMembers(teamId);
|
|
249
|
+
const member = members.find(m => m.user?.email === email);
|
|
250
|
+
|
|
251
|
+
if (!member) {
|
|
252
|
+
ui.error(`Member with email ${email} not found`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
ui.startSpinner({ message: `Removing ${email}...` });
|
|
257
|
+
await teamClient.removeMember(teamId, member.userId);
|
|
258
|
+
ui.stopSpinnerSuccess(`Removed ${email} from team`);
|
|
259
|
+
} catch (error: any) {
|
|
260
|
+
ui.stopSpinnerFail('Failed to remove member');
|
|
261
|
+
ui.error(error.message);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Team switch command
|
|
267
|
+
*/
|
|
268
|
+
export async function teamSwitchCommand(options: TeamSwitchOptions): Promise<void> {
|
|
269
|
+
try {
|
|
270
|
+
const userId = getCurrentUserId();
|
|
271
|
+
|
|
272
|
+
// If no team ID provided, list teams for selection
|
|
273
|
+
if (!options.teamId) {
|
|
274
|
+
ui.startSpinner({ message: 'Fetching teams...' });
|
|
275
|
+
const teams = await teamClient.listTeams(userId);
|
|
276
|
+
ui.stopSpinnerSuccess(`Found ${teams.length} teams`);
|
|
277
|
+
|
|
278
|
+
if (teams.length === 0) {
|
|
279
|
+
ui.info('No teams found. Create one with `nimbus team create <name>`');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
ui.newLine();
|
|
284
|
+
ui.info('Available teams:');
|
|
285
|
+
for (const team of teams) {
|
|
286
|
+
ui.print(` ${team.id} - ${team.name} (${team.plan})`);
|
|
287
|
+
}
|
|
288
|
+
ui.newLine();
|
|
289
|
+
ui.info('To switch: nimbus team switch <team-id>');
|
|
290
|
+
ui.info('Or set: export NIMBUS_TEAM_ID=<team-id>');
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Verify team exists and user has access
|
|
295
|
+
ui.startSpinner({ message: 'Switching team...' });
|
|
296
|
+
const team = await teamClient.getTeam(options.teamId);
|
|
297
|
+
if (!team) {
|
|
298
|
+
ui.stopSpinnerFail('Team not found');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
ui.stopSpinnerSuccess(`Switched to team "${team.name}"`);
|
|
303
|
+
ui.newLine();
|
|
304
|
+
ui.info(`Set this environment variable to persist:`);
|
|
305
|
+
ui.print(` export NIMBUS_TEAM_ID=${team.id}`);
|
|
306
|
+
} catch (error: any) {
|
|
307
|
+
ui.stopSpinnerFail('Failed to switch team');
|
|
308
|
+
ui.error(error.message);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Main team command dispatcher
|
|
314
|
+
*/
|
|
315
|
+
export async function teamCommand(subcommand: string, args: string[]): Promise<void> {
|
|
316
|
+
switch (subcommand) {
|
|
317
|
+
case 'create':
|
|
318
|
+
await teamCreateCommand(parseTeamCreateOptions(args));
|
|
319
|
+
break;
|
|
320
|
+
case 'invite':
|
|
321
|
+
await teamInviteCommand(parseTeamInviteOptions(args));
|
|
322
|
+
break;
|
|
323
|
+
case 'members':
|
|
324
|
+
await teamMembersCommand(parseTeamMembersOptions(args));
|
|
325
|
+
break;
|
|
326
|
+
case 'remove':
|
|
327
|
+
await teamRemoveCommand(parseTeamRemoveOptions(args));
|
|
328
|
+
break;
|
|
329
|
+
case 'switch':
|
|
330
|
+
await teamSwitchCommand(parseTeamSwitchOptions(args));
|
|
331
|
+
break;
|
|
332
|
+
case 'list':
|
|
333
|
+
await teamSwitchCommand(parseTeamSwitchOptions([])); // List mode
|
|
334
|
+
break;
|
|
335
|
+
default:
|
|
336
|
+
ui.error(`Unknown team command: ${subcommand}`);
|
|
337
|
+
ui.newLine();
|
|
338
|
+
ui.info('Available team commands:');
|
|
339
|
+
ui.print(' nimbus team create <name> - Create a new team');
|
|
340
|
+
ui.print(' nimbus team invite <email> - Invite a member');
|
|
341
|
+
ui.print(' nimbus team members - List team members');
|
|
342
|
+
ui.print(' nimbus team remove <email> - Remove a member');
|
|
343
|
+
ui.print(' nimbus team switch [team-id] - Switch to a team');
|
|
344
|
+
ui.print(' nimbus team list - List your teams');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for managing infrastructure templates
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { RestClient } from '../clients';
|
|
8
|
+
import { ui } from '../wizard/ui';
|
|
9
|
+
|
|
10
|
+
const STATE_SERVICE_URL = process.env.STATE_SERVICE_URL || 'http://localhost:3004';
|
|
11
|
+
|
|
12
|
+
export interface TemplateCommandOptions {
|
|
13
|
+
type?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
file?: string;
|
|
16
|
+
json?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* List all templates
|
|
21
|
+
*/
|
|
22
|
+
async function templateListCommand(options: TemplateCommandOptions = {}): Promise<void> {
|
|
23
|
+
ui.header('Templates');
|
|
24
|
+
ui.startSpinner({ message: 'Fetching templates...' });
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const client = new RestClient(STATE_SERVICE_URL);
|
|
28
|
+
const params = options.type ? `?type=${encodeURIComponent(options.type)}` : '';
|
|
29
|
+
const result = await client.get<any>(`/api/state/templates${params}`);
|
|
30
|
+
|
|
31
|
+
if (result.success && result.data) {
|
|
32
|
+
const templates = Array.isArray(result.data) ? result.data : [];
|
|
33
|
+
ui.stopSpinnerSuccess(`Found ${templates.length} template(s)`);
|
|
34
|
+
|
|
35
|
+
if (templates.length > 0) {
|
|
36
|
+
ui.table({
|
|
37
|
+
columns: [
|
|
38
|
+
{ key: 'id', header: 'ID' },
|
|
39
|
+
{ key: 'name', header: 'Name' },
|
|
40
|
+
{ key: 'type', header: 'Type' },
|
|
41
|
+
{ key: 'createdAt', header: 'Created' },
|
|
42
|
+
],
|
|
43
|
+
data: templates.map((t: any) => ({
|
|
44
|
+
id: t.id?.substring(0, 8) || '-',
|
|
45
|
+
name: t.name || '-',
|
|
46
|
+
type: t.type || '-',
|
|
47
|
+
createdAt: t.createdAt ? new Date(t.createdAt).toLocaleDateString() : '-',
|
|
48
|
+
})),
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
ui.info('No templates found. Use "nimbus template save" to create one.');
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
ui.stopSpinnerFail('Failed to fetch templates');
|
|
55
|
+
}
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
ui.stopSpinnerFail('Error fetching templates');
|
|
58
|
+
ui.error(error.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get a specific template by ID
|
|
64
|
+
*/
|
|
65
|
+
async function templateGetCommand(
|
|
66
|
+
id: string,
|
|
67
|
+
_options: TemplateCommandOptions = {}
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
ui.header(`Template: ${id}`);
|
|
70
|
+
ui.startSpinner({ message: 'Fetching template...' });
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const client = new RestClient(STATE_SERVICE_URL);
|
|
74
|
+
const result = await client.get<any>(`/api/state/templates/${encodeURIComponent(id)}`);
|
|
75
|
+
|
|
76
|
+
if (result.success && result.data) {
|
|
77
|
+
ui.stopSpinnerSuccess('Template retrieved');
|
|
78
|
+
const template = result.data;
|
|
79
|
+
|
|
80
|
+
ui.print(` ${ui.color('Name:', 'cyan')} ${template.name || '-'}`);
|
|
81
|
+
ui.print(` ${ui.color('Type:', 'cyan')} ${template.type || '-'}`);
|
|
82
|
+
ui.print(` ${ui.color('ID:', 'cyan')} ${template.id || '-'}`);
|
|
83
|
+
ui.print(` ${ui.color('Created:', 'cyan')} ${template.createdAt || '-'}`);
|
|
84
|
+
|
|
85
|
+
if (template.variables && Object.keys(template.variables).length > 0) {
|
|
86
|
+
ui.newLine();
|
|
87
|
+
ui.print(` ${ui.color('Variables:', 'cyan')}`);
|
|
88
|
+
for (const [key, val] of Object.entries(template.variables)) {
|
|
89
|
+
ui.print(` ${key}: ${JSON.stringify(val)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (template.content) {
|
|
94
|
+
ui.newLine();
|
|
95
|
+
ui.box({
|
|
96
|
+
title: 'Content',
|
|
97
|
+
content:
|
|
98
|
+
typeof template.content === 'string'
|
|
99
|
+
? template.content
|
|
100
|
+
: JSON.stringify(template.content, null, 2),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
ui.stopSpinnerFail('Template not found');
|
|
105
|
+
}
|
|
106
|
+
} catch (error: any) {
|
|
107
|
+
ui.stopSpinnerFail('Error fetching template');
|
|
108
|
+
ui.error(error.message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Save a new template
|
|
114
|
+
*/
|
|
115
|
+
async function templateSaveCommand(options: TemplateCommandOptions = {}): Promise<void> {
|
|
116
|
+
ui.header('Save Template');
|
|
117
|
+
|
|
118
|
+
if (!options.name) {
|
|
119
|
+
ui.error('Template name is required. Use --name <name>');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let content = '';
|
|
124
|
+
if (options.file) {
|
|
125
|
+
try {
|
|
126
|
+
const fs = await import('fs');
|
|
127
|
+
content = fs.readFileSync(options.file, 'utf-8');
|
|
128
|
+
} catch (err: any) {
|
|
129
|
+
ui.error(`Failed to read file: ${err.message}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
ui.startSpinner({ message: 'Saving template...' });
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const client = new RestClient(STATE_SERVICE_URL);
|
|
138
|
+
const result = await client.post<any>('/api/state/templates', {
|
|
139
|
+
name: options.name,
|
|
140
|
+
type: options.type || 'terraform',
|
|
141
|
+
content,
|
|
142
|
+
variables: {},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (result.success) {
|
|
146
|
+
ui.stopSpinnerSuccess(`Template "${options.name}" saved`);
|
|
147
|
+
if (result.data?.id) {
|
|
148
|
+
ui.info(`ID: ${result.data.id}`);
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
ui.stopSpinnerFail('Failed to save template');
|
|
152
|
+
}
|
|
153
|
+
} catch (error: any) {
|
|
154
|
+
ui.stopSpinnerFail('Error saving template');
|
|
155
|
+
ui.error(error.message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Delete a template by ID
|
|
161
|
+
*/
|
|
162
|
+
async function templateDeleteCommand(id: string): Promise<void> {
|
|
163
|
+
ui.header(`Delete Template: ${id}`);
|
|
164
|
+
ui.startSpinner({ message: 'Deleting template...' });
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const client = new RestClient(STATE_SERVICE_URL);
|
|
168
|
+
const result = await client.delete<any>(`/api/state/templates/${encodeURIComponent(id)}`);
|
|
169
|
+
|
|
170
|
+
if (result.success) {
|
|
171
|
+
ui.stopSpinnerSuccess('Template deleted');
|
|
172
|
+
} else {
|
|
173
|
+
ui.stopSpinnerFail('Failed to delete template');
|
|
174
|
+
}
|
|
175
|
+
} catch (error: any) {
|
|
176
|
+
ui.stopSpinnerFail('Error deleting template');
|
|
177
|
+
ui.error(error.message);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Main template command router
|
|
183
|
+
*/
|
|
184
|
+
export async function templateCommand(subcommand: string, args: string[]): Promise<void> {
|
|
185
|
+
const options: TemplateCommandOptions = {};
|
|
186
|
+
const positionalArgs: string[] = [];
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < args.length; i++) {
|
|
189
|
+
const arg = args[i];
|
|
190
|
+
if (arg === '--type' || arg === '-t') {
|
|
191
|
+
options.type = args[++i];
|
|
192
|
+
} else if (arg === '--name' || arg === '-n') {
|
|
193
|
+
options.name = args[++i];
|
|
194
|
+
} else if (arg === '--file' || arg === '-f') {
|
|
195
|
+
options.file = args[++i];
|
|
196
|
+
} else if (arg === '--json') {
|
|
197
|
+
options.json = true;
|
|
198
|
+
} else if (!arg.startsWith('-')) {
|
|
199
|
+
positionalArgs.push(arg);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
switch (subcommand) {
|
|
204
|
+
case 'list':
|
|
205
|
+
case 'ls':
|
|
206
|
+
await templateListCommand(options);
|
|
207
|
+
break;
|
|
208
|
+
case 'get':
|
|
209
|
+
if (positionalArgs.length < 1) {
|
|
210
|
+
ui.error('Usage: nimbus template get <id>');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
await templateGetCommand(positionalArgs[0], options);
|
|
214
|
+
break;
|
|
215
|
+
case 'save':
|
|
216
|
+
await templateSaveCommand(options);
|
|
217
|
+
break;
|
|
218
|
+
case 'delete':
|
|
219
|
+
case 'rm':
|
|
220
|
+
if (positionalArgs.length < 1) {
|
|
221
|
+
ui.error('Usage: nimbus template delete <id>');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
await templateDeleteCommand(positionalArgs[0]);
|
|
225
|
+
break;
|
|
226
|
+
default:
|
|
227
|
+
ui.error(`Unknown template subcommand: ${subcommand || '(none)'}`);
|
|
228
|
+
ui.info(
|
|
229
|
+
'Available commands: list, get <id>, save --name <name> [--file <path>], delete <id>'
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|