asyq 8.0.1 → 8.0.3
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/asyq.js +45 -49
- package/dist/asyq.js.map +1 -1
- package/package.json +1 -1
package/dist/asyq.js
CHANGED
|
@@ -9,7 +9,8 @@ import ora from "ora";
|
|
|
9
9
|
import boxen from "boxen";
|
|
10
10
|
import logUpdate from "log-update";
|
|
11
11
|
import TablePkg from "cli-table3";
|
|
12
|
-
import { select,
|
|
12
|
+
import { select, input } from "@inquirer/prompts";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
13
14
|
|
|
14
15
|
// src/scan.ts
|
|
15
16
|
import fs from "fs";
|
|
@@ -48,11 +49,11 @@ function scanProjectForEnvKeys(opts) {
|
|
|
48
49
|
let filesScanned = 0;
|
|
49
50
|
walk(root);
|
|
50
51
|
return { keys, filesScanned, contexts };
|
|
51
|
-
function addCtx(key,
|
|
52
|
+
function addCtx(key, relFile, line, snippet) {
|
|
52
53
|
if (!contexts[key]) contexts[key] = [];
|
|
53
54
|
if (contexts[key].length >= maxCtx) return;
|
|
54
55
|
contexts[key].push({
|
|
55
|
-
file,
|
|
56
|
+
file: relFile,
|
|
56
57
|
line,
|
|
57
58
|
snippet: snippet.trim().slice(0, 220)
|
|
58
59
|
});
|
|
@@ -76,15 +77,16 @@ function scanProjectForEnvKeys(opts) {
|
|
|
76
77
|
const content = safeRead(full);
|
|
77
78
|
if (!content) continue;
|
|
78
79
|
filesScanned++;
|
|
80
|
+
const rel = path.relative(root, full).replace(/\\/g, "/");
|
|
79
81
|
if (isEnvFile) {
|
|
80
|
-
extractFromEnvFile(content,
|
|
82
|
+
extractFromEnvFile(content, rel, keys, addCtx, keyOk);
|
|
81
83
|
} else {
|
|
82
|
-
extractFromCodeAndConfigs(content,
|
|
84
|
+
extractFromCodeAndConfigs(content, rel, keys, addCtx, keyOk);
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
|
-
function extractFromEnvFile(text,
|
|
89
|
+
function extractFromEnvFile(text, relFile, keys, addCtx, keyOk) {
|
|
88
90
|
const lines = text.split(/\r?\n/);
|
|
89
91
|
for (let i = 0; i < lines.length; i++) {
|
|
90
92
|
const ln = lines[i];
|
|
@@ -93,10 +95,10 @@ function extractFromEnvFile(text, fileName, keys, addCtx, keyOk) {
|
|
|
93
95
|
const k = m[1];
|
|
94
96
|
if (!keyOk(k)) continue;
|
|
95
97
|
keys.add(k);
|
|
96
|
-
addCtx(k,
|
|
98
|
+
addCtx(k, relFile, i + 1, ln);
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
|
-
function extractFromCodeAndConfigs(text,
|
|
101
|
+
function extractFromCodeAndConfigs(text, relFile, keys, addCtx, keyOk) {
|
|
100
102
|
const lines = text.split(/\r?\n/);
|
|
101
103
|
const patterns = [
|
|
102
104
|
/\bprocess(?:\?\.)?\.env(?:\?\.)?\.([A-Za-z_][A-Za-z0-9_]*)\b/g,
|
|
@@ -114,7 +116,7 @@ function extractFromCodeAndConfigs(text, fileName, keys, addCtx, keyOk) {
|
|
|
114
116
|
const k = match[1];
|
|
115
117
|
if (!keyOk(k)) continue;
|
|
116
118
|
keys.add(k);
|
|
117
|
-
addCtx(k,
|
|
119
|
+
addCtx(k, relFile, i + 1, ln);
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -172,6 +174,7 @@ function buildInput(opts) {
|
|
|
172
174
|
const system = [
|
|
173
175
|
"You generate documentation for environment variables.",
|
|
174
176
|
"Return ONLY JSON that matches the provided JSON Schema.",
|
|
177
|
+
"Do not include markdown or extra text.",
|
|
175
178
|
"Never output real secrets. Use safe placeholders.",
|
|
176
179
|
"Keep descriptions short and practical.",
|
|
177
180
|
"where_to_get must be actionable (dashboard, secret manager, CI, local service, etc.)."
|
|
@@ -201,8 +204,21 @@ function extractTextFromResponses(data) {
|
|
|
201
204
|
}
|
|
202
205
|
return "";
|
|
203
206
|
}
|
|
207
|
+
function tryParseJsonLoose(raw) {
|
|
208
|
+
try {
|
|
209
|
+
return JSON.parse(raw);
|
|
210
|
+
} catch {
|
|
211
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
212
|
+
if (!m) return null;
|
|
213
|
+
try {
|
|
214
|
+
return JSON.parse(m[0]);
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
204
220
|
async function generateEnvDocsWithOpenAI(opts) {
|
|
205
|
-
const
|
|
221
|
+
const input2 = buildInput(opts);
|
|
206
222
|
const res = await fetch("https://api.openai.com/v1/responses", {
|
|
207
223
|
method: "POST",
|
|
208
224
|
headers: {
|
|
@@ -211,7 +227,7 @@ async function generateEnvDocsWithOpenAI(opts) {
|
|
|
211
227
|
},
|
|
212
228
|
body: JSON.stringify({
|
|
213
229
|
model: opts.model,
|
|
214
|
-
input,
|
|
230
|
+
input: input2,
|
|
215
231
|
text: {
|
|
216
232
|
format: {
|
|
217
233
|
type: "json_schema",
|
|
@@ -226,12 +242,10 @@ async function generateEnvDocsWithOpenAI(opts) {
|
|
|
226
242
|
}
|
|
227
243
|
const data = await res.json();
|
|
228
244
|
const raw = extractTextFromResponses(data).trim();
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
parsed = JSON.parse(raw);
|
|
232
|
-
} catch {
|
|
245
|
+
const parsed = tryParseJsonLoose(raw);
|
|
246
|
+
if (!parsed) {
|
|
233
247
|
throw new Error(
|
|
234
|
-
"AI output was not valid JSON
|
|
248
|
+
"AI output was not valid JSON. Try again, or use a different model."
|
|
235
249
|
);
|
|
236
250
|
}
|
|
237
251
|
const items = Array.isArray(parsed?.items) ? parsed.items : [];
|
|
@@ -245,7 +259,15 @@ async function generateEnvDocsWithOpenAI(opts) {
|
|
|
245
259
|
}
|
|
246
260
|
|
|
247
261
|
// src/asyq.ts
|
|
248
|
-
|
|
262
|
+
var Table = TablePkg.default ?? TablePkg;
|
|
263
|
+
var MODELS = [
|
|
264
|
+
"gpt-5",
|
|
265
|
+
"gpt-5-mini",
|
|
266
|
+
"gpt-5-nano",
|
|
267
|
+
"gpt-4.1",
|
|
268
|
+
"gpt-4.1-mini",
|
|
269
|
+
"gpt-4.1-nano"
|
|
270
|
+
];
|
|
249
271
|
function getPackageVersion() {
|
|
250
272
|
try {
|
|
251
273
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -257,19 +279,9 @@ function getPackageVersion() {
|
|
|
257
279
|
return "unknown";
|
|
258
280
|
}
|
|
259
281
|
}
|
|
260
|
-
var Table = TablePkg.default ?? TablePkg;
|
|
261
|
-
var MODELS = [
|
|
262
|
-
"gpt-5",
|
|
263
|
-
"gpt-5-mini",
|
|
264
|
-
"gpt-5-nano",
|
|
265
|
-
"gpt-4.1",
|
|
266
|
-
"gpt-4.1-mini",
|
|
267
|
-
"gpt-4.1-nano"
|
|
268
|
-
];
|
|
269
282
|
function renderHeader() {
|
|
270
283
|
const body = [
|
|
271
284
|
pc.bold(`Asyq v${getPackageVersion()}`),
|
|
272
|
-
pc.dim(""),
|
|
273
285
|
pc.dim("Generate .env.example from your project\u2019s env usage"),
|
|
274
286
|
pc.dim("Created by @thev1ndu")
|
|
275
287
|
].join("\n");
|
|
@@ -324,14 +336,14 @@ async function pickModel() {
|
|
|
324
336
|
async function getApiKey() {
|
|
325
337
|
const envKey = process.env.OPENAI_API_KEY?.trim();
|
|
326
338
|
if (envKey) return envKey;
|
|
327
|
-
const key = await
|
|
339
|
+
const key = await input({
|
|
328
340
|
message: "Enter OpenAI API key (not saved)",
|
|
329
|
-
|
|
341
|
+
validate: (v) => v.trim().length > 0 || "API key cannot be empty"
|
|
330
342
|
});
|
|
331
|
-
return
|
|
343
|
+
return key.trim();
|
|
332
344
|
}
|
|
333
345
|
var program = new Command();
|
|
334
|
-
program.name("
|
|
346
|
+
program.name("asyq").description("Generate .env.example by scanning your project for env usage").version(`v${getPackageVersion()}`);
|
|
335
347
|
program.command("init").description("Scan project and generate .env.example").option("--root <dir>", "Project root to scan", ".").option("--out <file>", "Output file", ".env.example").option("--force", "Overwrite output if it exists").option(
|
|
336
348
|
"--include-lowercase",
|
|
337
349
|
"Include lowercase/mixed-case keys (not recommended)"
|
|
@@ -391,15 +403,6 @@ program.command("init").description("Scan project and generate .env.example").op
|
|
|
391
403
|
let content = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
392
404
|
if (mode === "ai" && model) {
|
|
393
405
|
const apiKey = await getApiKey();
|
|
394
|
-
if (!apiKey) {
|
|
395
|
-
steps[2].status = "fail";
|
|
396
|
-
renderSteps(steps);
|
|
397
|
-
finishSteps();
|
|
398
|
-
fail(
|
|
399
|
-
"OpenAI API key is required for AI-assisted mode.",
|
|
400
|
-
"Set OPENAI_API_KEY or enter it when prompted."
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
406
|
const aiSpinner = ora({
|
|
404
407
|
text: "Generating AI guidance",
|
|
405
408
|
spinner: "dots"
|
|
@@ -416,15 +419,8 @@ program.command("init").description("Scan project and generate .env.example").op
|
|
|
416
419
|
const byKey = new Map(docs.map((d) => [d.key, d]));
|
|
417
420
|
content = keys.map((k) => {
|
|
418
421
|
const d = byKey.get(k);
|
|
419
|
-
if (!d) {
|
|
420
|
-
|
|
421
|
-
`# ${k}`,
|
|
422
|
-
`# Description: not provided`,
|
|
423
|
-
`# Where to get it: not provided`,
|
|
424
|
-
`${k}=`,
|
|
425
|
-
""
|
|
426
|
-
].join("\n");
|
|
427
|
-
}
|
|
422
|
+
if (!d) return `${k}=
|
|
423
|
+
`;
|
|
428
424
|
const secretNote = d.is_secret ? "Secret value. Do not commit." : "Non-secret value (verify before committing).";
|
|
429
425
|
return [
|
|
430
426
|
`# ${d.key}`,
|
package/dist/asyq.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/asyq.ts","../src/scan.ts","../src/ai.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\nimport ora from \"ora\";\nimport boxen from \"boxen\";\nimport logUpdate from \"log-update\";\nimport TablePkg from \"cli-table3\";\nimport { select, password } from \"@inquirer/prompts\";\n\nimport { scanProjectForEnvKeys } from \"./scan.js\";\nimport { generateEnvDocsWithOpenAI } from \"./ai.js\";\n\nimport { fileURLToPath } from \"node:url\";\n\nfunction getPackageVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const pkgPath = path.resolve(__dirname, \"../package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n\n return pkg.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n// cli-table3 interop safety (works in ESM + CJS environments)\nconst Table: any = (TablePkg as any).default ?? (TablePkg as any);\n\ntype Step = {\n title: string;\n status: \"pending\" | \"running\" | \"done\" | \"fail\";\n detail?: string;\n};\n\nconst MODELS = [\n \"gpt-5\",\n \"gpt-5-mini\",\n \"gpt-5-nano\",\n \"gpt-4.1\",\n \"gpt-4.1-mini\",\n \"gpt-4.1-nano\",\n] as const;\n\ntype ModelName = (typeof MODELS)[number];\n\nfunction renderHeader() {\n const body = [\n pc.bold(`Asyq v${getPackageVersion()}`),\n pc.dim(\"\"),\n pc.dim(\"Generate .env.example from your project’s env usage\"),\n pc.dim(\"Created by @thev1ndu\"),\n ].join(\"\\n\");\n\n console.log(\n boxen(body, {\n padding: 1,\n borderStyle: \"round\",\n borderColor: \"cyan\",\n })\n );\n console.log(\"\");\n}\n\nfunction icon(status: Step[\"status\"]) {\n if (status === \"done\") return pc.green(\"✓\");\n if (status === \"fail\") return pc.red(\"✗\");\n if (status === \"running\") return pc.cyan(\"•\");\n return pc.dim(\"•\");\n}\n\nfunction renderSteps(steps: Step[]) {\n logUpdate(\n steps\n .map((s) => {\n const left = `${icon(s.status)} ${s.title}`;\n const right = s.detail ? pc.dim(s.detail) : \"\";\n return right ? `${left} ${right}` : left;\n })\n .join(\"\\n\")\n );\n}\n\nfunction finishSteps() {\n logUpdate.done();\n}\n\nfunction fail(message: string, hint?: string): never {\n console.error(pc.red(message));\n if (hint) console.error(pc.dim(hint));\n process.exit(1);\n}\n\nasync function pickMode(): Promise<\"default\" | \"ai\"> {\n return await select({\n message: \"How would you like to generate .env.example?\",\n choices: [\n { name: \"Default\", value: \"default\" },\n { name: \"AI-assisted\", value: \"ai\" },\n ],\n });\n}\n\nasync function pickModel(): Promise<ModelName> {\n return await select({\n message: \"Select an AI model\",\n default: \"gpt-4.1-mini\",\n choices: MODELS.map((m) => ({ name: m, value: m })),\n });\n}\n\nasync function getApiKey(): Promise<string> {\n const envKey = process.env.OPENAI_API_KEY?.trim();\n if (envKey) return envKey;\n\n const key = await password({\n message: \"Enter OpenAI API key (not saved)\",\n mask: \"*\",\n });\n\n return String(key ?? \"\").trim();\n}\n\nconst program = new Command();\n\nprogram\n .name(\"Asyq\")\n .description(\"Generate .env.example by scanning your project for env usage\")\n .version(`v${getPackageVersion()}`);\n\nprogram\n .command(\"init\")\n .description(\"Scan project and generate .env.example\")\n .option(\"--root <dir>\", \"Project root to scan\", \".\")\n .option(\"--out <file>\", \"Output file\", \".env.example\")\n .option(\"--force\", \"Overwrite output if it exists\")\n .option(\n \"--include-lowercase\",\n \"Include lowercase/mixed-case keys (not recommended)\"\n )\n .option(\"--debug\", \"Print scan diagnostics\")\n .action(async (opts) => {\n renderHeader();\n\n const root = path.resolve(process.cwd(), opts.root);\n const outFile = path.resolve(process.cwd(), opts.out);\n\n if (fs.existsSync(outFile) && !opts.force) {\n fail(`Output already exists: ${opts.out}`, \"Use --force to overwrite.\");\n }\n\n const mode = await pickMode();\n const model: ModelName | null = mode === \"ai\" ? await pickModel() : null;\n\n const steps: Step[] = [\n { title: \"Preparing\", status: \"running\", detail: `root: ${opts.root}` },\n { title: \"Scanning project files\", status: \"pending\" },\n {\n title: \"Writing .env.example\",\n status: \"pending\",\n detail: mode === \"ai\" ? `AI (${model})` : \"Default\",\n },\n ];\n\n renderSteps(steps);\n\n steps[0].status = \"done\";\n steps[1].status = \"running\";\n renderSteps(steps);\n\n const scanSpinner = ora({\n text: \"Scanning for env keys\",\n spinner: \"dots\",\n }).start();\n\n const res = scanProjectForEnvKeys({\n rootDir: root,\n includeLowercase: !!opts.includeLowercase,\n });\n\n scanSpinner.stop();\n\n steps[1].status = \"done\";\n steps[1].detail = `${res.filesScanned} files scanned`;\n steps[2].status = \"running\";\n renderSteps(steps);\n\n if (opts.debug) {\n console.log(pc.dim(\"\"));\n console.log(pc.dim(\"Diagnostics\"));\n console.log(pc.dim(` root: ${opts.root}`));\n console.log(pc.dim(` files scanned: ${res.filesScanned}`));\n console.log(pc.dim(` keys found: ${res.keys.size}`));\n console.log(pc.dim(\"\"));\n }\n\n if (res.keys.size === 0) {\n steps[2].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\n \"No environment variables found.\",\n \"Ensure your code uses process.env.KEY or ${KEY}.\"\n );\n }\n\n const keys = [...res.keys].sort((a, b) => a.localeCompare(b));\n\n // Default content\n let content = keys.map((k) => `${k}=`).join(\"\\n\") + \"\\n\";\n\n if (mode === \"ai\" && model) {\n const apiKey = await getApiKey();\n if (!apiKey) {\n steps[2].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\n \"OpenAI API key is required for AI-assisted mode.\",\n \"Set OPENAI_API_KEY or enter it when prompted.\"\n );\n }\n\n const aiSpinner = ora({\n text: \"Generating AI guidance\",\n spinner: \"dots\",\n }).start();\n\n try {\n const docs = await generateEnvDocsWithOpenAI({\n apiKey,\n model,\n projectHint:\n \"Write practical guidance for developers setting env vars.\",\n contexts: res.contexts,\n keys,\n });\n\n aiSpinner.stop();\n\n const byKey = new Map(docs.map((d) => [d.key, d]));\n\n content =\n keys\n .map((k) => {\n const d = byKey.get(k);\n if (!d) {\n return [\n `# ${k}`,\n `# Description: not provided`,\n `# Where to get it: not provided`,\n `${k}=`,\n \"\",\n ].join(\"\\n\");\n }\n\n const secretNote = d.is_secret\n ? \"Secret value. Do not commit.\"\n : \"Non-secret value (verify before committing).\";\n\n return [\n `# ${d.key}`,\n `# ${d.description}`,\n `# Where to get it: ${d.where_to_get}`,\n `# ${secretNote}`,\n `${d.key}=${d.example_value || \"\"}`,\n \"\",\n ].join(\"\\n\");\n })\n .join(\"\\n\")\n .trimEnd() + \"\\n\";\n } catch (e: any) {\n aiSpinner.stop();\n steps[2].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\"AI generation failed.\", e?.message ?? String(e));\n }\n }\n\n fs.writeFileSync(outFile, content, \"utf8\");\n\n steps[2].status = \"done\";\n renderSteps(steps);\n finishSteps();\n\n const table = new Table({\n style: { head: [], border: [] },\n colWidths: [18, 60],\n wordWrap: true,\n });\n\n table.push(\n [pc.dim(\"Output\"), pc.cyan(opts.out)],\n [pc.dim(\"Keys\"), pc.cyan(String(keys.length))],\n [pc.dim(\"Mode\"), pc.cyan(mode === \"ai\" ? `AI (${model})` : \"Default\")]\n );\n\n console.log(\"\");\n console.log(pc.bold(\"Complete\"));\n console.log(table.toString());\n console.log(pc.dim(\"Next steps\"));\n console.log(pc.dim(` 1) Fill values in ${opts.out}`));\n console.log(pc.dim(\" 2) Copy to .env (do not commit secrets)\"));\n console.log(\"\");\n });\n\nprogram.parse(process.argv);\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type ScanOptions = {\n rootDir: string;\n includeLowercase?: boolean;\n maxContextPerKey?: number;\n};\n\nexport type KeyContext = { file: string; line: number; snippet: string };\n\nexport type ScanResult = {\n keys: Set<string>;\n filesScanned: number;\n contexts: Record<string, KeyContext[]>;\n};\n\nconst IGNORE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \"out\",\n \"coverage\",\n \".turbo\",\n \".cache\",\n]);\n\n// Enterprise-safe default: UPPERCASE env keys only\nconst ENV_KEY_RE_STRICT = /^[A-Z][A-Z0-9_]*$/;\nconst ENV_KEY_RE_LOOSE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport function scanProjectForEnvKeys(opts: ScanOptions): ScanResult {\n const root = opts.rootDir;\n const maxCtx = opts.maxContextPerKey ?? 2;\n\n const keyOk = (k: string) =>\n (opts.includeLowercase ? ENV_KEY_RE_LOOSE : ENV_KEY_RE_STRICT).test(k);\n\n // Scan common source/config formats (avoid HTML/MD noise)\n const exts = new Set([\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".json\",\n \".yml\",\n \".yaml\",\n \".toml\",\n ]);\n\n const keys = new Set<string>();\n const contexts: Record<string, KeyContext[]> = {};\n let filesScanned = 0;\n\n walk(root);\n\n return { keys, filesScanned, contexts };\n\n function addCtx(key: string, file: string, line: number, snippet: string) {\n if (!contexts[key]) contexts[key] = [];\n if (contexts[key].length >= maxCtx) return;\n contexts[key].push({\n file,\n line,\n snippet: snippet.trim().slice(0, 220),\n });\n }\n\n function walk(dir: string) {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (!IGNORE_DIRS.has(entry.name)) walk(full);\n continue;\n }\n\n const isEnvFile = entry.name === \".env\" || entry.name.startsWith(\".env.\");\n const ext = path.extname(entry.name);\n\n if (!isEnvFile && !exts.has(ext)) continue;\n\n const content = safeRead(full);\n if (!content) continue;\n\n filesScanned++;\n\n if (isEnvFile) {\n extractFromEnvFile(content, entry.name, keys, addCtx, keyOk);\n } else {\n extractFromCodeAndConfigs(content, entry.name, keys, addCtx, keyOk);\n }\n }\n }\n}\n\nfunction extractFromEnvFile(\n text: string,\n fileName: string,\n keys: Set<string>,\n addCtx: (key: string, file: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n // KEY=... or export KEY=...\n const lines = text.split(/\\r?\\n/);\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n const m = ln.match(/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (!m) continue;\n const k = m[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, fileName, i + 1, ln);\n }\n}\n\nfunction extractFromCodeAndConfigs(\n text: string,\n fileName: string,\n keys: Set<string>,\n addCtx: (key: string, file: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n const lines = text.split(/\\r?\\n/);\n\n // Only structured env patterns here (NO generic KEY= parsing)\n const patterns: RegExp[] = [\n /\\bprocess(?:\\?\\.)?\\.env(?:\\?\\.)?\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n /\\bprocess(?:\\?\\.)?\\.env\\[\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\]/g,\n /\\bimport\\.meta\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n /\\bDeno\\.env\\.get\\(\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\)/g,\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g,\n ];\n\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n\n for (const re of patterns) {\n re.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = re.exec(ln))) {\n const k = match[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, fileName, i + 1, ln);\n }\n }\n }\n}\n\nfunction safeRead(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n","export type AIEnvDoc = {\n key: string;\n description: string;\n where_to_get: string;\n example_value: string;\n is_secret: boolean;\n};\n\nexport type AIGenerateOptions = {\n apiKey: string;\n model: string;\n projectHint?: string;\n contexts: Record<string, { file: string; line: number; snippet: string }[]>;\n keys: string[];\n};\n\nconst JSON_SCHEMA = {\n name: \"env_docs\",\n strict: true,\n schema: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n key: { type: \"string\" },\n description: { type: \"string\" },\n where_to_get: { type: \"string\" },\n example_value: { type: \"string\" },\n is_secret: { type: \"boolean\" },\n },\n required: [\n \"key\",\n \"description\",\n \"where_to_get\",\n \"example_value\",\n \"is_secret\",\n ],\n },\n },\n },\n required: [\"items\"],\n },\n} as const;\n\nfunction buildInput(opts: AIGenerateOptions) {\n const lines = opts.keys.map((k) => {\n const ctx = opts.contexts[k]?.[0];\n const seenAt = ctx ? `${ctx.file}:${ctx.line}` : \"unknown\";\n const snippet = ctx ? ctx.snippet : \"\";\n return `- ${k}\\n seen_at: ${seenAt}\\n snippet: ${snippet}`;\n });\n\n const system = [\n \"You generate documentation for environment variables.\",\n \"Return ONLY JSON that matches the provided JSON Schema.\",\n \"Never output real secrets. Use safe placeholders.\",\n \"Keep descriptions short and practical.\",\n \"where_to_get must be actionable (dashboard, secret manager, CI, local service, etc.).\",\n ].join(\" \");\n\n const user = [\n opts.projectHint ? `Project hint: ${opts.projectHint}` : \"\",\n \"Variables:\",\n ...lines,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n\n return [\n { role: \"system\", content: system },\n { role: \"user\", content: user },\n ];\n}\n\nfunction extractTextFromResponses(data: any): string {\n if (typeof data?.output_text === \"string\" && data.output_text.trim())\n return data.output_text;\n\n // Try to find text content in output array\n const out = data?.output;\n if (Array.isArray(out)) {\n for (const item of out) {\n const content = item?.content;\n if (!Array.isArray(content)) continue;\n for (const c of content) {\n if (typeof c?.text === \"string\" && c.text.trim()) return c.text;\n }\n }\n }\n return \"\";\n}\n\nexport async function generateEnvDocsWithOpenAI(\n opts: AIGenerateOptions\n): Promise<AIEnvDoc[]> {\n const input = buildInput(opts);\n\n const res = await fetch(\"https://api.openai.com/v1/responses\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${opts.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: opts.model,\n input,\n text: {\n format: {\n type: \"json_schema\",\n ...JSON_SCHEMA,\n },\n },\n }),\n });\n\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`OpenAI request failed (${res.status}): ${text}`);\n }\n\n const data: any = await res.json();\n const raw = extractTextFromResponses(data).trim();\n\n let parsed: any;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n \"AI output was not valid JSON (structured output expected).\"\n );\n }\n\n const items = Array.isArray(parsed?.items) ? parsed.items : [];\n return items\n .map((x: any) => ({\n key: String(x.key ?? \"\"),\n description: String(x.description ?? \"\"),\n where_to_get: String(x.where_to_get ?? \"\"),\n example_value: String(x.example_value ?? \"\"),\n is_secret: Boolean(x.is_secret),\n }))\n .filter((x: AIEnvDoc) => x.key.length > 0);\n}\n"],"mappings":";;;AACA,SAAS,eAAe;AACxB,OAAOA,SAAQ;AACf,OAAOC,WAAU;AACjB,OAAO,QAAQ;AACf,OAAO,SAAS;AAChB,OAAO,WAAW;AAClB,OAAO,eAAe;AACtB,OAAO,cAAc;AACrB,SAAS,QAAQ,gBAAgB;;;ACTjC,OAAO,QAAQ;AACf,OAAO,UAAU;AAgBjB,IAAM,cAAc,oBAAI,IAAI;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AAElB,SAAS,sBAAsB,MAA+B;AACnE,QAAM,OAAO,KAAK;AAClB,QAAM,SAAS,KAAK,oBAAoB;AAExC,QAAM,QAAQ,CAAC,OACZ,KAAK,mBAAmB,mBAAmB,mBAAmB,KAAK,CAAC;AAGvE,QAAM,OAAO,oBAAI,IAAI;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,WAAyC,CAAC;AAChD,MAAI,eAAe;AAEnB,OAAK,IAAI;AAET,SAAO,EAAE,MAAM,cAAc,SAAS;AAEtC,WAAS,OAAO,KAAa,MAAc,MAAc,SAAiB;AACxE,QAAI,CAAC,SAAS,GAAG,EAAG,UAAS,GAAG,IAAI,CAAC;AACrC,QAAI,SAAS,GAAG,EAAE,UAAU,OAAQ;AACpC,aAAS,GAAG,EAAE,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,MACA,SAAS,QAAQ,KAAK,EAAE,MAAM,GAAG,GAAG;AAAA,IACtC,CAAC;AAAA,EACH;AAEA,WAAS,KAAK,KAAa;AACzB,QAAI;AACJ,QAAI;AACF,gBAAU,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IACvD,QAAQ;AACN;AAAA,IACF;AAEA,eAAW,SAAS,SAAS;AAC3B,YAAM,OAAO,KAAK,KAAK,KAAK,MAAM,IAAI;AAEtC,UAAI,MAAM,YAAY,GAAG;AACvB,YAAI,CAAC,YAAY,IAAI,MAAM,IAAI,EAAG,MAAK,IAAI;AAC3C;AAAA,MACF;AAEA,YAAM,YAAY,MAAM,SAAS,UAAU,MAAM,KAAK,WAAW,OAAO;AACxE,YAAM,MAAM,KAAK,QAAQ,MAAM,IAAI;AAEnC,UAAI,CAAC,aAAa,CAAC,KAAK,IAAI,GAAG,EAAG;AAElC,YAAM,UAAU,SAAS,IAAI;AAC7B,UAAI,CAAC,QAAS;AAEd;AAEA,UAAI,WAAW;AACb,2BAAmB,SAAS,MAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,MAC7D,OAAO;AACL,kCAA0B,SAAS,MAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,mBACP,MACA,UACA,MACA,QACA,OACA;AAEA,QAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,KAAK,MAAM,CAAC;AAClB,UAAM,IAAI,GAAG,MAAM,gDAAgD;AACnE,QAAI,CAAC,EAAG;AACR,UAAM,IAAI,EAAE,CAAC;AACb,QAAI,CAAC,MAAM,CAAC,EAAG;AACf,SAAK,IAAI,CAAC;AACV,WAAO,GAAG,UAAU,IAAI,GAAG,EAAE;AAAA,EAC/B;AACF;AAEA,SAAS,0BACP,MACA,UACA,MACA,QACA,OACA;AACA,QAAM,QAAQ,KAAK,MAAM,OAAO;AAGhC,QAAM,WAAqB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,KAAK,MAAM,CAAC;AAElB,eAAW,MAAM,UAAU;AACzB,SAAG,YAAY;AACf,UAAI;AACJ,aAAQ,QAAQ,GAAG,KAAK,EAAE,GAAI;AAC5B,cAAM,IAAI,MAAM,CAAC;AACjB,YAAI,CAAC,MAAM,CAAC,EAAG;AACf,aAAK,IAAI,CAAC;AACV,eAAO,GAAG,UAAU,IAAI,GAAG,EAAE;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,SAAS,UAAiC;AACjD,MAAI;AACF,WAAO,GAAG,aAAa,UAAU,MAAM;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACvJA,IAAM,cAAc;AAAA,EAClB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,sBAAsB;AAAA,IACtB,YAAY;AAAA,MACV,OAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,sBAAsB;AAAA,UACtB,YAAY;AAAA,YACV,KAAK,EAAE,MAAM,SAAS;AAAA,YACtB,aAAa,EAAE,MAAM,SAAS;AAAA,YAC9B,cAAc,EAAE,MAAM,SAAS;AAAA,YAC/B,eAAe,EAAE,MAAM,SAAS;AAAA,YAChC,WAAW,EAAE,MAAM,UAAU;AAAA,UAC/B;AAAA,UACA,UAAU;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,UAAU,CAAC,OAAO;AAAA,EACpB;AACF;AAEA,SAAS,WAAW,MAAyB;AAC3C,QAAM,QAAQ,KAAK,KAAK,IAAI,CAAC,MAAM;AACjC,UAAM,MAAM,KAAK,SAAS,CAAC,IAAI,CAAC;AAChC,UAAM,SAAS,MAAM,GAAG,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK;AACjD,UAAM,UAAU,MAAM,IAAI,UAAU;AACpC,WAAO,KAAK,CAAC;AAAA,aAAgB,MAAM;AAAA,aAAgB,OAAO;AAAA,EAC5D,CAAC;AAED,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,GAAG;AAEV,QAAM,OAAO;AAAA,IACX,KAAK,cAAc,iBAAiB,KAAK,WAAW,KAAK;AAAA,IACzD;AAAA,IACA,GAAG;AAAA,EACL,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AAEZ,SAAO;AAAA,IACL,EAAE,MAAM,UAAU,SAAS,OAAO;AAAA,IAClC,EAAE,MAAM,QAAQ,SAAS,KAAK;AAAA,EAChC;AACF;AAEA,SAAS,yBAAyB,MAAmB;AACnD,MAAI,OAAO,MAAM,gBAAgB,YAAY,KAAK,YAAY,KAAK;AACjE,WAAO,KAAK;AAGd,QAAM,MAAM,MAAM;AAClB,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,eAAW,QAAQ,KAAK;AACtB,YAAM,UAAU,MAAM;AACtB,UAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,iBAAW,KAAK,SAAS;AACvB,YAAI,OAAO,GAAG,SAAS,YAAY,EAAE,KAAK,KAAK,EAAG,QAAO,EAAE;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,0BACpB,MACqB;AACrB,QAAM,QAAQ,WAAW,IAAI;AAE7B,QAAM,MAAM,MAAM,MAAM,uCAAuC;AAAA,IAC7D,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACnB,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,MAAM;AAAA,QACJ,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,GAAG;AAAA,QACL;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAM,IAAI,MAAM,0BAA0B,IAAI,MAAM,MAAM,IAAI,EAAE;AAAA,EAClE;AAEA,QAAM,OAAY,MAAM,IAAI,KAAK;AACjC,QAAM,MAAM,yBAAyB,IAAI,EAAE,KAAK;AAEhD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM,QAAQ,QAAQ,KAAK,IAAI,OAAO,QAAQ,CAAC;AAC7D,SAAO,MACJ,IAAI,CAAC,OAAY;AAAA,IAChB,KAAK,OAAO,EAAE,OAAO,EAAE;AAAA,IACvB,aAAa,OAAO,EAAE,eAAe,EAAE;AAAA,IACvC,cAAc,OAAO,EAAE,gBAAgB,EAAE;AAAA,IACzC,eAAe,OAAO,EAAE,iBAAiB,EAAE;AAAA,IAC3C,WAAW,QAAQ,EAAE,SAAS;AAAA,EAChC,EAAE,EACD,OAAO,CAAC,MAAgB,EAAE,IAAI,SAAS,CAAC;AAC7C;;;AFrIA,SAAS,qBAAqB;AAE9B,SAAS,oBAA4B;AACnC,MAAI;AACF,UAAM,aAAa,cAAc,YAAY,GAAG;AAChD,UAAM,YAAYC,MAAK,QAAQ,UAAU;AAEzC,UAAM,UAAUA,MAAK,QAAQ,WAAW,iBAAiB;AACzD,UAAM,MAAM,KAAK,MAAMC,IAAG,aAAa,SAAS,MAAM,CAAC;AAEvD,WAAO,IAAI,WAAW;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,IAAM,QAAc,SAAiB,WAAY;AAQjD,IAAM,SAAS;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIA,SAAS,eAAe;AACtB,QAAM,OAAO;AAAA,IACX,GAAG,KAAK,SAAS,kBAAkB,CAAC,EAAE;AAAA,IACtC,GAAG,IAAI,EAAE;AAAA,IACT,GAAG,IAAI,0DAAqD;AAAA,IAC5D,GAAG,IAAI,sBAAsB;AAAA,EAC/B,EAAE,KAAK,IAAI;AAEX,UAAQ;AAAA,IACN,MAAM,MAAM;AAAA,MACV,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AACA,UAAQ,IAAI,EAAE;AAChB;AAEA,SAAS,KAAK,QAAwB;AACpC,MAAI,WAAW,OAAQ,QAAO,GAAG,MAAM,QAAG;AAC1C,MAAI,WAAW,OAAQ,QAAO,GAAG,IAAI,QAAG;AACxC,MAAI,WAAW,UAAW,QAAO,GAAG,KAAK,QAAG;AAC5C,SAAO,GAAG,IAAI,QAAG;AACnB;AAEA,SAAS,YAAY,OAAe;AAClC;AAAA,IACE,MACG,IAAI,CAAC,MAAM;AACV,YAAM,OAAO,GAAG,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK;AACzC,YAAM,QAAQ,EAAE,SAAS,GAAG,IAAI,EAAE,MAAM,IAAI;AAC5C,aAAO,QAAQ,GAAG,IAAI,IAAI,KAAK,KAAK;AAAA,IACtC,CAAC,EACA,KAAK,IAAI;AAAA,EACd;AACF;AAEA,SAAS,cAAc;AACrB,YAAU,KAAK;AACjB;AAEA,SAAS,KAAK,SAAiB,MAAsB;AACnD,UAAQ,MAAM,GAAG,IAAI,OAAO,CAAC;AAC7B,MAAI,KAAM,SAAQ,MAAM,GAAG,IAAI,IAAI,CAAC;AACpC,UAAQ,KAAK,CAAC;AAChB;AAEA,eAAe,WAAsC;AACnD,SAAO,MAAM,OAAO;AAAA,IAClB,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,MAAM,WAAW,OAAO,UAAU;AAAA,MACpC,EAAE,MAAM,eAAe,OAAO,KAAK;AAAA,IACrC;AAAA,EACF,CAAC;AACH;AAEA,eAAe,YAAgC;AAC7C,SAAO,MAAM,OAAO;AAAA,IAClB,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,EAAE;AAAA,EACpD,CAAC;AACH;AAEA,eAAe,YAA6B;AAC1C,QAAM,SAAS,QAAQ,IAAI,gBAAgB,KAAK;AAChD,MAAI,OAAQ,QAAO;AAEnB,QAAM,MAAM,MAAM,SAAS;AAAA,IACzB,SAAS;AAAA,IACT,MAAM;AAAA,EACR,CAAC;AAED,SAAO,OAAO,OAAO,EAAE,EAAE,KAAK;AAChC;AAEA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,MAAM,EACX,YAAY,8DAA8D,EAC1E,QAAQ,IAAI,kBAAkB,CAAC,EAAE;AAEpC,QACG,QAAQ,MAAM,EACd,YAAY,wCAAwC,EACpD,OAAO,gBAAgB,wBAAwB,GAAG,EAClD,OAAO,gBAAgB,eAAe,cAAc,EACpD,OAAO,WAAW,+BAA+B,EACjD;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,WAAW,wBAAwB,EAC1C,OAAO,OAAO,SAAS;AACtB,eAAa;AAEb,QAAM,OAAOD,MAAK,QAAQ,QAAQ,IAAI,GAAG,KAAK,IAAI;AAClD,QAAM,UAAUA,MAAK,QAAQ,QAAQ,IAAI,GAAG,KAAK,GAAG;AAEpD,MAAIC,IAAG,WAAW,OAAO,KAAK,CAAC,KAAK,OAAO;AACzC,SAAK,0BAA0B,KAAK,GAAG,IAAI,2BAA2B;AAAA,EACxE;AAEA,QAAM,OAAO,MAAM,SAAS;AAC5B,QAAM,QAA0B,SAAS,OAAO,MAAM,UAAU,IAAI;AAEpE,QAAM,QAAgB;AAAA,IACpB,EAAE,OAAO,aAAa,QAAQ,WAAW,QAAQ,SAAS,KAAK,IAAI,GAAG;AAAA,IACtE,EAAE,OAAO,0BAA0B,QAAQ,UAAU;AAAA,IACrD;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,SAAS,OAAO,OAAO,KAAK,MAAM;AAAA,IAC5C;AAAA,EACF;AAEA,cAAY,KAAK;AAEjB,QAAM,CAAC,EAAE,SAAS;AAClB,QAAM,CAAC,EAAE,SAAS;AAClB,cAAY,KAAK;AAEjB,QAAM,cAAc,IAAI;AAAA,IACtB,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC,EAAE,MAAM;AAET,QAAM,MAAM,sBAAsB;AAAA,IAChC,SAAS;AAAA,IACT,kBAAkB,CAAC,CAAC,KAAK;AAAA,EAC3B,CAAC;AAED,cAAY,KAAK;AAEjB,QAAM,CAAC,EAAE,SAAS;AAClB,QAAM,CAAC,EAAE,SAAS,GAAG,IAAI,YAAY;AACrC,QAAM,CAAC,EAAE,SAAS;AAClB,cAAY,KAAK;AAEjB,MAAI,KAAK,OAAO;AACd,YAAQ,IAAI,GAAG,IAAI,EAAE,CAAC;AACtB,YAAQ,IAAI,GAAG,IAAI,aAAa,CAAC;AACjC,YAAQ,IAAI,GAAG,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;AAC1C,YAAQ,IAAI,GAAG,IAAI,oBAAoB,IAAI,YAAY,EAAE,CAAC;AAC1D,YAAQ,IAAI,GAAG,IAAI,iBAAiB,IAAI,KAAK,IAAI,EAAE,CAAC;AACpD,YAAQ,IAAI,GAAG,IAAI,EAAE,CAAC;AAAA,EACxB;AAEA,MAAI,IAAI,KAAK,SAAS,GAAG;AACvB,UAAM,CAAC,EAAE,SAAS;AAClB,gBAAY,KAAK;AACjB,gBAAY;AACZ;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,CAAC,GAAG,IAAI,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AAG5D,MAAI,UAAU,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,IAAI;AAEpD,MAAI,SAAS,QAAQ,OAAO;AAC1B,UAAM,SAAS,MAAM,UAAU;AAC/B,QAAI,CAAC,QAAQ;AACX,YAAM,CAAC,EAAE,SAAS;AAClB,kBAAY,KAAK;AACjB,kBAAY;AACZ;AAAA,QACE;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAAY,IAAI;AAAA,MACpB,MAAM;AAAA,MACN,SAAS;AAAA,IACX,CAAC,EAAE,MAAM;AAET,QAAI;AACF,YAAM,OAAO,MAAM,0BAA0B;AAAA,QAC3C;AAAA,QACA;AAAA,QACA,aACE;AAAA,QACF,UAAU,IAAI;AAAA,QACd;AAAA,MACF,CAAC;AAED,gBAAU,KAAK;AAEf,YAAM,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AAEjD,gBACE,KACG,IAAI,CAAC,MAAM;AACV,cAAM,IAAI,MAAM,IAAI,CAAC;AACrB,YAAI,CAAC,GAAG;AACN,iBAAO;AAAA,YACL,KAAK,CAAC;AAAA,YACN;AAAA,YACA;AAAA,YACA,GAAG,CAAC;AAAA,YACJ;AAAA,UACF,EAAE,KAAK,IAAI;AAAA,QACb;AAEA,cAAM,aAAa,EAAE,YACjB,iCACA;AAEJ,eAAO;AAAA,UACL,KAAK,EAAE,GAAG;AAAA,UACV,KAAK,EAAE,WAAW;AAAA,UAClB,sBAAsB,EAAE,YAAY;AAAA,UACpC,KAAK,UAAU;AAAA,UACf,GAAG,EAAE,GAAG,IAAI,EAAE,iBAAiB,EAAE;AAAA,UACjC;AAAA,QACF,EAAE,KAAK,IAAI;AAAA,MACb,CAAC,EACA,KAAK,IAAI,EACT,QAAQ,IAAI;AAAA,IACnB,SAAS,GAAQ;AACf,gBAAU,KAAK;AACf,YAAM,CAAC,EAAE,SAAS;AAClB,kBAAY,KAAK;AACjB,kBAAY;AACZ,WAAK,yBAAyB,GAAG,WAAW,OAAO,CAAC,CAAC;AAAA,IACvD;AAAA,EACF;AAEA,EAAAA,IAAG,cAAc,SAAS,SAAS,MAAM;AAEzC,QAAM,CAAC,EAAE,SAAS;AAClB,cAAY,KAAK;AACjB,cAAY;AAEZ,QAAM,QAAQ,IAAI,MAAM;AAAA,IACtB,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,IAC9B,WAAW,CAAC,IAAI,EAAE;AAAA,IAClB,UAAU;AAAA,EACZ,CAAC;AAED,QAAM;AAAA,IACJ,CAAC,GAAG,IAAI,QAAQ,GAAG,GAAG,KAAK,KAAK,GAAG,CAAC;AAAA,IACpC,CAAC,GAAG,IAAI,MAAM,GAAG,GAAG,KAAK,OAAO,KAAK,MAAM,CAAC,CAAC;AAAA,IAC7C,CAAC,GAAG,IAAI,MAAM,GAAG,GAAG,KAAK,SAAS,OAAO,OAAO,KAAK,MAAM,SAAS,CAAC;AAAA,EACvE;AAEA,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,GAAG,KAAK,UAAU,CAAC;AAC/B,UAAQ,IAAI,MAAM,SAAS,CAAC;AAC5B,UAAQ,IAAI,GAAG,IAAI,YAAY,CAAC;AAChC,UAAQ,IAAI,GAAG,IAAI,uBAAuB,KAAK,GAAG,EAAE,CAAC;AACrD,UAAQ,IAAI,GAAG,IAAI,2CAA2C,CAAC;AAC/D,UAAQ,IAAI,EAAE;AAChB,CAAC;AAEH,QAAQ,MAAM,QAAQ,IAAI;","names":["fs","path","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/asyq.ts","../src/scan.ts","../src/ai.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\nimport ora from \"ora\";\nimport boxen from \"boxen\";\nimport logUpdate from \"log-update\";\nimport TablePkg from \"cli-table3\";\nimport { select, input } from \"@inquirer/prompts\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { scanProjectForEnvKeys } from \"./scan.js\";\nimport { generateEnvDocsWithOpenAI } from \"./ai.js\";\n\n// cli-table3 interop safety (works in ESM + CJS environments)\nconst Table: any = (TablePkg as any).default ?? (TablePkg as any);\n\ntype Step = {\n title: string;\n status: \"pending\" | \"running\" | \"done\" | \"fail\";\n detail?: string;\n};\n\nconst MODELS = [\n \"gpt-5\",\n \"gpt-5-mini\",\n \"gpt-5-nano\",\n \"gpt-4.1\",\n \"gpt-4.1-mini\",\n \"gpt-4.1-nano\",\n] as const;\n\ntype ModelName = (typeof MODELS)[number];\n\nfunction getPackageVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n const pkgPath = path.resolve(__dirname, \"../package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n return pkg.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction renderHeader() {\n const body = [\n pc.bold(`Asyq v${getPackageVersion()}`),\n pc.dim(\"Generate .env.example from your project’s env usage\"),\n pc.dim(\"Created by @thev1ndu\"),\n ].join(\"\\n\");\n\n console.log(\n boxen(body, {\n padding: 1,\n borderStyle: \"round\",\n borderColor: \"cyan\",\n })\n );\n console.log(\"\");\n}\n\nfunction icon(status: Step[\"status\"]) {\n if (status === \"done\") return pc.green(\"✓\");\n if (status === \"fail\") return pc.red(\"✗\");\n if (status === \"running\") return pc.cyan(\"•\");\n return pc.dim(\"•\");\n}\n\nfunction renderSteps(steps: Step[]) {\n logUpdate(\n steps\n .map((s) => {\n const left = `${icon(s.status)} ${s.title}`;\n const right = s.detail ? pc.dim(s.detail) : \"\";\n return right ? `${left} ${right}` : left;\n })\n .join(\"\\n\")\n );\n}\n\nfunction finishSteps() {\n logUpdate.done();\n}\n\nfunction fail(message: string, hint?: string): never {\n console.error(pc.red(message));\n if (hint) console.error(pc.dim(hint));\n process.exit(1);\n}\n\nasync function pickMode(): Promise<\"default\" | \"ai\"> {\n return await select({\n message: \"How would you like to generate .env.example?\",\n choices: [\n { name: \"Default\", value: \"default\" },\n { name: \"AI-assisted\", value: \"ai\" },\n ],\n });\n}\n\nasync function pickModel(): Promise<ModelName> {\n return await select({\n message: \"Select an AI model\",\n default: \"gpt-4.1-mini\",\n choices: MODELS.map((m) => ({ name: m, value: m })),\n });\n}\n\nasync function getApiKey(): Promise<string> {\n const envKey = process.env.OPENAI_API_KEY?.trim();\n if (envKey) return envKey;\n\n const key = await input({\n message: \"Enter OpenAI API key (not saved)\",\n validate: (v) => v.trim().length > 0 || \"API key cannot be empty\",\n });\n\n return key.trim();\n}\n\nconst program = new Command();\n\nprogram\n .name(\"asyq\")\n .description(\"Generate .env.example by scanning your project for env usage\")\n .version(`v${getPackageVersion()}`);\n\nprogram\n .command(\"init\")\n .description(\"Scan project and generate .env.example\")\n .option(\"--root <dir>\", \"Project root to scan\", \".\")\n .option(\"--out <file>\", \"Output file\", \".env.example\")\n .option(\"--force\", \"Overwrite output if it exists\")\n .option(\n \"--include-lowercase\",\n \"Include lowercase/mixed-case keys (not recommended)\"\n )\n .option(\"--debug\", \"Print scan diagnostics\")\n .action(async (opts) => {\n renderHeader();\n\n const root = path.resolve(process.cwd(), opts.root);\n const outFile = path.resolve(process.cwd(), opts.out);\n\n if (fs.existsSync(outFile) && !opts.force) {\n fail(`Output already exists: ${opts.out}`, \"Use --force to overwrite.\");\n }\n\n const mode = await pickMode();\n const model: ModelName | null = mode === \"ai\" ? await pickModel() : null;\n\n const steps: Step[] = [\n { title: \"Preparing\", status: \"running\", detail: `root: ${opts.root}` },\n { title: \"Scanning project files\", status: \"pending\" },\n {\n title: \"Writing .env.example\",\n status: \"pending\",\n detail: mode === \"ai\" ? `AI (${model})` : \"Default\",\n },\n ];\n\n renderSteps(steps);\n\n steps[0].status = \"done\";\n steps[1].status = \"running\";\n renderSteps(steps);\n\n const scanSpinner = ora({\n text: \"Scanning for env keys\",\n spinner: \"dots\",\n }).start();\n\n const res = scanProjectForEnvKeys({\n rootDir: root,\n includeLowercase: !!opts.includeLowercase,\n });\n\n scanSpinner.stop();\n\n steps[1].status = \"done\";\n steps[1].detail = `${res.filesScanned} files scanned`;\n steps[2].status = \"running\";\n renderSteps(steps);\n\n if (opts.debug) {\n console.log(pc.dim(\"\"));\n console.log(pc.dim(\"Diagnostics\"));\n console.log(pc.dim(` root: ${opts.root}`));\n console.log(pc.dim(` files scanned: ${res.filesScanned}`));\n console.log(pc.dim(` keys found: ${res.keys.size}`));\n console.log(pc.dim(\"\"));\n }\n\n if (res.keys.size === 0) {\n steps[2].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\n \"No environment variables found.\",\n \"Ensure your code uses process.env.KEY or ${KEY}.\"\n );\n }\n\n const keys = [...res.keys].sort((a, b) => a.localeCompare(b));\n\n // Default content\n let content = keys.map((k) => `${k}=`).join(\"\\n\") + \"\\n\";\n\n if (mode === \"ai\" && model) {\n const apiKey = await getApiKey();\n\n const aiSpinner = ora({\n text: \"Generating AI guidance\",\n spinner: \"dots\",\n }).start();\n\n try {\n const docs = await generateEnvDocsWithOpenAI({\n apiKey,\n model,\n projectHint:\n \"Write practical guidance for developers setting env vars.\",\n contexts: res.contexts,\n keys,\n });\n\n aiSpinner.stop();\n\n const byKey = new Map(docs.map((d) => [d.key, d]));\n\n content =\n keys\n .map((k) => {\n const d = byKey.get(k);\n if (!d) return `${k}=\\n`;\n\n const secretNote = d.is_secret\n ? \"Secret value. Do not commit.\"\n : \"Non-secret value (verify before committing).\";\n\n return [\n `# ${d.key}`,\n `# ${d.description}`,\n `# Where to get it: ${d.where_to_get}`,\n `# ${secretNote}`,\n `${d.key}=${d.example_value || \"\"}`,\n \"\",\n ].join(\"\\n\");\n })\n .join(\"\\n\")\n .trimEnd() + \"\\n\";\n } catch (e: any) {\n aiSpinner.stop();\n steps[2].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\"AI generation failed.\", e?.message ?? String(e));\n }\n }\n\n fs.writeFileSync(outFile, content, \"utf8\");\n\n steps[2].status = \"done\";\n renderSteps(steps);\n finishSteps();\n\n const table = new Table({\n style: { head: [], border: [] },\n colWidths: [18, 60],\n wordWrap: true,\n });\n\n table.push(\n [pc.dim(\"Output\"), pc.cyan(opts.out)],\n [pc.dim(\"Keys\"), pc.cyan(String(keys.length))],\n [pc.dim(\"Mode\"), pc.cyan(mode === \"ai\" ? `AI (${model})` : \"Default\")]\n );\n\n console.log(\"\");\n console.log(pc.bold(\"Complete\"));\n console.log(table.toString());\n console.log(pc.dim(\"Next steps\"));\n console.log(pc.dim(` 1) Fill values in ${opts.out}`));\n console.log(pc.dim(\" 2) Copy to .env (do not commit secrets)\"));\n console.log(\"\");\n });\n\nprogram.parse(process.argv);\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type ScanOptions = {\n rootDir: string;\n includeLowercase?: boolean;\n maxContextPerKey?: number;\n};\n\nexport type KeyContext = { file: string; line: number; snippet: string };\n\nexport type ScanResult = {\n keys: Set<string>;\n filesScanned: number;\n contexts: Record<string, KeyContext[]>;\n};\n\nconst IGNORE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \"out\",\n \"coverage\",\n \".turbo\",\n \".cache\",\n]);\n\nconst ENV_KEY_RE_STRICT = /^[A-Z][A-Z0-9_]*$/;\nconst ENV_KEY_RE_LOOSE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport function scanProjectForEnvKeys(opts: ScanOptions): ScanResult {\n const root = opts.rootDir;\n const maxCtx = opts.maxContextPerKey ?? 2;\n\n const keyOk = (k: string) =>\n (opts.includeLowercase ? ENV_KEY_RE_LOOSE : ENV_KEY_RE_STRICT).test(k);\n\n const exts = new Set([\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".json\",\n \".yml\",\n \".yaml\",\n \".toml\",\n ]);\n\n const keys = new Set<string>();\n const contexts: Record<string, KeyContext[]> = {};\n let filesScanned = 0;\n\n walk(root);\n\n return { keys, filesScanned, contexts };\n\n function addCtx(key: string, relFile: string, line: number, snippet: string) {\n if (!contexts[key]) contexts[key] = [];\n if (contexts[key].length >= maxCtx) return;\n contexts[key].push({\n file: relFile,\n line,\n snippet: snippet.trim().slice(0, 220),\n });\n }\n\n function walk(dir: string) {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (!IGNORE_DIRS.has(entry.name)) walk(full);\n continue;\n }\n\n const isEnvFile = entry.name === \".env\" || entry.name.startsWith(\".env.\");\n const ext = path.extname(entry.name);\n\n if (!isEnvFile && !exts.has(ext)) continue;\n\n const content = safeRead(full);\n if (!content) continue;\n\n filesScanned++;\n\n const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n\n if (isEnvFile) {\n extractFromEnvFile(content, rel, keys, addCtx, keyOk);\n } else {\n extractFromCodeAndConfigs(content, rel, keys, addCtx, keyOk);\n }\n }\n }\n}\n\nfunction extractFromEnvFile(\n text: string,\n relFile: string,\n keys: Set<string>,\n addCtx: (key: string, relFile: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n const lines = text.split(/\\r?\\n/);\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n const m = ln.match(/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (!m) continue;\n const k = m[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, relFile, i + 1, ln);\n }\n}\n\nfunction extractFromCodeAndConfigs(\n text: string,\n relFile: string,\n keys: Set<string>,\n addCtx: (key: string, relFile: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n const lines = text.split(/\\r?\\n/);\n\n const patterns: RegExp[] = [\n /\\bprocess(?:\\?\\.)?\\.env(?:\\?\\.)?\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n /\\bprocess(?:\\?\\.)?\\.env\\[\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\]/g,\n /\\bimport\\.meta\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n /\\bDeno\\.env\\.get\\(\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\)/g,\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g,\n ];\n\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n\n for (const re of patterns) {\n re.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = re.exec(ln))) {\n const k = match[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, relFile, i + 1, ln);\n }\n }\n }\n}\n\nfunction safeRead(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n","export type AIEnvDoc = {\n key: string;\n description: string;\n where_to_get: string;\n example_value: string;\n is_secret: boolean;\n};\n\nexport type AIGenerateOptions = {\n apiKey: string;\n model: string;\n projectHint?: string;\n contexts: Record<string, { file: string; line: number; snippet: string }[]>;\n keys: string[];\n};\n\nconst JSON_SCHEMA = {\n name: \"env_docs\",\n strict: true,\n schema: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n key: { type: \"string\" },\n description: { type: \"string\" },\n where_to_get: { type: \"string\" },\n example_value: { type: \"string\" },\n is_secret: { type: \"boolean\" },\n },\n required: [\n \"key\",\n \"description\",\n \"where_to_get\",\n \"example_value\",\n \"is_secret\",\n ],\n },\n },\n },\n required: [\"items\"],\n },\n} as const;\n\nfunction buildInput(opts: AIGenerateOptions) {\n const lines = opts.keys.map((k) => {\n const ctx = opts.contexts[k]?.[0];\n const seenAt = ctx ? `${ctx.file}:${ctx.line}` : \"unknown\";\n const snippet = ctx ? ctx.snippet : \"\";\n return `- ${k}\\n seen_at: ${seenAt}\\n snippet: ${snippet}`;\n });\n\n const system = [\n \"You generate documentation for environment variables.\",\n \"Return ONLY JSON that matches the provided JSON Schema.\",\n \"Do not include markdown or extra text.\",\n \"Never output real secrets. Use safe placeholders.\",\n \"Keep descriptions short and practical.\",\n \"where_to_get must be actionable (dashboard, secret manager, CI, local service, etc.).\",\n ].join(\" \");\n\n const user = [\n opts.projectHint ? `Project hint: ${opts.projectHint}` : \"\",\n \"Variables:\",\n ...lines,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n\n return [\n { role: \"system\", content: system },\n { role: \"user\", content: user },\n ];\n}\n\nfunction extractTextFromResponses(data: any): string {\n if (typeof data?.output_text === \"string\" && data.output_text.trim())\n return data.output_text;\n\n const out = data?.output;\n if (Array.isArray(out)) {\n for (const item of out) {\n const content = item?.content;\n if (!Array.isArray(content)) continue;\n for (const c of content) {\n if (typeof c?.text === \"string\" && c.text.trim()) return c.text;\n }\n }\n }\n return \"\";\n}\n\nfunction tryParseJsonLoose(raw: string): any | null {\n // First try direct parse\n try {\n return JSON.parse(raw);\n } catch {\n // Try to extract the first {...} block\n const m = raw.match(/\\{[\\s\\S]*\\}/);\n if (!m) return null;\n try {\n return JSON.parse(m[0]);\n } catch {\n return null;\n }\n }\n}\n\nexport async function generateEnvDocsWithOpenAI(\n opts: AIGenerateOptions\n): Promise<AIEnvDoc[]> {\n const input = buildInput(opts);\n\n const res = await fetch(\"https://api.openai.com/v1/responses\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${opts.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: opts.model,\n input,\n text: {\n format: {\n type: \"json_schema\",\n ...JSON_SCHEMA,\n },\n },\n }),\n });\n\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`OpenAI request failed (${res.status}): ${text}`);\n }\n\n const data: any = await res.json();\n const raw = extractTextFromResponses(data).trim();\n\n const parsed = tryParseJsonLoose(raw);\n if (!parsed) {\n throw new Error(\n \"AI output was not valid JSON. Try again, or use a different model.\"\n );\n }\n\n const items = Array.isArray(parsed?.items) ? parsed.items : [];\n return items\n .map((x: any) => ({\n key: String(x.key ?? \"\"),\n description: String(x.description ?? \"\"),\n where_to_get: String(x.where_to_get ?? \"\"),\n example_value: String(x.example_value ?? \"\"),\n is_secret: Boolean(x.is_secret),\n }))\n .filter((x: AIEnvDoc) => x.key.length > 0);\n}\n"],"mappings":"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","names":["fs","path","input","path","fs"]}
|