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 +796 -284
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
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.
|
|
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
|
|
4205
|
+
var CLOUD_TARGETS = [
|
|
3861
4206
|
{ id: "vercel", label: "Vercel", icon: "\u25B2 " },
|
|
3862
4207
|
{ id: "netlify", label: "Netlify", icon: "\u{1F310}" },
|
|
3863
|
-
{ id: "
|
|
3864
|
-
{ id: "
|
|
3865
|
-
{ id: "
|
|
3866
|
-
{ id: "
|
|
3867
|
-
{ id: "
|
|
3868
|
-
{ id: "
|
|
3869
|
-
{ id: "
|
|
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: "
|
|
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:
|
|
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
|
-
|
|
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
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
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
|
-
|
|
5540
|
-
|
|
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
|
|
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
|
|
6210
|
+
console.log(chalk7.gray(" Select commands for your project. Detected commands are pre-selected."));
|
|
5818
6211
|
console.log();
|
|
5819
|
-
const
|
|
5820
|
-
const
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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-
|
|
6411
|
+
message: chalk7.white("Max file length (lines, 100-10000):"),
|
|
6030
6412
|
initial: 300,
|
|
6031
6413
|
min: 100,
|
|
6032
|
-
max:
|
|
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 || "
|
|
6429
|
+
answers.importOrder = importOrderResponse.importOrder || "";
|
|
6047
6430
|
const commentLangResponse = await prompts3({
|
|
6048
|
-
type: "
|
|
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{
|
|
6054
|
-
{ title: "\u{
|
|
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
|
-
|
|
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 || "
|
|
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:
|
|
6316
|
-
|
|
6317
|
-
|
|
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
|
-
|
|
6323
|
-
|
|
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:
|
|
6332
|
-
|
|
6333
|
-
|
|
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
|
-
|
|
6339
|
-
|
|
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:
|
|
6348
|
-
|
|
6349
|
-
|
|
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
|
-
|
|
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 (
|
|
6358
|
-
console.log(chalk7.green(` \u2713 Always: ${
|
|
6848
|
+
if (alwaysList.length > 0) {
|
|
6849
|
+
console.log(chalk7.green(` \u2713 Always: ${alwaysList.join(", ")}`));
|
|
6359
6850
|
}
|
|
6360
|
-
if (
|
|
6361
|
-
console.log(chalk7.yellow(` ? Ask: ${
|
|
6851
|
+
if (askList.length > 0) {
|
|
6852
|
+
console.log(chalk7.yellow(` ? Ask: ${askList.join(", ")}`));
|
|
6362
6853
|
}
|
|
6363
|
-
if (
|
|
6364
|
-
console.log(chalk7.red(` \u2717 Never: ${
|
|
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: "
|
|
6912
|
+
type: "select",
|
|
6420
6913
|
name: "snapshotTesting",
|
|
6421
6914
|
message: chalk7.white("Use snapshot testing?"),
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
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
|
|
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
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
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
|
-
|
|
6498
|
-
|
|
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.
|
|
6513
|
-
console.log(chalk7.gray("
|
|
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
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
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
|
-
}
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
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
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
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.
|
|
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);
|