infra-kit 0.1.72 → 0.1.74
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/cli.js +29 -15
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +32 -19
- package/dist/mcp.js.map +4 -4
- package/package.json +5 -5
- package/src/commands/doctor/doctor.ts +105 -0
- package/src/commands/doctor/index.ts +1 -0
- package/src/commands/env-clear/env-clear.ts +99 -0
- package/src/commands/env-clear/index.ts +1 -0
- package/src/commands/env-list/env-list.ts +50 -0
- package/src/commands/env-list/index.ts +1 -0
- package/src/commands/env-load/env-load.ts +94 -0
- package/src/commands/env-load/index.ts +1 -0
- package/src/commands/env-status/env-status.ts +97 -0
- package/src/commands/env-status/index.ts +1 -0
- package/src/entry/cli.ts +40 -3
- package/src/integrations/doppler/doppler-cli-auth.ts +25 -0
- package/src/integrations/doppler/doppler-project.ts +23 -0
- package/src/integrations/doppler/index.ts +2 -0
- package/src/lib/constants.ts +18 -0
- package/src/lib/load-env/load-env.ts +1 -0
- package/src/mcp/tools/index.ts +8 -0
- package/src/integrations/acli/acli-auth/acli-auth.ts +0 -25
- package/src/integrations/acli/acli-auth/index.ts +0 -1
- package/src/integrations/acli/index.ts +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infra-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.74",
|
|
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.
|
|
34
|
-
"@inquirer/confirm": "^6.0.
|
|
35
|
-
"@inquirer/select": "^5.1.
|
|
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.
|
|
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,99 @@
|
|
|
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 type { EnvCache } from 'src/lib/constants'
|
|
7
|
+
import {
|
|
8
|
+
ENV_CACHE_DIR,
|
|
9
|
+
ENV_CACHE_FILE,
|
|
10
|
+
INFRA_KIT_ENV_CONFIG_VAR,
|
|
11
|
+
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
12
|
+
INFRA_KIT_ENV_PROJECT_VAR,
|
|
13
|
+
} from 'src/lib/constants'
|
|
14
|
+
import { logger } from 'src/lib/logger'
|
|
15
|
+
import type { ToolsExecutionResult } from 'src/types'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Clear previously loaded environment variables. Usage: source <(infra-kit env-clear)
|
|
19
|
+
*/
|
|
20
|
+
export const envClear = async (): Promise<ToolsExecutionResult> => {
|
|
21
|
+
const cachePath = path.join(ENV_CACHE_DIR, ENV_CACHE_FILE)
|
|
22
|
+
|
|
23
|
+
if (!process.env[INFRA_KIT_ENV_CONFIG_VAR]) {
|
|
24
|
+
logger.error('No loaded environment found. Run `env-load` first.')
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: 'No loaded environment found. Run `env-load` first.',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const cache: EnvCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'))
|
|
37
|
+
const varNames = cache.varNames
|
|
38
|
+
|
|
39
|
+
// Only unset vars that are actually set in the current session
|
|
40
|
+
const activeVars = varNames.filter((v) => {
|
|
41
|
+
return v in process.env
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
if (activeVars.length === 0) {
|
|
45
|
+
fs.unlinkSync(cachePath)
|
|
46
|
+
logger.info('Current session is clear, no environment variables to unset.')
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: 'Current session is clear, no environment variables to unset.',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Output unset statements for shell sourcing
|
|
59
|
+
for (const varName of activeVars) {
|
|
60
|
+
process.stdout.write(`unset ${varName}\n`)
|
|
61
|
+
}
|
|
62
|
+
process.stdout.write(`unset ${INFRA_KIT_ENV_CONFIG_VAR}\n`)
|
|
63
|
+
process.stdout.write(`unset ${INFRA_KIT_ENV_PROJECT_VAR}\n`)
|
|
64
|
+
process.stdout.write(`unset ${INFRA_KIT_ENV_LOADED_AT_VAR}\n`)
|
|
65
|
+
|
|
66
|
+
// Remove cache file
|
|
67
|
+
fs.unlinkSync(cachePath)
|
|
68
|
+
|
|
69
|
+
logger.info(`Cleared ${activeVars.length} environment variables`)
|
|
70
|
+
|
|
71
|
+
const structuredContent = {
|
|
72
|
+
variableCount: activeVars.length,
|
|
73
|
+
unsetStatements: activeVars.map((v) => {
|
|
74
|
+
return `unset ${v}`
|
|
75
|
+
}),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: JSON.stringify(structuredContent, null, 2),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
structuredContent,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MCP Tool Registration
|
|
90
|
+
export const envClearMcpTool = {
|
|
91
|
+
name: 'env-clear',
|
|
92
|
+
description: 'Clear previously loaded environment variables. Usage: source <(infra-kit env-clear)',
|
|
93
|
+
inputSchema: {},
|
|
94
|
+
outputSchema: {
|
|
95
|
+
variableCount: z.number().describe('Number of variables cleared'),
|
|
96
|
+
unsetStatements: z.array(z.string()).describe('Unset statements generated'),
|
|
97
|
+
},
|
|
98
|
+
handler: envClear,
|
|
99
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { envClear, envClearMcpTool } from './env-clear'
|
|
@@ -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,94 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
import { $ } from 'zx'
|
|
6
|
+
|
|
7
|
+
import { validateDopplerCliAndAuth } from 'src/integrations/doppler'
|
|
8
|
+
import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
|
|
9
|
+
import type { EnvCache } from 'src/lib/constants'
|
|
10
|
+
import {
|
|
11
|
+
ENV_CACHE_DIR,
|
|
12
|
+
ENV_CACHE_FILE,
|
|
13
|
+
INFRA_KIT_ENV_CONFIG_VAR,
|
|
14
|
+
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
15
|
+
INFRA_KIT_ENV_PROJECT_VAR,
|
|
16
|
+
} from 'src/lib/constants'
|
|
17
|
+
import { logger } from 'src/lib/logger'
|
|
18
|
+
import type { ToolsExecutionResult } from 'src/types'
|
|
19
|
+
|
|
20
|
+
interface EnvLoadArgs {
|
|
21
|
+
config: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load environment variables from Doppler for the given config
|
|
26
|
+
*/
|
|
27
|
+
export const envLoad = async (args: EnvLoadArgs): Promise<ToolsExecutionResult> => {
|
|
28
|
+
await validateDopplerCliAndAuth()
|
|
29
|
+
|
|
30
|
+
const { config } = args
|
|
31
|
+
|
|
32
|
+
const project = await getDopplerProject()
|
|
33
|
+
|
|
34
|
+
const result = await $`doppler secrets download --no-file --format env --project ${project} --config ${config}`
|
|
35
|
+
const envContent = result.stdout.trim()
|
|
36
|
+
|
|
37
|
+
// Output shell-sourceable format
|
|
38
|
+
process.stdout.write('set -a\n')
|
|
39
|
+
process.stdout.write(`${envContent}\n`)
|
|
40
|
+
process.stdout.write('set +a\n')
|
|
41
|
+
process.stdout.write(`export ${INFRA_KIT_ENV_CONFIG_VAR}=${config}\n`)
|
|
42
|
+
process.stdout.write(`export ${INFRA_KIT_ENV_PROJECT_VAR}=${project}\n`)
|
|
43
|
+
process.stdout.write(`export ${INFRA_KIT_ENV_LOADED_AT_VAR}=${new Date().toISOString()}\n`)
|
|
44
|
+
|
|
45
|
+
// Extract variable names and save to cache
|
|
46
|
+
const varNames = envContent
|
|
47
|
+
.split('\n')
|
|
48
|
+
.filter((line) => {
|
|
49
|
+
return line.includes('=')
|
|
50
|
+
})
|
|
51
|
+
.map((line) => {
|
|
52
|
+
return line.split('=')[0]!
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const cachePath = path.join(ENV_CACHE_DIR, ENV_CACHE_FILE)
|
|
56
|
+
|
|
57
|
+
const cache: EnvCache = { config, loadedAt: new Date().toISOString(), varNames }
|
|
58
|
+
|
|
59
|
+
fs.mkdirSync(ENV_CACHE_DIR, { recursive: true })
|
|
60
|
+
fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2))
|
|
61
|
+
|
|
62
|
+
logger.info(`Loaded ${varNames.length} variables from ${project}/${config}`)
|
|
63
|
+
|
|
64
|
+
const structuredContent = {
|
|
65
|
+
variableCount: varNames.length,
|
|
66
|
+
project,
|
|
67
|
+
config,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: JSON.stringify(structuredContent, null, 2),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
structuredContent,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MCP Tool Registration
|
|
82
|
+
export const envLoadMcpTool = {
|
|
83
|
+
name: 'env-load',
|
|
84
|
+
description: 'Load environment variables from Doppler for a given config. Usage: source <(infra-kit env-load -c dev)',
|
|
85
|
+
inputSchema: {
|
|
86
|
+
config: z.string().describe('Environment config name to load (e.g. dev, arthur, renana)'),
|
|
87
|
+
},
|
|
88
|
+
outputSchema: {
|
|
89
|
+
variableCount: z.number().describe('Number of variables loaded'),
|
|
90
|
+
project: z.string().describe('Doppler project name'),
|
|
91
|
+
config: z.string().describe('Doppler config name'),
|
|
92
|
+
},
|
|
93
|
+
handler: envLoad,
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { envLoad, envLoadMcpTool } from './env-load'
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { validateDopplerCliAndAuth } from 'src/integrations/doppler'
|
|
7
|
+
import { getDopplerProject } from 'src/integrations/doppler/doppler-project'
|
|
8
|
+
import type { EnvCache } from 'src/lib/constants'
|
|
9
|
+
import {
|
|
10
|
+
ENV_CACHE_DIR,
|
|
11
|
+
ENV_CACHE_FILE,
|
|
12
|
+
ENVs,
|
|
13
|
+
INFRA_KIT_ENV_CONFIG_VAR,
|
|
14
|
+
INFRA_KIT_ENV_LOADED_AT_VAR,
|
|
15
|
+
INFRA_KIT_ENV_PROJECT_VAR,
|
|
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
|
|
34
|
+
const cachePath = path.join(ENV_CACHE_DIR, ENV_CACHE_FILE)
|
|
35
|
+
|
|
36
|
+
let sessionLoadedCount = 0
|
|
37
|
+
let sessionTotalCount = 0
|
|
38
|
+
const sessionConfig = process.env[INFRA_KIT_ENV_CONFIG_VAR] ?? null
|
|
39
|
+
const sessionProject = process.env[INFRA_KIT_ENV_PROJECT_VAR] ?? null
|
|
40
|
+
const sessionLoadedAt = process.env[INFRA_KIT_ENV_LOADED_AT_VAR] ?? null
|
|
41
|
+
|
|
42
|
+
if (sessionConfig) {
|
|
43
|
+
if (fs.existsSync(cachePath)) {
|
|
44
|
+
const cache: EnvCache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'))
|
|
45
|
+
|
|
46
|
+
sessionTotalCount = cache.varNames.length
|
|
47
|
+
sessionLoadedCount = cache.varNames.filter((v) => {
|
|
48
|
+
return v in process.env
|
|
49
|
+
}).length
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logger.info(
|
|
53
|
+
` Session: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (project: ${sessionProject}, config: ${sessionConfig}, loaded at: ${sessionLoadedAt})`,
|
|
54
|
+
)
|
|
55
|
+
} else {
|
|
56
|
+
logger.info(' Session: no env loaded')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const structuredContent = {
|
|
60
|
+
authenticated: true,
|
|
61
|
+
project,
|
|
62
|
+
configs: ENVs,
|
|
63
|
+
sessionLoadedCount,
|
|
64
|
+
sessionTotalCount,
|
|
65
|
+
sessionConfig,
|
|
66
|
+
sessionProject,
|
|
67
|
+
sessionLoadedAt,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: JSON.stringify(structuredContent, null, 2),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
structuredContent,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MCP Tool Registration
|
|
82
|
+
export const envStatusMcpTool = {
|
|
83
|
+
name: 'env-status',
|
|
84
|
+
description: 'Show Doppler authentication status and detected project info',
|
|
85
|
+
inputSchema: {},
|
|
86
|
+
outputSchema: {
|
|
87
|
+
authenticated: z.boolean().describe('Whether the user is authenticated'),
|
|
88
|
+
project: z.string().describe('Detected Doppler project name'),
|
|
89
|
+
configs: z.array(z.string()).describe('Available environment configs'),
|
|
90
|
+
sessionLoadedCount: z.number().describe('Number of cached vars active in the current session'),
|
|
91
|
+
sessionTotalCount: z.number().describe('Total number of cached var names'),
|
|
92
|
+
sessionConfig: z.string().nullable().describe('Doppler config name of the loaded session'),
|
|
93
|
+
sessionProject: z.string().nullable().describe('Doppler project name of the loaded session'),
|
|
94
|
+
sessionLoadedAt: z.string().nullable().describe('ISO 8601 timestamp of when the env was loaded'),
|
|
95
|
+
},
|
|
96
|
+
handler: envStatus,
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { envStatus, envStatusMcpTool } from './env-status'
|
package/src/entry/cli.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
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 { envList } from 'src/commands/env-list'
|
|
6
|
+
import { envLoad } from 'src/commands/env-load'
|
|
7
|
+
import { envStatus } from 'src/commands/env-status'
|
|
3
8
|
// Commands
|
|
4
9
|
import { ghMergeDev } from 'src/commands/gh-merge-dev'
|
|
5
10
|
import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
|
|
@@ -13,8 +18,6 @@ import { worktreesAdd } from 'src/commands/worktrees-add'
|
|
|
13
18
|
import { worktreesList } from 'src/commands/worktrees-list'
|
|
14
19
|
import { worktreesRemove } from 'src/commands/worktrees-remove'
|
|
15
20
|
import { worktreesSync } from 'src/commands/worktrees-sync'
|
|
16
|
-
// Integrations
|
|
17
|
-
import { validateGitHubCliAndAuth } from 'src/integrations/gh'
|
|
18
21
|
import { loadEnvFromGitRoot } from 'src/lib/load-env'
|
|
19
22
|
|
|
20
23
|
// Load .env before anything else
|
|
@@ -153,6 +156,40 @@ program
|
|
|
153
156
|
await worktreesRemove({ confirmedCommand: options.yes, all: options.all })
|
|
154
157
|
})
|
|
155
158
|
|
|
156
|
-
|
|
159
|
+
program
|
|
160
|
+
.command('doctor')
|
|
161
|
+
.description('Check installation and authentication status of gh and doppler CLIs')
|
|
162
|
+
.action(async () => {
|
|
163
|
+
await doctor()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
program
|
|
167
|
+
.command('env-status')
|
|
168
|
+
.description('Show Doppler authentication status and detected project info')
|
|
169
|
+
.action(async () => {
|
|
170
|
+
await envStatus()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
program
|
|
174
|
+
.command('env-list')
|
|
175
|
+
.description('List available Doppler configs for the detected project')
|
|
176
|
+
.action(async () => {
|
|
177
|
+
await envList()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
program
|
|
181
|
+
.command('env-load')
|
|
182
|
+
.description('Load environment variables from Doppler. Usage: source <(infra-kit env-load -c dev)')
|
|
183
|
+
.requiredOption('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')
|
|
184
|
+
.action(async (options) => {
|
|
185
|
+
await envLoad({ config: options.config })
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
program
|
|
189
|
+
.command('env-clear')
|
|
190
|
+
.description('Clear previously loaded environment variables. Usage: source <(infra-kit env-clear)')
|
|
191
|
+
.action(async () => {
|
|
192
|
+
await envClear()
|
|
193
|
+
})
|
|
157
194
|
|
|
158
195
|
program.parse()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import { $ } from 'zx'
|
|
3
|
+
|
|
4
|
+
import { logger } from 'src/lib/logger'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validate Doppler CLI installation and authentication status and throw an error if not valid
|
|
8
|
+
*/
|
|
9
|
+
export const validateDopplerCliAndAuth = async () => {
|
|
10
|
+
try {
|
|
11
|
+
await $`doppler --version`
|
|
12
|
+
} catch (error: unknown) {
|
|
13
|
+
logger.error({ error }, 'Error: Doppler CLI is not installed.')
|
|
14
|
+
logger.error('Please install it from: https://docs.doppler.com/docs/install-cli')
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await $`doppler me`
|
|
20
|
+
} catch (error: unknown) {
|
|
21
|
+
logger.error({ error }, 'Error: Doppler CLI is not authenticated.')
|
|
22
|
+
logger.error('Please authenticate by running: doppler login')
|
|
23
|
+
process.exit(1)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { DOPPLER_PROJECT_MAP } from 'src/lib/constants'
|
|
4
|
+
import { getProjectRoot } from 'src/lib/git-utils'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve Doppler project name from the current working directory
|
|
8
|
+
*/
|
|
9
|
+
export const getDopplerProject = async (): Promise<string> => {
|
|
10
|
+
const projectRoot = await getProjectRoot()
|
|
11
|
+
|
|
12
|
+
const dirName = path.basename(projectRoot)
|
|
13
|
+
const dopplerProject = DOPPLER_PROJECT_MAP[dirName]
|
|
14
|
+
|
|
15
|
+
if (!dopplerProject) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Could not determine Doppler project for directory "${dirName}". ` +
|
|
18
|
+
`Expected one of: ${Object.keys(DOPPLER_PROJECT_MAP).join(', ')}`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return dopplerProject
|
|
23
|
+
}
|
package/src/lib/constants.ts
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const ENVs = ['dev', 'arthur', 'renana', 'roman', 'eliran', 'oriana']
|
|
5
5
|
|
|
6
|
+
export const DOPPLER_PROJECT_MAP: Record<string, string> = {
|
|
7
|
+
'hulyo-monorepo': 'hulyo',
|
|
8
|
+
'travelist-monorepo': 'travelist',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ENV_CACHE_DIR = './node_modules/.cache/infra-kit'
|
|
12
|
+
export const ENV_CACHE_FILE = 'env-cache.json'
|
|
13
|
+
|
|
14
|
+
export const INFRA_KIT_ENV_CONFIG_VAR = 'INFRA_KIT_ENV_CONFIG'
|
|
15
|
+
export const INFRA_KIT_ENV_PROJECT_VAR = 'INFRA_KIT_ENV_PROJECT'
|
|
16
|
+
export const INFRA_KIT_ENV_LOADED_AT_VAR = 'INFRA_KIT_ENV_LOADED_AT'
|
|
17
|
+
|
|
18
|
+
export interface EnvCache {
|
|
19
|
+
config: string
|
|
20
|
+
loadedAt: string
|
|
21
|
+
varNames: string[]
|
|
22
|
+
}
|
|
23
|
+
|
|
6
24
|
export const WORKTREES_DIR_SUFFIX = '-worktrees'
|
|
7
25
|
// eslint-disable-next-line sonarjs/publicly-writable-directories
|
|
8
26
|
export const LOG_FILE_PATH = '/tmp/mcp-infra-kit.log'
|
|
@@ -15,6 +15,7 @@ export const loadEnvFromGitRoot = async (): Promise<void> => {
|
|
|
15
15
|
|
|
16
16
|
const gitRoot = await getProjectRoot()
|
|
17
17
|
|
|
18
|
+
// TODO: remove this after new env managemement
|
|
18
19
|
config({ path: resolve(gitRoot, '.env'), quiet: true })
|
|
19
20
|
|
|
20
21
|
logger.info('Loaded .env file from git repository root')
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
2
|
|
|
3
|
+
import { envClearMcpTool } from 'src/commands/env-clear'
|
|
4
|
+
import { envListMcpTool } from 'src/commands/env-list'
|
|
5
|
+
import { envLoadMcpTool } from 'src/commands/env-load'
|
|
6
|
+
import { envStatusMcpTool } from 'src/commands/env-status'
|
|
3
7
|
import { ghMergeDevMcpTool } from 'src/commands/gh-merge-dev'
|
|
4
8
|
import { ghReleaseDeliverMcpTool } from 'src/commands/gh-release-deliver'
|
|
5
9
|
import { ghReleaseDeployAllMcpTool } from 'src/commands/gh-release-deploy-all'
|
|
@@ -15,6 +19,10 @@ import { worktreesSyncMcpTool } from 'src/commands/worktrees-sync'
|
|
|
15
19
|
import { createToolHandler } from 'src/lib/tool-handler'
|
|
16
20
|
|
|
17
21
|
const tools = [
|
|
22
|
+
envStatusMcpTool,
|
|
23
|
+
envListMcpTool,
|
|
24
|
+
envLoadMcpTool,
|
|
25
|
+
envClearMcpTool,
|
|
18
26
|
ghMergeDevMcpTool,
|
|
19
27
|
releaseCreateMcpTool,
|
|
20
28
|
releaseCreateBatchMcpTool,
|