lynxprompt 0.4.4 → 0.4.6

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk16 from "chalk";
5
+ import chalk19 from "chalk";
6
6
 
7
7
  // src/commands/login.ts
8
8
  import chalk from "chalk";
@@ -153,6 +153,71 @@ var ApiClient = class {
153
153
  body: JSON.stringify(data)
154
154
  });
155
155
  }
156
+ // AI endpoints
157
+ async aiEditBlueprint(data) {
158
+ return this.request("/api/ai/edit-blueprint", {
159
+ method: "POST",
160
+ body: JSON.stringify(data)
161
+ });
162
+ }
163
+ // User preferences endpoints
164
+ async saveWizardPreferences(data) {
165
+ const preferences = [];
166
+ if (data.commands) {
167
+ for (const [key, value] of Object.entries(data.commands)) {
168
+ const strValue = Array.isArray(value) ? value.join(", ") : value;
169
+ if (strValue) {
170
+ preferences.push({ category: "commands", key, value: strValue, isDefault: true });
171
+ }
172
+ }
173
+ }
174
+ if (data.codeStyle) {
175
+ if (data.codeStyle.naming) {
176
+ preferences.push({ category: "code_style", key: "naming", value: data.codeStyle.naming, isDefault: true });
177
+ }
178
+ if (data.codeStyle.errorHandling) {
179
+ preferences.push({ category: "code_style", key: "errorHandling", value: data.codeStyle.errorHandling, isDefault: true });
180
+ }
181
+ if (data.codeStyle.loggingConventions) {
182
+ preferences.push({ category: "code_style", key: "loggingConventions", value: data.codeStyle.loggingConventions, isDefault: true });
183
+ }
184
+ if (data.codeStyle.notes) {
185
+ preferences.push({ category: "code_style", key: "notes", value: data.codeStyle.notes, isDefault: true });
186
+ }
187
+ }
188
+ if (data.boundaries) {
189
+ if (data.boundaries.preset) {
190
+ preferences.push({ category: "boundaries", key: "preset", value: data.boundaries.preset, isDefault: true });
191
+ }
192
+ if (data.boundaries.never?.length) {
193
+ preferences.push({ category: "boundaries", key: "never", value: JSON.stringify(data.boundaries.never), isDefault: true });
194
+ }
195
+ if (data.boundaries.ask?.length) {
196
+ preferences.push({ category: "boundaries", key: "ask", value: JSON.stringify(data.boundaries.ask), isDefault: true });
197
+ }
198
+ }
199
+ if (data.testing) {
200
+ if (data.testing.levels?.length) {
201
+ preferences.push({ category: "testing", key: "levels", value: JSON.stringify(data.testing.levels), isDefault: true });
202
+ }
203
+ if (data.testing.frameworks?.length) {
204
+ preferences.push({ category: "testing", key: "frameworks", value: JSON.stringify(data.testing.frameworks), isDefault: true });
205
+ }
206
+ if (data.testing.coverage !== void 0) {
207
+ preferences.push({ category: "testing", key: "coverage", value: String(data.testing.coverage), isDefault: true });
208
+ }
209
+ if (data.testing.notes) {
210
+ preferences.push({ category: "testing", key: "notes", value: data.testing.notes, isDefault: true });
211
+ }
212
+ }
213
+ if (preferences.length === 0) {
214
+ return { saved: 0 };
215
+ }
216
+ return this.request("/api/user/wizard-preferences", {
217
+ method: "POST",
218
+ body: JSON.stringify(preferences)
219
+ });
220
+ }
156
221
  };
