diff --git a/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc new file mode 120000 index 0000000..6100270 --- /dev/null +++ b/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc @@ -0,0 +1 @@ +../../CLAUDE.md \ No newline at end of file diff --git a/.github/.DS_Store b/.github/.DS_Store deleted file mode 100644 index 1f2c43e..0000000 Binary files a/.github/.DS_Store and /dev/null differ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b79a4a6..ab54b24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,29 +1,157 @@ -name: Release CLI +name: Release CLI (macOS test) on: + workflow_dispatch: + inputs: + publish_release: + description: "Publish / update the test prerelease (v0.0.0-test)" + type: boolean + required: false + default: true + push: - tags: - - 'v*' + branches: + - 1password/integration + +concurrency: + group: release-test-macos + cancel-in-progress: true jobs: - goreleaser: - runs-on: ubuntu-latest + build-and-release-macos: + runs-on: macos-latest + permissions: + contents: write + steps: - - name: Checkout + - name: Checkout (full history for tagging) uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v1 with: - go-version: '1.23' + bun-version: latest + + - name: Install deps + run: bun install --ignore-scripts + + - name: Patch 1Password SDK for bundling + shell: bash + run: | + echo "🔧 Patching 1Password SDK..." + bun run scripts/patch-1password.ts + + echo "" + echo "Verifying patch..." + if grep -q "ENKRYPTIFY BUNDLING PATCH" node_modules/@1password/sdk-core/nodejs/index.js; then + echo "✅ SDK successfully patched" + else + echo "❌ SDK patch verification failed" + exit 1 + fi + + - name: Build macOS binaries (x64 + arm64) + shell: bash + run: | + set -euo pipefail + mkdir -p dist/bin dist/archives + + bun build src/cli.ts \ + --compile \ + --minify-whitespace \ + --compile-autoload-tsconfig \ + --compile-autoload-package-json \ + --target=bun-darwin-x64 \ + --outfile=dist/bin/ek-darwin-x64 + + bun build src/cli.ts \ + --compile \ + --minify-whitespace \ + --compile-autoload-tsconfig \ + --compile-autoload-package-json \ + --target=bun-darwin-arm64 \ + --outfile=dist/bin/ek-darwin-arm64 + + # Copy WASM file needed by 1Password SDK + if [ -f "node_modules/@1password/sdk-core/nodejs/core_bg.wasm" ]; then + cp node_modules/@1password/sdk-core/nodejs/core_bg.wasm dist/bin/ + echo "✅ Copied 1Password WASM file" + else + echo "❌ ERROR: 1Password WASM file not found!" + exit 1 + fi + + # Create archives with binary and WASM file + tar -czf dist/archives/enkryptify_Darwin_x86_64.tar.gz -C dist/bin ek-darwin-x64 core_bg.wasm + tar -czf dist/archives/enkryptify_Darwin_arm64.tar.gz -C dist/bin ek-darwin-arm64 core_bg.wasm - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + # Generate checksums + (cd dist/archives && shasum -a 256 * > checksums.txt) + + # Show what we built + echo "" + echo "📦 Built artifacts:" + ls -lh dist/archives/ + echo "" + echo "Archive contents:" + tar -tzf dist/archives/enkryptify_Darwin_arm64.tar.gz + + - name: Move tag v0.0.0-test to this commit + if: ${{ github.event_name == 'push' || inputs.publish_release }} + shell: bash + run: | + set -euo pipefail + git config user.name "Enkryptify Bot" + git config user.email "bot@enkryptify.com" + git tag -f v0.0.0-test + git push -f origin v0.0.0-test + + - name: Publish / update GitHub prerelease (v0.0.0-test) + if: ${{ github.event_name == 'push' || inputs.publish_release }} + uses: softprops/action-gh-release@v2 with: - distribution: goreleaser - version: latest - args: release --clean + tag_name: v0.0.0-test + name: Test Release v0.0.0-test + prerelease: true + overwrite_files: true + files: | + dist/archives/* + body: | + ⚠️ Test prerelease from branch `1password/integration` + Commit: ${{ github.sha }} + + ## Installation & Usage + + ### macOS (Apple Silicon - M1/M2/M3) + ```bash + # Download and extract + curl -L -O https://github.com/${{ github.repository }}/releases/download/v0.0.0-test/enkryptify_Darwin_arm64.tar.gz + tar -xzf enkryptify_Darwin_arm64.tar.gz + + # Make executable and remove quarantine + chmod +x ek-darwin-arm64 + xattr -dr com.apple.quarantine ek-darwin-arm64 || true + + # Run (ensure core_bg.wasm is in the same directory) + ./ek-darwin-arm64 --help + ``` + + ### macOS (Intel) + ```bash + # Download and extract + curl -L -O https://github.com/${{ github.repository }}/releases/download/v0.0.0-test/enkryptify_Darwin_x86_64.tar.gz + tar -xzf enkryptify_Darwin_x86_64.tar.gz + + # Make executable and remove quarantine + chmod +x ek-darwin-x64 + xattr -dr com.apple.quarantine ek-darwin-x64 || true + + # Run (ensure core_bg.wasm is in the same directory) + ./ek-darwin-x64 --help + ``` + + ⚠️ **Important**: Both the executable and `core_bg.wasm` must be in the same directory for the CLI to work. env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index f827ec8..b12e5a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,36 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store ek -.DS_Store \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 84a8493..0000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,111 +0,0 @@ -project_name: enkryptify - -before: - hooks: - - go mod tidy - -builds: - - binary: ek - env: - - CGO_ENABLED=0 - goos: - - darwin - - freebsd - - linux - - netbsd - - openbsd - - windows - goarch: - - 386 - - amd64 - - arm - - arm64 - goarm: - - 6 - - 7 - ignore: - - goos: darwin - goarch: '386' - - goos: windows - goarch: '386' - - goos: freebsd - goarch: '386' - ldflags: - - -s -w - - -X main.version={{.Version}} - - -X main.commit={{.Commit}} - - -X main.date={{.Date}} - -archives: - - format: tar.gz - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }} - format_overrides: - - goos: windows - format: zip - -checksum: - name_template: 'checksums.txt' - algorithm: sha256 - -changelog: - sort: asc - filters: - exclude: - - '^docs:' - - '^test:' - - '^ci:' - - Merge pull request - - Merge branch - -brews: - - name: enkryptify - repository: - owner: enkryptify - name: homebrew-enkryptify - commit_author: - name: 'Enkryptify Bot' - email: 'bot@enkryptify.com' - directory: Formula - homepage: 'https://enkryptify.com' - description: 'Official Enkryptify CLI for injecting secrets into your codebase' - license: 'GPL-3.0-only' - test: | - system "#{bin}/ek --version" - -nfpms: - - id: enkryptify - file_name_template: >- - {{ .PackageName }}_ - {{- .Version }}_ - {{- .Os }}_ - {{- if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- with .Arm }}v{{ . }}{{ end }} - {{- with .Mips }}_{{ . }}{{ end }} - {{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }} - homepage: 'https://enkryptify.com' - maintainer: Enkryptify Bot - description: 'Official Enkryptify CLI for injecting secrets into your codebase' - license: GPL-3.0-only - bindir: /usr/bin - formats: - - deb - - rpm - - apk - -scoops: - - repository: - owner: Enkryptify - name: scoop-enkryptify - commit_author: - name: "Enkryptify Bot" - email: bot@enkryptify.com - homepage: "https://enkryptify.com" - description: "Official Enkryptify CLI for injecting secrets into your codebase" - license: GPL-3.0-only diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..485f496 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,15 @@ +{ + "singleQuote": false, + "semi": true, + "trailingComma": "all", + "printWidth": 120, + "tabWidth": 4, + "overrides": [ + { + "files": ["bun.lock", "package.json"], + "options": { + "requirePragma": true + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..515bea7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ], + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "always", + "source.organizeImports": "always" + }, + "[mdx]": { + "editor.defaultFormatter": null, + "editor.formatOnSave": false + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b8100b7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,111 @@ +--- +description: Use Bun instead of Node.js, npm, pnpm, or vite. +globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" +alwaysApply: false +--- + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; + +// import .css files directly and it works +import './index.css'; + +import { createRoot } from "react-dom/client"; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/LICENSE b/LICENSE index f288702..66bf8a1 100644 --- a/LICENSE +++ b/LICENSE @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + A CLI tool to inject secrets from Enkryptify and other providers in a secure way without .env files + Copyright (C) 2025 Enkryptify BV This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) + Enkryptify CLI Copyright (C) 2025 Enkryptify BV This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3da5932 --- /dev/null +++ b/README.md @@ -0,0 +1,751 @@ +# Enkryptify CLI + +Enkryptify CLI is a command-line tool that securely manages and injects secrets from various secret management providers (Enkryptify, AWS, etc.) directly into your commands and applications. + +**Key Features:** + +## Quick Start + +### 1. Login to Your Provider + +```bash +ek login +``` + +This will authenticate you with your default provider (Enkryptify). For other providers: + +```bash +ek login --provider enkryptify +ek login --provider aws +``` + +### 2. Configure Your Project + +Navigate to your project directory and run the configure command. You only need to do this once per project (works for both backend and frontend): + +```bash +cd /path/to/your/project +ek configure +``` + +The configuration process varies by provider. For Enkryptify, the interactive setup will guide you through: + +- Selecting your workspace +- Choosing your project +- Selecting the environment + +Once configured, the CLI automatically detects your project settings when you run commands from this directory or any subdirectory. + +### 3. Run Commands with Secrets + +Run any command with secrets automatically injected as environment variables. The `--` separator is optional (only needed if you have flags that might conflict): + +```bash +ek run npm start +ek run python app.py +ek run --env production npm run deploy +ek run -- pnpm run dev # -- is optional +``` + +Secrets are automatically fetched from your provider and injected as environment variables, making them available to your application at runtime. + +--- + +## Architecture + +## File Structure + +``` +src/ +├── cmd/ # CLI command handlers +│ ├── index.ts # Command registry +│ ├── login.ts +│ ├── configure.ts +│ ├── run.ts +│ ├── listCommand.ts +│ ├── create.ts +│ ├── update.ts +│ └── delete.ts +├── providers/ # Provider implementations +│ ├── base/ # Interfaces +│ │ ├── Provider.ts +│ │ └── AuthProvider.ts +│ ├── registry/ # Provider registry +│ │ ├── ProviderRegistry.ts +│ │ └── index.ts +│ └── enkryptfiy/ # Enkryptify provider +│ ├── provider.ts +│ ├── auth.ts +│ └── httpClient.ts +├── lib/ # Shared utilities +│ ├── config.ts # All config management (auth + project) +│ ├── inject.ts # Secret injection +│ ├── keyring.ts # Secure keyring storage +│ ├── error.ts # Error handling +│ ├── input.ts # User input helpers +│ └── terminal.ts # Terminal utilities +└── ui/ # Ink UI components + ├── LoginFlow.tsx + ├── RunFlow.tsx + ├── EnkryptifyLogin.tsx + ├── AwsLogin.tsx + ├── SecretsTable.tsx + ├── SelectItem.tsx + ├── Confirm.tsx + └── SuccessMessage.tsx +``` + +--- + +## Folder Explanations + +### `cmd/` Folder - Command Handlers + +**Purpose:** Contains all CLI command implementations. Commands are thin coordinators that delegate all actual work to providers. + +**What it does:** + +- Defines command structure (name, options, arguments) +- Validates user input +- Gets provider from registry +- Delegates to provider methods +- Handles errors consistently + +**Key principle:** Commands don't contain provider-specific logic. They coordinate the flow but let providers do the work. + +### `providers/` Folder - Provider Implementations + +**Purpose:** Contains all secret management provider integrations. Each provider knows how to authenticate, fetch secrets, and perform CRUD operations on secrets. + +**What it does:** + +- Implements provider-specific logic (API calls, data transformation) +- Handles authentication via AuthProvider +- Manages secrets (list, create, update, delete, inject) +- Provides project configuration flows + +**Key principle:** All providers implement the same `Provider` interface, so commands work with any provider without knowing which one. + +### `lib/` Folder - Shared Utilities + +**Purpose:** Contains reusable utilities used across the entire CLI. + +**What it does:** + +- Manages configuration (project configs, provider settings) +- Handles secret injection into environment variables +- Provides secure credential storage via system keyring +- Offers consistent error logging and user input helpers + +**Key principle:** Single source of truth for common operations. Used by commands, providers, and UI components. + +### `ui/` Folder - Terminal UI Components + +**Purpose:** Contains React components that render in the terminal using Ink. + +**What it does:** + +- Provides interactive UI for commands +- Shows loading states and progress +- Displays data in tables and formatted views +- Handles user selections and confirmations + +--- + +## Detailed File Explanations + +### `cmd/` Folder Files + +#### `cmd/index.ts` - Command Registry + +Central registry where all commands are registered. + +```typescript +export function registerCommands(program: Command) { + registerLoginCommand(program); + registerConfigureCommand(program); + registerRunCommand(program); + registerListCommand(program); + registerCreateCommand(program); + registerUpdateCommand(program); + registerDeleteCommand(program); +} +``` + +**How it works:** + +- `src/cli.ts` calls this on startup +- All commands become available to users +- To add a command: create file in `cmd/`, import and register here + +#### `cmd/login.ts` - Login Command Example + +Complete example showing how commands work: + +```typescript +export function registerLoginCommand(program: Command) { + program + .command("login") + .option("-p, --provider ", "Provider name") + .option("-f, --force", "Force re-authentication") + .action(async (options) => { + // 1. Determine provider + const providerName = options.provider || "enkryptify"; + + // 2. Get provider from registry + const provider = providerRegistry.get(providerName); + if (!provider) { + logError(`Provider "${providerName}" not found`); + process.exit(1); + } + + // 3. Show UI flow (which calls provider.login internally) + await LoginFlow({ provider, options }); + }); +} +``` + +**Pattern:** Validate → Get Provider → Delegate → Handle Errors + +#### Other Command Files + +All commands follow the same pattern: + +- `cmd/configure.ts` - Gets provider → calls `provider.configure()` → saves config +- `cmd/run.ts` - Finds config → gets provider → calls `provider.run()` → injects secrets → executes command +- `cmd/list.ts` - Finds config → gets provider → calls `provider.listSecrets()` → displays table +- `cmd/create.ts` - Finds config → gets provider → calls `provider.createSecret()` +- `cmd/update.ts` - Finds config → gets provider → calls `provider.updateSecret()` +- `cmd/delete.ts` - Finds config → gets provider → calls `provider.deleteSecret()` + +--- + +### `providers/` Folder Files + +#### `providers/base/Provider.ts` - Provider Interface + +**This is the main interface that ALL providers must implement.** + +The Provider interface defines methods for: + +1. **Authentication** - `login()` - delegates to AuthProvider +2. **Project Setup** - `configure()` - guides user through workspace/project/environment selection +3. **Secret CRUD Operations**: + - `listSecrets()` - List all secrets (with optional value display) + - `createSecret()` - Create a new secret + - `updateSecret()` - Update an existing secret + - `deleteSecret()` - Delete a secret +4. **Secret Injection** - `run()` - Fetch secrets and return them for injection + +```typescript +export interface Provider { + readonly name: string; + + // Authentication (delegates to AuthProvider) + login(options?: LoginOptions): Promise; + + // Project configuration + configure(options: string): Promise; + + // Secret CRUD operations + listSecrets(config: ProjectConfig, showValues?: string): Promise; + createSecret(config: ProjectConfig, name: string, value: string): Promise; + updateSecret(config: ProjectConfig, name: string, isPersonal?: boolean): Promise; + deleteSecret(config: ProjectConfig, name: string): Promise; + + // Secret injection (fetch secrets for running commands) + run(config: ProviderConfig, options?: runOptions): Promise; +} +``` + +**Key Points:** + +- Provider handles ALL secret management operations +- Provider.login() is just a delegation method - it calls AuthProvider.login() +- Commands use this interface, so they work with any provider + +#### `providers/base/AuthProvider.ts` - Authentication Interface + +**AuthProvider is ONLY responsible for authentication and credential storage.** + +It does NOT handle secrets. It only: + +1. **Login** - `login()` - Handles OAuth flow, token exchange, etc. +2. **Get Credentials** - `getCredentials()` - Retrieves stored tokens/credentials + +```typescript +export interface AuthProvider { + // Handle authentication flow (OAuth, PKCE, etc.) + login(options?: LoginOptions): Promise; + + // Retrieve stored credentials (tokens, API keys, etc.) + getCredentials(): Promise; +} +``` + +**Key Points:** + +- AuthProvider ONLY handles login and credential storage +- AuthProvider does NOT know about secrets +- Provider makes HTTP requests - HTTP client interceptor automatically gets token from keyring and adds to request headers +- `auth.getCredentials()` exists but is rarely needed - HTTP client handles it automatically +- Separation of concerns: Auth = AuthProvider, Secrets = Provider + +#### How Provider and AuthProvider Work Together + +```typescript +export class EnkryptifyProvider implements Provider { + private auth: EnkryptifyAuth; // AuthProvider instance + + constructor() { + this.auth = new EnkryptifyAuth(); + } + + // Provider.login() just delegates to AuthProvider + async login(options?: LoginOptions): Promise { + await this.auth.login(options); + // AuthProvider handles: + // - OAuth PKCE flow + // - Browser opening + // - Token exchange + // - Storing credentials in keyring + } + + // Provider methods make HTTP requests + // HTTP client interceptor automatically gets token from keyring and adds to headers + async run(config: ProjectConfig, options?: runOptions): Promise { + // HTTP client automatically injects token via interceptor + // No need to manually call auth.getCredentials() + const response = await http.get("/v1/secrets"); + + // Transform and return secrets + return transformSecrets(response.data); + } + + // Same pattern for other methods + // HTTP client handles authentication automatically + async listSecrets(config: ProjectConfig): Promise { + const response = await http.get("/v1/secrets"); + // Transform and return... + } + + async createSecret(config: ProjectConfig, name: string, value: string): Promise { + await http.post("/v1/secrets", { name, value }); + // HTTP client automatically adds auth token + } +} +``` + +**Flow:** + +1. User runs `ek login` +2. Command calls `LoginFlow({ provider })` - shows UI +3. UI component calls `provider.login()` +4. Provider delegates to `authProvider.login()` - handles OAuth, stores credentials in keyring +5. User runs `ek run npm start` +6. Command calls `provider.run()` +7. Provider makes HTTP request - HTTP client interceptor automatically gets token from keyring and adds to headers +8. Provider returns secrets +9. Command injects secrets and runs user's command + +#### `providers/registry/ProviderRegistry.ts` - Provider Storage + +Central registry that stores and retrieves providers. + +```typescript +export class ProviderRegistry { + private providers: Map = new Map(); + + register(provider: Provider): void { + this.providers.set(provider.name, provider); + } + + get(name: string): Provider | undefined { + return this.providers.get(name); + } + + list(): Provider[] { + return Array.from(this.providers.values()); + } + + has(name: string): boolean { + return this.providers.has(name); + } +} + +export const providerRegistry = new ProviderRegistry(); +``` + +**How it works:** + +- Singleton instance shared across application +- Providers register on startup in `providers/registry/index.ts` +- Commands look up providers by name + +**`providers/registry/index.ts` - Registration Point** + +```typescript +import { EnkryptifyProvider } from "@/providers/enkryptfiy/provider"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; + +// Register all providers here +providerRegistry.register(new EnkryptifyProvider()); +// Future: providerRegistry.register(new AwsProvider()); + +export { providerRegistry }; +``` + +**To add a new provider:** + +1. Create provider class implementing `Provider` interface +2. Create AuthProvider class implementing `AuthProvider` interface +3. **Create login UI component** in `ui/` folder (e.g., `ui/YourProviderLogin.tsx`) +4. **Register UI component** in `ui/LoginFlow.tsx` (add case in switch statement) +5. Import and register provider in `providers/registry/index.ts` +6. Done! Commands automatically work with new provider + +**Important:** You MUST create and register a login UI component. Without it, the login command will show "Unknown provider" error. + +--- + +### `lib/` Folder Files + +#### `lib/config.ts` - Configuration Management + +Manages all configuration (project configs and provider settings) in `~/.enkryptify/config.json`. + +**Available Methods:** + +```typescript +export const config = { + // Project configuration + findProjectConfig(startPath: string): Promise + // Walks up directory tree to find project config + + getConfigure(projectPath: string): Promise + // Gets exact project config + + createConfigure(projectPath: string, projectConfig: ProjectConfig): Promise + // Saves project configuration + + // Provider settings + getProvider(providerName: string): Promise | null> + // Gets provider settings + + updateProvider(providerName: string, settings: Record): Promise + // Updates provider settings +}; +``` + +#### `lib/inject.ts` - Secret Injection + +Safely injects secrets as environment variables. + +```typescript +export function buildEnvWithSecrets(secrets: Secret[]): typeof process.env { + const env = { ...process.env }; + + for (const secret of secrets) { + // Protect dangerous env vars (PATH, LD_PRELOAD, etc.) + if (isDangerousEnvVar(secret.name)) { + console.warn(`Warning: ${secret.name} is protected`); + continue; + } + env[secret.name] = secret.value; + } + + return env; +} +``` + +**Features:** + +- Merges secrets into environment variables +- Protects dangerous vars from override +- Validates secret names + +#### `lib/keyring.ts` - Secure Credential Storage + +Provides secure storage using OS keyring. + +```typescript +export const keyring = { + set(key: string, value: string): Promise, + get(key: string): Promise, + delete(key: string): Promise, + has(key: string): Promise, +}; +``` + +**Security:** + +- Uses OS keyring (macOS Keychain, Windows Credential Manager, Linux Secret Service) +- Credentials never in plain text files +- Service name: `"enkryptify-cli"` + +#### Other `lib/` Files + +- `lib/error.ts` - Consistent error logging (`logError()`) +- `lib/input.ts` - User input helpers (`getTextInput()`, `getSecureInput()`) +- `lib/terminal.ts` - Terminal utilities (`setupTerminalCleanup()`) + +--- + +### `ui/` Folder Files + +#### How Ink Works + +Ink is React for the terminal. Uses React patterns but renders to terminal: + +- `` = layout container +- `` = text display +- No CSS - use props like `padding`, `color`, etc. + +#### `ui/LoginFlow.tsx` - Login Flow Orchestrator + +Orchestrates login UI and routes to provider-specific components. + +```typescript +import { EnkryptifyLogin } from "./EnkryptifyLogin"; +import { AwsLogin } from "./AwsLogin"; +import { YourProviderLogin } from "./YourProviderLogin"; // Import your component + +function LoginFlowComponent({ provider, options, onError, onComplete }) { + const renderProviderComponent = () => { + switch (provider.name) { + case "enkryptify": + return ; + case "aws": + return ; + case "yourprovider": // Add your provider case + return ; + default: + return Unknown provider: {provider.name}; + } + }; + + return {renderProviderComponent()}; +} +``` + +**Important:** When adding a new provider, you MUST: + +1. Create a login UI component (e.g., `ui/YourProviderLogin.tsx`) +2. Import it in `ui/LoginFlow.tsx` +3. Add a case in the switch statement for your provider name + +#### `ui/EnkryptifyLogin.tsx` - Provider-Specific Login UI + +Shows loading state and calls provider.login(). + +```typescript +export function EnkryptifyLogin({ provider, onComplete, onError }) { + const [status, setStatus] = useState("loading"); + + useEffect(() => { + const performLogin = async () => { + try { + setMessage("Authenticating..."); + await provider.login(options); // Calls Provider.login() + setStatus("success"); + onComplete(); + } catch (error) { + setStatus("error"); + onError(error); + } + }; + void performLogin(); + }, []); + + return ( + + {status === "loading" && } + {status === "success" && ✓ Success} + {status === "error" && Error} + + ); +} +``` + +--- + +## How Everything Works Together - Complete Flow + +Example: User runs `ek login --provider enkryptify` + +``` +1. cli.ts + - Parses command: "login" + - Routes to registerLoginCommand + +2. cmd/login.ts + - Gets provider from registry: providerRegistry.get("enkryptify") + - Calls LoginFlow({ provider }) + +3. ui/LoginFlow.tsx + - Renders EnkryptifyLogin component + +4. ui/EnkryptifyLogin.tsx + - Shows "Authenticating..." spinner + - Calls provider.login(options) + +5. providers/enkryptfiy/provider.ts + - Delegates to auth.login(options) + +6. providers/enkryptfiy/auth.ts + - Runs OAuth PKCE flow + - Opens browser + - User authenticates + - Exchanges code for token + - Stores in keyring via lib/keyring.ts + +7. Flow completes + - UI shows "✓ Success" + - Command completes +``` + +--- + +## Configuration + +The CLI stores all configuration in `~/.enkryptify/config.json`. + +### Config File Structure + +```json +{ + "setups": { + "/absolute/path/to/project": { + "provider": "enkryptify", + "workspace_slug": "workspace-name", + "workspace_name": "Workspace Name", + "project_slug": "project-name", + "project_name": "Project Name", + "environment_id": "uuid-here" + } + }, + "providers": { + "enkryptify": {} + } +} +``` + +- **`setups`**: Maps project directory paths to their configuration +- **`providers`**: Provider-specific settings + +The CLI automatically finds the configuration for your current project by walking up the directory tree. + +--- + +## Commands + +### `ek login [--provider ] [--force]` + +Authenticate with a secret management provider. + +**Options:** + +- `--provider, -p ` - Provider name (defaults to 'enkryptify') +- `--force, -f` - Force re-authentication even if already logged in + +**Examples:** + +```bash +ek login +ek login --provider enkryptify +ek login --force # Re-authenticate +``` + +### `ek configure [provider]` + +Configure your project to use a specific provider and environment. + +**Examples:** + +```bash +ek configure +ek configure enkryptify +``` + +### `ek run [--env ] -- ` + +Run a command with secrets injected as environment variables. + +**Options:** + +- `--env, -e ` - Environment name to use (overrides default from config) for this run only. + +**Note:** The `--` separator is **optional**. You can run commands directly without it. Only use `--` if you have flags that might conflict with the CLI's options. + +**Examples:** + +```bash +ek run npm start +ek run python app.py +ek run --env production -- npm run deploy +ek run -- pnpm run dev # -- is optional +``` + +### `ek list [--show]` + +List all secrets in the current environment. + +**Options:** + +- `--show, -s` - Show secret values (default: masked) + +**Examples:** + +```bash +ek list +ek list --show # Show actual values +``` + +### `ek create [value]` + +Create a new secret in the current environment. + +**Arguments:** + +- `` - Secret name (required) - can only contain A-Z, a-z, 0-9, underscore (\_), and hyphen (-) +- `[value]` - Secret value (optional, will prompt if not provided) use "" for complex values + +**Examples:** + +```bash +ek create DATABASE_URL "postgres://..." +ek create API_KEY # Will prompt for value +``` + +### `ek update [--personal]` + +Update an existing secret. + +**Arguments:** + +- `` - Secret name to update + +**Options:** + +- `--personal` - Mark as personal secret (overrides team secret) + +**Examples:** + +```bash +ek update DATABASE_URL +ek update API_KEY --personal +``` + +### `ek delete ` + +Delete a secret. + +**Arguments:** + +- `` - Secret name to delete + +**Examples:** + +```bash +ek delete OLD_SECRET +``` diff --git a/bin/ek-darwin-arm64 b/bin/ek-darwin-arm64 new file mode 100755 index 0000000..c1ff5bd Binary files /dev/null and b/bin/ek-darwin-arm64 differ diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..53c020b --- /dev/null +++ b/bun.lock @@ -0,0 +1,1372 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "cli", + "dependencies": { + "@1password/sdk": "^0.3.1", + "@aws-sdk/client-secrets-manager": "^3.950.0", + "@aws-sdk/client-sso-oidc": "^3.946.0", + "@aws-sdk/client-sts": "^3.948.0", + "@aws-sdk/credential-provider-sso": "^3.946.0", + "@google-cloud/resource-manager": "^6.2.1", + "@google-cloud/secret-manager": "^6.1.1", + "@t3-oss/env-core": "^0.13.8", + "axios": "^1.13.2", + "commander": "^14.0.2", + "dotenv": "^17.2.3", + "google-auth-library": "^10.5.0", + "ink": "^6.5.1", + "ink-big-text": "^2.0.0", + "ink-gradient": "^3.0.0", + "ink-select-input": "^6.2.0", + "ink-spinner": "^5.0.0", + "ink-table": "^3.1.0", + "ink-text-input": "^6.0.0", + "keytar": "^7.9.0", + "open": "^11.0.0", + "prompts": "^2.4.2", + "react": "^19.2.0", + "react-devtools-core": "^6.1.2", + "semver": "^7.7.3", + "zod": "^4.1.13", + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/bun": "^1.3.3", + "@types/node": "^24.10.1", + "@types/prompts": "^2.4.9", + "@types/react": "^19.2.7", + "@types/semver": "^7.7.1", + "@typescript-eslint/eslint-plugin": "^8.49.0", + "@typescript-eslint/parser": "^8.49.0", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-security": "^3.0.1", + "globals": "^16.5.0", + "jiti": "^2.6.1", + "pkg": "^5.8.1", + "prettier": "^3.7.3", + "typescript-eslint": "^8.48.1", + }, + "peerDependencies": { + "typescript": "^5.9.3", + }, + }, + }, + "packages": { + "@1password/sdk": ["@1password/sdk@0.3.1", "", { "dependencies": { "@1password/sdk-core": "0.3.1" } }, "sha512-20zbQfqsjcECT0gvnAw4zONJDt3XQgNH946pZR0NV1Qxukyaz/DKB0cBnBNCCEWZg93Bah8poaR6gJCyuNX14w=="], + + "@1password/sdk-core": ["@1password/sdk-core@0.3.1", "", {}, "sha512-zFkbRznmE47kpke10OpO/9R0AF5csNWS+naFbadgXuFX1LlxY+2C28NSKbCXhLTqmcuWifBfPdZQ728GJ1i5xg=="], + + "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-mkOh+Wwawzuf5wa30bvc4nA+Qb6DIrGWgBhRR/Pw4T9nsgYait8izvXkNyU78D6Wcu3Z+KUdwCmLCxlWjEotYA=="], + + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/client-secrets-manager": ["@aws-sdk/client-secrets-manager@3.950.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/credential-provider-node": "3.948.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aOI7W+axLah68a0fOLafu8aIrLuGqyuDp6AsgaKlOVNwr2R3a7WaPquNgL9FPzYydiKKVbUkHHFvraicQIQdOw=="], + + "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.948.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iWjchXy8bIAVBUsKnbfKYXRwhLgRg3EqCQ5FTr3JbR+QR75rZm4ZOYXlvHGztVTmtAZ+PQVA1Y4zO7v7N87C0A=="], + + "@aws-sdk/client-sso-oidc": ["@aws-sdk/client-sso-oidc@3.948.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/credential-provider-node": "3.948.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-e5MC9Lsh9kLXVSlJEQEPeu2gGqNONt+i3w4Cxw9bjR47bZ3m9HISnFLMrfH30+XYoAvb+2t2jRADEDjfDJbuhg=="], + + "@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.948.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/credential-provider-node": "3.948.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-sBcuRA8nKvLqwI5QyBDbnvlNwHmxsBLRGNJmJb2dTby3ClX0SHuF/2EDSzbxUXJOU+iT61E+2FCNqY2xb+MAbg=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.947.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VR2V6dRELmzwAsCpK4GqxUi6UW5WNhAXS9F9AzWi5jvijwJo3nH92YNJUP4quMpgFZxJHEWyXLWgPjh9u0zYOA=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-inF09lh9SlHj63Vmr5d+LmwPXZc2IbK8lAruhOr3KLsZAIHEgHgGPXWDC2ukTEMzg0pkexQ6FOhXXad6klK4RA=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.948.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/credential-provider-env": "3.947.0", "@aws-sdk/credential-provider-http": "3.947.0", "@aws-sdk/credential-provider-login": "3.948.0", "@aws-sdk/credential-provider-process": "3.947.0", "@aws-sdk/credential-provider-sso": "3.948.0", "@aws-sdk/credential-provider-web-identity": "3.948.0", "@aws-sdk/nested-clients": "3.948.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Cl//Qh88e8HBL7yYkJNpF5eq76IO6rq8GsatKcfVBm7RFVxCqYEPSSBtkHdbtNwQdRQqAMXc6E/lEB/CZUDxnA=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.948.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/nested-clients": "3.948.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-gcKO2b6eeTuZGp3Vvgr/9OxajMrD3W+FZ2FCyJox363ZgMoYJsyNid1vuZrEuAGkx0jvveLXfwiVS0UXyPkgtw=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.948.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.947.0", "@aws-sdk/credential-provider-http": "3.947.0", "@aws-sdk/credential-provider-ini": "3.948.0", "@aws-sdk/credential-provider-process": "3.947.0", "@aws-sdk/credential-provider-sso": "3.948.0", "@aws-sdk/credential-provider-web-identity": "3.948.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ep5vRLnrRdcsP17Ef31sNN4g8Nqk/4JBydcUJuFRbGuyQtrZZrVT81UeH2xhz6d0BK6ejafDB9+ZpBjXuWT5/Q=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-WpanFbHe08SP1hAJNeDdBDVz9SGgMu/gc0XJ9u3uNpW99nKZjDpvPRAdW7WLA4K6essMjxWkguIGNOpij6Do2Q=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.948.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.948.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/token-providers": "3.948.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-gqLhX1L+zb/ZDnnYbILQqJ46j735StfWV5PbDjxRzBKS7GzsiYoaf6MyHseEopmWrez5zl5l6aWzig7UpzSeQQ=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.948.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/nested-clients": "3.948.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-MvYQlXVoJyfF3/SmnNzOVEtANRAiJIObEUYYyjTqKZTmcRIVVky0tPuG26XnB8LmTYgtESwJIZJj/Eyyc9WURQ=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.948.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Qa8Zj+EAqA0VlAVvxpRnpBpIWJI9KUwaioY1vkeNVwXPlNaz9y9zCKVM9iU9OZ5HXpoUg6TnhATAHXHAE8+QsQ=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.7", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-7rpKV8YNgCP2R4F9RjWZFcD2R+SO/0R4VHIbY9iZJdH2MzzJ8ZG7h8dZ2m8QkQd1fjx4wrFJGGPJUTYXPV3baA=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.948.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.948.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zcbJfBsB6h254o3NuoEkf0+UY1GpE9ioiQdENWv7odo69s8iaGBEQ4BDpsIMqcuiiUXw1uKIVNxCB1gUGYz8lw=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.948.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/nested-clients": "3.948.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-V487/kM4Teq5dcr1t5K6eoUKuqlGr9FRWL3MIMukMERJXHZvio6kox60FZ/YtciRHRI75u14YUqm2Dzddcu3+A=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.947.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-+vhHoDrdbb+zerV4noQk1DHaUMNzWFWPpPYjVTwW2186k5BEJIecAMChYkghRrBVJ3KPWP1+JnZwOd72F3d4rQ=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.2", "", {}, "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg=="], + + "@babel/generator": ["@babel/generator@7.18.2", "", { "dependencies": { "@babel/types": "^7.18.2", "@jridgewell/gen-mapping": "^0.3.0", "jsesc": "^2.5.1" } }, "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.18.4", "", { "bin": "./bin/babel-parser.js" }, "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow=="], + + "@babel/types": ["@babel/types@7.19.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.18.10", "@babel/helper-validator-identifier": "^7.18.6", "to-fast-properties": "^2.0.0" } }, "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], + + "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + + "@google-cloud/resource-manager": ["@google-cloud/resource-manager@6.2.1", "", { "dependencies": { "google-gax": "^5.0.0" } }, "sha512-x/7G3qwLt9e+D2sL3hMS/sa7F0r3mW+b+eikR7IqpJTp4n6l6MPSRo0auprf3xiG16H5IwFaIaJ7HoOaeh5tNw=="], + + "@google-cloud/secret-manager": ["@google-cloud/secret-manager@6.1.1", "", { "dependencies": { "google-gax": "^5.0.0" } }, "sha512-dwSuxJ9RNmAW46FjK1StiNIeOiSHHQs/XIy4VArJ6bBMR+WsIvR+zhPh2pa40aFa9uTty67j38Rl268TVV62EA=="], + + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], + + "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + + "@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], + + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="], + + "@smithy/core": ["@smithy/core@3.18.7", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.6", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="], + + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.14", "", { "dependencies": { "@smithy/core": "^3.18.7", "@smithy/middleware-serde": "^4.2.6", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@4.9.10", "", { "dependencies": { "@smithy/core": "^3.18.7", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ=="], + + "@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="], + + "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.13", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.16", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + + "@t3-oss/env-core": ["@t3-oss/env-core@0.13.8", "", { "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0-beta.0" }, "optionalPeers": ["arktype", "typescript", "valibot", "zod"] }, "sha512-L1inmpzLQyYu4+Q1DyrXsGJYCXbtXjC4cICw1uAKv0ppYPQv656lhZPU91Qd1VS6SO/bou1/q5ufVzBGbNsUpw=="], + + "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="], + + "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/gradient-string": ["@types/gradient-string@1.1.6", "", { "dependencies": { "@types/tinycolor2": "*" } }, "sha512-LkaYxluY4G5wR1M4AKQUal2q61Di1yVVCw42ImFTuaIoQVgmV0WP1xUaLB8zwb47mp82vWTpePI9JmrjEnJ7nQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], + + "@types/prompts": ["@types/prompts@2.4.9", "", { "dependencies": { "@types/node": "*", "kleur": "^3.0.3" } }, "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA=="], + + "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], + + "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="], + + "@types/tinycolor2": ["@types/tinycolor2@1.4.6", "", {}, "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.49.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/type-utils": "8.49.0", "@typescript-eslint/utils": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.49.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.49.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.49.0", "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0" } }, "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.49.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.49.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.49.0", "@typescript-eslint/tsconfig-utils": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.49.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], + + "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], + + "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], + + "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], + + "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="], + + "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], + + "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "cfonts": ["cfonts@3.3.1", "", { "dependencies": { "supports-color": "^8", "window-size": "^1" }, "bin": { "cfonts": "bin/index.js" } }, "sha512-ZGEmN3W9mViWEDjsuPo4nK4h39sfh6YtoneFYp9WLPI/rw8BaSSrfQC6jkrGW3JMvV3ZnExJB/AEqXc/nHYxkw=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + + "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cli-truncate": ["cli-truncate@5.1.1", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="], + + "cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], + + "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], + + "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], + + "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + + "define-property": ["define-property@1.0.0", "", { "dependencies": { "is-descriptor": "^1.0.0" } }, "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "duplexify": ["duplexify@4.1.3", "", { "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "es-abstract": ["es-abstract@1.24.0", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-iterator-helpers": ["es-iterator-helpers@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.4", "safe-array-concat": "^1.1.3" } }, "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], + + "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + + "es-toolkit": ["es-toolkit@1.42.0", "", {}, "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], + + "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], + + "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], + + "eslint-plugin-security": ["eslint-plugin-security@3.0.1", "", { "dependencies": { "safe-regex": "^2.1.1" } }, "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "from2": ["from2@2.3.0", "", { "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" } }, "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], + + "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + + "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], + + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], + + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], + + "google-gax": ["google-gax@5.0.6", "", { "dependencies": { "@grpc/grpc-js": "^1.12.6", "@grpc/proto-loader": "^0.8.0", "duplexify": "^4.1.3", "google-auth-library": "^10.1.0", "google-logging-utils": "^1.1.1", "node-fetch": "^3.3.2", "object-hash": "^3.0.0", "proto3-json-serializer": "^3.0.0", "protobufjs": "^7.5.3", "retry-request": "^8.0.0", "rimraf": "^5.0.1" } }, "sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA=="], + + "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "gradient-string": ["gradient-string@2.0.2", "", { "dependencies": { "chalk": "^4.1.2", "tinygradient": "^1.1.5" } }, "sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], + + "has": ["has@1.0.4", "", {}, "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ=="], + + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "ink": ["ink@6.5.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.1", "ansi-escapes": "^7.2.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^8.1.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-wF3j/DmkM8q5E+OtfdQhCRw8/0ahkc8CUTgEddxZzpEWPslu7YPL3t64MWRoI9m6upVGpfAg4ms2BBvxCdKRLQ=="], + + "ink-big-text": ["ink-big-text@2.0.0", "", { "dependencies": { "cfonts": "^3.1.1", "prop-types": "^15.8.1" }, "peerDependencies": { "ink": ">=4", "react": ">=18" } }, "sha512-Juzqv+rIOLGuhMJiE50VtS6dg6olWfzFdL7wsU/EARSL5Eaa5JNXMogMBm9AkjgzO2Y3UwWCOh87jbhSn8aNdw=="], + + "ink-gradient": ["ink-gradient@3.0.0", "", { "dependencies": { "@types/gradient-string": "^1.1.2", "gradient-string": "^2.0.2", "prop-types": "^15.8.1", "strip-ansi": "^7.1.0" }, "peerDependencies": { "ink": ">=4" } }, "sha512-OVyPBovBxE1tFcBhSamb+P1puqDP6pG3xFe2W9NiLgwUZd9RbcjBeR7twLbliUT9navrUstEf1ZcPKKvx71BsQ=="], + + "ink-select-input": ["ink-select-input@6.2.0", "", { "dependencies": { "figures": "^6.1.0", "to-rotated": "^1.0.0" }, "peerDependencies": { "ink": ">=5.0.0", "react": ">=18.0.0" } }, "sha512-304fZXxkpYxJ9si5lxRCaX01GNlmPBgOZumXXRnPYbHW/iI31cgQynqk2tRypGLOF1cMIwPUzL2LSm6q4I5rQQ=="], + + "ink-spinner": ["ink-spinner@5.0.0", "", { "dependencies": { "cli-spinners": "^2.7.0" }, "peerDependencies": { "ink": ">=4.0.0", "react": ">=18.0.0" } }, "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA=="], + + "ink-table": ["ink-table@3.1.0", "", { "dependencies": { "object-hash": "^2.0.3" }, "peerDependencies": { "ink": ">=3.0.0", "react": ">=16.8.0" } }, "sha512-qxVb4DIaEaJryvF9uZGydnmP9Hkmas3DCKVpEcBYC0E4eJd3qNgNe+PZKuzgCERFe9LfAS1TNWxCr9+AU4v3YA=="], + + "ink-text-input": ["ink-text-input@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "type-fest": "^4.18.2" }, "peerDependencies": { "ink": ">=5", "react": ">=18" } }, "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw=="], + + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + + "into-stream": ["into-stream@6.0.0", "", { "dependencies": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" } }, "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA=="], + + "is-accessor-descriptor": ["is-accessor-descriptor@1.0.1", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA=="], + + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], + + "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], + + "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], + + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], + + "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-core-module": ["is-core-module@2.9.0", "", { "dependencies": { "has": "^1.0.3" } }, "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A=="], + + "is-data-descriptor": ["is-data-descriptor@1.0.1", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw=="], + + "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], + + "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], + + "is-descriptor": ["is-descriptor@1.0.3", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], + + "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], + + "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], + + "is-number": ["is-number@3.0.0", "", { "dependencies": { "kind-of": "^3.0.2" } }, "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg=="], + + "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], + + "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], + + "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], + + "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], + + "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], + + "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], + + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsesc": ["jsesc@2.5.2", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="], + + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], + + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + + "keytar": ["keytar@7.9.0", "", { "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" } }, "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], + + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "multistream": ["multistream@4.1.0", "", { "dependencies": { "once": "^1.4.0", "readable-stream": "^3.6.0" } }, "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw=="], + + "napi-build-utils": ["napi-build-utils@1.0.2", "", {}, "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="], + + "node-addon-api": ["node-addon-api@4.3.0", "", {}, "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + + "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + + "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="], + + "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], + + "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + + "p-is-promise": ["p-is-promise@3.0.0", "", {}, "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pkg": ["pkg@5.8.1", "", { "dependencies": { "@babel/generator": "7.18.2", "@babel/parser": "7.18.4", "@babel/types": "7.19.0", "chalk": "^4.1.2", "fs-extra": "^9.1.0", "globby": "^11.1.0", "into-stream": "^6.0.0", "is-core-module": "2.9.0", "minimist": "^1.2.6", "multistream": "^4.1.0", "pkg-fetch": "3.4.2", "prebuild-install": "7.1.1", "resolve": "^1.22.0", "stream-meter": "^1.0.4" }, "peerDependencies": { "node-notifier": ">=9.0.1" }, "optionalPeers": ["node-notifier"], "bin": { "pkg": "lib-es5/bin.js" } }, "sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA=="], + + "pkg-fetch": ["pkg-fetch@3.4.2", "", { "dependencies": { "chalk": "^4.1.2", "fs-extra": "^9.1.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.6", "progress": "^2.0.3", "semver": "^7.3.5", "tar-fs": "^2.1.1", "yargs": "^16.2.0" }, "bin": { "pkg-fetch": "lib-es5/bin.js" } }, "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA=="], + + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + + "prebuild-install": ["prebuild-install@7.1.1", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.7.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + + "proto3-json-serializer": ["proto3-json-serializer@3.0.4", "", { "dependencies": { "protobufjs": "^7.4.0" } }, "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw=="], + + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + + "react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="], + + "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + + "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], + + "regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="], + + "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], + + "retry-request": ["retry-request@8.0.2", "", { "dependencies": { "extend": "^3.0.2", "teeny-request": "^10.0.0" } }, "sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], + + "safe-regex": ["safe-regex@2.1.1", "", { "dependencies": { "regexp-tree": "~0.1.1" } }, "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], + + "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], + + "stream-events": ["stream-events@1.0.5", "", { "dependencies": { "stubs": "^3.0.0" } }, "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg=="], + + "stream-meter": ["stream-meter@1.0.4", "", { "dependencies": { "readable-stream": "^2.1.4" } }, "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ=="], + + "stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="], + + "string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], + + "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], + + "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], + + "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], + + "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + + "stubs": ["stubs@3.0.0", "", {}, "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "teeny-request": ["teeny-request@10.1.0", "", { "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^3.3.2", "stream-events": "^1.0.5" } }, "sha512-3ZnLvgWF29jikg1sAQ1g0o+lr5JX6sVgYvfUJazn7ZjJroDBUTWp44/+cFVX0bULjv4vci+rBD+oGVAkWqhUbw=="], + + "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinygradient": ["tinygradient@1.1.5", "", { "dependencies": { "@types/tinycolor2": "^1.4.0", "tinycolor2": "^1.0.0" } }, "sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw=="], + + "to-fast-properties": ["to-fast-properties@2.0.0", "", {}, "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "to-rotated": ["to-rotated@1.0.0", "", {}, "sha512-KsEID8AfgUy+pxVRLsWp0VzCa69wxzUDZnzGbyIST/bcgcrMvTYoFBX/QORH4YApoD89EDuUovx4BTdpOn319Q=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], + + "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], + + "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], + + "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "typescript-eslint": ["typescript-eslint@8.48.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.1", "@typescript-eslint/parser": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A=="], + + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], + + "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], + + "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], + + "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], + + "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + + "window-size": ["window-size@1.1.1", "", { "dependencies": { "define-property": "^1.0.0", "is-number": "^3.0.0" }, "bin": { "window-size": "cli.js" } }, "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + + "wsl-utils": ["wsl-utils@0.3.0", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], + + "yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + + "zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "@grpc/proto-loader/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "cfonts/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "from2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "gaxios/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "google-gax/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "google-gax/object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + + "ink/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "ink-text-input/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "keytar/prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pkg/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + + "resolve/is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + + "stream-meter/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "teeny-request/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "to-regex-range/is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/type-utils": "8.48.1", "@typescript-eslint/utils": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA=="], + + "typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA=="], + + "typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.1", "@typescript-eslint/tsconfig-utils": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg=="], + + "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA=="], + + "widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@grpc/proto-loader/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@grpc/proto-loader/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@grpc/proto-loader/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "from2/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "from2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "from2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "keytar/prebuild-install/napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "pkg/resolve/is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "stream-meter/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "stream-meter/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "stream-meter/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1" } }, "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1" } }, "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.1", "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1" } }, "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@grpc/proto-loader/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@grpc/proto-loader/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@grpc/proto-loader/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@grpc/proto-loader/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "@grpc/proto-loader/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="], + + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@grpc/proto-loader/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@grpc/proto-loader/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "@grpc/proto-loader/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/cmd/login.go b/cmd/login.go deleted file mode 100644 index 8b8d3f7..0000000 --- a/cmd/login.go +++ /dev/null @@ -1,191 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "os" - "os/signal" - "syscall" - "time" - - "github.com/spf13/cobra" - - "github.com/Enkryptify/cli/internal/auth" - "github.com/Enkryptify/cli/internal/config" - "github.com/Enkryptify/cli/internal/ui" -) - -var ( - loginForce bool - loginHelp bool -) - -var loginCmd = &cobra.Command{ - Use: "login", - Short: "Authenticate with Enkryptify", - Long: `Authenticate with Enkryptify to access your secrets. - -This command will open a web browser to complete the OAuth authentication flow. -After successful authentication, your credentials will be securely stored -in your system's keyring. - -For server environments where browser authentication is not possible, you can -set the ENKRYPTIFY_TOKEN environment variable with your API token: - export ENKRYPTIFY_TOKEN=your_api_token_here - -Examples: - ek login # Login with default provider (Enkryptify) - ek login --force # Force re-authentication even if already logged in - ek login --help # Show this help message`, - - RunE: runLogin, -} - -func init() { - // Add flags - loginCmd.Flags().BoolVarP(&loginForce, "force", "f", false, "Force re-authentication even if already logged in") - loginCmd.Flags().BoolVarP(&loginHelp, "help", "h", false, "Show help for login command") - - // Add login command to root - rootCmd.AddCommand(loginCmd) -} - -func runLogin(cmd *cobra.Command, args []string) error { - // Show help if requested - if loginHelp { - return cmd.Help() - } - - // Show brand header - ui.ShowBrandHeader() - - // Create context with cancellation - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Handle interrupt signals - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) - - go func() { - <-sigChan - ui.PrintWarning("Login cancelled by user") - cancel() - }() - - // Load configuration - cfg, err := config.LoadConfig() - if err != nil { - ui.ShowAuthError(fmt.Errorf("failed to load configuration: %w", err)) - return err - } - - // Initialize Enkryptify auth - enkryptifyAuth := auth.NewEnkryptifyAuth() - - // Check if already authenticated (unless force flag is used) - if !loginForce { - authenticated, err := enkryptifyAuth.IsAuthenticated() - if err != nil { - ui.PrintWarning(fmt.Sprintf("Failed to check authentication status: %v", err)) - } else if authenticated { - // Try to get user info to verify the token is still valid - accessToken, err := enkryptifyAuth.GetAccessToken() - if err == nil { - userInfo, err := enkryptifyAuth.GetUserInfo(accessToken) - if err == nil { - ui.ShowAuthSuccess(userInfo.Email) - ui.PrintInfo("You are already authenticated. Use --force to re-authenticate.") - return nil - } - } - // If we can't verify the token, continue with login - ui.PrintWarning("Existing authentication appears to be invalid. Proceeding with login...") - } - } - - // Show current provider info - ui.ShowProviderInfo("Enkryptify", false) - - // Start authentication process - ui.PrintInfo("Starting authentication with Enkryptify...") - - // Create a timeout context for the entire login process - loginCtx, loginCancel := context.WithTimeout(ctx, 10*time.Minute) - defer loginCancel() - - // Perform login - if err := enkryptifyAuth.Login(loginCtx); err != nil { - if loginCtx.Err() == context.Canceled { - ui.PrintWarning("Login cancelled") - return nil - } - if loginCtx.Err() == context.DeadlineExceeded { - ui.ShowAuthError(fmt.Errorf("login timeout - please try again")) - return fmt.Errorf("login timeout") - } - - ui.ShowAuthError(err) - return err - } - - // Update configuration with successful login - cfg.SetProvider("enkryptify", config.Provider{ - Type: "enkryptify", - Settings: map[string]interface{}{ - "authenticated": true, - "last_login": time.Now().Unix(), - }, - }) - - if err := cfg.Save(); err != nil { - ui.PrintWarning("Authentication successful, but failed to save configuration.") - } - - return nil -} - -// ValidateAuthentication checks if the user is authenticated with any provider -func ValidateAuthentication() error { - enkryptifyAuth := auth.NewEnkryptifyAuth() - - authenticated, err := enkryptifyAuth.IsAuthenticated() - if err != nil { - return fmt.Errorf("failed to check authentication status: %w", err) - } - - if !authenticated { - return fmt.Errorf("not authenticated - please run 'ek login' first") - } - - // Verify token is still valid - _, err = enkryptifyAuth.GetAccessToken() - if err != nil { - return fmt.Errorf("authentication token is invalid - please run 'ek login' again") - } - - return nil -} - -// GetCurrentUser returns information about the currently authenticated user -func GetCurrentUser() (string, error) { - enkryptifyAuth := auth.NewEnkryptifyAuth() - - accessToken, err := enkryptifyAuth.GetAccessToken() - if err != nil { - return "", fmt.Errorf("failed to get access token: %w", err) - } - - userInfo, err := enkryptifyAuth.GetUserInfo(accessToken) - if err != nil { - return "", fmt.Errorf("failed to get user info: %w", err) - } - - return userInfo.Email, nil -} - -// Logout removes stored authentication information -func Logout() error { - enkryptifyAuth := auth.NewEnkryptifyAuth() - return enkryptifyAuth.Logout() -} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 98aeeb1..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,36 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -var version = "0.1.10" - -var rootCmd = &cobra.Command{ - Use: "ek", - Short: "Enkryptify CLI", - Long: ` - _____ _ _ _ __ - | ____|_ __ | | ___ __ _ _ _ __ | |_(_)/ _|_ _ - | _| | '_ \| |/ / '__| | | | '_ \| __| | |_| | | | - | |___| | | | <| | | |_| | |_) | |_| | _| |_| | - |_____|_| |_|_|\_\_| \__, | .__/ \__|_|_| \__, | - |___/|_| |___/ - `, - Version: version, -} - -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} - -func init() { - rootCmd.Flags().BoolP("version", "v", false, "Get the current version of the CLI") - rootCmd.SetVersionTemplate("{{.Version}}\n") -} diff --git a/cmd/run.go b/cmd/run.go deleted file mode 100644 index 2ad54d2..0000000 --- a/cmd/run.go +++ /dev/null @@ -1,119 +0,0 @@ -package cmd - -import ( - "fmt" - "strings" - - "github.com/spf13/cobra" - - "github.com/Enkryptify/cli/internal/config" - "github.com/Enkryptify/cli/internal/inject" - "github.com/Enkryptify/cli/internal/ui" -) - -var runCmd = &cobra.Command{ - Use: "run -- ", - Short: "Run a command with secrets injected as environment variables", - Long: `Run a command with secrets from Enkryptify injected as environment variables. - -This command fetches secrets from your configured Enkryptify workspace, project, and environment, -then runs the specified command with those secrets available as environment variables. - -You must first run 'ek setup' to link your current directory to an Enkryptify configuration. - -Examples: - ek run -- npm start # Run npm start with secrets - ek run -- pnpm run dev # Run pnpm dev with secrets - ek run -- python app.py # Run Python app with secrets - ek run -- docker-compose up # Run docker-compose with secrets - -Note: Use '--' to separate the ek command from the command you want to run.`, - - RunE: runRunCommand, - // Allow arbitrary arguments after -- - DisableFlagParsing: false, -} - -func init() { - rootCmd.AddCommand(runCmd) -} - -func runRunCommand(cmd *cobra.Command, args []string) error { - // Validate authentication - if err := ValidateAuthentication(); err != nil { - ui.PrintError("You must be authenticated to run commands with secrets") - ui.PrintInfo("Please run 'ek login' first") - return err - } - - // Get current directory setup - currentPath, err := config.GetCurrentWorkingDirectory() - if err != nil { - return fmt.Errorf("failed to get current directory: %w", err) - } - - setupStorage, err := config.LoadSetupStorage() - if err != nil { - return fmt.Errorf("failed to load setup configuration: %w", err) - } - - if !setupStorage.HasSetupForPath(currentPath) { - ui.PrintError("No setup found for current directory") - ui.PrintInfo("Please run 'ek setup' first to link this directory to an Enkryptify configuration") - return fmt.Errorf("directory not configured") - } - - setupConfig := setupStorage.GetSetupForPath(currentPath) - if setupConfig == nil { - return fmt.Errorf("failed to get setup configuration for current path") - } - - // Parse arguments - look for -- separator - var commandArgs []string - - // Find -- separator - separatorIndex := -1 - for i, arg := range args { - if arg == "--" { - separatorIndex = i - break - } - } - - if separatorIndex == -1 { - // No -- separator found, treat all args as the command - if len(args) == 0 { - ui.PrintError("No command provided") - ui.PrintInfo("Usage: ek run -- ") - ui.PrintInfo("Example: ek run -- npm start") - return fmt.Errorf("no command provided") - } - commandArgs = args - } else { - // Use args after -- - if separatorIndex+1 >= len(args) { - ui.PrintError("No command provided after '--'") - ui.PrintInfo("Usage: ek run -- ") - ui.PrintInfo("Example: ek run -- npm start") - return fmt.Errorf("no command provided after '--'") - } - commandArgs = args[separatorIndex+1:] - } - - // Show info about what we're doing - ui.PrintInfo(fmt.Sprintf("Running command with secrets from workspace: %s, project: %s", - setupConfig.WorkspaceSlug, setupConfig.ProjectSlug)) - ui.PrintInfo(fmt.Sprintf("Command: %s", strings.Join(commandArgs, " "))) - - // Inject secrets and run command - if err := inject.InjectSecretsAndRun( - setupConfig.WorkspaceSlug, - setupConfig.ProjectSlug, - setupConfig.EnvironmentID, - commandArgs, - ); err != nil { - return fmt.Errorf("failed to run command with secrets: %w", err) - } - - return nil -} diff --git a/cmd/setup.go b/cmd/setup.go deleted file mode 100644 index 0c96f74..0000000 --- a/cmd/setup.go +++ /dev/null @@ -1,216 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" - - "github.com/Enkryptify/cli/internal/auth" - "github.com/Enkryptify/cli/internal/config" - "github.com/Enkryptify/cli/internal/providers/enkryptify" - "github.com/Enkryptify/cli/internal/ui" -) - -var setupCmd = &cobra.Command{ - Use: "setup", - Short: "Link current directory to an Enkryptify repository", - Long: `Link the current git repository to an Enkryptify workspace, project, and environment. - -This command will guide you through selecting: -1. A workspace from your available workspaces -2. A project from the selected workspace -3. An environment from the selected project - -The configuration will be saved to ~/.enkryptify/config.json and associated with the current directory path.`, - - RunE: runSetup, -} - -func init() { - rootCmd.AddCommand(setupCmd) -} - -func runSetup(cmd *cobra.Command, args []string) error { - if err := ValidateAuthentication(); err != nil { - ui.PrintError("You must be authenticated to run setup") - ui.PrintInfo("Please run 'ek login' first") - return err - } - - currentPath, err := config.GetCurrentWorkingDirectory() - if err != nil { - return fmt.Errorf("failed to get current directory: %w", err) - } - - setupStorage, err := config.LoadSetupStorage() - if err != nil { - return fmt.Errorf("failed to load setup configuration: %w", err) - } - - client := enkryptify.NewClient() - - // Check if using environment variable token (for server environments) - if token := os.Getenv(auth.EnvTokenKey); token != "" { - return runNonInteractiveSetup(currentPath, setupStorage, client) - } - - // Interactive setup for user authentication - return runInteractiveSetup(currentPath, setupStorage, client) -} - -// runNonInteractiveSetup handles setup for server environments using env token -func runNonInteractiveSetup(currentPath string, setupStorage *config.SetupStorage, client *enkryptify.Client) error { - // Fetch project details from the token - tokenDetails, err := client.GetProjectTokenDetails() - if err != nil { - return fmt.Errorf("failed to fetch project token details: %w", err) - } - - // Check if setup already exists - if setupStorage.HasSetupForPath(currentPath) { - existingSetup := setupStorage.GetSetupForPath(currentPath) - // In non-interactive mode, we just overwrite silently - fmt.Printf("Updating existing setup for %s\n", currentPath) - fmt.Printf("Previous: workspace=%s, project=%s, environment=%s\n", - existingSetup.WorkspaceSlug, existingSetup.ProjectSlug, existingSetup.EnvironmentID) - } - - // Create setup config from token details - setupConfig := config.SetupConfig{ - Path: currentPath, - WorkspaceSlug: tokenDetails.Workspace.Slug, - ProjectSlug: tokenDetails.Project.Slug, - EnvironmentID: tokenDetails.EnvironmentID, - } - - setupStorage.AddOrUpdateSetup(setupConfig) - if err := setupStorage.Save(); err != nil { - return fmt.Errorf("failed to save setup configuration: %w", err) - } - - fmt.Printf("✓ Setup completed successfully!\n") - fmt.Printf(" Workspace: %s\n", tokenDetails.Workspace.Slug) - fmt.Printf(" Project: %s\n", tokenDetails.Project.Slug) - fmt.Printf(" Environment: %s\n", tokenDetails.EnvironmentID) - fmt.Printf(" Path: %s\n", currentPath) - - return nil -} - -// runInteractiveSetup handles setup with interactive prompts for user authentication -func runInteractiveSetup(currentPath string, setupStorage *config.SetupStorage, client *enkryptify.Client) error { - ui.ShowBrandHeader() - ui.PrintTitle("🔗 Enkryptify Repository Setup") - - if setupStorage.HasSetupForPath(currentPath) { - existingSetup := setupStorage.GetSetupForPath(currentPath) - ui.PrintWarning("Setup already exists for this directory") - ui.PrintInfo(fmt.Sprintf("Current setup: workspace=%s, project=%s, environment=%s", - existingSetup.WorkspaceSlug, existingSetup.ProjectSlug, existingSetup.EnvironmentID)) - - if !ui.ConfirmAction("Do you want to overwrite the existing setup?") { - ui.PrintInfo("Setup cancelled") - return nil - } - } - - ui.ShowProgress(1, 3, "Fetching workspaces...") - workspaces, err := client.GetWorkspaces() - if err != nil { - return fmt.Errorf("failed to fetch workspaces: %w", err) - } - - if len(workspaces) == 0 { - ui.PrintError("No workspaces found") - ui.PrintInfo("Please create a workspace in Enkryptify first") - return fmt.Errorf("no workspaces available") - } - - workspaceItems := make([]ui.SelectionItem, len(workspaces)) - for i, ws := range workspaces { - workspaceItems[i] = ui.SelectionItem{ - ID: ws.ID, - Name: ws.Name, - Slug: ws.Slug, - } - } - - selectedWorkspace, err := ui.SelectFromList(workspaceItems, "workspace") - if err != nil { - return err - } - - ui.ShowProgress(2, 3, "Fetching projects...") - projects, err := client.GetProjects(selectedWorkspace.Slug) - if err != nil { - return fmt.Errorf("failed to fetch projects: %w", err) - } - - if len(projects) == 0 { - ui.PrintError("No projects found in the selected workspace") - ui.PrintInfo("Please create a project in this workspace first") - return fmt.Errorf("no projects available") - } - - projectItems := make([]ui.SelectionItem, len(projects)) - for i, proj := range projects { - projectItems[i] = ui.SelectionItem{ - ID: proj.ID, - Name: proj.Name, - Slug: proj.Slug, - } - } - - selectedProject, err := ui.SelectFromList(projectItems, "project") - if err != nil { - return err - } - - ui.ShowProgress(3, 3, "Fetching environments...") - projectDetail, err := client.GetProjectDetail(selectedWorkspace.Slug, selectedProject.Slug) - if err != nil { - return fmt.Errorf("failed to fetch project detail: %w", err) - } - - if len(projectDetail.Environments) == 0 { - ui.PrintError("No environments found in the selected project") - ui.PrintInfo("Please create an environment in this project first") - return fmt.Errorf("no environments available") - } - - environmentItems := make([]ui.SelectionItem, len(projectDetail.Environments)) - for i, env := range projectDetail.Environments { - environmentItems[i] = ui.SelectionItem{ - ID: env.ID, - Name: env.Name, - - } - } - - selectedEnvironment, err := ui.SelectFromList(environmentItems, "environment") - if err != nil { - return err - } - - setupConfig := config.SetupConfig{ - Path: currentPath, - WorkspaceSlug: selectedWorkspace.Slug, - ProjectSlug: selectedProject.Slug, - EnvironmentID: selectedEnvironment.ID, - } - - setupStorage.AddOrUpdateSetup(setupConfig) - if err := setupStorage.Save(); err != nil { - return fmt.Errorf("failed to save setup configuration: %w", err) - } - - ui.PrintSeparator() - ui.PrintSuccess("Setup completed successfully!") - ui.PrintInfo(fmt.Sprintf("Workspace: %s (%s)", selectedWorkspace.Name, selectedWorkspace.Slug)) - ui.PrintInfo(fmt.Sprintf("Project: %s (%s)", selectedProject.Name, selectedProject.Slug)) - ui.PrintInfo(fmt.Sprintf("Environment: %s", selectedEnvironment.Name)) - ui.PrintInfo(fmt.Sprintf("Path: %s", currentPath)) - - return nil -} diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..1d5673d --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,83 @@ +import tsPlugin from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; +import eslintConfigPrettier from "eslint-config-prettier"; +import globals from "globals"; + +/** + * ESLint configuration for Bun/TypeScript CLI application + * + * @type {import("eslint").Linter.Config[]} + */ +export default [ + eslintConfigPrettier, + + { + files: ["**/*.ts", "**/*.tsx"], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaFeatures: { + modules: true, + jsx: true, + }, + ecmaVersion: 2022, + sourceType: "module", + projectService: true, + }, + globals: { + ...globals.node, + Bun: "readonly", + $: "readonly", + }, + }, + plugins: { + "@typescript-eslint": tsPlugin, + }, + rules: { + "no-debugger": "error", + "no-duplicate-imports": "error", + "prefer-const": "error", + "no-var": "error", + + "sort-imports": [ + "error", + { + ignoreCase: false, + ignoreDeclarationSort: true, + ignoreMemberSort: false, + memberSyntaxSortOrder: ["none", "all", "multiple", "single"], + allowSeparatedGroups: false, + }, + ], + + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-unsafe-assignment": "error", + "@typescript-eslint/no-unsafe-member-access": "error", + "@typescript-eslint/no-unsafe-call": "error", + "@typescript-eslint/no-unsafe-return": "error", + "@typescript-eslint/no-unsafe-argument": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_$|^_[a-zA-Z]", + destructuredArrayIgnorePattern: "^_$|^_[a-zA-Z]", + ignoreRestSiblings: true, + }, + ], + "@typescript-eslint/consistent-type-imports": [ + "error", + { + prefer: "type-imports", + fixStyle: "inline-type-imports", + }, + ], + "@typescript-eslint/no-misused-promises": "error", + "@typescript-eslint/no-floating-promises": "error", + }, + }, + + { + ignores: ["node_modules/**", "dist/**", "bin/**", "*.config.js", "*.config.ts", ".bun/**"], + }, +]; diff --git a/go.mod b/go.mod deleted file mode 100644 index 16a256e..0000000 --- a/go.mod +++ /dev/null @@ -1,31 +0,0 @@ -module github.com/Enkryptify/cli - -go 1.25.0 - -require ( - github.com/charmbracelet/lipgloss v1.1.0 - github.com/mitchellh/go-homedir v1.1.0 - github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c - github.com/spf13/cobra v1.9.1 - github.com/zalando/go-keyring v0.2.6 -) - -require ( - al.essio.dev/pkg/shellescape v1.6.0 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/colorprofile v0.3.2 // indirect - github.com/charmbracelet/x/ansi v0.10.1 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13 // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/danieljoos/wincred v1.2.2 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/pflag v1.0.7 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sys v0.35.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index d8caf85..0000000 --- a/go.sum +++ /dev/null @@ -1,65 +0,0 @@ -al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= -al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= -github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= -github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= -github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= -github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= -github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/install.sh b/install.sh deleted file mode 100644 index 1ef75bb..0000000 --- a/install.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash - -VERSION="0.1.10" -set -e - -if [ -t 1 ]; then - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[1;33m' - NC='\033[0m' -else - RED='' - GREEN='' - YELLOW='' - NC='' -fi - -OS="unknown" -case "$(uname -s)" in - Linux*) OS="Linux";; - Darwin*) OS="Darwin";; - MINGW*|MSYS*|CYGWIN*) - OS="Windows" - echo -e "${RED}Windows installation through this script is not supported.${NC}" - echo "Please install the Windows CLI from: https://docs.enkryptify.com/integrations/cli/install" - exit 1 - ;; - *) - echo -e "${RED}Unsupported operating system: $(uname -s)${NC}" - exit 1 - ;; -esac - -ARCH="unknown" -MACHINE=$(uname -m) -case $MACHINE in - x86_64|amd64) ARCH="x86_64";; - aarch64) ARCH="arm64";; - armv7l) ARCH="armv7";; - armv6l) ARCH="armv6";; - i386|i686) ARCH="i386";; - *) - echo -e "${RED}Unsupported architecture: $MACHINE${NC}" - exit 1 - ;; -esac - -echo -e "${YELLOW}Downloading Enkryptify CLI ${VERSION} for ${OS} ${ARCH}...${NC}" - -if ! TMPDIR=$(mktemp -d 2>/dev/null); then - TMPDIR="/tmp/ek-$(date +%s)" - mkdir -p "$TMPDIR" -fi - -DOWNLOAD_URL="https://github.com/Enkryptify/cli/releases/download/v${VERSION}/enkryptify_${OS}_${ARCH}.tar.gz" - -if command -v curl >/dev/null 2>&1; then - curl -L "${DOWNLOAD_URL}" -o "${TMPDIR}/ek.tar.gz" -elif command -v wget >/dev/null 2>&1; then - wget -O "${TMPDIR}/ek.tar.gz" "${DOWNLOAD_URL}" -else - echo -e "${RED}Error: Neither curl nor wget found. Please install either curl or wget.${NC}" - rm -rf "${TMPDIR}" - exit 1 -fi - -echo -e "${YELLOW}Installing Enkryptify CLI...${NC}" -tar xzf "${TMPDIR}/ek.tar.gz" -C "${TMPDIR}" - -INSTALL_DIR="/usr/local/bin" -if [ "$OS" = "Darwin" ]; then - # Check if /usr/local/bin exists and is writable - if [ ! -w "/usr/local/bin" ]; then - INSTALL_DIR="$HOME/bin" - mkdir -p "$INSTALL_DIR" - fi -fi - -if [ -w "$INSTALL_DIR" ]; then - install -m 755 "${TMPDIR}/ek" "$INSTALL_DIR/ek" -else - sudo install -m 755 "${TMPDIR}/ek" "$INSTALL_DIR/ek" -fi - -rm -rf "${TMPDIR}" -if command -v ek >/dev/null 2>&1; then - echo -e "${GREEN}Enkryptify CLI installed successfully!${NC}" - ek --version -else - if [ "$INSTALL_DIR" = "$HOME/bin" ]; then - echo "export PATH=\"\$PATH:$INSTALL_DIR\"" >> "$HOME/.profile" - echo -e "${YELLOW}Please run 'source ~/.profile' or start a new terminal session${NC}" - fi - echo -e "${RED}Installation completed but CLI not found in PATH${NC}" - exit 1 -fi \ No newline at end of file diff --git a/internal/auth/enkryptify.go b/internal/auth/enkryptify.go deleted file mode 100644 index d06b2a4..0000000 --- a/internal/auth/enkryptify.go +++ /dev/null @@ -1,482 +0,0 @@ -package auth - -import ( - "context" - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "os" - "strings" - "time" - - "github.com/pkg/browser" - - "github.com/Enkryptify/cli/internal/config" - "github.com/Enkryptify/cli/internal/keyring" - "github.com/Enkryptify/cli/internal/ui" -) - -const ( - ClientID = "enkryptify-cli" - AuthBaseURL = "https://app.enkryptify.com" - TokenEndpoint = "https://api.enkryptify.com/v1/auth/token" - UserInfoEndpoint = "https://api.enkryptify.com/v1/me" - RedirectURL = "http://localhost:51823/callback" - CallbackPort = "51823" - DefaultScopes = "openid profile email secrets:read secrets:write" - EnvTokenKey = "ENKRYPTIFY_TOKEN" -) - -// EnkryptifyAuth handles authentication with Enkryptify -type EnkryptifyAuth struct { - keyring *keyring.Store - config *config.Config - httpClient *http.Client -} - -// NewEnkryptifyAuth creates a new Enkryptify authentication handler -func NewEnkryptifyAuth() *EnkryptifyAuth { - return &EnkryptifyAuth{ - keyring: keyring.NewStore(), - httpClient: &http.Client{Timeout: 30 * time.Second}, - } -} - -// AuthResponse represents the response from the authentication server -type AuthResponse struct { - AccessToken string `json:"accessToken"` - TokenType string `json:"tokenType"` - ExpiresIn int64 `json:"expiresIn,omitempty"` -} - -// UserInfo represents user information from the API -type UserInfo struct { - ID string `json:"id"` - Email string `json:"email"` - Name string `json:"name,omitempty"` -} - -// generateCodeVerifier generates a code verifier for PKCE -func generateCodeVerifier() (string, error) { - bytes := make([]byte, 32) - if _, err := rand.Read(bytes); err != nil { - return "", err - } - return base64.RawURLEncoding.EncodeToString(bytes), nil -} - -// generateCodeChallenge generates a code challenge from a code verifier -func generateCodeChallenge(verifier string) string { - sha := sha256.Sum256([]byte(verifier)) - return base64.RawURLEncoding.EncodeToString(sha[:]) -} - -// Login performs the OAuth login flow with Enkryptify -func (e *EnkryptifyAuth) Login(ctx context.Context) error { - // Check if using environment variable token - if token := os.Getenv(EnvTokenKey); token != "" { - ui.PrintInfo(fmt.Sprintf("Authenticated using %s environment variable", EnvTokenKey)) - return nil - } - - // Load configuration - cfg, err := config.LoadConfig() - if err != nil { - return fmt.Errorf("failed to load config: %w", err) - } - e.config = cfg - - // Check if already authenticated - authenticated, err := e.IsAuthenticated() - if err != nil { - return fmt.Errorf("failed to check authentication status: %w", err) - } - - if authenticated { - authInfo, err := e.keyring.GetAuthInfo("enkryptify") - if err != nil { - return fmt.Errorf("failed to get auth info: %w", err) - } - - userInfo, err := e.GetUserInfo(authInfo.AccessToken) - if err == nil { - ui.ShowAuthSuccess(userInfo.Email) - return nil - } - // If we can't get user info (token expired/invalid), clear stored auth and proceed with login - ui.PrintWarning("Stored authentication is invalid or expired. Starting fresh login...") - e.keyring.DeleteAuthInfo("enkryptify") - } - - // Generate PKCE parameters - codeVerifier, err := generateCodeVerifier() - if err != nil { - return fmt.Errorf("failed to generate code verifier: %w", err) - } - codeChallenge := generateCodeChallenge(codeVerifier) - - // Generate state parameter for security - state, err := generateCodeVerifier() - if err != nil { - return fmt.Errorf("failed to generate state: %w", err) - } - - // Start local callback server - authResult := make(chan AuthResponse, 1) - errorResult := make(chan error, 1) - - server := &http.Server{Addr: ":" + CallbackPort} - - http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { - // Check for errors - if errCode := r.URL.Query().Get("error"); errCode != "" { - errDesc := r.URL.Query().Get("error_description") - if errDesc == "" { - errDesc = errCode - } - - // Show error page - w.Header().Set("Content-Type", "text/html") - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, ` - - Authentication Error - -

