lynxprompt 1.2.12 → 1.2.14

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/index.js CHANGED
@@ -1545,6 +1545,251 @@ var JS_TOOL_PATTERNS = {
1545
1545
  webpack: ["webpack"],
1546
1546
  turbo: ["turbo"]
1547
1547
  };
1548
+ async function detectExtendedCommands(cwd) {
1549
+ const cmds = {
1550
+ test: [],
1551
+ testCoverage: [],
1552
+ install: [],
1553
+ dev: [],
1554
+ build: [],
1555
+ lint: [],
1556
+ format: [],
1557
+ typecheck: [],
1558
+ clean: [],
1559
+ preCommit: [],
1560
+ additional: []
1561
+ };
1562
+ const addCmd = (category, cmd, desc) => {
1563
+ if (!cmds[category].some((c) => c.cmd === cmd)) {
1564
+ cmds[category].push({ cmd, desc });
1565
+ }
1566
+ };
1567
+ const packageJsonPath = join3(cwd, "package.json");
1568
+ if (await fileExists(packageJsonPath)) {
1569
+ try {
1570
+ const content = await readFile3(packageJsonPath, "utf-8");
1571
+ const pkg = JSON.parse(content);
1572
+ if (pkg.scripts) {
1573
+ for (const [name, script] of Object.entries(pkg.scripts)) {
1574
+ const scriptStr = String(script);
1575
+ const fullCmd = `npm run ${name}`;
1576
+ if (name.match(/^test$|^test:/i) || scriptStr.includes("jest") || scriptStr.includes("vitest") || scriptStr.includes("mocha")) {
1577
+ if (name.includes("cov") || scriptStr.includes("--coverage")) {
1578
+ addCmd("testCoverage", fullCmd, `Run ${name}`);
1579
+ } else {
1580
+ addCmd("test", fullCmd, `Run ${name}`);
1581
+ }
1582
+ } else if (name.match(/^lint$|^lint:/i) || scriptStr.includes("eslint") || scriptStr.includes("biome")) {
1583
+ addCmd("lint", fullCmd, `Run ${name}`);
1584
+ } else if (name.match(/^format$|^fmt$|format:/i) || scriptStr.includes("prettier")) {
1585
+ addCmd("format", fullCmd, `Run ${name}`);
1586
+ } else if (name.match(/^build$|^build:/i) || scriptStr.includes("tsc") || scriptStr.includes("webpack") || scriptStr.includes("vite build")) {
1587
+ addCmd("build", fullCmd, `Run ${name}`);
1588
+ } else if (name.match(/^dev$|^start$|^serve$/i)) {
1589
+ addCmd("dev", fullCmd, `Run ${name}`);
1590
+ } else if (name.match(/^typecheck$|^type-check$|^types$|^check:types/i) || scriptStr.includes("tsc --noEmit")) {
1591
+ addCmd("typecheck", fullCmd, `Run ${name}`);
1592
+ } else if (name.match(/^clean$|^clean:/i) || scriptStr.includes("rimraf") || scriptStr.includes("rm -rf")) {
1593
+ addCmd("clean", fullCmd, `Run ${name}`);
1594
+ } else if (name.match(/^prepare$|^precommit$|^pre-commit$|^husky/i)) {
1595
+ addCmd("preCommit", fullCmd, `Run ${name}`);
1596
+ } else if (name === "install" || name === "postinstall") {
1597
+ addCmd("install", fullCmd, `Run ${name}`);
1598
+ } else if (!["publish", "prepublish", "prepublishOnly", "version", "postversion"].includes(name)) {
1599
+ addCmd("additional", fullCmd, `Run ${name}`);
1600
+ }
1601
+ }
1602
+ }
1603
+ } catch {
1604
+ }
1605
+ }
1606
+ const pyprojectPath = join3(cwd, "pyproject.toml");
1607
+ if (await fileExists(pyprojectPath)) {
1608
+ try {
1609
+ const content = await readFile3(pyprojectPath, "utf-8");
1610
+ if (content.includes("pytest") || content.includes("[tool.pytest")) {
1611
+ addCmd("test", "python -m pytest tests/ -v --tb=short", "Run pytest");
1612
+ addCmd("testCoverage", "python -m pytest tests/ --cov=src --cov-report=term-missing", "Run pytest with coverage");
1613
+ }
1614
+ if (content.includes("ruff")) {
1615
+ addCmd("lint", "ruff check .", "Run ruff linter");
1616
+ addCmd("format", "ruff format .", "Run ruff formatter");
1617
+ }
1618
+ if (content.includes("black")) {
1619
+ addCmd("format", "black .", "Run black formatter");
1620
+ }
1621
+ if (content.includes("mypy")) {
1622
+ addCmd("typecheck", "mypy .", "Run mypy type checker");
1623
+ }
1624
+ const poetryScriptsMatch = content.match(/\[tool\.poetry\.scripts\]([\s\S]*?)(?=\n\[|$)/);
1625
+ if (poetryScriptsMatch) {
1626
+ const scriptLines = poetryScriptsMatch[1].split("\n").filter((l) => l.includes("="));
1627
+ for (const line of scriptLines) {
1628
+ const match = line.match(/(\w+)\s*=\s*"([^"]+)"/);
1629
+ if (match) {
1630
+ const [, name, entry] = match;
1631
+ addCmd("additional", `poetry run ${name}`, entry);
1632
+ }
1633
+ }
1634
+ }
1635
+ const poeMatch = content.match(/\[tool\.poe\.tasks\]([\s\S]*?)(?=\n\[tool\.|$)/);
1636
+ if (poeMatch) {
1637
+ const taskLines = poeMatch[1].split("\n").filter((l) => l.includes("="));
1638
+ for (const line of taskLines) {
1639
+ const match = line.match(/(\w+)\s*=\s*"([^"]+)"/);
1640
+ if (match) {
1641
+ const [, name, cmd] = match;
1642
+ if (name.match(/test/i)) {
1643
+ addCmd("test", `poe ${name}`, cmd);
1644
+ } else if (name.match(/lint/i)) {
1645
+ addCmd("lint", `poe ${name}`, cmd);
1646
+ } else if (name.match(/format/i)) {
1647
+ addCmd("format", `poe ${name}`, cmd);
1648
+ } else {
1649
+ addCmd("additional", `poe ${name}`, cmd);
1650
+ }
1651
+ }
1652
+ }
1653
+ }
1654
+ if (content.includes("fastapi") || content.includes("uvicorn")) {
1655
+ addCmd("dev", "uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload", "Run FastAPI dev server");
1656
+ }
1657
+ if (content.includes("[tool.poetry]")) {
1658
+ addCmd("install", "poetry install", "Install dependencies with Poetry");
1659
+ } else if (await fileExists(join3(cwd, "uv.lock"))) {
1660
+ addCmd("install", "uv sync", "Sync dependencies with uv");
1661
+ } else {
1662
+ addCmd("install", "pip install -r requirements.txt", "Install dependencies with pip");
1663
+ }
1664
+ } catch {
1665
+ }
1666
+ }
1667
+ const requirementsPath = join3(cwd, "requirements.txt");
1668
+ if (await fileExists(requirementsPath)) {
1669
+ try {
1670
+ const content = await readFile3(requirementsPath, "utf-8");
1671
+ if (content.includes("pytest")) {
1672
+ addCmd("test", "python -m pytest tests/ -v", "Run pytest");
1673
+ }
1674
+ addCmd("install", "pip install -r requirements.txt", "Install dependencies");
1675
+ } catch {
1676
+ }
1677
+ }
1678
+ const makefilePath = join3(cwd, "Makefile");
1679
+ if (await fileExists(makefilePath)) {
1680
+ try {
1681
+ const content = await readFile3(makefilePath, "utf-8");
1682
+ const targetMatches = content.matchAll(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(?:[a-zA-Z0-9_\- ]*)?$/gm);
1683
+ for (const match of targetMatches) {
1684
+ const target = match[1];
1685
+ const cmd = `make ${target}`;
1686
+ if (target.match(/^test$|^tests$/i)) {
1687
+ addCmd("test", cmd, `Make ${target}`);
1688
+ } else if (target.match(/^test[-_]?cov|^coverage$/i)) {
1689
+ addCmd("testCoverage", cmd, `Make ${target}`);
1690
+ } else if (target.match(/^lint$/i)) {
1691
+ addCmd("lint", cmd, `Make ${target}`);
1692
+ } else if (target.match(/^format$|^fmt$/i)) {
1693
+ addCmd("format", cmd, `Make ${target}`);
1694
+ } else if (target.match(/^build$/i)) {
1695
+ addCmd("build", cmd, `Make ${target}`);
1696
+ } else if (target.match(/^dev$|^run$|^serve$/i)) {
1697
+ addCmd("dev", cmd, `Make ${target}`);
1698
+ } else if (target.match(/^typecheck$|^types$/i)) {
1699
+ addCmd("typecheck", cmd, `Make ${target}`);
1700
+ } else if (target.match(/^clean$/i)) {
1701
+ addCmd("clean", cmd, `Make ${target}`);
1702
+ } else if (target.match(/^install$|^deps$/i)) {
1703
+ addCmd("install", cmd, `Make ${target}`);
1704
+ } else if (!["all", "default", ".PHONY", ".DEFAULT_GOAL"].includes(target)) {
1705
+ addCmd("additional", cmd, `Make ${target}`);
1706
+ }
1707
+ }
1708
+ } catch {
1709
+ }
1710
+ }
1711
+ const dockerComposePath = join3(cwd, "docker-compose.yml");
1712
+ const dockerComposeYamlPath = join3(cwd, "docker-compose.yaml");
1713
+ const composePath = await fileExists(dockerComposePath) ? dockerComposePath : await fileExists(dockerComposeYamlPath) ? dockerComposeYamlPath : null;
1714
+ if (composePath) {
1715
+ try {
1716
+ const content = await readFile3(composePath, "utf-8");
1717
+ const serviceMatches = content.matchAll(/^\s{2}([a-zA-Z_][a-zA-Z0-9_-]*):\s*$/gm);
1718
+ for (const match of serviceMatches) {
1719
+ const service = match[1];
1720
+ addCmd("additional", `docker compose up ${service}`, `Run ${service} service`);
1721
+ }
1722
+ addCmd("dev", "docker compose up", "Start all services");
1723
+ addCmd("build", "docker compose build", "Build all services");
1724
+ addCmd("clean", "docker compose down -v", "Stop and remove volumes");
1725
+ } catch {
1726
+ }
1727
+ }
1728
+ const dockerfilePath = join3(cwd, "Dockerfile");
1729
+ if (await fileExists(dockerfilePath)) {
1730
+ try {
1731
+ const content = await readFile3(dockerfilePath, "utf-8");
1732
+ const fromMatch = content.match(/FROM\s+([^\s]+)/);
1733
+ const imageName = fromMatch ? fromMatch[1].split(":")[0] : "app";
1734
+ addCmd("build", `docker build -t ${imageName} .`, "Build Docker image");
1735
+ addCmd("dev", `docker run -it --rm ${imageName}`, "Run Docker container");
1736
+ } catch {
1737
+ }
1738
+ }
1739
+ try {
1740
+ const files = await readFile3(join3(cwd, "."), "utf-8").catch(() => "");
1741
+ const dockerViewerPath = join3(cwd, "Dockerfile.viewer");
1742
+ if (await fileExists(dockerViewerPath)) {
1743
+ addCmd("build", "docker build -f Dockerfile.viewer -t app-viewer .", "Build viewer Docker image");
1744
+ }
1745
+ } catch {
1746
+ }
1747
+ const cargoPath = join3(cwd, "Cargo.toml");
1748
+ if (await fileExists(cargoPath)) {
1749
+ addCmd("build", "cargo build", "Build Rust project");
1750
+ addCmd("build", "cargo build --release", "Build release");
1751
+ addCmd("test", "cargo test", "Run Rust tests");
1752
+ addCmd("lint", "cargo clippy", "Run Clippy linter");
1753
+ addCmd("format", "cargo fmt", "Format Rust code");
1754
+ addCmd("dev", "cargo run", "Run Rust binary");
1755
+ addCmd("clean", "cargo clean", "Clean build artifacts");
1756
+ }
1757
+ const goModPath = join3(cwd, "go.mod");
1758
+ if (await fileExists(goModPath)) {
1759
+ addCmd("build", "go build", "Build Go project");
1760
+ addCmd("test", "go test ./...", "Run Go tests");
1761
+ addCmd("lint", "golangci-lint run", "Run golangci-lint");
1762
+ addCmd("format", "go fmt ./...", "Format Go code");
1763
+ addCmd("dev", "go run .", "Run Go binary");
1764
+ addCmd("clean", "go clean", "Clean build cache");
1765
+ addCmd("typecheck", "go vet ./...", "Run go vet");
1766
+ }
1767
+ const srcMainPath = join3(cwd, "src", "main.py");
1768
+ const mainPath = join3(cwd, "main.py");
1769
+ const appPath = join3(cwd, "app.py");
1770
+ if (await fileExists(srcMainPath)) {
1771
+ addCmd("dev", "python -m src.main", "Run main module");
1772
+ }
1773
+ if (await fileExists(mainPath)) {
1774
+ addCmd("dev", "python main.py", "Run main.py");
1775
+ }
1776
+ if (await fileExists(appPath)) {
1777
+ addCmd("dev", "python app.py", "Run app.py");
1778
+ }
1779
+ const schedulerPath = join3(cwd, "src", "scheduler.py");
1780
+ if (await fileExists(schedulerPath)) {
1781
+ addCmd("additional", "python -m src.scheduler", "Run scheduler");
1782
+ }
1783
+ const setupAuthPath = join3(cwd, "src", "setup_auth.py");
1784
+ if (await fileExists(setupAuthPath)) {
1785
+ addCmd("additional", "python -m src.setup_auth", "Setup authentication");
1786
+ }
1787
+ const webMainPath = join3(cwd, "src", "web", "main.py");
1788
+ if (await fileExists(webMainPath)) {
1789
+ addCmd("dev", "uvicorn src.web.main:app --host 0.0.0.0 --port 8080", "Run web viewer");
1790
+ }
1791
+ return cmds;
1792
+ }
1548
1793
  async function detectProject(cwd) {
1549
1794
  const detected = {
1550
1795
  name: null,
@@ -1831,6 +2076,7 @@ async function detectProject(cwd) {
1831
2076
  }
1832
2077
  }
1833
2078
  }