157
222
  var ApiRequestError = class extends Error {
158
223
  constructor(message, statusCode, response) {
@@ -234,11 +299,11 @@ async function tryOpenBrowser(url) {
234
299
  const { exec } = await import("child_process");
235
300
  const { promisify } = await import("util");
236
301
  const execAsync = promisify(exec);
237
- const platform = process.platform;
302
+ const platform2 = process.platform;
238
303
  let command;
239
- if (platform === "darwin") {
304
+ if (platform2 === "darwin") {
240
305
  command = `open "${url}"`;
241
- } else if (platform === "win32") {
306
+ } else if (platform2 === "win32") {
242
307
  command = `start "" "${url}"`;
243
308
  } else {
244
309
  command = `xdg-open "${url}"`;
@@ -1090,10 +1155,20 @@ var AGENTS = [
1090
1155
  popular: true
1091
1156
  },
1092
1157
  // === MARKDOWN FORMAT AGENTS ===
1158
+ {
1159
+ id: "antigravity",
1160
+ name: "Antigravity",
1161
+ description: "Google's AI-powered IDE with GEMINI.md support",
1162
+ patterns: ["GEMINI.md"],
1163
+ output: "GEMINI.md",
1164
+ format: "markdown",
1165
+ category: "popular",
1166
+ popular: true
1167
+ },
1093
1168
  {
1094
1169
  id: "gemini",
1095
- name: "Gemini",
1096
- description: "Google's Gemini AI assistant",
1170
+ name: "Gemini CLI",
1171
+ description: "Google's Gemini CLI tool",
1097
1172
  patterns: ["GEMINI.md"],
1098
1173
  output: "GEMINI.md",
1099
1174
  format: "markdown",
@@ -1415,8 +1490,10 @@ function formatDetectionResults(result) {
1415
1490
  }
1416
1491
 
1417
1492
  // src/utils/detect.ts
1418
- import { readFile as readFile3, access as access2 } from "fs/promises";
1493
+ import { readFile as readFile3, access as access2, rm, mkdtemp } from "fs/promises";
1419
1494
  import { join as join4 } from "path";
1495
+ import { tmpdir } from "os";
1496
+ import { execSync } from "child_process";
1420
1497
  var JS_FRAMEWORK_PATTERNS = {
1421
1498
  nextjs: ["next"],
1422
1499
  react: ["react", "react-dom"],
@@ -1746,6 +1823,231 @@ async function fileExists(path2) {
1746
1823
  return false;
1747
1824
  }
1748
1825
  }
1826
+ function detectRepoHost(url) {
1827
+ const lower = url.toLowerCase();
1828
+ if (lower.includes("github.com") || lower.includes("github:")) return "github";
1829
+ if (lower.includes("gitlab.com") || lower.includes("gitlab")) return "gitlab";
1830
+ if (lower.includes("bitbucket.org") || lower.includes("bitbucket:")) return "bitbucket";
1831
+ if (lower.includes("gitea.") || lower.includes("gitea:") || lower.includes("codeberg.org")) return "gitea";
1832
+ if (lower.includes("azure.com") || lower.includes("visualstudio.com") || lower.includes("dev.azure")) return "azure";
1833
+ return "other";
1834
+ }
1835
+ function parseGitHubUrl(url) {
1836
+ const patterns = [
1837
+ /github\.com[/:]([^/]+)\/([^/.]+)/,
1838
+ /^([^/]+)\/([^/]+)$/
1839
+ ];
1840
+ for (const pattern of patterns) {
1841
+ const match = url.match(pattern);
1842
+ if (match) {
1843
+ return { owner: match[1], repo: match[2].replace(/\.git$/, "") };
1844
+ }
1845
+ }
1846
+ return null;
1847
+ }
1848
+ function parseGitLabUrl(url) {
1849
+ const patterns = [
1850
+ /^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/,
1851
+ /^git@([^:]+):(.+?)(?:\.git)?$/
1852
+ ];
1853
+ for (const pattern of patterns) {
1854
+ const match = url.match(pattern);
1855
+ if (match) {
1856
+ const host = match[1];
1857
+ const path2 = match[2].replace(/\.git$/, "");
1858
+ if (host.includes("gitlab") || url.toLowerCase().includes("gitlab")) {
1859
+ return { path: path2, host };
1860
+ }
1861
+ }
1862
+ }
1863
+ return null;
1864
+ }
1865
+ async function detectFromGitHubApi(repoUrl) {
1866
+ const parsed = parseGitHubUrl(repoUrl);
1867
+ if (!parsed) return null;
1868
+ const { owner, repo } = parsed;
1869
+ try {
1870
+ const repoRes = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
1871
+ headers: { "User-Agent": "LynxPrompt-CLI" }
1872
+ });
1873
+ if (!repoRes.ok) return null;
1874
+ const repoInfo = await repoRes.json();
1875
+ if (repoInfo.private) return null;
1876
+ const detected = {
1877
+ name: repoInfo.name,
1878
+ description: repoInfo.description ?? void 0,
1879
+ stack: [],
1880
+ commands: {},
1881
+ packageManager: null,
1882
+ type: "application",
1883
+ repoHost: "github",
1884
+ repoUrl,
1885
+ license: repoInfo.license?.spdx_id?.toLowerCase()
1886
+ };
1887
+ const filesRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/`, {
1888
+ headers: { "User-Agent": "LynxPrompt-CLI" }
1889
+ });
1890
+ if (!filesRes.ok) return detected;
1891
+ const files = await filesRes.json();
1892
+ const fileNames = new Set(files.map((f) => f.name.toLowerCase()));
1893
+ if (fileNames.has("package.json")) {
1894
+ const pkgRes = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/HEAD/package.json`);
1895
+ if (pkgRes.ok) {
1896
+ try {
1897
+ const pkg = await pkgRes.json();
1898
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1899
+ if (allDeps["next"]) detected.stack.push("nextjs");
1900
+ if (allDeps["react"]) detected.stack.push("react");
1901
+ if (allDeps["vue"]) detected.stack.push("vue");
1902
+ if (allDeps["svelte"]) detected.stack.push("svelte");
1903
+ if (allDeps["express"]) detected.stack.push("express");
1904
+ if (allDeps["fastify"]) detected.stack.push("fastify");
1905
+ if (allDeps["typescript"]) detected.stack.push("typescript");
1906
+ if (allDeps["tailwindcss"]) detected.stack.push("tailwind");
1907
+ if (allDeps["prisma"]) detected.stack.push("prisma");
1908
+ if (allDeps["vitest"]) detected.stack.push("vitest");
1909
+ if (allDeps["jest"]) detected.stack.push("jest");
1910
+ if (detected.stack.length === 0 || detected.stack.length === 1 && detected.stack[0] === "typescript") {
1911
+ detected.stack.unshift("javascript");
1912
+ }
1913
+ if (pkg.scripts) {
1914
+ if (pkg.scripts.build) detected.commands.build = "npm run build";
1915
+ if (pkg.scripts.test) detected.commands.test = "npm run test";
1916
+ if (pkg.scripts.lint) detected.commands.lint = "npm run lint";
1917
+ if (pkg.scripts.dev) detected.commands.dev = "npm run dev";
1918
+ else if (pkg.scripts.start) detected.commands.dev = "npm run start";
1919
+ }
1920
+ } catch {
1921
+ }
1922
+ }
1923
+ }
1924
+ if (fileNames.has("pyproject.toml") || fileNames.has("requirements.txt")) detected.stack.push("python");
1925
+ if (fileNames.has("cargo.toml")) detected.stack.push("rust");
1926
+ if (fileNames.has("go.mod")) detected.stack.push("go");
1927
+ if (fileNames.has("dockerfile")) detected.hasDocker = true;
1928
+ if (files.some((f) => f.name === ".github" && f.type === "dir")) {
1929
+ detected.cicd = "github_actions";
1930
+ }
1931
+ if (fileNames.has(".gitlab-ci.yml")) detected.cicd = "gitlab_ci";
1932
+ return detected;
1933
+ } catch {
1934
+ return null;
1935
+ }
1936
+ }
1937
+ async function detectFromGitLabApi(repoUrl) {
1938
+ const parsed = parseGitLabUrl(repoUrl);
1939
+ if (!parsed) return null;
1940
+ const { path: projectPath, host } = parsed;
1941
+ const encodedPath = encodeURIComponent(projectPath);
1942
+ try {
1943
+ const repoRes = await fetch(`https://${host}/api/v4/projects/${encodedPath}`, {
1944
+ headers: { "User-Agent": "LynxPrompt-CLI" }
1945
+ });
1946
+ if (!repoRes.ok) return null;
1947
+ const repoInfo = await repoRes.json();
1948
+ if (repoInfo.visibility === "private") return null;
1949
+ const detected = {
1950
+ name: repoInfo.name,
1951
+ description: repoInfo.description ?? void 0,
1952
+ stack: [],
1953
+ commands: {},
1954
+ packageManager: null,
1955
+ type: "application",
1956
+ repoHost: "gitlab",
1957
+ repoUrl,
1958
+ license: repoInfo.license?.key?.toLowerCase()
1959
+ };
1960
+ const filesRes = await fetch(`https://${host}/api/v4/projects/${encodedPath}/repository/tree?per_page=100`, {
1961
+ headers: { "User-Agent": "LynxPrompt-CLI" }
1962
+ });
1963
+ if (!filesRes.ok) return detected;
1964
+ const files = await filesRes.json();
1965
+ const fileNames = new Set(files.map((f) => f.name.toLowerCase()));
1966
+ if (fileNames.has("package.json")) {
1967
+ const pkgRes = await fetch(`https://${host}/api/v4/projects/${encodedPath}/repository/files/package.json/raw?ref=HEAD`, {
1968
+ headers: { "User-Agent": "LynxPrompt-CLI" }
1969
+ });
1970
+ if (pkgRes.ok) {
1971
+ try {
1972
+ const pkg = await pkgRes.json();
1973
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1974
+ if (allDeps["next"]) detected.stack.push("nextjs");
1975
+ if (allDeps["react"]) detected.stack.push("react");
1976
+ if (allDeps["typescript"]) detected.stack.push("typescript");
1977
+ if (allDeps["tailwindcss"]) detected.stack.push("tailwind");
1978
+ if (detected.stack.length === 0) {
1979
+ detected.stack.unshift("javascript");
1980
+ }
1981
+ if (pkg.scripts) {
1982
+ if (pkg.scripts.build) detected.commands.build = "npm run build";
1983
+ if (pkg.scripts.test) detected.commands.test = "npm run test";
1984
+ if (pkg.scripts.lint) detected.commands.lint = "npm run lint";
1985
+ if (pkg.scripts.dev) detected.commands.dev = "npm run dev";
1986
+ }
1987
+ } catch {
1988
+ }
1989
+ }
1990
+ }
1991
+ if (fileNames.has("pyproject.toml") || fileNames.has("requirements.txt")) detected.stack.push("python");
1992
+ if (fileNames.has("cargo.toml")) detected.stack.push("rust");
1993
+ if (fileNames.has("go.mod")) detected.stack.push("go");
1994
+ if (fileNames.has("dockerfile")) detected.hasDocker = true;
1995
+ if (fileNames.has(".gitlab-ci.yml")) detected.cicd = "gitlab_ci";
1996
+ return detected;
1997
+ } catch {
1998
+ return null;
1999
+ }
2000
+ }
2001
+ async function detectFromShallowClone(repoUrl) {
2002
+ let tempDir = null;
2003
+ try {
2004
+ tempDir = await mkdtemp(join4(tmpdir(), "lynxprompt-detect-"));
2005
+ try {
2006
+ execSync(`git clone --depth 1 --quiet "${repoUrl}" "${tempDir}"`, {
2007
+ stdio: "pipe",
2008
+ timeout: 3e4
2009
+ });
2010
+ } catch {
2011
+ return null;
2012
+ }
2013
+ const detected = await detectProject(tempDir);
2014
+ if (detected) {
2015
+ detected.repoHost = detectRepoHost(repoUrl);
2016
+ detected.repoUrl = repoUrl;
2017
+ }
2018
+ return detected;
2019
+ } catch {
2020
+ return null;
2021
+ } finally {
2022
+ if (tempDir) {
2023
+ try {
2024
+ await rm(tempDir, { recursive: true, force: true });
2025
+ } catch {
2026
+ }
2027
+ }
2028
+ }
2029
+ }
2030
+ async function detectFromRemoteUrl(repoUrl) {
2031
+ const host = detectRepoHost(repoUrl);
2032
+ if (host === "github") {
2033
+ const result = await detectFromGitHubApi(repoUrl);
2034
+ if (result) return result;
2035
+ }
2036
+ if (host === "gitlab") {
2037
+ const result = await detectFromGitLabApi(repoUrl);
2038
+ if (result) return result;
2039
+ }
2040
+ return detectFromShallowClone(repoUrl);
2041
+ }
2042
+ function isGitUrl(str) {
2043
+ const patterns = [
2044
+ /^https?:\/\/[^/]+\/.*$/,
2045
+ /^git@[^:]+:.*$/,
2046
+ /^git:\/\/.*$/,
2047
+ /^ssh:\/\/.*$/
2048
+ ];
2049
+ return patterns.some((p) => p.test(str.trim()));
2050
+ }
1749
2051
 
1750
2052
  // src/commands/init.ts
1751
2053
  var LYNXPROMPT_DIR = ".lynxprompt";
@@ -2112,16 +2414,41 @@ import chalk8 from "chalk";
2112
2414
  import prompts4 from "prompts";
2113
2415
  import ora7 from "ora";
2114
2416
  import * as readline from "readline";
2417
+ import * as os from "os";
2115
2418
  import { writeFile as writeFile4, mkdir as mkdir4, access as access3, readFile as readFile5 } from "fs/promises";
2116
2419
  import { join as join6, dirname as dirname4 } from "path";
2117
2420
 
2118
2421
  // src/utils/generator.ts
2422
+ function bpVar(blueprintMode, varName, defaultValue) {
2423
+ if (!blueprintMode || !defaultValue) return defaultValue;
2424
+ return `[[${varName}|${defaultValue}]]`;
2425
+ }
2426
+ function fillVariables(content, variables) {
2427
+ return content.replace(/\[\[([A-Z_]+)\|([^\]]*)\]\]/g, (_, varName, defaultValue) => {
2428
+ return variables[varName] ?? defaultValue;
2429
+ });
2430
+ }
2431
+ function parseVariablesString(varsStr) {
2432
+ const variables = {};
2433
+ if (!varsStr) return variables;
2434
+ const parts = varsStr.split(",");
2435
+ for (const part of parts) {
2436
+ const eqIndex = part.indexOf("=");
2437
+ if (eqIndex > 0) {
2438
+ const key = part.slice(0, eqIndex).trim().toUpperCase();
2439
+ const value = part.slice(eqIndex + 1).trim();
2440
+ variables[key] = value;
2441
+ }
2442
+ }
2443
+ return variables;
2444
+ }
2119
2445
  var PLATFORM_FILES = {
2120
2446
  agents: "AGENTS.md",
2121
2447
  cursor: ".cursor/rules/project.mdc",
2122
2448
  claude: "CLAUDE.md",
2123
2449
  copilot: ".github/copilot-instructions.md",
2124
2450
  windsurf: ".windsurfrules",
2451
+ antigravity: "GEMINI.md",
2125
2452
  zed: ".zed/instructions.md",
2126
2453
  aider: ".aider.conf.yml",
2127
2454
  cline: ".clinerules",
@@ -2524,10 +2851,10 @@ var STATIC_FILE_PATHS = {
2524
2851
  };
2525
2852
  function generateConfig(options) {
2526
2853
  const files = {};
2527
- for (const platform of options.platforms) {
2528
- const filename = PLATFORM_FILES[platform];
2854
+ for (const platform2 of options.platforms) {
2855
+ const filename = PLATFORM_FILES[platform2];
2529
2856
  if (filename) {
2530
- files[filename] = generateFileContent(options, platform);
2857
+ files[filename] = generateFileContent(options, platform2);
2531
2858
  }
2532
2859
  }
2533
2860
  if (options.staticFiles && options.staticFiles.length > 0) {
@@ -2547,29 +2874,36 @@ function generateConfig(options) {
2547
2874
  if (options.includeFunding && !options.staticFiles?.includes("funding")) {
2548
2875
  files[".github/FUNDING.yml"] = STATIC_FILE_TEMPLATES.funding(options);
2549
2876
  }
2877
+ if (options.variables && Object.keys(options.variables).length > 0) {
2878
+ for (const [filename, content] of Object.entries(files)) {
2879
+ files[filename] = fillVariables(content, options.variables);
2880
+ }
2881
+ }
2550
2882
  return files;
2551
2883
  }
2552
- function generateFileContent(options, platform) {
2884
+ function generateFileContent(options, platform2) {
2553
2885
  const sections = [];
2554
- const isMdc = platform === "cursor";
2555
- const isYaml = platform === "aider" || platform === "tabnine";
2556
- const isPlainText = platform === "windsurf" || platform === "cline" || platform === "goose";
2886
+ const isMdc = platform2 === "cursor";
2887
+ const isYaml = platform2 === "aider" || platform2 === "tabnine";
2888
+ const isPlainText = platform2 === "windsurf" || platform2 === "cline" || platform2 === "goose";
2557
2889
  const isMarkdown = !isMdc && !isYaml && !isPlainText;
2890
+ const bp = options.blueprintMode || false;
2558
2891
  if (isYaml) {
2559
- return generateYamlConfig(options, platform);
2892
+ return generateYamlConfig(options, platform2);
2560
2893
  }
2894
+ const projectName = bpVar(bp, "PROJECT_NAME", options.name);
2561
2895
  if (isMdc) {
2562
2896
  sections.push("---");
2563
- sections.push(`description: "${options.name} - AI coding rules"`);
2897
+ sections.push(`description: "${projectName} - AI coding rules"`);
2564
2898
  sections.push('globs: ["**/*"]');
2565
2899
  sections.push("alwaysApply: true");
2566
2900
  sections.push("---");
2567
2901
  sections.push("");
2568
- sections.push(`# ${options.name} - AI Assistant Configuration`);
2902
+ sections.push(`# ${projectName} - AI Assistant Configuration`);
2569
2903
  sections.push("");
2570
2904
  }
2571
2905
  if (isMarkdown) {
2572
- sections.push(`# ${options.name} - AI Assistant Configuration`);
2906
+ sections.push(`# ${projectName} - AI Assistant Configuration`);
2573
2907
  sections.push("");
2574
2908
  }
2575
2909
  if (options.projectType) {
@@ -2590,16 +2924,17 @@ function generateFileContent(options, platform) {
2590
2924
  }
2591
2925
  }
2592
2926
  const personaDesc = PERSONA_DESCRIPTIONS[options.persona] || options.persona;
2927
+ const projectDesc = bpVar(bp, "PROJECT_DESCRIPTION", options.description || "");
2593
2928
  if (isMarkdown || isMdc) {
2594
2929
  sections.push("## Persona");
2595
2930
  sections.push("");
2596
- sections.push(`You are ${personaDesc}. You assist developers working on ${options.name}.`);
2931
+ sections.push(`You are ${personaDesc}. You assist developers working on ${projectName}.`);
2597
2932
  } else {
2598
- sections.push(`You are ${personaDesc}. You assist developers working on ${options.name}.`);
2933
+ sections.push(`You are ${personaDesc}. You assist developers working on ${projectName}.`);
2599
2934
  }
2600
2935
  if (options.description) {
2601
2936
  sections.push("");
2602
- sections.push(`Project description: ${options.description}`);
2937
+ sections.push(`Project description: ${projectDesc}`);
2603
2938
  }
2604
2939
  sections.push("");
2605
2940
  if (options.stack.length > 0) {
@@ -2937,9 +3272,9 @@ function generateFileContent(options, platform) {
2937
3272
  }
2938
3273
  return sections.join("\n");
2939
3274
  }
2940
- function generateYamlConfig(options, platform) {
3275
+ function generateYamlConfig(options, platform2) {
2941
3276
  const lines = [];
2942
- if (platform === "aider") {
3277
+ if (platform2 === "aider") {
2943
3278
  lines.push("# Aider configuration");
2944
3279
  lines.push(`# Project: ${options.name}`);
2945
3280
  lines.push("");
@@ -2957,7 +3292,7 @@ function generateYamlConfig(options, platform) {
2957
3292
  if (options.importantFiles?.includes("architecture")) {
2958
3293
  lines.push(" - ARCHITECTURE.md");
2959
3294
  }
2960
- } else if (platform === "tabnine") {
3295
+ } else if (platform2 === "tabnine") {
2961
3296
  lines.push("# Tabnine configuration");
2962
3297
  lines.push(`# Project: ${options.name}`);
2963
3298
  lines.push("");
@@ -2981,6 +3316,38 @@ function generateYamlConfig(options, platform) {
2981
3316
  }
2982
3317
 
2983
3318
  // src/commands/wizard.ts
3319
+ var DRAFTS_DIR = ".lynxprompt/drafts";
3320
+ async function saveDraftLocally(name, config2) {
3321
+ const draftsPath = join6(process.cwd(), DRAFTS_DIR);
3322
+ await mkdir4(draftsPath, { recursive: true });
3323
+ const draft = {
3324
+ name,
3325
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
3326
+ config: config2
3327
+ };
3328
+ const filename = `${name.replace(/[^a-zA-Z0-9-_]/g, "_")}.json`;
3329
+ await writeFile4(join6(draftsPath, filename), JSON.stringify(draft, null, 2), "utf-8");
3330
+ }
3331
+ async function loadDraftLocally(name) {
3332
+ try {
3333
+ const filename = `${name.replace(/[^a-zA-Z0-9-_]/g, "_")}.json`;
3334
+ const filepath = join6(process.cwd(), DRAFTS_DIR, filename);
3335
+ const content = await readFile5(filepath, "utf-8");
3336
+ return JSON.parse(content);
3337
+ } catch {
3338
+ return null;
3339
+ }
3340
+ }
3341
+ async function listLocalDrafts() {
3342
+ try {
3343
+ const draftsPath = join6(process.cwd(), DRAFTS_DIR);
3344
+ await access3(draftsPath);
3345
+ const files = await import("fs/promises").then((fs2) => fs2.readdir(draftsPath));
3346
+ return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
3347
+ } catch {
3348
+ return [];
3349
+ }
3350
+ }
2984
3351
  var STATIC_FILE_PATHS2 = {
2985
3352
  editorconfig: ".editorconfig",
2986
3353
  contributing: "CONTRIBUTING.md",
@@ -3057,6 +3424,7 @@ var ALL_PLATFORMS = [
3057
3424
  { id: "claude", name: "Claude Code", file: "CLAUDE.md", icon: "\u{1F9E0}", note: "Also works with Cursor" },
3058
3425
  { id: "copilot", name: "GitHub Copilot", file: ".github/copilot-instructions.md", icon: "\u{1F419}", note: "VS Code & JetBrains" },
3059
3426
  { id: "windsurf", name: "Windsurf", file: ".windsurfrules", icon: "\u{1F3C4}", note: "Codeium IDE" },
3427
+ { id: "antigravity", name: "Antigravity", file: "GEMINI.md", icon: "\u{1F48E}", note: "Google's AI-powered IDE" },
3060
3428
  { id: "zed", name: "Zed", file: ".zed/instructions.md", icon: "\u26A1", note: "Zed editor" },
3061
3429
  { id: "aider", name: "Aider", file: ".aider.conf.yml", icon: "\u{1F916}", note: "CLI AI pair programming" },
3062
3430
  { id: "cline", name: "Cline", file: ".clinerules", icon: "\u{1F527}", note: "VS Code extension" },
@@ -3368,6 +3736,42 @@ function canAccessTier(userTier, requiredTier) {
3368
3736
  const requiredLevels = { basic: 0, intermediate: 1, advanced: 2 };
3369
3737
  return tierLevels[userTier] >= requiredLevels[requiredTier];
3370
3738
  }
3739
+ function canAccessAI(userTier) {
3740
+ return userTier === "max" || userTier === "teams";
3741
+ }
3742
+ function getAIShortcutHint() {
3743
+ const platform2 = os.platform();
3744
+ if (platform2 === "darwin") {
3745
+ return "\u2318+I";
3746
+ } else {
3747
+ return "Ctrl+I";
3748
+ }
3749
+ }
3750
+ async function aiAssist(instruction, existingContent) {
3751
+ const spinner = ora7("AI is thinking...").start();
3752
+ try {
3753
+ const response = await api.aiEditBlueprint({
3754
+ content: existingContent,
3755
+ instruction,
3756
+ mode: existingContent ? "blueprint" : "wizard"
3757
+ });
3758
+ spinner.succeed("AI suggestion ready");
3759
+ return response.content;
3760
+ } catch (error) {
3761
+ if (error instanceof ApiRequestError) {
3762
+ if (error.statusCode === 403) {
3763
+ spinner.fail("AI editing requires Max or Teams subscription");
3764
+ } else if (error.statusCode === 429) {
3765
+ spinner.fail("Rate limit reached. Please wait a moment.");
3766
+ } else {
3767
+ spinner.fail(`AI error: ${error.message}`);
3768
+ }
3769
+ } else {
3770
+ spinner.fail("Failed to get AI suggestion");
3771
+ }
3772
+ return null;
3773
+ }
3774
+ }
3371
3775
  function getTierBadge(tier) {
3372
3776
  switch (tier) {
3373
3777
  case "intermediate":
@@ -3396,14 +3800,25 @@ function printBox(lines, color = chalk8.gray) {
3396
3800
  function showStep(current, step, userTier) {
3397
3801
  const availableSteps = getAvailableSteps(userTier);
3398
3802
  const total = availableSteps.length;
3399
- const progress = "\u25CF".repeat(current) + "\u25CB".repeat(total - current);
3803
+ let progressBar = "";
3804
+ for (let i = 1; i <= total; i++) {
3805
+ if (i < current) {
3806
+ progressBar += chalk8.green("\u25CF");
3807
+ } else if (i === current) {
3808
+ progressBar += chalk8.cyan.bold("\u25C9");
3809
+ } else {
3810
+ progressBar += chalk8.gray("\u25CB");
3811
+ }
3812
+ }
3400
3813
  const badge = getTierBadge(step.tier);
3401
3814
  console.log();
3402
- let stepLine = chalk8.cyan(` ${progress} Step ${current}/${total}: ${step.icon} ${step.title}`);
3815
+ console.log(chalk8.gray(" \u2550".repeat(30)));
3816
+ let stepLine = ` ${progressBar} ${chalk8.cyan.bold(`Step ${current}/${total}`)}: ${step.icon} ${chalk8.bold(step.title)}`;
3403
3817
  if (badge) {
3404
3818
  stepLine += " " + badge.color(`[${badge.label}]`);
3405
3819
  }
3406
3820
  console.log(stepLine);
3821
+ console.log(chalk8.gray(" \u2550".repeat(30)));
3407
3822
  console.log();
3408
3823
  }
3409
3824
  function showWizardOverview(userTier) {
@@ -3441,6 +3856,68 @@ async function wizardCommand(options) {
3441
3856
  console.log(chalk8.cyan.bold(" \u{1F431} LynxPrompt Wizard"));
3442
3857
  console.log(chalk8.gray(" Generate AI IDE configuration in seconds"));
3443
3858
  console.log();
3859
+ if (options.loadDraft) {
3860
+ const draft = await loadDraftLocally(options.loadDraft);
3861
+ if (draft) {
3862
+ console.log(chalk8.green(` \u2713 Loaded draft: ${draft.name} (saved ${new Date(draft.savedAt).toLocaleString()})`));
3863
+ console.log();
3864
+ Object.assign(options, draft.config);
3865
+ } else {
3866
+ const availableDrafts = await listLocalDrafts();
3867
+ console.log(chalk8.red(` \u2717 Draft "${options.loadDraft}" not found.`));
3868
+ if (availableDrafts.length > 0) {
3869
+ console.log(chalk8.gray(` Available drafts: ${availableDrafts.join(", ")}`));
3870
+ }
3871
+ console.log();
3872
+ }
3873
+ }
3874
+ if (options.repoUrl) {
3875
+ const spinner2 = ora7("Analyzing remote repository...").start();
3876
+ const detected2 = await detectFromRemoteUrl(options.repoUrl);
3877
+ if (detected2) {
3878
+ spinner2.succeed("Remote repository analyzed");
3879
+ if (!options.name && detected2.name) options.name = detected2.name;
3880
+ if (!options.description && detected2.description) options.description = detected2.description;
3881
+ if (!options.stack && detected2.stack.length > 0) options.stack = detected2.stack.join(",");
3882
+ if (!options.license && detected2.license) options.license = detected2.license;
3883
+ if (detected2.cicd) options.ciCd = detected2.cicd;
3884
+ } else {
3885
+ spinner2.fail("Could not analyze repository");
3886
+ }
3887
+ console.log();
3888
+ }
3889
+ if (options.detectOnly) {
3890
+ const detected2 = options.repoUrl ? await detectFromRemoteUrl(options.repoUrl) : await detectProject(process.cwd());
3891
+ if (detected2) {
3892
+ console.log(chalk8.green.bold(" \u{1F4CA} Project Analysis"));
3893
+ console.log();
3894
+ console.log(chalk8.white(` Name: ${detected2.name || "unknown"}`));
3895
+ if (detected2.description) console.log(chalk8.gray(` Description: ${detected2.description}`));
3896
+ console.log(chalk8.white(` Stack: ${detected2.stack.join(", ") || "none detected"}`));
3897
+ console.log(chalk8.white(` Type: ${detected2.type}`));
3898
+ if (detected2.packageManager) console.log(chalk8.white(` Package Manager: ${detected2.packageManager}`));
3899
+ if (detected2.repoHost) console.log(chalk8.white(` Repository Host: ${detected2.repoHost}`));
3900
+ if (detected2.license) console.log(chalk8.white(` License: ${detected2.license}`));
3901
+ if (detected2.cicd) console.log(chalk8.white(` CI/CD: ${detected2.cicd}`));
3902
+ if (detected2.hasDocker) console.log(chalk8.white(` Docker: yes`));
3903
+ if (detected2.commands) {
3904
+ console.log(chalk8.white(` Commands:`));
3905
+ if (detected2.commands.build) console.log(chalk8.gray(` build: ${detected2.commands.build}`));
3906
+ if (detected2.commands.test) console.log(chalk8.gray(` test: ${detected2.commands.test}`));
3907
+ if (detected2.commands.lint) console.log(chalk8.gray(` lint: ${detected2.commands.lint}`));
3908
+ if (detected2.commands.dev) console.log(chalk8.gray(` dev: ${detected2.commands.dev}`));
3909
+ }
3910
+ console.log();
3911
+ } else {
3912
+ console.log(chalk8.yellow(" No project detected."));
3913
+ console.log();
3914
+ }
3915
+ return;
3916
+ }
3917
+ if (options.blueprint) {
3918
+ console.log(chalk8.magenta(" \u{1F4CB} Template Mode: Generating with [[VARIABLE|default]] placeholders"));
3919
+ console.log();
3920
+ }
3444
3921
  const authenticated = isAuthenticated();
3445
3922
  const user = getUser();
3446
3923
  const userPlanRaw = user?.plan?.toLowerCase() || "free";
@@ -3455,6 +3932,8 @@ async function wizardCommand(options) {
3455
3932
  console.log(y("\u2502") + " ".repeat(W) + y("\u2502"));
3456
3933
  console.log(y("\u2502") + pad(" \u2022 Commands & Code Style [PRO]", W) + y("\u2502"));
3457
3934
  console.log(y("\u2502") + pad(" \u2022 Boundaries, Testing, Static Files [MAX]", W) + y("\u2502"));
3935
+ console.log(y("\u2502") + pad(" \u2022 Auto-detect from remote repos [MAX]", W) + y("\u2502"));
3936
+ console.log(y("\u2502") + pad(" \u2022 Save preferences to your profile", W) + y("\u2502"));
3458
3937
  console.log(y("\u2502") + pad(" \u2022 Push configs to cloud (lynxp push)", W) + y("\u2502"));
3459
3938
  console.log(y("\u2502") + pad(" \u2022 Sync across devices (lynxp sync)", W) + y("\u2502"));
3460
3939
  console.log(y("\u2502") + " ".repeat(W) + y("\u2502"));
@@ -3467,22 +3946,74 @@ async function wizardCommand(options) {
3467
3946
  console.log();
3468
3947
  }
3469
3948
  showWizardOverview(userTier);
3949
+ console.log(chalk8.gray(` \u{1F4BE} Tip: Type 'save:draftname' anytime to save progress locally`));
3950
+ console.log();
3470
3951
  const accessibleSteps = getAvailableSteps(userTier);
3471
3952
  const lockedSteps = WIZARD_STEPS.length - accessibleSteps.length;
3472
3953
  if (lockedSteps > 0) {
3473
3954
  console.log(chalk8.gray(` ${lockedSteps} step${lockedSteps > 1 ? "s" : ""} locked. Upgrade at ${chalk8.cyan("https://lynxprompt.com/pricing")}`));
3474
3955
  console.log();
3475
3956
  }
3476
- const detected = await detectProject(process.cwd());
3957
+ let detected = await detectProject(process.cwd());
3477
3958
  if (detected) {
3478
3959
  const detectedInfo = [
3479
- chalk8.green("\u2713 Project detected")
3960
+ chalk8.green("\u2713 Local project detected")
3480
3961
  ];
3481
3962
  if (detected.name) detectedInfo.push(chalk8.gray(` Name: ${detected.name}`));
3482
3963
  if (detected.stack.length > 0) detectedInfo.push(chalk8.gray(` Stack: ${detected.stack.join(", ")}`));
3483
3964
  if (detected.packageManager) detectedInfo.push(chalk8.gray(` Package manager: ${detected.packageManager}`));
3484
3965
  printBox(detectedInfo, chalk8.gray);
3485
3966
  console.log();
3967
+ } else {
3968
+ console.log(chalk8.gray(" No project detected in current directory."));
3969
+ console.log();
3970
+ }
3971
+ const canDetectRemote = canAccessAI(userTier);
3972
+ if (canDetectRemote) {
3973
+ console.log();
3974
+ console.log(chalk8.magenta.bold(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
3975
+ console.log(chalk8.magenta.bold(" \u2502 \u2728 AUTO-DETECT FROM REPOSITORY (MAX/TEAMS) \u2502"));
3976
+ console.log(chalk8.magenta.bold(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
3977
+ console.log(chalk8.gray(" Analyze any public GitHub/GitLab repo, or private with git credentials."));
3978
+ console.log(chalk8.gray(" We'll detect: languages, frameworks, commands, license, CI/CD, and more!"));
3979
+ console.log();
3980
+ const remoteResponse = await prompts4({
3981
+ type: "confirm",
3982
+ name: "useRemote",
3983
+ message: detected ? chalk8.white("\u{1F50D} Analyze a different remote repository instead?") : chalk8.white("\u{1F50D} Analyze a remote repository URL?"),
3984
+ initial: !detected
3985
+ }, promptConfig);
3986
+ if (remoteResponse.useRemote) {
3987
+ const urlResponse = await prompts4({
3988
+ type: "text",
3989
+ name: "url",
3990
+ message: chalk8.white("Enter the repository URL:"),
3991
+ hint: chalk8.gray("GitHub, GitLab, or any git host"),
3992
+ validate: (v) => isGitUrl(v) || "Please enter a valid Git URL"
3993
+ }, promptConfig);
3994
+ if (urlResponse.url) {
3995
+ const host = urlResponse.url.toLowerCase().includes("github") ? "GitHub API" : urlResponse.url.toLowerCase().includes("gitlab") ? "GitLab API" : "shallow clone";
3996
+ const remoteSpinner = ora7(`Analyzing remote repository via ${host}...`).start();
3997
+ const remoteDetected = await detectFromRemoteUrl(urlResponse.url);
3998
+ if (remoteDetected) {
3999
+ detected = remoteDetected;
4000
+ remoteSpinner.succeed("Remote repository analyzed");
4001
+ const detectedInfo = [
4002
+ chalk8.green("\u2713 Remote project detected")
4003
+ ];
4004
+ if (detected.name) detectedInfo.push(chalk8.gray(` Name: ${detected.name}`));
4005
+ if (detected.stack.length > 0) detectedInfo.push(chalk8.gray(` Stack: ${detected.stack.join(", ")}`));
4006
+ if (detected.repoUrl) detectedInfo.push(chalk8.gray(` Source: ${detected.repoUrl}`));
4007
+ printBox(detectedInfo, chalk8.gray);
4008
+ console.log();
4009
+ } else {
4010
+ remoteSpinner.fail("Could not analyze repository (may be private or inaccessible)");
4011
+ }
4012
+ }
4013
+ }
4014
+ } else if (!detected) {
4015
+ console.log(chalk8.yellow(" \u{1F4A1} Tip: Max/Teams users can analyze remote repository URLs"));
4016
+ console.log();
3486
4017
  }
3487
4018
  let config2;
3488
4019
  if (options.yes) {
@@ -3508,13 +4039,20 @@ async function wizardCommand(options) {
3508
4039
  }
3509
4040
  const spinner = ora7("Generating configuration...").start();
3510
4041
  try {
3511
- const files = generateConfig(config2);
4042
+ const variables = options.vars ? parseVariablesString(options.vars) : void 0;
4043
+ const finalConfig = {
4044
+ ...config2,
4045
+ blueprintMode: options.blueprint || config2.blueprintMode || false,
4046
+ variables
4047
+ };
4048
+ const files = generateConfig(finalConfig);
3512
4049
  spinner.stop();
3513
4050
  console.log();
3514
4051
  console.log(chalk8.green.bold(" \u2705 Generated:"));
3515
4052
  console.log();
4053
+ const outputDir = options.output || process.cwd();
3516
4054
  for (const [filename, content] of Object.entries(files)) {
3517
- const outputPath = join6(process.cwd(), filename);
4055
+ const outputPath = join6(outputDir, filename);
3518
4056
  let exists = false;
3519
4057
  try {
3520
4058
  await access3(outputPath);
@@ -3557,6 +4095,56 @@ async function wizardCommand(options) {
3557
4095
  nextStepsLines.push(chalk8.cyan(" lynxp status ") + chalk8.gray("View current setup"));
3558
4096
  printBox(nextStepsLines, chalk8.gray);
3559
4097
  console.log();
4098
+ if (options.saveDraft) {
4099
+ try {
4100
+ await saveDraftLocally(options.saveDraft, config2);
4101
+ console.log(chalk8.green(` \u{1F4BE} Draft saved as "${options.saveDraft}"`));
4102
+ console.log(chalk8.gray(` Load later with: lynxp wizard --load-draft ${options.saveDraft}`));
4103
+ console.log();
4104
+ } catch (err) {
4105
+ console.log(chalk8.yellow(` \u26A0\uFE0F Could not save draft: ${err instanceof Error ? err.message : "unknown error"}`));
4106
+ }
4107
+ }
4108
+ if (authenticated && !options.yes) {
4109
+ console.log();
4110
+ const savePrefsResponse = await prompts4({
4111
+ type: "confirm",
4112
+ name: "savePrefs",
4113
+ message: chalk8.white("Save these preferences to your profile for next time?"),
4114
+ initial: true
4115
+ });
4116
+ if (savePrefsResponse.savePrefs) {
4117
+ const saveSpinner = ora7("Saving preferences to your profile...").start();
4118
+ try {
4119
+ await api.saveWizardPreferences({
4120
+ commands: config2.commands,
4121
+ codeStyle: {
4122
+ naming: config2.namingConvention,
4123
+ errorHandling: config2.errorHandling,
4124
+ loggingConventions: config2.loggingConventions,
4125
+ notes: config2.styleNotes
4126
+ },
4127
+ boundaries: {
4128
+ preset: config2.boundaries,
4129
+ never: config2.boundaryNever,
4130
+ ask: config2.boundaryAsk
4131
+ },
4132
+ testing: {
4133
+ levels: config2.testLevels,
4134
+ frameworks: config2.testFrameworks,
4135
+ coverage: config2.coverageTarget,
4136
+ notes: config2.testNotes
4137
+ }
4138
+ });
4139
+ saveSpinner.succeed("Preferences saved to your profile");
4140
+ } catch (err) {
4141
+ saveSpinner.fail("Could not save preferences (you can still use the generated files)");
4142
+ if (err instanceof Error) {
4143
+ console.log(chalk8.gray(` ${err.message}`));
4144
+ }
4145
+ }
4146
+ }
4147
+ }
3560
4148
  } catch (error) {
3561
4149
  spinner.fail("Failed to generate files");
3562
4150
  console.error(chalk8.red("\n\u2717 An error occurred while generating configuration files."));
@@ -3588,11 +4176,12 @@ async function runInteractiveWizard(options, detected, userTier) {
3588
4176
  } else {
3589
4177
  console.log(chalk8.gray(" Select the AI editors you want to generate config for:"));
3590
4178
  console.log(chalk8.gray(" (AGENTS.md is recommended - works with most AI tools)"));
4179
+ console.log(chalk8.gray(" Type to search/filter the list."));
3591
4180
  console.log();
3592
4181
  const platformResponse = await prompts4({
3593
- type: "multiselect",
4182
+ type: "autocompleteMultiselect",
3594
4183
  name: "platforms",
3595
- message: chalk8.white("Select AI editors (16 supported):"),
4184
+ message: chalk8.white("Select AI editors (type to search):"),
3596
4185
  choices: ALL_PLATFORMS.map((p) => ({
3597
4186
  title: p.id === "agents" ? `${p.icon} ${p.name} ${chalk8.green.bold("\u2605 recommended")}` : `${p.icon} ${p.name}`,
3598
4187
  value: p.id,
@@ -3600,7 +4189,7 @@ async function runInteractiveWizard(options, detected, userTier) {
3600
4189
  selected: p.id === "agents"
3601
4190
  // Pre-select AGENTS.md
3602
4191
  })),
3603
- hint: chalk8.gray("space select \u2022 a toggle all \u2022 enter confirm"),
4192
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
3604
4193
  min: 1,
3605
4194
  instructions: false
3606
4195
  }, promptConfig);
@@ -3670,38 +4259,82 @@ async function runInteractiveWizard(options, detected, userTier) {
3670
4259
  initial: 0
3671
4260
  }, promptConfig);
3672
4261
  answers.architecture = archResponse.architecture || "";
4262
+ console.log();
4263
+ console.log(chalk8.yellow(" \u{1F9E9} Blueprint Template Mode"));
4264
+ console.log(chalk8.gray(" Create a reusable template with [[VARIABLE|default]] placeholders"));
4265
+ console.log(chalk8.gray(" that others can customize when using your blueprint."));
4266
+ console.log();
4267
+ const blueprintResponse = await prompts4({
4268
+ type: "toggle",
4269
+ name: "blueprintMode",
4270
+ message: chalk8.white("Create as Blueprint Template?"),
4271
+ initial: false,
4272
+ active: "Yes",
4273
+ inactive: "No"
4274
+ }, promptConfig);
4275
+ answers.blueprintMode = blueprintResponse.blueprintMode || false;
4276
+ if (answers.blueprintMode) {
4277
+ console.log(chalk8.green(" \u2713 Blueprint mode enabled - values will use [[VARIABLE|default]] syntax"));
4278
+ }
3673
4279
  const techStep = getCurrentStep("tech");
3674
4280
  showStep(currentStepNum, techStep, userTier);
3675
4281
  const letAiResponse = await prompts4({
3676
4282
  type: "toggle",
3677
4283
  name: "letAiDecide",
3678
4284
  message: chalk8.white("Let AI help choose additional technologies?"),
3679
- initial: false,
4285
+ initial: true,
4286
+ // Default to Yes
3680
4287
  active: "Yes",
3681
4288
  inactive: "No"
3682
4289
  }, promptConfig);
3683
- answers.letAiDecide = letAiResponse.letAiDecide || false;
4290
+ answers.letAiDecide = letAiResponse.letAiDecide ?? true;
3684
4291
  console.log();
3685
- console.log(chalk8.gray(" You can also select specific technologies below:"));
4292
+ console.log(chalk8.gray(" Select your tech stack (type to search/filter):"));
3686
4293
  console.log();
3687
- const allStackOptions = [...LANGUAGES, ...FRAMEWORKS, ...DATABASES];
3688
4294
  if (detected?.stack && detected.stack.length > 0) {
3689
- console.log(chalk8.gray(` Detected in project: ${detected.stack.join(", ")}`));
4295
+ console.log(chalk8.green(` \u2713 Detected in project: ${detected.stack.join(", ")}`));
3690
4296
  console.log();
3691
4297
  }
3692
- const stackResponse = await prompts4({
3693
- type: "multiselect",
3694
- name: "stack",
3695
- message: chalk8.white("Languages, frameworks & databases:"),
3696
- choices: allStackOptions.map((s) => ({
4298
+ const languageResponse = await prompts4({
4299
+ type: "autocompleteMultiselect",
4300
+ name: "languages",
4301
+ message: chalk8.white("Languages (type to search):"),
4302
+ choices: LANGUAGES.map((s) => ({
3697
4303
  title: s.title,
3698
- value: s.value
3699
- // No pre-selection - user must explicitly choose
4304
+ value: s.value,
4305
+ selected: detected?.stack?.includes(s.value)
4306
+ })),
4307
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4308
+ instructions: false
4309
+ }, promptConfig);
4310
+ const selectedLanguages = languageResponse.languages || [];
4311
+ const frameworkResponse = await prompts4({
4312
+ type: "autocompleteMultiselect",
4313
+ name: "frameworks",
4314
+ message: chalk8.white("Frameworks (type to search):"),
4315
+ choices: FRAMEWORKS.map((s) => ({
4316
+ title: s.title,
4317
+ value: s.value,
4318
+ selected: detected?.stack?.includes(s.value)
4319
+ })),
4320
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4321
+ instructions: false
4322
+ }, promptConfig);
4323
+ const selectedFrameworks = frameworkResponse.frameworks || [];
4324
+ const databaseResponse = await prompts4({
4325
+ type: "autocompleteMultiselect",
4326
+ name: "databases",
4327
+ message: chalk8.white("Databases (type to search):"),
4328
+ choices: DATABASES.map((s) => ({
4329
+ title: s.title,
4330
+ value: s.value,
4331
+ selected: detected?.stack?.includes(s.value)
3700
4332
  })),
3701
- hint: chalk8.gray("space select \u2022 a toggle all \u2022 enter confirm"),
4333
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
3702
4334
  instructions: false
3703
4335
  }, promptConfig);
3704
- answers.stack = stackResponse.stack || [];
4336
+ const selectedDatabases = databaseResponse.databases || [];
4337
+ answers.stack = [...selectedLanguages, ...selectedFrameworks, ...selectedDatabases];
3705
4338
  const repoStep = getCurrentStep("repo");
3706
4339
  showStep(currentStepNum, repoStep, userTier);
3707
4340
  if (detected?.repoHost || detected?.license || detected?.cicd) {
@@ -3732,10 +4365,11 @@ async function runInteractiveWizard(options, detected, userTier) {
3732
4365
  name: "isPublic",
3733
4366
  message: chalk8.white("Public repository?"),
3734
4367
  initial: false,
4368
+ // Default No
3735
4369
  active: "Yes",
3736
4370
  inactive: "No"
3737
4371
  }, promptConfig);
3738
- answers.isPublic = visibilityResponse.isPublic || false;
4372
+ answers.isPublic = visibilityResponse.isPublic ?? false;
3739
4373
  const licenseChoices = [
3740
4374
  { title: chalk8.gray("\u23ED Skip"), value: "" },
3741
4375
  ...LICENSES.map((l) => ({
@@ -4175,6 +4809,9 @@ async function runInteractiveWizard(options, detected, userTier) {
4175
4809
  console.log();
4176
4810
  console.log(chalk8.cyan(" \u{1F4DD} Customize file contents:"));
4177
4811
  console.log(chalk8.gray(" For each file, choose to use existing content, write new, or use defaults."));
4812
+ if (canAccessAI(userTier)) {
4813
+ console.log(chalk8.magenta(` \u2728 Tip: Type 'ai:' followed by your request to use AI assistance`));
4814
+ }
4178
4815
  console.log();
4179
4816
  answers.staticFileContents = {};
4180
4817
  for (const fileKey of answers.staticFiles) {
@@ -4220,9 +4857,43 @@ async function runInteractiveWizard(options, detected, userTier) {
4220
4857
  }, promptConfig);
4221
4858
  if (actionResponse.action === "new") {
4222
4859
  console.log();
4223
- const content = await readMultilineInput(` Content for ${filePath}:`);
4224
- if (content.trim()) {
4225
- answers.staticFileContents[fileKey] = content;
4860
+ if (canAccessAI(userTier)) {
4861
+ const aiPromptResponse = await prompts4({
4862
+ type: "text",
4863
+ name: "input",
4864
+ message: chalk8.white(`Content for ${filePath}:`),
4865
+ hint: chalk8.gray("Type 'ai:' + description OR paste content (press Enter twice to skip)")
4866
+ }, promptConfig);
4867
+ let content = aiPromptResponse.input || "";
4868
+ if (content.toLowerCase().startsWith("ai:")) {
4869
+ const aiInstruction = content.substring(3).trim();
4870
+ if (aiInstruction) {
4871
+ const aiResult = await aiAssist(`Generate ${filePath} content: ${aiInstruction}`, existingContent);
4872
+ if (aiResult) {
4873
+ console.log(chalk8.cyan(" AI-generated content preview (first 200 chars):"));
4874
+ console.log(chalk8.gray(" " + aiResult.substring(0, 200) + (aiResult.length > 200 ? "..." : "")));
4875
+ const acceptAI = await prompts4({
4876
+ type: "confirm",
4877
+ name: "accept",
4878
+ message: chalk8.white("Use this AI-generated content?"),
4879
+ initial: true
4880
+ }, promptConfig);
4881
+ if (acceptAI.accept) {
4882
+ content = aiResult;
4883
+ } else {
4884
+ content = "";
4885
+ }
4886
+ }
4887
+ }
4888
+ }
4889
+ if (content.trim()) {
4890
+ answers.staticFileContents[fileKey] = content;
4891
+ }
4892
+ } else {
4893
+ const content = await readMultilineInput(` Content for ${filePath}:`);
4894
+ if (content.trim()) {
4895
+ answers.staticFileContents[fileKey] = content;
4896
+ }
4226
4897
  }
4227
4898
  }
4228
4899
  }
@@ -4259,13 +4930,51 @@ async function runInteractiveWizard(options, detected, userTier) {
4259
4930
  } else {
4260
4931
  answers.persona = personaResponse.persona || "";
4261
4932
  }
4933
+ const hasAIAccess = canAccessAI(userTier);
4934
+ if (hasAIAccess) {
4935
+ console.log();
4936
+ console.log(chalk8.magenta(` \u2728 AI Assistant available (like ${getAIShortcutHint()} in the web UI)`));
4937
+ console.log(chalk8.gray(" Describe what you want to add, and AI will format it for your config."));
4938
+ console.log();
4939
+ }
4262
4940
  const extraNotesResponse = await prompts4({
4263
4941
  type: "text",
4264
4942
  name: "extraNotes",
4265
4943
  message: chalk8.white("Anything else AI should know? (optional):"),
4266
- hint: chalk8.gray("Special requirements, gotchas, team conventions...")
4944
+ hint: hasAIAccess ? chalk8.gray("Enter text or type 'ai:' followed by your request") : chalk8.gray("Special requirements, gotchas, team conventions...")
4267
4945
  }, promptConfig);
4268
- answers.extraNotes = extraNotesResponse.extraNotes || "";
4946
+ let extraNotes = extraNotesResponse.extraNotes || "";
4947
+ if (hasAIAccess && extraNotes.toLowerCase().startsWith("ai:")) {
4948
+ const aiInstruction = extraNotes.substring(3).trim();
4949
+ if (aiInstruction) {
4950
+ const aiResult = await aiAssist(aiInstruction);
4951
+ if (aiResult) {
4952
+ console.log();
4953
+ console.log(chalk8.cyan(" AI suggestion:"));
4954
+ console.log(chalk8.gray(" \u2500".repeat(30)));
4955
+ console.log(chalk8.white(" " + aiResult.split("\n").join("\n ")));
4956
+ console.log(chalk8.gray(" \u2500".repeat(30)));
4957
+ console.log();
4958
+ const acceptResponse = await prompts4({
4959
+ type: "confirm",
4960
+ name: "accept",
4961
+ message: chalk8.white("Use this AI-generated content?"),
4962
+ initial: true
4963
+ }, promptConfig);
4964
+ if (acceptResponse.accept) {
4965
+ extraNotes = aiResult;
4966
+ } else {
4967
+ const manualResponse = await prompts4({
4968
+ type: "text",
4969
+ name: "extraNotes",
4970
+ message: chalk8.white("Enter your own notes instead:")
4971
+ }, promptConfig);
4972
+ extraNotes = manualResponse.extraNotes || "";
4973
+ }
4974
+ }
4975
+ }
4976
+ }
4977
+ answers.extraNotes = extraNotes;
4269
4978
  console.log();
4270
4979
  console.log(chalk8.green(" \u2705 All steps completed!"));
4271
4980
  console.log();
@@ -4277,6 +4986,8 @@ async function runInteractiveWizard(options, detected, userTier) {
4277
4986
  persona: answers.persona,
4278
4987
  boundaries: answers.boundaries,
4279
4988
  commands: typeof answers.commands === "object" ? answers.commands : detected?.commands || {},
4989
+ // Blueprint mode
4990
+ blueprintMode: answers.blueprintMode,
4280
4991
  // Extended config for Pro/Max users
4281
4992
  projectType: answers.projectType,
4282
4993
  devOS: answers.devOS,
@@ -4285,12 +4996,14 @@ async function runInteractiveWizard(options, detected, userTier) {
4285
4996
  isPublic: answers.isPublic,
4286
4997
  license: answers.license,
4287
4998
  conventionalCommits: answers.conventionalCommits,
4999
+ letAiDecide: answers.letAiDecide,
4288
5000
  namingConvention: answers.namingConvention,
4289
5001
  errorHandling: answers.errorHandling,
4290
5002
  styleNotes: answers.styleNotes,
4291
5003
  aiBehavior: answers.aiBehavior,
4292
5004
  importantFiles: answers.importantFiles,
4293
5005
  selfImprove: answers.selfImprove,
5006
+ includePersonalData: answers.includePersonalData,
4294
5007
  boundaryNever: answers.boundaryNever,
4295
5008
  boundaryAsk: answers.boundaryAsk,
4296
5009
  testLevels: answers.testLevels,
@@ -4298,8 +5011,18 @@ async function runInteractiveWizard(options, detected, userTier) {
4298
5011
  coverageTarget: answers.coverageTarget,
4299
5012
  testNotes: answers.testNotes,
4300
5013
  staticFiles: answers.staticFiles,
5014
+ staticFileContents: answers.staticFileContents,
4301
5015
  includeFunding: answers.includeFunding,
4302
- extraNotes: answers.extraNotes
5016
+ extraNotes: answers.extraNotes,
5017
+ semver: answers.semver,
5018
+ dependabot: answers.dependabot,
5019
+ cicd: answers.cicd,
5020
+ deploymentTargets: answers.deploymentTargets,
5021
+ buildContainer: answers.buildContainer,
5022
+ containerRegistry: answers.containerRegistry,
5023
+ exampleRepoUrl: answers.exampleRepoUrl,
5024
+ documentationUrl: answers.documentationUrl,
5025
+ loggingConventions: answers.loggingConventions
4303
5026
  };
4304
5027
  }
4305
5028
 
@@ -5863,11 +6586,565 @@ async function listTrackedBlueprints(cwd) {
5863
6586
  console.log();
5864
6587
  }
5865
6588
 
6589
+ // src/commands/analyze.ts
6590
+ import chalk16 from "chalk";
6591
+ import ora13 from "ora";
6592
+ async function analyzeCommand(options) {
6593
+ console.log();
6594
+ if (!options.json) {
6595
+ console.log(chalk16.cyan.bold(" \u{1F50D} LynxPrompt Analyzer"));
6596
+ console.log(chalk16.gray(" Detect project configuration and tech stack"));
6597
+ console.log();
6598
+ }
6599
+ let detected;
6600
+ let source = "local";
6601
+ if (options.remote) {
6602
+ if (!isGitUrl(options.remote)) {
6603
+ if (!options.json) {
6604
+ console.log(chalk16.red(" \u2717 Invalid Git URL provided"));
6605
+ }
6606
+ process.exit(1);
6607
+ }
6608
+ const host = detectRepoHost(options.remote);
6609
+ const method = host === "github" ? "GitHub API" : host === "gitlab" ? "GitLab API" : "shallow clone";
6610
+ if (!options.json) {
6611
+ const spinner = ora13(`Analyzing remote repository via ${method}...`).start();
6612
+ detected = await detectFromRemoteUrl(options.remote);
6613
+ if (detected) {
6614
+ spinner.succeed("Remote repository analyzed");
6615
+ } else {
6616
+ spinner.fail("Could not analyze repository");
6617
+ console.log(chalk16.gray(" The repository may be private or inaccessible."));
6618
+ process.exit(1);
6619
+ }
6620
+ } else {
6621
+ detected = await detectFromRemoteUrl(options.remote);
6622
+ if (!detected) {
6623
+ console.log(JSON.stringify({ error: "Could not analyze repository" }));
6624
+ process.exit(1);
6625
+ }
6626
+ }
6627
+ source = options.remote;
6628
+ } else {
6629
+ if (!options.json) {
6630
+ const spinner = ora13("Analyzing current directory...").start();
6631
+ detected = await detectProject(process.cwd());
6632
+ if (detected) {
6633
+ spinner.succeed("Project analyzed");
6634
+ } else {
6635
+ spinner.info("No project detected");
6636
+ }
6637
+ } else {
6638
+ detected = await detectProject(process.cwd());
6639
+ }
6640
+ }
6641
+ if (options.json) {
6642
+ console.log(JSON.stringify({
6643
+ source,
6644
+ detected: detected || null
6645
+ }, null, 2));
6646
+ return;
6647
+ }
6648
+ console.log();
6649
+ if (!detected) {
6650
+ console.log(chalk16.yellow(" No project configuration detected."));
6651
+ console.log();
6652
+ console.log(chalk16.gray(" Tips:"));
6653
+ console.log(chalk16.gray(" \u2022 Make sure you're in a project directory with a package.json, pyproject.toml, etc."));
6654
+ console.log(chalk16.gray(" \u2022 Use --remote <url> to analyze a remote repository"));
6655
+ console.log();
6656
+ return;
6657
+ }
6658
+ console.log(chalk16.green.bold(" \u{1F4CA} Project Analysis"));
6659
+ console.log();
6660
+ console.log(chalk16.white(" Basic Info"));
6661
+ console.log(chalk16.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6662
+ console.log(` ${chalk16.dim("Name:")} ${chalk16.white(detected.name || "unknown")}`);
6663
+ if (detected.description) {
6664
+ console.log(` ${chalk16.dim("Description:")} ${chalk16.gray(detected.description)}`);
6665
+ }
6666
+ console.log(` ${chalk16.dim("Type:")} ${chalk16.white(detected.type || "application")}`);
6667
+ if (detected.license) {
6668
+ console.log(` ${chalk16.dim("License:")} ${chalk16.white(detected.license.toUpperCase())}`);
6669
+ }
6670
+ console.log();
6671
+ console.log(chalk16.white(" Tech Stack"));
6672
+ console.log(chalk16.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6673
+ if (detected.stack.length > 0) {
6674
+ for (const tech of detected.stack) {
6675
+ console.log(` ${chalk16.cyan("\u2022")} ${tech}`);
6676
+ }
6677
+ } else {
6678
+ console.log(chalk16.gray(" No tech stack detected"));
6679
+ }
6680
+ console.log();
6681
+ console.log(chalk16.white(" Infrastructure"));
6682
+ console.log(chalk16.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6683
+ console.log(` ${chalk16.dim("Package Manager:")} ${detected.packageManager || chalk16.gray("none detected")}`);
6684
+ console.log(` ${chalk16.dim("Repo Host:")} ${detected.repoHost || chalk16.gray("none detected")}`);
6685
+ console.log(` ${chalk16.dim("CI/CD:")} ${detected.cicd || chalk16.gray("none detected")}`);
6686
+ console.log(` ${chalk16.dim("Docker:")} ${detected.hasDocker ? chalk16.green("yes") : chalk16.gray("no")}`);
6687
+ console.log();
6688
+ if (detected.commands && Object.keys(detected.commands).length > 0) {
6689
+ console.log(chalk16.white(" Commands"));
6690
+ console.log(chalk16.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6691
+ for (const [key, value] of Object.entries(detected.commands)) {
6692
+ if (value) {
6693
+ const cmdValue = Array.isArray(value) ? value.join(", ") : value;
6694
+ console.log(` ${chalk16.dim(key.padEnd(10))} ${chalk16.yellow(cmdValue)}`);
6695
+ }
6696
+ }
6697
+ console.log();
6698
+ }
6699
+ console.log(chalk16.white(" \u{1F4A1} Recommendations"));
6700
+ console.log(chalk16.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6701
+ const recommendations = [];
6702
+ if (!detected.cicd) {
6703
+ recommendations.push("Add CI/CD configuration (GitHub Actions, GitLab CI, etc.)");
6704
+ }
6705
+ if (!detected.hasDocker && detected.type === "application") {
6706
+ recommendations.push("Consider adding Docker for containerization");
6707
+ }
6708
+ if (!detected.stack.some((s) => ["vitest", "jest", "pytest", "mocha"].includes(s))) {
6709
+ recommendations.push("Add a test framework (vitest, jest, pytest, etc.)");
6710
+ }
6711
+ if (!detected.commands?.lint) {
6712
+ recommendations.push("Add a linting command (eslint, ruff, clippy, etc.)");
6713
+ }
6714
+ if (!detected.license) {
6715
+ recommendations.push("Add a LICENSE file for open source projects");
6716
+ }
6717
+ if (recommendations.length > 0) {
6718
+ for (const rec of recommendations) {
6719
+ console.log(` ${chalk16.yellow("\u2022")} ${rec}`);
6720
+ }
6721
+ } else {
6722
+ console.log(chalk16.green(" \u2713 Project looks well-configured!"));
6723
+ }
6724
+ console.log();
6725
+ console.log(chalk16.gray(" Run 'lynxp wizard' to generate AI IDE configuration"));
6726
+ console.log();
6727
+ }
6728
+
6729
+ // src/commands/convert.ts
6730
+ import chalk17 from "chalk";
6731
+ import { readFile as readFile11, writeFile as writeFile7, access as access5 } from "fs/promises";
6732
+ import { join as join13, basename as basename2 } from "path";
6733
+ import ora14 from "ora";
6734
+ var SOURCE_FILES = {
6735
+ "agents.md": "agents",
6736
+ "claude.md": "claude",
6737
+ ".cursorrules": "cursor_legacy",
6738
+ ".cursor/rules/project.mdc": "cursor",
6739
+ "cursor.rules/project.mdc": "cursor",
6740
+ "project.mdc": "cursor",
6741
+ ".github/copilot-instructions.md": "copilot",
6742
+ "copilot-instructions.md": "copilot",
6743
+ ".windsurfrules": "windsurf",
6744
+ ".clinerules": "cline",
6745
+ ".aider.conf.yml": "aider"
6746
+ };
6747
+ var TARGET_FILES = {
6748
+ agents: "AGENTS.md",
6749
+ claude: "CLAUDE.md",
6750
+ cursor: ".cursor/rules/project.mdc",
6751
+ cursor_legacy: ".cursorrules",
6752
+ copilot: ".github/copilot-instructions.md",
6753
+ windsurf: ".windsurfrules",
6754
+ cline: ".clinerules",
6755
+ aider: ".aider.conf.yml",
6756
+ codex: "codex.md",
6757
+ supermaven: "supermaven.md",
6758
+ goose: ".goose/rules.txt"
6759
+ };
6760
+ var PLATFORM_NAMES = {
6761
+ agents: "AGENTS.md (Universal)",
6762
+ claude: "CLAUDE.md",
6763
+ cursor: "Cursor Rules (.mdc)",
6764
+ cursor_legacy: "Cursor Rules (legacy)",
6765
+ copilot: "GitHub Copilot",
6766
+ windsurf: "Windsurf Rules",
6767
+ cline: "Cline Rules",
6768
+ aider: "Aider Config",
6769
+ codex: "Codex",
6770
+ supermaven: "Supermaven",
6771
+ goose: "Goose Rules"
6772
+ };
6773
+ async function detectSourceFile(cwd) {
6774
+ for (const [pattern, platform2] of Object.entries(SOURCE_FILES)) {
6775
+ try {
6776
+ const fullPath = join13(cwd, pattern);
6777
+ await access5(fullPath);
6778
+ return { path: fullPath, platform: platform2 };
6779
+ } catch {
6780
+ }
6781
+ }
6782
+ const uppercaseVariants = ["AGENTS.md", "CLAUDE.md"];
6783
+ for (const variant of uppercaseVariants) {
6784
+ try {
6785
+ const fullPath = join13(cwd, variant);
6786
+ await access5(fullPath);
6787
+ const platform2 = variant.toLowerCase().replace(".md", "");
6788
+ return { path: fullPath, platform: platform2 };
6789
+ } catch {
6790
+ }
6791
+ }
6792
+ return null;
6793
+ }
6794
+ function parseMarkdownConfig(content) {
6795
+ const config2 = {};
6796
+ const sections = content.split(/^##\s+/m);
6797
+ for (const section of sections) {
6798
+ const lines = section.trim().split("\n");
6799
+ if (lines.length === 0) continue;
6800
+ const title = lines[0].toLowerCase().trim();
6801
+ const body = lines.slice(1).join("\n").trim();
6802
+ config2[title] = body;
6803
+ }
6804
+ config2._raw = content;
6805
+ return config2;
6806
+ }
6807
+ function generateTargetContent(config2, targetPlatform) {
6808
+ const rawContent = config2._raw || "";
6809
+ switch (targetPlatform) {
6810
+ case "cursor":
6811
+ return `---
6812
+ description: "AI coding rules"
6813
+ globs: ["**/*"]
6814
+ alwaysApply: true
6815
+ ---
6816
+
6817
+ ${rawContent.replace(/^#\s+.*$/m, "# AI Assistant Configuration")}
6818
+ `;
6819
+ case "cursor_legacy":
6820
+ return rawContent.replace(/^#\s+/gm, "").replace(/^##\s+/gm, "\n").replace(/^###\s+/gm, "").trim();
6821
+ case "windsurf":
6822
+ case "cline":
6823
+ case "goose":
6824
+ return rawContent.replace(/^#\s+/gm, "=== ").replace(/^##\s+/gm, "--- ").replace(/^###\s+/gm, "").replace(/\*\*/g, "").trim();
6825
+ case "aider":
6826
+ return `# Aider configuration
6827
+ # Converted from AI IDE configuration
6828
+
6829
+ lint-cmd: []
6830
+ auto-test: false
6831
+ read: []
6832
+ # AI rules converted below as conventions
6833
+ conventions:
6834
+ ${rawContent.split("\n").filter((line) => line.trim() && !line.startsWith("#")).map((line) => ` - "${line.replace(/"/g, '\\"').trim()}"`).slice(0, 20).join("\n")}
6835
+ `;
6836
+ case "copilot":
6837
+ return `# GitHub Copilot Instructions
6838
+
6839
+ ${rawContent}
6840
+ `;
6841
+ case "agents":
6842
+ case "claude":
6843
+ case "codex":
6844
+ case "supermaven":
6845
+ default:
6846
+ return rawContent;
6847
+ }
6848
+ }
6849
+ async function convertCommand(source, target, options) {
6850
+ console.log();
6851
+ console.log(chalk17.cyan.bold(" \u{1F504} LynxPrompt Convert"));
6852
+ console.log(chalk17.gray(" Convert AI IDE configuration between formats"));
6853
+ console.log();
6854
+ const cwd = process.cwd();
6855
+ let sourcePath;
6856
+ let sourcePlatform;
6857
+ if (source) {
6858
+ sourcePath = join13(cwd, source);
6859
+ const sourceBasename = basename2(source).toLowerCase();
6860
+ sourcePlatform = SOURCE_FILES[sourceBasename] || "unknown";
6861
+ } else {
6862
+ const detected = await detectSourceFile(cwd);
6863
+ if (!detected) {
6864
+ console.log(chalk17.red(" \u2717 No AI configuration file found in current directory"));
6865
+ console.log();
6866
+ console.log(chalk17.gray(" Supported source files:"));
6867
+ for (const file of Object.keys(SOURCE_FILES)) {
6868
+ console.log(chalk17.gray(` \u2022 ${file}`));
6869
+ }
6870
+ console.log();
6871
+ console.log(chalk17.gray(" Usage: lynxp convert <source> <target>"));
6872
+ console.log(chalk17.gray(" Example: lynxp convert AGENTS.md cursor"));
6873
+ process.exit(1);
6874
+ }
6875
+ sourcePath = detected.path;
6876
+ sourcePlatform = detected.platform;
6877
+ }
6878
+ const normalizedTarget = target.toLowerCase().replace("-", "_");
6879
+ if (!TARGET_FILES[normalizedTarget]) {
6880
+ console.log(chalk17.red(` \u2717 Unknown target format: ${target}`));
6881
+ console.log();
6882
+ console.log(chalk17.gray(" Supported target formats:"));
6883
+ for (const [key, name] of Object.entries(PLATFORM_NAMES)) {
6884
+ console.log(chalk17.gray(` \u2022 ${key.padEnd(15)} \u2192 ${name}`));
6885
+ }
6886
+ process.exit(1);
6887
+ }
6888
+ let sourceContent;
6889
+ try {
6890
+ sourceContent = await readFile11(sourcePath, "utf-8");
6891
+ } catch {
6892
+ console.log(chalk17.red(` \u2717 Could not read source file: ${sourcePath}`));
6893
+ process.exit(1);
6894
+ }
6895
+ console.log(chalk17.white(` Source: ${basename2(sourcePath)} (${PLATFORM_NAMES[sourcePlatform] || sourcePlatform})`));
6896
+ console.log(chalk17.white(` Target: ${PLATFORM_NAMES[normalizedTarget]}`));
6897
+ console.log();
6898
+ const spinner = ora14("Converting configuration...").start();
6899
+ const config2 = parseMarkdownConfig(sourceContent);
6900
+ const targetContent = generateTargetContent(config2, normalizedTarget);
6901
+ spinner.stop();
6902
+ const outputFilename = options.output || TARGET_FILES[normalizedTarget];
6903
+ const outputPath = join13(cwd, outputFilename);
6904
+ try {
6905
+ await access5(outputPath);
6906
+ if (!options.force) {
6907
+ console.log(chalk17.yellow(` \u26A0\uFE0F File already exists: ${outputFilename}`));
6908
+ console.log(chalk17.gray(" Use --force to overwrite"));
6909
+ process.exit(1);
6910
+ }
6911
+ } catch {
6912
+ }
6913
+ try {
6914
+ const { mkdir: mkdir6 } = await import("fs/promises");
6915
+ const outputDir = join13(cwd, outputFilename.split("/").slice(0, -1).join("/"));
6916
+ if (outputDir !== cwd) {
6917
+ await mkdir6(outputDir, { recursive: true });
6918
+ }
6919
+ await writeFile7(outputPath, targetContent, "utf-8");
6920
+ console.log(chalk17.green(` \u2713 Converted to ${outputFilename}`));
6921
+ console.log();
6922
+ console.log(chalk17.gray(` Lines: ${targetContent.split("\n").length}`));
6923
+ console.log(chalk17.gray(` Size: ${targetContent.length} bytes`));
6924
+ console.log();
6925
+ } catch (error) {
6926
+ console.log(chalk17.red(` \u2717 Could not write output: ${error instanceof Error ? error.message : "unknown error"}`));
6927
+ process.exit(1);
6928
+ }
6929
+ }
6930
+
6931
+ // src/commands/merge.ts
6932
+ import chalk18 from "chalk";
6933
+ import { readFile as readFile12, writeFile as writeFile8, access as access6 } from "fs/promises";
6934
+ import { join as join14, basename as basename3 } from "path";
6935
+ import ora15 from "ora";
6936
+ import prompts8 from "prompts";
6937
+ function parseMarkdownSections(content, sourceName) {
6938
+ const sections = [];
6939
+ const parts = content.split(/^(#{1,3})\s+(.+)$/m);
6940
+ let currentSection = null;
6941
+ for (let i = 0; i < parts.length; i++) {
6942
+ const part = parts[i];
6943
+ if (part.match(/^#{1,3}$/)) {
6944
+ if (currentSection) {
6945
+ sections.push(currentSection);
6946
+ }
6947
+ const title = parts[i + 1] || "Untitled";
6948
+ currentSection = {
6949
+ title: title.trim(),
6950
+ content: "",
6951
+ source: sourceName
6952
+ };
6953
+ i++;
6954
+ } else if (currentSection) {
6955
+ currentSection.content += part;
6956
+ } else if (part.trim()) {
6957
+ currentSection = {
6958
+ title: "Introduction",
6959
+ content: part,
6960
+ source: sourceName
6961
+ };
6962
+ }
6963
+ }
6964
+ if (currentSection) {
6965
+ sections.push(currentSection);
6966
+ }
6967
+ return sections;
6968
+ }
6969
+ function mergeSectionsByStrategy(allSections, strategy) {
6970
+ switch (strategy) {
6971
+ case "concat":
6972
+ return allSections.map((sections, i) => {
6973
+ const sourceName = sections[0]?.source || `Source ${i + 1}`;
6974
+ const content = sections.map((s) => {
6975
+ if (s.title !== "Introduction") {
6976
+ return `## ${s.title}
6977
+
6978
+ ${s.content.trim()}`;
6979
+ }
6980
+ return s.content.trim();
6981
+ }).join("\n\n");
6982
+ return `<!-- From: ${sourceName} -->
6983
+ ${content}`;
6984
+ }).join("\n\n---\n\n");
6985
+ case "sections":
6986
+ const sectionMap = /* @__PURE__ */ new Map();
6987
+ for (const fileSections of allSections) {
6988
+ for (const section of fileSections) {
6989
+ const normalized = section.title.toLowerCase();
6990
+ if (!sectionMap.has(normalized)) {
6991
+ sectionMap.set(normalized, []);
6992
+ }
6993
+ sectionMap.get(normalized).push(section);
6994
+ }
6995
+ }
6996
+ const merged = [];
6997
+ for (const [, sections] of sectionMap) {
6998
+ if (sections.length === 1) {
6999
+ merged.push(`## ${sections[0].title}
7000
+
7001
+ ${sections[0].content.trim()}`);
7002
+ } else {
7003
+ const combinedContent = sections.map((s) => `<!-- From: ${s.source} -->
7004
+ ${s.content.trim()}`).join("\n\n");
7005
+ merged.push(`## ${sections[0].title}
7006
+
7007
+ ${combinedContent}`);
7008
+ }
7009
+ }
7010
+ return merged.join("\n\n");
7011
+ case "smart":
7012
+ default:
7013
+ const seen = /* @__PURE__ */ new Set();
7014
+ const result = [];
7015
+ const flatSections = allSections.flat().sort((a, b) => {
7016
+ return b.content.length - a.content.length;
7017
+ });
7018
+ for (const section of flatSections) {
7019
+ const contentKey = section.content.toLowerCase().replace(/\s+/g, " ").trim().slice(0, 200);
7020
+ if (!seen.has(contentKey)) {
7021
+ seen.add(contentKey);
7022
+ if (section.title !== "Introduction") {
7023
+ result.push(`## ${section.title}
7024
+
7025
+ ${section.content.trim()}`);
7026
+ } else {
7027
+ result.push(section.content.trim());
7028
+ }
7029
+ }
7030
+ }
7031
+ return result.join("\n\n");
7032
+ }
7033
+ }
7034
+ async function mergeCommand(files, options) {
7035
+ console.log();
7036
+ console.log(chalk18.cyan.bold(" \u{1F500} LynxPrompt Merge"));
7037
+ console.log(chalk18.gray(" Merge multiple AI IDE configuration files"));
7038
+ console.log();
7039
+ if (files.length < 2) {
7040
+ console.log(chalk18.red(" \u2717 Please provide at least 2 files to merge"));
7041
+ console.log();
7042
+ console.log(chalk18.gray(" Usage: lynxp merge <file1> <file2> [...files]"));
7043
+ console.log(chalk18.gray(" Example: lynxp merge AGENTS.md team-rules.md --output merged.md"));
7044
+ console.log();
7045
+ console.log(chalk18.gray(" Options:"));
7046
+ console.log(chalk18.gray(" --output <file> Output filename (default: merged.md)"));
7047
+ console.log(chalk18.gray(" --strategy <type> Merge strategy: concat, sections, smart (default: smart)"));
7048
+ console.log(chalk18.gray(" --force Overwrite existing output file"));
7049
+ console.log(chalk18.gray(" --interactive Review and select sections to include"));
7050
+ process.exit(1);
7051
+ }
7052
+ const cwd = process.cwd();
7053
+ const strategy = options.strategy || "smart";
7054
+ console.log(chalk18.white(` Files to merge: ${files.length}`));
7055
+ console.log(chalk18.white(` Strategy: ${strategy}`));
7056
+ console.log();
7057
+ const allSections = [];
7058
+ for (const file of files) {
7059
+ const filePath = join14(cwd, file);
7060
+ try {
7061
+ await access6(filePath);
7062
+ } catch {
7063
+ console.log(chalk18.red(` \u2717 File not found: ${file}`));
7064
+ process.exit(1);
7065
+ }
7066
+ try {
7067
+ const content = await readFile12(filePath, "utf-8");
7068
+ const sections = parseMarkdownSections(content, basename3(file));
7069
+ allSections.push(sections);
7070
+ console.log(chalk18.gray(` \u2713 Read ${file} (${sections.length} sections)`));
7071
+ } catch (error) {
7072
+ console.log(chalk18.red(` \u2717 Could not read ${file}: ${error instanceof Error ? error.message : "unknown"}`));
7073
+ process.exit(1);
7074
+ }
7075
+ }
7076
+ console.log();
7077
+ if (options.interactive) {
7078
+ const flatSections = allSections.flatMap(
7079
+ (sections, i) => sections.map((s) => ({ ...s, fileIndex: i }))
7080
+ );
7081
+ const choices = flatSections.map((section, i) => ({
7082
+ title: `[${section.source}] ${section.title}`,
7083
+ value: i,
7084
+ selected: true
7085
+ }));
7086
+ const response = await prompts8({
7087
+ type: "multiselect",
7088
+ name: "sections",
7089
+ message: "Select sections to include:",
7090
+ choices,
7091
+ instructions: false,
7092
+ hint: "- Space to toggle, Enter to confirm"
7093
+ });
7094
+ if (!response.sections || response.sections.length === 0) {
7095
+ console.log(chalk18.yellow(" No sections selected, aborting."));
7096
+ process.exit(0);
7097
+ }
7098
+ const selectedSections = response.sections.map((i) => flatSections[i]);
7099
+ allSections.length = 0;
7100
+ allSections.push(selectedSections);
7101
+ }
7102
+ const spinner = ora15("Merging configurations...").start();
7103
+ const mergedContent = mergeSectionsByStrategy(allSections, strategy);
7104
+ spinner.stop();
7105
+ const finalContent = `# AI Assistant Configuration (Merged)
7106
+
7107
+ <!--
7108
+ Merged from: ${files.join(", ")}
7109
+ Strategy: ${strategy}
7110
+ Date: ${(/* @__PURE__ */ new Date()).toISOString()}
7111
+ -->
7112
+
7113
+ ${mergedContent}
7114
+ `;
7115
+ const outputFilename = options.output || "merged.md";
7116
+ const outputPath = join14(cwd, outputFilename);
7117
+ try {
7118
+ await access6(outputPath);
7119
+ if (!options.force) {
7120
+ console.log(chalk18.yellow(` \u26A0\uFE0F File already exists: ${outputFilename}`));
7121
+ console.log(chalk18.gray(" Use --force to overwrite"));
7122
+ process.exit(1);
7123
+ }
7124
+ } catch {
7125
+ }
7126
+ try {
7127
+ await writeFile8(outputPath, finalContent, "utf-8");
7128
+ console.log(chalk18.green(` \u2713 Merged to ${outputFilename}`));
7129
+ console.log();
7130
+ console.log(chalk18.gray(` Sources: ${files.length} files`));
7131
+ console.log(chalk18.gray(` Lines: ${finalContent.split("\n").length}`));
7132
+ console.log(chalk18.gray(` Size: ${finalContent.length} bytes`));
7133
+ console.log();
7134
+ } catch (error) {
7135
+ console.log(chalk18.red(` \u2717 Could not write output: ${error instanceof Error ? error.message : "unknown error"}`));
7136
+ process.exit(1);
7137
+ }
7138
+ }
7139
+
5866
7140
  // src/index.ts
5867
7141
  var program = new Command();
5868
7142
  program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version("0.3.0");
5869
- 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)").action(wizardCommand);
7143
+ 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);
5870
7144
  program.command("check").description("Validate AI configuration files (for CI/CD)").option("--ci", "CI mode - exit codes only (0=pass, 1=fail)").action(checkCommand);
7145
+ program.command("analyze").description("Analyze project configuration without generating files").option("-r, --remote <url>", "Analyze a remote repository (GitHub/GitLab)").option("-j, --json", "Output as JSON (for scripting)").action(analyzeCommand);
7146
+ program.command("convert [source] <target>").description("Convert AI config between formats (e.g., AGENTS.md \u2192 cursor)").option("-o, --output <file>", "Output filename").option("-f, --force", "Overwrite existing output file").action(convertCommand);
7147
+ program.command("merge <files...>").description("Merge multiple AI configuration files into one").option("-o, --output <file>", "Output filename (default: merged.md)").option("-s, --strategy <type>", "Merge strategy: concat, sections, smart (default: smart)").option("-f, --force", "Overwrite existing output file").option("-i, --interactive", "Review and select sections to include").action(mergeCommand);
5871
7148
  program.command("status").description("Show current AI configuration and tracked blueprints").action(statusCommand);
5872
7149
  program.command("pull <id>").description("Download and track a blueprint from the marketplace").option("-o, --output <path>", "Output directory", ".").option("-y, --yes", "Overwrite existing files without prompting").option("--preview", "Preview content without downloading").option("--no-track", "Don't track the blueprint for future syncs").action(pullCommand);
5873
7150
  program.command("search <query>").description("Search public blueprints in the marketplace").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
@@ -5885,33 +7162,40 @@ program.command("whoami").description("Show current authenticated user").action(
5885
7162
  program.addHelpText(
5886
7163
  "beforeAll",
5887
7164
  `
5888
- ${chalk16.cyan("\u{1F431} LynxPrompt CLI")} ${chalk16.gray("(also available as: lynxp)")}
5889
- ${chalk16.gray("Generate AI IDE configuration files from your terminal")}
7165
+ ${chalk19.cyan("\u{1F431} LynxPrompt CLI")} ${chalk19.gray("(also available as: lynxp)")}
7166
+ ${chalk19.gray("Generate AI IDE configuration files from your terminal")}
5890
7167
  `
5891
7168
  );
5892
7169
  program.addHelpText(
5893
7170
  "after",
5894
7171
  `
5895
- ${chalk16.cyan("Quick Start:")}
5896
- ${chalk16.white("$ lynxp wizard")} ${chalk16.gray("Generate config interactively")}
5897
- ${chalk16.white("$ lynxp wizard -y")} ${chalk16.gray("Generate AGENTS.md with defaults")}
5898
- ${chalk16.white("$ lynxp wizard -f cursor")} ${chalk16.gray("Generate .cursor/rules/")}
5899
-
5900
- ${chalk16.cyan("Marketplace:")}
5901
- ${chalk16.white("$ lynxp search nextjs")} ${chalk16.gray("Search blueprints")}
5902
- ${chalk16.white("$ lynxp pull bp_abc123")} ${chalk16.gray("Download and track a blueprint")}
5903
- ${chalk16.white("$ lynxp push")} ${chalk16.gray("Push local file to cloud")}
5904
- ${chalk16.white("$ lynxp link --list")} ${chalk16.gray("Show tracked blueprints")}
5905
-
5906
- ${chalk16.cyan("Blueprint Tracking:")}
5907
- ${chalk16.white("$ lynxp link AGENTS.md bp_xyz")} ${chalk16.gray("Link existing file to blueprint")}
5908
- ${chalk16.white("$ lynxp unlink AGENTS.md")} ${chalk16.gray("Disconnect from cloud")}
5909
- ${chalk16.white("$ lynxp diff bp_abc123")} ${chalk16.gray("Show changes vs cloud version")}
5910
-
5911
- ${chalk16.cyan("CI/CD:")}
5912
- ${chalk16.white("$ lynxp check --ci")} ${chalk16.gray("Validate config (exit code)")}
5913
-
5914
- ${chalk16.gray("Docs: https://lynxprompt.com/docs/cli")}
7172
+ ${chalk19.cyan("Quick Start:")}
7173
+ ${chalk19.white("$ lynxp wizard")} ${chalk19.gray("Generate config interactively")}
7174
+ ${chalk19.white("$ lynxp wizard -y")} ${chalk19.gray("Generate AGENTS.md with defaults")}
7175
+ ${chalk19.white("$ lynxp wizard -f cursor")} ${chalk19.gray("Generate .cursor/rules/")}
7176
+ ${chalk19.white("$ lynxp wizard --blueprint")} ${chalk19.gray("Generate with [[VAR|default]] placeholders")}
7177
+
7178
+ ${chalk19.cyan("Analysis & Tools:")}
7179
+ ${chalk19.white("$ lynxp analyze")} ${chalk19.gray("Analyze project tech stack")}
7180
+ ${chalk19.white("$ lynxp analyze -r <url>")} ${chalk19.gray("Analyze remote repository")}
7181
+ ${chalk19.white("$ lynxp convert AGENTS.md cursor")} ${chalk19.gray("Convert to Cursor format")}
7182
+ ${chalk19.white("$ lynxp merge a.md b.md -o out.md")} ${chalk19.gray("Merge multiple configs")}
7183
+
7184
+ ${chalk19.cyan("Marketplace:")}
7185
+ ${chalk19.white("$ lynxp search nextjs")} ${chalk19.gray("Search blueprints")}
7186
+ ${chalk19.white("$ lynxp pull bp_abc123")} ${chalk19.gray("Download and track a blueprint")}
7187
+ ${chalk19.white("$ lynxp push")} ${chalk19.gray("Push local file to cloud")}
7188
+ ${chalk19.white("$ lynxp link --list")} ${chalk19.gray("Show tracked blueprints")}
7189
+
7190
+ ${chalk19.cyan("Blueprint Tracking:")}
7191
+ ${chalk19.white("$ lynxp link AGENTS.md bp_xyz")} ${chalk19.gray("Link existing file to blueprint")}
7192
+ ${chalk19.white("$ lynxp unlink AGENTS.md")} ${chalk19.gray("Disconnect from cloud")}
7193
+ ${chalk19.white("$ lynxp diff bp_abc123")} ${chalk19.gray("Show changes vs cloud version")}
7194
+
7195
+ ${chalk19.cyan("CI/CD:")}
7196
+ ${chalk19.white("$ lynxp check --ci")} ${chalk19.gray("Validate config (exit code)")}
7197
+
7198
+ ${chalk19.gray("Docs: https://lynxprompt.com/docs/cli")}
5915
7199
  `
5916
7200
  );
5917
7201
  program.parse();