Authentication Error

-

%s

-

You can close this window and try again.

- - - `, errDesc) - - // Ensure response is sent before shutting down - if f, ok := w.(http.Flusher); ok { - f.Flush() - } - - // Delay shutdown to allow browser to receive response - go func() { - time.Sleep(1 * time.Second) - server.Shutdown(context.Background()) - }() - - errorResult <- fmt.Errorf("authentication error: %s", errDesc) - return - } - - // Verify state parameter - receivedState := r.URL.Query().Get("state") - if receivedState != state { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "Invalid state parameter") - - // Ensure response is sent before shutting down - if f, ok := w.(http.Flusher); ok { - f.Flush() - } - - // Delay shutdown to allow browser to receive response - go func() { - time.Sleep(1 * time.Second) - server.Shutdown(context.Background()) - }() - - errorResult <- fmt.Errorf("invalid state parameter") - return - } - - // Get authorization code - code := r.URL.Query().Get("code") - if code == "" { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "Missing authorization code") - - // Ensure response is sent before shutting down - if f, ok := w.(http.Flusher); ok { - f.Flush() - } - - // Delay shutdown to allow browser to receive response - go func() { - time.Sleep(1 * time.Second) - server.Shutdown(context.Background()) - }() - - errorResult <- fmt.Errorf("missing authorization code") - return - } - - // Exchange code for token - authResp, err := e.exchangeCodeForToken(code, codeVerifier) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Failed to exchange code for token") - - // Ensure response is sent before shutting down - if f, ok := w.(http.Flusher); ok { - f.Flush() - } - - // Delay shutdown to allow browser to receive response - go func() { - time.Sleep(1 * time.Second) - server.Shutdown(context.Background()) - }() - - errorResult <- fmt.Errorf("failed to exchange code for token: %w", err) - return - } - - // Show success page - w.Header().Set("Content-Type", "text/html") - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` - - Authentication Successful - -

