asyq 8.0.17 → 8.1.0

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 CHANGED
@@ -411,33 +411,75 @@ function detectWorkspaces(rootAbs) {
411
411
  }
412
412
  return [...found].sort((a, b) => a.localeCompare(b));
413
413
  }
414
+ async function pickWorkspaces(workspaces) {
415
+ const picked = await p.multiselect({
416
+ message: "Select workspaces to generate .env.example for",
417
+ options: workspaces.map((w) => ({ label: w, value: w })),
418
+ required: false
419
+ });
420
+ if (p.isCancel(picked)) {
421
+ p.cancel("Operation cancelled");
422
+ process.exit(0);
423
+ }
424
+ return picked ?? [];
425
+ }
414
426
  var program = new Command();
415
427
  program.name("asyq").description("Generate .env.example by scanning your project for env usage").version(`v${getPackageVersion()}`);
416
428
  program.command("init").description("Scan project and generate .env.example").option("--root <dir>", "Project root to scan", ".").option("--out <file>", "Output file name", ".env.example").option("--force", "Overwrite output if it exists").option(
417
429
  "--include-lowercase",
418
430
  "Include lowercase/mixed-case keys (not recommended)"
419
- ).option("--debug", "Print scan diagnostics").option("--monorepo", "Generate .env.example for root + each workspace").action(async (opts) => {
431
+ ).option("--debug", "Print scan diagnostics").option("--monorepo", "Generate .env.example for root + each workspace").option(
432
+ "--select",
433
+ "In monorepo mode: interactively choose which workspaces to generate for"
434
+ ).option(
435
+ "--workspaces <list>",
436
+ "In monorepo mode: comma-separated workspace list to generate for"
437
+ ).option("--no-root", "In monorepo mode: skip generating for repo root").action(async (opts) => {
420
438
  p.intro(pc.cyan(`
421
439
  Asyq v${getPackageVersion()} Created by @thev1ndu`));
422
440
  const rootAbs = path2.resolve(process.cwd(), opts.root);
423
441
  const outName = String(opts.out || ".env.example");
424
442
  const mode = await pickMode();
425
443
  const model = mode === "ai" ? await pickModel() : null;
426
- const targets = [
427
- { label: "root", dirAbs: rootAbs }
428
- ];
444
+ const targets = [];
445
+ if (opts.root !== false) {
446
+ targets.push({ label: "root", dirAbs: rootAbs });
447
+ }
429
448
  if (opts.monorepo) {
430
449
  const workspaces = detectWorkspaces(rootAbs);
431
- for (const rel of workspaces) {
432
- targets.push({ label: rel, dirAbs: path2.join(rootAbs, rel) });
450
+ if (workspaces.length === 0) {
451
+ p.note(
452
+ "No workspaces detected (pnpm-workspace.yaml / package.json workspaces / apps/* / packages/*).",
453
+ "Monorepo"
454
+ );
455
+ } else {
456
+ let selected = workspaces;
457
+ if (opts.workspaces) {
458
+ const allow = new Set(
459
+ String(opts.workspaces).split(",").map((s) => s.trim()).filter(Boolean)
460
+ );
461
+ selected = workspaces.filter((w) => allow.has(w));
462
+ const missing = [...allow].filter((x) => !workspaces.includes(x));
463
+ if (missing.length) {
464
+ p.note(missing.join("\n"), "Unknown workspaces ignored");
465
+ }
466
+ } else if (opts.select) {
467
+ selected = await pickWorkspaces(workspaces);
468
+ }
469
+ for (const rel of selected) {
470
+ targets.push({ label: rel, dirAbs: path2.join(rootAbs, rel) });
471
+ }
433
472
  }
434
473
  }
474
+ if (targets.length === 0) {
475
+ fail(
476
+ "No targets selected. Tip: remove --no-root or select at least one workspace."
477
+ );
478
+ }
435
479
  let apiKey = "";
436
480
  if (mode === "ai" && model) {
437
481
  apiKey = await getApiKey();
438
- if (!apiKey) {
439
- fail("OpenAI API key is required for AI-assisted mode.");
440
- }
482
+ if (!apiKey) fail("OpenAI API key is required for AI-assisted mode.");
441
483
  }
442
484
  const results = [];
443
485
  for (const t of targets) {
@@ -468,11 +510,16 @@ Asyq v${getPackageVersion()} Created by @thev1ndu`));
468
510
  );
469
511
  }
470
512
  if (res.keys.size === 0) {
513
+ p.note(
514
+ `No env vars detected in ${t.label}. Skipping ${outRelFromRoot}`,
515
+ "Nothing to write"
516
+ );
471
517
  results.push({
472
518
  target: t.label,
473
519
  outRel: outRelFromRoot,
474
520
  keys: 0,
475
- files: res.filesScanned
521
+ files: res.filesScanned,
522
+ wrote: false
476
523
  });
477
524
  continue;
478
525
  }
@@ -517,14 +564,16 @@ Asyq v${getPackageVersion()} Created by @thev1ndu`));
517
564
  target: t.label,
518
565
  outRel: outRelFromRoot,
519
566
  keys: keys.length,
520
- files: res.filesScanned
567
+ files: res.filesScanned,
568
+ wrote: true
521
569
  });
522
570
  }
