infra-kit 0.1.72 → 0.1.75

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.72",
4
+ "version": "0.1.75",
5
5
  "description": "infra-kit",
6
6
  "main": "dist/cli.js",
7
7
  "module": "dist/cli.js",
@@ -30,9 +30,9 @@
30
30
  "fix": "pnpm run prettier-fix && pnpm run eslint-fix && pnpm run qa"
31
31
  },
32
32
  "dependencies": {
33
- "@inquirer/checkbox": "^5.1.0",
34
- "@inquirer/confirm": "^6.0.8",
35
- "@inquirer/select": "^5.1.0",
33
+ "@inquirer/checkbox": "^5.1.2",
34
+ "@inquirer/confirm": "^6.0.10",
35
+ "@inquirer/select": "^5.1.2",
36
36
  "@modelcontextprotocol/sdk": "^1.27.1",
37
37
  "commander": "^14.0.3",
38
38
  "dotenv": "^17.3.1",
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@pkg/eslint-config": "workspace:*",
47
47
  "@pkg/vitest-config": "workspace:*",
48
- "esbuild": "^0.27.3",
48
+ "esbuild": "^0.27.4",
49
49
  "typescript": "^5.9.3"
50
50
  }
51
51
  }
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod'
2
+ import { $ } from 'zx'
3
+
4
+ import { logger } from 'src/lib/logger'
5
+ import type { ToolsExecutionResult } from 'src/types'
6
+
7
+ interface CheckResult {
8
+ name: string
9
+ status: 'pass' | 'fail'
10
+ message: string
11
+ }
12
+
13
+ const checkCommand = async (
14
+ name: string,
15
+ command: string[],
16
+ successMsg: string,
17
+ failMsg: string,
18
+ ): Promise<CheckResult> => {
19
+ try {
20
+ await $`${command}`
21
+
22
+ return { name, status: 'pass', message: successMsg }
23
+ } catch {
24
+ return { name, status: 'fail', message: failMsg }
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Check installation and authentication status of gh and doppler CLIs
30
+ */
31
+ export const doctor = async (): Promise<ToolsExecutionResult> => {
32
+ const checks: CheckResult[] = await Promise.all([
33
+ checkCommand(
34
+ 'gh installed',
35
+ ['gh', '--version'],
36
+ 'GitHub CLI is installed',
37
+ 'GitHub CLI is not installed. Install from: https://cli.github.com/',
38
+ ),
39
+ checkCommand(
40
+ 'gh authenticated',
41
+ ['gh', 'auth', 'status'],
42
+ 'GitHub CLI is authenticated',
43
+ 'GitHub CLI is not authenticated. Run: gh auth login',
44
+ ),
45
+ checkCommand(
46
+ 'doppler installed',
47
+ ['doppler', '--version'],
48
+ 'Doppler CLI is installed',
49
+ 'Doppler CLI is not installed. Install from: https://docs.doppler.com/docs/install-cli',
50
+ ),
51
+ checkCommand(
52
+ 'doppler authenticated',
53
+ ['doppler', 'me'],
54
+ 'Doppler CLI is authenticated',
55
+ 'Doppler CLI is not authenticated. Run: doppler login',
56
+ ),
57
+ ])
58
+
59
+ logger.info('Doctor check results:\n')
60
+
61
+ for (const check of checks) {
62
+ const icon = check.status === 'pass' ? '[PASS]' : '[FAIL]'
63
+
64
+ logger.info(` ${icon} ${check.name}: ${check.message}`)
65
+ }
66
+
67
+ const structuredContent = {
68
+ checks: checks.map((c) => {
69
+ return { name: c.name, status: c.status, message: c.message }
70
+ }),
71
+ allPassed: checks.every((c) => {
72
+ return c.status === 'pass'
73
+ }),
74
+ }
75
+
76
+ return {
77
+ content: [
78
+ {
79
+ type: 'text',
80
+ text: JSON.stringify(structuredContent, null, 2),
81
+ },
82
+ ],
83
+ structuredContent,
84
+ }
85
+ }
86
+
87
+ // MCP Tool Registration
88
+ export const doctorMcpTool = {
89
+ name: 'doctor',
90
+ description: 'Check installation and authentication status of gh and doppler CLIs',
91
+ inputSchema: {},
92
+ outputSchema: {
93
+ checks: z
94
+ .array(
95
+ z.object({
96
+ name: z.string().describe('Name of the check'),
97
+ status: z.enum(['pass', 'fail']).describe('Check result'),
98
+ message: z.string().describe('Details about the check result'),
99
+ }),
100
+ )
101
+ .describe('List of all check results'),
102
+ allPassed: z.boolean().describe('Whether all checks passed'),
103
+ },
104
+ handler: doctor,
105
+ }
@@ -0,0 +1 @@
1
+ export { doctor, doctorMcpTool } from './doctor'
@@ -0,0 +1,91 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import { z } from 'zod'
5
+
6
+ import {
7
+ ENV_CLEAR_FILE,
8
+ ENV_LOAD_FILE,
9
+ getSessionCacheDir,
10
+ INFRA_KIT_ENV_CONFIG_VAR,
11
+ INFRA_KIT_ENV_LOADED_AT_VAR,
12
+ INFRA_KIT_ENV_PROJECT_VAR,
13
+ parseVarNamesFromEnvFile,
14
+ } from 'src/lib/constants'
15
+ import { logger } from 'src/lib/logger'
16
+ import type { ToolsExecutionResult } from 'src/types'
17
+
18
+ /**
19
+ * Clear previously loaded environment variables. Usage: env-clear (after running eval "$(infra-kit env-init)" in shell)
20
+ */
21
+ export const envClear = async (): Promise<ToolsExecutionResult> => {
22
+ const cacheDir = getSessionCacheDir()
23
+ const envLoadPath = path.join(cacheDir, ENV_LOAD_FILE)
24
+
25
+ if (!fs.existsSync(envLoadPath)) {
26
+ logger.error('No loaded environment found. Run `env-load` first.')
27
+
28
+ return {
29
+ content: [
30
+ {
31
+ type: 'text',
32
+ text: 'No loaded environment found. Run `env-load` first.',
33
+ },
34
+ ],
35
+ }
36
+ }
37
+
38
+ const varNames = parseVarNamesFromEnvFile(envLoadPath)
39
+
40
+ // Build unset script
41
+ const unsetLines = [
42
+ ...varNames.map((v) => {
43
+ return `unset ${v}`
44
+ }),
45
+ `unset ${INFRA_KIT_ENV_CONFIG_VAR}`,
46
+ `unset ${INFRA_KIT_ENV_PROJECT_VAR}`,
47
+ `unset ${INFRA_KIT_ENV_LOADED_AT_VAR}`,
48
+ ]
49
+
50
+ // Write unset script to cache
51
+ const clearFilePath = path.resolve(cacheDir, ENV_CLEAR_FILE)
52
+
53
+ fs.mkdirSync(cacheDir, { recursive: true })
54
+ fs.writeFileSync(clearFilePath, `${unsetLines.join('\n')}\n`)
55
+
56
+ // REQUIRED
57
+ process.stdout.write(`${clearFilePath}\n`)
58
+
59
+ // Remove env load file so env-clear can detect "no env loaded" next time
60
+ fs.unlinkSync(envLoadPath)
61
+
62
+ logger.info(`Cleared ${varNames.length} environment variables`)
63
+
64
+ const structuredContent = {
65
+ variableCount: varNames.length,
66
+ unsetStatements: unsetLines,
67
+ }
68
+
69
+ return {
70
+ content: [
71
+ {
72
+ type: 'text',
73
+ text: JSON.stringify(structuredContent, null, 2),
74
+ },
75
+ ],
76
+ structuredContent,
77
+ }
78
+ }
79
+
80
+ // MCP Tool Registration
81
+ export const envClearMcpTool = {
82
+ name: 'env-clear',
83
+ description:
84
+ 'Clear previously loaded environment variables. Usage: env-clear (after running eval "$(infra-kit env-init)" in shell)',
85
+ inputSchema: {},
86
+ outputSchema: {
87
+ variableCount: z.number().describe('Number of variables cleared'),
88
+ unsetStatements: z.array(z.string()).describe('Unset statements generated'),
89
+ },
90
+ handler: envClear,
91
+ }
@@ -0,0 +1 @@
1
+ export { envClear, envClearMcpTool } from './env-clear'
@@ -0,0 +1,83 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import process from 'node:process'
5
+
6
+ import { logger } from 'src/lib/logger'
7
+
8
+ const MARKER_COMMENT = '# infra-kit shell functions'
9
+
10
+ /**
11
+ * Append infra-kit shell functions directly to .zshrc.
12
+ */
13
+ export const envInit = async (): Promise<void> => {
14
+ const zshrcPath = path.join(os.homedir(), '.zshrc')
15
+ const binPath = getBinPath()
16
+
17
+ if (!fs.existsSync(binPath)) {
18
+ logger.error(`Could not find infra-kit binary at ${binPath}`)
19
+
20
+ return
21
+ }
22
+
23
+ const shellBlock = buildShellBlock(binPath)
24
+
25
+ if (fs.existsSync(zshrcPath)) {
26
+ const content = fs.readFileSync(zshrcPath, 'utf-8')
27
+ const cleaned = removeExistingBlock(content)
28
+
29
+ fs.writeFileSync(zshrcPath, cleaned)
30
+ }
31
+
32
+ fs.appendFileSync(zshrcPath, `\n${shellBlock}\n`)
33
+ logger.info(`Added infra-kit shell functions to ${zshrcPath}`)
34
+ logger.info('Run `source ~/.zshrc` or open a new terminal to activate.')
35
+ }
36
+
37
+ const getBinPath = (): string => {
38
+ // resolve the absolute path to the infra-kit binary
39
+ return path.resolve(path.join(path.dirname(process.argv[1]!), 'cli.js'))
40
+ }
41
+
42
+ const isBlockLine = (line: string): boolean => {
43
+ return (
44
+ line.startsWith('#') ||
45
+ line.startsWith('env-load') ||
46
+ line.startsWith('env-clear') ||
47
+ line.startsWith('if ') ||
48
+ line.startsWith(' export INFRA_KIT_SESSION') ||
49
+ line.startsWith('fi')
50
+ )
51
+ }
52
+
53
+ const removeExistingBlock = (content: string): string => {
54
+ const markerIdx = content.indexOf(MARKER_COMMENT)
55
+
56
+ if (markerIdx === -1) return content
57
+
58
+ const before = content.slice(0, markerIdx).replace(/\n+$/, '')
59
+ const afterLines = content.slice(markerIdx).split('\n')
60
+
61
+ let i = 0
62
+
63
+ while (i < afterLines.length && isBlockLine(afterLines[i]!)) {
64
+ i++
65
+ }
66
+
67
+ const remaining = afterLines.slice(i).join('\n')
68
+
69
+ return before + (remaining ? `\n${remaining}` : '')
70
+ }
71
+
72
+ const buildShellBlock = (binPath: string): string => {
73
+ const runCmd = `node ${binPath}`
74
+
75
+ return [
76
+ MARKER_COMMENT,
77
+ 'if [[ -z "${INFRA_KIT_SESSION}" ]]; then',
78
+ ' export INFRA_KIT_SESSION=$(head -c 4 /dev/urandom | xxd -p)',
79
+ 'fi',
80
+ `env-load() { local f; f=$(${runCmd} env-load "$@") && source "$f"; }`,
81
+ `env-clear() { local f; f=$(${runCmd} env-clear) && source "$f"; }`,
82
+ ].join('\n')
83
+ }
@@ -0,0 +1 @@
1
+ export { envInit } from './env-init'
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod'
2
+
3
+ import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
4
+ import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
5
+ import { ENVs } from 'src/lib/constants'
6
+ import { logger } from 'src/lib/logger'
7
+ import type { ToolsExecutionResult } from 'src/types'
8
+
9
+ /**
10
+ * List available Doppler configs for the detected project
11
+ */
12
+ export const envList = async (): Promise<ToolsExecutionResult> => {
13
+ await validateDopplerCliAndAuth()
14
+
15
+ const project = await getDopplerProject()
16
+
17
+ logger.info(`Doppler Project: ${project}\n`)
18
+ logger.info('Available Configs:')
19
+
20
+ for (const env of ENVs) {
21
+ logger.info(` - ${env}`)
22
+ }
23
+
24
+ const structuredContent = {
25
+ project,
26
+ configs: ENVs,
27
+ }
28
+
29
+ return {
30
+ content: [
31
+ {
32
+ type: 'text',
33
+ text: JSON.stringify(structuredContent, null, 2),
34
+ },
35
+ ],
36
+ structuredContent,
37
+ }
38
+ }
39
+
40
+ // MCP Tool Registration
41
+ export const envListMcpTool = {
42
+ name: 'env-list',
43
+ description: 'List available Doppler configs for the detected project',
44
+ inputSchema: {},
45
+ outputSchema: {
46
+ project: z.string().describe('Detected Doppler project name'),
47
+ configs: z.array(z.string()).describe('Available environment configs'),
48
+ },
49
+ handler: envList,
50
+ }
@@ -0,0 +1 @@
1
+ export { envList, envListMcpTool } from './env-list'
@@ -0,0 +1,120 @@
1
+ import select from '@inquirer/select'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import process from 'node:process'
5
+ import { z } from 'zod'
6
+ import { $ } from 'zx'
7
+
8
+ import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
9
+ import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
10
+ import { commandEcho } from 'src/lib/command-echo'
11
+ import {
12
+ ENV_LOAD_FILE,
13
+ ENVs,
14
+ INFRA_KIT_ENV_CONFIG_VAR,
15
+ INFRA_KIT_ENV_LOADED_AT_VAR,
16
+ INFRA_KIT_ENV_PROJECT_VAR,
17
+ getSessionCacheDir,
18
+ } from 'src/lib/constants'
19
+ import { logger } from 'src/lib/logger'
20
+ import type { ToolsExecutionResult } from 'src/types'
21
+
22
+ interface EnvLoadArgs {
23
+ config?: string
24
+ quiet?: boolean
25
+ }
26
+
27
+ /**
28
+ * Load environment variables from Doppler for the given config
29
+ */
30
+ export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult> => {
31
+ await validateDopplerCliAndAuth()
32
+
33
+ const { config, quiet } = args
34
+
35
+ commandEcho.start('env-load')
36
+
37
+ let selectedConfig = ''
38
+
39
+ if (config) {
40
+ selectedConfig = config
41
+ } else {
42
+ commandEcho.setInteractive()
43
+ selectedConfig = await select({
44
+ message: 'Select environment config',
45
+ choices: ENVs.map((env) => {
46
+ return { name: env, value: env }
47
+ }),
48
+ })
49
+ }
50
+
51
+ commandEcho.addOption('--config', selectedConfig)
52
+
53
+ const project = await getDopplerProject()
54
+
55
+ $.quiet = true
56
+ const result =
57
+ await $`doppler secrets download --no-file --format env --project ${project} --config ${selectedConfig}`
58
+
59
+ $.quiet = false
60
+ const envContent = result.stdout.trim()
61
+
62
+ // Build env file content in dotenv format
63
+ const loadedAt = new Date().toISOString()
64
+ const envFileLines = [
65
+ 'set -a',
66
+ envContent,
67
+ `${INFRA_KIT_ENV_CONFIG_VAR}=${selectedConfig}`,
68
+ `${INFRA_KIT_ENV_PROJECT_VAR}=${project}`,
69
+ `${INFRA_KIT_ENV_LOADED_AT_VAR}=${loadedAt}`,
70
+ 'set +a',
71
+ ]
72
+
73
+ // Write env file to cache
74
+ const cacheDir = getSessionCacheDir()
75
+ const envFilePath = path.resolve(cacheDir, ENV_LOAD_FILE)
76
+
77
+ fs.mkdirSync(cacheDir, { recursive: true })
78
+ fs.writeFileSync(envFilePath, `${envFileLines.join('\n')}\n`)
79
+
80
+ // REQUIRED
81
+ process.stdout.write(`${envFilePath}\n`)
82
+
83
+ const varCount = envContent.split('\n').filter((line) => line.includes('=')).length
84
+
85
+ if (!quiet) {
86
+ logger.info(`Loaded ${varCount} variables from ${project}/${selectedConfig}`)
87
+ }
88
+
89
+ const structuredContent = {
90
+ variableCount: varCount,
91
+ project,
92
+ config: selectedConfig,
93
+ }
94
+
95
+ return {
96
+ content: [
97
+ {
98
+ type: 'text',
99
+ text: JSON.stringify(structuredContent, null, 2),
100
+ },
101
+ ],
102
+ structuredContent,
103
+ }
104
+ }
105
+
106
+ // MCP Tool Registration
107
+ export const envLoadMcpTool = {
108
+ name: 'env-load',
109
+ description:
110
+ 'Load environment variables from Doppler for a given config. Usage: env-load -c dev (after running eval "$(infra-kit env-init)" in shell)',
111
+ inputSchema: {
112
+ config: z.string().describe('Environment config name to load (e.g. dev, arthur, renana)'),
113
+ },
114
+ outputSchema: {
115
+ variableCount: z.number().describe('Number of variables loaded'),
116
+ project: z.string().describe('Doppler project name'),
117
+ config: z.string().describe('Doppler config name'),
118
+ },
119
+ handler: envLoad,
120
+ }
@@ -0,0 +1 @@
1
+ export { envLoad, envLoadMcpTool } from './env-load'
@@ -0,0 +1,103 @@
1
+ import path from 'node:path'
2
+ import process from 'node:process'
3
+ import { z } from 'zod'
4
+
5
+ import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
6
+ import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
7
+ import {
8
+ ENV_LOAD_FILE,
9
+ ENVs,
10
+ INFRA_KIT_ENV_CONFIG_VAR,
11
+ INFRA_KIT_ENV_LOADED_AT_VAR,
12
+ INFRA_KIT_ENV_PROJECT_VAR,
13
+ INFRA_KIT_SESSION_VAR,
14
+ getSessionCacheDir,
15
+ parseVarNamesFromEnvFile,
16
+ } from 'src/lib/constants'
17
+ import { logger } from 'src/lib/logger'
18
+ import type { ToolsExecutionResult } from 'src/types'
19
+
20
+ /**
21
+ * Show Doppler authentication status and detected project info
22
+ */
23
+ export const envStatus = async (): Promise<ToolsExecutionResult> => {
24
+ await validateDopplerCliAndAuth()
25
+
26
+ const project = await getDopplerProject()
27
+
28
+ logger.info('Doppler Environment Status:\n')
29
+ logger.info(` Authenticated: yes`)
30
+ logger.info(` Detected Project: ${project}`)
31
+ logger.info(` Available Configs: ${ENVs.join(', ')}`)
32
+
33
+ // Check session-loaded vars — getSessionCacheDir() throws if INFRA_KIT_SESSION is unset
34
+ const cacheDir = getSessionCacheDir()
35
+
36
+ const sessionId = process.env[INFRA_KIT_SESSION_VAR]!
37
+ const envLoadPath = path.join(cacheDir, ENV_LOAD_FILE)
38
+
39
+ let sessionLoadedCount = 0
40
+ let sessionTotalCount = 0
41
+ const sessionConfig = process.env[INFRA_KIT_ENV_CONFIG_VAR] ?? null
42
+ const sessionProject = process.env[INFRA_KIT_ENV_PROJECT_VAR] ?? null
43
+ const sessionLoadedAt = process.env[INFRA_KIT_ENV_LOADED_AT_VAR] ?? null
44
+
45
+ if (sessionConfig) {
46
+ const varNames = parseVarNamesFromEnvFile(envLoadPath)
47
+
48
+ if (varNames.length > 0) {
49
+ sessionTotalCount = varNames.length
50
+ sessionLoadedCount = varNames.filter((v) => {
51
+ return v in process.env
52
+ }).length
53
+ }
54
+
55
+ logger.info(
56
+ ` Session: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (project: ${sessionProject}, config: ${sessionConfig}, loaded at: ${sessionLoadedAt})`,
57
+ )
58
+ logger.info(` Session ID: ${sessionId}`)
59
+ } else {
60
+ logger.info(' Session: no env loaded')
61
+ }
62
+
63
+ const structuredContent = {
64
+ authenticated: true,
65
+ project,
66
+ configs: ENVs,
67
+ sessionId,
68
+ sessionLoadedCount,
69
+ sessionTotalCount,
70
+ sessionConfig,
71
+ sessionProject,
72
+ sessionLoadedAt,
73
+ }
74
+
75
+ return {
76
+ content: [
77
+ {
78
+ type: 'text',
79
+ text: JSON.stringify(structuredContent, null, 2),
80
+ },
81
+ ],
82
+ structuredContent,
83
+ }
84
+ }
85
+
86
+ // MCP Tool Registration
87
+ export const envStatusMcpTool = {
88
+ name: 'env-status',
89
+ description: 'Show Doppler authentication status and detected project info',
90
+ inputSchema: {},
91
+ outputSchema: {
92
+ authenticated: z.boolean().describe('Whether the user is authenticated'),
93
+ project: z.string().describe('Detected Doppler project name'),
94
+ configs: z.array(z.string()).describe('Available environment configs'),
95
+ sessionId: z.string().describe('Current terminal session ID'),
96
+ sessionLoadedCount: z.number().describe('Number of cached vars active in the current session'),
97
+ sessionTotalCount: z.number().describe('Total number of cached var names'),
98
+ sessionConfig: z.string().nullable().describe('Doppler config name of the loaded session'),
99
+ sessionProject: z.string().nullable().describe('Doppler project name of the loaded session'),
100
+ sessionLoadedAt: z.string().nullable().describe('ISO 8601 timestamp of when the env was loaded'),
101
+ },
102
+ handler: envStatus,
103
+ }
@@ -0,0 +1 @@
1
+ export { envStatus, envStatusMcpTool } from './env-status'
package/src/entry/cli.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import { Command } from 'commander'
2
2
 
3
+ import { doctor } from 'src/commands/doctor'
4
+ import { envClear } from 'src/commands/env-clear'
5
+ import { envInit } from 'src/commands/env-init'
6
+ import { envList } from 'src/commands/env-list'
7
+ import { envLoad } from 'src/commands/env-load'
8
+ import { envStatus } from 'src/commands/env-status'
3
9
  // Commands
4
10
  import { ghMergeDev } from 'src/commands/gh-merge-dev'
5
11
  import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
@@ -13,12 +19,6 @@ import { worktreesAdd } from 'src/commands/worktrees-add'
13
19
  import { worktreesList } from 'src/commands/worktrees-list'
14
20
  import { worktreesRemove } from 'src/commands/worktrees-remove'
15
21
  import { worktreesSync } from 'src/commands/worktrees-sync'
16
- // Integrations
17
- import { validateGitHubCliAndAuth } from 'src/integrations/gh'
18
- import { loadEnvFromGitRoot } from 'src/lib/load-env'
19
-
20
- // Load .env before anything else
21
- await loadEnvFromGitRoot()
22
22
 
23
23
  const program = new Command()
24
24
 
@@ -153,6 +153,48 @@ program
153
153
  await worktreesRemove({ confirmedCommand: options.yes, all: options.all })
154
154
  })
