@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,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade Command
|
|
3
|
+
*
|
|
4
|
+
* Checks for the latest Nimbus version and offers to upgrade.
|
|
5
|
+
* Supports npm registry, GitHub Releases binary download, and Homebrew.
|
|
6
|
+
*
|
|
7
|
+
* Detects the installation method and executes the appropriate upgrade
|
|
8
|
+
* command automatically (with user confirmation unless --force is passed).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { VERSION } from '../version';
|
|
12
|
+
|
|
13
|
+
/** GitHub repository used for release downloads. */
|
|
14
|
+
const GITHUB_REPO = 'the-ai-project-co/nimbus';
|
|
15
|
+
|
|
16
|
+
/** npm package name (may not be published yet). */
|
|
17
|
+
const NPM_PACKAGE = '@build-astron-co/nimbus';
|
|
18
|
+
|
|
19
|
+
/** Homebrew tap name for the formula. */
|
|
20
|
+
const HOMEBREW_TAP = 'the-ai-project-co/tap/nimbus';
|
|
21
|
+
|
|
22
|
+
export interface UpgradeOptions {
|
|
23
|
+
/** Skip confirmation prompt */
|
|
24
|
+
force?: boolean;
|
|
25
|
+
/** Check only, don't actually upgrade */
|
|
26
|
+
check?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Detected installation method for nimbus. */
|
|
30
|
+
type InstallMethod = 'homebrew' | 'npm' | 'bun' | 'binary' | 'unknown';
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// ANSI helpers (avoid importing the full wizard/ui for this standalone command)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
const c = {
|
|
37
|
+
reset: '\x1b[0m',
|
|
38
|
+
bold: '\x1b[1m',
|
|
39
|
+
dim: '\x1b[2m',
|
|
40
|
+
green: '\x1b[32m',
|
|
41
|
+
yellow: '\x1b[33m',
|
|
42
|
+
red: '\x1b[31m',
|
|
43
|
+
cyan: '\x1b[36m',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function bold(s: string): string {
|
|
47
|
+
return `${c.bold}${s}${c.reset}`;
|
|
48
|
+
}
|
|
49
|
+
function green(s: string): string {
|
|
50
|
+
return `${c.green}${s}${c.reset}`;
|
|
51
|
+
}
|
|
52
|
+
function yellow(s: string): string {
|
|
53
|
+
return `${c.yellow}${s}${c.reset}`;
|
|
54
|
+
}
|
|
55
|
+
function red(s: string): string {
|
|
56
|
+
return `${c.red}${s}${c.reset}`;
|
|
57
|
+
}
|
|
58
|
+
function dim(s: string): string {
|
|
59
|
+
return `${c.dim}${s}${c.reset}`;
|
|
60
|
+
}
|
|
61
|
+
function cyan(s: string): string {
|
|
62
|
+
return `${c.cyan}${s}${c.reset}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Spinner
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
const SPINNER_FRAMES = [
|
|
70
|
+
'\u28CB',
|
|
71
|
+
'\u2819',
|
|
72
|
+
'\u2839',
|
|
73
|
+
'\u2838',
|
|
74
|
+
'\u283C',
|
|
75
|
+
'\u2834',
|
|
76
|
+
'\u2826',
|
|
77
|
+
'\u2827',
|
|
78
|
+
'\u2807',
|
|
79
|
+
'\u280F',
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
interface Spinner {
|
|
83
|
+
update(msg: string): void;
|
|
84
|
+
success(msg: string): void;
|
|
85
|
+
fail(msg: string): void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function startSpinner(message: string): Spinner {
|
|
89
|
+
let frame = 0;
|
|
90
|
+
let currentMsg = message;
|
|
91
|
+
|
|
92
|
+
const interval = setInterval(() => {
|
|
93
|
+
const f = cyan(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
|
|
94
|
+
process.stderr.write(`\r\x1b[K ${f} ${currentMsg}`);
|
|
95
|
+
frame++;
|
|
96
|
+
}, 80);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
update(msg: string) {
|
|
100
|
+
currentMsg = msg;
|
|
101
|
+
},
|
|
102
|
+
success(msg: string) {
|
|
103
|
+
clearInterval(interval);
|
|
104
|
+
process.stderr.write(`\r\x1b[K ${green('\u2714')} ${msg}\n`);
|
|
105
|
+
},
|
|
106
|
+
fail(msg: string) {
|
|
107
|
+
clearInterval(interval);
|
|
108
|
+
process.stderr.write(`\r\x1b[K ${red('\u2716')} ${msg}\n`);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Confirmation prompt
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
async function confirmPrompt(message: string, defaultYes = true): Promise<boolean> {
|
|
118
|
+
const { createInterface } = await import('node:readline');
|
|
119
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
120
|
+
const hint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
121
|
+
|
|
122
|
+
return new Promise(resolve => {
|
|
123
|
+
rl.question(` ${message} ${hint} `, answer => {
|
|
124
|
+
rl.close();
|
|
125
|
+
const trimmed = answer.trim().toLowerCase();
|
|
126
|
+
if (!trimmed) {
|
|
127
|
+
resolve(defaultYes);
|
|
128
|
+
} else {
|
|
129
|
+
resolve(trimmed === 'y' || trimmed === 'yes');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Version fetching
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Try fetching the latest version from the npm registry.
|
|
141
|
+
* Returns the version string on success, or `null` when the package is not
|
|
142
|
+
* published or the registry is unreachable.
|
|
143
|
+
*/
|
|
144
|
+
async function fetchNpmVersion(): Promise<string | null> {
|
|
145
|
+
try {
|
|
146
|
+
const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE}/latest`, {
|
|
147
|
+
signal: AbortSignal.timeout(5000),
|
|
148
|
+
});
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const data = (await response.json()) as { version?: string };
|
|
153
|
+
return data.version ?? null;
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Try fetching the latest release tag from GitHub Releases.
|
|
161
|
+
* Returns the tag name (e.g. `'v0.3.0'`) on success, or `null` when the
|
|
162
|
+
* API is unreachable or there are no releases.
|
|
163
|
+
*/
|
|
164
|
+
async function fetchGitHubReleaseVersion(): Promise<string | null> {
|
|
165
|
+
try {
|
|
166
|
+
const response = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`, {
|
|
167
|
+
headers: { Accept: 'application/vnd.github+json' },
|
|
168
|
+
signal: AbortSignal.timeout(5000),
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const data = (await response.json()) as { tag_name?: string };
|
|
174
|
+
// Strip leading 'v' from tag name (e.g. 'v0.3.0' -> '0.3.0')
|
|
175
|
+
const tag = data.tag_name ?? null;
|
|
176
|
+
return tag ? tag.replace(/^v/, '') : null;
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Installation method detection
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
interface DetectionResult {
|
|
187
|
+
method: InstallMethod;
|
|
188
|
+
/** Human-readable description of what was detected. */
|
|
189
|
+
detail: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Detect how nimbus was installed by probing Homebrew, npm, bun, and the
|
|
194
|
+
* binary path. Detection commands are given a short timeout so they never
|
|
195
|
+
* block the CLI for too long.
|
|
196
|
+
*/
|
|
197
|
+
async function detectInstallMethod(): Promise<DetectionResult> {
|
|
198
|
+
const { execSync } = await import('node:child_process');
|
|
199
|
+
const execOpts = {
|
|
200
|
+
encoding: 'utf-8' as const,
|
|
201
|
+
stdio: ['pipe', 'pipe', 'pipe'] as ['pipe', 'pipe', 'pipe'],
|
|
202
|
+
timeout: 10_000,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// 1. Homebrew
|
|
206
|
+
try {
|
|
207
|
+
const brewList = execSync('brew list --formula 2>/dev/null', execOpts);
|
|
208
|
+
if (brewList.includes('nimbus')) {
|
|
209
|
+
return { method: 'homebrew', detail: `Homebrew formula (${HOMEBREW_TAP})` };
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
/* not homebrew */
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 2. npm global
|
|
216
|
+
try {
|
|
217
|
+
const npmList = execSync(`npm list -g ${NPM_PACKAGE} 2>/dev/null`, execOpts);
|
|
218
|
+
if (npmList.includes(NPM_PACKAGE)) {
|
|
219
|
+
return { method: 'npm', detail: `npm global package (${NPM_PACKAGE})` };
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
/* not npm */
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 3. bun global
|
|
226
|
+
try {
|
|
227
|
+
const bunList = execSync('bun pm ls -g 2>/dev/null', execOpts);
|
|
228
|
+
if (bunList.includes(NPM_PACKAGE)) {
|
|
229
|
+
return { method: 'bun', detail: `bun global package (${NPM_PACKAGE})` };
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
/* not bun */
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 4. Compiled binary (not running through bun/node)
|
|
236
|
+
const argv0 = process.argv[0] ?? '';
|
|
237
|
+
const isBinary = !argv0.includes('bun') && !argv0.includes('node');
|
|
238
|
+
if (isBinary) {
|
|
239
|
+
return { method: 'binary', detail: `standalone binary (${argv0 || '/usr/local/bin/nimbus'})` };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { method: 'unknown', detail: 'could not determine installation method' };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Upgrade execution helpers
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Run a shell command, streaming output to the terminal.
|
|
251
|
+
* Throws on non-zero exit code.
|
|
252
|
+
*/
|
|
253
|
+
async function runShellCommand(cmd: string, timeoutMs = 120_000): Promise<void> {
|
|
254
|
+
const { execSync } = await import('node:child_process');
|
|
255
|
+
execSync(cmd, { stdio: 'inherit', timeout: timeoutMs });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Verify the upgrade by checking the installed version.
|
|
260
|
+
* Returns the new version string, or null if verification failed.
|
|
261
|
+
*/
|
|
262
|
+
async function verifyUpgrade(): Promise<string | null> {
|
|
263
|
+
const { execSync } = await import('node:child_process');
|
|
264
|
+
try {
|
|
265
|
+
const output = execSync('nimbus --version 2>/dev/null', {
|
|
266
|
+
encoding: 'utf-8' as const,
|
|
267
|
+
stdio: ['pipe', 'pipe', 'pipe'] as ['pipe', 'pipe', 'pipe'],
|
|
268
|
+
timeout: 10_000,
|
|
269
|
+
});
|
|
270
|
+
// Output looks like "nimbus 0.3.0" or just "0.3.0"
|
|
271
|
+
const match = output.trim().match(/(\d+\.\d+\.\d+)/);
|
|
272
|
+
return match ? match[1] : null;
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Execute the upgrade via Homebrew.
|
|
280
|
+
*/
|
|
281
|
+
async function upgradeViaHomebrew(spinner: Spinner): Promise<void> {
|
|
282
|
+
spinner.update('Updating Homebrew tap...');
|
|
283
|
+
try {
|
|
284
|
+
await runShellCommand(`brew upgrade ${HOMEBREW_TAP} 2>&1`);
|
|
285
|
+
} catch {
|
|
286
|
+
// If the tap formula isn't found, try the short name
|
|
287
|
+
spinner.update('Trying brew upgrade nimbus...');
|
|
288
|
+
await runShellCommand('brew upgrade nimbus 2>&1');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Execute the upgrade via npm.
|
|
294
|
+
*/
|
|
295
|
+
async function upgradeViaNpm(spinner: Spinner): Promise<void> {
|
|
296
|
+
spinner.update(`Installing ${NPM_PACKAGE}@latest via npm...`);
|
|
297
|
+
await runShellCommand(`npm install -g ${NPM_PACKAGE}@latest 2>&1`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Execute the upgrade via bun.
|
|
302
|
+
*/
|
|
303
|
+
async function upgradeViaBun(spinner: Spinner): Promise<void> {
|
|
304
|
+
spinner.update(`Installing ${NPM_PACKAGE}@latest via bun...`);
|
|
305
|
+
await runShellCommand(`bun install -g ${NPM_PACKAGE}@latest 2>&1`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Execute the upgrade by downloading a compiled binary from GitHub Releases.
|
|
310
|
+
*/
|
|
311
|
+
async function upgradeViaBinaryDownload(latestVersion: string, spinner: Spinner): Promise<void> {
|
|
312
|
+
const platform =
|
|
313
|
+
process.platform === 'darwin' ? 'darwin' : process.platform === 'linux' ? 'linux' : null;
|
|
314
|
+
const arch = process.arch === 'arm64' ? 'arm64' : process.arch === 'x64' ? 'x64' : null;
|
|
315
|
+
|
|
316
|
+
if (!platform || !arch) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Unsupported platform/architecture: ${process.platform}/${process.arch}. ` +
|
|
319
|
+
`Please download the binary manually from https://github.com/${GITHUB_REPO}/releases/latest`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const assetName = `nimbus-${platform}-${arch}`;
|
|
324
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latestVersion}/${assetName}`;
|
|
325
|
+
const binaryPath = process.argv[0] || '/usr/local/bin/nimbus';
|
|
326
|
+
const tmpPath = `${binaryPath}.upgrade-tmp`;
|
|
327
|
+
|
|
328
|
+
spinner.update(`Downloading ${assetName} v${latestVersion}...`);
|
|
329
|
+
|
|
330
|
+
// Download to a temp file, make executable, then atomically replace
|
|
331
|
+
const cmd = [
|
|
332
|
+
`curl -fsSL "${downloadUrl}" -o "${tmpPath}"`,
|
|
333
|
+
`chmod +x "${tmpPath}"`,
|
|
334
|
+
`mv "${tmpPath}" "${binaryPath}"`,
|
|
335
|
+
].join(' && ');
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
await runShellCommand(cmd, 120_000);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
// Clean up temp file on failure
|
|
341
|
+
try {
|
|
342
|
+
const fs = await import('node:fs');
|
|
343
|
+
if (fs.existsSync(tmpPath)) {
|
|
344
|
+
fs.unlinkSync(tmpPath);
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
/* best effort */
|
|
348
|
+
}
|
|
349
|
+
throw err;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
// Main upgrade command
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Check for and install the latest version of Nimbus.
|
|
359
|
+
*/
|
|
360
|
+
export async function upgradeCommand(options: UpgradeOptions = {}): Promise<void> {
|
|
361
|
+
console.log(`Current version: ${bold(VERSION)}`);
|
|
362
|
+
console.log('Checking for updates...\n');
|
|
363
|
+
|
|
364
|
+
// Try npm registry first, then fall back to GitHub Releases
|
|
365
|
+
let latestVersion = await fetchNpmVersion();
|
|
366
|
+
let source: 'npm' | 'github' | 'none' = latestVersion ? 'npm' : 'none';
|
|
367
|
+
|
|
368
|
+
if (!latestVersion) {
|
|
369
|
+
latestVersion = await fetchGitHubReleaseVersion();
|
|
370
|
+
if (latestVersion) {
|
|
371
|
+
source = 'github';
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!latestVersion) {
|
|
376
|
+
console.log(
|
|
377
|
+
'No updates available (package not yet published to npm, and no GitHub releases found).'
|
|
378
|
+
);
|
|
379
|
+
console.log(`You are on version ${VERSION}.`);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (latestVersion === VERSION) {
|
|
384
|
+
console.log(green(`You're already on the latest version (${VERSION}).`));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Simple semver comparison to check if remote is actually newer
|
|
389
|
+
const currentParts = VERSION.split('.').map(Number);
|
|
390
|
+
const remoteParts = latestVersion.split('.').map(Number);
|
|
391
|
+
let isNewer = false;
|
|
392
|
+
for (let i = 0; i < 3; i++) {
|
|
393
|
+
if ((remoteParts[i] ?? 0) > (currentParts[i] ?? 0)) {
|
|
394
|
+
isNewer = true;
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
if ((remoteParts[i] ?? 0) < (currentParts[i] ?? 0)) {
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!isNewer) {
|
|
403
|
+
console.log(green(`You're already on the latest version (${VERSION}).`));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
console.log(
|
|
408
|
+
`${yellow('New version available:')} ${dim(VERSION)} ${dim('->')} ${bold(green(latestVersion))}`
|
|
409
|
+
);
|
|
410
|
+
if (source === 'github') {
|
|
411
|
+
console.log(dim(` Source: GitHub Releases (https://github.com/${GITHUB_REPO}/releases)`));
|
|
412
|
+
}
|
|
413
|
+
console.log('');
|
|
414
|
+
|
|
415
|
+
if (options.check) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Detect how nimbus was installed
|
|
420
|
+
const detection = await detectInstallMethod();
|
|
421
|
+
console.log(`Detected installation: ${bold(detection.detail)}`);
|
|
422
|
+
console.log('');
|
|
423
|
+
|
|
424
|
+
// For unknown install methods, just print manual instructions
|
|
425
|
+
if (detection.method === 'unknown') {
|
|
426
|
+
printManualInstructions(source);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Ask for confirmation unless --force
|
|
431
|
+
if (!options.force) {
|
|
432
|
+
const proceed = await confirmPrompt(`Upgrade nimbus ${VERSION} -> ${latestVersion}?`, true);
|
|
433
|
+
if (!proceed) {
|
|
434
|
+
console.log('\nUpgrade cancelled.');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
console.log('');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Execute the upgrade
|
|
441
|
+
const spinner = startSpinner('Upgrading nimbus...');
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
switch (detection.method) {
|
|
445
|
+
case 'homebrew':
|
|
446
|
+
await upgradeViaHomebrew(spinner);
|
|
447
|
+
break;
|
|
448
|
+
case 'npm':
|
|
449
|
+
await upgradeViaNpm(spinner);
|
|
450
|
+
break;
|
|
451
|
+
case 'bun':
|
|
452
|
+
await upgradeViaBun(spinner);
|
|
453
|
+
break;
|
|
454
|
+
case 'binary':
|
|
455
|
+
if (source !== 'github') {
|
|
456
|
+
spinner.fail('Binary upgrade requires GitHub Releases, but no release was found.');
|
|
457
|
+
printManualInstructions(source);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
await upgradeViaBinaryDownload(latestVersion, spinner);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
spinner.success('Upgrade command completed');
|
|
465
|
+
} catch (error: any) {
|
|
466
|
+
const msg = error.message || String(error);
|
|
467
|
+
spinner.fail(`Upgrade failed: ${msg}`);
|
|
468
|
+
console.log('');
|
|
469
|
+
|
|
470
|
+
// Provide recovery instructions
|
|
471
|
+
console.log(yellow('You can try upgrading manually:'));
|
|
472
|
+
printUpgradeInstructionForMethod(detection.method, latestVersion, source);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Verify the upgrade succeeded
|
|
477
|
+
console.log('');
|
|
478
|
+
const verifySpinner = startSpinner('Verifying upgrade...');
|
|
479
|
+
const newVersion = await verifyUpgrade();
|
|
480
|
+
|
|
481
|
+
if (newVersion && newVersion === latestVersion) {
|
|
482
|
+
verifySpinner.success(`Successfully upgraded to ${bold(green(newVersion))}`);
|
|
483
|
+
} else if (newVersion && newVersion !== VERSION) {
|
|
484
|
+
verifySpinner.success(`Upgraded to ${bold(green(newVersion))}`);
|
|
485
|
+
} else if (detection.method === 'binary') {
|
|
486
|
+
// For binary upgrades, the current process is the old binary so
|
|
487
|
+
// `nimbus --version` will report the new version only on next launch.
|
|
488
|
+
verifySpinner.success(`Binary replaced. Restart nimbus to use ${bold(green(latestVersion))}.`);
|
|
489
|
+
} else {
|
|
490
|
+
verifySpinner.fail('Could not verify the new version. Please run `nimbus --version` to check.');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// Helper: print manual instructions (fallback for unknown install method)
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
|
|
498
|
+
function printManualInstructions(source: 'npm' | 'github' | 'none'): void {
|
|
499
|
+
console.log('Could not detect installation method. Upgrade manually:');
|
|
500
|
+
console.log('');
|
|
501
|
+
console.log(` ${dim('# npm')}`);
|
|
502
|
+
console.log(` npm install -g ${NPM_PACKAGE}@latest`);
|
|
503
|
+
console.log('');
|
|
504
|
+
console.log(` ${dim('# bun')}`);
|
|
505
|
+
console.log(` bun install -g ${NPM_PACKAGE}@latest`);
|
|
506
|
+
console.log('');
|
|
507
|
+
console.log(` ${dim('# Homebrew')}`);
|
|
508
|
+
console.log(` brew upgrade ${HOMEBREW_TAP}`);
|
|
509
|
+
|
|
510
|
+
if (source === 'github') {
|
|
511
|
+
console.log('');
|
|
512
|
+
console.log(` ${dim('# Binary download')}`);
|
|
513
|
+
console.log(` https://github.com/${GITHUB_REPO}/releases/latest`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Print the specific upgrade instruction for a detected method (used after
|
|
519
|
+
* an automatic upgrade fails).
|
|
520
|
+
*/
|
|
521
|
+
function printUpgradeInstructionForMethod(
|
|
522
|
+
method: InstallMethod,
|
|
523
|
+
latestVersion: string,
|
|
524
|
+
source: 'npm' | 'github' | 'none'
|
|
525
|
+
): void {
|
|
526
|
+
switch (method) {
|
|
527
|
+
case 'homebrew':
|
|
528
|
+
console.log(` brew upgrade ${HOMEBREW_TAP}`);
|
|
529
|
+
break;
|
|
530
|
+
case 'npm':
|
|
531
|
+
console.log(` npm install -g ${NPM_PACKAGE}@latest`);
|
|
532
|
+
break;
|
|
533
|
+
case 'bun':
|
|
534
|
+
console.log(` bun install -g ${NPM_PACKAGE}@latest`);
|
|
535
|
+
break;
|
|
536
|
+
case 'binary': {
|
|
537
|
+
const platform = process.platform === 'darwin' ? 'darwin' : 'linux';
|
|
538
|
+
const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
539
|
+
const assetName = `nimbus-${platform}-${arch}`;
|
|
540
|
+
const url = `https://github.com/${GITHUB_REPO}/releases/download/v${latestVersion}/${assetName}`;
|
|
541
|
+
console.log(
|
|
542
|
+
` curl -fsSL "${url}" -o /usr/local/bin/nimbus && chmod +x /usr/local/bin/nimbus`
|
|
543
|
+
);
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
default:
|
|
547
|
+
printManualInstructions(source);
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Command
|
|
3
|
+
* Usage dashboard CLI command
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ui } from '../../wizard/ui';
|
|
7
|
+
import { billingClient } from '../../clients/enterprise-client';
|
|
8
|
+
import type { UsageOptions } from '../../types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get current team ID from config or environment
|
|
12
|
+
*/
|
|
13
|
+
function getCurrentTeamId(): string | null {
|
|
14
|
+
return process.env.NIMBUS_TEAM_ID || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse usage options
|
|
19
|
+
*/
|
|
20
|
+
export function parseUsageOptions(args: string[]): UsageOptions {
|
|
21
|
+
const options: UsageOptions = {};
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < args.length; i++) {
|
|
24
|
+
const arg = args[i];
|
|
25
|
+
if (arg === '--period' && args[i + 1]) {
|
|
26
|
+
options.period = args[++i] as 'day' | 'week' | 'month';
|
|
27
|
+
} else if (arg === '--team' && args[i + 1]) {
|
|
28
|
+
options.teamId = args[++i];
|
|
29
|
+
} else if (arg === '--json') {
|
|
30
|
+
options.json = true;
|
|
31
|
+
} else if (arg === '--non-interactive') {
|
|
32
|
+
options.nonInteractive = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return options;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Format number with commas
|
|
41
|
+
*/
|
|
42
|
+
function formatNumber(n: number): string {
|
|
43
|
+
return n.toLocaleString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format cost in USD
|
|
48
|
+
*/
|
|
49
|
+
function formatCost(n: number): string {
|
|
50
|
+
return `$${n.toFixed(2)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Usage command
|
|
55
|
+
*/
|
|
56
|
+
export async function usageCommand(options: UsageOptions): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
const teamId = options.teamId || getCurrentTeamId();
|
|
59
|
+
if (!teamId) {
|
|
60
|
+
ui.error('No team selected. Run `nimbus team switch <team-id>` first.');
|
|
61
|
+
ui.info('Or use: nimbus usage --team <team-id>');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const period = options.period || 'month';
|
|
66
|
+
|
|
67
|
+
ui.startSpinner({ message: `Fetching ${period} usage...` });
|
|
68
|
+
const usage = await billingClient.getUsage(teamId, period);
|
|
69
|
+
ui.stopSpinnerSuccess('Usage retrieved');
|
|
70
|
+
|
|
71
|
+
if (options.json) {
|
|
72
|
+
console.log(JSON.stringify(usage, null, 2));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ui.newLine();
|
|
77
|
+
ui.header(
|
|
78
|
+
'Usage Dashboard',
|
|
79
|
+
`${new Date(usage.period.start).toLocaleDateString()} - ${new Date(usage.period.end).toLocaleDateString()}`
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Summary
|
|
83
|
+
ui.section('Summary');
|
|
84
|
+
ui.print(` Total Operations: ${formatNumber(usage.totals.operations)}`);
|
|
85
|
+
ui.print(` Total Tokens: ${formatNumber(usage.totals.tokensUsed)}`);
|
|
86
|
+
ui.print(` Total Cost: ${formatCost(usage.totals.costUsd)}`);
|
|
87
|
+
|
|
88
|
+
// By operation type
|
|
89
|
+
const operationTypes = Object.entries(usage.byOperationType);
|
|
90
|
+
if (operationTypes.length > 0) {
|
|
91
|
+
ui.section('By Operation Type');
|
|
92
|
+
ui.table({
|
|
93
|
+
columns: [
|
|
94
|
+
{ key: 'type', header: 'Operation' },
|
|
95
|
+
{ key: 'count', header: 'Count' },
|
|
96
|
+
{ key: 'tokens', header: 'Tokens' },
|
|
97
|
+
{ key: 'cost', header: 'Cost' },
|
|
98
|
+
],
|
|
99
|
+
data: operationTypes.map(([type, data]) => ({
|
|
100
|
+
type,
|
|
101
|
+
count: formatNumber(data.count),
|
|
102
|
+
tokens: formatNumber(data.tokensUsed),
|
|
103
|
+
cost: formatCost(data.costUsd),
|
|
104
|
+
})),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// By user (if available)
|
|
109
|
+
if (usage.byUser && Object.keys(usage.byUser).length > 0) {
|
|
110
|
+
const userUsage = Object.entries(usage.byUser);
|
|
111
|
+
ui.section('By User');
|
|
112
|
+
ui.table({
|
|
113
|
+
columns: [
|
|
114
|
+
{ key: 'user', header: 'User' },
|
|
115
|
+
{ key: 'count', header: 'Operations' },
|
|
116
|
+
{ key: 'tokens', header: 'Tokens' },
|
|
117
|
+
{ key: 'cost', header: 'Cost' },
|
|
118
|
+
],
|
|
119
|
+
data: userUsage.map(([user, data]) => ({
|
|
120
|
+
user,
|
|
121
|
+
count: formatNumber(data.count),
|
|
122
|
+
tokens: formatNumber(data.tokensUsed),
|
|
123
|
+
cost: formatCost(data.costUsd),
|
|
124
|
+
})),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
ui.newLine();
|
|
129
|
+
ui.dim(`Period: ${period} | Use --period day|week|month to change`);
|
|
130
|
+
} catch (error: any) {
|
|
131
|
+
ui.stopSpinnerFail('Failed to get usage');
|
|
132
|
+
ui.error(error.message);
|
|
133
|
+
}
|
|
134
|
+
}
|