523
- const summary = results.map(
524
- (r) => `${pc.cyan(r.target.padEnd(20))} ${pc.green(
571
+ const summary = results.map((r) => {
572
+ const status = r.wrote ? pc.green("wrote") : pc.yellow("skipped");
573
+ return `${pc.cyan(r.target.padEnd(20))} ${pc.green(
525
574
  String(r.keys).padStart(3)
526
- )} keys \u2192 ${pc.dim(r.outRel)}`
527
- ).join("\n");
575
+ )} keys \u2192 ${pc.dim(r.outRel)} ${pc.dim(`(${status})`)}`;
576
+ }).join("\n");
528
577
  p.note(summary, "Generated");
529
578
  p.outro(pc.green("\u2713 All done!"));
530
579
  });
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 { fileURLToPath } from \"node:url\";\nimport * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\n\nimport { scanProjectForEnvKeys } from \"./scan.js\";\nimport { generateEnvDocsWithOpenAI } from \"./ai.js\";\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 fail(message: string): never {\n p.outro(pc.red(message));\n process.exit(1);\n}\n\nasync function pickMode(): Promise<\"default\" | \"ai\"> {\n const mode = await p.select({\n message: \"How would you like to generate .env.example?\",\n options: [\n { label: \"Default (fast)\", value: \"default\" },\n { label: \"AI-assisted (adds descriptions)\", value: \"ai\" },\n ],\n });\n\n if (p.isCancel(mode)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return mode as \"default\" | \"ai\";\n}\n\nasync function pickModel(): Promise<ModelName> {\n const model = await p.select({\n message: \"Select an AI model\",\n initialValue: \"gpt-4.1-mini\",\n options: MODELS.map((m) => ({ label: m, value: m })),\n });\n\n if (p.isCancel(model)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return model as ModelName;\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 p.password({\n message: \"Enter OpenAI API key (not saved)\",\n validate: (v) =>\n v.trim().length > 0 ? undefined : \"API key cannot be empty\",\n });\n\n if (p.isCancel(key)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return key.trim();\n}\n\n/* ----------------------------- Monorepo support ---------------------------- */\n\nfunction readWorkspaceGlobs(rootAbs: string): string[] {\n const globs: string[] = [];\n\n // pnpm-workspace.yaml\n const pnpmWs = path.join(rootAbs, \"pnpm-workspace.yaml\");\n if (fs.existsSync(pnpmWs)) {\n const txt = fs.readFileSync(pnpmWs, \"utf8\");\n for (const line of txt.split(/\\r?\\n/)) {\n const m = line.match(/^\\s*-\\s*[\"']?([^\"']+)[\"']?\\s*$/);\n if (m) globs.push(m[1].trim());\n }\n }\n\n // package.json workspaces\n const pkgPath = path.join(rootAbs, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n const ws = pkg?.workspaces;\n if (Array.isArray(ws)) globs.push(...ws);\n if (ws && Array.isArray(ws.packages)) globs.push(...ws.packages);\n } catch {\n // ignore\n }\n }\n\n return [...new Set(globs)].filter(Boolean);\n}\n\nfunction expandSimpleGlob(rootAbs: string, pattern: string): string[] {\n const norm = pattern.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n\n if (!norm.includes(\"*\")) {\n const abs = path.join(rootAbs, norm);\n return fs.existsSync(abs) && fs.statSync(abs).isDirectory() ? [norm] : [];\n }\n\n const m = norm.match(/^([^*]+)\\/\\*$/);\n if (!m) return [];\n\n const baseRel = m[1].replace(/\\/+$/, \"\");\n const baseAbs = path.join(rootAbs, baseRel);\n if (!fs.existsSync(baseAbs) || !fs.statSync(baseAbs).isDirectory()) return [];\n\n const out: string[] = [];\n for (const name of fs.readdirSync(baseAbs)) {\n const rel = `${baseRel}/${name}`;\n const abs = path.join(rootAbs, rel);\n if (!fs.statSync(abs).isDirectory()) continue;\n if (fs.existsSync(path.join(abs, \"package.json\"))) out.push(rel);\n }\n return out;\n}\n\nfunction detectWorkspaces(rootAbs: string): string[] {\n const globs = readWorkspaceGlobs(rootAbs);\n const found = new Set<string>();\n\n for (const g of globs) {\n for (const rel of expandSimpleGlob(rootAbs, g)) found.add(rel);\n }\n\n // Turbo-style fallback if no globs detected\n if (found.size === 0) {\n for (const base of [\"apps\", \"packages\"]) {\n const baseAbs = path.join(rootAbs, base);\n if (!fs.existsSync(baseAbs) || !fs.statSync(baseAbs).isDirectory())\n continue;\n for (const name of fs.readdirSync(baseAbs)) {\n const rel = `${base}/${name}`;\n const abs = path.join(rootAbs, rel);\n if (!fs.statSync(abs).isDirectory()) continue;\n if (fs.existsSync(path.join(abs, \"package.json\"))) found.add(rel);\n }\n }\n }\n\n return [...found].sort((a, b) => a.localeCompare(b));\n}\n\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 name\", \".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 .option(\"--monorepo\", \"Generate .env.example for root + each workspace\")\n .action(async (opts) => {\n p.intro(pc.cyan(`\\nAsyq v${getPackageVersion()} Created by @thev1ndu`));\n\n const rootAbs = path.resolve(process.cwd(), opts.root);\n const outName = String(opts.out || \".env.example\");\n\n const mode = await pickMode();\n const model: ModelName | null = mode === \"ai\" ? await pickModel() : null;\n\n const targets: { label: string; dirAbs: string }[] = [\n { label: \"root\", dirAbs: rootAbs },\n ];\n\n if (opts.monorepo) {\n const workspaces = detectWorkspaces(rootAbs);\n for (const rel of workspaces) {\n targets.push({ label: rel, dirAbs: path.join(rootAbs, rel) });\n }\n }\n\n // API key once for all targets (AI mode)\n let apiKey = \"\";\n if (mode === \"ai\" && model) {\n apiKey = await getApiKey();\n if (!apiKey) {\n fail(\"OpenAI API key is required for AI-assisted mode.\");\n }\n }\n\n const results: Array<{\n target: string;\n outRel: string;\n keys: number;\n files: number;\n }> = [];\n\n for (const t of targets) {\n const outFileAbs = path.join(t.dirAbs, outName);\n const outRelFromRoot =\n path.relative(rootAbs, outFileAbs).replace(/\\\\/g, \"/\") || outName;\n\n if (fs.existsSync(outFileAbs) && !opts.force) {\n fail(\n `Output already exists: ${outRelFromRoot}. Use --force to overwrite.`\n );\n }\n\n const s = p.spinner();\n s.start(`Scanning ${t.label} for environment variables`);\n\n const res = scanProjectForEnvKeys({\n rootDir: t.dirAbs,\n includeLowercase: !!opts.includeLowercase,\n });\n\n s.stop(\n `Scan complete: ${t.label} (${res.filesScanned} files, ${res.keys.size} keys)`\n );\n\n if (opts.debug) {\n p.note(\n [\n `dir: ${t.dirAbs}`,\n `files scanned: ${res.filesScanned}`,\n `keys found: ${res.keys.size}`,\n ].join(\"\\n\"),\n `${t.label} diagnostics`\n );\n }\n\n if (res.keys.size === 0) {\n results.push({\n target: t.label,\n outRel: outRelFromRoot,\n keys: 0,\n files: res.filesScanned,\n });\n continue;\n }\n\n const keys = [...res.keys].sort((a, b) => a.localeCompare(b));\n\n let content = keys.map((k) => `${k}=`).join(\"\\n\") + \"\\n\";\n\n if (mode === \"ai\" && model) {\n const aiSpinner = p.spinner();\n aiSpinner.start(`Writing .env.example documentation for ${t.label}`);\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 `Documented ${keys.length} env variables for ${t.label}`\n );\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(`Failed to write documentation for ${t.label}`);\n fail(e?.message ?? String(e));\n }\n }\n\n fs.writeFileSync(outFileAbs, content, \"utf8\");\n\n results.push({\n target: t.label,\n outRel: outRelFromRoot,\n keys: keys.length,\n files: res.filesScanned,\n });\n }\n\n // Summary table\n const summary = results\n .map(\n (r) =>\n `${pc.cyan(r.target.padEnd(20))} ${pc.green(\n String(r.keys).padStart(3)\n )} keys → ${pc.dim(r.outRel)}`\n )\n .join(\"\\n\");\n\n p.note(summary, \"Generated\");\n p.outro(pc.green(\"✓ All done!\"));\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 \".vercel\",\n \".netlify\",\n]);\n\n// ENV VARS MUST BE EXPLICIT + UPPERCASE\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 const seenInCode = new Set<string>();\n\n walk(root);\n\n // Only keep keys that were found in actual code, not just .env files\n // Normalize to uppercase for comparison\n const finalKeys = new Set<string>();\n const seenInCodeUpper = new Set([...seenInCode].map((k) => k.toUpperCase()));\n\n for (const key of keys) {\n if (seenInCodeUpper.has(key.toUpperCase())) {\n finalKeys.add(key);\n }\n }\n\n // Add any code-only keys that weren't in .env files\n for (const codeKey of seenInCode) {\n const upper = codeKey.toUpperCase();\n let found = false;\n for (const key of finalKeys) {\n if (key.toUpperCase() === upper) {\n found = true;\n break;\n }\n }\n if (!found && keyOk(codeKey)) {\n finalKeys.add(codeKey);\n }\n }\n\n return { keys: finalKeys, 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 const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n\n if (isEnvFile) {\n extractFromEnvFile(content, rel, keys, addCtx, keyOk);\n } else {\n extractFromCode(content, rel, keys, seenInCode, 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\n // Skip comments and empty lines\n if (!ln.trim() || ln.trim().startsWith(\"#\")) continue;\n\n // Match variable assignments\n const m = ln.match(/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (!m) continue;\n\n const k = m[1];\n if (!keyOk(k)) continue;\n\n keys.add(k);\n addCtx(k, relFile, i + 1, ln);\n }\n}\n\nfunction extractFromCode(\n text: string,\n relFile: string,\n keys: Set<string>,\n seenInCode: 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 // JavaScript/TypeScript environment variable patterns\n const patterns: RegExp[] = [\n // process.env.KEY or process?.env?.KEY\n /\\bprocess(?:\\?\\.|\\.)env(?:\\?\\.|\\.)([A-Za-z_][A-Za-z0-9_]*)\\b/gi,\n // process.env[\"KEY\"] or process?.env?.[\"KEY\"]\n /\\bprocess(?:\\?\\.|\\.)env\\[\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\]/gi,\n // import.meta.env.KEY (Vite, etc.)\n /\\bimport\\.meta\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/gi,\n // Deno.env.get(\"KEY\")\n /\\bDeno\\.env\\.get\\(\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\)/gi,\n // Bun.env.KEY\n /\\bBun\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/gi,\n ];\n\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n\n // Skip comments\n if (ln.trim().startsWith(\"//\") || ln.trim().startsWith(\"#\")) continue;\n\n for (const re of patterns) {\n re.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = re.exec(ln))) {\n const k = match[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n seenInCode.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","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 { fileURLToPath } from \"node:url\";\nimport * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\n\nimport { scanProjectForEnvKeys } from \"./scan.js\";\nimport { generateEnvDocsWithOpenAI } from \"./ai.js\";\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 fail(message: string): never {\n p.outro(pc.red(message));\n process.exit(1);\n}\n\nasync function pickMode(): Promise<\"default\" | \"ai\"> {\n const mode = await p.select({\n message: \"How would you like to generate .env.example?\",\n options: [\n { label: \"Default (fast)\", value: \"default\" },\n { label: \"AI-assisted (adds descriptions)\", value: \"ai\" },\n ],\n });\n\n if (p.isCancel(mode)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return mode as \"default\" | \"ai\";\n}\n\nasync function pickModel(): Promise<ModelName> {\n const model = await p.select({\n message: \"Select an AI model\",\n initialValue: \"gpt-4.1-mini\",\n options: MODELS.map((m) => ({ label: m, value: m })),\n });\n\n if (p.isCancel(model)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return model as ModelName;\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 p.password({\n message: \"Enter OpenAI API key (not saved)\",\n validate: (v) =>\n v.trim().length > 0 ? undefined : \"API key cannot be empty\",\n });\n\n if (p.isCancel(key)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return key.trim();\n}\n\n/* ----------------------------- Monorepo support ---------------------------- */\n\nfunction readWorkspaceGlobs(rootAbs: string): string[] {\n const globs: string[] = [];\n\n // pnpm-workspace.yaml\n const pnpmWs = path.join(rootAbs, \"pnpm-workspace.yaml\");\n if (fs.existsSync(pnpmWs)) {\n const txt = fs.readFileSync(pnpmWs, \"utf8\");\n for (const line of txt.split(/\\r?\\n/)) {\n const m = line.match(/^\\s*-\\s*[\"']?([^\"']+)[\"']?\\s*$/);\n if (m) globs.push(m[1].trim());\n }\n }\n\n // package.json workspaces\n const pkgPath = path.join(rootAbs, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n const ws = pkg?.workspaces;\n if (Array.isArray(ws)) globs.push(...ws);\n if (ws && Array.isArray(ws.packages)) globs.push(...ws.packages);\n } catch {\n // ignore\n }\n }\n\n return [...new Set(globs)].filter(Boolean);\n}\n\nfunction expandSimpleGlob(rootAbs: string, pattern: string): string[] {\n const norm = pattern.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n\n if (!norm.includes(\"*\")) {\n const abs = path.join(rootAbs, norm);\n return fs.existsSync(abs) && fs.statSync(abs).isDirectory() ? [norm] : [];\n }\n\n const m = norm.match(/^([^*]+)\\/\\*$/);\n if (!m) return [];\n\n const baseRel = m[1].replace(/\\/+$/, \"\");\n const baseAbs = path.join(rootAbs, baseRel);\n if (!fs.existsSync(baseAbs) || !fs.statSync(baseAbs).isDirectory()) return [];\n\n const out: string[] = [];\n for (const name of fs.readdirSync(baseAbs)) {\n const rel = `${baseRel}/${name}`;\n const abs = path.join(rootAbs, rel);\n if (!fs.statSync(abs).isDirectory()) continue;\n if (fs.existsSync(path.join(abs, \"package.json\"))) out.push(rel);\n }\n return out;\n}\n\nfunction detectWorkspaces(rootAbs: string): string[] {\n const globs = readWorkspaceGlobs(rootAbs);\n const found = new Set<string>();\n\n for (const g of globs) {\n for (const rel of expandSimpleGlob(rootAbs, g)) found.add(rel);\n }\n\n // Turbo-style fallback if no globs detected\n if (found.size === 0) {\n for (const base of [\"apps\", \"packages\"]) {\n const baseAbs = path.join(rootAbs, base);\n if (!fs.existsSync(baseAbs) || !fs.statSync(baseAbs).isDirectory())\n continue;\n for (const name of fs.readdirSync(baseAbs)) {\n const rel = `${base}/${name}`;\n const abs = path.join(rootAbs, rel);\n if (!fs.statSync(abs).isDirectory()) continue;\n if (fs.existsSync(path.join(abs, \"package.json\"))) found.add(rel);\n }\n }\n }\n\n return [...found].sort((a, b) => a.localeCompare(b));\n}\n\nasync function pickWorkspaces(workspaces: string[]): Promise<string[]> {\n const picked = await p.multiselect({\n message: \"Select workspaces to generate .env.example for\",\n options: workspaces.map((w) => ({ label: w, value: w })),\n required: false,\n });\n\n if (p.isCancel(picked)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return (picked as string[]) ?? [];\n}\n\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 name\", \".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 .option(\"--monorepo\", \"Generate .env.example for root + each workspace\")\n .option(\n \"--select\",\n \"In monorepo mode: interactively choose which workspaces to generate for\"\n )\n .option(\n \"--workspaces <list>\",\n \"In monorepo mode: comma-separated workspace list to generate for\"\n )\n .option(\"--no-root\", \"In monorepo mode: skip generating for repo root\")\n .action(async (opts) => {\n p.intro(pc.cyan(`\\nAsyq v${getPackageVersion()} Created by @thev1ndu`));\n\n const rootAbs = path.resolve(process.cwd(), opts.root);\n const outName = String(opts.out || \".env.example\");\n\n const mode = await pickMode();\n const model: ModelName | null = mode === \"ai\" ? await pickModel() : null;\n\n const targets: { label: string; dirAbs: string }[] = [];\n\n // Root target (default on, unless --no-root)\n if (opts.root !== false) {\n targets.push({ label: \"root\", dirAbs: rootAbs });\n }\n\n // Monorepo targets\n if (opts.monorepo) {\n const workspaces = detectWorkspaces(rootAbs);\n\n if (workspaces.length === 0) {\n p.note(\n \"No workspaces detected (pnpm-workspace.yaml / package.json workspaces / apps/* / packages/*).\",\n \"Monorepo\"\n );\n } else {\n let selected = workspaces;\n\n if (opts.workspaces) {\n const allow = new Set(\n String(opts.workspaces)\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n );\n selected = workspaces.filter((w) => allow.has(w));\n const missing = [...allow].filter((x) => !workspaces.includes(x));\n if (missing.length) {\n p.note(missing.join(\"\\n\"), \"Unknown workspaces ignored\");\n }\n } else if (opts.select) {\n selected = await pickWorkspaces(workspaces);\n }\n\n for (const rel of selected) {\n targets.push({ label: rel, dirAbs: path.join(rootAbs, rel) });\n }\n }\n }\n\n if (targets.length === 0) {\n fail(\n \"No targets selected. Tip: remove --no-root or select at least one workspace.\"\n );\n }\n\n // API key once for all targets (AI mode)\n let apiKey = \"\";\n if (mode === \"ai\" && model) {\n apiKey = await getApiKey();\n if (!apiKey) fail(\"OpenAI API key is required for AI-assisted mode.\");\n }\n\n const results: Array<{\n target: string;\n outRel: string;\n keys: number;\n files: number;\n wrote: boolean;\n }> = [];\n\n for (const t of targets) {\n const outFileAbs = path.join(t.dirAbs, outName);\n const outRelFromRoot =\n path.relative(rootAbs, outFileAbs).replace(/\\\\/g, \"/\") || outName;\n\n if (fs.existsSync(outFileAbs) && !opts.force) {\n fail(\n `Output already exists: ${outRelFromRoot}. Use --force to overwrite.`\n );\n }\n\n const s = p.spinner();\n s.start(`Scanning ${t.label} for environment variables`);\n\n const res = scanProjectForEnvKeys({\n rootDir: t.dirAbs,\n includeLowercase: !!opts.includeLowercase,\n });\n\n s.stop(\n `Scan complete: ${t.label} (${res.filesScanned} files, ${res.keys.size} keys)`\n );\n\n if (opts.debug) {\n p.note(\n [\n `dir: ${t.dirAbs}`,\n `files scanned: ${res.filesScanned}`,\n `keys found: ${res.keys.size}`,\n ].join(\"\\n\"),\n `${t.label} diagnostics`\n );\n }\n\n // If no keys, skip writing (current behavior), but show a clear note.\n if (res.keys.size === 0) {\n p.note(\n `No env vars detected in ${t.label}. Skipping ${outRelFromRoot}`,\n \"Nothing to write\"\n );\n results.push({\n target: t.label,\n outRel: outRelFromRoot,\n keys: 0,\n files: res.filesScanned,\n wrote: false,\n });\n continue;\n }\n\n const keys = [...res.keys].sort((a, b) => a.localeCompare(b));\n let content = keys.map((k) => `${k}=`).join(\"\\n\") + \"\\n\";\n\n if (mode === \"ai\" && model) {\n const aiSpinner = p.spinner();\n aiSpinner.start(`Writing .env.example documentation for ${t.label}`);\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 `Documented ${keys.length} env variables for ${t.label}`\n );\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(`Failed to write documentation for ${t.label}`);\n fail(e?.message ?? String(e));\n }\n }\n\n fs.writeFileSync(outFileAbs, content, \"utf8\");\n\n results.push({\n target: t.label,\n outRel: outRelFromRoot,\n keys: keys.length,\n files: res.filesScanned,\n wrote: true,\n });\n }\n\n // Summary table\n const summary = results\n .map((r) => {\n const status = r.wrote ? pc.green(\"wrote\") : pc.yellow(\"skipped\");\n return `${pc.cyan(r.target.padEnd(20))} ${pc.green(\n String(r.keys).padStart(3)\n )} keys → ${pc.dim(r.outRel)} ${pc.dim(`(${status})`)}`;\n })\n .join(\"\\n\");\n\n p.note(summary, \"Generated\");\n p.outro(pc.green(\"✓ All done!\"));\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 \".vercel\",\n \".netlify\",\n]);\n\n// ENV VARS MUST BE EXPLICIT + UPPERCASE\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 const seenInCode = new Set<string>();\n\n walk(root);\n\n // Only keep keys that were found in actual code, not just .env files\n // Normalize to uppercase for comparison\n const finalKeys = new Set<string>();\n const seenInCodeUpper = new Set([...seenInCode].map((k) => k.toUpperCase()));\n\n for (const key of keys) {\n if (seenInCodeUpper.has(key.toUpperCase())) {\n finalKeys.add(key);\n }\n }\n\n // Add any code-only keys that weren't in .env files\n for (const codeKey of seenInCode) {\n const upper = codeKey.toUpperCase();\n let found = false;\n for (const key of finalKeys) {\n if (key.toUpperCase() === upper) {\n found = true;\n break;\n }\n }\n if (!found && keyOk(codeKey)) {\n finalKeys.add(codeKey);\n }\n }\n\n return { keys: finalKeys, 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 const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n\n if (isEnvFile) {\n extractFromEnvFile(content, rel, keys, addCtx, keyOk);\n } else {\n extractFromCode(content, rel, keys, seenInCode, 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\n // Skip comments and empty lines\n if (!ln.trim() || ln.trim().startsWith(\"#\")) continue;\n\n // Match variable assignments\n const m = ln.match(/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (!m) continue;\n\n const k = m[1];\n if (!keyOk(k)) continue;\n\n keys.add(k);\n addCtx(k, relFile, i + 1, ln);\n }\n}\n\nfunction extractFromCode(\n text: string,\n relFile: string,\n keys: Set<string>,\n seenInCode: 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 // JavaScript/TypeScript environment variable patterns\n const patterns: RegExp[] = [\n // process.env.KEY or process?.env?.KEY\n /\\bprocess(?:\\?\\.|\\.)env(?:\\?\\.|\\.)([A-Za-z_][A-Za-z0-9_]*)\\b/gi,\n // process.env[\"KEY\"] or process?.env?.[\"KEY\"]\n /\\bprocess(?:\\?\\.|\\.)env\\[\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\]/gi,\n // import.meta.env.KEY (Vite, etc.)\n /\\bimport\\.meta\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/gi,\n // Deno.env.get(\"KEY\")\n /\\bDeno\\.env\\.get\\(\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\)/gi,\n // Bun.env.KEY\n /\\bBun\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/gi,\n ];\n\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n\n // Skip comments\n if (ln.trim().startsWith(\"//\") || ln.trim().startsWith(\"#\")) continue;\n\n for (const re of patterns) {\n re.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = re.exec(ln))) {\n const k = match[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n seenInCode.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","path","fs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asyq",
3
- "version": "8.0.17",
3
+ "version": "8.1.0",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "env",