@dotsetlabs/bellwether 0.10.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/CHANGELOG.md +291 -0
- package/LICENSE +21 -0
- package/README.md +739 -0
- package/dist/auth/credentials.d.ts +64 -0
- package/dist/auth/credentials.js +218 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/keychain.d.ts +64 -0
- package/dist/auth/keychain.js +268 -0
- package/dist/baseline/ab-testing.d.ts +80 -0
- package/dist/baseline/ab-testing.js +236 -0
- package/dist/baseline/ai-compatibility-scorer.d.ts +95 -0
- package/dist/baseline/ai-compatibility-scorer.js +606 -0
- package/dist/baseline/calibration.d.ts +77 -0
- package/dist/baseline/calibration.js +136 -0
- package/dist/baseline/category-matching.d.ts +85 -0
- package/dist/baseline/category-matching.js +289 -0
- package/dist/baseline/change-impact-analyzer.d.ts +98 -0
- package/dist/baseline/change-impact-analyzer.js +592 -0
- package/dist/baseline/comparator.d.ts +64 -0
- package/dist/baseline/comparator.js +916 -0
- package/dist/baseline/confidence.d.ts +55 -0
- package/dist/baseline/confidence.js +122 -0
- package/dist/baseline/converter.d.ts +61 -0
- package/dist/baseline/converter.js +585 -0
- package/dist/baseline/dependency-analyzer.d.ts +89 -0
- package/dist/baseline/dependency-analyzer.js +567 -0
- package/dist/baseline/deprecation-tracker.d.ts +133 -0
- package/dist/baseline/deprecation-tracker.js +322 -0
- package/dist/baseline/diff.d.ts +55 -0
- package/dist/baseline/diff.js +1584 -0
- package/dist/baseline/documentation-scorer.d.ts +205 -0
- package/dist/baseline/documentation-scorer.js +466 -0
- package/dist/baseline/embeddings.d.ts +118 -0
- package/dist/baseline/embeddings.js +251 -0
- package/dist/baseline/error-analyzer.d.ts +198 -0
- package/dist/baseline/error-analyzer.js +721 -0
- package/dist/baseline/evaluation/evaluator.d.ts +42 -0
- package/dist/baseline/evaluation/evaluator.js +323 -0
- package/dist/baseline/evaluation/expanded-dataset.d.ts +45 -0
- package/dist/baseline/evaluation/expanded-dataset.js +1164 -0
- package/dist/baseline/evaluation/golden-dataset.d.ts +58 -0
- package/dist/baseline/evaluation/golden-dataset.js +717 -0
- package/dist/baseline/evaluation/index.d.ts +15 -0
- package/dist/baseline/evaluation/index.js +15 -0
- package/dist/baseline/evaluation/types.d.ts +186 -0
- package/dist/baseline/evaluation/types.js +8 -0
- package/dist/baseline/external-dependency-detector.d.ts +181 -0
- package/dist/baseline/external-dependency-detector.js +524 -0
- package/dist/baseline/golden-output.d.ts +162 -0
- package/dist/baseline/golden-output.js +636 -0
- package/dist/baseline/health-scorer.d.ts +174 -0
- package/dist/baseline/health-scorer.js +451 -0
- package/dist/baseline/incremental-checker.d.ts +97 -0
- package/dist/baseline/incremental-checker.js +174 -0
- package/dist/baseline/index.d.ts +31 -0
- package/dist/baseline/index.js +42 -0
- package/dist/baseline/migration-generator.d.ts +137 -0
- package/dist/baseline/migration-generator.js +554 -0
- package/dist/baseline/migrations.d.ts +60 -0
- package/dist/baseline/migrations.js +197 -0
- package/dist/baseline/performance-tracker.d.ts +214 -0
- package/dist/baseline/performance-tracker.js +577 -0
- package/dist/baseline/pr-comment-generator.d.ts +117 -0
- package/dist/baseline/pr-comment-generator.js +546 -0
- package/dist/baseline/response-fingerprint.d.ts +127 -0
- package/dist/baseline/response-fingerprint.js +728 -0
- package/dist/baseline/response-schema-tracker.d.ts +129 -0
- package/dist/baseline/response-schema-tracker.js +420 -0
- package/dist/baseline/risk-scorer.d.ts +54 -0
- package/dist/baseline/risk-scorer.js +434 -0
- package/dist/baseline/saver.d.ts +89 -0
- package/dist/baseline/saver.js +554 -0
- package/dist/baseline/scenario-generator.d.ts +151 -0
- package/dist/baseline/scenario-generator.js +905 -0
- package/dist/baseline/schema-compare.d.ts +86 -0
- package/dist/baseline/schema-compare.js +557 -0
- package/dist/baseline/schema-evolution.d.ts +189 -0
- package/dist/baseline/schema-evolution.js +467 -0
- package/dist/baseline/semantic.d.ts +203 -0
- package/dist/baseline/semantic.js +908 -0
- package/dist/baseline/synonyms.d.ts +60 -0
- package/dist/baseline/synonyms.js +386 -0
- package/dist/baseline/telemetry.d.ts +165 -0
- package/dist/baseline/telemetry.js +294 -0
- package/dist/baseline/test-pruner.d.ts +120 -0
- package/dist/baseline/test-pruner.js +387 -0
- package/dist/baseline/types.d.ts +449 -0
- package/dist/baseline/types.js +5 -0
- package/dist/baseline/version.d.ts +138 -0
- package/dist/baseline/version.js +206 -0
- package/dist/cache/index.d.ts +5 -0
- package/dist/cache/index.js +5 -0
- package/dist/cache/response-cache.d.ts +151 -0
- package/dist/cache/response-cache.js +287 -0
- package/dist/ci/index.d.ts +60 -0
- package/dist/ci/index.js +342 -0
- package/dist/cli/commands/auth.d.ts +12 -0
- package/dist/cli/commands/auth.js +352 -0
- package/dist/cli/commands/badge.d.ts +3 -0
- package/dist/cli/commands/badge.js +74 -0
- package/dist/cli/commands/baseline-accept.d.ts +15 -0
- package/dist/cli/commands/baseline-accept.js +178 -0
- package/dist/cli/commands/baseline-migrate.d.ts +12 -0
- package/dist/cli/commands/baseline-migrate.js +164 -0
- package/dist/cli/commands/baseline.d.ts +14 -0
- package/dist/cli/commands/baseline.js +449 -0
- package/dist/cli/commands/beta.d.ts +10 -0
- package/dist/cli/commands/beta.js +231 -0
- package/dist/cli/commands/check.d.ts +11 -0
- package/dist/cli/commands/check.js +820 -0
- package/dist/cli/commands/cloud/badge.d.ts +3 -0
- package/dist/cli/commands/cloud/badge.js +74 -0
- package/dist/cli/commands/cloud/diff.d.ts +6 -0
- package/dist/cli/commands/cloud/diff.js +79 -0
- package/dist/cli/commands/cloud/history.d.ts +6 -0
- package/dist/cli/commands/cloud/history.js +102 -0
- package/dist/cli/commands/cloud/link.d.ts +9 -0
- package/dist/cli/commands/cloud/link.js +119 -0
- package/dist/cli/commands/cloud/login.d.ts +7 -0
- package/dist/cli/commands/cloud/login.js +499 -0
- package/dist/cli/commands/cloud/projects.d.ts +6 -0
- package/dist/cli/commands/cloud/projects.js +44 -0
- package/dist/cli/commands/cloud/shared.d.ts +7 -0
- package/dist/cli/commands/cloud/shared.js +42 -0
- package/dist/cli/commands/cloud/teams.d.ts +8 -0
- package/dist/cli/commands/cloud/teams.js +169 -0
- package/dist/cli/commands/cloud/upload.d.ts +8 -0
- package/dist/cli/commands/cloud/upload.js +181 -0
- package/dist/cli/commands/contract.d.ts +11 -0
- package/dist/cli/commands/contract.js +280 -0
- package/dist/cli/commands/discover.d.ts +3 -0
- package/dist/cli/commands/discover.js +82 -0
- package/dist/cli/commands/eval.d.ts +9 -0
- package/dist/cli/commands/eval.js +187 -0
- package/dist/cli/commands/explore.d.ts +11 -0
- package/dist/cli/commands/explore.js +437 -0
- package/dist/cli/commands/feedback.d.ts +9 -0
- package/dist/cli/commands/feedback.js +174 -0
- package/dist/cli/commands/golden.d.ts +12 -0
- package/dist/cli/commands/golden.js +407 -0
- package/dist/cli/commands/history.d.ts +10 -0
- package/dist/cli/commands/history.js +202 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +219 -0
- package/dist/cli/commands/interview.d.ts +3 -0
- package/dist/cli/commands/interview.js +903 -0
- package/dist/cli/commands/link.d.ts +10 -0
- package/dist/cli/commands/link.js +169 -0
- package/dist/cli/commands/login.d.ts +7 -0
- package/dist/cli/commands/login.js +499 -0
- package/dist/cli/commands/preset.d.ts +33 -0
- package/dist/cli/commands/preset.js +297 -0
- package/dist/cli/commands/profile.d.ts +33 -0
- package/dist/cli/commands/profile.js +286 -0
- package/dist/cli/commands/registry.d.ts +11 -0
- package/dist/cli/commands/registry.js +146 -0
- package/dist/cli/commands/shared.d.ts +79 -0
- package/dist/cli/commands/shared.js +196 -0
- package/dist/cli/commands/teams.d.ts +8 -0
- package/dist/cli/commands/teams.js +169 -0
- package/dist/cli/commands/test.d.ts +9 -0
- package/dist/cli/commands/test.js +500 -0
- package/dist/cli/commands/upload.d.ts +8 -0
- package/dist/cli/commands/upload.js +223 -0
- package/dist/cli/commands/validate-config.d.ts +6 -0
- package/dist/cli/commands/validate-config.js +35 -0
- package/dist/cli/commands/verify.d.ts +11 -0
- package/dist/cli/commands/verify.js +283 -0
- package/dist/cli/commands/watch.d.ts +12 -0
- package/dist/cli/commands/watch.js +253 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +178 -0
- package/dist/cli/interactive.d.ts +47 -0
- package/dist/cli/interactive.js +216 -0
- package/dist/cli/output/terminal-reporter.d.ts +19 -0
- package/dist/cli/output/terminal-reporter.js +104 -0
- package/dist/cli/output.d.ts +226 -0
- package/dist/cli/output.js +438 -0
- package/dist/cli/utils/env.d.ts +5 -0
- package/dist/cli/utils/env.js +14 -0
- package/dist/cli/utils/progress.d.ts +59 -0
- package/dist/cli/utils/progress.js +206 -0
- package/dist/cli/utils/server-context.d.ts +10 -0
- package/dist/cli/utils/server-context.js +36 -0
- package/dist/cloud/auth.d.ts +144 -0
- package/dist/cloud/auth.js +374 -0
- package/dist/cloud/client.d.ts +24 -0
- package/dist/cloud/client.js +65 -0
- package/dist/cloud/http-client.d.ts +38 -0
- package/dist/cloud/http-client.js +215 -0
- package/dist/cloud/index.d.ts +23 -0
- package/dist/cloud/index.js +25 -0
- package/dist/cloud/mock-client.d.ts +107 -0
- package/dist/cloud/mock-client.js +545 -0
- package/dist/cloud/types.d.ts +515 -0
- package/dist/cloud/types.js +15 -0
- package/dist/config/defaults.d.ts +160 -0
- package/dist/config/defaults.js +169 -0
- package/dist/config/loader.d.ts +24 -0
- package/dist/config/loader.js +122 -0
- package/dist/config/template.d.ts +42 -0
- package/dist/config/template.js +647 -0
- package/dist/config/validator.d.ts +2112 -0
- package/dist/config/validator.js +658 -0
- package/dist/constants/cloud.d.ts +107 -0
- package/dist/constants/cloud.js +110 -0
- package/dist/constants/core.d.ts +521 -0
- package/dist/constants/core.js +556 -0
- package/dist/constants/testing.d.ts +1283 -0
- package/dist/constants/testing.js +1568 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +10 -0
- package/dist/contract/index.d.ts +6 -0
- package/dist/contract/index.js +5 -0
- package/dist/contract/validator.d.ts +177 -0
- package/dist/contract/validator.js +574 -0
- package/dist/cost/index.d.ts +6 -0
- package/dist/cost/index.js +5 -0
- package/dist/cost/tracker.d.ts +134 -0
- package/dist/cost/tracker.js +313 -0
- package/dist/discovery/discovery.d.ts +16 -0
- package/dist/discovery/discovery.js +173 -0
- package/dist/discovery/types.d.ts +51 -0
- package/dist/discovery/types.js +2 -0
- package/dist/docs/agents.d.ts +3 -0
- package/dist/docs/agents.js +995 -0
- package/dist/docs/contract.d.ts +51 -0
- package/dist/docs/contract.js +1681 -0
- package/dist/docs/generator.d.ts +4 -0
- package/dist/docs/generator.js +4 -0
- package/dist/docs/html-reporter.d.ts +9 -0
- package/dist/docs/html-reporter.js +757 -0
- package/dist/docs/index.d.ts +10 -0
- package/dist/docs/index.js +11 -0
- package/dist/docs/junit-reporter.d.ts +18 -0
- package/dist/docs/junit-reporter.js +210 -0
- package/dist/docs/report.d.ts +14 -0
- package/dist/docs/report.js +44 -0
- package/dist/docs/sarif-reporter.d.ts +19 -0
- package/dist/docs/sarif-reporter.js +335 -0
- package/dist/docs/shared.d.ts +35 -0
- package/dist/docs/shared.js +162 -0
- package/dist/docs/templates.d.ts +12 -0
- package/dist/docs/templates.js +76 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.js +6 -0
- package/dist/errors/retry.d.ts +92 -0
- package/dist/errors/retry.js +323 -0
- package/dist/errors/types.d.ts +321 -0
- package/dist/errors/types.js +584 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +32 -0
- package/dist/interview/dependency-resolver.d.ts +11 -0
- package/dist/interview/dependency-resolver.js +32 -0
- package/dist/interview/interviewer.d.ts +232 -0
- package/dist/interview/interviewer.js +1939 -0
- package/dist/interview/mock-response-generator.d.ts +7 -0
- package/dist/interview/mock-response-generator.js +102 -0
- package/dist/interview/orchestrator.d.ts +237 -0
- package/dist/interview/orchestrator.js +1296 -0
- package/dist/interview/rate-limiter.d.ts +15 -0
- package/dist/interview/rate-limiter.js +55 -0
- package/dist/interview/response-validator.d.ts +10 -0
- package/dist/interview/response-validator.js +132 -0
- package/dist/interview/schema-inferrer.d.ts +8 -0
- package/dist/interview/schema-inferrer.js +71 -0
- package/dist/interview/schema-test-generator.d.ts +71 -0
- package/dist/interview/schema-test-generator.js +834 -0
- package/dist/interview/smart-value-generator.d.ts +155 -0
- package/dist/interview/smart-value-generator.js +554 -0
- package/dist/interview/stateful-test-runner.d.ts +19 -0
- package/dist/interview/stateful-test-runner.js +106 -0
- package/dist/interview/types.d.ts +561 -0
- package/dist/interview/types.js +2 -0
- package/dist/llm/anthropic.d.ts +41 -0
- package/dist/llm/anthropic.js +355 -0
- package/dist/llm/client.d.ts +123 -0
- package/dist/llm/client.js +42 -0
- package/dist/llm/factory.d.ts +38 -0
- package/dist/llm/factory.js +145 -0
- package/dist/llm/fallback.d.ts +140 -0
- package/dist/llm/fallback.js +379 -0
- package/dist/llm/index.d.ts +18 -0
- package/dist/llm/index.js +15 -0
- package/dist/llm/ollama.d.ts +37 -0
- package/dist/llm/ollama.js +330 -0
- package/dist/llm/openai.d.ts +25 -0
- package/dist/llm/openai.js +320 -0
- package/dist/llm/token-budget.d.ts +161 -0
- package/dist/llm/token-budget.js +395 -0
- package/dist/logging/logger.d.ts +70 -0
- package/dist/logging/logger.js +130 -0
- package/dist/metrics/collector.d.ts +106 -0
- package/dist/metrics/collector.js +547 -0
- package/dist/metrics/index.d.ts +7 -0
- package/dist/metrics/index.js +7 -0
- package/dist/metrics/prometheus.d.ts +20 -0
- package/dist/metrics/prometheus.js +241 -0
- package/dist/metrics/types.d.ts +209 -0
- package/dist/metrics/types.js +5 -0
- package/dist/persona/builtins.d.ts +54 -0
- package/dist/persona/builtins.js +219 -0
- package/dist/persona/index.d.ts +8 -0
- package/dist/persona/index.js +8 -0
- package/dist/persona/loader.d.ts +30 -0
- package/dist/persona/loader.js +190 -0
- package/dist/persona/types.d.ts +144 -0
- package/dist/persona/types.js +5 -0
- package/dist/persona/validation.d.ts +94 -0
- package/dist/persona/validation.js +332 -0
- package/dist/prompts/index.d.ts +5 -0
- package/dist/prompts/index.js +5 -0
- package/dist/prompts/templates.d.ts +180 -0
- package/dist/prompts/templates.js +431 -0
- package/dist/registry/client.d.ts +49 -0
- package/dist/registry/client.js +191 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.js +6 -0
- package/dist/registry/types.d.ts +140 -0
- package/dist/registry/types.js +6 -0
- package/dist/scenarios/evaluator.d.ts +43 -0
- package/dist/scenarios/evaluator.js +206 -0
- package/dist/scenarios/index.d.ts +10 -0
- package/dist/scenarios/index.js +9 -0
- package/dist/scenarios/loader.d.ts +20 -0
- package/dist/scenarios/loader.js +285 -0
- package/dist/scenarios/types.d.ts +153 -0
- package/dist/scenarios/types.js +8 -0
- package/dist/security/index.d.ts +17 -0
- package/dist/security/index.js +18 -0
- package/dist/security/payloads.d.ts +61 -0
- package/dist/security/payloads.js +268 -0
- package/dist/security/security-tester.d.ts +42 -0
- package/dist/security/security-tester.js +582 -0
- package/dist/security/types.d.ts +166 -0
- package/dist/security/types.js +8 -0
- package/dist/transport/base-transport.d.ts +59 -0
- package/dist/transport/base-transport.js +38 -0
- package/dist/transport/http-transport.d.ts +67 -0
- package/dist/transport/http-transport.js +238 -0
- package/dist/transport/mcp-client.d.ts +141 -0
- package/dist/transport/mcp-client.js +496 -0
- package/dist/transport/sse-transport.d.ts +88 -0
- package/dist/transport/sse-transport.js +316 -0
- package/dist/transport/stdio-transport.d.ts +43 -0
- package/dist/transport/stdio-transport.js +238 -0
- package/dist/transport/types.d.ts +125 -0
- package/dist/transport/types.js +16 -0
- package/dist/utils/concurrency.d.ts +123 -0
- package/dist/utils/concurrency.js +213 -0
- package/dist/utils/formatters.d.ts +16 -0
- package/dist/utils/formatters.js +37 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/jsonpath.d.ts +87 -0
- package/dist/utils/jsonpath.js +326 -0
- package/dist/utils/markdown.d.ts +113 -0
- package/dist/utils/markdown.js +265 -0
- package/dist/utils/network.d.ts +14 -0
- package/dist/utils/network.js +17 -0
- package/dist/utils/sanitize.d.ts +92 -0
- package/dist/utils/sanitize.js +191 -0
- package/dist/utils/semantic.d.ts +194 -0
- package/dist/utils/semantic.js +1051 -0
- package/dist/utils/smart-truncate.d.ts +94 -0
- package/dist/utils/smart-truncate.js +361 -0
- package/dist/utils/timeout.d.ts +153 -0
- package/dist/utils/timeout.js +205 -0
- package/dist/utils/yaml-parser.d.ts +58 -0
- package/dist/utils/yaml-parser.js +86 -0
- package/dist/validation/index.d.ts +32 -0
- package/dist/validation/index.js +32 -0
- package/dist/validation/semantic-test-generator.d.ts +50 -0
- package/dist/validation/semantic-test-generator.js +176 -0
- package/dist/validation/semantic-types.d.ts +66 -0
- package/dist/validation/semantic-types.js +94 -0
- package/dist/validation/semantic-validator.d.ts +38 -0
- package/dist/validation/semantic-validator.js +340 -0
- package/dist/verification/index.d.ts +6 -0
- package/dist/verification/index.js +5 -0
- package/dist/verification/types.d.ts +133 -0
- package/dist/verification/types.js +5 -0
- package/dist/verification/verifier.d.ts +30 -0
- package/dist/verification/verifier.js +309 -0
- package/dist/version.d.ts +19 -0
- package/dist/version.js +48 -0
- package/dist/workflow/auto-generator.d.ts +27 -0
- package/dist/workflow/auto-generator.js +513 -0
- package/dist/workflow/discovery.d.ts +40 -0
- package/dist/workflow/discovery.js +195 -0
- package/dist/workflow/executor.d.ts +82 -0
- package/dist/workflow/executor.js +611 -0
- package/dist/workflow/index.d.ts +10 -0
- package/dist/workflow/index.js +10 -0
- package/dist/workflow/loader.d.ts +24 -0
- package/dist/workflow/loader.js +194 -0
- package/dist/workflow/state-tracker.d.ts +98 -0
- package/dist/workflow/state-tracker.js +424 -0
- package/dist/workflow/types.d.ts +337 -0
- package/dist/workflow/types.js +5 -0
- package/package.json +94 -0
- package/schemas/bellwether-check.schema.json +651 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login command for Bellwether Cloud authentication using OAuth.
|
|
3
|
+
* Supports GitHub and Google as authentication providers.
|
|
4
|
+
*/
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
|
+
import { platform } from 'os';
|
|
8
|
+
import { createInterface } from 'readline';
|
|
9
|
+
import { getStoredSession, saveSession, clearSession, getBaseUrl, isMockSession, CONFIG_DIR, } from '../../../cloud/auth.js';
|
|
10
|
+
import { generateMockSession } from '../../../cloud/mock-client.js';
|
|
11
|
+
import { EXIT_CODES, TIME_CONSTANTS } from '../../../constants.js';
|
|
12
|
+
import { isLocalhost } from '../../../utils/index.js';
|
|
13
|
+
import * as output from '../../output.js';
|
|
14
|
+
const PROVIDER_NAMES = {
|
|
15
|
+
github: 'GitHub',
|
|
16
|
+
google: 'Google',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Header name for beta invite token (for pre-OAuth flow).
|
|
20
|
+
*/
|
|
21
|
+
const BETA_INVITE_TOKEN_HEADER = 'x-beta-invite-token';
|
|
22
|
+
export const loginCommand = new Command('login')
|
|
23
|
+
.description('Authenticate with Bellwether Cloud')
|
|
24
|
+
.option('--logout', 'Remove stored credentials')
|
|
25
|
+
.option('--mock', 'Generate a mock session for development')
|
|
26
|
+
.option('--status', 'Show current authentication status')
|
|
27
|
+
.option('--no-browser', 'Do not automatically open browser')
|
|
28
|
+
.option('--provider <provider>', 'OAuth provider to use (github, google)')
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
// Handle --status
|
|
31
|
+
if (options.status) {
|
|
32
|
+
await showStatus();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Handle --logout
|
|
36
|
+
if (options.logout) {
|
|
37
|
+
clearSession();
|
|
38
|
+
output.info('Logged out successfully.');
|
|
39
|
+
output.info('Stored credentials removed from ~/.bellwether/session.json');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Handle --mock
|
|
43
|
+
if (options.mock) {
|
|
44
|
+
const mockSession = generateMockSession();
|
|
45
|
+
saveSession(mockSession);
|
|
46
|
+
output.info('Mock session generated and saved.');
|
|
47
|
+
output.info(`Logged in as: ${mockSession.user.githubLogin} (mock)`);
|
|
48
|
+
output.info('\nYou can now use cloud commands in mock mode.');
|
|
49
|
+
output.info('Data will be stored locally in ~/.bellwether/mock-cloud/');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Check if already logged in
|
|
53
|
+
const existing = getStoredSession();
|
|
54
|
+
if (existing) {
|
|
55
|
+
output.info(`Already logged in as ${existing.user.githubLogin}`);
|
|
56
|
+
output.info(`Email: ${existing.user.email || 'N/A'}`);
|
|
57
|
+
output.info(`Plan: ${existing.user.plan}`);
|
|
58
|
+
if (isMockSession(existing.sessionToken)) {
|
|
59
|
+
output.info('\nUsing mock session - data stored locally.');
|
|
60
|
+
}
|
|
61
|
+
output.info('\nUse --logout to sign out.');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Start OAuth device flow
|
|
65
|
+
output.info('Bellwether Cloud Authentication\n');
|
|
66
|
+
// Determine OAuth provider
|
|
67
|
+
let provider;
|
|
68
|
+
if (options.provider) {
|
|
69
|
+
const p = options.provider.toLowerCase();
|
|
70
|
+
if (p !== 'github' && p !== 'google') {
|
|
71
|
+
output.error(`Invalid provider: ${options.provider}`);
|
|
72
|
+
output.info('Supported providers: github, google');
|
|
73
|
+
process.exit(EXIT_CODES.ERROR);
|
|
74
|
+
}
|
|
75
|
+
provider = p;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Prompt user to select provider
|
|
79
|
+
const rl = createInterface({
|
|
80
|
+
input: process.stdin,
|
|
81
|
+
output: process.stdout,
|
|
82
|
+
});
|
|
83
|
+
output.info('Select authentication provider:');
|
|
84
|
+
output.info(' 1. GitHub');
|
|
85
|
+
output.info(' 2. Google\n');
|
|
86
|
+
const selection = await new Promise((resolve) => {
|
|
87
|
+
rl.question('Enter choice (1 or 2): ', resolve);
|
|
88
|
+
});
|
|
89
|
+
rl.close();
|
|
90
|
+
if (selection === '1' || selection.toLowerCase() === 'github') {
|
|
91
|
+
provider = 'github';
|
|
92
|
+
}
|
|
93
|
+
else if (selection === '2' || selection.toLowerCase() === 'google') {
|
|
94
|
+
provider = 'google';
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
output.error('Invalid selection. Please enter 1 or 2.');
|
|
98
|
+
process.exit(EXIT_CODES.ERROR);
|
|
99
|
+
}
|
|
100
|
+
output.info('');
|
|
101
|
+
}
|
|
102
|
+
output.info(`Signing in with ${PROVIDER_NAMES[provider]}...\n`);
|
|
103
|
+
try {
|
|
104
|
+
// Check if beta mode is enabled and get invite code if needed
|
|
105
|
+
let inviteToken;
|
|
106
|
+
const betaStatus = await checkBetaStatus();
|
|
107
|
+
if (betaStatus.betaMode) {
|
|
108
|
+
output.info('Bellwether Cloud is currently in private beta.\n');
|
|
109
|
+
const rl = createInterface({
|
|
110
|
+
input: process.stdin,
|
|
111
|
+
output: process.stdout,
|
|
112
|
+
});
|
|
113
|
+
const hasInvite = await new Promise((resolve) => {
|
|
114
|
+
rl.question('Do you have a beta invitation code? (y/n): ', resolve);
|
|
115
|
+
});
|
|
116
|
+
if (hasInvite.toLowerCase() === 'y' || hasInvite.toLowerCase() === 'yes') {
|
|
117
|
+
const code = await new Promise((resolve) => {
|
|
118
|
+
rl.question('Enter your invitation code (XXXX-XXXX): ', resolve);
|
|
119
|
+
});
|
|
120
|
+
rl.close();
|
|
121
|
+
// Verify the invite code
|
|
122
|
+
const verifyResult = await verifyBetaInvite(code.trim());
|
|
123
|
+
if (!verifyResult.valid) {
|
|
124
|
+
output.error('Invalid or expired invitation code.');
|
|
125
|
+
output.info('\nJoin the waitlist at https://bellwether.sh');
|
|
126
|
+
process.exit(EXIT_CODES.ERROR);
|
|
127
|
+
}
|
|
128
|
+
output.info('Invitation verified! Proceeding with login...');
|
|
129
|
+
if (verifyResult.email) {
|
|
130
|
+
output.info(`Note: Please sign in with an account that has the email: ${verifyResult.email}\n`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
output.info('');
|
|
134
|
+
}
|
|
135
|
+
inviteToken = verifyResult.token;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
rl.close();
|
|
139
|
+
output.info('\nTo request beta access, visit https://bellwether.sh');
|
|
140
|
+
output.info('Join the waitlist and we\'ll send you an invitation.');
|
|
141
|
+
process.exit(EXIT_CODES.CLEAN);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Start OAuth device flow
|
|
145
|
+
const deviceAuth = await startDeviceFlow(provider, inviteToken);
|
|
146
|
+
output.info('To authenticate, visit:\n');
|
|
147
|
+
output.info(` ${deviceAuth.verification_uri}\n`);
|
|
148
|
+
output.info(`Enter code: ${deviceAuth.user_code}\n`);
|
|
149
|
+
// Try to open browser automatically
|
|
150
|
+
if (options.browser !== false) {
|
|
151
|
+
await openBrowser(deviceAuth.verification_uri);
|
|
152
|
+
}
|
|
153
|
+
// Poll for authorization completion
|
|
154
|
+
output.info('Waiting for authorization...');
|
|
155
|
+
const result = await pollForCompletion(provider, deviceAuth.device_code, deviceAuth.interval, deviceAuth.expires_in, inviteToken);
|
|
156
|
+
if (!result.session_token || !result.user) {
|
|
157
|
+
output.error('\nAuthorization failed or expired.');
|
|
158
|
+
process.exit(EXIT_CODES.ERROR);
|
|
159
|
+
}
|
|
160
|
+
// Fetch user teams
|
|
161
|
+
const authMe = await fetchAuthMe(result.session_token);
|
|
162
|
+
const teams = authMe?.teams ?? [];
|
|
163
|
+
// Auto-select first team as active (usually personal team)
|
|
164
|
+
const activeTeamId = teams.length > 0 ? teams[0].id : undefined;
|
|
165
|
+
const activeTeam = teams.find(t => t.id === activeTeamId);
|
|
166
|
+
// Save session
|
|
167
|
+
const session = {
|
|
168
|
+
sessionToken: result.session_token,
|
|
169
|
+
user: result.user,
|
|
170
|
+
expiresAt: new Date(Date.now() + TIME_CONSTANTS.SESSION_EXPIRATION_MS).toISOString(), // 30 days
|
|
171
|
+
activeTeamId,
|
|
172
|
+
teams,
|
|
173
|
+
};
|
|
174
|
+
saveSession(session);
|
|
175
|
+
const displayLogin = result.user.githubLogin || result.user.email || 'User';
|
|
176
|
+
output.info(`\nLogged in as ${displayLogin}`);
|
|
177
|
+
if (result.user.email) {
|
|
178
|
+
output.info(`Email: ${result.user.email}`);
|
|
179
|
+
}
|
|
180
|
+
if (activeTeam) {
|
|
181
|
+
output.info(`Team: ${activeTeam.name} (${activeTeam.plan})`);
|
|
182
|
+
if (teams.length > 1) {
|
|
183
|
+
output.info(`\nYou have access to ${teams.length} teams. Use \`bellwether teams\` to switch.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
output.info(`\nSession saved to ${CONFIG_DIR}/session.json`);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
output.error('Authentication failed: ' + (err instanceof Error ? err.message : String(err)));
|
|
190
|
+
process.exit(EXIT_CODES.ERROR);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
/**
|
|
194
|
+
* Check if beta mode is enabled.
|
|
195
|
+
*/
|
|
196
|
+
async function checkBetaStatus() {
|
|
197
|
+
const baseUrl = getBaseUrl();
|
|
198
|
+
try {
|
|
199
|
+
const response = await fetch(`${baseUrl}/beta/status`, {
|
|
200
|
+
method: 'GET',
|
|
201
|
+
headers: {
|
|
202
|
+
'Content-Type': 'application/json',
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
return { betaMode: false };
|
|
207
|
+
}
|
|
208
|
+
return response.json();
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// If we can't reach the server, assume no beta mode
|
|
212
|
+
return { betaMode: false };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Verify a beta invite code with the server.
|
|
217
|
+
* Returns the 64-char hex token for use in subsequent requests if valid.
|
|
218
|
+
*/
|
|
219
|
+
async function verifyBetaInvite(code) {
|
|
220
|
+
const baseUrl = getBaseUrl();
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch(`${baseUrl}/beta/verify-invite`, {
|
|
223
|
+
method: 'POST',
|
|
224
|
+
headers: {
|
|
225
|
+
'Content-Type': 'application/json',
|
|
226
|
+
},
|
|
227
|
+
body: JSON.stringify({ code }),
|
|
228
|
+
});
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
return { valid: false };
|
|
231
|
+
}
|
|
232
|
+
const result = await response.json();
|
|
233
|
+
if (result.valid && result.token) {
|
|
234
|
+
// Use the returned 64-char hex token for x-beta-invite-token header
|
|
235
|
+
return { valid: true, token: result.token, email: result.invite?.email };
|
|
236
|
+
}
|
|
237
|
+
return { valid: false };
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return { valid: false };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Start OAuth device flow.
|
|
245
|
+
*/
|
|
246
|
+
async function startDeviceFlow(provider, inviteToken) {
|
|
247
|
+
const baseUrl = getBaseUrl();
|
|
248
|
+
const headers = {
|
|
249
|
+
'Content-Type': 'application/json',
|
|
250
|
+
};
|
|
251
|
+
if (inviteToken) {
|
|
252
|
+
headers[BETA_INVITE_TOKEN_HEADER] = inviteToken;
|
|
253
|
+
}
|
|
254
|
+
const response = await fetch(`${baseUrl}/auth/${provider}/device`, {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
headers,
|
|
257
|
+
body: JSON.stringify({}),
|
|
258
|
+
});
|
|
259
|
+
if (!response.ok) {
|
|
260
|
+
const error = await response.text();
|
|
261
|
+
throw new Error(`Failed to start device flow: ${error}`);
|
|
262
|
+
}
|
|
263
|
+
return response.json();
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Poll for device authorization completion.
|
|
267
|
+
*/
|
|
268
|
+
async function pollForCompletion(provider, deviceCode, intervalSec, expiresInSec, inviteToken) {
|
|
269
|
+
const baseUrl = getBaseUrl();
|
|
270
|
+
const deadline = Date.now() + expiresInSec * 1000;
|
|
271
|
+
let dots = 0;
|
|
272
|
+
const headers = {
|
|
273
|
+
'Content-Type': 'application/json',
|
|
274
|
+
};
|
|
275
|
+
if (inviteToken) {
|
|
276
|
+
headers[BETA_INVITE_TOKEN_HEADER] = inviteToken;
|
|
277
|
+
}
|
|
278
|
+
while (Date.now() < deadline) {
|
|
279
|
+
// Wait for interval
|
|
280
|
+
await sleep(intervalSec * 1000);
|
|
281
|
+
// Clear previous line and show progress
|
|
282
|
+
process.stdout.write(`\rWaiting for authorization${'...'.slice(0, (dots % 3) + 1)} `);
|
|
283
|
+
dots++;
|
|
284
|
+
// Poll for status
|
|
285
|
+
const response = await fetch(`${baseUrl}/auth/${provider}/device/poll`, {
|
|
286
|
+
method: 'POST',
|
|
287
|
+
headers,
|
|
288
|
+
body: JSON.stringify({ device_code: deviceCode }),
|
|
289
|
+
});
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
throw new Error(`Poll request failed: ${response.status}`);
|
|
292
|
+
}
|
|
293
|
+
const result = await response.json();
|
|
294
|
+
if (result.error === 'authorization_pending') {
|
|
295
|
+
// Still waiting, continue polling
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (result.error === 'expired_token') {
|
|
299
|
+
throw new Error('Authorization expired. Please try again.');
|
|
300
|
+
}
|
|
301
|
+
if (result.error === 'access_denied') {
|
|
302
|
+
throw new Error('Authorization denied by user.');
|
|
303
|
+
}
|
|
304
|
+
if (result.error === 'email_mismatch') {
|
|
305
|
+
// SECURITY: OAuth email doesn't match invitation email
|
|
306
|
+
process.stdout.write('\r \r');
|
|
307
|
+
throw new Error(`Email mismatch: This invitation was sent to ${result.expected_email}, ` +
|
|
308
|
+
`but you signed in with an account using ${result.actual_email}. ` +
|
|
309
|
+
`Please sign in with an account that has the email ${result.expected_email}.`);
|
|
310
|
+
}
|
|
311
|
+
if (result.session_token && result.user) {
|
|
312
|
+
// Clear the waiting line
|
|
313
|
+
process.stdout.write('\r \r');
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
// Unknown response
|
|
317
|
+
throw new Error('Unexpected response from server');
|
|
318
|
+
}
|
|
319
|
+
throw new Error('Authorization timed out. Please try again.');
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Sleep for a number of milliseconds.
|
|
323
|
+
*/
|
|
324
|
+
function sleep(ms) {
|
|
325
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Fetch user and teams from /auth/me endpoint.
|
|
329
|
+
*/
|
|
330
|
+
async function fetchAuthMe(sessionToken) {
|
|
331
|
+
const baseUrl = getBaseUrl();
|
|
332
|
+
try {
|
|
333
|
+
const response = await fetch(`${baseUrl}/auth/me`, {
|
|
334
|
+
method: 'GET',
|
|
335
|
+
headers: {
|
|
336
|
+
'Authorization': `Bearer ${sessionToken}`,
|
|
337
|
+
'Content-Type': 'application/json',
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
return response.json();
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Validate a URL is safe to open in the browser.
|
|
351
|
+
* Only allows HTTPS URLs from trusted domains.
|
|
352
|
+
*/
|
|
353
|
+
function isValidBrowserUrl(url) {
|
|
354
|
+
try {
|
|
355
|
+
const parsed = new URL(url);
|
|
356
|
+
// Only allow HTTPS (or HTTP for localhost during development)
|
|
357
|
+
if (parsed.protocol !== 'https:' && !isLocalhost(parsed.hostname)) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
// Block javascript: and data: URLs
|
|
361
|
+
if (parsed.protocol === 'javascript:' || parsed.protocol === 'data:') {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
// Allow bellwether.sh, github.com, and google.com domains
|
|
365
|
+
const trustedDomains = [
|
|
366
|
+
'bellwether.sh',
|
|
367
|
+
'api.bellwether.sh',
|
|
368
|
+
'github.com',
|
|
369
|
+
'google.com',
|
|
370
|
+
'accounts.google.com',
|
|
371
|
+
'localhost',
|
|
372
|
+
'127.0.0.1',
|
|
373
|
+
];
|
|
374
|
+
const isDomainTrusted = trustedDomains.some(domain => parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`));
|
|
375
|
+
return isDomainTrusted;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Open URL in default browser.
|
|
383
|
+
* Uses execFile to prevent command injection via malicious URLs.
|
|
384
|
+
* Validates URL before opening for additional security.
|
|
385
|
+
*/
|
|
386
|
+
async function openBrowser(url) {
|
|
387
|
+
// Validate URL before opening
|
|
388
|
+
if (!isValidBrowserUrl(url)) {
|
|
389
|
+
output.warn('Warning: Skipping browser open for untrusted URL.');
|
|
390
|
+
output.warn(`URL: ${url}`);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const plat = platform();
|
|
394
|
+
let command;
|
|
395
|
+
let args;
|
|
396
|
+
switch (plat) {
|
|
397
|
+
case 'darwin':
|
|
398
|
+
command = 'open';
|
|
399
|
+
args = [url];
|
|
400
|
+
break;
|
|
401
|
+
case 'win32':
|
|
402
|
+
command = 'cmd';
|
|
403
|
+
args = ['/c', 'start', '', url];
|
|
404
|
+
break;
|
|
405
|
+
default:
|
|
406
|
+
// Linux and others
|
|
407
|
+
command = 'xdg-open';
|
|
408
|
+
args = [url];
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
return new Promise((resolve) => {
|
|
412
|
+
// Use execFile instead of exec to prevent shell injection
|
|
413
|
+
execFile(command, args, (error) => {
|
|
414
|
+
if (error) {
|
|
415
|
+
// Silently fail - user can open URL manually
|
|
416
|
+
output.info('(Could not open browser automatically)');
|
|
417
|
+
}
|
|
418
|
+
resolve();
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Show current authentication status.
|
|
424
|
+
*/
|
|
425
|
+
async function showStatus() {
|
|
426
|
+
const session = getStoredSession();
|
|
427
|
+
if (!session) {
|
|
428
|
+
output.info('Not logged in.');
|
|
429
|
+
output.info('\nRun `bellwether login` to authenticate.');
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const displayLogin = session.user.providerLogin || session.user.githubLogin || session.user.email || 'User';
|
|
433
|
+
const displayName = session.user.displayName || session.user.githubName;
|
|
434
|
+
output.info('Authentication Status');
|
|
435
|
+
output.info('---------------------');
|
|
436
|
+
output.info(`User: ${displayLogin}`);
|
|
437
|
+
if (displayName) {
|
|
438
|
+
output.info(`Name: ${displayName}`);
|
|
439
|
+
}
|
|
440
|
+
if (session.user.email) {
|
|
441
|
+
output.info(`Email: ${session.user.email}`);
|
|
442
|
+
}
|
|
443
|
+
output.info(`Mode: ${isMockSession(session.sessionToken) ? 'Mock (local storage)' : 'Cloud'}`);
|
|
444
|
+
// Show team information
|
|
445
|
+
if (session.teams && session.teams.length > 0) {
|
|
446
|
+
const activeTeam = session.teams.find(t => t.id === session.activeTeamId);
|
|
447
|
+
if (activeTeam) {
|
|
448
|
+
output.info(`Team: ${activeTeam.name} (${activeTeam.plan})`);
|
|
449
|
+
}
|
|
450
|
+
if (session.teams.length > 1) {
|
|
451
|
+
output.info(`\nAvailable teams (${session.teams.length}):`);
|
|
452
|
+
for (const team of session.teams) {
|
|
453
|
+
const marker = team.id === session.activeTeamId ? ' (active)' : '';
|
|
454
|
+
output.info(` - ${team.name} [${team.role}]${marker}`);
|
|
455
|
+
}
|
|
456
|
+
output.info('\nUse `bellwether teams switch` to change active team.');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Check beta access status if in cloud mode
|
|
460
|
+
if (!isMockSession(session.sessionToken)) {
|
|
461
|
+
const betaAccess = await checkBetaAccess(session.sessionToken);
|
|
462
|
+
if (betaAccess !== null) {
|
|
463
|
+
output.info(`Beta: ${betaAccess.hasBetaAccess ? 'Yes' : 'No'}`);
|
|
464
|
+
if (betaAccess.isAdmin) {
|
|
465
|
+
output.info(`Admin: Yes`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const expiresAt = new Date(session.expiresAt);
|
|
470
|
+
const now = new Date();
|
|
471
|
+
const daysRemaining = Math.ceil((expiresAt.getTime() - now.getTime()) / TIME_CONSTANTS.MS_PER_DAY);
|
|
472
|
+
output.info(`\nSession expires in ${daysRemaining} days`);
|
|
473
|
+
if (isMockSession(session.sessionToken)) {
|
|
474
|
+
output.info('\nData is stored locally in ~/.bellwether/mock-cloud/');
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Check if current user has beta access.
|
|
479
|
+
*/
|
|
480
|
+
async function checkBetaAccess(sessionToken) {
|
|
481
|
+
const baseUrl = getBaseUrl();
|
|
482
|
+
try {
|
|
483
|
+
const response = await fetch(`${baseUrl}/beta/access`, {
|
|
484
|
+
method: 'GET',
|
|
485
|
+
headers: {
|
|
486
|
+
'Authorization': `Bearer ${sessionToken}`,
|
|
487
|
+
'Content-Type': 'application/json',
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
if (!response.ok) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
return response.json();
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects command for listing projects.
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { getLinkedProject } from '../../../cloud/auth.js';
|
|
6
|
+
import { getSessionTokenOrExit, createAuthenticatedClient } from './shared.js';
|
|
7
|
+
import * as output from '../../output.js';
|
|
8
|
+
export const projectsCommand = new Command('projects')
|
|
9
|
+
.description('List Bellwether Cloud projects')
|
|
10
|
+
.option('--json', 'Output as JSON')
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
const sessionToken = getSessionTokenOrExit();
|
|
13
|
+
const client = createAuthenticatedClient(sessionToken);
|
|
14
|
+
const projects = await client.listProjects();
|
|
15
|
+
if (options.json) {
|
|
16
|
+
output.info(JSON.stringify(projects, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (projects.length === 0) {
|
|
20
|
+
output.info('No projects found.');
|
|
21
|
+
output.info('\nRun `bellwether link` to create a project.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Get current link for highlighting
|
|
25
|
+
const currentLink = getLinkedProject();
|
|
26
|
+
output.info('Your Projects\n');
|
|
27
|
+
output.info('ID Name Baselines Last Upload');
|
|
28
|
+
output.info('──────────────────── ─────────────────── ───────── ───────────────────');
|
|
29
|
+
for (const project of projects) {
|
|
30
|
+
const isLinked = currentLink?.projectId === project.id;
|
|
31
|
+
const marker = isLinked ? '* ' : ' ';
|
|
32
|
+
const lastUpload = project.lastUploadAt
|
|
33
|
+
? new Date(project.lastUploadAt).toLocaleDateString()
|
|
34
|
+
: 'Never';
|
|
35
|
+
output.info(`${marker}${project.id.padEnd(20)} ` +
|
|
36
|
+
`${project.name.slice(0, 19).padEnd(19)} ` +
|
|
37
|
+
`${project.baselineCount.toString().padStart(9)} ` +
|
|
38
|
+
`${lastUpload}`);
|
|
39
|
+
}
|
|
40
|
+
if (currentLink) {
|
|
41
|
+
output.info('\n* = Currently linked project');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type BellwetherConfig } from '../../../config/loader.js';
|
|
2
|
+
import type { BellwetherCloudClient } from '../../../cloud/types.js';
|
|
3
|
+
export declare function loadConfigOrExit(configPath?: string): BellwetherConfig;
|
|
4
|
+
export declare function getSessionTokenOrExit(sessionOverride?: string, message?: string): string;
|
|
5
|
+
export declare function createAuthenticatedClient(sessionToken: string, message?: string): BellwetherCloudClient;
|
|
6
|
+
export declare function resolveProjectId(projectIdArg?: string, projectOption?: string): string | undefined;
|
|
7
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { loadConfig, ConfigNotFoundError } from '../../../config/loader.js';
|
|
2
|
+
import { getSessionToken, getLinkedProject } from '../../../cloud/auth.js';
|
|
3
|
+
import { createCloudClient } from '../../../cloud/client.js';
|
|
4
|
+
import { EXIT_CODES } from '../../../constants.js';
|
|
5
|
+
import * as output from '../../output.js';
|
|
6
|
+
export function loadConfigOrExit(configPath) {
|
|
7
|
+
try {
|
|
8
|
+
return loadConfig(configPath);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error instanceof ConfigNotFoundError) {
|
|
12
|
+
output.error(error.message);
|
|
13
|
+
process.exit(EXIT_CODES.ERROR);
|
|
14
|
+
}
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function getSessionTokenOrExit(sessionOverride, message) {
|
|
19
|
+
const sessionToken = sessionOverride ?? getSessionToken();
|
|
20
|
+
if (!sessionToken) {
|
|
21
|
+
output.error(message ?? 'Not authenticated. Run `bellwether login` first.');
|
|
22
|
+
process.exit(EXIT_CODES.ERROR);
|
|
23
|
+
}
|
|
24
|
+
return sessionToken;
|
|
25
|
+
}
|
|
26
|
+
export function createAuthenticatedClient(sessionToken, message) {
|
|
27
|
+
const client = createCloudClient({ sessionToken });
|
|
28
|
+
if (!client.isAuthenticated()) {
|
|
29
|
+
output.error(message ?? 'Authentication failed. Run `bellwether login` to re-authenticate.');
|
|
30
|
+
process.exit(EXIT_CODES.ERROR);
|
|
31
|
+
}
|
|
32
|
+
return client;
|
|
33
|
+
}
|
|
34
|
+
export function resolveProjectId(projectIdArg, projectOption) {
|
|
35
|
+
const explicit = projectOption ?? projectIdArg;
|
|
36
|
+
if (explicit) {
|
|
37
|
+
return explicit;
|
|
38
|
+
}
|
|
39
|
+
const link = getLinkedProject();
|
|
40
|
+
return link?.projectId;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teams command for managing team selection in Bellwether Cloud.
|
|
3
|
+
*
|
|
4
|
+
* Allows users to list their teams and switch the active team for API requests.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
export declare const teamsCommand: Command;
|
|
8
|
+
//# sourceMappingURL=teams.d.ts.map
|