Agent value exploration#4
Conversation
Co-authored-by: DealPatrol <DealPatrol@users.noreply.github.com>
Co-authored-by: DealPatrol <DealPatrol@users.noreply.github.com>
|
Cursor Agent can help with this pull request. Just |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds a Node-based “env setup agent” to help users discover required environment variables across the codebase/docs and generate a .env.local (or template) via guided prompts, plus documentation and package scripts to run it.
Changes:
- Introduces
scripts/env-agent.mjsto scan for env var usage, print a report, and optionally write an env file. - Adds
pnpm env:scan/pnpm env:setupscripts for easier usage. - Documents the agent in
README.mdandQUICK_START.md.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| scripts/env-agent.mjs | New CLI tool to scan env var usage and generate .env files. |
| package.json | Adds env:scan and env:setup scripts. |
| README.md | Documents how to run the env setup agent and what it does/doesn’t do. |
| QUICK_START.md | Mentions the optional helper command for env setup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const outputPath = path.resolve(rootDir, args.outputPath) | ||
| let values = args.templateOnly ? new Map() : await readExistingEnv(outputPath) | ||
|
|
||
| if (!args.nonInteractive && report.length > 0 && !args.templateOnly) { | ||
| values = await promptForValues(report, values, args.overwrite) | ||
| } | ||
|
|
||
| const fileContent = renderEnvFile(report, values, !args.templateOnly) | ||
| await fs.mkdir(path.dirname(outputPath), { recursive: true }) | ||
| await fs.writeFile(outputPath, fileContent, 'utf8') |
There was a problem hiding this comment.
--output is resolved and written without any guardrails, so running the script with a crafted path can overwrite arbitrary files outside the repo. Since this is a setup tool that users may run verbatim from docs, consider refusing to write outside process.cwd() by default (or requiring an explicit --allow-outside-root confirmation) to reduce accidental damage.
| async function promptForValues(report, existingValues, shouldOverwrite) { | ||
| const rl = readline.createInterface({ input, output }) | ||
| const values = new Map(existingValues) | ||
|
|
||
| try { | ||
| for (const item of report) { | ||
| const alreadySet = values.has(item.key) && values.get(item.key) !== '' | ||
| if (alreadySet && !shouldOverwrite) { | ||
| continue | ||
| } | ||
|
|
||
| output.write(`\n${item.key}\n`) | ||
| output.write(` Type: ${item.type}\n`) | ||
|
|
||
| if (item.provider) { | ||
| output.write(` Provider: ${item.provider.name}\n`) | ||
| output.write(` Docs: ${item.provider.url}\n`) | ||
| output.write(` Note: ${item.provider.note}\n`) | ||
| } else { | ||
| output.write(' Provider: Unknown (check project docs)\n') | ||
| } | ||
|
|
||
| if (alreadySet) { | ||
| output.write(' Current value exists. Press enter to keep it.\n') | ||
| } else { | ||
| output.write(' Enter value (leave blank to skip for now).\n') | ||
| } | ||
|
|
||
| const prompt = alreadySet ? 'New value: ' : 'Value: ' | ||
| const response = await rl.question(prompt) | ||
|
|
There was a problem hiding this comment.
promptForValues uses readline.question(), which echoes user input to the terminal. For keys classified as secret, this will visibly display secrets in scrollback/terminal recordings. Consider using a no-echo/password prompt for item.type === 'secret' (or an explicit allowlist), while keeping normal echo for non-secret keys.
| const content = await fs.readFile(filePath, 'utf8') | ||
| const relativePath = toPosixPath(path.relative(rootDir, filePath)) | ||
| extractFromSource(content, relativePath, discoveredMap) |
There was a problem hiding this comment.
The scan loop reads each file as UTF-8 without handling read/parse failures; a single unreadable file (permissions) or non-UTF8 content will abort the entire agent. Consider wrapping the per-file readFile/extractFromSource in a try/catch and continuing (optionally with a brief warning) so the agent is resilient on real-world repos.
| const content = await fs.readFile(filePath, 'utf8') | |
| const relativePath = toPosixPath(path.relative(rootDir, filePath)) | |
| extractFromSource(content, relativePath, discoveredMap) | |
| try { | |
| const content = await fs.readFile(filePath, 'utf8') | |
| const relativePath = toPosixPath(path.relative(rootDir, filePath)) | |
| extractFromSource(content, relativePath, discoveredMap) | |
| } catch (error) { | |
| const relativePath = toPosixPath(path.relative(rootDir, filePath)) | |
| console.warn(`Skipping ${relativePath}: ${error.message}`) | |
| } |
| function classifyKey(key) { | ||
| const isPublic = key.startsWith('NEXT_PUBLIC_') || key.startsWith('PUBLIC_') | ||
| const isSecret = !isPublic && SECRET_HINTS.some((hint) => key.includes(hint)) | ||
|
|
There was a problem hiding this comment.
SECRET_HINTS includes the substring KEY, which causes many non-secret variables (e.g. STACK_PUBLISHED_CLIENT_KEY from the README) to be classified as secret. This makes the report/env-file comments misleading. Consider narrowing the heuristic (e.g., remove KEY and rely on more specific hints like SECRET/TOKEN/PASSWORD or add an explicit public allowlist for known published/client keys).
| function classifyKey(key) { | |
| const isPublic = key.startsWith('NEXT_PUBLIC_') || key.startsWith('PUBLIC_') | |
| const isSecret = !isPublic && SECRET_HINTS.some((hint) => key.includes(hint)) | |
| const PUBLIC_ENV_ALLOWLIST = new Set([ | |
| // Known published/client keys that are documented as public | |
| 'STACK_PUBLISHED_CLIENT_KEY', | |
| ]) | |
| function classifyKey(key) { | |
| if (PUBLIC_ENV_ALLOWLIST.has(key)) { | |
| return 'public' | |
| } | |
| const isPublic = | |
| key.startsWith('NEXT_PUBLIC_') || key.startsWith('PUBLIC_') | |
| // Use a narrowed set of secret hints that excludes overly generic "key" hints | |
| const effectiveSecretHints = Array.isArray(SECRET_HINTS) | |
| ? SECRET_HINTS.filter((hint) => !/key/i.test(String(hint))) | |
| : [] | |
| const isSecret = | |
| !isPublic && | |
| effectiveSecretHints.length > 0 && | |
| effectiveSecretHints.some((hint) => key.includes(hint)) |
Co-authored-by: DealPatrol <DealPatrol@users.noreply.github.com>
Co-authored-by: DealPatrol <DealPatrol@users.noreply.github.com>
Co-authored-by: DealPatrol <DealPatrol@users.noreply.github.com>
Co-authored-by: DealPatrol <DealPatrol@users.noreply.github.com>
Co-authored-by: DealPatrol <DealPatrol@users.noreply.github.com>
Add a secure environment setup agent to automate discovery and configuration of project environment variables.
The agent helps users identify required environment variables, provides links to common service providers for setup, and interactively prompts for values to write to
.env.local, ensuring secrets are never automatically scraped or stolen. This addresses the user's request for an agent to manage environment variables in a secure and guided manner.