@envmanager-cli/cli 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -11,6 +11,9 @@ declare const ConfigSchema: z.ZodObject<{
11
11
  organization_id: z.ZodOptional<z.ZodString>;
12
12
  output: z.ZodDefault<z.ZodString>;
13
13
  api_url: z.ZodOptional<z.ZodString>;
14
+ format: z.ZodOptional<z.ZodEnum<[string, ...string[]]>>;
15
+ k8s_namespace: z.ZodOptional<z.ZodString>;
16
+ k8s_name: z.ZodOptional<z.ZodString>;
14
17
  }, "strip", z.ZodTypeAny, {
15
18
  output: string;
16
19
  project_id?: string | undefined;
@@ -19,6 +22,9 @@ declare const ConfigSchema: z.ZodObject<{
19
22
  environment_id?: string | undefined;
20
23
  organization_id?: string | undefined;
21
24
  api_url?: string | undefined;
25
+ format?: string | undefined;
26
+ k8s_namespace?: string | undefined;
27
+ k8s_name?: string | undefined;
22
28
  }, {
23
29
  project_id?: string | undefined;
24
30
  project_name?: string | undefined;
@@ -27,6 +33,9 @@ declare const ConfigSchema: z.ZodObject<{
27
33
  organization_id?: string | undefined;
28
34
  output?: string | undefined;
29
35
  api_url?: string | undefined;
36
+ format?: string | undefined;
37
+ k8s_namespace?: string | undefined;
38
+ k8s_name?: string | undefined;
30
39
  }>;
31
40
  type Config = z.infer<typeof ConfigSchema>;
32
41
  declare function loadConfig(): Config | null;
package/dist/index.js CHANGED
@@ -197,6 +197,20 @@ async function createClient() {
197
197
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
198
198
  import { join as join2, dirname } from "path";
199
199
  import { z } from "zod";
200
+
201
+ // src/lib/formatters.ts
202
+ import * as yaml from "js-yaml";
203
+ var EXPORT_FORMATS = [
204
+ "dotenv",
205
+ "docker-compose",
206
+ "k8s-secret",
207
+ "k8s-configmap",
208
+ "vercel",
209
+ "railway",
210
+ "render"
211
+ ];
212
+
213
+ // src/lib/config.ts
200
214
  var ConfigSchema = z.object({
201
215
  project_id: z.string().uuid().optional(),
202
216
  project_name: z.string().optional(),
@@ -204,7 +218,10 @@ var ConfigSchema = z.object({
204
218
  environment_id: z.string().uuid().optional(),
205
219
  organization_id: z.string().uuid().optional(),
206
220
  output: z.string().default(".env"),
207
- api_url: z.string().url().optional()
221
+ api_url: z.string().url().optional(),
222
+ format: z.enum(EXPORT_FORMATS).optional(),
223
+ k8s_namespace: z.string().optional(),
224
+ k8s_name: z.string().optional()
208
225
  });
209
226
  var CONFIG_FILENAMES = ["envmanager.json", ".envmanagerrc"];
210
227
  function findConfigFile(startDir = process.cwd()) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/client.ts","../src/lib/credentials.ts","../src/lib/auth.ts","../src/lib/config.ts"],"sourcesContent":["import { createClient as createSupabaseClient, SupabaseClient } from '@supabase/supabase-js'\nimport { getCredentials, getApiKeyFromEnv, getStoredApiUrl, getStoredApiKey } from './credentials.js'\nimport { ensureAuthenticated, exchangeApiKeyForToken, clearCachedToken } from './auth.js'\n\nconst DEFAULT_API_URL = 'https://rhopfaburfflrdwpowcd.supabase.co'\nconst DEFAULT_ANON_KEY = 'sb_publishable_Y2EpPiIN3KPjQMc1GLVXjw__ghRVLC4'\nconst LOCAL_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'\n\nfunction getApiUrl(): string {\n return process.env.ENVMANAGER_API_URL || getStoredApiUrl() || DEFAULT_API_URL\n}\n\nfunction getAnonKey(): string {\n const apiUrl = getApiUrl()\n if (apiUrl.includes('localhost') || apiUrl.includes('127.0.0.1')) {\n return LOCAL_ANON_KEY\n }\n return process.env.ENVMANAGER_ANON_KEY || DEFAULT_ANON_KEY\n}\n\nlet clientInstance: SupabaseClient | null = null\nlet currentAccessToken: string | null = null\n\nexport async function createClient(): Promise<SupabaseClient> {\n if (clientInstance) {\n return clientInstance\n }\n\n // Priority: 1. Environment API key, 2. Stored API key (from login), 3. Session tokens\n const apiKey = getApiKeyFromEnv() || getStoredApiKey()\n\n if (apiKey) {\n const tokenResponse = await exchangeApiKeyForToken(apiKey)\n currentAccessToken = tokenResponse.access_token\n\n clientInstance = createSupabaseClient(getApiUrl(), getAnonKey(), {\n global: {\n headers: {\n Authorization: `Bearer ${currentAccessToken}`\n }\n }\n })\n\n await clientInstance.realtime.setAuth(currentAccessToken)\n return clientInstance\n }\n\n // Legacy flow: session tokens\n currentAccessToken = await ensureAuthenticated()\n\n clientInstance = createSupabaseClient(getApiUrl(), getAnonKey(), {\n global: {\n headers: {\n Authorization: `Bearer ${currentAccessToken}`\n }\n }\n })\n\n await clientInstance.realtime.setAuth(currentAccessToken)\n\n return clientInstance\n}\n\nexport function getAccessToken(): string | null {\n return currentAccessToken\n}\n\nexport async function refreshClientAuth(): Promise<void> {\n if (!clientInstance) return\n\n const accessToken = await ensureAuthenticated()\n currentAccessToken = accessToken\n\n // Update the realtime connection's auth token (preserves active channels)\n await clientInstance.realtime.setAuth(accessToken)\n}\n\nexport function resetClient(): void {\n clientInstance = null\n currentAccessToken = null\n clearCachedToken()\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, chmodSync } from 'fs'\nimport { homedir } from 'os'\nimport { join } from 'path'\n\nconst CONFIG_DIR = join(homedir(), '.config', 'envmanager')\nconst CREDENTIALS_FILE = join(CONFIG_DIR, 'auth.json')\n\ninterface Credentials {\n // New flow: API key based auth (preferred)\n apiKey?: string\n // Legacy flow: session tokens\n accessToken?: string\n refreshToken?: string\n expiresAt?: number\n // Common\n apiUrl?: string\n email?: string\n}\n\nfunction ensureConfigDir(): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })\n }\n}\n\nexport function getCredentials(): Credentials | null {\n if (!existsSync(CREDENTIALS_FILE)) {\n return null\n }\n\n try {\n const content = readFileSync(CREDENTIALS_FILE, 'utf-8')\n return JSON.parse(content) as Credentials\n } catch {\n return null\n }\n}\n\nexport function saveCredentials(credentials: Credentials): void {\n ensureConfigDir()\n writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { mode: 0o600 })\n chmodSync(CREDENTIALS_FILE, 0o600)\n}\n\nexport function clearCredentials(): void {\n if (existsSync(CREDENTIALS_FILE)) {\n unlinkSync(CREDENTIALS_FILE)\n }\n}\n\nexport function isTokenExpired(): boolean {\n const creds = getCredentials()\n if (!creds) return true\n // API key auth doesn't expire in the same way - let the server validate\n if (creds.apiKey) return false\n if (!creds.expiresAt) return true\n return Date.now() >= creds.expiresAt\n}\n\nconst PROACTIVE_REFRESH_BUFFER_MS = 5 * 60 * 1000\n\nexport function shouldRefreshToken(): boolean {\n const creds = getCredentials()\n if (!creds) return false\n // API key auth doesn't need proactive refresh\n if (creds.apiKey) return false\n if (!creds.expiresAt) return false\n return Date.now() >= (creds.expiresAt - PROACTIVE_REFRESH_BUFFER_MS)\n}\n\nexport function getApiKeyFromEnv(): string | null {\n return process.env.ENVMANAGER_API_KEY || null\n}\n\nexport function getStoredApiKey(): string | null {\n const creds = getCredentials()\n return creds?.apiKey || null\n}\n\nexport function getStoredApiUrl(): string | null {\n const creds = getCredentials()\n return creds?.apiUrl || null\n}\n","import { createServer, IncomingMessage, ServerResponse } from 'http'\nimport { randomBytes, createHash } from 'crypto'\nimport open from 'open'\nimport { saveCredentials, getCredentials, isTokenExpired, shouldRefreshToken, getStoredApiKey, getStoredApiUrl } from './credentials.js'\n\nconst DEFAULT_APP_URL = 'https://envmanager.com'\nconst DEFAULT_API_URL = 'https://rhopfaburfflrdwpowcd.supabase.co'\n\nfunction getAppUrl(): string {\n return process.env.ENVMANAGER_APP_URL || DEFAULT_APP_URL\n}\n\nfunction getApiUrl(): string {\n return process.env.ENVMANAGER_API_URL || getStoredApiUrl() || DEFAULT_API_URL\n}\n\nfunction generateCodeVerifier(): string {\n return randomBytes(32).toString('base64url')\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n return createHash('sha256').update(verifier).digest('base64url')\n}\n\nfunction generateState(): string {\n return randomBytes(16).toString('hex')\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n\ninterface AuthCallbackResult {\n apiKey?: string\n accessToken?: string\n refreshToken?: string\n expiresIn?: number\n}\n\nasync function startCallbackServer(expectedState: string, port: number = 8976): Promise<AuthCallbackResult> {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n server.close()\n reject(new Error('Authentication timed out after 5 minutes'))\n }, 5 * 60 * 1000)\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n \n if (url.pathname === '/callback') {\n const apiKey = url.searchParams.get('api_key')\n const accessToken = url.searchParams.get('access_token')\n const refreshToken = url.searchParams.get('refresh_token')\n const expiresIn = url.searchParams.get('expires_in')\n const state = url.searchParams.get('state')\n const error = url.searchParams.get('error')\n const errorDescription = url.searchParams.get('error_description')\n\n if (error) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(`\n <html>\n <body style=\"font-family: system-ui; padding: 40px; text-align: center;\">\n <h1>Authentication Failed</h1>\n <p>${escapeHtml(errorDescription || error || 'Unknown error')}</p>\n <p>You can close this window.</p>\n </body>\n </html>\n `)\n clearTimeout(timeout)\n server.close()\n reject(new Error(errorDescription || error))\n return\n }\n\n // Accept either API key (new flow) or session tokens (legacy/fallback)\n if (!apiKey && (!accessToken || !refreshToken)) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(`\n <html>\n <body style=\"font-family: system-ui; padding: 40px; text-align: center;\">\n <h1>Invalid Callback</h1>\n <p>Missing authentication credentials.</p>\n </body>\n </html>\n `)\n clearTimeout(timeout)\n server.close()\n reject(new Error('Invalid callback: missing credentials'))\n return\n }\n\n if (!state || state !== expectedState) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(`\n <html>\n <body style=\"font-family: system-ui; padding: 40px; text-align: center;\">\n <h1>Invalid State</h1>\n <p>State mismatch - possible CSRF attack.</p>\n </body>\n </html>\n `)\n clearTimeout(timeout)\n server.close()\n reject(new Error('State mismatch - authentication failed'))\n return\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(`\n <html>\n <body style=\"font-family: system-ui; padding: 40px; text-align: center;\">\n <h1>Authentication Successful!</h1>\n <p>You can close this window and return to your terminal.</p>\n </body>\n </html>\n `)\n\n clearTimeout(timeout)\n server.close()\n\n // Return API key if present (new flow), otherwise return tokens (legacy)\n if (apiKey) {\n resolve({ apiKey })\n } else {\n resolve({\n accessToken: accessToken!,\n refreshToken: refreshToken!,\n expiresIn: parseInt(expiresIn || '3600', 10)\n })\n }\n } else {\n res.writeHead(404)\n res.end('Not found')\n }\n })\n\n server.listen(port, '127.0.0.1', () => {})\n \n server.on('error', (err: Error & { code?: string }) => {\n clearTimeout(timeout)\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${port} is already in use. Please close the application using it.`))\n } else {\n reject(err)\n }\n })\n })\n}\n\ninterface TokenResponse {\n access_token: string\n refresh_token: string\n expires_in: number\n user?: {\n id: string\n email?: string\n }\n}\n\nasync function exchangeCodeForTokens(code: string, codeVerifier: string): Promise<TokenResponse> {\n const apiUrl = getApiUrl()\n const response = await fetch(`${apiUrl}/functions/v1/cli-auth-callback`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n code,\n code_verifier: codeVerifier,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string }\n throw new Error(errorData.error || `HTTP ${response.status}`)\n }\n\n return response.json() as Promise<TokenResponse>\n}\n\n// In-memory cache for exchanged API key tokens (process lifetime only)\nlet cachedToken: { accessToken: string; refreshToken: string; expiresAt: number } | null = null\n\nexport function clearCachedToken(): void {\n cachedToken = null\n}\n\nexport async function refreshTokens(): Promise<void> {\n const credentials = getCredentials()\n if (!credentials?.refreshToken) {\n throw new Error('No refresh token available')\n }\n\n const apiUrl = getApiUrl()\n const response = await fetch(`${apiUrl}/functions/v1/cli-auth-refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n refresh_token: credentials.refreshToken,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: `HTTP ${response.status}` })) as { error?: string }\n const errorMessage = errorData.error || `HTTP ${response.status}`\n throw new Error(`Token refresh failed: ${errorMessage}`)\n }\n\n const tokens = await response.json() as TokenResponse\n \n saveCredentials({\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: Date.now() + tokens.expires_in * 1000,\n email: credentials.email,\n })\n}\n\nexport async function exchangeApiKeyForToken(apiKey: string): Promise<TokenResponse> {\n const apiUrl = getApiUrl()\n const response = await fetch(`${apiUrl}/functions/v1/cli-api-key-auth`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n },\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string }\n throw new Error(errorData.error || `HTTP ${response.status}`)\n }\n\n return response.json() as Promise<TokenResponse>\n}\n\nexport async function loginWithBrowser(): Promise<{ email?: string }> {\n const state = generateState()\n const port = 8976\n const redirectUri = `http://localhost:${port}/callback`\n\n const appUrl = getAppUrl()\n const authUrl = new URL(`${appUrl}/auth/cli-login`)\n authUrl.searchParams.set('redirect_uri', redirectUri)\n authUrl.searchParams.set('state', state)\n\n const serverPromise = startCallbackServer(state, port)\n\n await open(authUrl.toString())\n\n const result = await serverPromise\n\n // New flow: API key based authentication\n if (result.apiKey) {\n saveCredentials({\n apiKey: result.apiKey,\n apiUrl: getApiUrl(),\n })\n return {}\n }\n\n // Legacy flow: session tokens (fallback)\n saveCredentials({\n accessToken: result.accessToken!,\n refreshToken: result.refreshToken!,\n expiresAt: Date.now() + (result.expiresIn || 3600) * 1000,\n apiUrl: getApiUrl(),\n })\n\n return {}\n}\n\nexport async function ensureAuthenticated(): Promise<string> {\n const credentials = getCredentials()\n\n if (!credentials) {\n throw new Error('Not authenticated. Run `envmanager login` first.')\n }\n\n // New flow: API key based authentication\n const storedApiKey = getStoredApiKey()\n if (storedApiKey) {\n // Return cached token if still valid (with 5-minute buffer)\n if (cachedToken && Date.now() < cachedToken.expiresAt - 5 * 60 * 1000) {\n return cachedToken.accessToken\n }\n\n const tokens = await exchangeApiKeyForToken(storedApiKey)\n cachedToken = {\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: Date.now() + tokens.expires_in * 1000,\n }\n return cachedToken.accessToken\n }\n\n // Legacy flow: session token refresh\n if (shouldRefreshToken() || isTokenExpired()) {\n try {\n await refreshTokens()\n const newCreds = getCredentials()\n if (newCreds?.accessToken) {\n return newCreds.accessToken\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n throw new Error(`Session refresh failed: ${message}\\nRun \\`envmanager login\\` to re-authenticate.`)\n }\n }\n\n if (!credentials.accessToken) {\n throw new Error('Not authenticated. Run `envmanager login` first.')\n }\n\n return credentials.accessToken\n}\n\nexport async function tryRefreshToken(): Promise<boolean> {\n try {\n if (shouldRefreshToken() || isTokenExpired()) {\n await refreshTokens()\n return true\n }\n return false\n } catch {\n return false\n }\n}\n","import { existsSync, readFileSync } from 'fs'\nimport { join, dirname } from 'path'\nimport { z } from 'zod'\n\nconst ConfigSchema = z.object({\n project_id: z.string().uuid().optional(),\n project_name: z.string().optional(),\n environment: z.string().optional(),\n environment_id: z.string().uuid().optional(),\n organization_id: z.string().uuid().optional(),\n output: z.string().default('.env'),\n api_url: z.string().url().optional()\n})\n\nexport type Config = z.infer<typeof ConfigSchema>\n\nconst CONFIG_FILENAMES = ['envmanager.json', '.envmanagerrc']\n\nfunction findConfigFile(startDir: string = process.cwd()): string | null {\n let currentDir = startDir\n \n while (currentDir !== dirname(currentDir)) {\n for (const filename of CONFIG_FILENAMES) {\n const configPath = join(currentDir, filename)\n if (existsSync(configPath)) {\n return configPath\n }\n }\n currentDir = dirname(currentDir)\n }\n \n return null\n}\n\nexport function loadConfig(): Config | null {\n const configPath = findConfigFile()\n \n if (!configPath) {\n return null\n }\n \n try {\n const content = readFileSync(configPath, 'utf-8')\n const parsed = JSON.parse(content)\n return ConfigSchema.parse(parsed)\n } catch (error) {\n if (error instanceof z.ZodError) {\n console.error('Invalid config file:', error.errors)\n }\n return null\n }\n}\n\nexport function getConfigPath(): string | null {\n return findConfigFile()\n}\n"],"mappings":"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","names":["DEFAULT_API_URL","getApiUrl","existsSync","readFileSync","join"]}
1
+ {"version":3,"sources":["../src/lib/client.ts","../src/lib/credentials.ts","../src/lib/auth.ts","../src/lib/config.ts","../src/lib/formatters.ts"],"sourcesContent":["import { createClient as createSupabaseClient, SupabaseClient } from '@supabase/supabase-js'\nimport { getCredentials, getApiKeyFromEnv, getStoredApiUrl, getStoredApiKey } from './credentials.js'\nimport { ensureAuthenticated, exchangeApiKeyForToken, clearCachedToken } from './auth.js'\n\nconst DEFAULT_API_URL = 'https://rhopfaburfflrdwpowcd.supabase.co'\nconst DEFAULT_ANON_KEY = 'sb_publishable_Y2EpPiIN3KPjQMc1GLVXjw__ghRVLC4'\nconst LOCAL_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'\n\nfunction getApiUrl(): string {\n return process.env.ENVMANAGER_API_URL || getStoredApiUrl() || DEFAULT_API_URL\n}\n\nfunction getAnonKey(): string {\n const apiUrl = getApiUrl()\n if (apiUrl.includes('localhost') || apiUrl.includes('127.0.0.1')) {\n return LOCAL_ANON_KEY\n }\n return process.env.ENVMANAGER_ANON_KEY || DEFAULT_ANON_KEY\n}\n\nlet clientInstance: SupabaseClient | null = null\nlet currentAccessToken: string | null = null\n\nexport async function createClient(): Promise<SupabaseClient> {\n if (clientInstance) {\n return clientInstance\n }\n\n // Priority: 1. Environment API key, 2. Stored API key (from login), 3. Session tokens\n const apiKey = getApiKeyFromEnv() || getStoredApiKey()\n\n if (apiKey) {\n const tokenResponse = await exchangeApiKeyForToken(apiKey)\n currentAccessToken = tokenResponse.access_token\n\n clientInstance = createSupabaseClient(getApiUrl(), getAnonKey(), {\n global: {\n headers: {\n Authorization: `Bearer ${currentAccessToken}`\n }\n }\n })\n\n await clientInstance.realtime.setAuth(currentAccessToken)\n return clientInstance\n }\n\n // Legacy flow: session tokens\n currentAccessToken = await ensureAuthenticated()\n\n clientInstance = createSupabaseClient(getApiUrl(), getAnonKey(), {\n global: {\n headers: {\n Authorization: `Bearer ${currentAccessToken}`\n }\n }\n })\n\n await clientInstance.realtime.setAuth(currentAccessToken)\n\n return clientInstance\n}\n\nexport function getAccessToken(): string | null {\n return currentAccessToken\n}\n\nexport async function refreshClientAuth(): Promise<void> {\n if (!clientInstance) return\n\n const accessToken = await ensureAuthenticated()\n currentAccessToken = accessToken\n\n // Update the realtime connection's auth token (preserves active channels)\n await clientInstance.realtime.setAuth(accessToken)\n}\n\nexport function resetClient(): void {\n clientInstance = null\n currentAccessToken = null\n clearCachedToken()\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, chmodSync } from 'fs'\nimport { homedir } from 'os'\nimport { join } from 'path'\n\nconst CONFIG_DIR = join(homedir(), '.config', 'envmanager')\nconst CREDENTIALS_FILE = join(CONFIG_DIR, 'auth.json')\n\ninterface Credentials {\n // New flow: API key based auth (preferred)\n apiKey?: string\n // Legacy flow: session tokens\n accessToken?: string\n refreshToken?: string\n expiresAt?: number\n // Common\n apiUrl?: string\n email?: string\n}\n\nfunction ensureConfigDir(): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })\n }\n}\n\nexport function getCredentials(): Credentials | null {\n if (!existsSync(CREDENTIALS_FILE)) {\n return null\n }\n\n try {\n const content = readFileSync(CREDENTIALS_FILE, 'utf-8')\n return JSON.parse(content) as Credentials\n } catch {\n return null\n }\n}\n\nexport function saveCredentials(credentials: Credentials): void {\n ensureConfigDir()\n writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { mode: 0o600 })\n chmodSync(CREDENTIALS_FILE, 0o600)\n}\n\nexport function clearCredentials(): void {\n if (existsSync(CREDENTIALS_FILE)) {\n unlinkSync(CREDENTIALS_FILE)\n }\n}\n\nexport function isTokenExpired(): boolean {\n const creds = getCredentials()\n if (!creds) return true\n // API key auth doesn't expire in the same way - let the server validate\n if (creds.apiKey) return false\n if (!creds.expiresAt) return true\n return Date.now() >= creds.expiresAt\n}\n\nconst PROACTIVE_REFRESH_BUFFER_MS = 5 * 60 * 1000\n\nexport function shouldRefreshToken(): boolean {\n const creds = getCredentials()\n if (!creds) return false\n // API key auth doesn't need proactive refresh\n if (creds.apiKey) return false\n if (!creds.expiresAt) return false\n return Date.now() >= (creds.expiresAt - PROACTIVE_REFRESH_BUFFER_MS)\n}\n\nexport function getApiKeyFromEnv(): string | null {\n return process.env.ENVMANAGER_API_KEY || null\n}\n\nexport function getStoredApiKey(): string | null {\n const creds = getCredentials()\n return creds?.apiKey || null\n}\n\nexport function getStoredApiUrl(): string | null {\n const creds = getCredentials()\n return creds?.apiUrl || null\n}\n","import { createServer, IncomingMessage, ServerResponse } from 'http'\nimport { randomBytes, createHash } from 'crypto'\nimport open from 'open'\nimport { saveCredentials, getCredentials, isTokenExpired, shouldRefreshToken, getStoredApiKey, getStoredApiUrl } from './credentials.js'\n\nconst DEFAULT_APP_URL = 'https://envmanager.com'\nconst DEFAULT_API_URL = 'https://rhopfaburfflrdwpowcd.supabase.co'\n\nfunction getAppUrl(): string {\n return process.env.ENVMANAGER_APP_URL || DEFAULT_APP_URL\n}\n\nfunction getApiUrl(): string {\n return process.env.ENVMANAGER_API_URL || getStoredApiUrl() || DEFAULT_API_URL\n}\n\nfunction generateCodeVerifier(): string {\n return randomBytes(32).toString('base64url')\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n return createHash('sha256').update(verifier).digest('base64url')\n}\n\nfunction generateState(): string {\n return randomBytes(16).toString('hex')\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n\ninterface AuthCallbackResult {\n apiKey?: string\n accessToken?: string\n refreshToken?: string\n expiresIn?: number\n}\n\nasync function startCallbackServer(expectedState: string, port: number = 8976): Promise<AuthCallbackResult> {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n server.close()\n reject(new Error('Authentication timed out after 5 minutes'))\n }, 5 * 60 * 1000)\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`)\n \n if (url.pathname === '/callback') {\n const apiKey = url.searchParams.get('api_key')\n const accessToken = url.searchParams.get('access_token')\n const refreshToken = url.searchParams.get('refresh_token')\n const expiresIn = url.searchParams.get('expires_in')\n const state = url.searchParams.get('state')\n const error = url.searchParams.get('error')\n const errorDescription = url.searchParams.get('error_description')\n\n if (error) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(`\n <html>\n <body style=\"font-family: system-ui; padding: 40px; text-align: center;\">\n <h1>Authentication Failed</h1>\n <p>${escapeHtml(errorDescription || error || 'Unknown error')}</p>\n <p>You can close this window.</p>\n </body>\n </html>\n `)\n clearTimeout(timeout)\n server.close()\n reject(new Error(errorDescription || error))\n return\n }\n\n // Accept either API key (new flow) or session tokens (legacy/fallback)\n if (!apiKey && (!accessToken || !refreshToken)) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(`\n <html>\n <body style=\"font-family: system-ui; padding: 40px; text-align: center;\">\n <h1>Invalid Callback</h1>\n <p>Missing authentication credentials.</p>\n </body>\n </html>\n `)\n clearTimeout(timeout)\n server.close()\n reject(new Error('Invalid callback: missing credentials'))\n return\n }\n\n if (!state || state !== expectedState) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end(`\n <html>\n <body style=\"font-family: system-ui; padding: 40px; text-align: center;\">\n <h1>Invalid State</h1>\n <p>State mismatch - possible CSRF attack.</p>\n </body>\n </html>\n `)\n clearTimeout(timeout)\n server.close()\n reject(new Error('State mismatch - authentication failed'))\n return\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(`\n <html>\n <body style=\"font-family: system-ui; padding: 40px; text-align: center;\">\n <h1>Authentication Successful!</h1>\n <p>You can close this window and return to your terminal.</p>\n </body>\n </html>\n `)\n\n clearTimeout(timeout)\n server.close()\n\n // Return API key if present (new flow), otherwise return tokens (legacy)\n if (apiKey) {\n resolve({ apiKey })\n } else {\n resolve({\n accessToken: accessToken!,\n refreshToken: refreshToken!,\n expiresIn: parseInt(expiresIn || '3600', 10)\n })\n }\n } else {\n res.writeHead(404)\n res.end('Not found')\n }\n })\n\n server.listen(port, '127.0.0.1', () => {})\n \n server.on('error', (err: Error & { code?: string }) => {\n clearTimeout(timeout)\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${port} is already in use. Please close the application using it.`))\n } else {\n reject(err)\n }\n })\n })\n}\n\ninterface TokenResponse {\n access_token: string\n refresh_token: string\n expires_in: number\n user?: {\n id: string\n email?: string\n }\n}\n\nasync function exchangeCodeForTokens(code: string, codeVerifier: string): Promise<TokenResponse> {\n const apiUrl = getApiUrl()\n const response = await fetch(`${apiUrl}/functions/v1/cli-auth-callback`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n code,\n code_verifier: codeVerifier,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string }\n throw new Error(errorData.error || `HTTP ${response.status}`)\n }\n\n return response.json() as Promise<TokenResponse>\n}\n\n// In-memory cache for exchanged API key tokens (process lifetime only)\nlet cachedToken: { accessToken: string; refreshToken: string; expiresAt: number } | null = null\n\nexport function clearCachedToken(): void {\n cachedToken = null\n}\n\nexport async function refreshTokens(): Promise<void> {\n const credentials = getCredentials()\n if (!credentials?.refreshToken) {\n throw new Error('No refresh token available')\n }\n\n const apiUrl = getApiUrl()\n const response = await fetch(`${apiUrl}/functions/v1/cli-auth-refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n refresh_token: credentials.refreshToken,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: `HTTP ${response.status}` })) as { error?: string }\n const errorMessage = errorData.error || `HTTP ${response.status}`\n throw new Error(`Token refresh failed: ${errorMessage}`)\n }\n\n const tokens = await response.json() as TokenResponse\n \n saveCredentials({\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: Date.now() + tokens.expires_in * 1000,\n email: credentials.email,\n })\n}\n\nexport async function exchangeApiKeyForToken(apiKey: string): Promise<TokenResponse> {\n const apiUrl = getApiUrl()\n const response = await fetch(`${apiUrl}/functions/v1/cli-api-key-auth`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n },\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string }\n throw new Error(errorData.error || `HTTP ${response.status}`)\n }\n\n return response.json() as Promise<TokenResponse>\n}\n\nexport async function loginWithBrowser(): Promise<{ email?: string }> {\n const state = generateState()\n const port = 8976\n const redirectUri = `http://localhost:${port}/callback`\n\n const appUrl = getAppUrl()\n const authUrl = new URL(`${appUrl}/auth/cli-login`)\n authUrl.searchParams.set('redirect_uri', redirectUri)\n authUrl.searchParams.set('state', state)\n\n const serverPromise = startCallbackServer(state, port)\n\n await open(authUrl.toString())\n\n const result = await serverPromise\n\n // New flow: API key based authentication\n if (result.apiKey) {\n saveCredentials({\n apiKey: result.apiKey,\n apiUrl: getApiUrl(),\n })\n return {}\n }\n\n // Legacy flow: session tokens (fallback)\n saveCredentials({\n accessToken: result.accessToken!,\n refreshToken: result.refreshToken!,\n expiresAt: Date.now() + (result.expiresIn || 3600) * 1000,\n apiUrl: getApiUrl(),\n })\n\n return {}\n}\n\nexport async function ensureAuthenticated(): Promise<string> {\n const credentials = getCredentials()\n\n if (!credentials) {\n throw new Error('Not authenticated. Run `envmanager login` first.')\n }\n\n // New flow: API key based authentication\n const storedApiKey = getStoredApiKey()\n if (storedApiKey) {\n // Return cached token if still valid (with 5-minute buffer)\n if (cachedToken && Date.now() < cachedToken.expiresAt - 5 * 60 * 1000) {\n return cachedToken.accessToken\n }\n\n const tokens = await exchangeApiKeyForToken(storedApiKey)\n cachedToken = {\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: Date.now() + tokens.expires_in * 1000,\n }\n return cachedToken.accessToken\n }\n\n // Legacy flow: session token refresh\n if (shouldRefreshToken() || isTokenExpired()) {\n try {\n await refreshTokens()\n const newCreds = getCredentials()\n if (newCreds?.accessToken) {\n return newCreds.accessToken\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n throw new Error(`Session refresh failed: ${message}\\nRun \\`envmanager login\\` to re-authenticate.`)\n }\n }\n\n if (!credentials.accessToken) {\n throw new Error('Not authenticated. Run `envmanager login` first.')\n }\n\n return credentials.accessToken\n}\n\nexport async function tryRefreshToken(): Promise<boolean> {\n try {\n if (shouldRefreshToken() || isTokenExpired()) {\n await refreshTokens()\n return true\n }\n return false\n } catch {\n return false\n }\n}\n","import { existsSync, readFileSync } from 'fs'\nimport { join, dirname } from 'path'\nimport { z } from 'zod'\nimport { EXPORT_FORMATS } from './formatters.js'\n\nconst ConfigSchema = z.object({\n project_id: z.string().uuid().optional(),\n project_name: z.string().optional(),\n environment: z.string().optional(),\n environment_id: z.string().uuid().optional(),\n organization_id: z.string().uuid().optional(),\n output: z.string().default('.env'),\n api_url: z.string().url().optional(),\n format: z.enum(EXPORT_FORMATS as [string, ...string[]]).optional(),\n k8s_namespace: z.string().optional(),\n k8s_name: z.string().optional(),\n})\n\nexport type Config = z.infer<typeof ConfigSchema>\n\nconst CONFIG_FILENAMES = ['envmanager.json', '.envmanagerrc']\n\nfunction findConfigFile(startDir: string = process.cwd()): string | null {\n let currentDir = startDir\n \n while (currentDir !== dirname(currentDir)) {\n for (const filename of CONFIG_FILENAMES) {\n const configPath = join(currentDir, filename)\n if (existsSync(configPath)) {\n return configPath\n }\n }\n currentDir = dirname(currentDir)\n }\n \n return null\n}\n\nexport function loadConfig(): Config | null {\n const configPath = findConfigFile()\n \n if (!configPath) {\n return null\n }\n \n try {\n const content = readFileSync(configPath, 'utf-8')\n const parsed = JSON.parse(content)\n return ConfigSchema.parse(parsed)\n } catch (error) {\n if (error instanceof z.ZodError) {\n console.error('Invalid config file:', error.errors)\n }\n return null\n }\n}\n\nexport function getConfigPath(): string | null {\n return findConfigFile()\n}\n","import * as yaml from 'js-yaml'\n\nexport type ExportFormat = 'dotenv' | 'docker-compose' | 'k8s-secret' | 'k8s-configmap' | 'vercel' | 'railway' | 'render'\n\nexport const EXPORT_FORMATS: ExportFormat[] = [\n 'dotenv', 'docker-compose', 'k8s-secret', 'k8s-configmap', 'vercel', 'railway', 'render'\n]\n\nexport interface ExportVariable {\n key: string\n value: string\n isSecret: boolean\n}\n\nexport interface K8sConfig {\n name: string\n namespace: string\n}\n\n/**\n * Sanitize a name to be valid K8s resource name (DNS subdomain format)\n */\nexport function sanitizeK8sName(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/^-+|-+$/g, '')\n .substring(0, 63)\n}\n\nexport function toDotEnv(variables: ExportVariable[]): string {\n return variables\n .map(v => {\n const needsQuotes = /[\\s='\"#\\n\\r]/.test(v.value)\n const value = needsQuotes\n ? `\"${v.value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n')}\"`\n : v.value\n return `${v.key}=${value}`\n })\n .join('\\n')\n}\n\nexport function toDockerCompose(variables: ExportVariable[]): string {\n const envData: Record<string, string> = {}\n for (const v of variables) {\n envData[v.key] = v.value\n }\n\n const snippet = {\n services: {\n app: {\n environment: envData\n }\n }\n }\n\n const yamlOutput = yaml.dump(snippet, { lineWidth: -1, quotingType: '\"' })\n\n return `# Docker Compose environment section\\n# Copy this into your docker-compose.yml and replace 'app' with your service name\\n${yamlOutput}`\n}\n\nexport function toKubernetesSecret(variables: ExportVariable[], config: K8sConfig): string {\n const data: Record<string, string> = {}\n for (const v of variables) {\n data[v.key] = Buffer.from(v.value).toString('base64')\n }\n\n const manifest = {\n apiVersion: 'v1',\n kind: 'Secret',\n metadata: {\n name: config.name,\n namespace: config.namespace,\n },\n type: 'Opaque',\n data\n }\n\n return yaml.dump(manifest, { lineWidth: -1, quotingType: '\"' })\n}\n\nexport function toKubernetesConfigMap(variables: ExportVariable[], config: K8sConfig): string {\n const data: Record<string, string> = {}\n for (const v of variables) {\n data[v.key] = v.value\n }\n\n const manifest = {\n apiVersion: 'v1',\n kind: 'ConfigMap',\n metadata: {\n name: config.name,\n namespace: config.namespace,\n },\n data\n }\n\n return yaml.dump(manifest, { lineWidth: -1, quotingType: '\"' })\n}\n\nexport function toVercelCLI(variables: ExportVariable[]): string {\n if (variables.length === 0) return '# No variables to export'\n\n const header = `# Vercel CLI commands to add environment variables\n# Run these commands in your project directory\n# Docs: https://vercel.com/docs/cli/env\n# Note: You may need to run 'vercel login' first\n\n`\n\n const commands = variables.map(v => {\n if (v.value.includes('\\n')) {\n const escapedValue = v.value.replace(/'/g, \"'\\\\''\")\n return `vercel env add ${v.key} production << 'EOF'\\n${escapedValue}\\nEOF`\n } else {\n const escapedValue = v.value.replace(/'/g, \"'\\\\''\")\n return `echo '${escapedValue}' | vercel env add ${v.key} production`\n }\n }).join('\\n\\n')\n\n return header + commands\n}\n\nexport function toRailwayCLI(variables: ExportVariable[]): string {\n if (variables.length === 0) return '# No variables to export'\n\n const header = `# Railway CLI commands to set environment variables\n# Run these commands in your project directory\n# Docs: https://docs.railway.app/reference/cli-api#variables-set\n# Note: You may need to run 'railway login' first\n\n`\n\n const commands = variables.map(v => {\n const escapedValue = v.value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')\n return `railway variables set ${v.key}=\"${escapedValue}\"`\n }).join('\\n')\n\n return header + commands\n}\n\nexport function toRenderCLI(variables: ExportVariable[]): string {\n if (variables.length === 0) return '# No variables to export'\n\n const header = `# Render CLI commands to set environment variables\n# Run these commands in your project directory\n# Docs: https://render.com/docs/cli\n# Note: You may need to authenticate first\n\n`\n\n const commands = variables.map(v => {\n const escapedValue = v.value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')\n return `render env:set ${v.key}=\"${escapedValue}\"`\n }).join('\\n')\n\n return header + commands\n}\n\nexport function formatVariables(\n variables: ExportVariable[],\n format: ExportFormat,\n k8sConfig?: K8sConfig\n): string {\n switch (format) {\n case 'dotenv':\n return toDotEnv(variables)\n case 'docker-compose':\n return toDockerCompose(variables)\n case 'k8s-secret':\n return toKubernetesSecret(variables, k8sConfig ?? { name: 'app-secrets', namespace: 'default' })\n case 'k8s-configmap':\n return toKubernetesConfigMap(variables, k8sConfig ?? { name: 'app-config', namespace: 'default' })\n case 'vercel':\n return toVercelCLI(variables)\n case 'railway':\n return toRailwayCLI(variables)\n case 'render':\n return toRenderCLI(variables)\n }\n}\n"],"mappings":";;;AAAA,SAAS,gBAAgB,4BAA4C;;;ACArE,SAAS,YAAY,WAAW,cAAc,eAAe,YAAY,iBAAiB;AAC1F,SAAS,eAAe;AACxB,SAAS,YAAY;AAErB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,YAAY;AAC1D,IAAM,mBAAmB,KAAK,YAAY,WAAW;AAcrD,SAAS,kBAAwB;AAC/B,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,cAAU,YAAY,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,EACxD;AACF;AAEO,SAAS,iBAAqC;AACnD,MAAI,CAAC,WAAW,gBAAgB,GAAG;AACjC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,kBAAkB,OAAO;AACtD,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,gBAAgB,aAAgC;AAC9D,kBAAgB;AAChB,gBAAc,kBAAkB,KAAK,UAAU,aAAa,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AACrF,YAAU,kBAAkB,GAAK;AACnC;AAEO,SAAS,mBAAyB;AACvC,MAAI,WAAW,gBAAgB,GAAG;AAChC,eAAW,gBAAgB;AAAA,EAC7B;AACF;AAEO,SAAS,iBAA0B;AACxC,QAAM,QAAQ,eAAe;AAC7B,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,MAAM,OAAQ,QAAO;AACzB,MAAI,CAAC,MAAM,UAAW,QAAO;AAC7B,SAAO,KAAK,IAAI,KAAK,MAAM;AAC7B;AAEA,IAAM,8BAA8B,IAAI,KAAK;AAEtC,SAAS,qBAA8B;AAC5C,QAAM,QAAQ,eAAe;AAC7B,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,MAAM,OAAQ,QAAO;AACzB,MAAI,CAAC,MAAM,UAAW,QAAO;AAC7B,SAAO,KAAK,IAAI,KAAM,MAAM,YAAY;AAC1C;AAEO,SAAS,mBAAkC;AAChD,SAAO,QAAQ,IAAI,sBAAsB;AAC3C;AAEO,SAAS,kBAAiC;AAC/C,QAAM,QAAQ,eAAe;AAC7B,SAAO,OAAO,UAAU;AAC1B;AAEO,SAAS,kBAAiC;AAC/C,QAAM,QAAQ,eAAe;AAC7B,SAAO,OAAO,UAAU;AAC1B;;;AChFA,OAAO,UAAU;AAIjB,IAAM,kBAAkB;AAMxB,SAAS,YAAoB;AAC3B,SAAO,QAAQ,IAAI,sBAAsB,gBAAgB,KAAK;AAChE;AA2KA,IAAI,cAAuF;AAM3F,eAAsB,gBAA+B;AACnD,QAAM,cAAc,eAAe;AACnC,MAAI,CAAC,aAAa,cAAc;AAC9B,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,MAAM,MAAM,GAAG,MAAM,kCAAkC;AAAA,IACtE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,eAAe,YAAY;AAAA,IAC7B,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,EAAE,OAAO,QAAQ,SAAS,MAAM,GAAG,EAAE;AAC1F,UAAM,eAAe,UAAU,SAAS,QAAQ,SAAS,MAAM;AAC/D,UAAM,IAAI,MAAM,yBAAyB,YAAY,EAAE;AAAA,EACzD;AAEA,QAAM,SAAS,MAAM,SAAS,KAAK;AAEnC,kBAAgB;AAAA,IACd,aAAa,OAAO;AAAA,IACpB,cAAc,OAAO;AAAA,IACrB,WAAW,KAAK,IAAI,IAAI,OAAO,aAAa;AAAA,IAC5C,OAAO,YAAY;AAAA,EACrB,CAAC;AACH;AAEA,eAAsB,uBAAuB,QAAwC;AACnF,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,MAAM,MAAM,GAAG,MAAM,kCAAkC;AAAA,IACtE,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,iBAAiB,UAAU,MAAM;AAAA,MACjC,gBAAgB;AAAA,IAClB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,EAAE,OAAO,gBAAgB,EAAE;AAChF,UAAM,IAAI,MAAM,UAAU,SAAS,QAAQ,SAAS,MAAM,EAAE;AAAA,EAC9D;AAEA,SAAO,SAAS,KAAK;AACvB;AAsCA,eAAsB,sBAAuC;AAC3D,QAAM,cAAc,eAAe;AAEnC,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAGA,QAAM,eAAe,gBAAgB;AACrC,MAAI,cAAc;AAEhB,QAAI,eAAe,KAAK,IAAI,IAAI,YAAY,YAAY,IAAI,KAAK,KAAM;AACrE,aAAO,YAAY;AAAA,IACrB;AAEA,UAAM,SAAS,MAAM,uBAAuB,YAAY;AACxD,kBAAc;AAAA,MACZ,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,MACrB,WAAW,KAAK,IAAI,IAAI,OAAO,aAAa;AAAA,IAC9C;AACA,WAAO,YAAY;AAAA,EACrB;AAGA,MAAI,mBAAmB,KAAK,eAAe,GAAG;AAC5C,QAAI;AACF,YAAM,cAAc;AACpB,YAAM,WAAW,eAAe;AAChC,UAAI,UAAU,aAAa;AACzB,eAAO,SAAS;AAAA,MAClB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,YAAM,IAAI,MAAM,2BAA2B,OAAO;AAAA,6CAAgD;AAAA,IACpG;AAAA,EACF;AAEA,MAAI,CAAC,YAAY,aAAa;AAC5B,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,SAAO,YAAY;AACrB;;;AF3TA,IAAMA,mBAAkB;AACxB,IAAM,mBAAmB;AACzB,IAAM,iBAAiB;AAEvB,SAASC,aAAoB;AAC3B,SAAO,QAAQ,IAAI,sBAAsB,gBAAgB,KAAKD;AAChE;AAEA,SAAS,aAAqB;AAC5B,QAAM,SAASC,WAAU;AACzB,MAAI,OAAO,SAAS,WAAW,KAAK,OAAO,SAAS,WAAW,GAAG;AAChE,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,IAAI,uBAAuB;AAC5C;AAEA,IAAI,iBAAwC;AAC5C,IAAI,qBAAoC;AAExC,eAAsB,eAAwC;AAC5D,MAAI,gBAAgB;AAClB,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,iBAAiB,KAAK,gBAAgB;AAErD,MAAI,QAAQ;AACV,UAAM,gBAAgB,MAAM,uBAAuB,MAAM;AACzD,yBAAqB,cAAc;AAEnC,qBAAiB,qBAAqBA,WAAU,GAAG,WAAW,GAAG;AAAA,MAC/D,QAAQ;AAAA,QACN,SAAS;AAAA,UACP,eAAe,UAAU,kBAAkB;AAAA,QAC7C;AAAA,MACF;AAAA,IACF,CAAC;AAED,UAAM,eAAe,SAAS,QAAQ,kBAAkB;AACxD,WAAO;AAAA,EACT;AAGA,uBAAqB,MAAM,oBAAoB;AAE/C,mBAAiB,qBAAqBA,WAAU,GAAG,WAAW,GAAG;AAAA,IAC/D,QAAQ;AAAA,MACN,SAAS;AAAA,QACP,eAAe,UAAU,kBAAkB;AAAA,MAC7C;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,eAAe,SAAS,QAAQ,kBAAkB;AAExD,SAAO;AACT;;;AG7DA,SAAS,cAAAC,aAAY,gBAAAC,qBAAoB;AACzC,SAAS,QAAAC,OAAM,eAAe;AAC9B,SAAS,SAAS;;;ACFlB,YAAY,UAAU;AAIf,IAAM,iBAAiC;AAAA,EAC5C;AAAA,EAAU;AAAA,EAAkB;AAAA,EAAc;AAAA,EAAiB;AAAA,EAAU;AAAA,EAAW;AAClF;;;ADDA,IAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACvC,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC3C,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC5C,QAAQ,EAAE,OAAO,EAAE,QAAQ,MAAM;AAAA,EACjC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACnC,QAAQ,EAAE,KAAK,cAAuC,EAAE,SAAS;AAAA,EACjE,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,UAAU,EAAE,OAAO,EAAE,SAAS;AAChC,CAAC;AAID,IAAM,mBAAmB,CAAC,mBAAmB,eAAe;AAE5D,SAAS,eAAe,WAAmB,QAAQ,IAAI,GAAkB;AACvE,MAAI,aAAa;AAEjB,SAAO,eAAe,QAAQ,UAAU,GAAG;AACzC,eAAW,YAAY,kBAAkB;AACvC,YAAM,aAAaC,MAAK,YAAY,QAAQ;AAC5C,UAAIC,YAAW,UAAU,GAAG;AAC1B,eAAO;AAAA,MACT;AAAA,IACF;AACA,iBAAa,QAAQ,UAAU;AAAA,EACjC;AAEA,SAAO;AACT;AAEO,SAAS,aAA4B;AAC1C,QAAM,aAAa,eAAe;AAElC,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAUC,cAAa,YAAY,OAAO;AAChD,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO,aAAa,MAAM,MAAM;AAAA,EAClC,SAAS,OAAO;AACd,QAAI,iBAAiB,EAAE,UAAU;AAC/B,cAAQ,MAAM,wBAAwB,MAAM,MAAM;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AACF;","names":["DEFAULT_API_URL","getApiUrl","existsSync","readFileSync","join","join","existsSync","readFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envmanager-cli/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "CLI for EnvManager - secure environment variable management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",