Authentication Successful!

-

You have successfully authenticated with Enkryptify.

-

You can now close this window and return to your terminal.

- - - `) - - // Ensure response is sent before shutting down - if f, ok := w.(http.Flusher); ok { - f.Flush() - } - - // Delay shutdown to allow browser to receive response - go func() { - time.Sleep(1 * time.Second) - server.Shutdown(context.Background()) - }() - - authResult <- *authResp - }) - - // Start the server in a goroutine - go func() { - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - errorResult <- fmt.Errorf("failed to start callback server: %w", err) - } - }() - - // Wait for server to start - time.Sleep(100 * time.Millisecond) - - // Build authorization URL - authURL := fmt.Sprintf("%s/oauth/authorize?"+ - "client_id=%s&"+ - "response_type=code&"+ - "redirect_uri=%s&"+ - "scope=%s&"+ - "state=%s&"+ - "code_challenge=%s&"+ - "code_challenge_method=S256", - AuthBaseURL, - url.QueryEscape(ClientID), - url.QueryEscape(RedirectURL), - url.QueryEscape(DefaultScopes), - url.QueryEscape(state), - url.QueryEscape(codeChallenge), - ) - - // Show instructions and open browser - showAuthInstructions(authURL) - - if err := browser.OpenURL(authURL); err != nil { - ui.PrintWarning("Failed to open browser automatically. Please open the URL manually.") - } - - ui.ShowWaitingForAuth() - - // Wait for authentication result - select { - case authResp := <-authResult: - // Get user info - userInfo, err := e.GetUserInfo(authResp.AccessToken) - if err != nil { - return fmt.Errorf("failed to get user info: %w", err) - } - - // Store authentication info - authInfo := &config.AuthInfo{ - AccessToken: authResp.AccessToken, - ExpiresAt: time.Now().Unix() + authResp.ExpiresIn, - UserID: userInfo.ID, - Email: userInfo.Email, - } - - if err := e.keyring.StoreAuthInfo("enkryptify", authInfo); err != nil { - return fmt.Errorf("failed to store auth info: %w", err) - } - - // Update configuration - e.config.SetProvider("enkryptify", config.Provider{ - Type: "enkryptify", - Settings: map[string]interface{}{ - "authenticated": true, - "last_login": time.Now().Unix(), - }, - }) - - if err := e.config.Save(); err != nil { - ui.PrintWarning("Failed to save configuration, but authentication was successful.") - } - - ui.ShowAuthSuccess(userInfo.Email) - return nil - - case err := <-errorResult: - return err - - case <-ctx.Done(): - server.Shutdown(context.Background()) - return ctx.Err() - - case <-time.After(5 * time.Minute): - server.Shutdown(context.Background()) - return fmt.Errorf("authentication timeout") - } -} - -// exchangeCodeForToken exchanges an authorization code for access token -func (e *EnkryptifyAuth) exchangeCodeForToken(code, codeVerifier string) (*AuthResponse, error) { - payload := map[string]interface{}{ - "grant_type": "authorization_code", - "client_id": ClientID, - "code": code, - "redirect_uri": RedirectURL, - "code_verifier": codeVerifier, - } - jsonData, err := json.Marshal(payload) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", TokenEndpoint, strings.NewReader(string(jsonData))) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - - resp, err := e.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("token exchange failed with status %d: %s", resp.StatusCode, string(bodyBytes)) - } - - var authResp AuthResponse - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&authResp); err != nil { - return nil, fmt.Errorf("failed to decode token response: %w", err) - } - - return &authResp, nil -} - -// GetUserInfo retrieves user information using an access token -func (e *EnkryptifyAuth) GetUserInfo(accessToken string) (*UserInfo, error) { - req, err := http.NewRequest("GET", UserInfoEndpoint, nil) - if err != nil { - return nil, err - } - - req.Header.Set("X-API-Key", accessToken) - req.Header.Set("Accept", "application/json") - - resp, err := e.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to get user info, status: %d", resp.StatusCode) - } - - var userInfo UserInfo - if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { - return nil, err - } - - return &userInfo, nil -} - -// IsAuthenticated checks if the user is authenticated -func (e *EnkryptifyAuth) IsAuthenticated() (bool, error) { - // Check for environment variable token first (for server environments) - if token := os.Getenv(EnvTokenKey); token != "" { - return true, nil - } - - return e.keyring.IsAuthenticated("enkryptify") -} - -// Logout removes stored authentication information -func (e *EnkryptifyAuth) Logout() error { - if err := e.keyring.DeleteAuthInfo("enkryptify"); err != nil { - return fmt.Errorf("failed to delete auth info: %w", err) - } - - ui.PrintSuccess("Successfully logged out from Enkryptify") - return nil -} - -// GetAccessToken retrieves the current access token -func (e *EnkryptifyAuth) GetAccessToken() (string, error) { - // Check for environment variable token first (for server environments) - if token := os.Getenv(EnvTokenKey); token != "" { - return token, nil - } - - authInfo, err := e.keyring.GetAuthInfo("enkryptify") - if err != nil { - return "", err - } - - if authInfo == nil { - return "", fmt.Errorf("not authenticated") - } - - // Check if token is expired (with 5 minute buffer) - if authInfo.ExpiresAt > 0 && time.Now().Unix() > (authInfo.ExpiresAt-300) { - return "", fmt.Errorf("access token expired, please run login command") - } - - return authInfo.AccessToken, nil -} - - -func showAuthInstructions(authURL string) { - ui.PrintTitle("🔐 Enkryptify Authentication") - ui.PrintSubtitle("To authenticate with Enkryptify, please follow these steps:") - - fmt.Println() - ui.PrintInfo("1. A web browser will open automatically") - ui.PrintInfo("2. If the browser doesn't open, manually visit the URL below") - ui.PrintInfo("3. Sign in to your Enkryptify account") - ui.PrintInfo("4. Authorize the CLI application") - ui.PrintInfo("5. Return to this terminal once you've completed the authorization") - - ui.PrintSeparator() - - fmt.Println() - ui.PrintInfo("Authentication URL:\n" + authURL) - fmt.Println() - - ui.PrintSeparator() -} \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index fb736d1..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,130 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/mitchellh/go-homedir" -) - -// Config represents the CLI configuration -type Config struct { - DefaultProvider string `json:"default_provider"` - Providers map[string]Provider `json:"providers"` -} - -// Provider represents a secrets provider configuration -type Provider struct { - Type string `json:"type"` - Settings map[string]interface{} `json:"settings"` -} - -// AuthInfo represents authentication information -type AuthInfo struct { - AccessToken string `json:"access_token"` - ExpiresAt int64 `json:"expires_at,omitempty"` - UserID string `json:"user_id,omitempty"` - Email string `json:"email,omitempty"` -} - -const ( - ConfigFileName = ".enkryptify" - ConfigDirName = "enkryptify" -) - -// GetConfigDir returns the configuration directory path -func GetConfigDir() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", fmt.Errorf("failed to get home directory: %w", err) - } - - configDir := filepath.Join(home, ".config", ConfigDirName) - return configDir, nil -} - -// GetConfigPath returns the full path to the config file -func GetConfigPath() (string, error) { - configDir, err := GetConfigDir() - if err != nil { - return "", err - } - - return filepath.Join(configDir, "config.json"), nil -} - -// LoadConfig loads the configuration from disk -func LoadConfig() (*Config, error) { - configPath, err := GetConfigPath() - if err != nil { - return nil, err - } - - // Return default config if file doesn't exist - if _, err := os.Stat(configPath); os.IsNotExist(err) { - return &Config{ - DefaultProvider: "enkryptify", - Providers: make(map[string]Provider), - }, nil - } - - data, err := os.ReadFile(configPath) - if err != nil { - return nil, fmt.Errorf("failed to read config file: %w", err) - } - - var config Config - if err := json.Unmarshal(data, &config); err != nil { - return nil, fmt.Errorf("failed to parse config file: %w", err) - } - - // Initialize providers map if nil - if config.Providers == nil { - config.Providers = make(map[string]Provider) - } - - return &config, nil -} - -// SaveConfig saves the configuration to disk -func (c *Config) Save() error { - configPath, err := GetConfigPath() - if err != nil { - return err - } - - // Ensure config directory exists - configDir := filepath.Dir(configPath) - if err := os.MkdirAll(configDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %w", err) - } - - data, err := json.MarshalIndent(c, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal config: %w", err) - } - - if err := os.WriteFile(configPath, data, 0600); err != nil { - return fmt.Errorf("failed to write config file: %w", err) - } - - return nil -} - -// SetProvider sets the configuration for a specific provider -func (c *Config) SetProvider(name string, provider Provider) { - c.Providers[name] = provider -} - -// GetProvider gets the configuration for a specific provider -func (c *Config) GetProvider(name string) (Provider, bool) { - provider, exists := c.Providers[name] - return provider, exists -} - -// SetDefaultProvider sets the default provider -func (c *Config) SetDefaultProvider(name string) { - c.DefaultProvider = name -} diff --git a/internal/config/setup.go b/internal/config/setup.go deleted file mode 100644 index 12dde97..0000000 --- a/internal/config/setup.go +++ /dev/null @@ -1,114 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/mitchellh/go-homedir" -) - -type SetupConfig struct { - Path string `json:"path"` - WorkspaceSlug string `json:"workspace_slug"` - ProjectSlug string `json:"project_slug"` - EnvironmentID string `json:"environment_id"` -} - -type SetupStorage struct { - Setups []SetupConfig `json:"setups"` -} - -func GetSetupConfigPath() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", fmt.Errorf("failed to get home directory: %w", err) - } - - return filepath.Join(home, ".enkryptify", "config.json"), nil -} - -func LoadSetupStorage() (*SetupStorage, error) { - configPath, err := GetSetupConfigPath() - if err != nil { - return nil, err - } - - if _, err := os.Stat(configPath); os.IsNotExist(err) { - return &SetupStorage{ - Setups: []SetupConfig{}, - }, nil - } - - data, err := os.ReadFile(configPath) - if err != nil { - return nil, fmt.Errorf("failed to read setup config file: %w", err) - } - - var storage SetupStorage - if err := json.Unmarshal(data, &storage); err != nil { - return nil, fmt.Errorf("failed to parse setup config file: %w", err) - } - - if storage.Setups == nil { - storage.Setups = []SetupConfig{} - } - - return &storage, nil -} - -func (s *SetupStorage) Save() error { - configPath, err := GetSetupConfigPath() - if err != nil { - return err - } - - configDir := filepath.Dir(configPath) - if err := os.MkdirAll(configDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %w", err) - } - - data, err := json.MarshalIndent(s, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal setup config: %w", err) - } - - if err := os.WriteFile(configPath, data, 0600); err != nil { - return fmt.Errorf("failed to write setup config file: %w", err) - } - - return nil -} - -func (s *SetupStorage) HasSetupForPath(path string) bool { - for _, setup := range s.Setups { - if setup.Path == path { - return true - } - } - return false -} - -func (s *SetupStorage) AddOrUpdateSetup(setup SetupConfig) { - for i, existingSetup := range s.Setups { - if existingSetup.Path == setup.Path { - s.Setups[i] = setup - return - } - } - s.Setups = append(s.Setups, setup) -} - -func (s *SetupStorage) GetSetupForPath(path string) *SetupConfig { - for _, setup := range s.Setups { - if setup.Path == path { - return &setup - } - } - return nil -} - -func GetCurrentWorkingDirectory() (string, error) { - return os.Getwd() -} diff --git a/internal/inject/env.go b/internal/inject/env.go deleted file mode 100644 index bec19b5..0000000 --- a/internal/inject/env.go +++ /dev/null @@ -1,70 +0,0 @@ -package inject - -import ( - "fmt" - "os" - "os/exec" - "syscall" - - "github.com/Enkryptify/cli/internal/providers/enkryptify" -) - -// SecretToEnvVar converts a secret to environment variable format -func SecretToEnvVar(secret enkryptify.Secret, environmentID string) (string, string, error) { - // Find the value for the specific environment - for _, value := range secret.Values { - if value.EnvironmentID == environmentID { - return secret.Name, value.Value, nil - } - } - - return "", "", fmt.Errorf("no value found for secret %s in environment %s", secret.Name, environmentID) -} - -// InjectSecretsAndRun fetches secrets and runs the command with them as environment variables -func InjectSecretsAndRun(workspaceSlug, projectSlug, environmentID string, command []string) error { - if len(command) == 0 { - return fmt.Errorf("no command provided") - } - - // Create enkryptify client - client := enkryptify.NewClient() - - // Fetch secrets - secrets, err := client.GetSecrets(workspaceSlug, projectSlug, environmentID) - if err != nil { - return fmt.Errorf("failed to fetch secrets: %w", err) - } - - // Convert secrets to environment variables - envVars := os.Environ() // Start with current environment - - for _, secret := range secrets { - name, value, err := SecretToEnvVar(secret, environmentID) - if err != nil { - // Log warning but don't fail - secret might not have value for this environment - continue - } - envVars = append(envVars, fmt.Sprintf("%s=%s", name, value)) - } - - // Prepare command - cmd := exec.Command(command[0], command[1:]...) - cmd.Env = envVars - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - - // Run command and wait for completion - if err := cmd.Run(); err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - // Command failed with non-zero exit code - if status, ok := exitError.Sys().(syscall.WaitStatus); ok { - os.Exit(status.ExitStatus()) - } - } - return fmt.Errorf("failed to execute command: %w", err) - } - - return nil -} diff --git a/internal/keyring/keyring.go b/internal/keyring/keyring.go deleted file mode 100644 index 0615c14..0000000 --- a/internal/keyring/keyring.go +++ /dev/null @@ -1,155 +0,0 @@ -package keyring - -import ( - "encoding/json" - "fmt" - - "github.com/Enkryptify/cli/internal/config" - "github.com/zalando/go-keyring" -) - -const ( - ServiceName = "enkryptify-cli" -) - -// Store represents a secure keyring store -type Store struct { - serviceName string -} - -// NewStore creates a new keyring store -func NewStore() *Store { - return &Store{ - serviceName: ServiceName, - } -} - -// StoreAuthInfo securely stores authentication information for a provider -func (s *Store) StoreAuthInfo(provider string, authInfo *config.AuthInfo) error { - data, err := json.Marshal(authInfo) - if err != nil { - return fmt.Errorf("failed to marshal auth info: %w", err) - } - - key := fmt.Sprintf("%s-auth", provider) - if err := keyring.Set(s.serviceName, key, string(data)); err != nil { - return fmt.Errorf("failed to store auth info in keyring: %w", err) - } - - return nil -} - -// GetAuthInfo retrieves authentication information for a provider -func (s *Store) GetAuthInfo(provider string) (*config.AuthInfo, error) { - key := fmt.Sprintf("%s-auth", provider) - data, err := keyring.Get(s.serviceName, key) - if err != nil { - if err == keyring.ErrNotFound { - return nil, nil // No auth info found, not an error - } - return nil, fmt.Errorf("failed to get auth info from keyring: %w", err) - } - - var authInfo config.AuthInfo - if err := json.Unmarshal([]byte(data), &authInfo); err != nil { - return nil, fmt.Errorf("failed to unmarshal auth info: %w", err) - } - - return &authInfo, nil -} - -// DeleteAuthInfo removes authentication information for a provider -func (s *Store) DeleteAuthInfo(provider string) error { - key := fmt.Sprintf("%s-auth", provider) - if err := keyring.Delete(s.serviceName, key); err != nil { - if err == keyring.ErrNotFound { - return nil // Already deleted, not an error - } - return fmt.Errorf("failed to delete auth info from keyring: %w", err) - } - - return nil -} - -// IsAuthenticated checks if the user is authenticated with a provider -func (s *Store) IsAuthenticated(provider string) (bool, error) { - authInfo, err := s.GetAuthInfo(provider) - if err != nil { - return false, err - } - - return authInfo != nil && authInfo.AccessToken != "", nil -} - -// StoreGenericSecret stores a generic secret for a provider with a specific key -func (s *Store) StoreGenericSecret(provider, key, value string) error { - keyName := fmt.Sprintf("%s-%s", provider, key) - if err := keyring.Set(s.serviceName, keyName, value); err != nil { - return fmt.Errorf("failed to store secret in keyring: %w", err) - } - - return nil -} - -// GetGenericSecret retrieves a generic secret for a provider with a specific key -func (s *Store) GetGenericSecret(provider, key string) (string, error) { - keyName := fmt.Sprintf("%s-%s", provider, key) - value, err := keyring.Get(s.serviceName, keyName) - if err != nil { - if err == keyring.ErrNotFound { - return "", nil // Secret not found, not an error - } - return "", fmt.Errorf("failed to get secret from keyring: %w", err) - } - - return value, nil -} - -// DeleteGenericSecret removes a generic secret for a provider with a specific key -func (s *Store) DeleteGenericSecret(provider, key string) error { - keyName := fmt.Sprintf("%s-%s", provider, key) - if err := keyring.Delete(s.serviceName, keyName); err != nil { - if err == keyring.ErrNotFound { - return nil // Already deleted, not an error - } - return fmt.Errorf("failed to delete secret from keyring: %w", err) - } - - return nil -} - -// ListProviders returns a list of providers that have stored authentication info -func (s *Store) ListProviders() ([]string, error) { - // Note: go-keyring doesn't provide a way to list keys, so this is a limitation. - // For now, we'll return known providers that we check for auth info. - // In a real implementation, you might want to maintain a list in the config. - providers := []string{"enkryptify"} - var authenticatedProviders []string - - for _, provider := range providers { - authenticated, err := s.IsAuthenticated(provider) - if err != nil { - continue // Skip on error - } - if authenticated { - authenticatedProviders = append(authenticatedProviders, provider) - } - } - - return authenticatedProviders, nil -} - -// ClearAll removes all stored secrets (use with caution) -func (s *Store) ClearAll() error { - providers := []string{"enkryptify"} // Known providers - - for _, provider := range providers { - // Try to delete auth info - if err := s.DeleteAuthInfo(provider); err != nil { - // Log error but continue with other providers - fmt.Printf("Warning: failed to delete auth info for %s: %v\n", provider, err) - } - } - - return nil -} diff --git a/internal/providers/enkryptify/provider.go b/internal/providers/enkryptify/provider.go deleted file mode 100644 index 75b20a3..0000000 --- a/internal/providers/enkryptify/provider.go +++ /dev/null @@ -1,180 +0,0 @@ -package enkryptify - -import ( - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/Enkryptify/cli/internal/auth" -) - -const ( - BaseAPIURL = "https://api.enkryptify.com/v1" -) - -type Client struct { - httpClient *http.Client - auth *auth.EnkryptifyAuth -} - -type Workspace struct { - ID string `json:"id"` - Name string `json:"name"` - Slug string `json:"slug"` - OwnerID string `json:"ownerId"` -} - -type Project struct { - ID string `json:"id"` - Name string `json:"name"` - Slug string `json:"slug"` - Secrets int `json:"secrets"` - Environments int `json:"environments"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` -} - -type TeamWithProjects struct { - ID string `json:"id"` - Name string `json:"name"` - Projects []Project `json:"projects"` -} - -type ProjectDetail struct { - ID string `json:"id"` - Name string `json:"name"` - Slug string `json:"slug"` - Environments []Environment `json:"environments"` -} - -type Environment struct { - ID string `json:"id"` - Name string `json:"name"` -} - -type Secret struct { - ID string `json:"id"` - Name string `json:"name"` - Note string `json:"note"` - Type string `json:"type"` - DataType string `json:"dataType"` - Values []SecretValue `json:"values"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` -} - -type SecretValue struct { - EnvironmentID string `json:"environmentId"` - Value string `json:"value"` -} - -type ProjectTokenResponse struct { - ID string `json:"id"` - Workspace struct { - ID string `json:"id"` - Slug string `json:"slug"` - } `json:"workspace"` - Project struct { - ID string `json:"id"` - Slug string `json:"slug"` - } `json:"project"` - EnvironmentID string `json:"environmentId"` -} - -func NewClient() *Client { - return &Client{ - httpClient: &http.Client{Timeout: 30 * time.Second}, - auth: auth.NewEnkryptifyAuth(), - } -} - -func (c *Client) makeRequest(method, endpoint string, result interface{}) error { - accessToken, err := c.auth.GetAccessToken() - if err != nil { - return fmt.Errorf("failed to get access token: %w", err) - } - - req, err := http.NewRequest(method, BaseAPIURL+endpoint, nil) - if err != nil { - return fmt.Errorf("failed to create request: %w", err) - } - - req.Header.Set("X-API-Key", accessToken) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return fmt.Errorf("request failed: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("API request failed with status %d", resp.StatusCode) - } - - if err := json.NewDecoder(resp.Body).Decode(result); err != nil { - return fmt.Errorf("failed to decode response: %w", err) - } - - return nil -} - -func (c *Client) GetWorkspaces() ([]Workspace, error) { - var workspaces []Workspace - - if err := c.makeRequest("GET", "/workspace", &workspaces); err != nil { - return nil, fmt.Errorf("failed to get workspaces: %w", err) - } - - return workspaces, nil -} - -func (c *Client) GetProjects(workspaceSlug string) ([]Project, error) { - var teamsWithProjects []TeamWithProjects - - endpoint := fmt.Sprintf("/workspace/%s/project", workspaceSlug) - if err := c.makeRequest("GET", endpoint, &teamsWithProjects); err != nil { - return nil, fmt.Errorf("failed to get projects: %w", err) - } - - var allProjects []Project - for _, team := range teamsWithProjects { - allProjects = append(allProjects, team.Projects...) - } - - return allProjects, nil -} - -func (c *Client) GetProjectDetail(workspaceSlug, projectSlug string) (*ProjectDetail, error) { - var projectDetail ProjectDetail - - endpoint := fmt.Sprintf("/workspace/%s/project/%s", workspaceSlug, projectSlug) - if err := c.makeRequest("GET", endpoint, &projectDetail); err != nil { - return nil, fmt.Errorf("failed to get project detail: %w", err) - } - - return &projectDetail, nil -} - -func (c *Client) GetSecrets(workspaceSlug, projectSlug, environmentID string) ([]Secret, error) { - var secrets []Secret - - endpoint := fmt.Sprintf("/workspace/%s/project/%s/secret?environmentId=%s", workspaceSlug, projectSlug, environmentID) - if err := c.makeRequest("GET", endpoint, &secrets); err != nil { - return nil, fmt.Errorf("failed to get secrets: %w", err) - } - - return secrets, nil -} - -func (c *Client) GetProjectTokenDetails() (*ProjectTokenResponse, error) { - var tokenDetails ProjectTokenResponse - - if err := c.makeRequest("GET", "/auth/project-token", &tokenDetails); err != nil { - return nil, fmt.Errorf("failed to get project token details: %w", err) - } - - return &tokenDetails, nil -} \ No newline at end of file diff --git a/internal/ui/login.go b/internal/ui/login.go deleted file mode 100644 index ce1ce0b..0000000 --- a/internal/ui/login.go +++ /dev/null @@ -1,31 +0,0 @@ -package ui - -import ( - "fmt" -) - -func ShowAuthSuccess(email string) { - fmt.Println() - PrintSeparator() - PrintSuccess("Successfully authenticated with Enkryptify!") - if email != "" { - PrintInfo(fmt.Sprintf("Logged in as: %s", email)) - } - PrintInfo("You can now use the Enkryptify CLI to manage your secrets.") - PrintSeparator() -} - -func ShowAuthError(err error) { - fmt.Println() - PrintSeparator() - PrintError("Authentication failed!") - PrintError(err.Error()) - PrintInfo("Please try running 'ek login' again.") - PrintSeparator() -} - -func ShowWaitingForAuth() { - fmt.Println() - PrintInfo("Waiting for authentication to complete...") - PrintSubtitle("Please complete the authentication in your web browser.") -} \ No newline at end of file diff --git a/internal/ui/selection.go b/internal/ui/selection.go deleted file mode 100644 index e90cadd..0000000 --- a/internal/ui/selection.go +++ /dev/null @@ -1,233 +0,0 @@ -package ui - -import ( - "fmt" - "os" - "os/exec" - "strings" - - "github.com/charmbracelet/lipgloss" -) - -// capitalize returns a string with the first letter capitalized -func capitalize(s string) string { - if s == "" { - return s - } - return strings.ToUpper(s[:1]) + s[1:] -} - -type SelectionItem struct { - ID string - Name string - Slug string - Description string -} - -// Terminal control functions -func enableRawMode() (*exec.Cmd, error) { - cmd := exec.Command("stty", "-echo", "cbreak") - cmd.Stdin = os.Stdin - return cmd, cmd.Run() -} - -func disableRawMode() { - cmd := exec.Command("stty", "echo", "-cbreak") - cmd.Stdin = os.Stdin - cmd.Run() -} - -func hideCursor() { - fmt.Print("\033[?25l") -} - -func showCursor() { - fmt.Print("\033[?25h") -} - -func clearScreen() { - fmt.Print("\033[2J\033[H") -} - - - -// Styles for the interactive selection -var ( - selectedItemStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#000000")). - Background(primaryColor). - Bold(true). - Padding(0, 2). - Margin(0, 1) - - unselectedItemStyle = lipgloss.NewStyle(). - Foreground(primaryColor). - Padding(0, 2). - Margin(0, 1) - - itemDescriptionStyle = lipgloss.NewStyle(). - Foreground(mutedColor). - Italic(true). - PaddingLeft(4) - - instructionStyle = lipgloss.NewStyle(). - Foreground(mutedColor). - Italic(true). - Margin(1, 0) - - headerStyle = lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder()). - BorderForeground(primaryColor). - Padding(1, 2). - Margin(1, 0) -) - -func SelectFromList(items []SelectionItem, itemType string) (*SelectionItem, error) { - if len(items) == 0 { - return nil, fmt.Errorf("no %s available", itemType) - } - - if len(items) == 1 { - PrintInfo(fmt.Sprintf("Found 1 %s: %s", itemType, items[0].Name)) - return &items[0], nil - } - - return interactiveSelect(items, itemType) -} - -func interactiveSelect(items []SelectionItem, itemType string) (*SelectionItem, error) { - selectedIndex := 0 - - // Enable raw mode for key detection - _, err := enableRawMode() - if err != nil { - // Fallback to simple numbered selection if raw mode fails - return fallbackSelect(items, itemType) - } - defer disableRawMode() - - hideCursor() - defer showCursor() - - for { - clearScreen() - renderSelection(items, itemType, selectedIndex) - - // Read single character input - var b [1]byte - os.Stdin.Read(b[:]) - - switch b[0] { - case 27: // ESC sequence - // Read the next two characters for arrow keys - var seq [2]byte - os.Stdin.Read(seq[:]) - if seq[0] == 91 { // '[' - switch seq[1] { - case 65: // Up arrow - if selectedIndex > 0 { - selectedIndex-- - } - case 66: // Down arrow - if selectedIndex < len(items)-1 { - selectedIndex++ - } - } - } - case 13, 10: // Enter - clearScreen() - selected := &items[selectedIndex] - PrintSuccess(fmt.Sprintf("Selected %s: %s (%s)", itemType, selected.Name, selected.Slug)) - return selected, nil - case 'y', 'Y': // Y key - clearScreen() - selected := &items[selectedIndex] - PrintSuccess(fmt.Sprintf("Selected %s: %s (%s)", itemType, selected.Name, selected.Slug)) - return selected, nil - case 'q', 'Q', 3: // Q key or Ctrl+C - clearScreen() - return nil, fmt.Errorf("selection cancelled") - } - } -} - -func renderSelection(items []SelectionItem, itemType string, selectedIndex int) { - // Header - header := fmt.Sprintf("Select %s", capitalize(itemType)) - fmt.Println(headerStyle.Render(header)) - - // Instructions - instructions := "Use ↑/↓ to navigate • Enter or Y to select • Q to quit" - fmt.Println(instructionStyle.Render(instructions)) - fmt.Println() - - // Items - for i, item := range items { - var itemLine string - var descLine string - - if i == selectedIndex { - itemLine = selectedItemStyle.Render(fmt.Sprintf("● %s (%s)", item.Name, item.Slug)) - } else { - itemLine = unselectedItemStyle.Render(fmt.Sprintf(" %s (%s)", item.Name, item.Slug)) - } - - fmt.Println(itemLine) - - if item.Description != "" { - if i == selectedIndex { - descLine = itemDescriptionStyle.Render(fmt.Sprintf(" %s", item.Description)) - } else { - descLine = itemDescriptionStyle.Render(fmt.Sprintf(" %s", item.Description)) - } - fmt.Println(descLine) - } - fmt.Println() - } -} - -// Fallback to numbered selection if raw mode fails -func fallbackSelect(items []SelectionItem, itemType string) (*SelectionItem, error) { - PrintTitle(fmt.Sprintf("Select %s", capitalize(itemType))) - PrintInfo(fmt.Sprintf("Found %d %s:", len(items), itemType)) - - for i, item := range items { - description := "" - if item.Description != "" { - description = fmt.Sprintf(" - %s", item.Description) - } - PrintInfo(fmt.Sprintf("%d. %s (%s)%s", i+1, item.Name, item.Slug, description)) - } - - for { - fmt.Print("\nEnter your choice (1-" + fmt.Sprintf("%d", len(items)) + "): ") - var input string - fmt.Scanln(&input) - - if choice := parseChoice(input, len(items)); choice >= 0 { - selected := &items[choice] - PrintSuccess(fmt.Sprintf("Selected %s: %s (%s)", itemType, selected.Name, selected.Slug)) - return selected, nil - } - PrintError(fmt.Sprintf("Invalid choice. Please enter a number between 1 and %d", len(items))) - } -} - -func parseChoice(input string, maxItems int) int { - input = strings.TrimSpace(input) - if input == "" { - return -1 - } - - var choice int - if n, err := fmt.Sscanf(input, "%d", &choice); n == 1 && err == nil { - if choice >= 1 && choice <= maxItems { - return choice - 1 - } - } - return -1 -} - -func ShowProgress(step int, total int, message string) { - PrintInfo(fmt.Sprintf("[%d/%d] %s", step, total, message)) -} diff --git a/internal/ui/ui.go b/internal/ui/ui.go deleted file mode 100644 index 3e41f8d..0000000 --- a/internal/ui/ui.go +++ /dev/null @@ -1,151 +0,0 @@ -package ui - -import ( - "fmt" - "strings" - - "github.com/charmbracelet/lipgloss" -) - -var ( - primaryColor = lipgloss.Color("#2B7FFF") - errorColor = lipgloss.Color("#E64545") - warningColor = lipgloss.Color("#FFC233") - successColor = lipgloss.Color("#2AC769") - mutedColor = lipgloss.Color("#777777") - - // Base styles - titleStyle = lipgloss.NewStyle(). - Foreground(primaryColor). - Bold(true). - Margin(1, 0) - - subtitleStyle = lipgloss.NewStyle(). - Foreground(mutedColor). - Italic(true) - - successStyle = lipgloss.NewStyle(). - Foreground(successColor). - Bold(true) - - errorStyle = lipgloss.NewStyle(). - Foreground(errorColor). - Bold(true) - - warningStyle = lipgloss.NewStyle(). - Foreground(warningColor). - Bold(true) - - infoStyle = lipgloss.NewStyle() - - codeStyle = lipgloss.NewStyle(). - Background(lipgloss.Color("#2A2A2A")). - Foreground(lipgloss.Color("#FFFFFF")). - Padding(0, 1). - Margin(1, 0) -) - -// Spinner represents a simple text-based loading spinner -type Spinner struct { - frames []string - index int -} - -// NewSpinner creates a new spinner instance -func NewSpinner() *Spinner { - return &Spinner{ - frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, - index: 0, - } -} - -// Next returns the next frame of the spinner -func (s *Spinner) Next() string { - frame := s.frames[s.index] - s.index = (s.index + 1) % len(s.frames) - return frame -} - -// PrintTitle prints a styled title -func PrintTitle(title string) { - fmt.Println(titleStyle.Render(title)) -} - -// PrintSubtitle prints a styled subtitle -func PrintSubtitle(subtitle string) { - fmt.Println(subtitleStyle.Render(subtitle)) -} - -// PrintSuccess prints a success message -func PrintSuccess(message string) { - fmt.Println(successStyle.Render("✓ " + message)) -} - -// PrintError prints an error message -func PrintError(message string) { - fmt.Println(errorStyle.Render("✗ " + message)) -} - -// PrintWarning prints a warning message -func PrintWarning(message string) { - fmt.Println(warningStyle.Render("⚠ " + message)) -} - -// PrintInfo prints an info message -func PrintInfo(message string) { - fmt.Println(infoStyle.Render(message)) -} - -// PrintCode prints code or URLs in a styled box -func PrintCode(code string) { - fmt.Println(codeStyle.Render(code)) -} - -// PrintSeparator prints a visual separator -func PrintSeparator() { - separator := strings.Repeat("─", 60) - fmt.Println(lipgloss.NewStyle().Foreground(mutedColor).Render(separator)) -} - -// ShowLoadingMessage shows a loading message with optional spinner -func ShowLoadingMessage(message string, showSpinner bool) { - if showSpinner { - spinner := NewSpinner() - fmt.Printf("\r%s %s", spinner.Next(), message) - } else { - PrintInfo(message) - } -} - -// ConfirmAction asks for user confirmation -func ConfirmAction(message string) bool { - fmt.Printf("%s (y/N): ", message) - var response string - fmt.Scanln(&response) - return strings.ToLower(strings.TrimSpace(response)) == "y" -} - -// ShowProviderInfo shows information about the current provider -func ShowProviderInfo(provider string, authenticated bool) { - PrintInfo(fmt.Sprintf("Provider: %s", provider)) - if authenticated { - PrintSuccess("Status: Authenticated") - } else { - PrintWarning("Status: Not authenticated") - } -} - -// ShowBrandHeader shows the Enkryptify brand header -func ShowBrandHeader() { - header := ` - _____ _ _ _ __ - | ____|_ __ | | ___ __ _ _ _ __ | |_(_)/ _|_ _ - | _| | '_ \| |/ / '__| | | | '_ \| __| | |_| | | | - | |___| | | | <| | | |_| | |_) | |_| | _| |_| | - |_____|_| |_|_|\_\_| \__, | .__/ \__|_|_| \__, | - |___/|_| |___/ -` - - fmt.Println(titleStyle.Render(header)) - PrintSeparator() -} diff --git a/main.go b/main.go deleted file mode 100644 index 411efc8..0000000 --- a/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/Enkryptify/cli/cmd" - -func main() { - cmd.Execute() -} diff --git a/package.json b/package.json new file mode 100644 index 0000000..20e2e4b --- /dev/null +++ b/package.json @@ -0,0 +1,80 @@ +{ + "name": "cli", + "module": "index.ts", + "type": "module", + "private": true, + "scripts": { + + + "build": "bun build src/cli.ts --outdir dist --target node --minify", + "package": "bun run build && pkg dist/cli.js --out-path bin --targets node18-linux-x64,node18-linux-arm64,node18-macos-x64,node18-macos-arm64,node18-win-x64,node18-win-arm64", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "postinstall": "bun run scripts/patch-1password.ts" + + }, + "pkg": { + "scripts": ["dist/**/*.js"], + "targets": [ + "node18-linux-x64", + "node18-linux-arm64", + "node18-macos-x64", + "node18-macos-arm64", + "node18-win-x64", + "node18-win-arm64" + ], + "outputPath": "bin" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/bun": "^1.3.3", + "@types/node": "^24.10.1", + "@types/prompts": "^2.4.9", + "@types/react": "^19.2.7", + "@types/semver": "^7.7.1", + "@typescript-eslint/eslint-plugin": "^8.49.0", + "@typescript-eslint/parser": "^8.49.0", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-security": "^3.0.1", + "globals": "^16.5.0", + "jiti": "^2.6.1", + "pkg": "^5.8.1", + "prettier": "^3.7.3", + "typescript-eslint": "^8.48.1" + }, + "peerDependencies": { + "typescript": "^5.9.3" + }, + "dependencies": { + "@1password/sdk": "^0.3.1", + "@aws-sdk/client-secrets-manager": "^3.950.0", + "@aws-sdk/client-sso-oidc": "^3.946.0", + "@aws-sdk/client-sts": "^3.948.0", + "@aws-sdk/credential-provider-sso": "^3.946.0", + "@google-cloud/resource-manager": "^6.2.1", + "@google-cloud/secret-manager": "^6.1.1", + "@t3-oss/env-core": "^0.13.8", + "axios": "^1.13.2", + "commander": "^14.0.2", + "dotenv": "^17.2.3", + "google-auth-library": "^10.5.0", + "ink": "^6.5.1", + "ink-big-text": "^2.0.0", + "ink-gradient": "^3.0.0", + "ink-select-input": "^6.2.0", + "ink-spinner": "^5.0.0", + "ink-table": "^3.1.0", + "ink-text-input": "^6.0.0", + "keytar": "^7.9.0", + "open": "^11.0.0", + "prompts": "^2.4.2", + "react": "^19.2.0", + "react-devtools-core": "^6.1.2", + "semver": "^7.7.3", + "zod": "^4.1.13" + } +} diff --git a/scripts/patch-1password.ts b/scripts/patch-1password.ts new file mode 100644 index 0000000..c599173 --- /dev/null +++ b/scripts/patch-1password.ts @@ -0,0 +1,69 @@ +import { existsSync, readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; + +console.log("🔧 Patching 1Password SDK for bundled execution...\n"); + +const sdkPath = resolve("node_modules/@1password/sdk-core/nodejs/index.js"); + +if (!existsSync(sdkPath)) { + console.warn("⚠️ 1Password SDK not found yet at:", sdkPath); + console.warn(" This is normal during initial install. SDK will be patched after installation completes."); + console.warn(" If you see this after install, run: bun run patch:1password\n"); + process.exit(0); // Exit successfully, don't fail the install +} + +let content = readFileSync(sdkPath, "utf-8"); + +// Check if already patched +if (content.includes("// ENKRYPTIFY BUNDLING PATCH")) { + console.log("✅ SDK already patched for bundled execution"); + process.exit(0); +} + +// Find the WASM loading pattern +const patterns = [ + /var bytes = __require\("fs"\)\.readFileSync\(path3\);/g, + /const bytes = require\("fs"\)\.readFileSync\(wasmPath\);/g, + /fs\.readFileSync\(.*?\.wasm['"]\)/g, +]; + +let patched = false; + +for (const pattern of patterns) { + if (pattern.test(content)) { + content = content.replace( + pattern, + `// ENKRYPTIFY BUNDLING PATCH + var fs = __require("fs") || require("fs"); + var path = __require("path") || require("path"); + var wasmPath = path3 || arguments[0]; + + // When bundled, look for WASM next to the executable + if (process.execPath && (process.execPath.includes('ek-darwin') || process.execPath.includes('ek-linux') || process.execPath.includes('ek.exe'))) { + var execDir = path.dirname(process.execPath); + var bundledWasm = path.join(execDir, 'core_bg.wasm'); + if (fs.existsSync(bundledWasm)) { + wasmPath = bundledWasm; + console.log('📦 Using bundled WASM:', bundledWasm); + } + } + + var bytes = fs.readFileSync(wasmPath);`, + ); + patched = true; + break; + } +} + +if (patched) { + writeFileSync(sdkPath, content); + console.log("✅ Successfully patched 1Password SDK!"); + console.log(" WASM will be loaded from executable directory when bundled\n"); +} else { + console.warn("⚠️ Could not find WASM loading pattern in SDK"); + console.warn(" The SDK structure may have changed"); + console.warn(" Build will continue, but runtime may fail\n"); + + // Don't exit with error - let the build continue + // The developer will see the runtime error if this fails +} diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..5ed856c --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,48 @@ +import { registerCommands } from "@/cmd/index"; +import { env } from "@/env"; +import { logError } from "@/lib/error"; +import { setupTerminalCleanup } from "@/lib/terminal"; +import "@/providers/registry/index.js"; +import { Command } from "commander"; +import { getCompletions } from "./complete/complete"; + +const isCompletion = process.argv[2] === "__complete"; + +const program = new Command(); + +program.configureOutput({ + writeErr: (str) => { + const errorMatch = str.match(/error:\s*(.+)/i); + if (errorMatch && errorMatch[1]) { + logError(errorMatch[1].trim()); + } else { + logError(str.trim()); + } + }, +}); + +program + .name("kk") + .description("Enkryptify CLI") + .version(env.CLI_VERSION ?? "0.0.0", "-v, --version"); + +registerCommands(program); + +if (isCompletion) { + const words = process.argv.slice(3); + const completions = getCompletions(program, ["ek", ...words]); + console.log(completions.join("\n")); + process.exit(0); +} + +setupTerminalCleanup(); + +program + .parseAsync(process.argv) + .then(() => { + process.exit(0); + }) + .catch((error) => { + logError(error instanceof Error ? error.message : String(error)); + process.exit(1); + }); diff --git a/src/cmd/configure.ts b/src/cmd/configure.ts new file mode 100644 index 0000000..0da6c02 --- /dev/null +++ b/src/cmd/configure.ts @@ -0,0 +1,57 @@ +import { config } from "@/lib/config"; +import { logError } from "@/lib/error"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; +import type { Command } from "commander"; + +export async function configure(providerName: string | undefined): Promise { + const fallbackProviderName = "enkryptify"; + const finalProviderName = providerName || fallbackProviderName; + + const provider = providerRegistry.get(finalProviderName); + if (!provider) { + const availableProviders = providerRegistry + .list() + .map((p) => p.name) + .join(", "); + + if (!providerName) { + throw new Error( + `No provider specified and default "${fallbackProviderName}" is not available.\n` + + `Available providers: ${availableProviders || "none"}`, + ); + } else { + throw new Error( + `Provider "${finalProviderName}" not found. Available providers: ${availableProviders || "none"}`, + ); + } + } + const providerConfig = await config.getProvider(finalProviderName); + if (!providerConfig) { + throw new Error( + `Provider "${finalProviderName}" is not configured. Please run "ek login --provider ${finalProviderName}" first.`, + ); + } + + const projectPath = process.cwd(); + + const projectConfig = await provider.configure(projectPath); + + await config.createConfigure(projectPath, projectConfig); +} + +export function registerConfigureCommand(program: Command) { + program + .command("configure") + .alias("setup") + .description("The configure command is used to set up a project with a secrets provider.") + .option("--provider ", "Provider name (defaults to 'enkryptify' if available)") + .action(async (options: { provider?: string }) => { + try { + await configure(options.provider); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logError(errorMessage); + process.exit(1); + } + }); +} diff --git a/src/cmd/create.ts b/src/cmd/create.ts new file mode 100644 index 0000000..1fbe61f --- /dev/null +++ b/src/cmd/create.ts @@ -0,0 +1,61 @@ +import { config } from "@/lib/config"; +import { logError } from "@/lib/error"; +import { getSecureInput } from "@/lib/input"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; +import { showMessage } from "@/ui/SuccessMessage"; +import type { Command } from "commander"; + +export async function createSecretCommand(name: string, value: string): Promise { + const namePattern = /^[A-Za-z0-9_-]+$/; + if (!namePattern.test(name)) { + throw new Error( + `Invalid secret name "${name}". Name can only contain A-Z, a-z, 0-9, underscore (_), and hyphen (-).`, + ); + } + + const projectConfig = await config.findProjectConfig(process.cwd()); + + const provider = providerRegistry.get(projectConfig.provider); + if (!provider) { + const availableProviders = providerRegistry + .list() + .map((p) => p.name) + .join(", "); + throw new Error(`Provider "${projectConfig.provider}" not found. Available providers: ${availableProviders}`); + } + + if (!value || value.trim().length === 0) { + throw new Error("Secret value cannot be empty."); + } + + await provider.createSecret(projectConfig, name, value); + + showMessage(`Secret created successfully! Name: ${name}`); +} + +export function registerCreateCommand(program: Command) { + program + .command("create") + .description("Create a new secret in the current environment") + .argument("", "Secret name (key) - can only contain A-Z, a-z, 0-9, underscore (_), and hyphen (-)") + .argument( + "[value]", + 'Secret value. Use quotes for values with spaces or special characters Example: ek create "my value!@#$%^&*()"', + ) + .action(async (name: string, value?: string) => { + try { + let secretValue = value; + if (!secretValue || secretValue.trim().length === 0) { + secretValue = await getSecureInput("Enter secret value: "); + if (!secretValue || secretValue.trim().length === 0) { + throw new Error("Secret value cannot be empty please provide a value."); + } + } + await createSecretCommand(name, secretValue); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logError(errorMessage); + process.exit(1); + } + }); +} diff --git a/src/cmd/delete.ts b/src/cmd/delete.ts new file mode 100644 index 0000000..94723f2 --- /dev/null +++ b/src/cmd/delete.ts @@ -0,0 +1,39 @@ +import { config } from "@/lib/config"; +import { logError } from "@/lib/error"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; +import type { Command } from "commander"; + +export async function deleteSecretCommand(name: string): Promise { + if (!name || !name.trim()) { + throw new Error("Secret name is required. Please provide a secret name"); + } + + const projectConfig = await config.findProjectConfig(process.cwd()); + + const provider = providerRegistry.get(projectConfig.provider); + if (!provider) { + const availableProviders = providerRegistry + .list() + .map((p) => p.name) + .join(", "); + throw new Error(`Provider "${projectConfig.provider}" not found. Available providers: ${availableProviders}`); + } + + await provider.deleteSecret(projectConfig, name); +} + +export function registerDeleteCommand(program: Command) { + program + .command("delete") + .description("The delete command is used to delete a secret from the current environment.") + .argument("", "Secret name (key) to delete. Example: ek delete MySecret") + .action(async (name: string) => { + try { + await deleteSecretCommand(name); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logError(errorMessage); + process.exit(1); + } + }); +} diff --git a/src/cmd/index.ts b/src/cmd/index.ts new file mode 100644 index 0000000..4e3e1cc --- /dev/null +++ b/src/cmd/index.ts @@ -0,0 +1,18 @@ +import { registerConfigureCommand } from "@/cmd/configure"; +import { registerCreateCommand } from "@/cmd/create"; +import { registerDeleteCommand } from "@/cmd/delete"; +import { registerListCommand } from "@/cmd/listCommand"; +import { registerLoginCommand } from "@/cmd/login"; +import { registerRunCommand } from "@/cmd/run"; +import { registerUpdateCommand } from "@/cmd/update"; +import type { Command } from "commander"; + +export function registerCommands(program: Command) { + registerLoginCommand(program); + registerConfigureCommand(program); + registerRunCommand(program); + registerListCommand(program); + registerCreateCommand(program); + registerDeleteCommand(program); + registerUpdateCommand(program); +} diff --git a/src/cmd/listCommand.ts b/src/cmd/listCommand.ts new file mode 100644 index 0000000..e73f953 --- /dev/null +++ b/src/cmd/listCommand.ts @@ -0,0 +1,40 @@ +import { config } from "@/lib/config"; +import { logError } from "@/lib/error"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; +import { showSecretsTable } from "@/ui/SecretsTable"; +import { type Command } from "commander"; + +export async function ListSecretsCommand(mode: "show" | "hide"): Promise { + const projectConfig = await config.findProjectConfig(process.cwd()); + + const provider = providerRegistry.get(projectConfig.provider); + if (!provider) { + throw new Error( + `Provider "${projectConfig.provider}" not found. Available providers: ${providerRegistry + .list() + .map((p) => p.name) + .join(", ")}`, + ); + } + + const secrets = await provider.listSecrets(projectConfig, mode); + showSecretsTable(secrets); +} + +export function registerListCommand(program: Command) { + program + .command("list") + .description("The list command is used to show the secrets in the current environment.") + .option("-s, --show", "Show the table with the secrets values (default: masked)") + .action(async (opts: { show?: boolean }) => { + try { + const mode: "show" | "hide" = opts.show ? "show" : "hide"; + + await ListSecretsCommand(mode); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logError(errorMessage); + process.exit(1); + } + }); +} diff --git a/src/cmd/login.ts b/src/cmd/login.ts new file mode 100644 index 0000000..1a9ece6 --- /dev/null +++ b/src/cmd/login.ts @@ -0,0 +1,66 @@ +import { logError } from "@/lib/error"; +import { getSecureInput } from "@/lib/input"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; +import { LoginFlow } from "@/ui/LoginFlow"; +import type { Command } from "commander"; + +export function registerLoginCommand(program: Command) { + program + .command("login") + .description("The login command is used to authenticate with a provider.") + .option("-p, --provider ", "Provider name (defaults to 'enkryptify' if available)") + .option("-f, --force", "Force re-authentication even if already logged in") + .option("-k, --key ", "Token/key for providers that require it (e.g. 1Password)") + .action(async (options: { provider?: string; force?: boolean; key?: string }) => { + const fallbackProviderName = "enkryptify"; + const providerName = options.provider || fallbackProviderName; + + const providerInstance = providerRegistry.get(providerName); + + if (!providerInstance) { + const availableProviders = providerRegistry + .list() + .map((p) => p.name) + .join(", "); + + if (!options.provider) { + logError( + `No provider specified and default "${fallbackProviderName}" is not available.\n` + + `Available providers: ${availableProviders || "none"}`, + ); + } else { + logError( + `Provider "${providerName}" not found. Available providers: ${availableProviders || "none"}`, + ); + } + + process.exit(1); + } + + // For 1Password, prompt for token BEFORE showing UI if not provided via CLI + let token = options.key; + if (providerName === "onePassword" && !token) { + token = await getSecureInput("Enter your 1Password service account token (input hidden): "); + } + + try { + await LoginFlow({ + provider: providerInstance, + options: { + providerName: providerName, + force: options.force, + key: token, + }, + onError: (error) => { + const errorMessage = error instanceof Error ? error.message : String(error); + logError(errorMessage); + process.exit(1); + }, + }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logError(errorMessage); + process.exit(1); + } + }); +} diff --git a/src/cmd/run.ts b/src/cmd/run.ts new file mode 100644 index 0000000..a0f0705 --- /dev/null +++ b/src/cmd/run.ts @@ -0,0 +1,68 @@ +import { type ProjectConfig, config } from "@/lib/config"; +import { logError } from "@/lib/error"; +import { buildEnvWithSecrets } from "@/lib/inject"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; +import { RunFlow } from "@/ui/RunFlow"; +import type { Command } from "commander"; + +export async function runCommand( + projectconfig: ProjectConfig, + cmd: string[], + options?: { env?: string }, +): Promise { + const provider = providerRegistry.get(projectconfig.provider); + if (!provider) { + const availableProviders = providerRegistry + .list() + .map((p) => p.name) + .join(", "); + throw new Error(`Provider "${projectconfig.provider}" not found. Available providers: ${availableProviders}`); + } + + const secrets = await provider.run(projectconfig, { env: options?.env }); + const env = buildEnvWithSecrets(secrets); + + const [bin, ...args] = cmd; + + if (!bin) { + throw new Error("Command is required. Please provide a command to run."); + } + const proc = Bun.spawn([bin, ...args], { + env: env, + stdout: "inherit", + stderr: "inherit", + }); + + const exitCode = await proc.exited; + + if (exitCode !== 0) { + process.exit(exitCode); + } +} + +export function registerRunCommand(program: Command) { + program + .command("run") + .description("Run a command with secrets from the provider injected as environment variables.") + .option("-e, --env ", "Environment name to use (overrides default from config)") + .argument( + "", + "Command and arguments to run (e.g. 'pnpm run dev' or use '--' to separate: 'ek run -- pnpm run dev')", + ) + .action(async (cmd: string[], opts: { env?: string }) => { + try { + const projectConfig: ProjectConfig = await config.findProjectConfig(process.cwd()); + + await RunFlow({ + envName: opts.env, + run: async () => { + await runCommand(projectConfig, cmd, opts); + }, + }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logError(errorMessage); + process.exit(1); + } + }); +} diff --git a/src/cmd/update.ts b/src/cmd/update.ts new file mode 100644 index 0000000..d56282e --- /dev/null +++ b/src/cmd/update.ts @@ -0,0 +1,40 @@ +import { config } from "@/lib/config"; +import { logError } from "@/lib/error"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; +import type { Command } from "commander"; + +export async function updateSecretCommand(name: string, isPersonal?: boolean): Promise { + if (!name || !name.trim()) { + throw new Error("Secret name is required. Please provide a secret name"); + } + + const projectConfig = await config.findProjectConfig(process.cwd()); + + const provider = providerRegistry.get(projectConfig.provider); + if (!provider) { + const availableProviders = providerRegistry + .list() + .map((p) => p.name) + .join(", "); + throw new Error(`Provider "${projectConfig.provider}" not found. Available providers: ${availableProviders}`); + } + + await provider.updateSecret(projectConfig, name, isPersonal); +} + +export function registerUpdateCommand(program: Command) { + program + .command("update") + .description("Update a secret in the current environment") + .argument("", "Secret name (key) to update. Example: ek update MySecret") + .option("--ispersonal", "Make the secret personal this flag is only available for enkryptify provider") + .action(async (name: string, opts?: { ispersonal?: boolean }) => { + try { + await updateSecretCommand(name, opts?.ispersonal); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logError(errorMessage); + process.exit(1); + } + }); +} diff --git a/src/complete/complete.ts b/src/complete/complete.ts new file mode 100644 index 0000000..b6881cc --- /dev/null +++ b/src/complete/complete.ts @@ -0,0 +1,22 @@ +import type { Command } from "commander"; + +function getAliases(c: Command): string[] { + return typeof c.aliases === "function" ? c.aliases() : []; +} + +export function getCompletions(program: Command, words: string[]): string[] { + let cmd: Command = program; + + const args = words.slice(1); + + for (const word of args) { + const sub = cmd.commands.find((c) => c.name() === word || getAliases(c).includes(word)); + if (!sub) break; + cmd = sub; + } + + const subcommands = cmd.commands.flatMap((c) => [c.name(), ...getAliases(c)]); + const options = cmd.options.map((o) => o.long).filter(Boolean) as string[]; + + return Array.from(new Set([...subcommands, ...options])); +} diff --git a/src/complete/ek.bash b/src/complete/ek.bash new file mode 100644 index 0000000..2db3dd9 --- /dev/null +++ b/src/complete/ek.bash @@ -0,0 +1,9 @@ +_ek() { + local cur + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + + mapfile -t COMPREPLY < <(ek __complete "${COMP_WORDS[@]:1}") +} + +complete -F _ek ek diff --git a/src/complete/ek.ps1 b/src/complete/ek.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/src/complete/ek.zsh b/src/complete/ek.zsh new file mode 100644 index 0000000..5e302f2 --- /dev/null +++ b/src/complete/ek.zsh @@ -0,0 +1,9 @@ + +_ek_complete() { + local -a suggestions + suggestions=("${(@f)$(ek __complete ${words[2,-1]})}") + _describe 'values' suggestions +} + +compdef _ek_complete ek + diff --git a/src/complete/install-completions.sh b/src/complete/install-completions.sh new file mode 100644 index 0000000..e69de29 diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 0000000..a81b12f --- /dev/null +++ b/src/env.ts @@ -0,0 +1,11 @@ +import { createEnv } from "@t3-oss/env-core"; +import { z } from "zod"; + +export const env = createEnv({ + server: { + API_BASE_URL: z.string().url("API_BASE_URL must be a valid URL"), + APP_BASE_URL: z.string().url("APP_BASE_URL must be a valid URL"), + CLI_VERSION: z.string(), + }, + runtimeEnv: process.env, +}); diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..723a496 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,228 @@ +import { logError } from "@/lib/error"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; +import * as fs from "fs/promises"; +import * as os from "os"; +import * as path from "path"; + +export type ProjectConfig = { + path: string; + provider: string; + [key: string]: string; +}; + +type ConfigFile = { + setups: { + [projectPath: string]: { + provider: string; + [key: string]: string; + }; + }; + providers: { + [providerName: string]: Record; + }; +}; + +const CONFIG_FILE = path.join(os.homedir(), ".enkryptify", "config.json"); + +function exitWithError(message: string): never { + logError(`FATAL ERROR: ${message}\nThe application cannot continue.`); + process.exit(1); +} + +async function createDefaultConfig(): Promise { + const defaultConfig: ConfigFile = { setups: {}, providers: {} }; + + try { + await fs.mkdir(path.dirname(CONFIG_FILE), { recursive: true }); + await fs.writeFile(CONFIG_FILE, JSON.stringify(defaultConfig, null, 2), "utf-8"); + return defaultConfig; + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err); + const errorCode = err instanceof Error && "code" in err ? err.code : undefined; + + let additionalInfo = ""; + if (errorCode === "EACCES") { + additionalInfo = + `\nThe directory "${path.dirname(CONFIG_FILE)}" exists but you don't have write permissions.\n` + + `Try running: chmod 755 "${path.dirname(CONFIG_FILE)}"`; + } else if (errorCode === "ENOENT") { + additionalInfo = `\nThe parent directory does not exist and could not be created.`; + } else { + additionalInfo = + `\nThis might be due to:\n` + + `- Insufficient permissions on the directory\n` + + `- Disk space issues\n` + + `- Filesystem restrictions\n` + + `\nYou can try creating the directory manually:\n` + + ` mkdir -p "${path.dirname(CONFIG_FILE)}"`; + } + + exitWithError( + `Failed to create configuration file:\n"${CONFIG_FILE}"\n\n` + `Error: ${errorMessage}${additionalInfo}`, + ); + } +} + +export async function loadConfig(): Promise { + try { + const data = await fs.readFile(CONFIG_FILE, "utf-8"); + + if (!data.trim()) { + const config: ConfigFile = { setups: {}, providers: {} }; + await saveConfig(config); + return config; + } + + try { + const parsed = JSON.parse(data) as unknown; + + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { + exitWithError( + `Configuration file must be a JSON object:\n"${CONFIG_FILE}"\n\n` + + `Found: ${Array.isArray(parsed) ? "array" : parsed === null ? "null" : typeof parsed}\n` + + `Expected: { "setups": {}, "providers": {} }\n` + + `Fix: Delete the file or edit it manually.`, + ); + } + + const config = parsed as Partial; + + if (!config.setups) config.setups = {}; + if (!config.providers) config.providers = {}; + if (Array.isArray(config.setups)) { + const setupsObj: ConfigFile["setups"] = {}; + + for (const setup of config.setups as ProjectConfig[]) { + if (!setup.path) continue; + const normalizedPath = path.resolve(setup.path); + const { path: _, ...setupData } = setup as ProjectConfig; + setupsObj[normalizedPath] = { + provider: setup.provider ?? "enkryptify", + ...(setupData as Record), + }; + } + + config.setups = setupsObj; + await saveConfig(config as ConfigFile); + } + + return config as ConfigFile; + } catch (parseErr: unknown) { + exitWithError( + `Configuration file contains invalid JSON:\n"${CONFIG_FILE}"\n\n` + + `Error: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}\n` + + `Please fix the file or delete it to start fresh.`, + ); + } + } catch (err: unknown) { + if (err instanceof Error && "code" in err && err.code === "ENOENT") { + return await createDefaultConfig(); + } + + if (err instanceof Error && "code" in err && err.code === "EACCES") { + exitWithError( + `Permission denied reading configuration file:\n"${CONFIG_FILE}"\n\n` + + `Please check file permissions.`, + ); + } + + const errorMessage = err instanceof Error ? err.message : String(err); + exitWithError(`Cannot access configuration file:\n"${CONFIG_FILE}"\n\n` + `Reason: ${errorMessage}`); + } +} + +export async function saveConfig(config: ConfigFile): Promise { + try { + await fs.mkdir(path.dirname(CONFIG_FILE), { recursive: true }); + + const tempFile = `${CONFIG_FILE}.tmp`; + await fs.writeFile(tempFile, JSON.stringify(config, null, 2), "utf-8"); + await fs.rename(tempFile, CONFIG_FILE); + } catch (err: unknown) { + if (err instanceof Error && "code" in err && err.code === "EACCES") { + exitWithError( + `Permission denied writing configuration file:\n"${CONFIG_FILE}"\n\n` + + `Please check file and directory permissions.`, + ); + } + + if (err instanceof Error && "code" in err && err.code === "EROFS") { + exitWithError( + `Cannot write to read-only filesystem:\n"${CONFIG_FILE}"\n\n` + + `The filesystem is mounted as read-only.`, + ); + } + + const errorMessage = err instanceof Error ? err.message : String(err); + exitWithError(`Cannot save configuration file:\n"${CONFIG_FILE}"\n\n` + `Reason: ${errorMessage}`); + } +} + +async function updateProvider(providerName: string, settings: Record): Promise { + if (!providerRegistry.has(providerName)) { + const available = providerRegistry.list().map((p) => p.name); + const availableList = available.length > 0 ? available.join(", ") : "none"; + + exitWithError(`Provider "${providerName}" does not exist.\n` + `Available providers: ${availableList}`); + } + + const config = await loadConfig(); + + config.providers[providerName] = { + ...(config.providers[providerName] || {}), + ...settings, + }; + + await saveConfig(config); +} + +async function getProvider(providerName: string): Promise | null> { + const config = await loadConfig(); + return config.providers?.[providerName] || null; +} + +async function createConfigure(projectPath: string, projectConfig: ProjectConfig): Promise { + const config = await loadConfig(); + const normalizedPath = path.resolve(projectPath); + + if (!config.setups) config.setups = {}; + + const { path: _, ...setupData } = projectConfig; + config.setups[normalizedPath] = setupData; + + await saveConfig(config); +} + +async function getConfigure(projectPath: string): Promise { + const config = await loadConfig(); + const normalizedPath = path.resolve(projectPath); + const setup = config.setups?.[normalizedPath]; + return setup ? { path: normalizedPath, ...setup } : null; +} + +async function findProjectConfig(startPath: string): Promise { + const config = await loadConfig(); + let currentPath = path.resolve(startPath); + const root = path.parse(currentPath).root; + + while (currentPath !== root) { + const normalizedPath = path.resolve(currentPath); + const setup = config.setups?.[normalizedPath]; + if (setup) { + return { path: normalizedPath, ...setup }; + } + currentPath = path.dirname(currentPath); + } + + throw new Error( + "No project configuration found. Please run 'ek configure ' to set up your project first.", + ); +} + +export const config = { + updateProvider, + getProvider, + createConfigure, + getConfigure, + findProjectConfig, +}; diff --git a/src/lib/error.ts b/src/lib/error.ts new file mode 100644 index 0000000..e068077 --- /dev/null +++ b/src/lib/error.ts @@ -0,0 +1,5 @@ +export function logError(message: string): void { + const red = "\x1b[31m"; + const reset = "\x1b[0m"; + console.error(`${red}⚠️ ${message}${reset}`); +} diff --git a/src/lib/inject.ts b/src/lib/inject.ts new file mode 100644 index 0000000..1de7887 --- /dev/null +++ b/src/lib/inject.ts @@ -0,0 +1,67 @@ +import type { Secret } from "@/providers/base/Provider"; + +/** + * Environment variables that should never be overridden by secrets + * These control critical system behavior and could be exploited + */ +const DANGEROUS_ENV_VARS = new Set([ + "PATH", + "PATHEXT", + + "LD_PRELOAD", + "LD_LIBRARY_PATH", + "DYLD_LIBRARY_PATH", + "DYLD_INSERT_LIBRARIES", + "DYLD_FRAMEWORK_PATH", + + "PYTHONPATH", + "NODE_PATH", + "PERL5LIB", + "RUBYLIB", + "CLASSPATH", + + "IFS", + "CDPATH", + "ENV", + "BASH_ENV", + "SHELL", + + "HOME", + "USER", + "USERNAME", + "SUDO_USER", + "SUDO_UID", + "SUDO_GID", +]); + +function isDangerousEnvVar(name: string): boolean { + if (!name || typeof name !== "string") { + return false; + } + return DANGEROUS_ENV_VARS.has(name.toUpperCase()); +} + +export function buildEnvWithSecrets(secrets: Secret[]): typeof process.env { + const env = { ...process.env }; + + for (const secret of secrets) { + if (!secret?.name) continue; + + if (isDangerousEnvVar(secret.name)) { + console.warn( + `⚠️ Warning: Secret "${secret.name}" conflicts with a protected environment variable ` + + `(${secret.name.toUpperCase()}). It will not be injected for security reasons.`, + ); + continue; + } + + if (secret.name.includes("\0")) { + console.warn(`⚠️ Warning: Secret name "${secret.name}" contains null bytes and will be skipped.`); + continue; + } + + env[secret.name] = secret.value; + } + + return env; +} diff --git a/src/lib/input.ts b/src/lib/input.ts new file mode 100644 index 0000000..310816b --- /dev/null +++ b/src/lib/input.ts @@ -0,0 +1,35 @@ +import prompts from "prompts"; + +export async function getSecureInput(prompt: string): Promise { + const response = await prompts({ + type: "password", + name: "value", + message: prompt, + validate: (value: string) => { + if (!value || value.trim().length === 0) { + return "Value cannot be empty"; + } + return true; + }, + }); + + if (!response.value) { + throw new Error("Input cancelled or empty"); + } + + return response.value as string; +} + +export async function getTextInput(prompt: string): Promise { + const response = await prompts({ + type: "text", + name: "value", + message: prompt, + }); + + if (response.value === undefined) { + throw new Error("Input cancelled"); + } + + return response.value as string; +} diff --git a/src/lib/keyring.ts b/src/lib/keyring.ts new file mode 100644 index 0000000..65e44c4 --- /dev/null +++ b/src/lib/keyring.ts @@ -0,0 +1,41 @@ +import * as keytar from "keytar"; + +const SERVICE_NAME = "enkryptify-cli"; + +export interface Keyring { + set(key: string, value: string): Promise; + get(key: string): Promise; + delete(key: string): Promise; + has(key: string): Promise; +} + +class OSKeyring implements Keyring { + async set(key: string, value: string): Promise { + await keytar.setPassword(SERVICE_NAME, key, value); + } + + async get(key: string): Promise { + try { + const value = await keytar.getPassword(SERVICE_NAME, key); + return value; + } catch (error: unknown) { + console.warn(error instanceof Error ? error.message : String(error)); + return null; + } + } + + async delete(key: string): Promise { + try { + await keytar.deletePassword(SERVICE_NAME, key); + } catch (error: unknown) { + console.warn(error instanceof Error ? error.message : String(error)); + } + } + + async has(key: string): Promise { + const value = await this.get(key); + return value !== null; + } +} + +export const keyring = new OSKeyring(); diff --git a/src/lib/terminal.ts b/src/lib/terminal.ts new file mode 100644 index 0000000..d54360e --- /dev/null +++ b/src/lib/terminal.ts @@ -0,0 +1,27 @@ +export function restoreCursor(): void { + process.stdout.write("\x1b[?25h"); +} + +export function setupTerminalCleanup(): void { + const cleanup = () => { + restoreCursor(); + }; + + process.on("exit", cleanup); + + process.on("SIGINT", () => { + cleanup(); + process.exit(0); + }); + + process.on("SIGTERM", () => { + cleanup(); + process.exit(0); + }); + + process.on("uncaughtException", (error) => { + cleanup(); + console.error(error); + process.exit(1); + }); +} diff --git a/src/providers/aws/auth.ts b/src/providers/aws/auth.ts new file mode 100644 index 0000000..3e55051 --- /dev/null +++ b/src/providers/aws/auth.ts @@ -0,0 +1,32 @@ +import { config } from "@/lib/config"; +import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts"; +import type { AuthProvider, Credentials, LoginOptions } from "../base/AuthProvider"; + +export class AwsAuth implements AuthProvider { + private readonly PROVIDER_NAME = "aws"; + private readonly awsClient = new STSClient({}); + + async login(_options?: LoginOptions): Promise { + try { + const result = await this.awsClient.send(new GetCallerIdentityCommand({})); + + console.log("✅ AWS authentication successful"); + console.log("Account:", result.Account); + console.log("ARN:", result.Arn); + await config.updateProvider(this.PROVIDER_NAME, {}); + } catch (err: unknown) { + console.error("❌ AWS authentication failed", err instanceof Error ? err.message : String(err)); + console.error( + "Make sure you are logged in by running:\n" + + " aws configure (for access keys)\n" + + " aws sso login (for SSO)", + ); + + throw new Error("AWS authentication failed"); + } + } + + getCredentials(): Promise { + throw new Error("Method not implemented."); + } +} diff --git a/src/providers/aws/provider.ts b/src/providers/aws/provider.ts new file mode 100644 index 0000000..aedaac8 --- /dev/null +++ b/src/providers/aws/provider.ts @@ -0,0 +1,279 @@ +import { type ProjectConfig, config } from "@/lib/config"; +import { getSecureInput, getTextInput } from "@/lib/input"; +import { AwsAuth } from "@/providers/aws/auth"; +import { confirm } from "@/ui/Confirm"; +import { + CreateSecretCommand, + DeleteSecretCommand, + GetSecretValueCommand, + ListSecretsCommand, + PutSecretValueCommand, + SecretsManagerClient, +} from "@aws-sdk/client-secrets-manager"; +import type { LoginOptions } from "../base/AuthProvider"; +import type { Provider, Secret, runOptions } from "../base/Provider"; + +export class AwsProvider implements Provider { + readonly name = "aws"; + private auth: AwsAuth; + private readonly secretsClient: SecretsManagerClient; + + constructor() { + this.auth = new AwsAuth(); + this.secretsClient = new SecretsManagerClient({}); + } + + async configure(options: string): Promise { + const setup = await config.getConfigure(options); + if (setup) { + const overwrite = await confirm("Setup already exists. Overwrite?"); + if (!overwrite) { + return setup; + } + } + + while (true) { + const rawPrefix = await getTextInput("Enter the AWS Secrets Manager prefix (e.g. myapp/ or myapp/dev/):"); + const prefix = rawPrefix.trim(); + + if (!prefix) { + console.log("Prefix is required and cannot be empty."); + continue; + } + + let nextToken: string | undefined; + let found = false; + + try { + do { + const res = await this.secretsClient.send(new ListSecretsCommand({ NextToken: nextToken })); + + for (const secret of res.SecretList ?? []) { + if (secret.Name?.startsWith(prefix)) { + found = true; + break; + } + } + + if (found) break; + nextToken = res.NextToken; + } while (nextToken); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + console.log(`Failed to query AWS Secrets Manager: ${message}`); + continue; + } + + if (!found) { + console.log(`❌ No secrets found with prefix "${prefix}". Please try again.`); + continue; + } + + console.log(`✔ AWS configuration is complete for this project.`); + return { + path: options, + provider: this.name, + prefix, + }; + } + } + async run(config: ProjectConfig, options?: runOptions): Promise { + const prefix = config.prefix ?? options?.env; + + if (!prefix) { + throw new Error("AWS provider config is missing `prefix`"); + } + + const allSecrets = []; + let nextToken: string | undefined; + + do { + const res = await this.secretsClient.send(new ListSecretsCommand({ NextToken: nextToken })); + + allSecrets.push(...(res.SecretList ?? [])); + nextToken = res.NextToken; + } while (nextToken); + + const matches = allSecrets.filter((s) => s.Name && s.Name.startsWith(prefix)); + + const secrets = await Promise.all( + matches.map(async (meta) => { + const valueRes = await this.secretsClient.send( + new GetSecretValueCommand({ + SecretId: meta.ARN ?? meta.Name!, + }), + ); + + const fullName = meta.Name!; + + let envName = fullName.slice(prefix.length); + envName = envName.replace(/^\/+/, ""); + + return { + name: envName, + value: valueRes.SecretString ?? "", + }; + }), + ); + + return secrets; + } + + async createSecret(config: ProjectConfig, name: string, value: string): Promise { + const prefix = config.prefix; + if (!prefix) { + throw new Error("AWS provider config is missing `prefix`"); + } + + if (!value || value.trim().length === 0) { + throw new Error("Secret value cannot be empty."); + } + + const fullName = this.buildFullName(prefix, name); + + try { + await this.secretsClient.send( + new CreateSecretCommand({ + Name: fullName, + SecretString: value, + }), + ); + console.log(`✔ Secret "${name}" created successfully in prefix "${prefix}".`); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`❌ Failed to create secret "${name}": ${message}`); + throw new Error(`Failed to create secret "${name}"`, { cause: error }); + } + } + async updateSecret(config: ProjectConfig, name: string): Promise { + const prefix = config.prefix; + if (!prefix) { + throw new Error("AWS provider config is missing `prefix`"); + } + + if (!name || !name.trim()) { + throw new Error("Secret name is required"); + } + + const awsSecretName = this.buildFullName(prefix, name); + + try { + await this.secretsClient.send( + new GetSecretValueCommand({ + SecretId: awsSecretName, + }), + ); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`❌ Secret "${name}" not found or cannot be read: ${message}`, { cause: error }); + } + + const newValue = await getSecureInput(`Enter new value for ${name}: `); + + if (!newValue || !newValue.trim()) { + throw new Error("Secret value cannot be empty"); + } + + try { + await this.secretsClient.send( + new PutSecretValueCommand({ + SecretId: awsSecretName, + SecretString: newValue, + }), + ); + + console.log(`✔ Secret "${name}" updated successfully`); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`❌ Failed to update secret "${name}": ${message}`, { cause: error }); + } + } + async deleteSecret(config: ProjectConfig, name: string): Promise { + const prefix = config.prefix; + + if (!prefix) { + throw new Error("AWS provider config is missing `prefix`"); + } + + if (!name || !name.trim()) { + throw new Error("Secret name is required. Please provide a secret name."); + } + + const fullName = this.buildFullName(prefix, name); + + try { + await this.secretsClient.send( + new GetSecretValueCommand({ + SecretId: fullName, + }), + ); + + await this.secretsClient.send( + new DeleteSecretCommand({ + SecretId: fullName, + ForceDeleteWithoutRecovery: true, + }), + ); + + console.log(`✔ Secret "${name}" deleted successfully from prefix "${prefix}".`); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`❌ Failed to delete secret "${name}": ${message}`); + } + } + async listSecrets(config: ProjectConfig, showValues?: string): Promise { + const prefix = config.prefix; + + if (!prefix) { + throw new Error("AWS provider config is missing `prefix`"); + } + + const shouldShow = showValues === "show"; + + const allSecrets = []; + let nextToken: string | undefined; + + do { + const res = await this.secretsClient.send(new ListSecretsCommand({ NextToken: nextToken })); + + allSecrets.push(...(res.SecretList ?? [])); + nextToken = res.NextToken; + } while (nextToken); + + const matches = allSecrets.filter((s) => s.Name && s.Name.startsWith(prefix)); + + const secrets = await Promise.all( + matches.map(async (meta) => { + const valueRes = await this.secretsClient.send( + new GetSecretValueCommand({ + SecretId: meta.ARN ?? meta.Name!, + }), + ); + + const fullName = meta.Name!; + + let envName = fullName.slice(prefix.length); + envName = envName.replace(/^\/+/, ""); + + return { + name: envName, + value: shouldShow ? (valueRes.SecretString ?? "") : "*********", + }; + }), + ); + + return secrets; + } + + async login(options?: LoginOptions): Promise { + await this.auth.login(options); + } + + private normalizePrefix(prefix: string): string { + return prefix.replace(/\/+$/, ""); + } + + private buildFullName(prefix: string, name: string): string { + return `${this.normalizePrefix(prefix)}/${name}`; + } +} diff --git a/src/providers/base/AuthProvider.ts b/src/providers/base/AuthProvider.ts new file mode 100644 index 0000000..fad517f --- /dev/null +++ b/src/providers/base/AuthProvider.ts @@ -0,0 +1,15 @@ +export interface Credentials { + [key: string]: string; +} + +export type LoginOptions = { + providerName: string; + force?: boolean; + key?: string; +}; + +export interface AuthProvider { + login(options?: LoginOptions): Promise; + + getCredentials(): Promise; +} diff --git a/src/providers/base/Provider.ts b/src/providers/base/Provider.ts new file mode 100644 index 0000000..4337221 --- /dev/null +++ b/src/providers/base/Provider.ts @@ -0,0 +1,38 @@ +import type { ProjectConfig } from "@/lib/config"; +import type { LoginOptions } from "@/providers/base/AuthProvider"; + +export type Secret = { + id?: string; + name: string; + value: string; + isPersonal?: boolean; + environmentId?: string; +}; + +export type runOptions = { + env?: string; + [key: string]: string | undefined; +}; + +export type ProviderConfig = { + provider: string; + [key: string]: string; +}; + +export interface Provider { + readonly name: string; + + login(options?: LoginOptions): Promise; + + configure(options: string): Promise; + + run(config: ProjectConfig, options?: runOptions): Promise; + + createSecret(config: ProjectConfig, name: string, value: string): Promise; + + updateSecret(config: ProjectConfig, name: string, isPersonal?: boolean): Promise; + + deleteSecret(config: ProjectConfig, name: string): Promise; + + listSecrets(config: ProjectConfig, showValues?: string): Promise; +} diff --git a/src/providers/enkryptify/auth.ts b/src/providers/enkryptify/auth.ts new file mode 100644 index 0000000..71f83e8 --- /dev/null +++ b/src/providers/enkryptify/auth.ts @@ -0,0 +1,304 @@ +import { env } from "@/env"; +import { config as configManager } from "@/lib/config"; +import { keyring } from "@/lib/keyring"; +import type { AuthProvider, Credentials, LoginOptions } from "@/providers/base/AuthProvider"; +import http from "@/providers/enkryptify/httpClient"; +import { createHash, randomBytes } from "crypto"; +import open from "open"; +import { URL } from "url"; + +type UserInfo = { + id: string; + email: string; + name: string; +}; + +type AuthResponse = { + accessToken: string; + tokenType: string; + expiresIn: number; +}; + +type StoredAuthData = { + accessToken: string; + userId: string; + email: string; +}; + +function base64Url(buf: Buffer) { + return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+/g, ""); +} + +export class EnkryptifyAuth implements AuthProvider { + private readonly PROVIDER_NAME = "enkryptify"; + private readonly CLIENT_ID = "enkryptify-cli"; + private readonly REDIRECT_URL = "http://localhost:51823/callback"; + private readonly CALLBACK_PORT = 51823; + private readonly DEFAULT_SCOPES = "openid profile email secrets:read secrets:write"; + + async login(options?: LoginOptions): Promise { + let envToken: string | undefined; + + try { + const creds = await this.getCredentials(); + envToken = creds.accessToken; + } catch (error: unknown) { + console.warn(error instanceof Error ? error.message : String(error)); + envToken = undefined; + } + + if (envToken) { + if (options?.force) { + await keyring.delete(this.PROVIDER_NAME); + } else { + const isAuth = await this.getUserInfo(envToken).catch(() => false); + if (isAuth) { + console.log("Already authenticated. Use --force to re-authenticate."); + + await configManager.updateProvider(this.PROVIDER_NAME, {}); + return; + } else { + await keyring.delete(this.PROVIDER_NAME); + } + } + } + + const codeVerifier = base64Url(randomBytes(32)); + const codeChallenge = base64Url(createHash("sha256").update(codeVerifier).digest()); + const state = base64Url(randomBytes(32)); + + const authResponse = await this.runPkceFlow({ + codeVerifier, + codeChallenge, + state, + }); + + const userInfo = await this.getUserInfo(authResponse.accessToken); + if (!userInfo) { + throw new Error("Failed to fetch user info after authentication"); + } + + await this.markAuthenticated(authResponse.accessToken, userInfo); + } + + private async runPkceFlow(params: { + codeVerifier: string; + codeChallenge: string; + state: string; + }): Promise { + const { codeVerifier, codeChallenge, state } = params; + + return new Promise((resolve, reject) => { + const self = this; + let server: ReturnType | null = null; + let timeoutId: ReturnType | null = null; + + function cleanup() { + void server?.stop(); + if (timeoutId) clearTimeout(timeoutId); + server = null; + timeoutId = null; + } + + function fail(error: Error) { + cleanup(); + reject(error); + } + + async function handleCallback(req: Request): Promise { + try { + const url = new URL(req.url); + const error = url.searchParams.get("error"); + const errorDesc = url.searchParams.get("error_description") || error || ""; + + if (error) { + setTimeout(() => { + fail(new Error(`authentication error: ${errorDesc}`)); + }, 1000); + return self.authErrorResponse(errorDesc); + } + + if (url.searchParams.get("state") !== state) { + setTimeout(() => { + fail(new Error("invalid state parameter")); + }, 1000); + return self.authErrorResponse("Invalid state parameter"); + } + + const code = url.searchParams.get("code"); + if (!code) { + setTimeout(() => { + fail(new Error("missing authorization code")); + }, 1000); + return self.authErrorResponse("Missing authorization code"); + } + + const authResp = await self.exchangeCodeForToken(code, codeVerifier); + + setTimeout(() => { + cleanup(); + resolve(authResp); + }, 1000); + + return self.authSuccessResponse(); + } catch (err: unknown) { + setTimeout(() => { + fail(err instanceof Error ? err : new Error(String(err))); + }, 1000); + return new Response("Internal error", { status: 500 }); + } + } + + server = Bun.serve({ + port: self.CALLBACK_PORT, + routes: { "/callback": handleCallback }, + fetch: () => new Response("Not Found", { status: 404 }), + }); + + const authUrl = this.buildAuthUrl(codeChallenge, state); + this.logAuthInstructions(authUrl); + + open(authUrl).catch((err) => { + console.warn("\n Failed to open browser. Please open this URL manually:"); + console.warn(authUrl); + console.warn(String(err)); + }); + + timeoutId = setTimeout( + () => { + fail(new Error("authentication timeout")); + }, + 5 * 60 * 1000, + ); + }); + } + + private buildAuthUrl(codeChallenge: string, state: string): string { + const authUrl = new URL("/oauth/authorize", env.APP_BASE_URL); + authUrl.searchParams.set("client_id", this.CLIENT_ID); + authUrl.searchParams.set("response_type", "code"); + authUrl.searchParams.set("redirect_uri", this.REDIRECT_URL); + authUrl.searchParams.set("scope", this.DEFAULT_SCOPES); + authUrl.searchParams.set("state", state); + authUrl.searchParams.set("code_challenge", codeChallenge); + authUrl.searchParams.set("code_challenge_method", "S256"); + + return authUrl.toString(); + } + + private logAuthInstructions(authUrl: string): void { + console.log("\n🌐 Opening browser for authentication..."); + console.log(`\n 📋 AUTHENTICATION URL:`); + console.log(` ${authUrl}\n`); + } + + private authErrorResponse(message: string): Response { + const escapedMessage = message + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + return new Response( + ` + Authentication Error + +