155
155
 
156
- await validateGitHubCliAndAuth()
156
+ program
157
+ .command('doctor')
158
+ .description('Check installation and authentication status of gh and doppler CLIs')
159
+ .action(async () => {
160
+ await doctor()
161
+ })
162
+
163
+ program
164
+ .command('env-status')
165
+ .description('Show Doppler authentication status and detected project info')
166
+ .action(async () => {
167
+ await envStatus()
168
+ })
169
+
170
+ program
171
+ .command('env-list')
172
+ .description('List available Doppler configs for the detected project')
173
+ .action(async () => {
174
+ await envList()
175
+ })
176
+
177
+ program
178
+ .command('env-init')
179
+ .description('Set up shell functions for env-load/env-clear in .zshrc')
180
+ .action(async () => {
181
+ await envInit()
182
+ })
183
+
184
+ program
185
+ .command('env-load')
186
+ .description('Load environment variables from Doppler. Usage: env-load -c dev (after shell init)')
187
+ .option('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')
188
+ .option('-q, --quiet', 'Suppress info logging')
189
+ .action(async (options) => {
190
+ await envLoad({ config: options.config, quiet: options.quiet })
191
+ })
192
+
193
+ program
194
+ .command('env-clear')
195
+ .description('Clear previously loaded environment variables. Usage: env-clear (after shell init)')
196
+ .action(async () => {
197
+ await envClear()
198
+ })
157
199
 
158
200
  program.parse()
package/src/entry/mcp.ts CHANGED
@@ -1,16 +1,11 @@
1
- /* eslint-disable antfu/no-top-level-await */
2
1
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
2
  import process from 'node:process'
4
3
 
5
4
  import { setupErrorHandlers } from 'src/lib/error-handlers'
6
- import { loadEnvFromGitRoot } from 'src/lib/load-env'
7
5
  import { initLoggerMcp } from 'src/lib/logger'
8
6
 
9
7
  import { createMcpServer } from '../mcp/server'
10
8
 
11
- // Load .env before anything else
12
- await loadEnvFromGitRoot()
13
-
14
9
  const logger = initLoggerMcp()
15
10
 
16
11
  const startServer = async () => {