2079
+ detected.detectedCommands = await detectExtendedCommands(cwd);
1834
2080
  return detected.stack.length > 0 || detected.name ? detected : null;
1835
2081
  }
1836
2082
  async function fileExists(path2) {
@@ -1881,7 +2127,7 @@ function parseGitLabUrl(url) {
1881
2127
  return null;
1882
2128
  }
1883
2129
  var OPEN_SOURCE_LICENSES = ["mit", "apache-2.0", "gpl-3.0", "lgpl-3.0", "agpl-3.0", "bsd-2-clause", "bsd-3-clause", "mpl-2.0", "unlicense", "cc0-1.0", "isc"];
1884
- var STATIC_FILES = [".editorconfig", "CONTRIBUTING.md", "CODE_OF_CONDUCT.md", "SECURITY.md", "ROADMAP.md", ".gitignore", "LICENSE", "README.md", "ARCHITECTURE.md", "CHANGELOG.md"];
2130
+ var STATIC_FILES = [".editorconfig", "CONTRIBUTING.md", "CODE_OF_CONDUCT.md", "SECURITY.md", "ROADMAP.md", ".gitignore", ".github/FUNDING.yml", "LICENSE", "README.md", "ARCHITECTURE.md", "CHANGELOG.md"];
1885
2131
  async function detectFromGitHubApi(repoUrl) {
1886
2132
  const parsed = parseGitHubUrl(repoUrl);
1887
2133
  if (!parsed) return null;
@@ -1919,10 +2165,22 @@ async function detectFromGitHubApi(repoUrl) {
1919
2165
  const files = await filesRes.json();
1920
2166
  const fileNames = new Set(files.map((f) => f.name.toLowerCase()));
1921
2167
  for (const file of STATIC_FILES) {
2168
+ if (file.includes("/")) continue;
1922
2169
  if (files.some((f) => f.name.toLowerCase() === file.toLowerCase())) {
1923
2170
  detected.existingFiles.push(file);
1924
2171
  }
1925
2172
  }
2173
+ if (files.some((f) => f.name === ".github" && f.type === "dir")) {
2174
+ const ghFilesRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/.github`, {
2175
+ headers: { "User-Agent": "LynxPrompt-CLI" }
2176
+ });
2177
+ if (ghFilesRes.ok) {
2178
+ const ghFiles = await ghFilesRes.json();
2179
+ if (ghFiles.some((f) => f.name.toLowerCase() === "funding.yml")) {
2180
+ detected.existingFiles.push(".github/FUNDING.yml");
2181
+ }
2182
+ }
2183
+ }
1926
2184
  if (fileNames.has("dockerfile") || fileNames.has("docker-compose.yml") || fileNames.has("docker-compose.yaml")) {
1927
2185
  detected.hasDocker = true;
1928
2186
  detected.stack.push("docker");
@@ -2439,10 +2697,19 @@ var AI_BEHAVIOR_DESCRIPTIONS = {
2439
2697
  };
2440
2698
  var IMPORTANT_FILES_PATHS = {
2441
2699
  readme: "README.md",
2442
- package: "package.json or pyproject.toml",
2443
- tsconfig: "tsconfig.json or similar config",
2444
- architecture: "ARCHITECTURE.md",
2445
- contributing: "CONTRIBUTING.md"
2700
+ package_json: "package.json",
2701
+ changelog: "CHANGELOG.md",
2702
+ contributing: "CONTRIBUTING.md",
2703
+ makefile: "Makefile",
2704
+ dockerfile: "Dockerfile",
2705
+ docker_compose: "docker-compose.yml",
2706
+ env_example: ".env.example",
2707
+ openapi: "openapi.yaml / swagger.json",
2708
+ architecture_md: "ARCHITECTURE.md",
2709
+ // Legacy aliases for backwards compatibility
2710
+ package: "package.json",
2711
+ tsconfig: "tsconfig.json",
2712
+ architecture: "ARCHITECTURE.md"
2446
2713
  };
2447
2714
  var BOUNDARIES = {
2448
2715
  conservative: {
@@ -3289,10 +3556,37 @@ function generateFileContent(options, platform2) {
3289
3556
  }
3290
3557
  }
3291
3558
  const security = options.security;
3292
- if (security && (security.secretsManagement?.length || security.securityTooling?.length || security.authPatterns?.length || security.dataHandling?.length || security.additionalNotes)) {
3559
+ if (security && (security.authProviders?.length || security.secretsManagement?.length || security.securityTooling?.length || security.authPatterns?.length || security.dataHandling?.length || security.compliance?.length || security.analytics?.length || security.additionalNotes)) {
3293
3560
  if (isMarkdown || isMdc) {
3294
3561
  sections.push("## \u{1F510} Security Configuration");
3295
3562
  sections.push("");
3563
+ if (security.authProviders?.length) {
3564
+ sections.push("### Authentication Providers");
3565
+ sections.push("");
3566
+ const authProviderLabels = {
3567
+ google: "Google",
3568
+ github: "GitHub",
3569
+ microsoft: "Microsoft",
3570
+ apple: "Apple",
3571
+ facebook: "Facebook",
3572
+ twitter: "Twitter/X",
3573
+ linkedin: "LinkedIn",
3574
+ discord: "Discord",
3575
+ slack: "Slack",
3576
+ gitlab: "GitLab",
3577
+ bitbucket: "Bitbucket",
3578
+ email_password: "Email/Password",
3579
+ phone_sms: "Phone/SMS",
3580
+ magic_link: "Magic Link",
3581
+ passkeys: "Passkeys/WebAuthn",
3582
+ saml: "SAML SSO",
3583
+ ldap: "LDAP/Active Directory"
3584
+ };
3585
+ for (const p of security.authProviders) {
3586
+ sections.push(`- ${authProviderLabels[p] || p}`);
3587
+ }
3588
+ sections.push("");
3589
+ }
3296
3590
  if (security.secretsManagement?.length) {
3297
3591
  sections.push("### Secrets Management");
3298
3592
  sections.push("");
@@ -3416,6 +3710,48 @@ function generateFileContent(options, platform2) {
3416
3710
  }
3417
3711
  sections.push("");
3418
3712
  }
3713
+ if (security.compliance?.length) {
3714
+ sections.push("### Compliance Standards");
3715
+ sections.push("");
3716
+ const complianceLabels = {
3717
+ gdpr: "GDPR (EU)",
3718
+ ccpa: "CCPA (California)",
3719
+ hipaa: "HIPAA (Healthcare)",
3720
+ soc2: "SOC 2",
3721
+ pci_dss: "PCI-DSS (Payment)",
3722
+ iso27001: "ISO 27001",
3723
+ fedramp: "FedRAMP",
3724
+ none: "No specific requirements"
3725
+ };
3726
+ for (const c of security.compliance) {
3727
+ sections.push(`- ${complianceLabels[c] || c}`);
3728
+ }
3729
+ sections.push("");
3730
+ }
3731
+ if (security.analytics?.length) {
3732
+ sections.push("### Analytics Tools");
3733
+ sections.push("");
3734
+ const analyticsLabels = {
3735
+ none: "No analytics",
3736
+ umami: "Umami",
3737
+ plausible: "Plausible",
3738
+ fathom: "Fathom",
3739
+ posthog: "PostHog",
3740
+ mixpanel: "Mixpanel",
3741
+ amplitude: "Amplitude",
3742
+ google_analytics: "Google Analytics",
3743
+ segment: "Segment",
3744
+ heap: "Heap",
3745
+ matomo: "Matomo",
3746
+ simple_analytics: "Simple Analytics",
3747
+ pirsch: "Pirsch",
3748
+ countly: "Countly"
3749
+ };
3750
+ for (const a of security.analytics) {
3751
+ sections.push(`- ${analyticsLabels[a] || a}`);
3752
+ }
3753
+ sections.push("");
3754
+ }
3419
3755
  if (security.additionalNotes) {
3420
3756
  sections.push("### Additional Security Notes");
3421
3757
  sections.push("");
@@ -3424,6 +3760,9 @@ function generateFileContent(options, platform2) {
3424
3760
  }
3425
3761
  } else {
3426
3762
  sections.push("Security Configuration:");
3763
+ if (security.authProviders?.length) {
3764
+ sections.push(`- Auth Providers: ${security.authProviders.join(", ")}`);
3765
+ }
3427
3766
  if (security.secretsManagement?.length) {
3428
3767
  sections.push(`- Secrets: ${security.secretsManagement.join(", ")}`);
3429
3768
  }
@@ -3436,6 +3775,12 @@ function generateFileContent(options, platform2) {
3436
3775
  if (security.dataHandling?.length) {
3437
3776
  sections.push(`- Data: ${security.dataHandling.join(", ")}`);
3438
3777
  }
3778
+ if (security.compliance?.length) {
3779
+ sections.push(`- Compliance: ${security.compliance.join(", ")}`);
3780
+ }
3781
+ if (security.analytics?.length) {
3782
+ sections.push(`- Analytics: ${security.analytics.join(", ")}`);
3783
+ }
3439
3784
  sections.push("");
3440
3785
  }
3441
3786
  }
@@ -3587,7 +3932,7 @@ function generateYamlConfig(options, platform2) {
3587
3932
 
3588
3933
  // src/commands/wizard.ts
3589
3934
  var DRAFTS_DIR = ".lynxprompt/drafts";
3590
- var CLI_VERSION = "1.2.12";
3935
+ var CLI_VERSION = "1.2.14";
3591
3936
  async function saveDraftLocally(name, config2, stepReached) {
3592
3937
  const draftsPath = join4(process.cwd(), DRAFTS_DIR);
3593
3938
  await mkdir3(draftsPath, { recursive: true });
@@ -3857,19 +4202,42 @@ var CICD_OPTIONS = [
3857
4202
  { id: "drone", label: "Drone", icon: "\u{1F681}" },
3858
4203
  { id: "buildkite", label: "Buildkite", icon: "\u{1F9F1}" }
3859
4204
  ];
3860
- var DEPLOYMENT_TARGETS = [
4205
+ var CLOUD_TARGETS = [
3861
4206
  { id: "vercel", label: "Vercel", icon: "\u25B2 " },
3862
4207
  { id: "netlify", label: "Netlify", icon: "\u{1F310}" },
3863
- { id: "aws", label: "AWS", icon: "\u2601\uFE0F " },
3864
- { id: "gcp", label: "Google Cloud", icon: "\u{1F308}" },
3865
- { id: "azure", label: "Azure", icon: "\u{1F537}" },
3866
- { id: "docker", label: "Docker", icon: "\u{1F433}" },
3867
- { id: "kubernetes", label: "Kubernetes", icon: "\u2638\uFE0F " },
3868
- { id: "heroku", label: "Heroku", icon: "\u{1F7E3}" },
3869
- { id: "digitalocean", label: "DigitalOcean", icon: "\u{1F535}" },
4208
+ { id: "cloudflare_pages", label: "Cloudflare Pages", icon: "\u{1F536}" },
4209
+ { id: "cloudflare_workers", label: "Cloudflare Workers", icon: "\u{1F536}" },
4210
+ { id: "aws_lambda", label: "AWS Lambda", icon: "\u2601\uFE0F " },
4211
+ { id: "aws_ecs", label: "AWS ECS", icon: "\u2601\uFE0F " },
4212
+ { id: "aws_ec2", label: "AWS EC2", icon: "\u2601\uFE0F " },
4213
+ { id: "gcp_cloudrun", label: "GCP Cloud Run", icon: "\u{1F308}" },
4214
+ { id: "gcp_appengine", label: "GCP App Engine", icon: "\u{1F308}" },
4215
+ { id: "azure_appservice", label: "Azure App Service", icon: "\u{1F537}" },
4216
+ { id: "azure_functions", label: "Azure Functions", icon: "\u{1F537}" },
3870
4217
  { id: "railway", label: "Railway", icon: "\u{1F682}" },
4218
+ { id: "render", label: "Render", icon: "\u{1F3A8}" },
3871
4219
  { id: "fly", label: "Fly.io", icon: "\u2708\uFE0F " },
3872
- { id: "cloudflare", label: "Cloudflare", icon: "\u{1F536}" }
4220
+ { id: "heroku", label: "Heroku", icon: "\u{1F7E3}" },
4221
+ { id: "digitalocean_app", label: "DigitalOcean App Platform", icon: "\u{1F535}" },
4222
+ { id: "deno_deploy", label: "Deno Deploy", icon: "\u{1F995}" }
4223
+ ];
4224
+ var SELF_HOSTED_TARGETS = [
4225
+ { id: "docker", label: "Docker", icon: "\u{1F433}" },
4226
+ { id: "docker_compose", label: "Docker Compose", icon: "\u{1F433}" },
4227
+ { id: "kubernetes", label: "Kubernetes", icon: "\u2638\uFE0F " },
4228
+ { id: "k3s", label: "K3s", icon: "\u2638\uFE0F " },
4229
+ { id: "podman", label: "Podman", icon: "\u{1F9AD}" },
4230
+ { id: "bare_metal", label: "Bare Metal", icon: "\u{1F5A5}\uFE0F " },
4231
+ { id: "vm", label: "Virtual Machine", icon: "\u{1F4BB}" },
4232
+ { id: "proxmox", label: "Proxmox", icon: "\u{1F537}" },
4233
+ { id: "unraid", label: "Unraid", icon: "\u{1F7E0}" },
4234
+ { id: "truenas", label: "TrueNAS", icon: "\u{1F535}" },
4235
+ { id: "synology", label: "Synology NAS", icon: "\u{1F4C1}" },
4236
+ { id: "coolify", label: "Coolify", icon: "\u2744\uFE0F " },
4237
+ { id: "dokku", label: "Dokku", icon: "\u{1F433}" },
4238
+ { id: "caprover", label: "CapRover", icon: "\u{1F6A2}" },
4239
+ { id: "portainer", label: "Portainer", icon: "\u{1F40B}" },
4240
+ { id: "rancher", label: "Rancher", icon: "\u{1F404}" }
3873
4241
  ];
3874
4242
  var CONTAINER_REGISTRIES = [
3875
4243
  { id: "dockerhub", label: "Docker Hub", icon: "\u{1F433}" },
@@ -5446,14 +5814,14 @@ async function runInteractiveWizard(options, detected, userTier) {
5446
5814
  name: "changelogTool",
5447
5815
  message: chalk7.white("Changelog management:"),
5448
5816
  choices: [
5449
- { title: "Manual", value: "manual" },
5450
- { title: "Conventional Changelog", value: "conventional_changelog" },
5451
- { title: "Release Please", value: "release_please" },
5452
- { title: "Semantic Release", value: "semantic_release" },
5453
- { title: "Changesets", value: "changesets" },
5454
- { title: "GitHub Releases", value: "github_releases" },
5455
- { title: "Keep a Changelog", value: "keep_a_changelog" },
5456
- { title: "Other", value: "other" }
5817
+ { title: "Manual - Write CHANGELOG.md by hand", value: "manual" },
5818
+ { title: "Conventional Changelog - Auto-generate from commit messages", value: "conventional_changelog" },
5819
+ { title: "Release Please - Google's automated release management", value: "release_please" },
5820
+ { title: "Semantic Release - Fully automated versioning & publishing", value: "semantic_release" },
5821
+ { title: "Changesets - Monorepo-friendly version management", value: "changesets" },
5822
+ { title: "GitHub Releases - Use GitHub's built-in release notes", value: "github_releases" },
5823
+ { title: "Keep a Changelog - Manual following keepachangelog.com format", value: "keep_a_changelog" },
5824
+ { title: "Other - Custom changelog tooling", value: "other" }
5457
5825
  ],
5458
5826
  initial: 0
5459
5827
  }, promptConfig);
@@ -5473,13 +5841,15 @@ async function runInteractiveWizard(options, detected, userTier) {
5473
5841
  name: "branchStrategy",
5474
5842
  message: chalk7.white("Branch strategy:"),
5475
5843
  choices: [
5844
+ { title: "\u{1F3AE} None (toy project) - No branching, commit directly to main", value: "none" },
5476
5845
  { title: "\u{1F30A} GitHub Flow - Simple: main + feature branches", value: "github_flow" },
5477
5846
  { title: "\u{1F333} Gitflow - develop, feature, release, hotfix branches", value: "gitflow" },
5478
5847
  { title: "\u{1F682} Trunk-Based - Short-lived branches, continuous integration", value: "trunk_based" },
5479
5848
  { title: "\u{1F98A} GitLab Flow - Environment branches (staging, production)", value: "gitlab_flow" },
5480
5849
  { title: "\u{1F680} Release Flow - main + release branches", value: "release_flow" }
5481
5850
  ],
5482
- initial: 0
5851
+ initial: 1
5852
+ // Default to GitHub Flow (index 1 after adding "none")
5483
5853
  }, promptConfig);
5484
5854
  answers.branchStrategy = branchStrategyResponse.branchStrategy || "github_flow";
5485
5855
  const defaultBranchResponse = await prompts3({
@@ -5495,18 +5865,7 @@ async function runInteractiveWizard(options, detected, userTier) {
5495
5865
  initial: 0
5496
5866
  }, promptConfig);
5497
5867
  answers.defaultBranch = defaultBranchResponse.defaultBranch || "main";
5498
- const commitWorkflowResponse = await prompts3({
5499
- type: "select",
5500
- name: "commitWorkflow",
5501
- message: chalk7.white("Commit workflow preference:"),
5502
- choices: [
5503
- { title: "\u{1F500} Branch + PR - Create branches and open pull requests", value: "branch_pr" },
5504
- { title: "\u2B06\uFE0F Direct to main - Commit directly to main/master branch", value: "direct_main" }
5505
- ],
5506
- initial: 0
5507
- // Default to branch + PR
5508
- }, promptConfig);
5509
- answers.commitWorkflow = commitWorkflowResponse.commitWorkflow || "branch_pr";
5868
+ answers.commitWorkflow = answers.branchStrategy === "none" ? "direct_main" : "branch_pr";
5510
5869
  const cicdChoices = [
5511
5870
  { title: chalk7.gray("\u23ED Skip"), value: "" },
5512
5871
  ...CICD_OPTIONS.map((c) => ({
@@ -5523,27 +5882,61 @@ async function runInteractiveWizard(options, detected, userTier) {
5523
5882
  initial: detectedCicdIndex > 0 ? detectedCicdIndex : 0
5524
5883
  }, promptConfig);
5525
5884
  answers.cicd = cicdResponse.cicd || "";
5526
- const deployChoices = sortSelectedFirst(DEPLOYMENT_TARGETS.map((t) => ({
5527
- title: t.id === "docker" && detected?.hasDocker ? `${t.icon}${t.label} ${chalk7.green("(detected)")}` : `${t.icon}${t.label}`,
5528
- selected: t.id === "docker" && detected?.hasDocker,
5529
- value: t.id
5530
- })));
5531
- const deployResponse = await prompts3({
5532
- type: "autocompleteMultiselect",
5533
- name: "deploymentTargets",
5534
- message: chalk7.white("Deployment targets (type to search):"),
5535
- choices: deployChoices,
5536
- hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5537
- instructions: false
5885
+ const deployTypeResponse = await prompts3({
5886
+ type: "select",
5887
+ name: "deployType",
5888
+ message: chalk7.white("Deployment environment:"),
5889
+ choices: [
5890
+ { title: "\u2601\uFE0F Cloud only - PaaS, serverless, managed services", value: "cloud" },
5891
+ { title: "\u{1F3E0} Self-hosted only - On-premise, homelab, VPS", value: "self_hosted" },
5892
+ { title: "\u{1F504} Both cloud and self-hosted", value: "both" },
5893
+ { title: chalk7.gray("\u23ED Skip"), value: "skip" }
5894
+ ],
5895
+ initial: 0
5538
5896
  }, promptConfig);
5539
- answers.deploymentTargets = deployResponse.deploymentTargets || [];
5540
- const dockerSelected = (answers.deploymentTargets || []).includes("docker");
5897
+ const deployType = deployTypeResponse.deployType || "skip";
5898
+ let allDeployTargets = [];
5899
+ if (deployType === "cloud" || deployType === "both") {
5900
+ const cloudChoices = CLOUD_TARGETS.map((t) => ({
5901
+ title: `${t.icon}${t.label}`,
5902
+ value: t.id
5903
+ }));
5904
+ const cloudResponse = await prompts3({
5905
+ type: "autocompleteMultiselect",
5906
+ name: "cloudTargets",
5907
+ message: chalk7.white("Cloud deployment targets (type to search):"),
5908
+ choices: cloudChoices,
5909
+ hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5910
+ instructions: false
5911
+ }, promptConfig);
5912
+ allDeployTargets = [...cloudResponse.cloudTargets || []];
5913
+ }
5914
+ if (deployType === "self_hosted" || deployType === "both") {
5915
+ const selfHostedChoices = sortSelectedFirst(SELF_HOSTED_TARGETS.map((t) => ({
5916
+ title: t.id === "docker" && detected?.hasDocker ? `${t.icon}${t.label} ${chalk7.green("(detected)")}` : `${t.icon}${t.label}`,
5917
+ selected: t.id === "docker" && detected?.hasDocker,
5918
+ value: t.id
5919
+ })));
5920
+ const selfHostedResponse = await prompts3({
5921
+ type: "autocompleteMultiselect",
5922
+ name: "selfHostedTargets",
5923
+ message: chalk7.white("Self-hosted deployment targets (type to search):"),
5924
+ choices: selfHostedChoices,
5925
+ hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5926
+ instructions: false
5927
+ }, promptConfig);
5928
+ allDeployTargets = [...allDeployTargets, ...selfHostedResponse.selfHostedTargets || []];
5929
+ }
5930
+ answers.deploymentTargets = allDeployTargets;
5931
+ const dockerSelected = (answers.deploymentTargets || []).some(
5932
+ (t) => ["docker", "docker_compose", "kubernetes", "k3s", "podman"].includes(t)
5933
+ ) || detected?.hasDocker;
5541
5934
  const containerResponse = await prompts3({
5542
5935
  type: "toggle",
5543
5936
  name: "buildContainer",
5544
5937
  message: chalk7.white("Build container images (Docker)?"),
5545
5938
  initial: dockerSelected,
5546
- // Default Yes if Docker selected
5939
+ // Default Yes if container platform selected
5547
5940
  active: "Yes",
5548
5941
  inactive: "No"
5549
5942
  }, promptConfig);
@@ -5814,16 +6207,36 @@ async function runInteractiveWizard(options, detected, userTier) {
5814
6207
  if (canAccessTier(userTier, "intermediate")) {
5815
6208
  const commandsStep = getCurrentStep("commands");
5816
6209
  showStep(currentStepNum, commandsStep, userTier);
5817
- console.log(chalk7.gray(" Select common commands for your project (type to search):"));
6210
+ console.log(chalk7.gray(" Select commands for your project. Detected commands are pre-selected."));
5818
6211
  console.log();
5819
- const buildChoices = sortSelectedFirst(COMMON_COMMANDS.build.map((c) => {
5820
- const isDetected = detected?.commands?.build === c;
5821
- return {
5822
- title: isDetected ? `${chalk7.cyan(c)} ${chalk7.green("(detected)")}` : chalk7.cyan(c),
5823
- value: c,
5824
- selected: isDetected
5825
- };
5826
- }));
6212
+ const buildCommandChoices = (category, commonCmds, color) => {
6213
+ const detectedCmds2 = detected?.detectedCommands?.[category] || [];
6214
+ const detectedSet = new Set(detectedCmds2.map((d) => d.cmd));
6215
+ const choices = detectedCmds2.map((d) => ({
6216
+ title: `${color(d.cmd)} ${chalk7.green("(detected)")}${d.desc ? chalk7.gray(` - ${d.desc}`) : ""}`,
6217
+ value: d.cmd,
6218
+ selected: true
6219
+ }));
6220
+ for (const cmd of commonCmds) {
6221
+ if (!detectedSet.has(cmd)) {
6222
+ choices.push({
6223
+ title: color(cmd),
6224
+ value: cmd,
6225
+ selected: false
6226
+ });
6227
+ }
6228
+ }
6229
+ return choices;
6230
+ };
6231
+ const detectedCmds = detected?.detectedCommands;
6232
+ if (detectedCmds) {
6233
+ const totalDetected = Object.values(detectedCmds).reduce((sum, arr) => sum + (arr?.length || 0), 0);
6234
+ if (totalDetected > 0) {
6235
+ console.log(chalk7.green(` \u2713 ${totalDetected} commands detected from your project`));
6236
+ console.log();
6237
+ }
6238
+ }
6239
+ const buildChoices = buildCommandChoices("build", COMMON_COMMANDS.build, chalk7.cyan);
5827
6240
  const buildResponse = await prompts3({
5828
6241
  type: "autocompleteMultiselect",
5829
6242
  name: "build",
@@ -5832,14 +6245,7 @@ async function runInteractiveWizard(options, detected, userTier) {
5832
6245
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5833
6246
  instructions: false
5834
6247
  }, promptConfig);
5835
- const testChoices = sortSelectedFirst(COMMON_COMMANDS.test.map((c) => {
5836
- const isDetected = detected?.commands?.test === c;
5837
- return {
5838
- title: isDetected ? `${chalk7.yellow(c)} ${chalk7.green("(detected)")}` : chalk7.yellow(c),
5839
- value: c,
5840
- selected: isDetected
5841
- };
5842
- }));
6248
+ const testChoices = buildCommandChoices("test", COMMON_COMMANDS.test, chalk7.yellow);
5843
6249
  const testResponse = await prompts3({
5844
6250
  type: "autocompleteMultiselect",
5845
6251
  name: "test",
@@ -5848,30 +6254,16 @@ async function runInteractiveWizard(options, detected, userTier) {
5848
6254
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5849
6255
  instructions: false
5850
6256
  }, promptConfig);
5851
- const lintChoices = sortSelectedFirst(COMMON_COMMANDS.lint.map((c) => {
5852
- const isDetected = detected?.commands?.lint === c;
5853
- return {
5854
- title: isDetected ? `${chalk7.green(c)} ${chalk7.green("(detected)")}` : chalk7.green(c),
5855
- value: c,
5856
- selected: isDetected
5857
- };
5858
- }));
6257
+ const lintChoices = buildCommandChoices("lint", COMMON_COMMANDS.lint, chalk7.green);
5859
6258
  const lintResponse = await prompts3({
5860
6259
  type: "autocompleteMultiselect",
5861
6260
  name: "lint",
5862
- message: chalk7.white("Lint/format commands (type to search):"),
6261
+ message: chalk7.white("Lint commands (type to search):"),
5863
6262
  choices: lintChoices,
5864
6263
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5865
6264
  instructions: false
5866
6265
  }, promptConfig);
5867
- const devChoices = sortSelectedFirst(COMMON_COMMANDS.dev.map((c) => {
5868
- const isDetected = detected?.commands?.dev === c;
5869
- return {
5870
- title: isDetected ? `${chalk7.magenta(c)} ${chalk7.green("(detected)")}` : chalk7.magenta(c),
5871
- value: c,
5872
- selected: isDetected
5873
- };
5874
- }));
6266
+ const devChoices = buildCommandChoices("dev", COMMON_COMMANDS.dev, chalk7.magenta);
5875
6267
  const devResponse = await prompts3({
5876
6268
  type: "autocompleteMultiselect",
5877
6269
  name: "dev",
@@ -5880,58 +6272,48 @@ async function runInteractiveWizard(options, detected, userTier) {
5880
6272
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5881
6273
  instructions: false
5882
6274
  }, promptConfig);
6275
+ const formatChoices = buildCommandChoices("format", COMMON_COMMANDS.format, chalk7.blue);
5883
6276
  const formatResponse = await prompts3({
5884
6277
  type: "autocompleteMultiselect",
5885
6278
  name: "format",
5886
6279
  message: chalk7.white("Format commands (type to search):"),
5887
- choices: COMMON_COMMANDS.format.map((c) => ({
5888
- title: chalk7.blue(c),
5889
- value: c
5890
- })),
6280
+ choices: formatChoices,
5891
6281
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5892
6282
  instructions: false
5893
6283
  }, promptConfig);
6284
+ const typecheckChoices = buildCommandChoices("typecheck", COMMON_COMMANDS.typecheck, chalk7.gray);
5894
6285
  const typecheckResponse = await prompts3({
5895
6286
  type: "autocompleteMultiselect",
5896
6287
  name: "typecheck",
5897
6288
  message: chalk7.white("Typecheck commands (type to search):"),
5898
- choices: COMMON_COMMANDS.typecheck.map((c) => ({
5899
- title: chalk7.gray(c),
5900
- value: c
5901
- })),
6289
+ choices: typecheckChoices,
5902
6290
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5903
6291
  instructions: false
5904
6292
  }, promptConfig);
6293
+ const cleanChoices = buildCommandChoices("clean", COMMON_COMMANDS.clean, chalk7.red);
5905
6294
  const cleanResponse = await prompts3({
5906
6295
  type: "autocompleteMultiselect",
5907
6296
  name: "clean",
5908
6297
  message: chalk7.white("Clean commands (type to search):"),
5909
- choices: COMMON_COMMANDS.clean.map((c) => ({
5910
- title: chalk7.red(c),
5911
- value: c
5912
- })),
6298
+ choices: cleanChoices,
5913
6299
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5914
6300
  instructions: false
5915
6301
  }, promptConfig);
6302
+ const preCommitChoices = buildCommandChoices("preCommit", COMMON_COMMANDS.preCommit, chalk7.yellow);
5916
6303
  const preCommitResponse = await prompts3({
5917
6304
  type: "autocompleteMultiselect",
5918
6305
  name: "preCommit",
5919
6306
  message: chalk7.white("Pre-commit hooks (type to search):"),
5920
- choices: COMMON_COMMANDS.preCommit.map((c) => ({
5921
- title: chalk7.yellow(c),
5922
- value: c
5923
- })),
6307
+ choices: preCommitChoices,
5924
6308
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5925
6309
  instructions: false
5926
6310
  }, promptConfig);
6311
+ const additionalChoices = buildCommandChoices("additional", COMMON_COMMANDS.additional, chalk7.blue);
5927
6312
  const additionalResponse = await prompts3({
5928
6313
  type: "autocompleteMultiselect",
5929
6314
  name: "additional",
5930
6315
  message: chalk7.white("Additional commands (type to search):"),
5931
- choices: COMMON_COMMANDS.additional.map((c) => ({
5932
- title: chalk7.blue(c),
5933
- value: c
5934
- })),
6316
+ choices: additionalChoices,
5935
6317
  hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
5936
6318
  instructions: false
5937
6319
  }, promptConfig);
@@ -6026,10 +6408,10 @@ async function runInteractiveWizard(options, detected, userTier) {
6026
6408
  const maxFileLengthResponse = await prompts3({
6027
6409
  type: "number",
6028
6410
  name: "maxFileLength",
6029
- message: chalk7.white("Max file length (lines, 100-1000):"),
6411
+ message: chalk7.white("Max file length (lines, 100-10000):"),
6030
6412
  initial: 300,
6031
6413
  min: 100,
6032
- max: 1e3
6414
+ max: 1e4
6033
6415
  }, promptConfig);
6034
6416
  answers.maxFileLength = maxFileLengthResponse.maxFileLength || 300;
6035
6417
  const importOrderResponse = await prompts3({
@@ -6037,25 +6419,85 @@ async function runInteractiveWizard(options, detected, userTier) {
6037
6419
  name: "importOrder",
6038
6420
  message: chalk7.white("Import order preference:"),
6039
6421
  choices: [
6422
+ { title: chalk7.gray("\u23ED Skip"), value: "" },
6040
6423
  { title: "Grouped (external \u2192 internal \u2192 relative)", value: "grouped" },
6041
6424
  { title: "Alphabetical (sort A-Z)", value: "sorted" },
6042
6425
  { title: "Natural (leave as written)", value: "natural" }
6043
6426
  ],
6044
6427
  initial: 0
6045
6428
  }, promptConfig);
6046
- answers.importOrder = importOrderResponse.importOrder || "grouped";
6429
+ answers.importOrder = importOrderResponse.importOrder || "";
6047
6430
  const commentLangResponse = await prompts3({
6048
- type: "select",
6431
+ type: "autocomplete",
6049
6432
  name: "commentLanguage",
6050
6433
  message: chalk7.white("Comment language:"),
6051
6434
  choices: [
6435
+ { title: chalk7.gray("\u23ED Skip"), value: "" },
6052
6436
  { title: "\u{1F1EC}\u{1F1E7} English", value: "english" },
6053
- { title: "\u{1F30D} Native language", value: "native" },
6054
- { title: "\u{1F5E3}\uFE0F Any (team preference)", value: "any" }
6437
+ { title: "\u{1F1EA}\u{1F1F8} Spanish (Espa\xF1ol)", value: "spanish" },
6438
+ { title: "\u{1F1EB}\u{1F1F7} French (Fran\xE7ais)", value: "french" },
6439
+ { title: "\u{1F1E9}\u{1F1EA} German (Deutsch)", value: "german" },
6440
+ { title: "\u{1F1EE}\u{1F1F9} Italian (Italiano)", value: "italian" },
6441
+ { title: "\u{1F1F5}\u{1F1F9} Portuguese (Portugu\xEAs)", value: "portuguese" },
6442
+ { title: "\u{1F1F3}\u{1F1F1} Dutch (Nederlands)", value: "dutch" },
6443
+ { title: "\u{1F1F7}\u{1F1FA} Russian (\u0420\u0443\u0441\u0441\u043A\u0438\u0439)", value: "russian" },
6444
+ { title: "\u{1F1E8}\u{1F1F3} Chinese Simplified (\u7B80\u4F53\u4E2D\u6587)", value: "chinese_simplified" },
6445
+ { title: "\u{1F1F9}\u{1F1FC} Chinese Traditional (\u7E41\u9AD4\u4E2D\u6587)", value: "chinese_traditional" },
6446
+ { title: "\u{1F1EF}\u{1F1F5} Japanese (\u65E5\u672C\u8A9E)", value: "japanese" },
6447
+ { title: "\u{1F1F0}\u{1F1F7} Korean (\uD55C\uAD6D\uC5B4)", value: "korean" },
6448
+ { title: "\u{1F1F8}\u{1F1E6} Arabic (\u0627\u0644\u0639\u0631\u0628\u064A\u0629)", value: "arabic" },
6449
+ { title: "\u{1F1EE}\u{1F1F1} Hebrew (\u05E2\u05D1\u05E8\u05D9\u05EA)", value: "hebrew" },
6450
+ { title: "\u{1F1EE}\u{1F1F3} Hindi (\u0939\u093F\u0928\u094D\u0926\u0940)", value: "hindi" },
6451
+ { title: "\u{1F1E7}\u{1F1E9} Bengali (\u09AC\u09BE\u0982\u09B2\u09BE)", value: "bengali" },
6452
+ { title: "\u{1F1F5}\u{1F1F0} Urdu (\u0627\u0631\u062F\u0648)", value: "urdu" },
6453
+ { title: "\u{1F1F9}\u{1F1ED} Thai (\u0E44\u0E17\u0E22)", value: "thai" },
6454
+ { title: "\u{1F1FB}\u{1F1F3} Vietnamese (Ti\u1EBFng Vi\u1EC7t)", value: "vietnamese" },
6455
+ { title: "\u{1F1EE}\u{1F1E9} Indonesian (Bahasa Indonesia)", value: "indonesian" },
6456
+ { title: "\u{1F1F2}\u{1F1FE} Malay (Bahasa Melayu)", value: "malay" },
6457
+ { title: "\u{1F1F5}\u{1F1ED} Filipino (Tagalog)", value: "filipino" },
6458
+ { title: "\u{1F1F5}\u{1F1F1} Polish (Polski)", value: "polish" },
6459
+ { title: "\u{1F1E8}\u{1F1FF} Czech (\u010Ce\u0161tina)", value: "czech" },
6460
+ { title: "\u{1F1F8}\u{1F1F0} Slovak (Sloven\u010Dina)", value: "slovak" },
6461
+ { title: "\u{1F1ED}\u{1F1FA} Hungarian (Magyar)", value: "hungarian" },
6462
+ { title: "\u{1F1F7}\u{1F1F4} Romanian (Rom\xE2n\u0103)", value: "romanian" },
6463
+ { title: "\u{1F1E7}\u{1F1EC} Bulgarian (\u0411\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438)", value: "bulgarian" },
6464
+ { title: "\u{1F1FA}\u{1F1E6} Ukrainian (\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430)", value: "ukrainian" },
6465
+ { title: "\u{1F1EC}\u{1F1F7} Greek (\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC)", value: "greek" },
6466
+ { title: "\u{1F1F9}\u{1F1F7} Turkish (T\xFCrk\xE7e)", value: "turkish" },
6467
+ { title: "\u{1F1F8}\u{1F1EA} Swedish (Svenska)", value: "swedish" },
6468
+ { title: "\u{1F1F3}\u{1F1F4} Norwegian (Norsk)", value: "norwegian" },
6469
+ { title: "\u{1F1E9}\u{1F1F0} Danish (Dansk)", value: "danish" },
6470
+ { title: "\u{1F1EB}\u{1F1EE} Finnish (Suomi)", value: "finnish" },
6471
+ { title: "\u{1F1EA}\u{1F1EA} Estonian (Eesti)", value: "estonian" },
6472
+ { title: "\u{1F1F1}\u{1F1FB} Latvian (Latvie\u0161u)", value: "latvian" },
6473
+ { title: "\u{1F1F1}\u{1F1F9} Lithuanian (Lietuvi\u0173)", value: "lithuanian" },
6474
+ { title: "\u{1F1F8}\u{1F1EE} Slovenian (Sloven\u0161\u010Dina)", value: "slovenian" },
6475
+ { title: "\u{1F1ED}\u{1F1F7} Croatian (Hrvatski)", value: "croatian" },
6476
+ { title: "\u{1F1F7}\u{1F1F8} Serbian (\u0421\u0440\u043F\u0441\u043A\u0438)", value: "serbian" },
6477
+ { title: "\u{1F1EE}\u{1F1F7} Persian/Farsi (\u0641\u0627\u0631\u0633\u06CC)", value: "persian" },
6478
+ { title: "\u{1F1FF}\u{1F1E6} Afrikaans", value: "afrikaans" },
6479
+ { title: "\u{1F1F3}\u{1F1EC} Swahili (Kiswahili)", value: "swahili" },
6480
+ { title: "\u{1F1EA}\u{1F1EC} Egyptian Arabic", value: "egyptian_arabic" },
6481
+ { title: "\u{1F1F2}\u{1F1FD} Latin American Spanish", value: "latam_spanish" },
6482
+ { title: "\u{1F1E7}\u{1F1F7} Brazilian Portuguese", value: "brazilian_portuguese" },
6483
+ { title: "\u{1F1E8}\u{1F1E6} Canadian French", value: "canadian_french" },
6484
+ { title: "\u{1F1E6}\u{1F1F9} Austrian German", value: "austrian_german" },
6485
+ { title: "\u{1F1E8}\u{1F1ED} Swiss German", value: "swiss_german" },
6486
+ { title: "\u{1F524} Other (specify)", value: "other" }
6055
6487
  ],
6056
- initial: 0
6488
+ hint: chalk7.gray("type to filter"),
6489
+ suggest: (input, choices) => choices.filter((c) => c.title.toLowerCase().includes(input.toLowerCase()))
6057
6490
  }, promptConfig);
6058
- answers.commentLanguage = commentLangResponse.commentLanguage || "english";
6491
+ answers.commentLanguage = commentLangResponse.commentLanguage || "";
6492
+ if (answers.commentLanguage === "other") {
6493
+ const customLangResponse = await prompts3({
6494
+ type: "text",
6495
+ name: "customLanguage",
6496
+ message: chalk7.white("Specify comment language:"),
6497
+ validate: (v) => v.trim() ? true : "Please enter a language"
6498
+ }, promptConfig);
6499
+ answers.commentLanguage = customLangResponse.customLanguage || "english";
6500
+ }
6059
6501
  const docStyleResponse = await prompts3({
6060
6502
  type: "select",
6061
6503
  name: "docStyle",
@@ -6304,23 +6746,40 @@ async function runInteractiveWizard(options, detected, userTier) {
6304
6746
  const boundariesStep = getCurrentStep("boundaries");
6305
6747
  showStep(currentStepNum, boundariesStep, userTier);
6306
6748
  console.log(chalk7.gray(" Define what AI should never do, ask first, or always do."));
6307
- console.log(chalk7.gray(" Each option can only be in one category."));
6749
+ console.log(chalk7.gray(" Each option can only be in one category. Select 'Other' to add custom."));
6308
6750
  console.log();
6309
6751
  const usedOptions = /* @__PURE__ */ new Set();
6752
+ const OTHER_MARKER = "__other__";
6310
6753
  console.log(chalk7.red.bold(" \u2717 NEVER ALLOW - AI will refuse to do"));
6311
6754
  const neverResponse = await prompts3({
6312
6755
  type: "autocompleteMultiselect",
6313
6756
  name: "never",
6314
6757
  message: chalk7.white("Never allow (type to filter):"),
6315
- choices: BOUNDARY_OPTIONS.map((o) => ({
6316
- title: chalk7.red(o),
6317
- value: o
6318
- })),
6758
+ choices: [
6759
+ ...BOUNDARY_OPTIONS.map((o) => ({
6760
+ title: chalk7.red(o),
6761
+ value: o
6762
+ })),
6763
+ { title: chalk7.magenta("\u270E Other (custom)"), value: OTHER_MARKER }
6764
+ ],
6319
6765
  hint: chalk7.gray("space select \u2022 enter confirm"),
6320
6766
  instructions: false
6321
6767
  }, promptConfig);
6322
- answers.boundaryNever = neverResponse.never || [];
6323
- answers.boundaryNever.forEach((o) => usedOptions.add(o));
6768
+ let neverList = (neverResponse.never || []).filter((o) => o !== OTHER_MARKER);
6769
+ if ((neverResponse.never || []).includes(OTHER_MARKER)) {
6770
+ const customNeverResponse = await prompts3({
6771
+ type: "text",
6772
+ name: "custom",
6773
+ message: chalk7.white("Enter custom 'never allow' items (comma-separated):"),
6774
+ hint: chalk7.gray("e.g., Push to production, Deploy without approval")
6775
+ }, promptConfig);
6776
+ if (customNeverResponse.custom) {
6777
+ const customItems = customNeverResponse.custom.split(",").map((s) => s.trim()).filter(Boolean);
6778
+ neverList = [...neverList, ...customItems];
6779
+ }
6780
+ }
6781
+ answers.boundaryNever = neverList;
6782
+ neverList.forEach((o) => usedOptions.add(o));
6324
6783
  console.log();
6325
6784
  console.log(chalk7.yellow.bold(" ? ASK FIRST - AI will ask before doing"));
6326
6785
  const availableForAsk = BOUNDARY_OPTIONS.filter((o) => !usedOptions.has(o));
@@ -6328,15 +6787,31 @@ async function runInteractiveWizard(options, detected, userTier) {
6328
6787
  type: "autocompleteMultiselect",
6329
6788
  name: "ask",
6330
6789
  message: chalk7.white("Ask first (type to filter):"),
6331
- choices: availableForAsk.map((o) => ({
6332
- title: chalk7.yellow(o),
6333
- value: o
6334
- })),
6790
+ choices: [
6791
+ ...availableForAsk.map((o) => ({
6792
+ title: chalk7.yellow(o),
6793
+ value: o
6794
+ })),
6795
+ { title: chalk7.magenta("\u270E Other (custom)"), value: OTHER_MARKER }
6796
+ ],
6335
6797
  hint: chalk7.gray("space select \u2022 enter confirm"),
6336
6798
  instructions: false
6337
6799
  }, promptConfig);
6338
- answers.boundaryAsk = askResponse.ask || [];
6339
- answers.boundaryAsk.forEach((o) => usedOptions.add(o));
6800
+ let askList = (askResponse.ask || []).filter((o) => o !== OTHER_MARKER);
6801
+ if ((askResponse.ask || []).includes(OTHER_MARKER)) {
6802
+ const customAskResponse = await prompts3({
6803
+ type: "text",
6804
+ name: "custom",
6805
+ message: chalk7.white("Enter custom 'ask first' items (comma-separated):"),
6806
+ hint: chalk7.gray("e.g., Change external API calls, Modify authentication")
6807
+ }, promptConfig);
6808
+ if (customAskResponse.custom) {
6809
+ const customItems = customAskResponse.custom.split(",").map((s) => s.trim()).filter(Boolean);
6810
+ askList = [...askList, ...customItems];
6811
+ }
6812
+ }
6813
+ answers.boundaryAsk = askList;
6814
+ askList.forEach((o) => usedOptions.add(o));
6340
6815
  console.log();
6341
6816
  console.log(chalk7.green.bold(" \u2713 ALWAYS ALLOW - AI will do these automatically"));
6342
6817
  const availableForAlways = BOUNDARY_OPTIONS.filter((o) => !usedOptions.has(o));
@@ -6344,24 +6819,40 @@ async function runInteractiveWizard(options, detected, userTier) {
6344
6819
  type: "autocompleteMultiselect",
6345
6820
  name: "always",
6346
6821
  message: chalk7.white("Always allow (type to filter):"),
6347
- choices: availableForAlways.map((o) => ({
6348
- title: chalk7.green(o),
6349
- value: o
6350
- })),
6822
+ choices: [
6823
+ ...availableForAlways.map((o) => ({
6824
+ title: chalk7.green(o),
6825
+ value: o
6826
+ })),
6827
+ { title: chalk7.magenta("\u270E Other (custom)"), value: OTHER_MARKER }
6828
+ ],
6351
6829
  hint: chalk7.gray("space select \u2022 enter confirm"),
6352
6830
  instructions: false
6353
6831
  }, promptConfig);
6354
- answers.boundaryAlways = alwaysResponse.always || [];
6832
+ let alwaysList = (alwaysResponse.always || []).filter((o) => o !== OTHER_MARKER);
6833
+ if ((alwaysResponse.always || []).includes(OTHER_MARKER)) {
6834
+ const customAlwaysResponse = await prompts3({
6835
+ type: "text",
6836
+ name: "custom",
6837
+ message: chalk7.white("Enter custom 'always allow' items (comma-separated):"),
6838
+ hint: chalk7.gray("e.g., Add comments, Format code")
6839
+ }, promptConfig);
6840
+ if (customAlwaysResponse.custom) {
6841
+ const customItems = customAlwaysResponse.custom.split(",").map((s) => s.trim()).filter(Boolean);
6842
+ alwaysList = [...alwaysList, ...customItems];
6843
+ }
6844
+ }
6845
+ answers.boundaryAlways = alwaysList;
6355
6846
  console.log();
6356
6847
  console.log(chalk7.gray(" Boundary summary:"));
6357
- if (answers.boundaryAlways.length > 0) {
6358
- console.log(chalk7.green(` \u2713 Always: ${answers.boundaryAlways.join(", ")}`));
6848
+ if (alwaysList.length > 0) {
6849
+ console.log(chalk7.green(` \u2713 Always: ${alwaysList.join(", ")}`));
6359
6850
  }
6360
- if (answers.boundaryAsk.length > 0) {
6361
- console.log(chalk7.yellow(` ? Ask: ${answers.boundaryAsk.join(", ")}`));
6851
+ if (askList.length > 0) {
6852
+ console.log(chalk7.yellow(` ? Ask: ${askList.join(", ")}`));
6362
6853
  }
6363
- if (answers.boundaryNever.length > 0) {
6364
- console.log(chalk7.red(` \u2717 Never: ${answers.boundaryNever.join(", ")}`));
6854
+ if (neverList.length > 0) {
6855
+ console.log(chalk7.red(` \u2717 Never: ${neverList.join(", ")}`));
6365
6856
  }
6366
6857
  } else {
6367
6858
  answers.boundaries = options.boundaries || "standard";
@@ -6415,15 +6906,20 @@ async function runInteractiveWizard(options, detected, userTier) {
6415
6906
  inactive: "No"
6416
6907
  }, promptConfig);
6417
6908
  answers.tddPreference = tddResponse.tddPreference ?? false;
6909
+ console.log(chalk7.gray(" Snapshot testing captures expected output (HTML, JSON, etc.) and compares"));
6910
+ console.log(chalk7.gray(" future runs against it. Useful for UI components, API responses, serialization."));
6418
6911
  const snapshotResponse = await prompts3({
6419
- type: "toggle",
6912
+ type: "select",
6420
6913
  name: "snapshotTesting",
6421
6914
  message: chalk7.white("Use snapshot testing?"),
6422
- initial: false,
6423
- active: "Yes",
6424
- inactive: "No"
6915
+ choices: [
6916
+ { title: chalk7.gray("\u23ED Skip"), value: "skip" },
6917
+ { title: "Yes - Use snapshot testing for output validation", value: "yes" },
6918
+ { title: "No - Avoid snapshot tests", value: "no" }
6919
+ ],
6920
+ initial: 0
6425
6921
  }, promptConfig);
6426
- answers.snapshotTesting = snapshotResponse.snapshotTesting ?? false;
6922
+ answers.snapshotTesting = snapshotResponse.snapshotTesting === "yes";
6427
6923
  const mockResponse = await prompts3({
6428
6924
  type: "select",
6429
6925
  name: "mockStrategy",
@@ -6447,159 +6943,175 @@ async function runInteractiveWizard(options, detected, userTier) {
6447
6943
  if (canAccessTier(userTier, "advanced")) {
6448
6944
  const staticStep = getCurrentStep("static");
6449
6945
  showStep(currentStepNum, staticStep, userTier);
6450
- console.log(chalk7.gray(" How should static file content be handled?"));
6451
- console.log();
6452
- console.log(chalk7.gray(" \u{1F4C4} Config only: Content embedded in AI config file (AI has context, no local files)"));
6453
- console.log(chalk7.gray(" \u{1F4C1} Both: Create local files AND embed content in AI config file"));
6454
- console.log();
6455
- const staticFileHandlingResponse = await prompts3({
6456
- type: "select",
6457
- name: "handling",
6458
- message: chalk7.white("Where to add static file content?"),
6459
- choices: [
6460
- { title: "\u{1F4C4} Config file only (recommended)", value: "config_only", description: "Content goes in AI config, no separate files created" },
6461
- { title: "\u{1F4C1} Both local files AND config", value: "both", description: "Create files locally AND embed in AI config" }
6462
- ],
6463
- initial: 0
6464
- }, promptConfig);
6465
- answers.staticFileHandling = staticFileHandlingResponse.handling || "config_only";
6466
- console.log();
6467
- console.log(chalk7.gray(" Select project files to include:"));
6468
- console.log();
6469
- const STATIC_FILE_OPTIONS = [
6470
- { title: "\u{1F4DD} .editorconfig", value: "editorconfig", desc: "Consistent code formatting" },
6471
- { title: "\u{1F91D} CONTRIBUTING.md", value: "contributing", desc: "Contributor guidelines" },
6472
- { title: "\u{1F4DC} CODE_OF_CONDUCT.md", value: "codeOfConduct", desc: "Community standards" },
6473
- { title: "\u{1F512} SECURITY.md", value: "security", desc: "Vulnerability reporting" },
6474
- { title: "\u{1F5FA}\uFE0F ROADMAP.md", value: "roadmap", desc: "Project roadmap" },
6475
- { title: "\u{1F4CB} .gitignore", value: "gitignore", desc: "Git ignore patterns" },
6476
- { title: "\u{1F4B0} FUNDING.yml", value: "funding", desc: "GitHub Sponsors config" },
6477
- { title: "\u{1F4C4} LICENSE", value: "license", desc: "License file" },
6478
- { title: "\u{1F4D6} README.md", value: "readme", desc: "Project readme" },
6479
- { title: "\u{1F3D7}\uFE0F ARCHITECTURE.md", value: "architecture", desc: "Architecture docs" },
6480
- { title: "\u{1F4DD} CHANGELOG.md", value: "changelog", desc: "Version history" }
6481
- ];
6482
- const existingFiles = {};
6483
- for (const opt of STATIC_FILE_OPTIONS) {
6484
- const filePath = STATIC_FILE_PATHS2[opt.value];
6485
- if (filePath) {
6486
- const content = await readExistingFile(join4(process.cwd(), filePath));
6487
- if (content) {
6488
- existingFiles[opt.value] = content;
6489
- }
6490
- }
6491
- }
6492
- const existingCount = Object.keys(existingFiles).length;
6493
- if (existingCount > 0) {
6494
- console.log(chalk7.green(` \u2713 Found ${existingCount} existing file(s) in your project`));
6946
+ let skipStaticFiles = false;
6947
+ if (detected) {
6948
+ console.log(chalk7.gray(" Since you're working with an existing repository, you may already have these files."));
6495
6949
  console.log();
6950
+ const skipResponse = await prompts3({
6951
+ type: "toggle",
6952
+ name: "skip",
6953
+ message: chalk7.white("Skip static files configuration?"),
6954
+ initial: true,
6955
+ active: "Yes, skip",
6956
+ inactive: "No, configure"
6957
+ }, promptConfig);
6958
+ skipStaticFiles = skipResponse.skip ?? true;
6496
6959
  }
6497
- const staticFilesResponse = await prompts3({
6498
- type: "autocompleteMultiselect",
6499
- name: "staticFiles",
6500
- message: chalk7.white("Include static files (type to search):"),
6501
- choices: STATIC_FILE_OPTIONS.map((f) => ({
6502
- title: existingFiles[f.value] ? `${f.title} ${chalk7.green("(exists)")}` : f.title,
6503
- value: f.value,
6504
- description: chalk7.gray(f.desc)
6505
- })),
6506
- hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
6507
- instructions: false
6508
- }, promptConfig);
6509
- answers.staticFiles = staticFilesResponse.staticFiles || [];
6510
- if (answers.staticFiles?.length > 0) {
6960
+ if (!skipStaticFiles) {
6961
+ console.log(chalk7.gray(" How should static file content be handled?"));
6511
6962
  console.log();
6512
- console.log(chalk7.cyan(" \u{1F4DD} Customize file contents:"));
6513
- console.log(chalk7.gray(" For each file, choose to use existing content, write new, or use defaults."));
6514
- if (canAccessAI(userTier)) {
6515
- console.log(chalk7.magenta(` \u2728 Tip: Type 'ai:' followed by your request to use AI assistance`));
6516
- }
6963
+ console.log(chalk7.gray(" \u{1F4C4} Config only: Content embedded in AI config file (AI has context, no local files)"));
6964
+ console.log(chalk7.gray(" \u{1F4C1} Both: Create local files AND embed content in AI config file"));
6517
6965
  console.log();
6518
- answers.staticFileContents = {};
6519
- for (const fileKey of answers.staticFiles) {
6520
- const fileOpt = STATIC_FILE_OPTIONS.find((f) => f.value === fileKey);
6521
- if (!fileOpt) continue;
6522
- const filePath = STATIC_FILE_PATHS2[fileKey];
6523
- const existingContent = existingFiles[fileKey];
6524
- if (existingContent) {
6525
- const preview = existingContent.split("\n").slice(0, 3).join("\n");
6526
- console.log(chalk7.gray(` \u2500\u2500\u2500 ${filePath} (existing) \u2500\u2500\u2500`));
6527
- console.log(chalk7.gray(preview.substring(0, 150) + (preview.length > 150 ? "..." : "")));
6528
- console.log();
6529
- const actionResponse = await prompts3({
6530
- type: "select",
6531
- name: "action",
6532
- message: chalk7.white(`${filePath}:`),
6533
- choices: [
6534
- { title: chalk7.green("\u2713 Use existing content"), value: "existing" },
6535
- { title: chalk7.yellow("\u270F\uFE0F Write new content"), value: "new" },
6536
- { title: chalk7.gray("\u26A1 Generate default"), value: "default" }
6537
- ],
6538
- initial: 0
6539
- }, promptConfig);
6540
- if (actionResponse.action === "existing") {
6541
- answers.staticFileContents[fileKey] = existingContent;
6542
- } else if (actionResponse.action === "new") {
6543
- console.log();
6544
- const content = await readMultilineInput(` Content for ${filePath}:`);
6545
- if (content.trim()) {
6546
- answers.staticFileContents[fileKey] = content;
6547
- }
6966
+ const staticFileHandlingResponse = await prompts3({
6967
+ type: "select",
6968
+ name: "handling",
6969
+ message: chalk7.white("Where to add static file content?"),
6970
+ choices: [
6971
+ { title: "\u{1F4C4} Config file only (recommended)", value: "config_only", description: "Content goes in AI config, no separate files created" },
6972
+ { title: "\u{1F4C1} Both local files AND config", value: "both", description: "Create files locally AND embed in AI config" }
6973
+ ],
6974
+ initial: 0
6975
+ }, promptConfig);
6976
+ answers.staticFileHandling = staticFileHandlingResponse.handling || "config_only";
6977
+ console.log();
6978
+ console.log(chalk7.gray(" Select project files to include:"));
6979
+ console.log();
6980
+ const STATIC_FILE_OPTIONS = [
6981
+ { title: "\u{1F4DD} .editorconfig", value: "editorconfig", desc: "Consistent code formatting" },
6982
+ { title: "\u{1F91D} CONTRIBUTING.md", value: "contributing", desc: "Contributor guidelines" },
6983
+ { title: "\u{1F4DC} CODE_OF_CONDUCT.md", value: "codeOfConduct", desc: "Community standards" },
6984
+ { title: "\u{1F512} SECURITY.md", value: "security", desc: "Vulnerability reporting" },
6985
+ { title: "\u{1F5FA}\uFE0F ROADMAP.md", value: "roadmap", desc: "Project roadmap" },
6986
+ { title: "\u{1F4CB} .gitignore", value: "gitignore", desc: "Git ignore patterns" },
6987
+ { title: "\u{1F4B0} FUNDING.yml", value: "funding", desc: "GitHub Sponsors config" },
6988
+ { title: "\u{1F4C4} LICENSE", value: "license", desc: "License file" },
6989
+ { title: "\u{1F4D6} README.md", value: "readme", desc: "Project readme" },
6990
+ { title: "\u{1F3D7}\uFE0F ARCHITECTURE.md", value: "architecture", desc: "Architecture docs" },
6991
+ { title: "\u{1F4DD} CHANGELOG.md", value: "changelog", desc: "Version history" }
6992
+ ];
6993
+ const existingFiles = {};
6994
+ for (const opt of STATIC_FILE_OPTIONS) {
6995
+ const filePath = STATIC_FILE_PATHS2[opt.value];
6996
+ if (filePath) {
6997
+ const content = await readExistingFile(join4(process.cwd(), filePath));
6998
+ if (content) {
6999
+ existingFiles[opt.value] = content;
6548
7000
  }
6549
- } else {
6550
- const actionResponse = await prompts3({
6551
- type: "select",
6552
- name: "action",
6553
- message: chalk7.white(`${filePath}:`),
6554
- choices: [
6555
- { title: chalk7.gray("\u26A1 Generate default"), value: "default" },
6556
- { title: chalk7.yellow("\u270F\uFE0F Write custom content"), value: "new" }
6557
- ],
6558
- initial: 0
6559
- }, promptConfig);
6560
- if (actionResponse.action === "new") {
7001
+ }
7002
+ }
7003
+ const existingCount = Object.keys(existingFiles).length;
7004
+ if (existingCount > 0) {
7005
+ console.log(chalk7.green(` \u2713 Found ${existingCount} existing file(s) in your project`));
7006
+ console.log();
7007
+ }
7008
+ const staticFilesResponse = await prompts3({
7009
+ type: "autocompleteMultiselect",
7010
+ name: "staticFiles",
7011
+ message: chalk7.white("Include static files (type to search):"),
7012
+ choices: STATIC_FILE_OPTIONS.map((f) => ({
7013
+ title: existingFiles[f.value] ? `${f.title} ${chalk7.green("(exists)")}` : f.title,
7014
+ value: f.value,
7015
+ description: chalk7.gray(f.desc)
7016
+ })),
7017
+ hint: chalk7.gray("type to filter \u2022 space select \u2022 enter confirm"),
7018
+ instructions: false
7019
+ }, promptConfig);
7020
+ answers.staticFiles = staticFilesResponse.staticFiles || [];
7021
+ if (answers.staticFiles?.length > 0) {
7022
+ console.log();
7023
+ console.log(chalk7.cyan(" \u{1F4DD} Customize file contents:"));
7024
+ console.log(chalk7.gray(" For each file, choose to use existing content, write new, or use defaults."));
7025
+ if (canAccessAI(userTier)) {
7026
+ console.log(chalk7.magenta(` \u2728 Tip: Type 'ai:' followed by your request to use AI assistance`));
7027
+ }
7028
+ console.log();
7029
+ answers.staticFileContents = {};
7030
+ for (const fileKey of answers.staticFiles) {
7031
+ const fileOpt = STATIC_FILE_OPTIONS.find((f) => f.value === fileKey);
7032
+ if (!fileOpt) continue;
7033
+ const filePath = STATIC_FILE_PATHS2[fileKey];
7034
+ const existingContent = existingFiles[fileKey];
7035
+ if (existingContent) {
7036
+ const preview = existingContent.split("\n").slice(0, 3).join("\n");
7037
+ console.log(chalk7.gray(` \u2500\u2500\u2500 ${filePath} (existing) \u2500\u2500\u2500`));
7038
+ console.log(chalk7.gray(preview.substring(0, 150) + (preview.length > 150 ? "..." : "")));
6561
7039
  console.log();
6562
- if (canAccessAI(userTier)) {
6563
- const aiPromptResponse = await prompts3({
6564
- type: "text",
6565
- name: "input",
6566
- message: chalk7.white(`Content for ${filePath}:`),
6567
- hint: chalk7.gray("Type 'ai:' + description OR paste content (press Enter twice to skip)")
6568
- }, promptConfig);
6569
- let content = aiPromptResponse.input || "";
6570
- if (content.toLowerCase().startsWith("ai:")) {
6571
- const aiInstruction = content.substring(3).trim();
6572
- if (aiInstruction) {
6573
- const aiResult = await aiAssist(`Generate ${filePath} content: ${aiInstruction}`, existingContent);
6574
- if (aiResult) {
6575
- console.log(chalk7.cyan(" AI-generated content preview (first 200 chars):"));
6576
- console.log(chalk7.gray(" " + aiResult.substring(0, 200) + (aiResult.length > 200 ? "..." : "")));
6577
- const acceptAI = await prompts3({
6578
- type: "confirm",
6579
- name: "accept",
6580
- message: chalk7.white("Use this AI-generated content?"),
6581
- initial: true
6582
- }, promptConfig);
6583
- if (acceptAI.accept) {
6584
- content = aiResult;
6585
- } else {
6586
- content = "";
6587
- }
6588
- }
6589
- }
6590
- }
6591
- if (content.trim()) {
6592
- answers.staticFileContents[fileKey] = content;
6593
- }
6594
- } else {
7040
+ const actionResponse = await prompts3({
7041
+ type: "select",
7042
+ name: "action",
7043
+ message: chalk7.white(`${filePath}:`),
7044
+ choices: [
7045
+ { title: chalk7.green("\u2713 Use existing content"), value: "existing" },
7046
+ { title: chalk7.yellow("\u270F\uFE0F Write new content"), value: "new" },
7047
+ { title: chalk7.gray("\u26A1 Generate default"), value: "default" }
7048
+ ],
7049
+ initial: 0
7050
+ }, promptConfig);
7051
+ if (actionResponse.action === "existing") {
7052
+ answers.staticFileContents[fileKey] = existingContent;
7053
+ } else if (actionResponse.action === "new") {
7054
+ console.log();
6595
7055
  const content = await readMultilineInput(` Content for ${filePath}:`);
6596
7056
  if (content.trim()) {
6597
7057
  answers.staticFileContents[fileKey] = content;
6598
7058
  }
6599
7059
  }
7060
+ } else {
7061
+ const actionResponse = await prompts3({
7062
+ type: "select",
7063
+ name: "action",
7064
+ message: chalk7.white(`${filePath}:`),
7065
+ choices: [
7066
+ { title: chalk7.gray("\u26A1 Generate default"), value: "default" },
7067
+ { title: chalk7.yellow("\u270F\uFE0F Write custom content"), value: "new" }
7068
+ ],
7069
+ initial: 0
7070
+ }, promptConfig);
7071
+ if (actionResponse.action === "new") {
7072
+ console.log();
7073
+ if (canAccessAI(userTier)) {
7074
+ const aiPromptResponse = await prompts3({
7075
+ type: "text",
7076
+ name: "input",
7077
+ message: chalk7.white(`Content for ${filePath}:`),
7078
+ hint: chalk7.gray("Type 'ai:' + description OR paste content (press Enter twice to skip)")
7079
+ }, promptConfig);
7080
+ let content = aiPromptResponse.input || "";
7081
+ if (content.toLowerCase().startsWith("ai:")) {
7082
+ const aiInstruction = content.substring(3).trim();
7083
+ if (aiInstruction) {
7084
+ const aiResult = await aiAssist(`Generate ${filePath} content: ${aiInstruction}`, existingContent);
7085
+ if (aiResult) {
7086
+ console.log(chalk7.cyan(" AI-generated content preview (first 200 chars):"));
7087
+ console.log(chalk7.gray(" " + aiResult.substring(0, 200) + (aiResult.length > 200 ? "..." : "")));
7088
+ const acceptAI = await prompts3({
7089
+ type: "confirm",
7090
+ name: "accept",
7091
+ message: chalk7.white("Use this AI-generated content?"),
7092
+ initial: true
7093
+ }, promptConfig);
7094
+ if (acceptAI.accept) {
7095
+ content = aiResult;
7096
+ } else {
7097
+ content = "";
7098
+ }
7099
+ }
7100
+ }
7101
+ }
7102
+ if (content.trim()) {
7103
+ answers.staticFileContents[fileKey] = content;
7104
+ }
7105
+ } else {
7106
+ const content = await readMultilineInput(` Content for ${filePath}:`);
7107
+ if (content.trim()) {
7108
+ answers.staticFileContents[fileKey] = content;
7109
+ }
7110
+ }
7111
+ }
6600
7112
  }
7113
+ console.log();
6601
7114
  }
6602
- console.log();
6603
7115
  }
6604
7116
  }
6605
7117
  }
@@ -8810,7 +9322,7 @@ function handleError2(error) {
8810
9322
  }
8811
9323
 
8812
9324
  // src/index.ts
8813
- var CLI_VERSION2 = "1.2.12";
9325
+ var CLI_VERSION2 = "1.2.14";
8814
9326
  var program = new Command();
8815
9327
  program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version(CLI_VERSION2);
8816
9328
  program.command("wizard").description("Generate AI IDE configuration (recommended for most users)").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("-s, --stack <stack>", "Tech stack (comma-separated)").option("-f, --format <format>", "Output format: agents, cursor, or comma-separated for multiple").option("-p, --platforms <platforms>", "Alias for --format (deprecated)").option("--persona <persona>", "AI persona (fullstack, backend, frontend, devops, data, security)").option("--boundaries <level>", "Boundary preset (conservative, standard, permissive)").option("-y, --yes", "Skip prompts, use defaults (generates AGENTS.md)").option("-o, --output <dir>", "Output directory (default: current directory)").option("--repo-url <url>", "Analyze remote repository URL (GitHub/GitLab supported)").option("--blueprint", "Generate with [[VARIABLE|default]] placeholders for templates").option("--license <type>", "License type (mit, apache-2.0, gpl-3.0, etc.)").option("--ci-cd <platform>", "CI/CD platform (github_actions, gitlab_ci, jenkins, etc.)").option("--project-type <type>", "Project type (work, leisure, opensource, learning)").option("--detect-only", "Only detect project info, don't generate files").option("--load-draft <name>", "Load a saved wizard draft").option("--save-draft <name>", "Save wizard state as a draft (auto-saves at end)").option("--vars <values>", "Fill variables: VAR1=value1,VAR2=value2").action(wizardCommand);