Authentication Error

+

${escapedMessage}

+

You can close this window and try again.

+ + `, + { status: 400, headers: { "Content-Type": "text/html" } }, + ); + } + + private authSuccessResponse(): Response { + return new Response( + ` + Authentication Successful + +

Authentication Successful!

+

You have successfully authenticated with Enkryptify.

+

You can now close this window and return to your terminal.

+ + `, + { status: 200, headers: { "Content-Type": "text/html" } }, + ); + } + private async exchangeCodeForToken(code: string, codeVerifier: string): Promise { + const payload = { + grant_type: "authorization_code", + client_id: this.CLIENT_ID, + code, + redirect_uri: this.REDIRECT_URL, + code_verifier: codeVerifier, + }; + + const res = await http.post("/v1/auth/token", payload, { + validateStatus: () => true, + }); + + if (res.status < 200 || res.status >= 300) { + const text = typeof res.data === "string" ? res.data : JSON.stringify(res.data); + throw new Error(`token exchange failed with status ${res.status}: ${text}`); + } + + const data = res.data as AuthResponse; + if (!data.accessToken) { + throw new Error("token exchange response missing accessToken"); + } + + return data; + } + + private async markAuthenticated(accessToken: string, user: UserInfo): Promise { + await keyring.set( + this.PROVIDER_NAME, + JSON.stringify({ + accessToken, + userId: user.id, + email: user.email, + }), + ); + + await configManager.updateProvider(this.PROVIDER_NAME, {}); + } + + async getUserInfo(token: string): Promise { + const res = await http.get("/v1/me", { + headers: { + "X-API-Key": token, + }, + validateStatus: () => true, + }); + + if (res.status === 401 || res.status === 403) { + return null; + } + + if (res.status < 200 || res.status >= 300) { + const text = typeof res.data === "string" ? res.data : JSON.stringify(res.data); + throw new Error(`failed to get user info, status: ${res.status}, body: ${text}`); + } + + return res.data as UserInfo; + } + + async getCredentials(): Promise { + const authDataString = await keyring.get(this.PROVIDER_NAME); + if (!authDataString) { + throw new Error('Not authenticated. Please run "ek login enkryptify" first.'); + } + + try { + const authData = JSON.parse(authDataString) as StoredAuthData; + if (!authData || !authData.accessToken) { + throw new Error('Not authenticated. Please run "ek login enkryptify" first.'); + } + return { accessToken: authData.accessToken }; + } catch (error: unknown) { + console.warn(error instanceof Error ? error.message : String(error)); + throw new Error('Not authenticated. Please run "ek login enkryptify" first.'); + } + } +} diff --git a/src/providers/enkryptify/httpClient.ts b/src/providers/enkryptify/httpClient.ts new file mode 100644 index 0000000..3555b48 --- /dev/null +++ b/src/providers/enkryptify/httpClient.ts @@ -0,0 +1,67 @@ +import { env } from "@/env"; +import { keyring } from "@/lib/keyring"; +import axios, { type AxiosError, type AxiosInstance } from "axios"; + +type StoredAuthData = { + accessToken: string; + userId: string; + email: string; +}; + +const http: AxiosInstance = axios.create({ + baseURL: env.API_BASE_URL, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, +}); + +http.interceptors.request.use( + async (config) => { + try { + const authDataString = await keyring.get("enkryptify"); + if (!authDataString) { + return config; + } + + const authData = JSON.parse(authDataString) as StoredAuthData; + const token = authData?.accessToken; + + if ( + token && + typeof token === "string" && + config.headers && + typeof config.headers === "object" && + !Array.isArray(config.headers) + ) { + config.headers["X-API-Key"] = token; + } + } catch (error) { + console.warn("Failed to retrieve auth token:", error instanceof Error ? error.message : String(error)); + } + + return config; + }, + (error) => Promise.reject(error), +); + +const ERROR_MESSAGES: Record = { + 401: "Authentication failed. Please check your credentials.", + 404: "Resource not found.", + 403: "You don't have permission to perform this action.", + 500: "Something went wrong on the server. Please try again later.", +}; + +http.interceptors.response.use( + (response) => response, + async (error: AxiosError) => { + const status = error.response?.status; + + if (status && ERROR_MESSAGES[status]) { + return Promise.reject(new Error(ERROR_MESSAGES[status])); + } + + return Promise.reject(error); + }, +); +export default http; diff --git a/src/providers/enkryptify/provider.ts b/src/providers/enkryptify/provider.ts new file mode 100644 index 0000000..13fb73d --- /dev/null +++ b/src/providers/enkryptify/provider.ts @@ -0,0 +1,383 @@ +import { type ProjectConfig, config } from "@/lib/config"; +import { getSecureInput, getTextInput } from "@/lib/input"; +import type { LoginOptions } from "@/providers/base/AuthProvider"; +import type { Provider, Secret, runOptions } from "@/providers/base/Provider"; +import { EnkryptifyAuth } from "@/providers/enkryptify/auth"; +import http from "@/providers/enkryptify/httpClient"; +import { confirm } from "@/ui/Confirm"; +import { selectName } from "@/ui/SelectItem"; +import { showMessage } from "@/ui/SuccessMessage"; +import { AxiosError } from "axios"; + +type Workspace = { + id: string; + name: string; + slug: string; +}; + +type Project = { + id: string; + name: string; + slug: string; +}; + +type ProjectTeam = { + projects: Project[]; +}; + +type Environment = { + id: string; + name: string; +}; + +type Resource = Workspace | Project | ProjectTeam | Environment | ApiSecret; + +type ApiSecretValue = { + environmentId: string; + value: string; + isPersonal: boolean; +}; + +type ApiSecret = { + id: string; + name: string; + values: ApiSecretValue[]; +}; + +export class EnkryptifyProvider implements Provider { + private auth: EnkryptifyAuth; + readonly name = "enkryptify"; + + constructor() { + this.auth = new EnkryptifyAuth(); + } + + async listSecrets(config: ProjectConfig, showValues?: string): Promise { + const { workspace_slug, project_slug, environment_id } = this.checkProjectConfig(config); + + const response = await http.get(`/v1/workspace/${workspace_slug}/project/${project_slug}/secret`, { + params: { environment_id: environment_id }, + }); + + const shouldShow = showValues === "show"; + const currentEnvId = config.environment_id; + + const secretsValues: Secret[] = response.data.map((secret) => { + const personalValue = secret.values.find((v) => v.environmentId === currentEnvId && v.isPersonal === true); + const nonPersonalValue = secret.values.find( + (v) => v.environmentId === currentEnvId && v.isPersonal === false, + ); + + const matching = personalValue || nonPersonalValue; + + return { + id: secret.id, + name: secret.name, + value: shouldShow && matching ? (matching.value ?? "") : "*********", + isPersonal: matching?.isPersonal ?? false, + environmentId: shouldShow && matching ? (matching.environmentId ?? "") : "*********", + }; + }); + + return secretsValues; + } + + async login(options?: LoginOptions): Promise { + await this.auth.login(options); + } + + async configure(options: string): Promise { + const setup = await config.getConfigure(options); + if (setup) { + const overwrite = await confirm("Setup already exists. Overwrite?"); + if (!overwrite) { + return setup; + } + } + + const provider = this.name; + + const workspaces = await this.fetchResource("/v1/workspace"); + + if (workspaces.length === 0) { + throw new Error("No workspaces found. Please create a workspace first before setting up."); + } + + const workspaceSlug = await selectName( + workspaces.map((ws) => `${ws.slug}`), + "Select workspace", + ); + + if (!workspaceSlug) throw new Error("Failed to select workspace"); + + const selectedWorkspace = workspaces.find((ws) => ws.slug === workspaceSlug); + if (!selectedWorkspace) { + throw new Error("Failed to find selected workspace"); + } + + const projectsResponse = await this.fetchResource(`/v1/workspace/${workspaceSlug}/project`); + + const allProjects: Project[] = []; + for (const team of projectsResponse) { + if (team.projects && Array.isArray(team.projects)) { + allProjects.push(...team.projects); + } + } + + if (allProjects.length === 0) { + throw new Error( + `No projects found in workspace "${selectedWorkspace.name}". Please create a project first before setting up.`, + ); + } + + const projectSlug = await selectName( + allProjects.map((p) => p.slug), + "Select project", + ); + + if (!projectSlug) throw new Error("Failed to select project"); + + const selectedProject = allProjects.find((p) => p.slug === projectSlug); + if (!selectedProject) { + throw new Error("Failed to find selected project"); + } + + const environments = await this.fetchResource( + `/v1/workspace/${workspaceSlug}/project/${projectSlug}/environment`, + ); + + if (environments.length === 0) { + throw new Error( + `No environments found in project "${selectedProject.name}". Please create an environment first before setting up.`, + ); + } + + const environmentName = await selectName( + environments.map((e) => e.name), + "Select environment", + ); + + if (!environmentName) throw new Error("Failed to select environment"); + + const environmentId = environments.find((e) => e.name === environmentName)?.id; + if (!environmentId) throw new Error("Failed to find environment ID"); + + const projectConfig: ProjectConfig = { + path: options, + provider: provider, + workspace_slug: workspaceSlug, + project_slug: projectSlug, + environment_id: environmentId, + }; + + showMessage( + `Setup completed successfully! Workspace: ${selectedWorkspace.name}, Project: ${selectedProject.name}, Environment: ${environmentName}`, + ); + + return projectConfig; + } + + async run(config: ProjectConfig, options?: runOptions): Promise { + const { workspace_slug, project_slug, environment_id } = this.checkProjectConfig(config); + + const environments = await this.fetchResource( + `/v1/workspace/${workspace_slug}/project/${project_slug}/environment`, + ); + if (environments.length === 0) { + throw new Error(`No environments found in project "${project_slug}". Please create an environment`); + } + + const targetEnvKey = options?.env ?? environment_id; + + const targetEnvironment = environments.find((e) => e.name === targetEnvKey || e.id === targetEnvKey); + + if (!targetEnvironment) { + const availableNames = environments.map((e) => e.name).join(", "); + throw new Error(`Environment "${targetEnvKey}" not found. Available environments: ${availableNames}`); + } + + const targetEnvironmentId = targetEnvironment.id; + + const response = await http.get(`/v1/workspace/${workspace_slug}/project/${project_slug}/secret`, { + params: { environment_id: targetEnvironmentId }, + }); + + const apiSecrets = response.data; + + const secretsValues: Secret[] = apiSecrets.map((secret) => { + const personalValue = secret.values.find( + (v) => v.environmentId === targetEnvironmentId && v.isPersonal === true, + ); + const nonPersonalValue = secret.values.find( + (v) => v.environmentId === targetEnvironmentId && v.isPersonal === false, + ); + + const matching = personalValue || nonPersonalValue; + + return { + id: secret.id, + name: secret.name, + value: matching?.value ?? "", + isPersonal: matching?.isPersonal ?? false, + environmentId: matching?.environmentId ?? "", + }; + }); + + return secretsValues; + } + + async createSecret(config: ProjectConfig, name: string, value: string): Promise { + const { workspace_slug, project_slug, environment_id } = this.checkProjectConfig(config); + + await http.post(`/v1/workspace/${workspace_slug}/project/${project_slug}/secret`, { + environments: [environment_id], + secrets: [ + { + key: name, + value: value, + type: "runtime", + dataType: "text", + }, + ], + }); + } + + async updateSecret(config: ProjectConfig, name: string, isPersonalFlag?: boolean): Promise { + const { workspace_slug, project_slug, environment_id } = this.checkProjectConfig(config); + + // Fetch all secrets without environment filter to get full secret data + const response = await http.get(`/v1/workspace/${workspace_slug}/project/${project_slug}/secret`); + + if (response.data.length === 0) { + throw new Error("No secrets found. Please create a secret first."); + } + + if (!name || !name.trim()) { + throw new Error("Secret name is required. Please provide a secret name"); + } + + const existingSecret = response.data.find((s) => s.name === name); + if (!existingSecret) { + throw new Error(`Secret "${name}" not found.`); + } + + const existingPersonalValue = existingSecret.values.find( + (v) => v.environmentId === environment_id && v.isPersonal === true, + ); + const existingNonPersonalValue = existingSecret.values.find( + (v) => v.environmentId === environment_id && v.isPersonal === false, + ); + + const isPersonal = isPersonalFlag !== undefined ? isPersonalFlag : false; + + const newNameInput = await getTextInput(`Enter new name (press Enter to keep "${name}"): `); + const newName = newNameInput.trim() || name; + + const namePattern = /^[A-Za-z0-9_-]+$/; + if (!namePattern.test(newName)) { + throw new Error( + `Invalid secret name "${newName}". Name can only contain A-Z, a-z, 0-9, underscore (_), and hyphen (-).`, + ); + } + + if (response.data.some((s) => s.name === newName && s.id !== existingSecret.id)) { + throw new Error(`Secret with name "${newName}" already exists.`); + } + + const newValue = await getSecureInput("Enter new value: "); + if (!newValue || !newValue.trim()) { + throw new Error("Secret value cannot be empty."); + } + + const otherEnvironmentValues = existingSecret.values.filter((v) => v.environmentId !== environment_id); + + const currentEnvironmentValues: ApiSecretValue[] = []; + + if (isPersonal) { + if (existingNonPersonalValue) { + currentEnvironmentValues.push(existingNonPersonalValue); + } + currentEnvironmentValues.push({ + environmentId: environment_id, + value: newValue, + isPersonal: true, + }); + } else { + if (existingPersonalValue) { + currentEnvironmentValues.push(existingPersonalValue); + } + currentEnvironmentValues.push({ + environmentId: environment_id, + value: newValue, + isPersonal: false, + }); + } + + const updatedValues = [...otherEnvironmentValues, ...currentEnvironmentValues]; + + await http.put(`/v1/workspace/${workspace_slug}/project/${project_slug}/secret/${existingSecret.id}`, { + name: newName, + type: "runtime", + dataType: "text", + values: updatedValues, + }); + + showMessage(`Secret updated successfully!`); + } + + async deleteSecret(config: ProjectConfig, name: string): Promise { + const { workspace_slug, project_slug } = this.checkProjectConfig(config); + + if (!name || !name.trim()) { + throw new Error("Secret name is required. Please provide a secret name"); + } + + const response = await this.fetchResource( + `/v1/workspace/${workspace_slug}/project/${project_slug}/secret`, + ); + if (response.length === 0) { + throw new Error("No secrets found"); + } + + const secret = response.find((s) => s.name === name); + if (!secret) { + throw new Error(`Secret "${name}" not found.`); + } + + try { + await http.delete(`/v1/workspace/${workspace_slug}/project/${project_slug}/secret/${secret.id}`); + showMessage(`Secret "${name}" deleted successfully!`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to delete secret: ${errorMessage}`); + } + } + + private async fetchResource(url: string): Promise { + try { + const response = await http.get(url); + if (!response.data) { + const resourceName = url.split("/").filter(Boolean).pop() || "resource"; + throw new Error(`No ${resourceName} found. Please create a ${resourceName} first.`); + } + + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + const status = error.response?.status; + if (status) throw new Error(`Failed to fetch resources from ${url}. Status: ${status ?? "unknown"}.`); + } + throw error; + } + } + + checkProjectConfig(config: ProjectConfig) { + const { workspace_slug, project_slug, environment_id } = config; + if (!workspace_slug || !project_slug || !environment_id) { + throw new Error( + "Invalid config: missing workspace_slug, project_slug, or environment_id pls run ek setup first", + ); + } + return { workspace_slug, project_slug, environment_id }; + } +} diff --git a/src/providers/gcp/auth.ts b/src/providers/gcp/auth.ts new file mode 100644 index 0000000..c5e3663 --- /dev/null +++ b/src/providers/gcp/auth.ts @@ -0,0 +1,44 @@ +import { config } from "@/lib/config"; +import { GoogleAuth } from "google-auth-library"; +import type { AuthProvider, Credentials, LoginOptions } from "../base/AuthProvider"; + +export class GcpAuth implements AuthProvider { + private readonly PROVIDER_NAME = "gcp"; + private readonly gcpClient: GoogleAuth; + + constructor() { + this.gcpClient = new GoogleAuth({ + scopes: process.env.GCP_SCOPES?.split(",") ?? ["https://www.googleapis.com/auth/cloud-platform"], + }); + } + + async login(options?: LoginOptions): Promise { + console.log("Logging in to Google Cloud...", options); + + try { + const client = await this.gcpClient.getClient(); + const projectId = await this.gcpClient.getProjectId(); + + if (typeof client.getAccessToken === "function") { + await client.getAccessToken(); + } + + console.log("✅ Google Cloud authenticated"); + console.log("Project:", projectId); + + await config.updateProvider(this.PROVIDER_NAME, {}); + } catch (err: unknown) { + console.error("❌ Google Cloud authentication failed"); + console.error( + "Make sure you are logged in by running:\n" + + "1. gcloud init \n 2. gcloud auth application-default login \n", + ); + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Google Cloud authentication failed: ${message}`, { cause: err }); + } + } + + getCredentials(): Promise { + throw new Error("Method not implemented."); + } +} diff --git a/src/providers/gcp/provider.ts b/src/providers/gcp/provider.ts new file mode 100644 index 0000000..77db28d --- /dev/null +++ b/src/providers/gcp/provider.ts @@ -0,0 +1,259 @@ +import { type ProjectConfig, config } from "@/lib/config"; +import { getSecureInput } from "@/lib/input"; +import { confirm } from "@/ui/Confirm"; +import { selectName } from "@/ui/SelectItem"; +import { showMessage } from "@/ui/SuccessMessage"; +import { ProjectsClient } from "@google-cloud/resource-manager"; +import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; +import type { LoginOptions } from "../base/AuthProvider"; +import type { Provider, Secret, runOptions } from "../base/Provider"; +import { GcpAuth } from "./auth"; + +export class GcpProvider implements Provider { + readonly name = "gcp"; + private auth: GcpAuth; + private projectsClient: ProjectsClient; + private secretsClient: SecretManagerServiceClient; + constructor() { + this.auth = new GcpAuth(); + this.projectsClient = new ProjectsClient(); + this.secretsClient = new SecretManagerServiceClient(); + } + + login(options?: LoginOptions): Promise { + return this.auth.login(options); + } + async configure(options: string): Promise { + const setup = await config.getConfigure(options); + if (setup) { + const overwrite = await confirm("Setup already exists. Overwrite?"); + if (!overwrite) { + return setup; + } + } + + try { + const [projects] = await this.projectsClient.searchProjects({}); + if (projects.length === 0) { + throw new Error("No projects found. Please create a project first before setting up."); + } + + const labels = projects.map((p) => { + const name = p.displayName ?? p.name ?? "Unnamed project"; + const id = p.projectId ?? "unknown-id"; + return `${name}/${id}`; + }); + + const selected = await selectName(labels, "Select project"); + if (!selected) throw new Error("Failed to select project"); + + const selectedId = selected.split("/").pop(); + if (!selectedId) throw new Error("Failed to parse selected project id"); + + const project = projects.find((p) => p.projectId === selectedId); + if (!project || !project.name || !project.projectId) { + throw new Error("Failed to find selected project"); + } + + const projectConfig: ProjectConfig = { + path: options, + provider: this.name, + projectName: project.name, + projectId: project.projectId, + }; + + showMessage(`Setup completed successfully! Project: ${project.name}/(${project.projectId})`); + return projectConfig; + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to configure GCP project message: ${message}`); + } + } + async run(config: ProjectConfig, options?: runOptions): Promise { + const { projectId } = config; + + if (!projectId) { + throw new Error("Project id is not set. Configure the GCP project first."); + } + console.log(options?.env); + return await this.getAllSecretsWithAllVersions(projectId, options?.env); + } + async createSecret(config: ProjectConfig, name: string, value: string): Promise { + const { projectId } = config; + + if (!projectId) { + throw new Error("Project id is not set. Configure the GCP project first before creating secrets."); + } + + if (!name || !name.trim()) { + throw new Error("Secret name is required and cannot be empty."); + } + + if (!value || !value.trim()) { + throw new Error("Secret value is required and cannot be empty."); + } + + const parent = `projects/${projectId}`; + + try { + try { + await this.secretsClient.createSecret({ + parent, + secretId: name, + secret: { + replication: { + automatic: {}, + }, + }, + }); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + if (!message.includes("ALREADY_EXISTS")) { + throw err; + } + } + + await this.secretsClient.addSecretVersion({ + parent: `${parent}/secrets/${name}`, + payload: { + data: Buffer.from(value, "utf8"), + }, + }); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to create or update secret "${name}" in GCP Secret Manager: ${message}`); + } + } + + async updateSecret(config: ProjectConfig, name: string, _isPersonal?: boolean): Promise { + const { projectId } = config; + + if (!projectId) { + throw new Error("Project id is not set. Configure the GCP project first before updating secrets."); + } + + if (!name || !name.trim()) { + throw new Error("Secret name is required and cannot be empty."); + } + + const secretName = `projects/${projectId}/secrets/${name}`; + + try { + await this.secretsClient.getSecret({ name: secretName }); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Secret "${name}" not found or cannot be read: ${message}`); + } + + const newValue = await getSecureInput(`Enter new value for "${name}": `); + + if (!newValue || !newValue.trim()) { + throw new Error("Secret value cannot be empty."); + } + + try { + await this.secretsClient.addSecretVersion({ + parent: secretName, + payload: { + data: Buffer.from(newValue, "utf8"), + }, + }); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to update secret "${name}" in GCP Secret Manager: ${message}`); + } + } + + async deleteSecret(config: ProjectConfig, name: string): Promise { + const { projectId } = config; + + if (!projectId) { + throw new Error("Project id is not set. Configure the GCP project first before deleting secrets."); + } + + if (!name || !name.trim()) { + throw new Error("Secret name is required and cannot be empty."); + } + + const secretName = `projects/${projectId}/secrets/${name}`; + + try { + await this.secretsClient.deleteSecret({ + name: secretName, + }); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to delete secret "${name}" from GCP Secret Manager: ${message}`); + } + } + async listSecrets(config: ProjectConfig, showValues?: string): Promise { + const { projectId } = config; + + if (!projectId) { + throw new Error("Project id is not set. Configure the GCP project first."); + } + + const shouldShow = showValues === "show"; + + const allSecrets = await this.getAllSecretsWithAllVersions(projectId); + + return allSecrets.map((s) => ({ + ...s, + value: shouldShow ? s.value : "****************", + })); + } + + async getAllSecretsWithAllVersions(projectId: string, env?: string): Promise { + let parent: string; + if (env) { + parent = `projects/${env}`; + } else { + parent = `projects/${projectId}`; + } + + try { + const [secrets] = await this.secretsClient.listSecrets({ parent }); + + if (secrets.length === 0) { + throw new Error("No secrets found in project. Please create a secret first."); + } + + const all: Secret[] = []; + + for (const meta of secrets) { + if (!meta.name) continue; + + const secretResourceName = meta.name; + const secretId = secretResourceName.split("/").pop() ?? secretResourceName; + + const [versions] = await this.secretsClient.listSecretVersions({ + parent: secretResourceName, + }); + + for (const v of versions) { + if (!v.name) continue; + + const [version] = await this.secretsClient.accessSecretVersion({ name: v.name }); + + const value = + version.payload?.data instanceof Uint8Array + ? Buffer.from(version.payload.data).toString("utf8") + : ""; + + const versionId = v.name.split("/").pop() ?? v.name; + + all.push({ + id: `${secretId}:${versionId}`, + name: secretId, + value, + }); + } + } + + return all; + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to fetch all secrets and versions for project "${projectId}": ${message}`); + } + } +} diff --git a/src/providers/onePassword/auth.ts b/src/providers/onePassword/auth.ts new file mode 100644 index 0000000..23dc426 --- /dev/null +++ b/src/providers/onePassword/auth.ts @@ -0,0 +1,53 @@ +import { config } from "@/lib/config"; +import { keyring } from "@/lib/keyring"; +import { createClient } from "@1password/sdk"; +import type { AuthProvider, Credentials, LoginOptions } from "../base/AuthProvider"; + +export class OnePasswordAuth implements AuthProvider { + private readonly PROVIDER_NAME = "onePassword"; + + constructor() {} + + async login(options?: LoginOptions): Promise { + try { + const token = options?.key; + + if (!token || !token.trim()) { + throw new Error("No 1Password service account token provided."); + } + + const client = await createClient({ + auth: token, + integrationName: "My 1Password Integration", + integrationVersion: "v1.0.0", + }); + + const vaults = await client.vaults.list(); + console.log("✅ 1Password SDK authentication successful"); + console.log(`Accessible vaults: ${vaults.length}`); + + await keyring.set(this.PROVIDER_NAME, token); + await config.updateProvider(this.PROVIDER_NAME, {}); + } catch (error: unknown) { + console.error("❌ 1Password authentication failed", error instanceof Error ? error.message : String(error)); + throw new Error("❌ Invalid 1Password service account token", { cause: error }); + } + } + + async getCredentials(): Promise { + try { + const token = await keyring.get(this.PROVIDER_NAME); + + if (!token || !token.trim()) { + throw new Error(`Not authenticated. Please run "ek login --provider ${this.PROVIDER_NAME}" first.`); + } + + return { + token, + }; + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to retrieve 1Password credentials: ${message}`, { cause: error }); + } + } +} diff --git a/src/providers/onePassword/provider.ts b/src/providers/onePassword/provider.ts new file mode 100644 index 0000000..1e52302 --- /dev/null +++ b/src/providers/onePassword/provider.ts @@ -0,0 +1,321 @@ +import { type ProjectConfig, config } from "@/lib/config"; +import { getSecureInput, getTextInput } from "@/lib/input"; +import { confirm } from "@/ui/Confirm"; +import { selectName } from "@/ui/SelectItem"; +import { showMessage } from "@/ui/SuccessMessage"; +import { type Client, ItemCategory, createClient } from "@1password/sdk"; +import dotenv from "dotenv"; +import type { LoginOptions } from "../base/AuthProvider"; +import type { Provider, Secret, runOptions } from "../base/Provider"; +import { OnePasswordAuth } from "./auth"; + +type ParsedNote = { key: string; value: string }; + +function parseEnvLine(line: string): ParsedNote | null { + try { + const parsed = dotenv.parse(line); + const entries = Object.entries(parsed); + + if (entries.length !== 1) return null; + + const entry = entries[0]; + if (!entry) return null; + + const [key, value] = entry; + return { key, value }; + } catch { + return null; + } +} + +function ensureVaultAndNote(config: ProjectConfig): { vaultId: string; noteId: string } { + if (!config.vaultId) { + throw new Error("Vault ID is not set. Run setup first."); + } + if (!config.noteId) { + throw new Error("Note ID is not set. Run setup first."); + } + return { vaultId: config.vaultId, noteId: config.noteId }; +} + +function parseNotes(notes: string): ParsedNote[] { + return notes + .split("\n") + .map((l) => l.trim()) + .filter((l) => l && !l.startsWith("#")) + .map(parseEnvLine) + .filter((v): v is ParsedNote => Boolean(v)); +} + +async function getSecureNotes(client: Client, vaultId: string) { + try { + const items = await client.items.list(vaultId); + return items.filter((i) => i.category === ItemCategory.SecureNote); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to fetch Secure Notes from vault: ${message}`); + } +} + +export class OnePasswordProvider implements Provider { + readonly name = "onePassword"; + + private readonly auth = new OnePasswordAuth(); + private client?: Client; + + async login(options?: LoginOptions): Promise { + await this.auth.login(options); + this.client = undefined; + } + + private async getClient(): Promise { + if (this.client) return this.client; + + try { + const { token } = await this.auth.getCredentials(); + if (!token) { + throw new Error(`Not logged in. Run "ek login --provider onePassword".`); + } + + this.client = await createClient({ + auth: token, + integrationName: "Enkryptify", + integrationVersion: "1.0.0", + }); + + return this.client; + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to initialize 1Password client: ${message}`); + } + } + + async configure(path: string): Promise { + try { + const existing = await config.getConfigure(path); + + if (existing && !(await confirm("Setup already exists. Overwrite?"))) { + return existing; + } + + const client = await this.getClient(); + const vaults = await client.vaults.list(); + + if (!vaults.length) throw new Error("No vaults found."); + + const labels = vaults.map((v) => `${v.title} (${v.id})`); + const selected = await selectName(labels, "Select vault"); + const vault = vaults.find((v) => `${v.title} (${v.id})` === selected); + + if (!vault) throw new Error("Vault selection failed."); + + const notes = await getSecureNotes(client, vault.id); + + if (!notes.length) throw new Error(`No Secure Notes found in vault "${vault.title}".`); + + const noteLabels = notes.map((n) => n.title || "Untitled"); + const selectedNote = await selectName(noteLabels, "Select Secure Note"); + + const note = notes.find((n) => n.title === selectedNote); + if (!note) throw new Error("Secure Note selection failed."); + + const projectConfig: ProjectConfig = { + path, + provider: this.name, + vaultId: vault.id, + vaultTitle: vault.title, + noteName: note.title, + noteId: note.id, + }; + + showMessage(`Vault selected: ${vault.title}, Note selected: ${note.title}`); + return projectConfig; + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to configure 1Password: ${message}`); + } + } + + async run(config: ProjectConfig, options?: runOptions): Promise { + try { + const { vaultId, noteId: defaultNoteId } = ensureVaultAndNote(config); + const client = await this.getClient(); + + let targetNoteId = defaultNoteId; + + if (options?.env) { + const notes = await getSecureNotes(client, vaultId); + const targetNote = notes.find((n) => n.title === options.env); + if (!targetNote) { + const availableNotes = notes.map((n) => n.title).join(", "); + throw new Error( + `Secure Note "${options.env}" not found. Available notes: ${availableNotes || "none"}`, + ); + } + targetNoteId = targetNote.id; + } + + const note = await client.items.get(vaultId, targetNoteId); + if (!note.notes) { + return []; + } + + const secrets: Secret[] = []; + for (const entry of parseNotes(note.notes)) { + secrets.push({ + name: entry.key, + value: entry.value, + }); + } + + return secrets; + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to fetch secrets from 1Password: ${message}`); + } + } + + async createSecret(config: ProjectConfig, keyName: string, value: string): Promise { + try { + if (!value || value.trim().length === 0) { + throw new Error("Secret value cannot be empty."); + } + + if (!keyName || !keyName.trim()) { + throw new Error("Secret key name is required. Example: ek create "); + } + + const { vaultId, noteId } = ensureVaultAndNote(config); + const client = await this.getClient(); + + const note = await client.items.get(vaultId, noteId); + const currentNotes = note.notes?.trim() ?? ""; + + if (currentNotes) { + const entries = parseNotes(currentNotes); + const existingKey = entries.find((e) => e.key === keyName.trim()); + if (existingKey) { + throw new Error(`Key "${keyName}" already exists in note "${note.title}". Use update instead.`); + } + } + + const updatedNotes = currentNotes + ? `${currentNotes}\n${keyName.trim()}=${value}` + : `${keyName.trim()}=${value}`; + + await client.items.put({ + ...note, + notes: updatedNotes, + }); + + showMessage(`Secret added! Note: "${note.title}", Key: "${keyName}"`); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to create secret in 1Password: ${message}`); + } + } + + async updateSecret(config: ProjectConfig, keyName: string): Promise { + try { + if (!keyName || !keyName.trim()) { + throw new Error("Key name is required. Please provide a key name."); + } + + const { vaultId, noteId } = ensureVaultAndNote(config); + const client = await this.getClient(); + + const note = await client.items.get(vaultId, noteId); + if (!note.notes) { + throw new Error("Note has no content."); + } + + const entries = parseNotes(note.notes); + if (!entries.length) throw new Error("No secrets found."); + + const selectedKey = keyName.trim(); + const entry = entries.find((e) => e.key === selectedKey); + if (!entry) throw new Error(`Key "${selectedKey}" not found.`); + + const renamedKey = + (await getTextInput(`Rename key? (current: "${selectedKey}", Enter to keep): `))?.trim() || selectedKey; + + const newValue = (await getSecureInput(`Enter new value for "${renamedKey}": `))?.trim(); + if (!newValue) throw new Error("Value cannot be empty."); + + const updatedNotes = entries + .map((e) => (e.key === selectedKey ? `${renamedKey}=${newValue}` : `${e.key}=${e.value}`)) + .join("\n"); + + await client.items.put({ + ...note, + notes: updatedNotes, + }); + + showMessage(`Updated "${renamedKey}" in "${note.title}".`); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to update secret in 1Password: ${message}`); + } + } + + async deleteSecret(config: ProjectConfig, keyName: string): Promise { + try { + if (!keyName || !keyName.trim()) { + throw new Error("Key name is required. Please provide a key name."); + } + + const { vaultId, noteId } = ensureVaultAndNote(config); + const client = await this.getClient(); + + const note = await client.items.get(vaultId, noteId); + if (!note.notes) { + throw new Error("Note has no content."); + } + + const entries = parseNotes(note.notes); + const filtered = entries.filter((e) => e.key !== keyName.trim()); + + if (filtered.length === entries.length) { + throw new Error(`Key "${keyName}" not found in note "${note.title}".`); + } + + const updatedNotes = filtered.map((e) => `${e.key}=${e.value}`).join("\n"); + + await client.items.put({ + ...note, + notes: updatedNotes, + }); + + showMessage(`Deleted key "${keyName}" from note "${note.title}".`); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to delete secret "${keyName}": ${message}`); + } + } + + async listSecrets(config: ProjectConfig, showValues?: string): Promise { + try { + const { vaultId, noteId } = ensureVaultAndNote(config); + const client = await this.getClient(); + const reveal = showValues === "show"; + + const full = await client.items.get(vaultId, noteId); + if (!full.notes) { + return []; + } + + const secrets: Secret[] = []; + for (const { key, value } of parseNotes(full.notes)) { + secrets.push({ + name: key, + value: reveal ? value : "********", + }); + } + + return secrets; + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to list secrets in 1Password: ${message}`); + } + } +} diff --git a/src/providers/registry/ProviderRegistry.ts b/src/providers/registry/ProviderRegistry.ts new file mode 100644 index 0000000..4c2592f --- /dev/null +++ b/src/providers/registry/ProviderRegistry.ts @@ -0,0 +1,23 @@ +import type { Provider } from "@/providers/base/Provider"; + +export class ProviderRegistry { + private providers: Map = new Map(); + + register(provider: Provider): void { + this.providers.set(provider.name, provider); + } + + get(name: string): Provider | undefined { + return this.providers.get(name); + } + + list(): Provider[] { + return Array.from(this.providers.values()); + } + + has(name: string): boolean { + return this.providers.has(name); + } +} + +export const providerRegistry = new ProviderRegistry(); diff --git a/src/providers/registry/index.ts b/src/providers/registry/index.ts new file mode 100644 index 0000000..4d5a512 --- /dev/null +++ b/src/providers/registry/index.ts @@ -0,0 +1,11 @@ +import { AwsProvider } from "@/providers/aws/provider"; +import { EnkryptifyProvider } from "@/providers/enkryptify/provider"; +import { GcpProvider } from "@/providers/gcp/provider"; +import { OnePasswordProvider } from "@/providers/onePassword/provider"; +import { providerRegistry } from "@/providers/registry/ProviderRegistry"; + +providerRegistry.register(new EnkryptifyProvider()); +providerRegistry.register(new AwsProvider()); +providerRegistry.register(new GcpProvider()); +providerRegistry.register(new OnePasswordProvider()); +export { AwsProvider, EnkryptifyProvider, GcpProvider, OnePasswordProvider, providerRegistry }; diff --git a/src/ui/AwsLogin.tsx b/src/ui/AwsLogin.tsx new file mode 100644 index 0000000..ea4814c --- /dev/null +++ b/src/ui/AwsLogin.tsx @@ -0,0 +1,81 @@ +import type { LoginOptions } from "@/providers/base/AuthProvider"; +import type { Provider } from "@/providers/base/Provider"; +import { Box, Text } from "ink"; +import Spinner from "ink-spinner"; +import { useEffect, useRef, useState } from "react"; + +export interface AwsLoginProps { + provider: Provider; + options?: LoginOptions; + onError?: (error: Error) => void; + onComplete?: () => void; +} + +export function AwsLogin({ provider, options, onError, onComplete }: AwsLoginProps) { + const [status, setStatus] = useState<"loading" | "success" | "error">("loading"); + const [message, setMessage] = useState(""); + const onErrorRef = useRef(onError); + const onCompleteRef = useRef(onComplete); + + useEffect(() => { + onErrorRef.current = onError; + onCompleteRef.current = onComplete; + }); + useEffect(() => { + const performLogin = async () => { + try { + setMessage(`Authenticating with AWS...`); + await provider.login(options); + setStatus("success"); + setMessage(`✓ Successfully authenticated with AWS`); + process.nextTick(() => { + onCompleteRef.current?.(); + }); + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + setStatus("error"); + setMessage(`⚠️ ${err.message}`); + onErrorRef.current?.(err); + } + }; + + void performLogin(); + }, [provider, options]); + + return ( + <> + + {status === "loading" && ( + + + + + {message} + + )} + + {status === "success" && ( + + {message} + + )} + + {status === "error" && ( + + + {message} + + + )} + + + {status === "loading" && ( + + + Using your local AWS CLI credentials (profile, SSO, or access keys) to verify your identity... + + + )} + + ); +} diff --git a/src/ui/Confirm.tsx b/src/ui/Confirm.tsx new file mode 100644 index 0000000..d01581a --- /dev/null +++ b/src/ui/Confirm.tsx @@ -0,0 +1,16 @@ +import prompts from "prompts"; + +export async function confirm(message: string): Promise { + const response = await prompts({ + type: "confirm", + name: "value", + message: message, + initial: false, + }); + + if (response.value === undefined) { + throw new Error("Input cancelled"); + } + + return response.value as boolean; +} diff --git a/src/ui/EnkryptifyLogin.tsx b/src/ui/EnkryptifyLogin.tsx new file mode 100644 index 0000000..f311a8b --- /dev/null +++ b/src/ui/EnkryptifyLogin.tsx @@ -0,0 +1,78 @@ +import type { LoginOptions } from "@/providers/base/AuthProvider"; +import type { Provider } from "@/providers/base/Provider"; +import { Box, Text } from "ink"; +import Spinner from "ink-spinner"; +import { useEffect, useRef, useState } from "react"; +export interface EnkryptifyLoginProps { + provider: Provider; + options?: LoginOptions; + onError?: (error: Error) => void; + onComplete?: () => void; +} + +export function EnkryptifyLogin({ provider, options, onError, onComplete }: EnkryptifyLoginProps) { + const [status, setStatus] = useState<"loading" | "success" | "error">("loading"); + const [message, setMessage] = useState(""); + const onErrorRef = useRef(onError); + const onCompleteRef = useRef(onComplete); + + useEffect(() => { + onErrorRef.current = onError; + onCompleteRef.current = onComplete; + }); + useEffect(() => { + const performLogin = async () => { + try { + setMessage(`Authenticating with enkryptify...`); + await provider.login(options); + setStatus("success"); + setMessage(`✓ Successfully authenticated with enkryptify`); + setTimeout(() => { + onCompleteRef.current?.(); + }, 1000); + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + setStatus("error"); + setMessage(`⚠️ ${err.message}`); + onErrorRef.current?.(err); + } + }; + + void performLogin(); + }, [provider, options]); + + return ( + <> + + {status === "loading" && ( + + + + + {message} + + )} + + {status === "success" && ( + + {message} + + )} + + {status === "error" && ( + + + {message} + + + )} + + + {status === "loading" && ( + + Please complete authentication in your browser... + + )} + + ); +} diff --git a/src/ui/GcpLogin.tsx b/src/ui/GcpLogin.tsx new file mode 100644 index 0000000..8e2588b --- /dev/null +++ b/src/ui/GcpLogin.tsx @@ -0,0 +1,70 @@ +import type { LoginOptions } from "@/providers/base/AuthProvider"; +import type { Provider } from "@/providers/base/Provider"; +import { Box, Text } from "ink"; +import Spinner from "ink-spinner"; +import { useEffect, useState } from "react"; + +export interface GcpLoginProps { + provider: Provider; + options?: LoginOptions; + onError?: (error: Error) => void; + onComplete?: () => void; +} +export function GcpLogin({ provider, options, onError, onComplete }: GcpLoginProps) { + const [status, setStatus] = useState<"loading" | "success" | "error">("loading"); + const [message, setMessage] = useState(""); + + useEffect(() => { + const performLogin = async () => { + try { + setMessage("Authenticating with Google Cloud..."); + await provider.login(options); + setStatus("success"); + setMessage("✅ Google Cloud authenticated"); + process.nextTick(() => { + onComplete?.(); + }); + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + setStatus("error"); + setMessage(`⚠️ ${err.message}`); + onError?.(err); + } + }; + + void performLogin(); + }, [provider, options, onError, onComplete]); + + return ( + <> + {status === "loading" && ( + + + + + + {message} + + + + Using your local Google Cloud credentials to verify your identity... + + + )} + + {status === "success" && ( + + {message} + + )} + + {status === "error" && ( + + + {message} + + + )} + + ); +} diff --git a/src/ui/LoginFlow.tsx b/src/ui/LoginFlow.tsx new file mode 100644 index 0000000..9c3aaf5 --- /dev/null +++ b/src/ui/LoginFlow.tsx @@ -0,0 +1,90 @@ +import type { LoginOptions } from "@/providers/base/AuthProvider"; +import type { Provider } from "@/providers/base/Provider"; +import { Box, Text, render } from "ink"; +import { AwsLogin } from "./AwsLogin"; +import { EnkryptifyLogin } from "./EnkryptifyLogin"; +import { GcpLogin } from "./GcpLogin"; +import { OnePasswordLogin } from "./onePassword"; + +export interface LoginFlowProps { + provider: Provider; + options?: LoginOptions; + onError?: (error: Error) => void; + onComplete?: () => void; +} + +function LoginFlowComponent({ provider, options, onError, onComplete }: LoginFlowProps) { + const renderProviderComponent = () => { + switch (provider.name) { + case "enkryptify": + return ( + + ); + case "aws": + return ; + case "gcp": + return ; + + case "onePassword": + return ( + + ); + default: + return ( + + Unknown provider: {provider.name} + + ); + } + }; + + return ( + + {renderProviderComponent()} + + ); +} +export async function LoginFlow({ provider, options, onError }: LoginFlowProps): Promise { + return new Promise((resolve, reject) => { + let isResolved = false; + + const login = render( + { + if (isResolved) return; + isResolved = true; + onError?.(error); + process.nextTick(() => { + login.unmount(); + reject(error); + }); + }} + onComplete={() => { + if (isResolved) return; + isResolved = true; + process.nextTick(() => { + login.unmount(); + resolve(); + }); + }} + />, + ); + + login + .waitUntilExit() + .then(() => { + if (!isResolved) { + isResolved = true; + resolve(); + } + }) + .catch((error) => { + if (!isResolved) { + isResolved = true; + reject(error); + } + }); + }); +} diff --git a/src/ui/RunFlow.tsx b/src/ui/RunFlow.tsx new file mode 100644 index 0000000..97aa7c1 --- /dev/null +++ b/src/ui/RunFlow.tsx @@ -0,0 +1,40 @@ +import { Box, Text, render } from "ink"; +import Spinner from "ink-spinner"; + +export interface RunFlowProps { + envName?: string; + run: () => Promise; +} + +function SpinnerComponent({ message }: { message: string }) { + return ( + + + + + {message} + + ); +} + +export async function RunFlow({ envName, run }: RunFlowProps): Promise { + const loadingMessage = envName ? `Injecting secrets for environment "${envName}"...` : "Injecting secrets..."; + + const spinner = render(, { + stdout: process.stderr, + }); + + try { + await run(); + + spinner.unmount(); + const successMessage = envName + ? `Secrets injected successfully for environment "${envName}".\n` + : "Secrets injected successfully.\n"; + process.stderr.write(successMessage); + } catch (error) { + spinner.unmount(); + // Re-throw error - let command handler log it + throw error; + } +} diff --git a/src/ui/SecretsTable.tsx b/src/ui/SecretsTable.tsx new file mode 100644 index 0000000..b6db871 --- /dev/null +++ b/src/ui/SecretsTable.tsx @@ -0,0 +1,68 @@ +import type { Secret } from "@/providers/base/Provider"; +import { Box, Text, render, useStdout } from "ink"; + +const MAX_ROWS_TO_DISPLAY = 100; + +function truncateValue(value: string, maxLength: number): string { + if (value.length <= maxLength) return value; + return value.substring(0, maxLength - 3) + "..."; +} + +function SecretsList({ secrets }: { secrets: Secret[] }) { + const { stdout } = useStdout(); + const columns = stdout?.columns ?? 80; + const nameColumnWidth = Math.min(25, Math.floor(columns * 0.25)); + const valueMaxLength = Math.max(15, columns - nameColumnWidth - 8); + + const displaySecrets = secrets.slice(0, MAX_ROWS_TO_DISPLAY); + const hasMore = secrets.length > MAX_ROWS_TO_DISPLAY; + + return ( + + + + Name + + + Value + + + + {displaySecrets.map((secret, index) => { + const truncatedValue = truncateValue(secret.value, valueMaxLength); + const namePadding = " ".repeat(Math.max(0, nameColumnWidth - secret.name.length)); + + return ( + + + + + {secret.name} + {namePadding} + + + + {truncatedValue} + + + {index < displaySecrets.length - 1 && } + + ); + })} + {hasMore && ( + + ... and {secrets.length - MAX_ROWS_TO_DISPLAY} more secrets + + )} + + ); +} + +export function showSecretsTable(secrets: Secret[]): void { + if (secrets.length === 0) { + console.log("No secrets found."); + return; + } + + render(); +} diff --git a/src/ui/SelectItem.tsx b/src/ui/SelectItem.tsx new file mode 100644 index 0000000..3cc1e28 --- /dev/null +++ b/src/ui/SelectItem.tsx @@ -0,0 +1,32 @@ +import { restoreCursor } from "@/lib/terminal"; +import { Box, Text, render } from "ink"; +import SelectInput from "ink-select-input"; + +export async function selectName(options: string[], title?: string): Promise { + const items = options.map((name, index) => ({ + label: name, + value: name, + key: `${name}-${index}`, + })); + return new Promise((resolve) => { + const select = render( + + {title && ( + + {title} + + )} + + { + select.unmount(); + restoreCursor(); + resolve(item.value as string); + }} + /> + + , + ); + }); +} diff --git a/src/ui/SuccessMessage.tsx b/src/ui/SuccessMessage.tsx new file mode 100644 index 0000000..1d28af2 --- /dev/null +++ b/src/ui/SuccessMessage.tsx @@ -0,0 +1,16 @@ +import { Box, Text, render } from "ink"; + +function MessageDisplay({ message }: { message: string }) { + return ( + + {message} + + ); +} + +export function showMessage(message: string): void { + const msg = render(); + setTimeout(() => { + msg.unmount(); + }, 2000); +} diff --git a/src/ui/onePassword.tsx b/src/ui/onePassword.tsx new file mode 100644 index 0000000..62cf3d4 --- /dev/null +++ b/src/ui/onePassword.tsx @@ -0,0 +1,74 @@ +import type { LoginOptions } from "@/providers/base/AuthProvider"; +import type { Provider } from "@/providers/base/Provider"; +import { Box, Text } from "ink"; +import Spinner from "ink-spinner"; +import { useEffect, useState } from "react"; + +export interface OnePasswordLoginProps { + provider: Provider; + options?: LoginOptions; + onError?: (error: Error) => void; + onComplete?: () => void; +} + +export function OnePasswordLogin({ provider, options, onError, onComplete }: OnePasswordLoginProps) { + const [status, setStatus] = useState<"loading" | "success" | "error">("loading"); + const [message, setMessage] = useState(""); + + useEffect(() => { + const performLogin = async () => { + try { + setMessage("Authenticating with 1Password..."); + await provider.login(options); + setStatus("success"); + setMessage("✓ Successfully authenticated with 1Password"); + process.nextTick(() => { + onComplete?.(); + }); + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + setStatus("error"); + setMessage(`⚠️ ${err.message}`); + onError?.(err); + } + }; + + void performLogin(); + }, [provider, options, onError, onComplete]); + + return ( + <> + {status === "loading" && ( + + + + + + {message} + + + + Using your 1Password token to verify your identity... + + + )} + + {status === "success" && ( + + {message} + + )} + + {status === "error" && ( + + + {message} + + + Press Ctrl+C to exit, or re-run the command to try again. + + + )} + + ); +} diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..a74692f --- /dev/null +++ b/test.ts @@ -0,0 +1,2 @@ +console.log(process.env.coco); +console.log(process.env.devName); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8c07eaa --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Path aliases + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}