lynxprompt 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1435 -243
- package/dist/index.js.map +1 -1
- package/package.json +16 -2
package/dist/index.js
CHANGED
|
@@ -200,12 +200,7 @@ async function loginCommand() {
|
|
|
200
200
|
pollSpinner.succeed("Authentication successful!");
|
|
201
201
|
setToken(result.token);
|
|
202
202
|
setUser(result.user);
|
|
203
|
-
|
|
204
|
-
console.log(chalk.green(`\u2705 Logged in as ${chalk.bold(result.user.email)}`));
|
|
205
|
-
console.log(chalk.gray(` Plan: ${result.user.plan}`));
|
|
206
|
-
console.log(chalk.gray(` Token stored securely in config`));
|
|
207
|
-
console.log();
|
|
208
|
-
console.log(chalk.cyan("You're ready to use LynxPrompt CLI!"));
|
|
203
|
+
displayWelcome(result.user);
|
|
209
204
|
return;
|
|
210
205
|
}
|
|
211
206
|
if (result.status === "expired") {
|
|
@@ -259,6 +254,53 @@ async function tryOpenBrowser(url) {
|
|
|
259
254
|
function sleep(ms) {
|
|
260
255
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
261
256
|
}
|
|
257
|
+
function displayWelcome(user) {
|
|
258
|
+
const plan = user.plan?.toUpperCase() || "FREE";
|
|
259
|
+
const name = user.name || user.email.split("@")[0];
|
|
260
|
+
const planConfig = {
|
|
261
|
+
FREE: { color: chalk.gray, emoji: "\u{1F193}", badge: "Free" },
|
|
262
|
+
PRO: { color: chalk.cyan, emoji: "\u26A1", badge: "Pro" },
|
|
263
|
+
MAX: { color: chalk.magenta, emoji: "\u{1F680}", badge: "Max" },
|
|
264
|
+
TEAMS: { color: chalk.yellow, emoji: "\u{1F465}", badge: "Teams" }
|
|
265
|
+
};
|
|
266
|
+
const config2 = planConfig[plan] || planConfig.FREE;
|
|
267
|
+
console.log();
|
|
268
|
+
console.log(chalk.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\u2500\u2500\u2500\u2500\u2510"));
|
|
269
|
+
console.log(chalk.bold("\u2502") + " " + chalk.bold("\u2502"));
|
|
270
|
+
console.log(chalk.bold("\u2502") + chalk.green.bold(` ${config2.emoji} Welcome to LynxPrompt CLI!`) + " " + chalk.bold("\u2502"));
|
|
271
|
+
console.log(chalk.bold("\u2502") + " " + chalk.bold("\u2502"));
|
|
272
|
+
console.log(chalk.bold("\u2502") + ` ${chalk.white("User:")} ${chalk.bold(name.padEnd(38))}` + chalk.bold("\u2502"));
|
|
273
|
+
console.log(chalk.bold("\u2502") + ` ${chalk.white("Plan:")} ${config2.color(config2.badge.padEnd(38))}` + chalk.bold("\u2502"));
|
|
274
|
+
console.log(chalk.bold("\u2502") + " " + chalk.bold("\u2502"));
|
|
275
|
+
console.log(chalk.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\u2500\u2500\u2500\u2500\u2518"));
|
|
276
|
+
console.log();
|
|
277
|
+
console.log(chalk.bold("\u{1F4CB} Your CLI Capabilities:"));
|
|
278
|
+
console.log();
|
|
279
|
+
console.log(chalk.green(" \u2713") + " " + chalk.white("lynxprompt init") + chalk.gray(" - Generate config files"));
|
|
280
|
+
console.log(chalk.green(" \u2713") + " " + chalk.white("lynxprompt wizard") + chalk.gray(" - Interactive wizard"));
|
|
281
|
+
console.log(chalk.green(" \u2713") + " " + chalk.white("lynxprompt list") + chalk.gray(" - List your blueprints"));
|
|
282
|
+
console.log(chalk.green(" \u2713") + " " + chalk.white("lynxprompt pull <id>") + chalk.gray(" - Download blueprints"));
|
|
283
|
+
console.log(chalk.green(" \u2713") + " " + chalk.white("lynxprompt push") + chalk.gray(" - Upload blueprints to marketplace"));
|
|
284
|
+
console.log(chalk.green(" \u2713") + " " + chalk.white("lynxprompt link") + chalk.gray(" - Link project to blueprint"));
|
|
285
|
+
console.log(chalk.green(" \u2713") + " " + chalk.white("lynxprompt sync") + chalk.gray(" - Sync linked blueprints"));
|
|
286
|
+
console.log(chalk.green(" \u2713") + " " + chalk.white("lynxprompt diff") + chalk.gray(" - Compare local vs remote"));
|
|
287
|
+
if (plan === "PRO" || plan === "MAX" || plan === "TEAMS") {
|
|
288
|
+
console.log();
|
|
289
|
+
console.log(chalk.cyan(" \u26A1") + " " + chalk.white("Advanced wizards") + chalk.gray(" - More customization options"));
|
|
290
|
+
console.log(chalk.cyan(" \u26A1") + " " + chalk.white("Sell blueprints") + chalk.gray(" - Monetize your configurations"));
|
|
291
|
+
}
|
|
292
|
+
if (plan === "MAX" || plan === "TEAMS") {
|
|
293
|
+
console.log(chalk.magenta(" \u{1F680}") + " " + chalk.white("All paid blueprints") + chalk.gray(" - Access premium content"));
|
|
294
|
+
console.log(chalk.magenta(" \u{1F680}") + " " + chalk.white("Priority support") + chalk.gray(" - Get help faster"));
|
|
295
|
+
}
|
|
296
|
+
if (plan === "TEAMS") {
|
|
297
|
+
console.log(chalk.yellow(" \u{1F465}") + " " + chalk.white("Team blueprints") + chalk.gray(" - Share with your team"));
|
|
298
|
+
console.log(chalk.yellow(" \u{1F465}") + " " + chalk.white("SSO integration") + chalk.gray(" - Enterprise authentication"));
|
|
299
|
+
}
|
|
300
|
+
console.log();
|
|
301
|
+
console.log(chalk.gray("Token stored securely. Run ") + chalk.cyan("lynxprompt --help") + chalk.gray(" to see all commands."));
|
|
302
|
+
console.log();
|
|
303
|
+
}
|
|
262
304
|
|
|
263
305
|
// src/commands/logout.ts
|
|
264
306
|
import chalk2 from "chalk";
|
|
@@ -1370,7 +1412,7 @@ function formatDetectionResults(result) {
|
|
|
1370
1412
|
}
|
|
1371
1413
|
|
|
1372
1414
|
// src/utils/detect.ts
|
|
1373
|
-
import { readFile as readFile3, access as
|
|
1415
|
+
import { readFile as readFile3, access as access2 } from "fs/promises";
|
|
1374
1416
|
import { join as join4 } from "path";
|
|
1375
1417
|
var JS_FRAMEWORK_PATTERNS = {
|
|
1376
1418
|
nextjs: ["next"],
|
|
@@ -1593,7 +1635,7 @@ async function detectProject(cwd) {
|
|
|
1593
1635
|
}
|
|
1594
1636
|
async function fileExists(path2) {
|
|
1595
1637
|
try {
|
|
1596
|
-
await
|
|
1638
|
+
await access2(path2);
|
|
1597
1639
|
return true;
|
|
1598
1640
|
} catch {
|
|
1599
1641
|
return false;
|
|
@@ -1964,7 +2006,7 @@ async function initCommand(options) {
|
|
|
1964
2006
|
import chalk8 from "chalk";
|
|
1965
2007
|
import prompts4 from "prompts";
|
|
1966
2008
|
import ora7 from "ora";
|
|
1967
|
-
import { writeFile as writeFile4, mkdir as mkdir4, access as
|
|
2009
|
+
import { writeFile as writeFile4, mkdir as mkdir4, access as access3 } from "fs/promises";
|
|
1968
2010
|
import { join as join6, dirname as dirname4 } from "path";
|
|
1969
2011
|
|
|
1970
2012
|
// src/utils/generator.ts
|
|
@@ -1974,7 +2016,17 @@ var PLATFORM_FILES = {
|
|
|
1974
2016
|
claude: "CLAUDE.md",
|
|
1975
2017
|
copilot: ".github/copilot-instructions.md",
|
|
1976
2018
|
windsurf: ".windsurfrules",
|
|
1977
|
-
zed: ".zed/instructions.md"
|
|
2019
|
+
zed: ".zed/instructions.md",
|
|
2020
|
+
aider: ".aider.conf.yml",
|
|
2021
|
+
cline: ".clinerules",
|
|
2022
|
+
continue: ".continue/rules.md",
|
|
2023
|
+
cody: ".cody/rules.md",
|
|
2024
|
+
amazonq: ".amazonq/rules/project.md",
|
|
2025
|
+
tabnine: ".tabnine.yaml",
|
|
2026
|
+
supermaven: ".supermaven/rules.md",
|
|
2027
|
+
codegpt: ".codegpt/rules.md",
|
|
2028
|
+
void: ".void/rules.md",
|
|
2029
|
+
goose: ".goosehints"
|
|
1978
2030
|
};
|
|
1979
2031
|
var PERSONA_DESCRIPTIONS = {
|
|
1980
2032
|
backend: "a senior backend developer specializing in APIs, databases, and microservices architecture",
|
|
@@ -1995,6 +2047,8 @@ var STACK_NAMES = {
|
|
|
1995
2047
|
ruby: "Ruby",
|
|
1996
2048
|
php: "PHP",
|
|
1997
2049
|
swift: "Swift",
|
|
2050
|
+
kotlin: "Kotlin",
|
|
2051
|
+
cpp: "C/C++",
|
|
1998
2052
|
react: "React",
|
|
1999
2053
|
nextjs: "Next.js",
|
|
2000
2054
|
vue: "Vue.js",
|
|
@@ -2007,10 +2061,44 @@ var STACK_NAMES = {
|
|
|
2007
2061
|
spring: "Spring Boot",
|
|
2008
2062
|
rails: "Ruby on Rails",
|
|
2009
2063
|
laravel: "Laravel",
|
|
2064
|
+
nestjs: "NestJS",
|
|
2065
|
+
vite: "Vite",
|
|
2066
|
+
"react-native": "React Native",
|
|
2067
|
+
postgresql: "PostgreSQL",
|
|
2068
|
+
mysql: "MySQL",
|
|
2069
|
+
mongodb: "MongoDB",
|
|
2070
|
+
redis: "Redis",
|
|
2071
|
+
sqlite: "SQLite",
|
|
2072
|
+
supabase: "Supabase",
|
|
2073
|
+
firebase: "Firebase",
|
|
2010
2074
|
prisma: "Prisma",
|
|
2011
2075
|
tailwind: "Tailwind CSS",
|
|
2012
2076
|
fastify: "Fastify"
|
|
2013
2077
|
};
|
|
2078
|
+
var NAMING_DESCRIPTIONS = {
|
|
2079
|
+
language_default: "follow idiomatic conventions for the primary language",
|
|
2080
|
+
camelCase: "use camelCase for variables and functions",
|
|
2081
|
+
snake_case: "use snake_case for variables and functions",
|
|
2082
|
+
PascalCase: "use PascalCase for classes and types",
|
|
2083
|
+
"kebab-case": "use kebab-case for file names and CSS classes"
|
|
2084
|
+
};
|
|
2085
|
+
var AI_BEHAVIOR_DESCRIPTIONS = {
|
|
2086
|
+
explain_changes: "Always explain what changes you're making and why before implementing them",
|
|
2087
|
+
preserve_style: "Preserve and follow the existing code style in the project",
|
|
2088
|
+
minimal_changes: "Make minimal, focused changes - avoid unnecessary refactoring",
|
|
2089
|
+
no_comments: "Avoid adding unnecessary comments; code should be self-documenting",
|
|
2090
|
+
prefer_simple: "Prefer simpler solutions over clever ones",
|
|
2091
|
+
test_first: "Write tests before implementing new functionality (TDD)",
|
|
2092
|
+
no_console: "Remove console.log/print statements before committing",
|
|
2093
|
+
type_strict: "Be strict with types - avoid any/Any/Object types"
|
|
2094
|
+
};
|
|
2095
|
+
var IMPORTANT_FILES_PATHS = {
|
|
2096
|
+
readme: "README.md",
|
|
2097
|
+
package: "package.json or pyproject.toml",
|
|
2098
|
+
tsconfig: "tsconfig.json or similar config",
|
|
2099
|
+
architecture: "ARCHITECTURE.md",
|
|
2100
|
+
contributing: "CONTRIBUTING.md"
|
|
2101
|
+
};
|
|
2014
2102
|
var BOUNDARIES = {
|
|
2015
2103
|
conservative: {
|
|
2016
2104
|
always: ["Read any file in the project", "Run lint and format commands"],
|
|
@@ -2069,6 +2157,12 @@ var BOUNDARIES = {
|
|
|
2069
2157
|
]
|
|
2070
2158
|
}
|
|
2071
2159
|
};
|
|
2160
|
+
var TEST_LEVEL_DESCRIPTIONS = {
|
|
2161
|
+
smoke: "Quick sanity checks for critical paths",
|
|
2162
|
+
unit: "Unit tests for individual functions and components",
|
|
2163
|
+
integration: "Integration tests for component interactions",
|
|
2164
|
+
e2e: "End-to-end tests for full user flows"
|
|
2165
|
+
};
|
|
2072
2166
|
function generateConfig(options) {
|
|
2073
2167
|
const files = {};
|
|
2074
2168
|
for (const platform of options.platforms) {
|
|
@@ -2082,8 +2176,12 @@ function generateConfig(options) {
|
|
|
2082
2176
|
function generateFileContent(options, platform) {
|
|
2083
2177
|
const sections = [];
|
|
2084
2178
|
const isMdc = platform === "cursor";
|
|
2085
|
-
const
|
|
2086
|
-
const
|
|
2179
|
+
const isYaml = platform === "aider" || platform === "tabnine";
|
|
2180
|
+
const isPlainText = platform === "windsurf" || platform === "cline" || platform === "goose";
|
|
2181
|
+
const isMarkdown = !isMdc && !isYaml && !isPlainText;
|
|
2182
|
+
if (isYaml) {
|
|
2183
|
+
return generateYamlConfig(options, platform);
|
|
2184
|
+
}
|
|
2087
2185
|
if (isMdc) {
|
|
2088
2186
|
sections.push("---");
|
|
2089
2187
|
sections.push(`description: "${options.name} - AI coding rules"`);
|
|
@@ -2098,6 +2196,23 @@ function generateFileContent(options, platform) {
|
|
|
2098
2196
|
sections.push(`# ${options.name} - AI Assistant Configuration`);
|
|
2099
2197
|
sections.push("");
|
|
2100
2198
|
}
|
|
2199
|
+
if (options.projectType) {
|
|
2200
|
+
const typeContexts = {
|
|
2201
|
+
work: "This is a professional/enterprise project. Follow strict procedures and maintain high code quality.",
|
|
2202
|
+
leisure: "This is a personal/hobby project. Feel free to be more experimental and creative.",
|
|
2203
|
+
opensource: "This is an open-source project. Consider community guidelines and contribution standards.",
|
|
2204
|
+
learning: "This is an educational project. Explain concepts and be patient with learning-focused approaches."
|
|
2205
|
+
};
|
|
2206
|
+
if (typeContexts[options.projectType]) {
|
|
2207
|
+
if (isMarkdown || isMdc) {
|
|
2208
|
+
sections.push(`> **Project Context:** ${typeContexts[options.projectType]}`);
|
|
2209
|
+
sections.push("");
|
|
2210
|
+
} else {
|
|
2211
|
+
sections.push(`Project Context: ${typeContexts[options.projectType]}`);
|
|
2212
|
+
sections.push("");
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2101
2216
|
const personaDesc = PERSONA_DESCRIPTIONS[options.persona] || options.persona;
|
|
2102
2217
|
if (isMarkdown || isMdc) {
|
|
2103
2218
|
sections.push("## Persona");
|
|
@@ -2128,7 +2243,25 @@ function generateFileContent(options, platform) {
|
|
|
2128
2243
|
}
|
|
2129
2244
|
sections.push("");
|
|
2130
2245
|
}
|
|
2131
|
-
|
|
2246
|
+
if (options.repoHost || options.license || options.conventionalCommits) {
|
|
2247
|
+
if (isMarkdown || isMdc) {
|
|
2248
|
+
sections.push("## Repository");
|
|
2249
|
+
sections.push("");
|
|
2250
|
+
if (options.repoHost) {
|
|
2251
|
+
sections.push(`- **Host:** ${options.repoHost.charAt(0).toUpperCase() + options.repoHost.slice(1)}`);
|
|
2252
|
+
}
|
|
2253
|
+
if (options.license && options.license !== "none") {
|
|
2254
|
+
sections.push(`- **License:** ${options.license.toUpperCase()}`);
|
|
2255
|
+
}
|
|
2256
|
+
if (options.conventionalCommits) {
|
|
2257
|
+
sections.push("- **Commits:** Follow [Conventional Commits](https://conventionalcommits.org) format");
|
|
2258
|
+
}
|
|
2259
|
+
sections.push("");
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
const hasCommands = options.commands && Object.values(options.commands).some(
|
|
2263
|
+
(v) => Array.isArray(v) ? v.length > 0 : Boolean(v)
|
|
2264
|
+
);
|
|
2132
2265
|
if (hasCommands) {
|
|
2133
2266
|
if (isMarkdown || isMdc) {
|
|
2134
2267
|
sections.push("## Commands");
|
|
@@ -2139,24 +2272,68 @@ function generateFileContent(options, platform) {
|
|
|
2139
2272
|
} else {
|
|
2140
2273
|
sections.push("Commands:");
|
|
2141
2274
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2275
|
+
const cmdCategories = ["build", "test", "lint", "dev", "custom"];
|
|
2276
|
+
for (const cat of cmdCategories) {
|
|
2277
|
+
const cmd = options.commands[cat];
|
|
2278
|
+
if (cmd) {
|
|
2279
|
+
const cmds = Array.isArray(cmd) ? cmd : [cmd];
|
|
2280
|
+
for (const c of cmds) {
|
|
2281
|
+
if (c) {
|
|
2282
|
+
const label = cat.charAt(0).toUpperCase() + cat.slice(1);
|
|
2283
|
+
sections.push(isMarkdown || isMdc ? `# ${label}: ${c}` : `- ${label}: ${c}`);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2144
2287
|
}
|
|
2145
|
-
if (
|
|
2146
|
-
sections.push(
|
|
2288
|
+
if (isMarkdown || isMdc) {
|
|
2289
|
+
sections.push("```");
|
|
2147
2290
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2291
|
+
sections.push("");
|
|
2292
|
+
}
|
|
2293
|
+
if (options.aiBehavior && options.aiBehavior.length > 0) {
|
|
2294
|
+
if (isMarkdown || isMdc) {
|
|
2295
|
+
sections.push("## AI Behavior Rules");
|
|
2296
|
+
sections.push("");
|
|
2297
|
+
for (const rule of options.aiBehavior) {
|
|
2298
|
+
const desc = AI_BEHAVIOR_DESCRIPTIONS[rule];
|
|
2299
|
+
if (desc) {
|
|
2300
|
+
sections.push(`- ${desc}`);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
sections.push("");
|
|
2150
2304
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2305
|
+
}
|
|
2306
|
+
if (options.importantFiles && options.importantFiles.length > 0) {
|
|
2307
|
+
if (isMarkdown || isMdc) {
|
|
2308
|
+
sections.push("## Important Files to Read");
|
|
2309
|
+
sections.push("");
|
|
2310
|
+
sections.push("Always read these files first to understand the project context:");
|
|
2311
|
+
sections.push("");
|
|
2312
|
+
for (const file of options.importantFiles) {
|
|
2313
|
+
const path2 = IMPORTANT_FILES_PATHS[file];
|
|
2314
|
+
if (path2) {
|
|
2315
|
+
sections.push(`- \`${path2}\``);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
sections.push("");
|
|
2153
2319
|
}
|
|
2320
|
+
}
|
|
2321
|
+
if (options.selfImprove) {
|
|
2154
2322
|
if (isMarkdown || isMdc) {
|
|
2155
|
-
sections.push("
|
|
2323
|
+
sections.push("## Self-Improving Blueprint");
|
|
2324
|
+
sections.push("");
|
|
2325
|
+
sections.push("> **Auto-update enabled:** As you work on this project, track patterns and update this configuration file to better reflect the project's conventions and preferences.");
|
|
2326
|
+
sections.push("");
|
|
2156
2327
|
}
|
|
2157
|
-
sections.push("");
|
|
2158
2328
|
}
|
|
2159
|
-
|
|
2329
|
+
let boundaries = BOUNDARIES[options.boundaries];
|
|
2330
|
+
if (options.boundaryNever?.length || options.boundaryAsk?.length) {
|
|
2331
|
+
boundaries = {
|
|
2332
|
+
...boundaries,
|
|
2333
|
+
never: options.boundaryNever?.length ? options.boundaryNever : boundaries.never,
|
|
2334
|
+
askFirst: options.boundaryAsk?.length ? options.boundaryAsk : boundaries.askFirst
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2160
2337
|
if (boundaries) {
|
|
2161
2338
|
if (isMarkdown || isMdc) {
|
|
2162
2339
|
sections.push("## Boundaries");
|
|
@@ -2201,6 +2378,27 @@ function generateFileContent(options, platform) {
|
|
|
2201
2378
|
if (isMarkdown || isMdc) {
|
|
2202
2379
|
sections.push("## Code Style");
|
|
2203
2380
|
sections.push("");
|
|
2381
|
+
if (options.namingConvention) {
|
|
2382
|
+
const namingDesc = NAMING_DESCRIPTIONS[options.namingConvention];
|
|
2383
|
+
if (namingDesc) {
|
|
2384
|
+
sections.push(`- **Naming:** ${namingDesc}`);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
if (options.errorHandling) {
|
|
2388
|
+
const errorStyles = {
|
|
2389
|
+
try_catch: "Use try/catch blocks for error handling",
|
|
2390
|
+
result_types: "Use Result/Either types for error handling",
|
|
2391
|
+
error_codes: "Use error codes with proper documentation",
|
|
2392
|
+
exceptions: "Use custom exception classes"
|
|
2393
|
+
};
|
|
2394
|
+
if (errorStyles[options.errorHandling]) {
|
|
2395
|
+
sections.push(`- **Errors:** ${errorStyles[options.errorHandling]}`);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
if (options.styleNotes) {
|
|
2399
|
+
sections.push(`- **Notes:** ${options.styleNotes}`);
|
|
2400
|
+
}
|
|
2401
|
+
sections.push("");
|
|
2204
2402
|
sections.push("Follow these conventions:");
|
|
2205
2403
|
sections.push("");
|
|
2206
2404
|
if (options.stack.includes("typescript") || options.stack.includes("javascript")) {
|
|
@@ -2234,6 +2432,49 @@ function generateFileContent(options, platform) {
|
|
|
2234
2432
|
sections.push("- Keep functions focused and testable");
|
|
2235
2433
|
sections.push("");
|
|
2236
2434
|
}
|
|
2435
|
+
if (options.testLevels?.length || options.testFrameworks?.length || options.coverageTarget) {
|
|
2436
|
+
if (isMarkdown || isMdc) {
|
|
2437
|
+
sections.push("## Testing Strategy");
|
|
2438
|
+
sections.push("");
|
|
2439
|
+
if (options.testLevels?.length) {
|
|
2440
|
+
sections.push("### Test Levels");
|
|
2441
|
+
sections.push("");
|
|
2442
|
+
for (const level of options.testLevels) {
|
|
2443
|
+
const desc = TEST_LEVEL_DESCRIPTIONS[level];
|
|
2444
|
+
if (desc) {
|
|
2445
|
+
sections.push(`- **${level.charAt(0).toUpperCase() + level.slice(1)}:** ${desc}`);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
sections.push("");
|
|
2449
|
+
}
|
|
2450
|
+
if (options.testFrameworks?.length) {
|
|
2451
|
+
sections.push("### Frameworks");
|
|
2452
|
+
sections.push("");
|
|
2453
|
+
sections.push(`Use: ${options.testFrameworks.join(", ")}`);
|
|
2454
|
+
sections.push("");
|
|
2455
|
+
}
|
|
2456
|
+
if (options.coverageTarget) {
|
|
2457
|
+
sections.push(`### Coverage Target: ${options.coverageTarget}%`);
|
|
2458
|
+
sections.push("");
|
|
2459
|
+
}
|
|
2460
|
+
if (options.testNotes) {
|
|
2461
|
+
sections.push(`**Notes:** ${options.testNotes}`);
|
|
2462
|
+
sections.push("");
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
if (options.extraNotes) {
|
|
2467
|
+
if (isMarkdown || isMdc) {
|
|
2468
|
+
sections.push("## Additional Notes");
|
|
2469
|
+
sections.push("");
|
|
2470
|
+
sections.push(options.extraNotes);
|
|
2471
|
+
sections.push("");
|
|
2472
|
+
} else {
|
|
2473
|
+
sections.push("Additional Notes:");
|
|
2474
|
+
sections.push(options.extraNotes);
|
|
2475
|
+
sections.push("");
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2237
2478
|
if (isMarkdown || isMdc) {
|
|
2238
2479
|
sections.push("---");
|
|
2239
2480
|
sections.push("");
|
|
@@ -2241,105 +2482,446 @@ function generateFileContent(options, platform) {
|
|
|
2241
2482
|
}
|
|
2242
2483
|
return sections.join("\n");
|
|
2243
2484
|
}
|
|
2485
|
+
function generateYamlConfig(options, platform) {
|
|
2486
|
+
const lines = [];
|
|
2487
|
+
if (platform === "aider") {
|
|
2488
|
+
lines.push("# Aider configuration");
|
|
2489
|
+
lines.push(`# Project: ${options.name}`);
|
|
2490
|
+
lines.push("");
|
|
2491
|
+
lines.push("# Model settings");
|
|
2492
|
+
lines.push("model: gpt-4");
|
|
2493
|
+
lines.push("");
|
|
2494
|
+
lines.push("# Code style");
|
|
2495
|
+
if (options.stack.includes("typescript") || options.stack.includes("javascript")) {
|
|
2496
|
+
lines.push("auto-lint: true");
|
|
2497
|
+
}
|
|
2498
|
+
lines.push("");
|
|
2499
|
+
lines.push("# Custom instructions");
|
|
2500
|
+
lines.push("read:");
|
|
2501
|
+
lines.push(" - README.md");
|
|
2502
|
+
if (options.importantFiles?.includes("architecture")) {
|
|
2503
|
+
lines.push(" - ARCHITECTURE.md");
|
|
2504
|
+
}
|
|
2505
|
+
} else if (platform === "tabnine") {
|
|
2506
|
+
lines.push("# Tabnine configuration");
|
|
2507
|
+
lines.push(`# Project: ${options.name}`);
|
|
2508
|
+
lines.push("");
|
|
2509
|
+
lines.push("version: 1.0.0");
|
|
2510
|
+
lines.push("");
|
|
2511
|
+
lines.push("project:");
|
|
2512
|
+
lines.push(` name: ${options.name}`);
|
|
2513
|
+
if (options.description) {
|
|
2514
|
+
lines.push(` description: "${options.description}"`);
|
|
2515
|
+
}
|
|
2516
|
+
lines.push("");
|
|
2517
|
+
lines.push("context:");
|
|
2518
|
+
lines.push(" include:");
|
|
2519
|
+
lines.push(' - "**/*.ts"');
|
|
2520
|
+
lines.push(' - "**/*.js"');
|
|
2521
|
+
lines.push(' - "**/*.py"');
|
|
2522
|
+
}
|
|
2523
|
+
lines.push("");
|
|
2524
|
+
lines.push(`# Generated by LynxPrompt CLI`);
|
|
2525
|
+
return lines.join("\n");
|
|
2526
|
+
}
|
|
2244
2527
|
|
|
2245
2528
|
// src/commands/wizard.ts
|
|
2529
|
+
var WIZARD_STEPS = [
|
|
2530
|
+
{ id: "format", title: "Output Format", icon: "\u{1F4E4}", tier: "basic" },
|
|
2531
|
+
{ id: "project", title: "Project Basics", icon: "\u2728", tier: "basic" },
|
|
2532
|
+
{ id: "tech", title: "Tech Stack", icon: "\u{1F4BB}", tier: "basic" },
|
|
2533
|
+
{ id: "repo", title: "Repository Setup", icon: "\u{1F500}", tier: "basic" },
|
|
2534
|
+
{ id: "commands", title: "Commands", icon: "\u{1F4CB}", tier: "intermediate" },
|
|
2535
|
+
{ id: "code_style", title: "Code Style", icon: "\u{1FA84}", tier: "intermediate" },
|
|
2536
|
+
{ id: "ai", title: "AI Behavior", icon: "\u{1F9E0}", tier: "basic" },
|
|
2537
|
+
{ id: "boundaries", title: "Boundaries", icon: "\u{1F6E1}\uFE0F", tier: "advanced" },
|
|
2538
|
+
{ id: "testing", title: "Testing Strategy", icon: "\u{1F9EA}", tier: "advanced" },
|
|
2539
|
+
{ id: "static", title: "Static Files", icon: "\u{1F4C4}", tier: "advanced" },
|
|
2540
|
+
{ id: "extra", title: "Final Details", icon: "\u{1F4AC}", tier: "basic" }
|
|
2541
|
+
];
|
|
2542
|
+
var ALL_PLATFORMS = [
|
|
2543
|
+
{ id: "agents", name: "Universal (AGENTS.md)", file: "AGENTS.md", icon: "\u{1F310}", note: "Works with all AI-enabled IDEs" },
|
|
2544
|
+
{ id: "cursor", name: "Cursor", file: ".cursor/rules/", icon: "\u26A1", note: "Native project rules format" },
|
|
2545
|
+
{ id: "claude", name: "Claude Code", file: "CLAUDE.md", icon: "\u{1F9E0}", note: "Also works with Cursor" },
|
|
2546
|
+
{ id: "copilot", name: "GitHub Copilot", file: ".github/copilot-instructions.md", icon: "\u{1F419}", note: "VS Code & JetBrains" },
|
|
2547
|
+
{ id: "windsurf", name: "Windsurf", file: ".windsurfrules", icon: "\u{1F3C4}", note: "Codeium IDE" },
|
|
2548
|
+
{ id: "zed", name: "Zed", file: ".zed/instructions.md", icon: "\u26A1", note: "Zed editor" },
|
|
2549
|
+
{ id: "aider", name: "Aider", file: ".aider.conf.yml", icon: "\u{1F916}", note: "CLI AI pair programming" },
|
|
2550
|
+
{ id: "cline", name: "Cline", file: ".clinerules", icon: "\u{1F527}", note: "VS Code extension" },
|
|
2551
|
+
{ id: "continue", name: "Continue", file: ".continue/config.json", icon: "\u27A1\uFE0F", note: "Open-source autopilot" },
|
|
2552
|
+
{ id: "cody", name: "Sourcegraph Cody", file: ".cody/config.json", icon: "\u{1F50D}", note: "Context-aware AI" },
|
|
2553
|
+
{ id: "amazonq", name: "Amazon Q", file: ".amazonq/rules/", icon: "\u{1F4E6}", note: "AWS AI assistant" },
|
|
2554
|
+
{ id: "tabnine", name: "Tabnine", file: ".tabnine.yaml", icon: "\u{1F4DD}", note: "AI code completion" },
|
|
2555
|
+
{ id: "supermaven", name: "Supermaven", file: ".supermaven/config.json", icon: "\u{1F9B8}", note: "Fast AI completions" },
|
|
2556
|
+
{ id: "codegpt", name: "CodeGPT", file: ".codegpt/config.json", icon: "\u{1F4AC}", note: "VS Code AI assistant" },
|
|
2557
|
+
{ id: "void", name: "Void", file: ".void/config.json", icon: "\u{1F573}\uFE0F", note: "Open-source Cursor alt" },
|
|
2558
|
+
{ id: "goose", name: "Goose", file: ".goosehints", icon: "\u{1FABF}", note: "Block AI agent" }
|
|
2559
|
+
];
|
|
2246
2560
|
var OUTPUT_FORMATS = [
|
|
2247
|
-
{
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
},
|
|
2253
|
-
{
|
|
2254
|
-
title: "Cursor (.cursor/rules/)",
|
|
2255
|
-
value: "cursor",
|
|
2256
|
-
description: "Cursor IDE with MDC format"
|
|
2257
|
-
},
|
|
2258
|
-
{
|
|
2259
|
-
title: "Multiple formats",
|
|
2260
|
-
value: "multiple",
|
|
2261
|
-
description: "Select multiple AI editors to generate for"
|
|
2262
|
-
}
|
|
2561
|
+
{ title: "\u{1F310} AGENTS.md (Universal)", value: "agents", description: "Works with Claude, Copilot, Aider, Devin & more", recommended: true },
|
|
2562
|
+
{ title: "\u26A1 Cursor", value: "cursor", description: ".cursor/rules/ native format" },
|
|
2563
|
+
{ title: "\u{1F9E0} Claude Code", value: "claude", description: "CLAUDE.md format" },
|
|
2564
|
+
{ title: "\u{1F419} GitHub Copilot", value: "copilot", description: ".github/copilot-instructions.md" },
|
|
2565
|
+
{ title: "\u{1F3C4} Windsurf", value: "windsurf", description: ".windsurfrules configuration" },
|
|
2566
|
+
{ title: "\u{1F4E6} Multiple platforms...", value: "multiple", description: "Select from 16+ supported AI editors" }
|
|
2263
2567
|
];
|
|
2264
|
-
var
|
|
2265
|
-
{ title: "TypeScript", value: "typescript" },
|
|
2266
|
-
{ title: "JavaScript", value: "javascript" },
|
|
2267
|
-
{ title: "Python", value: "python" },
|
|
2268
|
-
{ title: "Go", value: "go" },
|
|
2269
|
-
{ title: "Rust", value: "rust" },
|
|
2270
|
-
{ title: "Java", value: "java" },
|
|
2271
|
-
{ title: "C#/.NET", value: "csharp" },
|
|
2272
|
-
{ title: "Ruby", value: "ruby" },
|
|
2273
|
-
{ title: "PHP", value: "php" },
|
|
2274
|
-
{ title: "Swift", value: "swift" }
|
|
2568
|
+
var LANGUAGES = [
|
|
2569
|
+
{ title: "\u{1F537} TypeScript", value: "typescript" },
|
|
2570
|
+
{ title: "\u{1F7E1} JavaScript", value: "javascript" },
|
|
2571
|
+
{ title: "\u{1F40D} Python", value: "python" },
|
|
2572
|
+
{ title: "\u{1F535} Go", value: "go" },
|
|
2573
|
+
{ title: "\u{1F980} Rust", value: "rust" },
|
|
2574
|
+
{ title: "\u2615 Java", value: "java" },
|
|
2575
|
+
{ title: "\u{1F49C} C#/.NET", value: "csharp" },
|
|
2576
|
+
{ title: "\u{1F48E} Ruby", value: "ruby" },
|
|
2577
|
+
{ title: "\u{1F418} PHP", value: "php" },
|
|
2578
|
+
{ title: "\u{1F34E} Swift", value: "swift" },
|
|
2579
|
+
{ title: "\u{1F536} Kotlin", value: "kotlin" },
|
|
2580
|
+
{ title: "\u2B1B C/C++", value: "cpp" }
|
|
2275
2581
|
];
|
|
2276
2582
|
var FRAMEWORKS = [
|
|
2277
|
-
{ title: "React", value: "react" },
|
|
2278
|
-
{ title: "Next.js", value: "nextjs" },
|
|
2279
|
-
{ title: "Vue.js", value: "vue" },
|
|
2280
|
-
{ title: "Angular", value: "angular" },
|
|
2281
|
-
{ title: "Svelte", value: "svelte" },
|
|
2282
|
-
{ title: "Express", value: "express" },
|
|
2283
|
-
{ title: "FastAPI", value: "fastapi" },
|
|
2284
|
-
{ title: "Django", value: "django" },
|
|
2285
|
-
{ title: "Flask", value: "flask" },
|
|
2286
|
-
{ title: "Spring
|
|
2287
|
-
{ title: "Rails", value: "rails" },
|
|
2288
|
-
{ title: "Laravel", value: "laravel" }
|
|
2583
|
+
{ title: "\u269B\uFE0F React", value: "react" },
|
|
2584
|
+
{ title: "\u25B2 Next.js", value: "nextjs" },
|
|
2585
|
+
{ title: "\u{1F49A} Vue.js", value: "vue" },
|
|
2586
|
+
{ title: "\u{1F170}\uFE0F Angular", value: "angular" },
|
|
2587
|
+
{ title: "\u{1F525} Svelte", value: "svelte" },
|
|
2588
|
+
{ title: "\u{1F682} Express", value: "express" },
|
|
2589
|
+
{ title: "\u26A1 FastAPI", value: "fastapi" },
|
|
2590
|
+
{ title: "\u{1F3B8} Django", value: "django" },
|
|
2591
|
+
{ title: "\u{1F9EA} Flask", value: "flask" },
|
|
2592
|
+
{ title: "\u{1F343} Spring", value: "spring" },
|
|
2593
|
+
{ title: "\u{1F48E} Rails", value: "rails" },
|
|
2594
|
+
{ title: "\u{1F534} Laravel", value: "laravel" },
|
|
2595
|
+
{ title: "\u{1F3D7}\uFE0F NestJS", value: "nestjs" },
|
|
2596
|
+
{ title: "\u26A1 Vite", value: "vite" },
|
|
2597
|
+
{ title: "\u{1F4F1} React Native", value: "react-native" }
|
|
2598
|
+
];
|
|
2599
|
+
var DATABASES = [
|
|
2600
|
+
{ title: "\u{1F418} PostgreSQL", value: "postgresql" },
|
|
2601
|
+
{ title: "\u{1F42C} MySQL", value: "mysql" },
|
|
2602
|
+
{ title: "\u{1F343} MongoDB", value: "mongodb" },
|
|
2603
|
+
{ title: "\u{1F534} Redis", value: "redis" },
|
|
2604
|
+
{ title: "\u{1F4CA} SQLite", value: "sqlite" },
|
|
2605
|
+
{ title: "\u2601\uFE0F Supabase", value: "supabase" },
|
|
2606
|
+
{ title: "\u{1F525} Firebase", value: "firebase" },
|
|
2607
|
+
{ title: "\u{1F4C2} Prisma", value: "prisma" }
|
|
2289
2608
|
];
|
|
2290
|
-
var
|
|
2291
|
-
{
|
|
2292
|
-
{
|
|
2293
|
-
{
|
|
2294
|
-
{
|
|
2295
|
-
{
|
|
2296
|
-
{
|
|
2609
|
+
var REPO_HOSTS = [
|
|
2610
|
+
{ id: "github", label: "GitHub", icon: "\u{1F419}" },
|
|
2611
|
+
{ id: "gitlab", label: "GitLab", icon: "\u{1F98A}" },
|
|
2612
|
+
{ id: "bitbucket", label: "Bitbucket", icon: "\u{1FAA3}" },
|
|
2613
|
+
{ id: "gitea", label: "Gitea", icon: "\u{1F375}" },
|
|
2614
|
+
{ id: "azure", label: "Azure DevOps", icon: "\u2601\uFE0F" },
|
|
2615
|
+
{ id: "other", label: "Other", icon: "\u{1F4E6}" }
|
|
2616
|
+
];
|
|
2617
|
+
var LICENSES = [
|
|
2618
|
+
{ id: "mit", label: "MIT" },
|
|
2619
|
+
{ id: "apache-2.0", label: "Apache 2.0" },
|
|
2620
|
+
{ id: "gpl-3.0", label: "GPL 3.0" },
|
|
2621
|
+
{ id: "lgpl-3.0", label: "LGPL 3.0" },
|
|
2622
|
+
{ id: "agpl-3.0", label: "AGPL 3.0" },
|
|
2623
|
+
{ id: "bsd-3", label: "BSD 3-Clause" },
|
|
2624
|
+
{ id: "mpl-2.0", label: "MPL 2.0" },
|
|
2625
|
+
{ id: "unlicense", label: "Unlicense" },
|
|
2626
|
+
{ id: "none", label: "None / Proprietary" }
|
|
2627
|
+
];
|
|
2628
|
+
var COMMON_COMMANDS = {
|
|
2629
|
+
build: [
|
|
2630
|
+
"npm run build",
|
|
2631
|
+
"pnpm build",
|
|
2632
|
+
"yarn build",
|
|
2633
|
+
"bun run build",
|
|
2634
|
+
"next build",
|
|
2635
|
+
"vite build",
|
|
2636
|
+
"tsc",
|
|
2637
|
+
"tsc --noEmit",
|
|
2638
|
+
"go build",
|
|
2639
|
+
"cargo build",
|
|
2640
|
+
"cargo build --release",
|
|
2641
|
+
"mvn package",
|
|
2642
|
+
"gradle build",
|
|
2643
|
+
"dotnet build",
|
|
2644
|
+
"docker build -t app .",
|
|
2645
|
+
"docker compose build"
|
|
2646
|
+
],
|
|
2647
|
+
test: [
|
|
2648
|
+
"npm test",
|
|
2649
|
+
"pnpm test",
|
|
2650
|
+
"yarn test",
|
|
2651
|
+
"bun test",
|
|
2652
|
+
"vitest",
|
|
2653
|
+
"vitest run",
|
|
2654
|
+
"jest",
|
|
2655
|
+
"jest --coverage",
|
|
2656
|
+
"pytest",
|
|
2657
|
+
"pytest --cov",
|
|
2658
|
+
"go test ./...",
|
|
2659
|
+
"cargo test",
|
|
2660
|
+
"mvn test",
|
|
2661
|
+
"gradle test",
|
|
2662
|
+
"playwright test",
|
|
2663
|
+
"cypress run"
|
|
2664
|
+
],
|
|
2665
|
+
lint: [
|
|
2666
|
+
"npm run lint",
|
|
2667
|
+
"pnpm lint",
|
|
2668
|
+
"eslint .",
|
|
2669
|
+
"eslint . --fix",
|
|
2670
|
+
"prettier --check .",
|
|
2671
|
+
"prettier --write .",
|
|
2672
|
+
"ruff check",
|
|
2673
|
+
"ruff format",
|
|
2674
|
+
"black .",
|
|
2675
|
+
"flake8",
|
|
2676
|
+
"golangci-lint run",
|
|
2677
|
+
"cargo clippy",
|
|
2678
|
+
"rubocop"
|
|
2679
|
+
],
|
|
2680
|
+
dev: [
|
|
2681
|
+
"npm run dev",
|
|
2682
|
+
"pnpm dev",
|
|
2683
|
+
"yarn dev",
|
|
2684
|
+
"bun run dev",
|
|
2685
|
+
"next dev",
|
|
2686
|
+
"vite",
|
|
2687
|
+
"vite dev",
|
|
2688
|
+
"uvicorn main:app --reload",
|
|
2689
|
+
"flask run",
|
|
2690
|
+
"rails server",
|
|
2691
|
+
"go run .",
|
|
2692
|
+
"cargo run",
|
|
2693
|
+
"dotnet run"
|
|
2694
|
+
]
|
|
2695
|
+
};
|
|
2696
|
+
var NAMING_CONVENTIONS = [
|
|
2697
|
+
{ id: "language_default", label: "Follow language conventions", desc: "Use idiomatic style" },
|
|
2698
|
+
{ id: "camelCase", label: "camelCase", desc: "JavaScript, TypeScript, Java" },
|
|
2699
|
+
{ id: "snake_case", label: "snake_case", desc: "Python, Ruby, Rust, Go" },
|
|
2700
|
+
{ id: "PascalCase", label: "PascalCase", desc: "C#, .NET classes" },
|
|
2701
|
+
{ id: "kebab-case", label: "kebab-case", desc: "CSS, HTML, URLs" }
|
|
2297
2702
|
];
|
|
2298
|
-
var
|
|
2299
|
-
{
|
|
2300
|
-
{
|
|
2301
|
-
{
|
|
2302
|
-
{
|
|
2303
|
-
{
|
|
2304
|
-
|
|
2305
|
-
|
|
2703
|
+
var ERROR_PATTERNS = [
|
|
2704
|
+
{ id: "try_catch", label: "try/catch blocks" },
|
|
2705
|
+
{ id: "result_types", label: "Result/Either types" },
|
|
2706
|
+
{ id: "error_codes", label: "Error codes" },
|
|
2707
|
+
{ id: "exceptions", label: "Custom exceptions" },
|
|
2708
|
+
{ id: "other", label: "Other" }
|
|
2709
|
+
];
|
|
2710
|
+
var AI_BEHAVIOR_RULES = [
|
|
2711
|
+
{ id: "explain_changes", label: "Explain changes before making them", recommended: true },
|
|
2712
|
+
{ id: "preserve_style", label: "Preserve existing code style", recommended: true },
|
|
2713
|
+
{ id: "minimal_changes", label: "Make minimal, focused changes", recommended: true },
|
|
2714
|
+
{ id: "no_comments", label: "Avoid adding unnecessary comments", recommended: false },
|
|
2715
|
+
{ id: "prefer_simple", label: "Prefer simpler solutions", recommended: true },
|
|
2716
|
+
{ id: "test_first", label: "Write tests before implementation", recommended: false },
|
|
2717
|
+
{ id: "no_console", label: "Remove console.log/print before committing", recommended: false },
|
|
2718
|
+
{ id: "type_strict", label: "Be strict with types (no any/Any)", recommended: false }
|
|
2719
|
+
];
|
|
2720
|
+
var IMPORTANT_FILES = [
|
|
2721
|
+
{ id: "readme", label: "README.md", icon: "\u{1F4D6}" },
|
|
2722
|
+
{ id: "package", label: "package.json / pyproject.toml", icon: "\u{1F4E6}" },
|
|
2723
|
+
{ id: "tsconfig", label: "tsconfig.json / config files", icon: "\u2699\uFE0F" },
|
|
2724
|
+
{ id: "architecture", label: "ARCHITECTURE.md", icon: "\u{1F3D7}\uFE0F" },
|
|
2725
|
+
{ id: "contributing", label: "CONTRIBUTING.md", icon: "\u{1F91D}" }
|
|
2306
2726
|
];
|
|
2307
2727
|
var BOUNDARY_PRESETS = [
|
|
2308
2728
|
{
|
|
2309
|
-
title: "Standard
|
|
2729
|
+
title: "\u{1F7E2} Standard",
|
|
2310
2730
|
value: "standard",
|
|
2731
|
+
description: "Balanced freedom & safety",
|
|
2311
2732
|
always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
|
|
2312
2733
|
askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
|
|
2313
2734
|
never: ["Delete production data", "Modify .env secrets", "Force push"]
|
|
2314
2735
|
},
|
|
2315
2736
|
{
|
|
2316
|
-
title: "Conservative
|
|
2737
|
+
title: "\u{1F7E1} Conservative",
|
|
2317
2738
|
value: "conservative",
|
|
2739
|
+
description: "Ask before most changes",
|
|
2318
2740
|
always: ["Read any file", "Run lint/format commands"],
|
|
2319
2741
|
askFirst: ["Modify any file", "Add dependencies", "Create files", "Run tests"],
|
|
2320
2742
|
never: ["Delete files", "Modify .env", "Push to git"]
|
|
2321
2743
|
},
|
|
2322
2744
|
{
|
|
2323
|
-
title: "Permissive
|
|
2745
|
+
title: "\u{1F7E0} Permissive",
|
|
2324
2746
|
value: "permissive",
|
|
2747
|
+
description: "AI can modify freely",
|
|
2325
2748
|
always: ["Modify any file in src/", "Run any script", "Add dependencies", "Create files"],
|
|
2326
2749
|
askFirst: ["Modify root configs", "Delete directories"],
|
|
2327
2750
|
never: ["Modify .env", "Access external APIs without confirmation"]
|
|
2328
2751
|
}
|
|
2329
2752
|
];
|
|
2753
|
+
var BOUNDARY_OPTIONS = [
|
|
2754
|
+
"Delete files",
|
|
2755
|
+
"Create new files",
|
|
2756
|
+
"Rename/move files",
|
|
2757
|
+
"Rewrite large sections",
|
|
2758
|
+
"Refactor architecture",
|
|
2759
|
+
"Change dependencies",
|
|
2760
|
+
"Modify database schema",
|
|
2761
|
+
"Update API contracts",
|
|
2762
|
+
"Touch CI pipelines",
|
|
2763
|
+
"Modify Docker config",
|
|
2764
|
+
"Change environment vars",
|
|
2765
|
+
"Update docs automatically",
|
|
2766
|
+
"Edit README",
|
|
2767
|
+
"Handle secrets/credentials",
|
|
2768
|
+
"Modify auth logic",
|
|
2769
|
+
"Delete failing tests",
|
|
2770
|
+
"Skip tests temporarily"
|
|
2771
|
+
];
|
|
2772
|
+
var TEST_FRAMEWORKS = [
|
|
2773
|
+
"jest",
|
|
2774
|
+
"vitest",
|
|
2775
|
+
"mocha",
|
|
2776
|
+
"ava",
|
|
2777
|
+
"tap",
|
|
2778
|
+
"pytest",
|
|
2779
|
+
"unittest",
|
|
2780
|
+
"nose2",
|
|
2781
|
+
"go test",
|
|
2782
|
+
"testify",
|
|
2783
|
+
"cargo test",
|
|
2784
|
+
"rstest",
|
|
2785
|
+
"junit",
|
|
2786
|
+
"testng",
|
|
2787
|
+
"spock",
|
|
2788
|
+
"rspec",
|
|
2789
|
+
"minitest",
|
|
2790
|
+
"phpunit",
|
|
2791
|
+
"pest",
|
|
2792
|
+
"playwright",
|
|
2793
|
+
"cypress",
|
|
2794
|
+
"puppeteer",
|
|
2795
|
+
"selenium"
|
|
2796
|
+
];
|
|
2797
|
+
var TEST_LEVELS = [
|
|
2798
|
+
{ id: "smoke", label: "Smoke", desc: "Quick sanity checks" },
|
|
2799
|
+
{ id: "unit", label: "Unit", desc: "Individual functions/components" },
|
|
2800
|
+
{ id: "integration", label: "Integration", desc: "Component interactions" },
|
|
2801
|
+
{ id: "e2e", label: "E2E", desc: "Full user flows" }
|
|
2802
|
+
];
|
|
2803
|
+
var PROJECT_TYPES = [
|
|
2804
|
+
{ id: "work", label: "Work", icon: "\u{1F4BC}", description: "Professional/enterprise project" },
|
|
2805
|
+
{ id: "leisure", label: "Leisure", icon: "\u{1F3AE}", description: "Personal/hobby project" },
|
|
2806
|
+
{ id: "opensource", label: "Open Source", icon: "\u{1F30D}", description: "Community-driven project" },
|
|
2807
|
+
{ id: "learning", label: "Learning", icon: "\u{1F4DA}", description: "Educational/experimental" }
|
|
2808
|
+
];
|
|
2809
|
+
function canAccessTier(userTier, requiredTier) {
|
|
2810
|
+
const tierLevels = { free: 0, pro: 1, max: 2, teams: 2 };
|
|
2811
|
+
const requiredLevels = { basic: 0, intermediate: 1, advanced: 2 };
|
|
2812
|
+
return tierLevels[userTier] >= requiredLevels[requiredTier];
|
|
2813
|
+
}
|
|
2814
|
+
function getTierBadge(tier) {
|
|
2815
|
+
switch (tier) {
|
|
2816
|
+
case "intermediate":
|
|
2817
|
+
return { label: "PRO", color: chalk8.cyan };
|
|
2818
|
+
case "advanced":
|
|
2819
|
+
return { label: "MAX", color: chalk8.magenta };
|
|
2820
|
+
default:
|
|
2821
|
+
return null;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
function getAvailableSteps(userTier) {
|
|
2825
|
+
return WIZARD_STEPS.filter((step) => canAccessTier(userTier, step.tier));
|
|
2826
|
+
}
|
|
2827
|
+
function printBox(lines, color = chalk8.gray) {
|
|
2828
|
+
const maxLen = Math.max(...lines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "").length));
|
|
2829
|
+
const top = "\u250C" + "\u2500".repeat(maxLen + 2) + "\u2510";
|
|
2830
|
+
const bottom = "\u2514" + "\u2500".repeat(maxLen + 2) + "\u2518";
|
|
2831
|
+
console.log(color(top));
|
|
2832
|
+
for (const line of lines) {
|
|
2833
|
+
const stripped = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2834
|
+
const padding = " ".repeat(maxLen - stripped.length);
|
|
2835
|
+
console.log(color("\u2502 ") + line + padding + color(" \u2502"));
|
|
2836
|
+
}
|
|
2837
|
+
console.log(color(bottom));
|
|
2838
|
+
}
|
|
2839
|
+
function showStep(current, step, userTier) {
|
|
2840
|
+
const availableSteps = getAvailableSteps(userTier);
|
|
2841
|
+
const total = availableSteps.length;
|
|
2842
|
+
const progress = "\u25CF".repeat(current) + "\u25CB".repeat(total - current);
|
|
2843
|
+
const badge = getTierBadge(step.tier);
|
|
2844
|
+
console.log();
|
|
2845
|
+
let stepLine = chalk8.cyan(` ${progress} Step ${current}/${total}: ${step.icon} ${step.title}`);
|
|
2846
|
+
if (badge) {
|
|
2847
|
+
stepLine += " " + badge.color(`[${badge.label}]`);
|
|
2848
|
+
}
|
|
2849
|
+
console.log(stepLine);
|
|
2850
|
+
console.log();
|
|
2851
|
+
}
|
|
2852
|
+
function showWizardOverview(userTier) {
|
|
2853
|
+
console.log(chalk8.bold(" \u{1F4CB} Wizard Steps Overview:"));
|
|
2854
|
+
console.log();
|
|
2855
|
+
let stepNum = 1;
|
|
2856
|
+
for (const step of WIZARD_STEPS) {
|
|
2857
|
+
const canAccess = canAccessTier(userTier, step.tier);
|
|
2858
|
+
const badge = getTierBadge(step.tier);
|
|
2859
|
+
if (canAccess) {
|
|
2860
|
+
let line = chalk8.green(` ${stepNum.toString().padStart(2)}. \u2713 ${step.icon} ${step.title}`);
|
|
2861
|
+
if (badge) {
|
|
2862
|
+
line += " " + badge.color(`[${badge.label}]`);
|
|
2863
|
+
}
|
|
2864
|
+
console.log(line);
|
|
2865
|
+
stepNum++;
|
|
2866
|
+
} else {
|
|
2867
|
+
let line = chalk8.gray(` \u2500 \u{1F512} ${step.icon} ${step.title}`);
|
|
2868
|
+
if (badge) {
|
|
2869
|
+
line += " " + badge.color.dim(`[${badge.label}]`);
|
|
2870
|
+
}
|
|
2871
|
+
console.log(line);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
console.log();
|
|
2875
|
+
}
|
|
2876
|
+
var promptConfig = {
|
|
2877
|
+
onCancel: () => {
|
|
2878
|
+
console.log(chalk8.yellow("\n Cancelled. Run 'lynxp wizard' anytime to restart.\n"));
|
|
2879
|
+
process.exit(0);
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2330
2882
|
async function wizardCommand(options) {
|
|
2331
2883
|
console.log();
|
|
2332
|
-
console.log(chalk8.cyan("\u{1F431} LynxPrompt Wizard"));
|
|
2333
|
-
console.log(chalk8.gray("Generate AI IDE configuration in seconds"));
|
|
2884
|
+
console.log(chalk8.cyan.bold(" \u{1F431} LynxPrompt Wizard"));
|
|
2885
|
+
console.log(chalk8.gray(" Generate AI IDE configuration in seconds"));
|
|
2334
2886
|
console.log();
|
|
2887
|
+
const authenticated = isAuthenticated();
|
|
2888
|
+
const user = getUser();
|
|
2889
|
+
const userPlanRaw = user?.plan?.toLowerCase() || "free";
|
|
2890
|
+
const userTier = ["pro", "max", "teams"].includes(userPlanRaw) ? userPlanRaw : "free";
|
|
2891
|
+
const userPlanDisplay = user?.plan?.toUpperCase() || "FREE";
|
|
2892
|
+
if (!authenticated) {
|
|
2893
|
+
console.log(chalk8.yellow("\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
2894
|
+
console.log(chalk8.yellow("\u2502") + chalk8.white(" \u{1F4A1} ") + chalk8.gray("Log in for full wizard features:") + " " + chalk8.yellow("\u2502"));
|
|
2895
|
+
console.log(chalk8.yellow("\u2502") + " " + chalk8.yellow("\u2502"));
|
|
2896
|
+
console.log(chalk8.yellow("\u2502") + chalk8.gray(" \u2022 ") + chalk8.white("Commands & Code Style") + chalk8.cyan(" [PRO]") + " " + chalk8.yellow("\u2502"));
|
|
2897
|
+
console.log(chalk8.yellow("\u2502") + chalk8.gray(" \u2022 ") + chalk8.white("Boundaries, Testing, Static Files") + chalk8.magenta(" [MAX]") + " " + chalk8.yellow("\u2502"));
|
|
2898
|
+
console.log(chalk8.yellow("\u2502") + chalk8.gray(" \u2022 ") + chalk8.white("Push configs to cloud") + chalk8.gray(" (lynxp push)") + " " + chalk8.yellow("\u2502"));
|
|
2899
|
+
console.log(chalk8.yellow("\u2502") + chalk8.gray(" \u2022 ") + chalk8.white("Sync across devices") + chalk8.gray(" (lynxp sync)") + " " + chalk8.yellow("\u2502"));
|
|
2900
|
+
console.log(chalk8.yellow("\u2502") + " " + chalk8.yellow("\u2502"));
|
|
2901
|
+
console.log(chalk8.yellow("\u2502") + chalk8.cyan(" Run: lynxp login") + " " + chalk8.yellow("\u2502"));
|
|
2902
|
+
console.log(chalk8.yellow("\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
2903
|
+
console.log();
|
|
2904
|
+
} else {
|
|
2905
|
+
const planEmoji = userTier === "teams" ? "\u{1F465}" : userTier === "max" ? "\u{1F680}" : userTier === "pro" ? "\u26A1" : "\u{1F193}";
|
|
2906
|
+
console.log(chalk8.green(` \u2713 Logged in as ${chalk8.bold(user?.name || user?.email)} ${planEmoji} ${chalk8.gray(userPlanDisplay)}`));
|
|
2907
|
+
console.log();
|
|
2908
|
+
}
|
|
2909
|
+
showWizardOverview(userTier);
|
|
2910
|
+
const accessibleSteps = getAvailableSteps(userTier);
|
|
2911
|
+
const lockedSteps = WIZARD_STEPS.length - accessibleSteps.length;
|
|
2912
|
+
if (lockedSteps > 0) {
|
|
2913
|
+
console.log(chalk8.gray(` ${lockedSteps} step${lockedSteps > 1 ? "s" : ""} locked. Upgrade at ${chalk8.cyan("https://lynxprompt.com/pricing")}`));
|
|
2914
|
+
console.log();
|
|
2915
|
+
}
|
|
2335
2916
|
const detected = await detectProject(process.cwd());
|
|
2336
2917
|
if (detected) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
if (detected.
|
|
2341
|
-
if (detected.
|
|
2342
|
-
if (detected.
|
|
2918
|
+
const detectedInfo = [
|
|
2919
|
+
chalk8.green("\u2713 Project detected")
|
|
2920
|
+
];
|
|
2921
|
+
if (detected.name) detectedInfo.push(chalk8.gray(` Name: ${detected.name}`));
|
|
2922
|
+
if (detected.stack.length > 0) detectedInfo.push(chalk8.gray(` Stack: ${detected.stack.join(", ")}`));
|
|
2923
|
+
if (detected.packageManager) detectedInfo.push(chalk8.gray(` Package manager: ${detected.packageManager}`));
|
|
2924
|
+
printBox(detectedInfo, chalk8.gray);
|
|
2343
2925
|
console.log();
|
|
2344
2926
|
}
|
|
2345
2927
|
let config2;
|
|
@@ -2362,19 +2944,20 @@ async function wizardCommand(options) {
|
|
|
2362
2944
|
commands: detected?.commands || {}
|
|
2363
2945
|
};
|
|
2364
2946
|
} else {
|
|
2365
|
-
config2 = await runInteractiveWizard(options, detected);
|
|
2947
|
+
config2 = await runInteractiveWizard(options, detected, userTier);
|
|
2366
2948
|
}
|
|
2367
2949
|
const spinner = ora7("Generating configuration...").start();
|
|
2368
2950
|
try {
|
|
2369
2951
|
const files = generateConfig(config2);
|
|
2370
2952
|
spinner.stop();
|
|
2371
2953
|
console.log();
|
|
2372
|
-
console.log(chalk8.green("\u2705 Generated:"));
|
|
2954
|
+
console.log(chalk8.green.bold(" \u2705 Generated:"));
|
|
2955
|
+
console.log();
|
|
2373
2956
|
for (const [filename, content] of Object.entries(files)) {
|
|
2374
2957
|
const outputPath = join6(process.cwd(), filename);
|
|
2375
2958
|
let exists = false;
|
|
2376
2959
|
try {
|
|
2377
|
-
await
|
|
2960
|
+
await access3(outputPath);
|
|
2378
2961
|
exists = true;
|
|
2379
2962
|
} catch {
|
|
2380
2963
|
}
|
|
@@ -2386,7 +2969,7 @@ async function wizardCommand(options) {
|
|
|
2386
2969
|
initial: false
|
|
2387
2970
|
});
|
|
2388
2971
|
if (!response.overwrite) {
|
|
2389
|
-
console.log(chalk8.yellow(`
|
|
2972
|
+
console.log(chalk8.yellow(` \u23ED\uFE0F Skipped: ${filename}`));
|
|
2390
2973
|
continue;
|
|
2391
2974
|
}
|
|
2392
2975
|
}
|
|
@@ -2395,15 +2978,24 @@ async function wizardCommand(options) {
|
|
|
2395
2978
|
await mkdir4(dir, { recursive: true });
|
|
2396
2979
|
}
|
|
2397
2980
|
await writeFile4(outputPath, content, "utf-8");
|
|
2398
|
-
console.log(`
|
|
2981
|
+
console.log(` ${chalk8.cyan("\u2192")} ${chalk8.bold(filename)}`);
|
|
2399
2982
|
}
|
|
2400
2983
|
console.log();
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2984
|
+
const nextStepsLines = [
|
|
2985
|
+
chalk8.gray("Your AI assistant will now follow these instructions."),
|
|
2986
|
+
"",
|
|
2987
|
+
chalk8.gray("Next steps:"),
|
|
2988
|
+
chalk8.cyan(" lynxp check ") + chalk8.gray("Validate configuration")
|
|
2989
|
+
];
|
|
2990
|
+
if (authenticated) {
|
|
2991
|
+
nextStepsLines.push(chalk8.cyan(" lynxp push ") + chalk8.gray("Upload to cloud"));
|
|
2992
|
+
nextStepsLines.push(chalk8.cyan(" lynxp link ") + chalk8.gray("Link to a blueprint"));
|
|
2993
|
+
nextStepsLines.push(chalk8.cyan(" lynxp sync ") + chalk8.gray("Sync with linked blueprint"));
|
|
2994
|
+
} else {
|
|
2995
|
+
nextStepsLines.push(chalk8.gray(" lynxp login ") + chalk8.yellow("Log in to push & sync"));
|
|
2996
|
+
}
|
|
2997
|
+
nextStepsLines.push(chalk8.cyan(" lynxp status ") + chalk8.gray("View current setup"));
|
|
2998
|
+
printBox(nextStepsLines, chalk8.gray);
|
|
2407
2999
|
console.log();
|
|
2408
3000
|
} catch (error) {
|
|
2409
3001
|
spinner.fail("Failed to generate files");
|
|
@@ -2415,121 +3007,476 @@ async function wizardCommand(options) {
|
|
|
2415
3007
|
process.exit(1);
|
|
2416
3008
|
}
|
|
2417
3009
|
}
|
|
2418
|
-
async function runInteractiveWizard(options, detected) {
|
|
3010
|
+
async function runInteractiveWizard(options, detected, userTier) {
|
|
2419
3011
|
const answers = {};
|
|
3012
|
+
const availableSteps = getAvailableSteps(userTier);
|
|
3013
|
+
let currentStepNum = 0;
|
|
3014
|
+
const getCurrentStep = (stepId) => {
|
|
3015
|
+
const step = availableSteps.find((s) => s.id === stepId);
|
|
3016
|
+
if (step) {
|
|
3017
|
+
currentStepNum++;
|
|
3018
|
+
return step;
|
|
3019
|
+
}
|
|
3020
|
+
return null;
|
|
3021
|
+
};
|
|
3022
|
+
const formatStep = getCurrentStep("format");
|
|
3023
|
+
showStep(currentStepNum, formatStep, userTier);
|
|
2420
3024
|
let platforms;
|
|
2421
3025
|
if (options.format) {
|
|
2422
3026
|
platforms = options.format.split(",").map((f) => f.trim());
|
|
3027
|
+
console.log(chalk8.gray(` Using format from flag: ${platforms.join(", ")}`));
|
|
2423
3028
|
} else {
|
|
2424
3029
|
const formatResponse = await prompts4({
|
|
2425
3030
|
type: "select",
|
|
2426
3031
|
name: "format",
|
|
2427
|
-
message: "
|
|
3032
|
+
message: chalk8.white("Where will you use this?"),
|
|
2428
3033
|
choices: OUTPUT_FORMATS.map((f) => ({
|
|
2429
|
-
title: f.recommended ? `${f.title} ${chalk8.green("
|
|
3034
|
+
title: f.recommended ? `${f.title} ${chalk8.green.bold("\u2605 recommended")}` : f.title,
|
|
2430
3035
|
value: f.value,
|
|
2431
|
-
description: f.description
|
|
3036
|
+
description: chalk8.gray(f.description)
|
|
2432
3037
|
})),
|
|
2433
|
-
initial: 0
|
|
2434
|
-
|
|
2435
|
-
});
|
|
3038
|
+
initial: 0,
|
|
3039
|
+
hint: chalk8.gray("\u2191\u2193 navigate \u2022 enter select")
|
|
3040
|
+
}, promptConfig);
|
|
2436
3041
|
if (formatResponse.format === "multiple") {
|
|
3042
|
+
console.log();
|
|
3043
|
+
console.log(chalk8.gray(" Select the AI editors you want to generate config for:"));
|
|
3044
|
+
console.log();
|
|
2437
3045
|
const platformResponse = await prompts4({
|
|
2438
3046
|
type: "multiselect",
|
|
2439
3047
|
name: "platforms",
|
|
2440
|
-
message: "Select AI editors:",
|
|
2441
|
-
choices:
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
3048
|
+
message: chalk8.white("Select AI editors (16 supported):"),
|
|
3049
|
+
choices: ALL_PLATFORMS.map((p) => ({
|
|
3050
|
+
title: `${p.icon} ${p.name}`,
|
|
3051
|
+
value: p.id,
|
|
3052
|
+
description: chalk8.gray(p.note)
|
|
3053
|
+
})),
|
|
3054
|
+
hint: chalk8.gray("space select \u2022 a toggle all \u2022 enter confirm"),
|
|
3055
|
+
min: 1,
|
|
3056
|
+
instructions: false
|
|
3057
|
+
}, promptConfig);
|
|
2445
3058
|
platforms = platformResponse.platforms || ["agents"];
|
|
3059
|
+
console.log(chalk8.green(` \u2713 Selected ${platforms.length} platform${platforms.length === 1 ? "" : "s"}`));
|
|
2446
3060
|
} else {
|
|
2447
3061
|
platforms = [formatResponse.format || "agents"];
|
|
2448
3062
|
}
|
|
2449
3063
|
}
|
|
2450
3064
|
answers.platforms = platforms;
|
|
3065
|
+
const projectStep = getCurrentStep("project");
|
|
3066
|
+
showStep(currentStepNum, projectStep, userTier);
|
|
2451
3067
|
const nameResponse = await prompts4({
|
|
2452
3068
|
type: "text",
|
|
2453
3069
|
name: "name",
|
|
2454
|
-
message: "Project name:",
|
|
2455
|
-
initial: options.name || detected?.name || "my-project"
|
|
2456
|
-
|
|
3070
|
+
message: chalk8.white("Project name:"),
|
|
3071
|
+
initial: options.name || detected?.name || "my-project",
|
|
3072
|
+
hint: chalk8.gray("Used in the generated config header")
|
|
3073
|
+
}, promptConfig);
|
|
2457
3074
|
answers.name = nameResponse.name || "my-project";
|
|
2458
3075
|
const descResponse = await prompts4({
|
|
2459
3076
|
type: "text",
|
|
2460
3077
|
name: "description",
|
|
2461
|
-
message: "Brief description
|
|
2462
|
-
initial: options.description || ""
|
|
2463
|
-
|
|
3078
|
+
message: chalk8.white("Brief description:"),
|
|
3079
|
+
initial: options.description || "",
|
|
3080
|
+
hint: chalk8.gray("optional - helps AI understand context")
|
|
3081
|
+
}, promptConfig);
|
|
2464
3082
|
answers.description = descResponse.description || "";
|
|
2465
|
-
const
|
|
3083
|
+
const typeResponse = await prompts4({
|
|
3084
|
+
type: "select",
|
|
3085
|
+
name: "projectType",
|
|
3086
|
+
message: chalk8.white("Project type:"),
|
|
3087
|
+
choices: PROJECT_TYPES.map((t) => ({
|
|
3088
|
+
title: `${t.icon} ${t.label}`,
|
|
3089
|
+
value: t.id,
|
|
3090
|
+
description: chalk8.gray(t.description)
|
|
3091
|
+
})),
|
|
3092
|
+
initial: 0
|
|
3093
|
+
}, promptConfig);
|
|
3094
|
+
answers.projectType = typeResponse.projectType || "work";
|
|
3095
|
+
const techStep = getCurrentStep("tech");
|
|
3096
|
+
showStep(currentStepNum, techStep, userTier);
|
|
3097
|
+
const allStackOptions = [...LANGUAGES, ...FRAMEWORKS, ...DATABASES];
|
|
2466
3098
|
const detectedStackSet = new Set(detected?.stack || []);
|
|
3099
|
+
if (detectedStackSet.size > 0) {
|
|
3100
|
+
console.log(chalk8.gray(` Auto-selected: ${detected?.stack?.join(", ")}`));
|
|
3101
|
+
console.log();
|
|
3102
|
+
}
|
|
2467
3103
|
const stackResponse = await prompts4({
|
|
2468
3104
|
type: "multiselect",
|
|
2469
3105
|
name: "stack",
|
|
2470
|
-
message: "
|
|
3106
|
+
message: chalk8.white("Languages, frameworks & databases:"),
|
|
2471
3107
|
choices: allStackOptions.map((s) => ({
|
|
2472
3108
|
title: s.title,
|
|
2473
3109
|
value: s.value,
|
|
2474
3110
|
selected: detectedStackSet.has(s.value)
|
|
2475
3111
|
})),
|
|
2476
|
-
hint: "
|
|
2477
|
-
|
|
3112
|
+
hint: chalk8.gray("space select \u2022 a toggle all \u2022 enter confirm"),
|
|
3113
|
+
instructions: false
|
|
3114
|
+
}, promptConfig);
|
|
2478
3115
|
answers.stack = stackResponse.stack || [];
|
|
3116
|
+
const repoStep = getCurrentStep("repo");
|
|
3117
|
+
showStep(currentStepNum, repoStep, userTier);
|
|
3118
|
+
const repoHostResponse = await prompts4({
|
|
3119
|
+
type: "select",
|
|
3120
|
+
name: "repoHost",
|
|
3121
|
+
message: chalk8.white("Repository host:"),
|
|
3122
|
+
choices: REPO_HOSTS.map((h) => ({
|
|
3123
|
+
title: `${h.icon} ${h.label}`,
|
|
3124
|
+
value: h.id
|
|
3125
|
+
})),
|
|
3126
|
+
initial: 0
|
|
3127
|
+
}, promptConfig);
|
|
3128
|
+
answers.repoHost = repoHostResponse.repoHost || "github";
|
|
3129
|
+
const visibilityResponse = await prompts4({
|
|
3130
|
+
type: "toggle",
|
|
3131
|
+
name: "isPublic",
|
|
3132
|
+
message: chalk8.white("Public repository?"),
|
|
3133
|
+
initial: false,
|
|
3134
|
+
active: "Yes",
|
|
3135
|
+
inactive: "No"
|
|
3136
|
+
}, promptConfig);
|
|
3137
|
+
answers.isPublic = visibilityResponse.isPublic || false;
|
|
3138
|
+
const licenseResponse = await prompts4({
|
|
3139
|
+
type: "select",
|
|
3140
|
+
name: "license",
|
|
3141
|
+
message: chalk8.white("License:"),
|
|
3142
|
+
choices: LICENSES.map((l) => ({
|
|
3143
|
+
title: l.label,
|
|
3144
|
+
value: l.id
|
|
3145
|
+
})),
|
|
3146
|
+
initial: 0
|
|
3147
|
+
}, promptConfig);
|
|
3148
|
+
answers.license = licenseResponse.license || "mit";
|
|
3149
|
+
const conventionalResponse = await prompts4({
|
|
3150
|
+
type: "toggle",
|
|
3151
|
+
name: "conventionalCommits",
|
|
3152
|
+
message: chalk8.white("Use Conventional Commits?"),
|
|
3153
|
+
initial: true,
|
|
3154
|
+
active: "Yes",
|
|
3155
|
+
inactive: "No"
|
|
3156
|
+
}, promptConfig);
|
|
3157
|
+
answers.conventionalCommits = conventionalResponse.conventionalCommits ?? true;
|
|
3158
|
+
if (canAccessTier(userTier, "intermediate")) {
|
|
3159
|
+
const commandsStep = getCurrentStep("commands");
|
|
3160
|
+
showStep(currentStepNum, commandsStep, userTier);
|
|
3161
|
+
console.log(chalk8.gray(" Select common commands for your project:"));
|
|
3162
|
+
console.log();
|
|
3163
|
+
const buildResponse = await prompts4({
|
|
3164
|
+
type: "multiselect",
|
|
3165
|
+
name: "build",
|
|
3166
|
+
message: chalk8.white("Build commands:"),
|
|
3167
|
+
choices: COMMON_COMMANDS.build.slice(0, 12).map((c) => ({
|
|
3168
|
+
title: chalk8.cyan(c),
|
|
3169
|
+
value: c,
|
|
3170
|
+
selected: detected?.commands?.build === c
|
|
3171
|
+
})),
|
|
3172
|
+
hint: chalk8.gray("space select \u2022 enter confirm"),
|
|
3173
|
+
instructions: false
|
|
3174
|
+
}, promptConfig);
|
|
3175
|
+
const testResponse = await prompts4({
|
|
3176
|
+
type: "multiselect",
|
|
3177
|
+
name: "test",
|
|
3178
|
+
message: chalk8.white("Test commands:"),
|
|
3179
|
+
choices: COMMON_COMMANDS.test.slice(0, 12).map((c) => ({
|
|
3180
|
+
title: chalk8.yellow(c),
|
|
3181
|
+
value: c,
|
|
3182
|
+
selected: detected?.commands?.test === c
|
|
3183
|
+
})),
|
|
3184
|
+
hint: chalk8.gray("space select \u2022 enter confirm"),
|
|
3185
|
+
instructions: false
|
|
3186
|
+
}, promptConfig);
|
|
3187
|
+
const lintResponse = await prompts4({
|
|
3188
|
+
type: "multiselect",
|
|
3189
|
+
name: "lint",
|
|
3190
|
+
message: chalk8.white("Lint/format commands:"),
|
|
3191
|
+
choices: COMMON_COMMANDS.lint.slice(0, 12).map((c) => ({
|
|
3192
|
+
title: chalk8.green(c),
|
|
3193
|
+
value: c,
|
|
3194
|
+
selected: detected?.commands?.lint === c
|
|
3195
|
+
})),
|
|
3196
|
+
hint: chalk8.gray("space select \u2022 enter confirm"),
|
|
3197
|
+
instructions: false
|
|
3198
|
+
}, promptConfig);
|
|
3199
|
+
const devResponse = await prompts4({
|
|
3200
|
+
type: "multiselect",
|
|
3201
|
+
name: "dev",
|
|
3202
|
+
message: chalk8.white("Dev server commands:"),
|
|
3203
|
+
choices: COMMON_COMMANDS.dev.slice(0, 12).map((c) => ({
|
|
3204
|
+
title: chalk8.magenta(c),
|
|
3205
|
+
value: c,
|
|
3206
|
+
selected: detected?.commands?.dev === c
|
|
3207
|
+
})),
|
|
3208
|
+
hint: chalk8.gray("space select \u2022 enter confirm"),
|
|
3209
|
+
instructions: false
|
|
3210
|
+
}, promptConfig);
|
|
3211
|
+
answers.commands = {
|
|
3212
|
+
build: buildResponse.build || [],
|
|
3213
|
+
test: testResponse.test || [],
|
|
3214
|
+
lint: lintResponse.lint || [],
|
|
3215
|
+
dev: devResponse.dev || []
|
|
3216
|
+
};
|
|
3217
|
+
const customCmdResponse = await prompts4({
|
|
3218
|
+
type: "text",
|
|
3219
|
+
name: "custom",
|
|
3220
|
+
message: chalk8.white("Additional custom command (optional):"),
|
|
3221
|
+
hint: chalk8.gray("e.g., npm run migrate, make deploy")
|
|
3222
|
+
}, promptConfig);
|
|
3223
|
+
if (customCmdResponse.custom) {
|
|
3224
|
+
answers.commands.custom = customCmdResponse.custom;
|
|
3225
|
+
}
|
|
3226
|
+
} else {
|
|
3227
|
+
answers.commands = detected?.commands || {};
|
|
3228
|
+
}
|
|
3229
|
+
if (canAccessTier(userTier, "intermediate")) {
|
|
3230
|
+
const styleStep = getCurrentStep("code_style");
|
|
3231
|
+
showStep(currentStepNum, styleStep, userTier);
|
|
3232
|
+
const namingResponse = await prompts4({
|
|
3233
|
+
type: "select",
|
|
3234
|
+
name: "naming",
|
|
3235
|
+
message: chalk8.white("Naming convention:"),
|
|
3236
|
+
choices: NAMING_CONVENTIONS.map((n) => ({
|
|
3237
|
+
title: n.label,
|
|
3238
|
+
value: n.id,
|
|
3239
|
+
description: chalk8.gray(n.desc)
|
|
3240
|
+
})),
|
|
3241
|
+
initial: 0
|
|
3242
|
+
}, promptConfig);
|
|
3243
|
+
answers.namingConvention = namingResponse.naming || "language_default";
|
|
3244
|
+
const errorResponse = await prompts4({
|
|
3245
|
+
type: "select",
|
|
3246
|
+
name: "errorHandling",
|
|
3247
|
+
message: chalk8.white("Error handling pattern:"),
|
|
3248
|
+
choices: ERROR_PATTERNS.map((e) => ({
|
|
3249
|
+
title: e.label,
|
|
3250
|
+
value: e.id
|
|
3251
|
+
})),
|
|
3252
|
+
initial: 0
|
|
3253
|
+
}, promptConfig);
|
|
3254
|
+
answers.errorHandling = errorResponse.errorHandling || "try_catch";
|
|
3255
|
+
const styleNotesResponse = await prompts4({
|
|
3256
|
+
type: "text",
|
|
3257
|
+
name: "styleNotes",
|
|
3258
|
+
message: chalk8.white("Additional style notes (optional):"),
|
|
3259
|
+
hint: chalk8.gray("e.g., prefer named exports, max line length 100")
|
|
3260
|
+
}, promptConfig);
|
|
3261
|
+
answers.styleNotes = styleNotesResponse.styleNotes || "";
|
|
3262
|
+
}
|
|
3263
|
+
const aiStep = getCurrentStep("ai");
|
|
3264
|
+
showStep(currentStepNum, aiStep, userTier);
|
|
3265
|
+
const aiBehaviorResponse = await prompts4({
|
|
3266
|
+
type: "multiselect",
|
|
3267
|
+
name: "aiBehavior",
|
|
3268
|
+
message: chalk8.white("AI behavior rules:"),
|
|
3269
|
+
choices: AI_BEHAVIOR_RULES.map((r) => ({
|
|
3270
|
+
title: r.recommended ? `${r.label} ${chalk8.green("\u2605")}` : r.label,
|
|
3271
|
+
value: r.id,
|
|
3272
|
+
selected: r.recommended
|
|
3273
|
+
})),
|
|
3274
|
+
hint: chalk8.gray("space select \u2022 enter confirm"),
|
|
3275
|
+
instructions: false
|
|
3276
|
+
}, promptConfig);
|
|
3277
|
+
answers.aiBehavior = aiBehaviorResponse.aiBehavior || [];
|
|
3278
|
+
const importantFilesResponse = await prompts4({
|
|
3279
|
+
type: "multiselect",
|
|
3280
|
+
name: "importantFiles",
|
|
3281
|
+
message: chalk8.white("Important files AI should read:"),
|
|
3282
|
+
choices: IMPORTANT_FILES.map((f) => ({
|
|
3283
|
+
title: `${f.icon} ${f.label}`,
|
|
3284
|
+
value: f.id,
|
|
3285
|
+
selected: f.id === "readme" || f.id === "package"
|
|
3286
|
+
})),
|
|
3287
|
+
hint: chalk8.gray("space select \u2022 enter confirm"),
|
|
3288
|
+
instructions: false
|
|
3289
|
+
}, promptConfig);
|
|
3290
|
+
answers.importantFiles = importantFilesResponse.importantFiles || [];
|
|
3291
|
+
const selfImproveResponse = await prompts4({
|
|
3292
|
+
type: "toggle",
|
|
3293
|
+
name: "selfImprove",
|
|
3294
|
+
message: chalk8.white("Enable self-improving blueprint?"),
|
|
3295
|
+
initial: false,
|
|
3296
|
+
active: "Yes",
|
|
3297
|
+
inactive: "No"
|
|
3298
|
+
}, promptConfig);
|
|
3299
|
+
answers.selfImprove = selfImproveResponse.selfImprove || false;
|
|
3300
|
+
if (canAccessTier(userTier, "advanced")) {
|
|
3301
|
+
const boundariesStep = getCurrentStep("boundaries");
|
|
3302
|
+
showStep(currentStepNum, boundariesStep, userTier);
|
|
3303
|
+
const presetResponse = await prompts4({
|
|
3304
|
+
type: "select",
|
|
3305
|
+
name: "boundaryPreset",
|
|
3306
|
+
message: chalk8.white("Boundary preset:"),
|
|
3307
|
+
choices: BOUNDARY_PRESETS.map((b) => ({
|
|
3308
|
+
title: b.title,
|
|
3309
|
+
value: b.value,
|
|
3310
|
+
description: chalk8.gray(b.description)
|
|
3311
|
+
})),
|
|
3312
|
+
initial: 0
|
|
3313
|
+
}, promptConfig);
|
|
3314
|
+
answers.boundaries = presetResponse.boundaryPreset || "standard";
|
|
3315
|
+
const selectedPreset = BOUNDARY_PRESETS.find((b) => b.value === answers.boundaries);
|
|
3316
|
+
if (selectedPreset) {
|
|
3317
|
+
console.log();
|
|
3318
|
+
console.log(chalk8.gray(" Preset details:"));
|
|
3319
|
+
console.log(chalk8.green(` \u2713 Always: ${selectedPreset.always.slice(0, 3).join(", ")}`));
|
|
3320
|
+
console.log(chalk8.yellow(` ? Ask: ${selectedPreset.askFirst.slice(0, 2).join(", ")}`));
|
|
3321
|
+
console.log(chalk8.red(` \u2717 Never: ${selectedPreset.never.slice(0, 2).join(", ")}`));
|
|
3322
|
+
}
|
|
3323
|
+
const customizeResponse = await prompts4({
|
|
3324
|
+
type: "toggle",
|
|
3325
|
+
name: "customize",
|
|
3326
|
+
message: chalk8.white("Customize specific boundaries?"),
|
|
3327
|
+
initial: false,
|
|
3328
|
+
active: "Yes",
|
|
3329
|
+
inactive: "No"
|
|
3330
|
+
}, promptConfig);
|
|
3331
|
+
if (customizeResponse.customize) {
|
|
3332
|
+
console.log();
|
|
3333
|
+
console.log(chalk8.gray(" Select actions AI should NEVER do:"));
|
|
3334
|
+
const neverResponse = await prompts4({
|
|
3335
|
+
type: "multiselect",
|
|
3336
|
+
name: "never",
|
|
3337
|
+
message: chalk8.white("Never allow:"),
|
|
3338
|
+
choices: BOUNDARY_OPTIONS.map((o) => ({
|
|
3339
|
+
title: chalk8.red(o),
|
|
3340
|
+
value: o,
|
|
3341
|
+
selected: selectedPreset?.never.includes(o)
|
|
3342
|
+
})),
|
|
3343
|
+
instructions: false
|
|
3344
|
+
}, promptConfig);
|
|
3345
|
+
answers.boundaryNever = neverResponse.never || [];
|
|
3346
|
+
console.log(chalk8.gray(" Select actions AI should ASK before doing:"));
|
|
3347
|
+
const askResponse = await prompts4({
|
|
3348
|
+
type: "multiselect",
|
|
3349
|
+
name: "ask",
|
|
3350
|
+
message: chalk8.white("Ask first:"),
|
|
3351
|
+
choices: BOUNDARY_OPTIONS.filter((o) => !answers.boundaryNever?.includes(o)).map((o) => ({
|
|
3352
|
+
title: chalk8.yellow(o),
|
|
3353
|
+
value: o,
|
|
3354
|
+
selected: selectedPreset?.askFirst.includes(o)
|
|
3355
|
+
})),
|
|
3356
|
+
instructions: false
|
|
3357
|
+
}, promptConfig);
|
|
3358
|
+
answers.boundaryAsk = askResponse.ask || [];
|
|
3359
|
+
}
|
|
3360
|
+
} else {
|
|
3361
|
+
answers.boundaries = options.boundaries || "standard";
|
|
3362
|
+
}
|
|
3363
|
+
if (canAccessTier(userTier, "advanced")) {
|
|
3364
|
+
const testingStep = getCurrentStep("testing");
|
|
3365
|
+
showStep(currentStepNum, testingStep, userTier);
|
|
3366
|
+
const testLevelsResponse = await prompts4({
|
|
3367
|
+
type: "multiselect",
|
|
3368
|
+
name: "testLevels",
|
|
3369
|
+
message: chalk8.white("Test levels:"),
|
|
3370
|
+
choices: TEST_LEVELS.map((l) => ({
|
|
3371
|
+
title: `${l.label} - ${chalk8.gray(l.desc)}`,
|
|
3372
|
+
value: l.id,
|
|
3373
|
+
selected: l.id === "unit" || l.id === "integration"
|
|
3374
|
+
})),
|
|
3375
|
+
instructions: false
|
|
3376
|
+
}, promptConfig);
|
|
3377
|
+
answers.testLevels = testLevelsResponse.testLevels || [];
|
|
3378
|
+
const detectedFrameworks = answers.stack?.includes("typescript") || answers.stack?.includes("javascript") ? ["jest", "vitest"] : answers.stack?.includes("python") ? ["pytest"] : [];
|
|
3379
|
+
const testFrameworkResponse = await prompts4({
|
|
3380
|
+
type: "multiselect",
|
|
3381
|
+
name: "testFrameworks",
|
|
3382
|
+
message: chalk8.white("Testing frameworks:"),
|
|
3383
|
+
choices: TEST_FRAMEWORKS.slice(0, 16).map((f) => ({
|
|
3384
|
+
title: f,
|
|
3385
|
+
value: f,
|
|
3386
|
+
selected: detectedFrameworks.includes(f)
|
|
3387
|
+
})),
|
|
3388
|
+
hint: chalk8.gray("space select \u2022 enter confirm"),
|
|
3389
|
+
instructions: false
|
|
3390
|
+
}, promptConfig);
|
|
3391
|
+
answers.testFrameworks = testFrameworkResponse.testFrameworks || [];
|
|
3392
|
+
const coverageResponse = await prompts4({
|
|
3393
|
+
type: "number",
|
|
3394
|
+
name: "coverage",
|
|
3395
|
+
message: chalk8.white("Target code coverage (%):"),
|
|
3396
|
+
initial: 80,
|
|
3397
|
+
min: 0,
|
|
3398
|
+
max: 100
|
|
3399
|
+
}, promptConfig);
|
|
3400
|
+
answers.coverageTarget = coverageResponse.coverage ?? 80;
|
|
3401
|
+
const testNotesResponse = await prompts4({
|
|
3402
|
+
type: "text",
|
|
3403
|
+
name: "testNotes",
|
|
3404
|
+
message: chalk8.white("Testing notes (optional):"),
|
|
3405
|
+
hint: chalk8.gray("e.g., run e2e on main only, use msw for mocking")
|
|
3406
|
+
}, promptConfig);
|
|
3407
|
+
answers.testNotes = testNotesResponse.testNotes || "";
|
|
3408
|
+
}
|
|
3409
|
+
if (canAccessTier(userTier, "advanced")) {
|
|
3410
|
+
const staticStep = getCurrentStep("static");
|
|
3411
|
+
showStep(currentStepNum, staticStep, userTier);
|
|
3412
|
+
console.log(chalk8.gray(" Generate additional project files:"));
|
|
3413
|
+
console.log();
|
|
3414
|
+
const staticFilesResponse = await prompts4({
|
|
3415
|
+
type: "multiselect",
|
|
3416
|
+
name: "staticFiles",
|
|
3417
|
+
message: chalk8.white("Include static files:"),
|
|
3418
|
+
choices: [
|
|
3419
|
+
{ title: "\u{1F4DD} .editorconfig", value: "editorconfig", description: chalk8.gray("Consistent code formatting") },
|
|
3420
|
+
{ title: "\u{1F91D} CONTRIBUTING.md", value: "contributing", description: chalk8.gray("Contributor guidelines") },
|
|
3421
|
+
{ title: "\u{1F4DC} CODE_OF_CONDUCT.md", value: "codeOfConduct", description: chalk8.gray("Community standards") },
|
|
3422
|
+
{ title: "\u{1F512} SECURITY.md", value: "security", description: chalk8.gray("Vulnerability reporting") },
|
|
3423
|
+
{ title: "\u{1F5FA}\uFE0F ROADMAP.md", value: "roadmap", description: chalk8.gray("Project roadmap") },
|
|
3424
|
+
{ title: "\u{1F4CB} .gitignore", value: "gitignore", description: chalk8.gray("Git ignore patterns"), selected: true }
|
|
3425
|
+
],
|
|
3426
|
+
hint: chalk8.gray("space select \u2022 enter confirm"),
|
|
3427
|
+
instructions: false
|
|
3428
|
+
}, promptConfig);
|
|
3429
|
+
answers.staticFiles = staticFilesResponse.staticFiles || [];
|
|
3430
|
+
if (answers.repoHost === "github" && answers.isPublic) {
|
|
3431
|
+
const fundingResponse = await prompts4({
|
|
3432
|
+
type: "toggle",
|
|
3433
|
+
name: "funding",
|
|
3434
|
+
message: chalk8.white("Generate FUNDING.yml for GitHub Sponsors?"),
|
|
3435
|
+
initial: false,
|
|
3436
|
+
active: "Yes",
|
|
3437
|
+
inactive: "No"
|
|
3438
|
+
}, promptConfig);
|
|
3439
|
+
answers.includeFunding = fundingResponse.funding || false;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
const extraStep = getCurrentStep("extra");
|
|
3443
|
+
showStep(currentStepNum, extraStep, userTier);
|
|
2479
3444
|
const personaResponse = await prompts4({
|
|
2480
3445
|
type: "select",
|
|
2481
3446
|
name: "persona",
|
|
2482
|
-
message: "AI persona:",
|
|
2483
|
-
choices:
|
|
3447
|
+
message: chalk8.white("AI assistant persona:"),
|
|
3448
|
+
choices: [
|
|
3449
|
+
{ title: "\u{1F9D1}\u200D\u{1F4BB} Full-Stack Developer", value: "fullstack", description: chalk8.gray("Complete application development") },
|
|
3450
|
+
{ title: "\u2699\uFE0F Backend Developer", value: "backend", description: chalk8.gray("APIs, databases, services") },
|
|
3451
|
+
{ title: "\u{1F3A8} Frontend Developer", value: "frontend", description: chalk8.gray("UI, components, styling") },
|
|
3452
|
+
{ title: "\u{1F680} DevOps Engineer", value: "devops", description: chalk8.gray("Infrastructure, CI/CD") },
|
|
3453
|
+
{ title: "\u{1F4CA} Data Engineer", value: "data", description: chalk8.gray("Pipelines, ETL, analytics") },
|
|
3454
|
+
{ title: "\u{1F512} Security Engineer", value: "security", description: chalk8.gray("Secure code, auditing") },
|
|
3455
|
+
{ title: "\u270F\uFE0F Custom...", value: "custom", description: chalk8.gray("Define your own") }
|
|
3456
|
+
],
|
|
2484
3457
|
initial: 0
|
|
2485
|
-
|
|
2486
|
-
});
|
|
3458
|
+
}, promptConfig);
|
|
2487
3459
|
if (personaResponse.persona === "custom") {
|
|
2488
3460
|
const customPersona = await prompts4({
|
|
2489
3461
|
type: "text",
|
|
2490
3462
|
name: "value",
|
|
2491
|
-
message: "Describe the custom persona:"
|
|
2492
|
-
|
|
3463
|
+
message: chalk8.white("Describe the custom persona:"),
|
|
3464
|
+
hint: chalk8.gray("e.g., 'ML engineer focused on PyTorch'")
|
|
3465
|
+
}, promptConfig);
|
|
2493
3466
|
answers.persona = customPersona.value || "fullstack";
|
|
2494
3467
|
} else {
|
|
2495
3468
|
answers.persona = personaResponse.persona || "fullstack";
|
|
2496
3469
|
}
|
|
2497
|
-
const
|
|
2498
|
-
type: "
|
|
2499
|
-
name: "
|
|
2500
|
-
message: "AI
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
console.log();
|
|
2508
|
-
console.log(chalk8.gray("Auto-detected commands:"));
|
|
2509
|
-
if (detected.commands.build) console.log(chalk8.gray(` Build: ${detected.commands.build}`));
|
|
2510
|
-
if (detected.commands.test) console.log(chalk8.gray(` Test: ${detected.commands.test}`));
|
|
2511
|
-
if (detected.commands.lint) console.log(chalk8.gray(` Lint: ${detected.commands.lint}`));
|
|
2512
|
-
if (detected.commands.dev) console.log(chalk8.gray(` Dev: ${detected.commands.dev}`));
|
|
2513
|
-
const editCommands = await prompts4({
|
|
2514
|
-
type: "confirm",
|
|
2515
|
-
name: "edit",
|
|
2516
|
-
message: "Edit commands?",
|
|
2517
|
-
initial: false
|
|
2518
|
-
});
|
|
2519
|
-
if (editCommands.edit) {
|
|
2520
|
-
const commandsResponse = await prompts4([
|
|
2521
|
-
{ type: "text", name: "build", message: "Build:", initial: detected.commands.build },
|
|
2522
|
-
{ type: "text", name: "test", message: "Test:", initial: detected.commands.test },
|
|
2523
|
-
{ type: "text", name: "lint", message: "Lint:", initial: detected.commands.lint },
|
|
2524
|
-
{ type: "text", name: "dev", message: "Dev:", initial: detected.commands.dev }
|
|
2525
|
-
]);
|
|
2526
|
-
answers.commands = commandsResponse;
|
|
2527
|
-
} else {
|
|
2528
|
-
answers.commands = detected.commands;
|
|
2529
|
-
}
|
|
2530
|
-
} else {
|
|
2531
|
-
answers.commands = {};
|
|
2532
|
-
}
|
|
3470
|
+
const extraNotesResponse = await prompts4({
|
|
3471
|
+
type: "text",
|
|
3472
|
+
name: "extraNotes",
|
|
3473
|
+
message: chalk8.white("Anything else AI should know? (optional):"),
|
|
3474
|
+
hint: chalk8.gray("Special requirements, gotchas, team conventions...")
|
|
3475
|
+
}, promptConfig);
|
|
3476
|
+
answers.extraNotes = extraNotesResponse.extraNotes || "";
|
|
3477
|
+
console.log();
|
|
3478
|
+
console.log(chalk8.green(" \u2705 All steps completed!"));
|
|
3479
|
+
console.log();
|
|
2533
3480
|
return {
|
|
2534
3481
|
name: answers.name,
|
|
2535
3482
|
description: answers.description,
|
|
@@ -2537,7 +3484,28 @@ async function runInteractiveWizard(options, detected) {
|
|
|
2537
3484
|
platforms: answers.platforms,
|
|
2538
3485
|
persona: answers.persona,
|
|
2539
3486
|
boundaries: answers.boundaries,
|
|
2540
|
-
commands: answers.commands
|
|
3487
|
+
commands: typeof answers.commands === "object" ? answers.commands : detected?.commands || {},
|
|
3488
|
+
// Extended config for Pro/Max users
|
|
3489
|
+
projectType: answers.projectType,
|
|
3490
|
+
repoHost: answers.repoHost,
|
|
3491
|
+
isPublic: answers.isPublic,
|
|
3492
|
+
license: answers.license,
|
|
3493
|
+
conventionalCommits: answers.conventionalCommits,
|
|
3494
|
+
namingConvention: answers.namingConvention,
|
|
3495
|
+
errorHandling: answers.errorHandling,
|
|
3496
|
+
styleNotes: answers.styleNotes,
|
|
3497
|
+
aiBehavior: answers.aiBehavior,
|
|
3498
|
+
importantFiles: answers.importantFiles,
|
|
3499
|
+
selfImprove: answers.selfImprove,
|
|
3500
|
+
boundaryNever: answers.boundaryNever,
|
|
3501
|
+
boundaryAsk: answers.boundaryAsk,
|
|
3502
|
+
testLevels: answers.testLevels,
|
|
3503
|
+
testFrameworks: answers.testFrameworks,
|
|
3504
|
+
coverageTarget: answers.coverageTarget,
|
|
3505
|
+
testNotes: answers.testNotes,
|
|
3506
|
+
staticFiles: answers.staticFiles,
|
|
3507
|
+
includeFunding: answers.includeFunding,
|
|
3508
|
+
extraNotes: answers.extraNotes
|
|
2541
3509
|
};
|
|
2542
3510
|
}
|
|
2543
3511
|
|
|
@@ -2604,7 +3572,7 @@ function handleApiError3(error) {
|
|
|
2604
3572
|
|
|
2605
3573
|
// src/commands/status.ts
|
|
2606
3574
|
import chalk10 from "chalk";
|
|
2607
|
-
import {
|
|
3575
|
+
import { readFile as readFile5, readdir, access as access4 } from "fs/promises";
|
|
2608
3576
|
import { join as join7 } from "path";
|
|
2609
3577
|
import { existsSync as existsSync4 } from "fs";
|
|
2610
3578
|
var CONFIG_FILES = [
|
|
@@ -2634,7 +3602,7 @@ async function statusCommand() {
|
|
|
2634
3602
|
const configPath = join7(cwd, ".lynxprompt/conf.yml");
|
|
2635
3603
|
if (existsSync4(configPath)) {
|
|
2636
3604
|
try {
|
|
2637
|
-
const content = await
|
|
3605
|
+
const content = await readFile5(configPath, "utf-8");
|
|
2638
3606
|
const { parse: parse5 } = await import("yaml");
|
|
2639
3607
|
const config2 = parse5(content);
|
|
2640
3608
|
if (config2?.exporters?.length > 0) {
|
|
@@ -2677,8 +3645,8 @@ async function statusCommand() {
|
|
|
2677
3645
|
for (const config2 of CONFIG_FILES) {
|
|
2678
3646
|
const filePath = join7(cwd, config2.path);
|
|
2679
3647
|
try {
|
|
2680
|
-
await
|
|
2681
|
-
const content = await
|
|
3648
|
+
await access4(filePath);
|
|
3649
|
+
const content = await readFile5(filePath, "utf-8");
|
|
2682
3650
|
const lines = content.split("\n").length;
|
|
2683
3651
|
const size = formatBytes(content.length);
|
|
2684
3652
|
foundAny = true;
|
|
@@ -2699,7 +3667,7 @@ async function statusCommand() {
|
|
|
2699
3667
|
const dirPath = join7(cwd, config2.path);
|
|
2700
3668
|
if (existsSync4(dirPath)) {
|
|
2701
3669
|
try {
|
|
2702
|
-
const files = await
|
|
3670
|
+
const files = await readdir(dirPath);
|
|
2703
3671
|
const ruleFiles = files.filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
2704
3672
|
if (ruleFiles.length > 0) {
|
|
2705
3673
|
foundAny = true;
|
|
@@ -2757,7 +3725,7 @@ function formatBytes(bytes) {
|
|
|
2757
3725
|
import chalk11 from "chalk";
|
|
2758
3726
|
import ora9 from "ora";
|
|
2759
3727
|
import prompts5 from "prompts";
|
|
2760
|
-
import { readFile as
|
|
3728
|
+
import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5, readdir as readdir2 } from "fs/promises";
|
|
2761
3729
|
import { join as join8, dirname as dirname5 } from "path";
|
|
2762
3730
|
import { existsSync as existsSync5 } from "fs";
|
|
2763
3731
|
import * as yaml3 from "yaml";
|
|
@@ -2778,7 +3746,7 @@ async function syncCommand(options = {}) {
|
|
|
2778
3746
|
const spinner = ora9("Loading configuration...").start();
|
|
2779
3747
|
let config2;
|
|
2780
3748
|
try {
|
|
2781
|
-
const configContent = await
|
|
3749
|
+
const configContent = await readFile6(configPath, "utf-8");
|
|
2782
3750
|
config2 = yaml3.parse(configContent);
|
|
2783
3751
|
spinner.succeed("Configuration loaded");
|
|
2784
3752
|
} catch (error) {
|
|
@@ -2875,12 +3843,12 @@ async function syncCommand(options = {}) {
|
|
|
2875
3843
|
async function loadRules(rulesPath) {
|
|
2876
3844
|
const files = [];
|
|
2877
3845
|
try {
|
|
2878
|
-
const entries = await
|
|
3846
|
+
const entries = await readdir2(rulesPath, { withFileTypes: true });
|
|
2879
3847
|
for (const entry of entries) {
|
|
2880
3848
|
if (!entry.isFile()) continue;
|
|
2881
3849
|
if (!entry.name.endsWith(".md")) continue;
|
|
2882
3850
|
const filePath = join8(rulesPath, entry.name);
|
|
2883
|
-
const content = await
|
|
3851
|
+
const content = await readFile6(filePath, "utf-8");
|
|
2884
3852
|
if (content.trim()) {
|
|
2885
3853
|
files.push({ name: entry.name, content: content.trim() });
|
|
2886
3854
|
}
|
|
@@ -2930,7 +3898,7 @@ function formatForAgent(agent, content) {
|
|
|
2930
3898
|
return content;
|
|
2931
3899
|
}
|
|
2932
3900
|
}
|
|
2933
|
-
function formatAsMdc(content,
|
|
3901
|
+
function formatAsMdc(content, _agent) {
|
|
2934
3902
|
const frontmatter = yaml3.stringify({
|
|
2935
3903
|
description: "LynxPrompt rules - AI coding guidelines",
|
|
2936
3904
|
globs: ["**/*"],
|
|
@@ -2941,7 +3909,7 @@ ${frontmatter}---
|
|
|
2941
3909
|
|
|
2942
3910
|
${content}`;
|
|
2943
3911
|
}
|
|
2944
|
-
function formatAsMarkdown(content,
|
|
3912
|
+
function formatAsMarkdown(content, _agent) {
|
|
2945
3913
|
const header = `# AI Coding Rules
|
|
2946
3914
|
|
|
2947
3915
|
> Generated by [LynxPrompt](https://lynxprompt.com)
|
|
@@ -2949,7 +3917,7 @@ function formatAsMarkdown(content, agent) {
|
|
|
2949
3917
|
`;
|
|
2950
3918
|
return header + content;
|
|
2951
3919
|
}
|
|
2952
|
-
function formatAsJson(content,
|
|
3920
|
+
function formatAsJson(content, _agent) {
|
|
2953
3921
|
return JSON.stringify(
|
|
2954
3922
|
{
|
|
2955
3923
|
$schema: "https://lynxprompt.com/schemas/rules.json",
|
|
@@ -2968,7 +3936,7 @@ function formatAsJson(content, agent) {
|
|
|
2968
3936
|
// src/commands/agents.ts
|
|
2969
3937
|
import chalk12 from "chalk";
|
|
2970
3938
|
import prompts6 from "prompts";
|
|
2971
|
-
import { readFile as
|
|
3939
|
+
import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
2972
3940
|
import { join as join9 } from "path";
|
|
2973
3941
|
import { existsSync as existsSync6 } from "fs";
|
|
2974
3942
|
import * as yaml4 from "yaml";
|
|
@@ -3170,7 +4138,7 @@ async function loadConfig() {
|
|
|
3170
4138
|
return null;
|
|
3171
4139
|
}
|
|
3172
4140
|
try {
|
|
3173
|
-
const content = await
|
|
4141
|
+
const content = await readFile7(configPath, "utf-8");
|
|
3174
4142
|
return yaml4.parse(content);
|
|
3175
4143
|
} catch {
|
|
3176
4144
|
return null;
|
|
@@ -3186,7 +4154,7 @@ async function saveConfig(config2) {
|
|
|
3186
4154
|
// src/commands/check.ts
|
|
3187
4155
|
import chalk13 from "chalk";
|
|
3188
4156
|
import ora10 from "ora";
|
|
3189
|
-
import { readFile as
|
|
4157
|
+
import { readFile as readFile8, readdir as readdir3, stat } from "fs/promises";
|
|
3190
4158
|
import { join as join10 } from "path";
|
|
3191
4159
|
import { existsSync as existsSync7 } from "fs";
|
|
3192
4160
|
import * as yaml5 from "yaml";
|
|
@@ -3252,7 +4220,7 @@ async function validateLynxPromptConfig(cwd) {
|
|
|
3252
4220
|
return { errors, warnings };
|
|
3253
4221
|
}
|
|
3254
4222
|
try {
|
|
3255
|
-
const content = await
|
|
4223
|
+
const content = await readFile8(configPath, "utf-8");
|
|
3256
4224
|
const config2 = yaml5.parse(content);
|
|
3257
4225
|
if (!config2.version) {
|
|
3258
4226
|
warnings.push(".lynxprompt/conf.yml: Missing 'version' field");
|
|
@@ -3325,7 +4293,7 @@ async function checkCommand(options = {}) {
|
|
|
3325
4293
|
if (existsSync7(filePath)) {
|
|
3326
4294
|
result.files.push(file.path);
|
|
3327
4295
|
try {
|
|
3328
|
-
const content = await
|
|
4296
|
+
const content = await readFile8(filePath, "utf-8");
|
|
3329
4297
|
const validation = validateMarkdown(content, file.path);
|
|
3330
4298
|
result.errors.push(...validation.errors);
|
|
3331
4299
|
result.warnings.push(...validation.warnings);
|
|
@@ -3338,13 +4306,13 @@ async function checkCommand(options = {}) {
|
|
|
3338
4306
|
const dirPath = join10(cwd, dir.path);
|
|
3339
4307
|
if (existsSync7(dirPath)) {
|
|
3340
4308
|
try {
|
|
3341
|
-
const files = await
|
|
4309
|
+
const files = await readdir3(dirPath);
|
|
3342
4310
|
for (const file of files) {
|
|
3343
4311
|
const filePath = join10(dirPath, file);
|
|
3344
|
-
const fileStat = await
|
|
4312
|
+
const fileStat = await stat(filePath);
|
|
3345
4313
|
if (fileStat.isFile()) {
|
|
3346
4314
|
result.files.push(`${dir.path}/${file}`);
|
|
3347
|
-
const content = await
|
|
4315
|
+
const content = await readFile8(filePath, "utf-8");
|
|
3348
4316
|
if (file.endsWith(".mdc")) {
|
|
3349
4317
|
const validation = validateMdc(content, `${dir.path}/${file}`);
|
|
3350
4318
|
result.errors.push(...validation.errors);
|
|
@@ -3420,7 +4388,7 @@ async function checkCommand(options = {}) {
|
|
|
3420
4388
|
// src/commands/diff.ts
|
|
3421
4389
|
import chalk14 from "chalk";
|
|
3422
4390
|
import ora11 from "ora";
|
|
3423
|
-
import { readFile as
|
|
4391
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3424
4392
|
import { join as join11 } from "path";
|
|
3425
4393
|
import { existsSync as existsSync8 } from "fs";
|
|
3426
4394
|
function computeDiff(oldText, newText) {
|
|
@@ -3484,7 +4452,6 @@ function longestCommonSubsequence(a, b) {
|
|
|
3484
4452
|
function formatDiff(diff, contextLines = 3) {
|
|
3485
4453
|
const output = [];
|
|
3486
4454
|
let lastPrintedIndex = -1;
|
|
3487
|
-
let inHunk = false;
|
|
3488
4455
|
const changeIndices = diff.map((d, i) => d.type !== "same" ? i : -1).filter((i) => i !== -1);
|
|
3489
4456
|
if (changeIndices.length === 0) {
|
|
3490
4457
|
return chalk14.gray(" (no changes)");
|
|
@@ -3515,7 +4482,7 @@ function getDiffStats(diff) {
|
|
|
3515
4482
|
unchanged: diff.filter((d) => d.type === "same").length
|
|
3516
4483
|
};
|
|
3517
4484
|
}
|
|
3518
|
-
async function diffCommand(
|
|
4485
|
+
async function diffCommand(fileOrId, options = {}) {
|
|
3519
4486
|
console.log();
|
|
3520
4487
|
console.log(chalk14.cyan("\u{1F431} LynxPrompt Diff"));
|
|
3521
4488
|
console.log();
|
|
@@ -3524,14 +4491,90 @@ async function diffCommand(blueprintId, options = {}) {
|
|
|
3524
4491
|
await diffLocal(cwd);
|
|
3525
4492
|
return;
|
|
3526
4493
|
}
|
|
3527
|
-
|
|
3528
|
-
|
|
4494
|
+
const trackedFiles = await checkSyncStatus(cwd);
|
|
4495
|
+
if (fileOrId) {
|
|
4496
|
+
const tracked = await findBlueprintByFile(cwd, fileOrId);
|
|
4497
|
+
if (tracked) {
|
|
4498
|
+
await diffFileWithBlueprint(cwd, fileOrId, tracked.id);
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
await diffWithBlueprintId(cwd, fileOrId);
|
|
4502
|
+
return;
|
|
4503
|
+
}
|
|
4504
|
+
if (trackedFiles.length === 0) {
|
|
4505
|
+
console.log(chalk14.yellow("No tracked blueprints found."));
|
|
4506
|
+
console.log();
|
|
4507
|
+
console.log(chalk14.gray("To track a blueprint and compare changes:"));
|
|
4508
|
+
console.log(chalk14.gray(" 1. Pull a blueprint: lynxp pull <blueprint-id>"));
|
|
4509
|
+
console.log(chalk14.gray(" 2. Or link an existing file: lynxp link"));
|
|
3529
4510
|
console.log();
|
|
3530
|
-
console.log(chalk14.gray("
|
|
3531
|
-
console.log(chalk14.gray(" lynxp diff
|
|
3532
|
-
console.log(chalk14.gray(" lynxp diff --local Compare .lynxprompt/rules/ with exports"));
|
|
4511
|
+
console.log(chalk14.gray("Other options:"));
|
|
4512
|
+
console.log(chalk14.gray(" lynxp diff --local Compare .lynxprompt/rules/ with exported files"));
|
|
3533
4513
|
return;
|
|
3534
4514
|
}
|
|
4515
|
+
let hasChanges = false;
|
|
4516
|
+
for (const { blueprint, localModified, fileExists: fileExists2 } of trackedFiles) {
|
|
4517
|
+
if (!fileExists2) {
|
|
4518
|
+
console.log(chalk14.red(`\u2717 ${blueprint.file} - file not found`));
|
|
4519
|
+
continue;
|
|
4520
|
+
}
|
|
4521
|
+
console.log(chalk14.cyan(`\u{1F4C4} ${blueprint.file}`));
|
|
4522
|
+
console.log(chalk14.gray(` Linked to: ${blueprint.name} (${blueprint.id})`));
|
|
4523
|
+
if (localModified) {
|
|
4524
|
+
hasChanges = true;
|
|
4525
|
+
await diffFileWithBlueprint(cwd, blueprint.file, blueprint.id, true);
|
|
4526
|
+
} else {
|
|
4527
|
+
console.log(chalk14.green(" \u2713 In sync with cloud"));
|
|
4528
|
+
}
|
|
4529
|
+
console.log();
|
|
4530
|
+
}
|
|
4531
|
+
if (!hasChanges) {
|
|
4532
|
+
console.log(chalk14.green("\u2713 All tracked files are in sync with their cloud blueprints!"));
|
|
4533
|
+
} else {
|
|
4534
|
+
console.log(chalk14.gray("To push local changes: lynxp push"));
|
|
4535
|
+
console.log(chalk14.gray("To pull cloud changes: lynxp pull <id>"));
|
|
4536
|
+
}
|
|
4537
|
+
console.log();
|
|
4538
|
+
}
|
|
4539
|
+
async function diffFileWithBlueprint(cwd, file, blueprintId, compact = false) {
|
|
4540
|
+
const filePath = join11(cwd, file);
|
|
4541
|
+
if (!existsSync8(filePath)) {
|
|
4542
|
+
console.log(chalk14.red(`\u2717 File not found: ${file}`));
|
|
4543
|
+
return;
|
|
4544
|
+
}
|
|
4545
|
+
const spinner = compact ? null : ora11("Fetching blueprint...").start();
|
|
4546
|
+
try {
|
|
4547
|
+
const { blueprint } = await api.getBlueprint(blueprintId);
|
|
4548
|
+
spinner?.stop();
|
|
4549
|
+
if (!blueprint || !blueprint.content) {
|
|
4550
|
+
console.log(chalk14.red(`\u2717 Blueprint has no content`));
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
const localContent = await readFile9(filePath, "utf-8");
|
|
4554
|
+
const diff = computeDiff(blueprint.content, localContent);
|
|
4555
|
+
const stats = getDiffStats(diff);
|
|
4556
|
+
if (stats.added === 0 && stats.removed === 0) {
|
|
4557
|
+
if (!compact) {
|
|
4558
|
+
console.log(chalk14.green("\u2713 Files are identical!"));
|
|
4559
|
+
}
|
|
4560
|
+
} else {
|
|
4561
|
+
if (!compact) {
|
|
4562
|
+
console.log(chalk14.gray("Changes (cloud \u2192 local):"));
|
|
4563
|
+
console.log();
|
|
4564
|
+
}
|
|
4565
|
+
console.log(formatDiff(diff));
|
|
4566
|
+
console.log(chalk14.gray(` ${chalk14.green(`+${stats.added}`)} ${chalk14.red(`-${stats.removed}`)} lines`));
|
|
4567
|
+
}
|
|
4568
|
+
} catch (error) {
|
|
4569
|
+
spinner?.stop();
|
|
4570
|
+
if (error instanceof ApiRequestError) {
|
|
4571
|
+
console.log(chalk14.red(`\u2717 Could not fetch blueprint: ${error.message}`));
|
|
4572
|
+
} else {
|
|
4573
|
+
console.log(chalk14.red("\u2717 Failed to compare"));
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
async function diffWithBlueprintId(cwd, blueprintId) {
|
|
3535
4578
|
if (!isAuthenticated()) {
|
|
3536
4579
|
console.log(chalk14.yellow("\u26A0 Not logged in. Some blueprints may not be accessible."));
|
|
3537
4580
|
console.log(chalk14.gray("Run 'lynxp login' to authenticate."));
|
|
@@ -3563,7 +4606,7 @@ async function diffCommand(blueprintId, options = {}) {
|
|
|
3563
4606
|
const fullPath = join11(cwd, path2);
|
|
3564
4607
|
if (existsSync8(fullPath)) {
|
|
3565
4608
|
try {
|
|
3566
|
-
localContent = await
|
|
4609
|
+
localContent = await readFile9(fullPath, "utf-8");
|
|
3567
4610
|
localPath = path2;
|
|
3568
4611
|
break;
|
|
3569
4612
|
} catch {
|
|
@@ -3625,7 +4668,7 @@ async function diffLocal(cwd) {
|
|
|
3625
4668
|
}
|
|
3626
4669
|
let rulesContent;
|
|
3627
4670
|
try {
|
|
3628
|
-
rulesContent = await
|
|
4671
|
+
rulesContent = await readFile9(rulesPath, "utf-8");
|
|
3629
4672
|
} catch {
|
|
3630
4673
|
console.log(chalk14.red("\u2717 Could not read .lynxprompt/rules/agents.md"));
|
|
3631
4674
|
return;
|
|
@@ -3639,7 +4682,7 @@ async function diffLocal(cwd) {
|
|
|
3639
4682
|
const filePath = join11(cwd, file.path);
|
|
3640
4683
|
if (existsSync8(filePath)) {
|
|
3641
4684
|
try {
|
|
3642
|
-
const exportedContent = await
|
|
4685
|
+
const exportedContent = await readFile9(filePath, "utf-8");
|
|
3643
4686
|
let compareContent = exportedContent;
|
|
3644
4687
|
if (file.path.endsWith(".mdc")) {
|
|
3645
4688
|
const frontmatterEnd = exportedContent.indexOf("---", 3);
|
|
@@ -3691,33 +4734,53 @@ function getSourceFromVisibility2(visibility) {
|
|
|
3691
4734
|
return "marketplace";
|
|
3692
4735
|
}
|
|
3693
4736
|
}
|
|
3694
|
-
async function linkCommand(
|
|
4737
|
+
async function linkCommand(fileArg, blueprintIdArg, options = {}) {
|
|
3695
4738
|
const cwd = process.cwd();
|
|
3696
4739
|
if (options.list) {
|
|
3697
4740
|
await listTrackedBlueprints(cwd);
|
|
3698
4741
|
return;
|
|
3699
4742
|
}
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
4743
|
+
console.log();
|
|
4744
|
+
console.log(chalk15.cyan("\u{1F431} Link File to Blueprint"));
|
|
4745
|
+
console.log();
|
|
4746
|
+
let file;
|
|
4747
|
+
let blueprintId = blueprintIdArg;
|
|
4748
|
+
if (!fileArg) {
|
|
4749
|
+
const configFiles = [
|
|
4750
|
+
"AGENTS.md",
|
|
4751
|
+
"CLAUDE.md",
|
|
4752
|
+
".cursor/rules/project.mdc",
|
|
4753
|
+
".github/copilot-instructions.md",
|
|
4754
|
+
".windsurfrules",
|
|
4755
|
+
".zed/instructions.md",
|
|
4756
|
+
".clinerules"
|
|
4757
|
+
];
|
|
4758
|
+
const foundFiles = configFiles.filter((f) => existsSync9(join12(cwd, f)));
|
|
4759
|
+
if (foundFiles.length === 0) {
|
|
4760
|
+
console.log(chalk15.yellow("No AI configuration files found in this directory."));
|
|
4761
|
+
console.log();
|
|
4762
|
+
console.log(chalk15.gray("Create one first:"));
|
|
4763
|
+
console.log(chalk15.gray(" lynxp wizard Generate a new config file"));
|
|
4764
|
+
console.log(chalk15.gray(" lynxp pull <id> Download from marketplace"));
|
|
4765
|
+
return;
|
|
4766
|
+
}
|
|
4767
|
+
const { selectedFile } = await prompts7({
|
|
4768
|
+
type: "select",
|
|
4769
|
+
name: "selectedFile",
|
|
4770
|
+
message: "Which file do you want to link to a cloud blueprint?",
|
|
4771
|
+
choices: foundFiles.map((f) => ({
|
|
4772
|
+
title: f,
|
|
4773
|
+
value: f,
|
|
4774
|
+
description: "Local file exists"
|
|
4775
|
+
}))
|
|
4776
|
+
});
|
|
4777
|
+
if (!selectedFile) {
|
|
4778
|
+
console.log(chalk15.gray("Cancelled."));
|
|
4779
|
+
return;
|
|
4780
|
+
}
|
|
4781
|
+
file = selectedFile;
|
|
4782
|
+
} else {
|
|
4783
|
+
file = fileArg;
|
|
3721
4784
|
}
|
|
3722
4785
|
const filePath = join12(cwd, file);
|
|
3723
4786
|
if (!existsSync9(filePath)) {
|
|
@@ -3726,7 +4789,9 @@ async function linkCommand(file, blueprintId, options = {}) {
|
|
|
3726
4789
|
}
|
|
3727
4790
|
const existing = await findBlueprintByFile(cwd, file);
|
|
3728
4791
|
if (existing) {
|
|
3729
|
-
console.log(chalk15.yellow(
|
|
4792
|
+
console.log(chalk15.yellow(`This file is already linked to: ${existing.name}`));
|
|
4793
|
+
console.log(chalk15.gray(` ID: ${existing.id}`));
|
|
4794
|
+
console.log();
|
|
3730
4795
|
const { proceed } = await prompts7({
|
|
3731
4796
|
type: "confirm",
|
|
3732
4797
|
name: "proceed",
|
|
@@ -3738,11 +4803,120 @@ async function linkCommand(file, blueprintId, options = {}) {
|
|
|
3738
4803
|
return;
|
|
3739
4804
|
}
|
|
3740
4805
|
}
|
|
3741
|
-
if (!
|
|
3742
|
-
|
|
3743
|
-
chalk15.yellow("
|
|
3744
|
-
|
|
3745
|
-
|
|
4806
|
+
if (!blueprintId) {
|
|
4807
|
+
if (!isAuthenticated()) {
|
|
4808
|
+
console.log(chalk15.yellow("You need to login to access your blueprints."));
|
|
4809
|
+
const { doLogin } = await prompts7({
|
|
4810
|
+
type: "confirm",
|
|
4811
|
+
name: "doLogin",
|
|
4812
|
+
message: "Login now?",
|
|
4813
|
+
initial: true
|
|
4814
|
+
});
|
|
4815
|
+
if (doLogin) {
|
|
4816
|
+
console.log(chalk15.gray("Run 'lynxp login' in another terminal, then come back here."));
|
|
4817
|
+
return;
|
|
4818
|
+
}
|
|
4819
|
+
console.log(chalk15.gray("Cancelled."));
|
|
4820
|
+
return;
|
|
4821
|
+
}
|
|
4822
|
+
const { searchMethod } = await prompts7({
|
|
4823
|
+
type: "select",
|
|
4824
|
+
name: "searchMethod",
|
|
4825
|
+
message: "How do you want to find the blueprint?",
|
|
4826
|
+
choices: [
|
|
4827
|
+
{ title: "\u{1F4CB} From my blueprints", value: "list" },
|
|
4828
|
+
{ title: "\u{1F50D} Search marketplace", value: "search" },
|
|
4829
|
+
{ title: "\u{1F522} Enter ID directly", value: "manual" }
|
|
4830
|
+
]
|
|
4831
|
+
});
|
|
4832
|
+
if (!searchMethod) {
|
|
4833
|
+
console.log(chalk15.gray("Cancelled."));
|
|
4834
|
+
return;
|
|
4835
|
+
}
|
|
4836
|
+
if (searchMethod === "list") {
|
|
4837
|
+
const spinner2 = ora12("Fetching your blueprints...").start();
|
|
4838
|
+
try {
|
|
4839
|
+
const { blueprints } = await api.listBlueprints();
|
|
4840
|
+
spinner2.stop();
|
|
4841
|
+
if (!blueprints || blueprints.length === 0) {
|
|
4842
|
+
console.log(chalk15.yellow("You don't have any blueprints yet."));
|
|
4843
|
+
console.log(chalk15.gray("Create one with 'lynxp push' or search the marketplace."));
|
|
4844
|
+
return;
|
|
4845
|
+
}
|
|
4846
|
+
const { selected } = await prompts7({
|
|
4847
|
+
type: "select",
|
|
4848
|
+
name: "selected",
|
|
4849
|
+
message: "Select a blueprint:",
|
|
4850
|
+
choices: blueprints.map((b) => ({
|
|
4851
|
+
title: b.name,
|
|
4852
|
+
value: b.id,
|
|
4853
|
+
description: b.description?.substring(0, 50) || ""
|
|
4854
|
+
}))
|
|
4855
|
+
});
|
|
4856
|
+
if (!selected) {
|
|
4857
|
+
console.log(chalk15.gray("Cancelled."));
|
|
4858
|
+
return;
|
|
4859
|
+
}
|
|
4860
|
+
blueprintId = selected;
|
|
4861
|
+
} catch {
|
|
4862
|
+
spinner2.stop();
|
|
4863
|
+
console.log(chalk15.red("\u2717 Could not fetch blueprints"));
|
|
4864
|
+
return;
|
|
4865
|
+
}
|
|
4866
|
+
} else if (searchMethod === "search") {
|
|
4867
|
+
const { query } = await prompts7({
|
|
4868
|
+
type: "text",
|
|
4869
|
+
name: "query",
|
|
4870
|
+
message: "Search for:"
|
|
4871
|
+
});
|
|
4872
|
+
if (!query) {
|
|
4873
|
+
console.log(chalk15.gray("Cancelled."));
|
|
4874
|
+
return;
|
|
4875
|
+
}
|
|
4876
|
+
const spinner2 = ora12(`Searching for "${query}"...`).start();
|
|
4877
|
+
try {
|
|
4878
|
+
const results = await api.searchBlueprints(query, 10);
|
|
4879
|
+
spinner2.stop();
|
|
4880
|
+
if (!results.templates || results.templates.length === 0) {
|
|
4881
|
+
console.log(chalk15.yellow(`No blueprints found for "${query}"`));
|
|
4882
|
+
return;
|
|
4883
|
+
}
|
|
4884
|
+
const { selected } = await prompts7({
|
|
4885
|
+
type: "select",
|
|
4886
|
+
name: "selected",
|
|
4887
|
+
message: "Select a blueprint:",
|
|
4888
|
+
choices: results.templates.map((b) => ({
|
|
4889
|
+
title: `${b.name} (\u2605 ${b.likes})`,
|
|
4890
|
+
value: b.id,
|
|
4891
|
+
description: b.author ? `by ${b.author}` : ""
|
|
4892
|
+
}))
|
|
4893
|
+
});
|
|
4894
|
+
if (!selected) {
|
|
4895
|
+
console.log(chalk15.gray("Cancelled."));
|
|
4896
|
+
return;
|
|
4897
|
+
}
|
|
4898
|
+
blueprintId = selected;
|
|
4899
|
+
} catch {
|
|
4900
|
+
spinner2.stop();
|
|
4901
|
+
console.log(chalk15.red("\u2717 Search failed"));
|
|
4902
|
+
return;
|
|
4903
|
+
}
|
|
4904
|
+
} else {
|
|
4905
|
+
const { manualId } = await prompts7({
|
|
4906
|
+
type: "text",
|
|
4907
|
+
name: "manualId",
|
|
4908
|
+
message: "Enter blueprint ID:"
|
|
4909
|
+
});
|
|
4910
|
+
if (!manualId) {
|
|
4911
|
+
console.log(chalk15.gray("Cancelled."));
|
|
4912
|
+
return;
|
|
4913
|
+
}
|
|
4914
|
+
blueprintId = manualId;
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4917
|
+
if (!blueprintId) {
|
|
4918
|
+
console.log(chalk15.red("\u2717 No blueprint ID provided."));
|
|
4919
|
+
return;
|
|
3746
4920
|
}
|
|
3747
4921
|
const spinner = ora12(`Fetching blueprint ${chalk15.cyan(blueprintId)}...`).start();
|
|
3748
4922
|
try {
|
|
@@ -3778,11 +4952,10 @@ async function linkCommand(file, blueprintId, options = {}) {
|
|
|
3778
4952
|
console.log(chalk15.green(`\u2705 Linked: ${file} \u2192 ${blueprint.id}`));
|
|
3779
4953
|
console.log();
|
|
3780
4954
|
console.log(chalk15.gray("Next steps:"));
|
|
3781
|
-
console.log(chalk15.gray(` \u2022 Run 'lynxp
|
|
3782
|
-
console.log(chalk15.gray(` \u2022 Run 'lynxp diff ${blueprintId}' to see differences`));
|
|
4955
|
+
console.log(chalk15.gray(` \u2022 Run 'lynxp diff' to see differences`));
|
|
3783
4956
|
console.log(chalk15.gray(` \u2022 Run 'lynxp status' to see all tracked blueprints`));
|
|
3784
4957
|
if (!isMarketplace) {
|
|
3785
|
-
console.log(chalk15.gray(` \u2022 Run 'lynxp push
|
|
4958
|
+
console.log(chalk15.gray(` \u2022 Run 'lynxp push' to push local changes to cloud`));
|
|
3786
4959
|
}
|
|
3787
4960
|
console.log();
|
|
3788
4961
|
} catch (error) {
|
|
@@ -3801,23 +4974,43 @@ async function linkCommand(file, blueprintId, options = {}) {
|
|
|
3801
4974
|
}
|
|
3802
4975
|
}
|
|
3803
4976
|
}
|
|
3804
|
-
async function unlinkCommand(
|
|
4977
|
+
async function unlinkCommand(fileArg) {
|
|
3805
4978
|
const cwd = process.cwd();
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
4979
|
+
console.log();
|
|
4980
|
+
console.log(chalk15.cyan("\u{1F431} Unlink File from Blueprint"));
|
|
4981
|
+
console.log();
|
|
4982
|
+
let file;
|
|
4983
|
+
if (!fileArg) {
|
|
4984
|
+
const status = await checkSyncStatus(cwd);
|
|
4985
|
+
if (status.length === 0) {
|
|
4986
|
+
console.log(chalk15.yellow("No files are currently linked to blueprints."));
|
|
4987
|
+
return;
|
|
4988
|
+
}
|
|
4989
|
+
const { selectedFile } = await prompts7({
|
|
4990
|
+
type: "select",
|
|
4991
|
+
name: "selectedFile",
|
|
4992
|
+
message: "Which file do you want to unlink?",
|
|
4993
|
+
choices: status.map(({ blueprint }) => ({
|
|
4994
|
+
title: blueprint.file,
|
|
4995
|
+
value: blueprint.file,
|
|
4996
|
+
description: `${blueprint.name} (${blueprint.source})`
|
|
4997
|
+
}))
|
|
4998
|
+
});
|
|
4999
|
+
if (!selectedFile) {
|
|
5000
|
+
console.log(chalk15.gray("Cancelled."));
|
|
5001
|
+
return;
|
|
5002
|
+
}
|
|
5003
|
+
file = selectedFile;
|
|
5004
|
+
} else {
|
|
5005
|
+
file = fileArg;
|
|
3812
5006
|
}
|
|
3813
5007
|
const tracked = await findBlueprintByFile(cwd, file);
|
|
3814
5008
|
if (!tracked) {
|
|
3815
|
-
console.log(chalk15.yellow(
|
|
5009
|
+
console.log(chalk15.yellow(`File is not linked to any blueprint: ${file}`));
|
|
3816
5010
|
return;
|
|
3817
5011
|
}
|
|
3818
|
-
console.log();
|
|
3819
|
-
console.log(chalk15.
|
|
3820
|
-
console.log(chalk15.gray(` Name: ${tracked.name}`));
|
|
5012
|
+
console.log(chalk15.gray(`Currently linked to: ${tracked.name}`));
|
|
5013
|
+
console.log(chalk15.gray(` ID: ${tracked.id}`));
|
|
3821
5014
|
console.log(chalk15.gray(` Source: ${tracked.source}`));
|
|
3822
5015
|
console.log();
|
|
3823
5016
|
const { confirm } = await prompts7({
|
|
@@ -3834,8 +5027,7 @@ async function unlinkCommand(file) {
|
|
|
3834
5027
|
if (success) {
|
|
3835
5028
|
console.log();
|
|
3836
5029
|
console.log(chalk15.green(`\u2705 Unlinked: ${file}`));
|
|
3837
|
-
console.log(chalk15.gray(" The file is now
|
|
3838
|
-
console.log(chalk15.gray(" It will no longer receive updates from the cloud blueprint."));
|
|
5030
|
+
console.log(chalk15.gray(" The file is now standalone. Changes won't sync with the cloud."));
|
|
3839
5031
|
console.log();
|
|
3840
5032
|
} else {
|
|
3841
5033
|
console.log(chalk15.red("\u2717 Failed to unlink file."));
|
|
@@ -3851,7 +5043,7 @@ async function listTrackedBlueprints(cwd) {
|
|
|
3851
5043
|
console.log();
|
|
3852
5044
|
console.log(chalk15.gray("To track a blueprint:"));
|
|
3853
5045
|
console.log(chalk15.gray(" lynxp pull <blueprint-id> Download and track a blueprint"));
|
|
3854
|
-
console.log(chalk15.gray(" lynxp link
|
|
5046
|
+
console.log(chalk15.gray(" lynxp link Link an existing file to a blueprint"));
|
|
3855
5047
|
return;
|
|
3856
5048
|
}
|
|
3857
5049
|
for (const { blueprint, localModified, fileExists: fileExists2 } of status) {
|
|
@@ -3888,8 +5080,8 @@ program.command("search <query>").description("Search public blueprints in the m
|
|
|
3888
5080
|
program.command("list").description("List your blueprints").option("-l, --limit <number>", "Number of results", "20").option("-v, --visibility <visibility>", "Filter: PRIVATE, TEAM, PUBLIC, or all").action(listCommand);
|
|
3889
5081
|
program.command("push [file]").description("Push local file to LynxPrompt cloud as a blueprint").option("-n, --name <name>", "Blueprint name").option("-d, --description <desc>", "Blueprint description").option("-v, --visibility <vis>", "Visibility: PRIVATE, TEAM, or PUBLIC", "PRIVATE").option("-t, --tags <tags>", "Tags (comma-separated)").option("-y, --yes", "Skip prompts").action(pushCommand);
|
|
3890
5082
|
program.command("link [file] [blueprint-id]").description("Link a local file to a cloud blueprint for tracking").option("--list", "List all tracked blueprints").action(linkCommand);
|
|
3891
|
-
program.command("unlink
|
|
3892
|
-
program.command("diff [
|
|
5083
|
+
program.command("unlink [file]").description("Disconnect a local file from its cloud blueprint").action(unlinkCommand);
|
|
5084
|
+
program.command("diff [file-or-id]").description("Compare tracked files with their cloud blueprints").option("--local", "Compare .lynxprompt/rules/ with exported files").action(diffCommand);
|
|
3893
5085
|
program.command("init").description("Initialize .lynxprompt/ for multi-editor sync (advanced)").option("-y, --yes", "Skip prompts and use defaults").option("-f, --force", "Re-initialize even if already initialized").action(initCommand);
|
|
3894
5086
|
program.command("sync").description("Sync .lynxprompt/rules/ to all configured agents").option("--dry-run", "Preview changes without writing files").option("-f, --force", "Skip prompts (for CI/automation)").action(syncCommand);
|
|
3895
5087
|
program.command("agents [action] [agent]").description("Manage AI agents (list, enable, disable, detect)").option("-i, --interactive", "Interactive agent selection").action(agentsCommand);
|