lynxprompt 0.3.0 → 0.3.1
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 +480 -201
- package/dist/index.js.map +1 -1
- package/package.json +16 -2
package/dist/index.js
CHANGED
|
@@ -1370,7 +1370,7 @@ function formatDetectionResults(result) {
|
|
|
1370
1370
|
}
|
|
1371
1371
|
|
|
1372
1372
|
// src/utils/detect.ts
|
|
1373
|
-
import { readFile as readFile3, access as
|
|
1373
|
+
import { readFile as readFile3, access as access2 } from "fs/promises";
|
|
1374
1374
|
import { join as join4 } from "path";
|
|
1375
1375
|
var JS_FRAMEWORK_PATTERNS = {
|
|
1376
1376
|
nextjs: ["next"],
|
|
@@ -1593,7 +1593,7 @@ async function detectProject(cwd) {
|
|
|
1593
1593
|
}
|
|
1594
1594
|
async function fileExists(path2) {
|
|
1595
1595
|
try {
|
|
1596
|
-
await
|
|
1596
|
+
await access2(path2);
|
|
1597
1597
|
return true;
|
|
1598
1598
|
} catch {
|
|
1599
1599
|
return false;
|
|
@@ -1964,7 +1964,7 @@ async function initCommand(options) {
|
|
|
1964
1964
|
import chalk8 from "chalk";
|
|
1965
1965
|
import prompts4 from "prompts";
|
|
1966
1966
|
import ora7 from "ora";
|
|
1967
|
-
import { writeFile as writeFile4, mkdir as mkdir4, access as
|
|
1967
|
+
import { writeFile as writeFile4, mkdir as mkdir4, access as access3 } from "fs/promises";
|
|
1968
1968
|
import { join as join6, dirname as dirname4 } from "path";
|
|
1969
1969
|
|
|
1970
1970
|
// src/utils/generator.ts
|
|
@@ -2245,101 +2245,137 @@ function generateFileContent(options, platform) {
|
|
|
2245
2245
|
// src/commands/wizard.ts
|
|
2246
2246
|
var OUTPUT_FORMATS = [
|
|
2247
2247
|
{
|
|
2248
|
-
title: "AGENTS.md
|
|
2248
|
+
title: "\u{1F310} AGENTS.md",
|
|
2249
2249
|
value: "agents",
|
|
2250
|
-
description: "
|
|
2250
|
+
description: "Universal format - Claude, Copilot, Aider, & more",
|
|
2251
2251
|
recommended: true
|
|
2252
2252
|
},
|
|
2253
2253
|
{
|
|
2254
|
-
title: "Cursor
|
|
2254
|
+
title: "\u{1F5B1}\uFE0F Cursor",
|
|
2255
2255
|
value: "cursor",
|
|
2256
|
-
description: "
|
|
2256
|
+
description: ".cursor/rules/ with MDC format"
|
|
2257
2257
|
},
|
|
2258
2258
|
{
|
|
2259
|
-
title: "
|
|
2259
|
+
title: "\u{1F30A} Windsurf",
|
|
2260
|
+
value: "windsurf",
|
|
2261
|
+
description: ".windsurfrules configuration"
|
|
2262
|
+
},
|
|
2263
|
+
{
|
|
2264
|
+
title: "\u{1F916} Claude Code",
|
|
2265
|
+
value: "claude",
|
|
2266
|
+
description: "CLAUDE.md for Claude AI"
|
|
2267
|
+
},
|
|
2268
|
+
{
|
|
2269
|
+
title: "\u{1F4E6} Multiple",
|
|
2260
2270
|
value: "multiple",
|
|
2261
|
-
description: "
|
|
2271
|
+
description: "Generate for multiple AI editors"
|
|
2262
2272
|
}
|
|
2263
2273
|
];
|
|
2264
2274
|
var TECH_STACKS = [
|
|
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" }
|
|
2275
|
+
{ title: "\u{1F537} TypeScript", value: "typescript" },
|
|
2276
|
+
{ title: "\u{1F7E1} JavaScript", value: "javascript" },
|
|
2277
|
+
{ title: "\u{1F40D} Python", value: "python" },
|
|
2278
|
+
{ title: "\u{1F535} Go", value: "go" },
|
|
2279
|
+
{ title: "\u{1F980} Rust", value: "rust" },
|
|
2280
|
+
{ title: "\u2615 Java", value: "java" },
|
|
2281
|
+
{ title: "\u{1F49C} C#/.NET", value: "csharp" },
|
|
2282
|
+
{ title: "\u{1F48E} Ruby", value: "ruby" },
|
|
2283
|
+
{ title: "\u{1F418} PHP", value: "php" },
|
|
2284
|
+
{ title: "\u{1F34E} Swift", value: "swift" }
|
|
2275
2285
|
];
|
|
2276
2286
|
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" }
|
|
2287
|
+
{ title: "\u269B\uFE0F React", value: "react" },
|
|
2288
|
+
{ title: "\u25B2 Next.js", value: "nextjs" },
|
|
2289
|
+
{ title: "\u{1F49A} Vue.js", value: "vue" },
|
|
2290
|
+
{ title: "\u{1F170}\uFE0F Angular", value: "angular" },
|
|
2291
|
+
{ title: "\u{1F525} Svelte", value: "svelte" },
|
|
2292
|
+
{ title: "\u{1F682} Express", value: "express" },
|
|
2293
|
+
{ title: "\u26A1 FastAPI", value: "fastapi" },
|
|
2294
|
+
{ title: "\u{1F3B8} Django", value: "django" },
|
|
2295
|
+
{ title: "\u{1F9EA} Flask", value: "flask" },
|
|
2296
|
+
{ title: "\u{1F343} Spring", value: "spring" },
|
|
2297
|
+
{ title: "\u{1F48E} Rails", value: "rails" },
|
|
2298
|
+
{ title: "\u{1F534} Laravel", value: "laravel" },
|
|
2299
|
+
{ title: "\u{1F3D7}\uFE0F NestJS", value: "nestjs" },
|
|
2300
|
+
{ title: "\u26A1 Vite", value: "vite" },
|
|
2301
|
+
{ title: "\u{1F4F1} React Native", value: "react-native" }
|
|
2289
2302
|
];
|
|
2290
2303
|
var PLATFORMS = [
|
|
2291
|
-
{ title: "AGENTS.md (Universal)", value: "agents", filename: "AGENTS.md" },
|
|
2292
|
-
{ title: "Cursor
|
|
2293
|
-
{ title: "Claude Code
|
|
2294
|
-
{ title: "GitHub Copilot", value: "copilot", filename: ".github/copilot-instructions.md" },
|
|
2295
|
-
{ title: "Windsurf
|
|
2296
|
-
{ title: "Zed", value: "zed", filename: ".zed/instructions.md" }
|
|
2304
|
+
{ title: "\u{1F310} AGENTS.md (Universal)", value: "agents", filename: "AGENTS.md" },
|
|
2305
|
+
{ title: "\u{1F5B1}\uFE0F Cursor", value: "cursor", filename: ".cursor/rules/project.mdc" },
|
|
2306
|
+
{ title: "\u{1F916} Claude Code", value: "claude", filename: "CLAUDE.md" },
|
|
2307
|
+
{ title: "\u{1F419} GitHub Copilot", value: "copilot", filename: ".github/copilot-instructions.md" },
|
|
2308
|
+
{ title: "\u{1F30A} Windsurf", value: "windsurf", filename: ".windsurfrules" },
|
|
2309
|
+
{ title: "\u26A1 Zed", value: "zed", filename: ".zed/instructions.md" },
|
|
2310
|
+
{ title: "\u{1F916} Cline", value: "cline", filename: ".clinerules" }
|
|
2297
2311
|
];
|
|
2298
2312
|
var PERSONAS = [
|
|
2299
|
-
{ title: "Full-Stack Developer
|
|
2300
|
-
{ title: "Backend Developer
|
|
2301
|
-
{ title: "Frontend Developer
|
|
2302
|
-
{ title: "DevOps Engineer
|
|
2303
|
-
{ title: "Data Engineer
|
|
2304
|
-
{ title: "Security Engineer
|
|
2305
|
-
{ title: "Custom...", value: "custom" }
|
|
2313
|
+
{ title: "\u{1F9D1}\u200D\u{1F4BB} Full-Stack Developer", value: "fullstack", description: "Complete application development" },
|
|
2314
|
+
{ title: "\u2699\uFE0F Backend Developer", value: "backend", description: "APIs, databases, services" },
|
|
2315
|
+
{ title: "\u{1F3A8} Frontend Developer", value: "frontend", description: "UI, components, styling" },
|
|
2316
|
+
{ title: "\u{1F680} DevOps Engineer", value: "devops", description: "Infrastructure, CI/CD" },
|
|
2317
|
+
{ title: "\u{1F4CA} Data Engineer", value: "data", description: "Pipelines, ETL, analytics" },
|
|
2318
|
+
{ title: "\u{1F512} Security Engineer", value: "security", description: "Secure code, auditing" },
|
|
2319
|
+
{ title: "\u270F\uFE0F Custom...", value: "custom", description: "Define your own" }
|
|
2306
2320
|
];
|
|
2307
2321
|
var BOUNDARY_PRESETS = [
|
|
2308
2322
|
{
|
|
2309
|
-
title: "Standard
|
|
2323
|
+
title: "\u{1F7E2} Standard",
|
|
2310
2324
|
value: "standard",
|
|
2325
|
+
description: "Balanced freedom & safety",
|
|
2311
2326
|
always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
|
|
2312
2327
|
askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
|
|
2313
2328
|
never: ["Delete production data", "Modify .env secrets", "Force push"]
|
|
2314
2329
|
},
|
|
2315
2330
|
{
|
|
2316
|
-
title: "Conservative
|
|
2331
|
+
title: "\u{1F7E1} Conservative",
|
|
2317
2332
|
value: "conservative",
|
|
2333
|
+
description: "Ask before most changes",
|
|
2318
2334
|
always: ["Read any file", "Run lint/format commands"],
|
|
2319
2335
|
askFirst: ["Modify any file", "Add dependencies", "Create files", "Run tests"],
|
|
2320
2336
|
never: ["Delete files", "Modify .env", "Push to git"]
|
|
2321
2337
|
},
|
|
2322
2338
|
{
|
|
2323
|
-
title: "Permissive
|
|
2339
|
+
title: "\u{1F7E0} Permissive",
|
|
2324
2340
|
value: "permissive",
|
|
2341
|
+
description: "AI can modify freely",
|
|
2325
2342
|
always: ["Modify any file in src/", "Run any script", "Add dependencies", "Create files"],
|
|
2326
2343
|
askFirst: ["Modify root configs", "Delete directories"],
|
|
2327
2344
|
never: ["Modify .env", "Access external APIs without confirmation"]
|
|
2328
2345
|
}
|
|
2329
2346
|
];
|
|
2347
|
+
function showStep(current, total, title) {
|
|
2348
|
+
const progress = "\u25CF".repeat(current) + "\u25CB".repeat(total - current);
|
|
2349
|
+
console.log();
|
|
2350
|
+
console.log(chalk8.cyan(` ${progress} Step ${current}/${total}: ${title}`));
|
|
2351
|
+
console.log();
|
|
2352
|
+
}
|
|
2353
|
+
function printBox(lines, color = chalk8.gray) {
|
|
2354
|
+
const maxLen = Math.max(...lines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "").length));
|
|
2355
|
+
const top = "\u250C" + "\u2500".repeat(maxLen + 2) + "\u2510";
|
|
2356
|
+
const bottom = "\u2514" + "\u2500".repeat(maxLen + 2) + "\u2518";
|
|
2357
|
+
console.log(color(top));
|
|
2358
|
+
for (const line of lines) {
|
|
2359
|
+
const stripped = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2360
|
+
const padding = " ".repeat(maxLen - stripped.length);
|
|
2361
|
+
console.log(color("\u2502 ") + line + padding + color(" \u2502"));
|
|
2362
|
+
}
|
|
2363
|
+
console.log(color(bottom));
|
|
2364
|
+
}
|
|
2330
2365
|
async function wizardCommand(options) {
|
|
2331
2366
|
console.log();
|
|
2332
|
-
console.log(chalk8.cyan("\u{1F431} LynxPrompt Wizard"));
|
|
2333
|
-
console.log(chalk8.gray("Generate AI IDE configuration in seconds"));
|
|
2367
|
+
console.log(chalk8.cyan.bold(" \u{1F431} LynxPrompt Wizard"));
|
|
2368
|
+
console.log(chalk8.gray(" Generate AI IDE configuration in seconds"));
|
|
2334
2369
|
console.log();
|
|
2335
2370
|
const detected = await detectProject(process.cwd());
|
|
2336
2371
|
if (detected) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
if (detected.
|
|
2341
|
-
if (detected.
|
|
2342
|
-
if (detected.
|
|
2372
|
+
const detectedInfo = [
|
|
2373
|
+
chalk8.green("\u2713 Project detected")
|
|
2374
|
+
];
|
|
2375
|
+
if (detected.name) detectedInfo.push(chalk8.gray(` Name: ${detected.name}`));
|
|
2376
|
+
if (detected.stack.length > 0) detectedInfo.push(chalk8.gray(` Stack: ${detected.stack.join(", ")}`));
|
|
2377
|
+
if (detected.packageManager) detectedInfo.push(chalk8.gray(` Package manager: ${detected.packageManager}`));
|
|
2378
|
+
printBox(detectedInfo, chalk8.gray);
|
|
2343
2379
|
console.log();
|
|
2344
2380
|
}
|
|
2345
2381
|
let config2;
|
|
@@ -2369,12 +2405,13 @@ async function wizardCommand(options) {
|
|
|
2369
2405
|
const files = generateConfig(config2);
|
|
2370
2406
|
spinner.stop();
|
|
2371
2407
|
console.log();
|
|
2372
|
-
console.log(chalk8.green("\u2705 Generated:"));
|
|
2408
|
+
console.log(chalk8.green.bold(" \u2705 Generated:"));
|
|
2409
|
+
console.log();
|
|
2373
2410
|
for (const [filename, content] of Object.entries(files)) {
|
|
2374
2411
|
const outputPath = join6(process.cwd(), filename);
|
|
2375
2412
|
let exists = false;
|
|
2376
2413
|
try {
|
|
2377
|
-
await
|
|
2414
|
+
await access3(outputPath);
|
|
2378
2415
|
exists = true;
|
|
2379
2416
|
} catch {
|
|
2380
2417
|
}
|
|
@@ -2386,7 +2423,7 @@ async function wizardCommand(options) {
|
|
|
2386
2423
|
initial: false
|
|
2387
2424
|
});
|
|
2388
2425
|
if (!response.overwrite) {
|
|
2389
|
-
console.log(chalk8.yellow(`
|
|
2426
|
+
console.log(chalk8.yellow(` \u23ED\uFE0F Skipped: ${filename}`));
|
|
2390
2427
|
continue;
|
|
2391
2428
|
}
|
|
2392
2429
|
}
|
|
@@ -2395,15 +2432,17 @@ async function wizardCommand(options) {
|
|
|
2395
2432
|
await mkdir4(dir, { recursive: true });
|
|
2396
2433
|
}
|
|
2397
2434
|
await writeFile4(outputPath, content, "utf-8");
|
|
2398
|
-
console.log(`
|
|
2435
|
+
console.log(` ${chalk8.cyan("\u2192")} ${chalk8.bold(filename)}`);
|
|
2399
2436
|
}
|
|
2400
2437
|
console.log();
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2438
|
+
printBox([
|
|
2439
|
+
chalk8.gray("Your AI assistant will now follow these instructions."),
|
|
2440
|
+
"",
|
|
2441
|
+
chalk8.gray("Next steps:"),
|
|
2442
|
+
chalk8.cyan(" lynxp check ") + chalk8.gray("Validate configuration"),
|
|
2443
|
+
chalk8.cyan(" lynxp push ") + chalk8.gray("Sync to cloud"),
|
|
2444
|
+
chalk8.cyan(" lynxp status ") + chalk8.gray("View current setup")
|
|
2445
|
+
], chalk8.gray);
|
|
2407
2446
|
console.log();
|
|
2408
2447
|
} catch (error) {
|
|
2409
2448
|
spinner.fail("Failed to generate files");
|
|
@@ -2417,119 +2456,135 @@ async function wizardCommand(options) {
|
|
|
2417
2456
|
}
|
|
2418
2457
|
async function runInteractiveWizard(options, detected) {
|
|
2419
2458
|
const answers = {};
|
|
2459
|
+
const totalSteps = 5;
|
|
2460
|
+
const promptConfig = {
|
|
2461
|
+
onCancel: () => {
|
|
2462
|
+
console.log(chalk8.yellow("\n Cancelled. Run 'lynxp wizard' anytime to restart.\n"));
|
|
2463
|
+
process.exit(0);
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
2466
|
+
showStep(1, totalSteps, "Output Format");
|
|
2420
2467
|
let platforms;
|
|
2421
2468
|
if (options.format) {
|
|
2422
2469
|
platforms = options.format.split(",").map((f) => f.trim());
|
|
2470
|
+
console.log(chalk8.gray(` Using format from flag: ${platforms.join(", ")}`));
|
|
2423
2471
|
} else {
|
|
2424
2472
|
const formatResponse = await prompts4({
|
|
2425
2473
|
type: "select",
|
|
2426
2474
|
name: "format",
|
|
2427
|
-
message: "
|
|
2475
|
+
message: chalk8.white("Where will you use this?"),
|
|
2428
2476
|
choices: OUTPUT_FORMATS.map((f) => ({
|
|
2429
|
-
title: f.recommended ? `${f.title} ${chalk8.green("
|
|
2477
|
+
title: f.recommended ? `${f.title} ${chalk8.green.bold("\u2605 recommended")}` : f.title,
|
|
2430
2478
|
value: f.value,
|
|
2431
|
-
description: f.description
|
|
2479
|
+
description: chalk8.gray(f.description)
|
|
2432
2480
|
})),
|
|
2433
|
-
initial: 0
|
|
2434
|
-
|
|
2435
|
-
});
|
|
2481
|
+
initial: 0,
|
|
2482
|
+
hint: chalk8.gray("\u2191\u2193 navigate \u2022 enter select")
|
|
2483
|
+
}, promptConfig);
|
|
2436
2484
|
if (formatResponse.format === "multiple") {
|
|
2485
|
+
console.log();
|
|
2437
2486
|
const platformResponse = await prompts4({
|
|
2438
2487
|
type: "multiselect",
|
|
2439
2488
|
name: "platforms",
|
|
2440
|
-
message: "Select AI editors:",
|
|
2441
|
-
choices: PLATFORMS.map((p) => ({
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2489
|
+
message: chalk8.white("Select AI editors:"),
|
|
2490
|
+
choices: PLATFORMS.map((p) => ({
|
|
2491
|
+
title: p.title,
|
|
2492
|
+
value: p.value
|
|
2493
|
+
})),
|
|
2494
|
+
hint: chalk8.gray("space select \u2022 a toggle all \u2022 enter confirm"),
|
|
2495
|
+
min: 1,
|
|
2496
|
+
instructions: false
|
|
2497
|
+
}, promptConfig);
|
|
2445
2498
|
platforms = platformResponse.platforms || ["agents"];
|
|
2446
2499
|
} else {
|
|
2447
2500
|
platforms = [formatResponse.format || "agents"];
|
|
2448
2501
|
}
|
|
2449
2502
|
}
|
|
2450
2503
|
answers.platforms = platforms;
|
|
2504
|
+
showStep(2, totalSteps, "Project Info");
|
|
2451
2505
|
const nameResponse = await prompts4({
|
|
2452
2506
|
type: "text",
|
|
2453
2507
|
name: "name",
|
|
2454
|
-
message: "Project name:",
|
|
2455
|
-
initial: options.name || detected?.name || "my-project"
|
|
2456
|
-
|
|
2508
|
+
message: chalk8.white("Project name:"),
|
|
2509
|
+
initial: options.name || detected?.name || "my-project",
|
|
2510
|
+
hint: chalk8.gray("Used in the generated config header")
|
|
2511
|
+
}, promptConfig);
|
|
2457
2512
|
answers.name = nameResponse.name || "my-project";
|
|
2458
2513
|
const descResponse = await prompts4({
|
|
2459
2514
|
type: "text",
|
|
2460
2515
|
name: "description",
|
|
2461
|
-
message: "Brief description
|
|
2462
|
-
initial: options.description || ""
|
|
2463
|
-
|
|
2516
|
+
message: chalk8.white("Brief description:"),
|
|
2517
|
+
initial: options.description || "",
|
|
2518
|
+
hint: chalk8.gray("optional - helps AI understand context")
|
|
2519
|
+
}, promptConfig);
|
|
2464
2520
|
answers.description = descResponse.description || "";
|
|
2521
|
+
showStep(3, totalSteps, "Tech Stack");
|
|
2465
2522
|
const allStackOptions = [...TECH_STACKS, ...FRAMEWORKS];
|
|
2466
2523
|
const detectedStackSet = new Set(detected?.stack || []);
|
|
2524
|
+
const preselected = allStackOptions.map((s, i) => detectedStackSet.has(s.value) ? i : -1).filter((i) => i !== -1);
|
|
2525
|
+
if (preselected.length > 0) {
|
|
2526
|
+
console.log(chalk8.gray(` Auto-selected: ${detected?.stack?.join(", ")}`));
|
|
2527
|
+
console.log();
|
|
2528
|
+
}
|
|
2467
2529
|
const stackResponse = await prompts4({
|
|
2468
2530
|
type: "multiselect",
|
|
2469
2531
|
name: "stack",
|
|
2470
|
-
message: "Tech stack:",
|
|
2532
|
+
message: chalk8.white("Tech stack:"),
|
|
2471
2533
|
choices: allStackOptions.map((s) => ({
|
|
2472
2534
|
title: s.title,
|
|
2473
2535
|
value: s.value,
|
|
2474
2536
|
selected: detectedStackSet.has(s.value)
|
|
2475
2537
|
})),
|
|
2476
|
-
hint: "
|
|
2477
|
-
|
|
2538
|
+
hint: chalk8.gray("space select \u2022 a toggle all \u2022 enter confirm"),
|
|
2539
|
+
instructions: false
|
|
2540
|
+
}, promptConfig);
|
|
2478
2541
|
answers.stack = stackResponse.stack || [];
|
|
2542
|
+
showStep(4, totalSteps, "AI Persona");
|
|
2479
2543
|
const personaResponse = await prompts4({
|
|
2480
2544
|
type: "select",
|
|
2481
2545
|
name: "persona",
|
|
2482
|
-
message: "AI
|
|
2483
|
-
choices: PERSONAS
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2546
|
+
message: chalk8.white("What role should the AI take?"),
|
|
2547
|
+
choices: PERSONAS.map((p) => ({
|
|
2548
|
+
title: p.title,
|
|
2549
|
+
value: p.value,
|
|
2550
|
+
description: chalk8.gray(p.description)
|
|
2551
|
+
})),
|
|
2552
|
+
initial: 0,
|
|
2553
|
+
hint: chalk8.gray("\u2191\u2193 navigate \u2022 enter select")
|
|
2554
|
+
}, promptConfig);
|
|
2487
2555
|
if (personaResponse.persona === "custom") {
|
|
2488
2556
|
const customPersona = await prompts4({
|
|
2489
2557
|
type: "text",
|
|
2490
2558
|
name: "value",
|
|
2491
|
-
message: "Describe the custom persona:"
|
|
2492
|
-
|
|
2559
|
+
message: chalk8.white("Describe the custom persona:"),
|
|
2560
|
+
hint: chalk8.gray("e.g., 'ML engineer focused on PyTorch and data pipelines'")
|
|
2561
|
+
}, promptConfig);
|
|
2493
2562
|
answers.persona = customPersona.value || "fullstack";
|
|
2494
2563
|
} else {
|
|
2495
2564
|
answers.persona = personaResponse.persona || "fullstack";
|
|
2496
2565
|
}
|
|
2566
|
+
showStep(5, totalSteps, "AI Boundaries");
|
|
2497
2567
|
const boundaryResponse = await prompts4({
|
|
2498
2568
|
type: "select",
|
|
2499
2569
|
name: "boundaries",
|
|
2500
|
-
message: "AI
|
|
2501
|
-
choices: BOUNDARY_PRESETS.map((b) => ({
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2570
|
+
message: chalk8.white("How much freedom should the AI have?"),
|
|
2571
|
+
choices: BOUNDARY_PRESETS.map((b) => ({
|
|
2572
|
+
title: b.title,
|
|
2573
|
+
value: b.value,
|
|
2574
|
+
description: chalk8.gray(b.description)
|
|
2575
|
+
})),
|
|
2576
|
+
initial: 0,
|
|
2577
|
+
hint: chalk8.gray("\u2191\u2193 navigate \u2022 enter select")
|
|
2578
|
+
}, promptConfig);
|
|
2505
2579
|
answers.boundaries = boundaryResponse.boundaries || "standard";
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
console.log(
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
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 = {};
|
|
2580
|
+
const selectedBoundary = BOUNDARY_PRESETS.find((b) => b.value === answers.boundaries);
|
|
2581
|
+
if (selectedBoundary) {
|
|
2582
|
+
console.log();
|
|
2583
|
+
console.log(chalk8.gray(" Always allowed: ") + chalk8.green(selectedBoundary.always.slice(0, 2).join(", ")));
|
|
2584
|
+
console.log(chalk8.gray(" Ask first: ") + chalk8.yellow(selectedBoundary.askFirst.slice(0, 2).join(", ")));
|
|
2585
|
+
console.log(chalk8.gray(" Never: ") + chalk8.red(selectedBoundary.never.slice(0, 2).join(", ")));
|
|
2532
2586
|
}
|
|
2587
|
+
answers.commands = detected?.commands || {};
|
|
2533
2588
|
return {
|
|
2534
2589
|
name: answers.name,
|
|
2535
2590
|
description: answers.description,
|
|
@@ -2604,7 +2659,7 @@ function handleApiError3(error) {
|
|
|
2604
2659
|
|
|
2605
2660
|
// src/commands/status.ts
|
|
2606
2661
|
import chalk10 from "chalk";
|
|
2607
|
-
import {
|
|
2662
|
+
import { readFile as readFile5, readdir, access as access4 } from "fs/promises";
|
|
2608
2663
|
import { join as join7 } from "path";
|
|
2609
2664
|
import { existsSync as existsSync4 } from "fs";
|
|
2610
2665
|
var CONFIG_FILES = [
|
|
@@ -2634,7 +2689,7 @@ async function statusCommand() {
|
|
|
2634
2689
|
const configPath = join7(cwd, ".lynxprompt/conf.yml");
|
|
2635
2690
|
if (existsSync4(configPath)) {
|
|
2636
2691
|
try {
|
|
2637
|
-
const content = await
|
|
2692
|
+
const content = await readFile5(configPath, "utf-8");
|
|
2638
2693
|
const { parse: parse5 } = await import("yaml");
|
|
2639
2694
|
const config2 = parse5(content);
|
|
2640
2695
|
if (config2?.exporters?.length > 0) {
|
|
@@ -2677,8 +2732,8 @@ async function statusCommand() {
|
|
|
2677
2732
|
for (const config2 of CONFIG_FILES) {
|
|
2678
2733
|
const filePath = join7(cwd, config2.path);
|
|
2679
2734
|
try {
|
|
2680
|
-
await
|
|
2681
|
-
const content = await
|
|
2735
|
+
await access4(filePath);
|
|
2736
|
+
const content = await readFile5(filePath, "utf-8");
|
|
2682
2737
|
const lines = content.split("\n").length;
|
|
2683
2738
|
const size = formatBytes(content.length);
|
|
2684
2739
|
foundAny = true;
|
|
@@ -2699,7 +2754,7 @@ async function statusCommand() {
|
|
|
2699
2754
|
const dirPath = join7(cwd, config2.path);
|
|
2700
2755
|
if (existsSync4(dirPath)) {
|
|
2701
2756
|
try {
|
|
2702
|
-
const files = await
|
|
2757
|
+
const files = await readdir(dirPath);
|
|
2703
2758
|
const ruleFiles = files.filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
2704
2759
|
if (ruleFiles.length > 0) {
|
|
2705
2760
|
foundAny = true;
|
|
@@ -2757,7 +2812,7 @@ function formatBytes(bytes) {
|
|
|
2757
2812
|
import chalk11 from "chalk";
|
|
2758
2813
|
import ora9 from "ora";
|
|
2759
2814
|
import prompts5 from "prompts";
|
|
2760
|
-
import { readFile as
|
|
2815
|
+
import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5, readdir as readdir2 } from "fs/promises";
|
|
2761
2816
|
import { join as join8, dirname as dirname5 } from "path";
|
|
2762
2817
|
import { existsSync as existsSync5 } from "fs";
|
|
2763
2818
|
import * as yaml3 from "yaml";
|
|
@@ -2778,7 +2833,7 @@ async function syncCommand(options = {}) {
|
|
|
2778
2833
|
const spinner = ora9("Loading configuration...").start();
|
|
2779
2834
|
let config2;
|
|
2780
2835
|
try {
|
|
2781
|
-
const configContent = await
|
|
2836
|
+
const configContent = await readFile6(configPath, "utf-8");
|
|
2782
2837
|
config2 = yaml3.parse(configContent);
|
|
2783
2838
|
spinner.succeed("Configuration loaded");
|
|
2784
2839
|
} catch (error) {
|
|
@@ -2875,12 +2930,12 @@ async function syncCommand(options = {}) {
|
|
|
2875
2930
|
async function loadRules(rulesPath) {
|
|
2876
2931
|
const files = [];
|
|
2877
2932
|
try {
|
|
2878
|
-
const entries = await
|
|
2933
|
+
const entries = await readdir2(rulesPath, { withFileTypes: true });
|
|
2879
2934
|
for (const entry of entries) {
|
|
2880
2935
|
if (!entry.isFile()) continue;
|
|
2881
2936
|
if (!entry.name.endsWith(".md")) continue;
|
|
2882
2937
|
const filePath = join8(rulesPath, entry.name);
|
|
2883
|
-
const content = await
|
|
2938
|
+
const content = await readFile6(filePath, "utf-8");
|
|
2884
2939
|
if (content.trim()) {
|
|
2885
2940
|
files.push({ name: entry.name, content: content.trim() });
|
|
2886
2941
|
}
|
|
@@ -2930,7 +2985,7 @@ function formatForAgent(agent, content) {
|
|
|
2930
2985
|
return content;
|
|
2931
2986
|
}
|
|
2932
2987
|
}
|
|
2933
|
-
function formatAsMdc(content,
|
|
2988
|
+
function formatAsMdc(content, _agent) {
|
|
2934
2989
|
const frontmatter = yaml3.stringify({
|
|
2935
2990
|
description: "LynxPrompt rules - AI coding guidelines",
|
|
2936
2991
|
globs: ["**/*"],
|
|
@@ -2941,7 +2996,7 @@ ${frontmatter}---
|
|
|
2941
2996
|
|
|
2942
2997
|
${content}`;
|
|
2943
2998
|
}
|
|
2944
|
-
function formatAsMarkdown(content,
|
|
2999
|
+
function formatAsMarkdown(content, _agent) {
|
|
2945
3000
|
const header = `# AI Coding Rules
|
|
2946
3001
|
|
|
2947
3002
|
> Generated by [LynxPrompt](https://lynxprompt.com)
|
|
@@ -2949,7 +3004,7 @@ function formatAsMarkdown(content, agent) {
|
|
|
2949
3004
|
`;
|
|
2950
3005
|
return header + content;
|
|
2951
3006
|
}
|
|
2952
|
-
function formatAsJson(content,
|
|
3007
|
+
function formatAsJson(content, _agent) {
|
|
2953
3008
|
return JSON.stringify(
|
|
2954
3009
|
{
|
|
2955
3010
|
$schema: "https://lynxprompt.com/schemas/rules.json",
|
|
@@ -2968,7 +3023,7 @@ function formatAsJson(content, agent) {
|
|
|
2968
3023
|
// src/commands/agents.ts
|
|
2969
3024
|
import chalk12 from "chalk";
|
|
2970
3025
|
import prompts6 from "prompts";
|
|
2971
|
-
import { readFile as
|
|
3026
|
+
import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
2972
3027
|
import { join as join9 } from "path";
|
|
2973
3028
|
import { existsSync as existsSync6 } from "fs";
|
|
2974
3029
|
import * as yaml4 from "yaml";
|
|
@@ -3170,7 +3225,7 @@ async function loadConfig() {
|
|
|
3170
3225
|
return null;
|
|
3171
3226
|
}
|
|
3172
3227
|
try {
|
|
3173
|
-
const content = await
|
|
3228
|
+
const content = await readFile7(configPath, "utf-8");
|
|
3174
3229
|
return yaml4.parse(content);
|
|
3175
3230
|
} catch {
|
|
3176
3231
|
return null;
|
|
@@ -3186,7 +3241,7 @@ async function saveConfig(config2) {
|
|
|
3186
3241
|
// src/commands/check.ts
|
|
3187
3242
|
import chalk13 from "chalk";
|
|
3188
3243
|
import ora10 from "ora";
|
|
3189
|
-
import { readFile as
|
|
3244
|
+
import { readFile as readFile8, readdir as readdir3, stat } from "fs/promises";
|
|
3190
3245
|
import { join as join10 } from "path";
|
|
3191
3246
|
import { existsSync as existsSync7 } from "fs";
|
|
3192
3247
|
import * as yaml5 from "yaml";
|
|
@@ -3252,7 +3307,7 @@ async function validateLynxPromptConfig(cwd) {
|
|
|
3252
3307
|
return { errors, warnings };
|
|
3253
3308
|
}
|
|
3254
3309
|
try {
|
|
3255
|
-
const content = await
|
|
3310
|
+
const content = await readFile8(configPath, "utf-8");
|
|
3256
3311
|
const config2 = yaml5.parse(content);
|
|
3257
3312
|
if (!config2.version) {
|
|
3258
3313
|
warnings.push(".lynxprompt/conf.yml: Missing 'version' field");
|
|
@@ -3325,7 +3380,7 @@ async function checkCommand(options = {}) {
|
|
|
3325
3380
|
if (existsSync7(filePath)) {
|
|
3326
3381
|
result.files.push(file.path);
|
|
3327
3382
|
try {
|
|
3328
|
-
const content = await
|
|
3383
|
+
const content = await readFile8(filePath, "utf-8");
|
|
3329
3384
|
const validation = validateMarkdown(content, file.path);
|
|
3330
3385
|
result.errors.push(...validation.errors);
|
|
3331
3386
|
result.warnings.push(...validation.warnings);
|
|
@@ -3338,13 +3393,13 @@ async function checkCommand(options = {}) {
|
|
|
3338
3393
|
const dirPath = join10(cwd, dir.path);
|
|
3339
3394
|
if (existsSync7(dirPath)) {
|
|
3340
3395
|
try {
|
|
3341
|
-
const files = await
|
|
3396
|
+
const files = await readdir3(dirPath);
|
|
3342
3397
|
for (const file of files) {
|
|
3343
3398
|
const filePath = join10(dirPath, file);
|
|
3344
|
-
const fileStat = await
|
|
3399
|
+
const fileStat = await stat(filePath);
|
|
3345
3400
|
if (fileStat.isFile()) {
|
|
3346
3401
|
result.files.push(`${dir.path}/${file}`);
|
|
3347
|
-
const content = await
|
|
3402
|
+
const content = await readFile8(filePath, "utf-8");
|
|
3348
3403
|
if (file.endsWith(".mdc")) {
|
|
3349
3404
|
const validation = validateMdc(content, `${dir.path}/${file}`);
|
|
3350
3405
|
result.errors.push(...validation.errors);
|
|
@@ -3420,7 +3475,7 @@ async function checkCommand(options = {}) {
|
|
|
3420
3475
|
// src/commands/diff.ts
|
|
3421
3476
|
import chalk14 from "chalk";
|
|
3422
3477
|
import ora11 from "ora";
|
|
3423
|
-
import { readFile as
|
|
3478
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3424
3479
|
import { join as join11 } from "path";
|
|
3425
3480
|
import { existsSync as existsSync8 } from "fs";
|
|
3426
3481
|
function computeDiff(oldText, newText) {
|
|
@@ -3484,7 +3539,6 @@ function longestCommonSubsequence(a, b) {
|
|
|
3484
3539
|
function formatDiff(diff, contextLines = 3) {
|
|
3485
3540
|
const output = [];
|
|
3486
3541
|
let lastPrintedIndex = -1;
|
|
3487
|
-
let inHunk = false;
|
|
3488
3542
|
const changeIndices = diff.map((d, i) => d.type !== "same" ? i : -1).filter((i) => i !== -1);
|
|
3489
3543
|
if (changeIndices.length === 0) {
|
|
3490
3544
|
return chalk14.gray(" (no changes)");
|
|
@@ -3515,7 +3569,7 @@ function getDiffStats(diff) {
|
|
|
3515
3569
|
unchanged: diff.filter((d) => d.type === "same").length
|
|
3516
3570
|
};
|
|
3517
3571
|
}
|
|
3518
|
-
async function diffCommand(
|
|
3572
|
+
async function diffCommand(fileOrId, options = {}) {
|
|
3519
3573
|
console.log();
|
|
3520
3574
|
console.log(chalk14.cyan("\u{1F431} LynxPrompt Diff"));
|
|
3521
3575
|
console.log();
|
|
@@ -3524,14 +3578,90 @@ async function diffCommand(blueprintId, options = {}) {
|
|
|
3524
3578
|
await diffLocal(cwd);
|
|
3525
3579
|
return;
|
|
3526
3580
|
}
|
|
3527
|
-
|
|
3528
|
-
|
|
3581
|
+
const trackedFiles = await checkSyncStatus(cwd);
|
|
3582
|
+
if (fileOrId) {
|
|
3583
|
+
const tracked = await findBlueprintByFile(cwd, fileOrId);
|
|
3584
|
+
if (tracked) {
|
|
3585
|
+
await diffFileWithBlueprint(cwd, fileOrId, tracked.id);
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
await diffWithBlueprintId(cwd, fileOrId);
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
if (trackedFiles.length === 0) {
|
|
3592
|
+
console.log(chalk14.yellow("No tracked blueprints found."));
|
|
3593
|
+
console.log();
|
|
3594
|
+
console.log(chalk14.gray("To track a blueprint and compare changes:"));
|
|
3595
|
+
console.log(chalk14.gray(" 1. Pull a blueprint: lynxp pull <blueprint-id>"));
|
|
3596
|
+
console.log(chalk14.gray(" 2. Or link an existing file: lynxp link"));
|
|
3597
|
+
console.log();
|
|
3598
|
+
console.log(chalk14.gray("Other options:"));
|
|
3599
|
+
console.log(chalk14.gray(" lynxp diff --local Compare .lynxprompt/rules/ with exported files"));
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
let hasChanges = false;
|
|
3603
|
+
for (const { blueprint, localModified, fileExists: fileExists2 } of trackedFiles) {
|
|
3604
|
+
if (!fileExists2) {
|
|
3605
|
+
console.log(chalk14.red(`\u2717 ${blueprint.file} - file not found`));
|
|
3606
|
+
continue;
|
|
3607
|
+
}
|
|
3608
|
+
console.log(chalk14.cyan(`\u{1F4C4} ${blueprint.file}`));
|
|
3609
|
+
console.log(chalk14.gray(` Linked to: ${blueprint.name} (${blueprint.id})`));
|
|
3610
|
+
if (localModified) {
|
|
3611
|
+
hasChanges = true;
|
|
3612
|
+
await diffFileWithBlueprint(cwd, blueprint.file, blueprint.id, true);
|
|
3613
|
+
} else {
|
|
3614
|
+
console.log(chalk14.green(" \u2713 In sync with cloud"));
|
|
3615
|
+
}
|
|
3529
3616
|
console.log();
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
console.log(chalk14.
|
|
3617
|
+
}
|
|
3618
|
+
if (!hasChanges) {
|
|
3619
|
+
console.log(chalk14.green("\u2713 All tracked files are in sync with their cloud blueprints!"));
|
|
3620
|
+
} else {
|
|
3621
|
+
console.log(chalk14.gray("To push local changes: lynxp push"));
|
|
3622
|
+
console.log(chalk14.gray("To pull cloud changes: lynxp pull <id>"));
|
|
3623
|
+
}
|
|
3624
|
+
console.log();
|
|
3625
|
+
}
|
|
3626
|
+
async function diffFileWithBlueprint(cwd, file, blueprintId, compact = false) {
|
|
3627
|
+
const filePath = join11(cwd, file);
|
|
3628
|
+
if (!existsSync8(filePath)) {
|
|
3629
|
+
console.log(chalk14.red(`\u2717 File not found: ${file}`));
|
|
3533
3630
|
return;
|
|
3534
3631
|
}
|
|
3632
|
+
const spinner = compact ? null : ora11("Fetching blueprint...").start();
|
|
3633
|
+
try {
|
|
3634
|
+
const { blueprint } = await api.getBlueprint(blueprintId);
|
|
3635
|
+
spinner?.stop();
|
|
3636
|
+
if (!blueprint || !blueprint.content) {
|
|
3637
|
+
console.log(chalk14.red(`\u2717 Blueprint has no content`));
|
|
3638
|
+
return;
|
|
3639
|
+
}
|
|
3640
|
+
const localContent = await readFile9(filePath, "utf-8");
|
|
3641
|
+
const diff = computeDiff(blueprint.content, localContent);
|
|
3642
|
+
const stats = getDiffStats(diff);
|
|
3643
|
+
if (stats.added === 0 && stats.removed === 0) {
|
|
3644
|
+
if (!compact) {
|
|
3645
|
+
console.log(chalk14.green("\u2713 Files are identical!"));
|
|
3646
|
+
}
|
|
3647
|
+
} else {
|
|
3648
|
+
if (!compact) {
|
|
3649
|
+
console.log(chalk14.gray("Changes (cloud \u2192 local):"));
|
|
3650
|
+
console.log();
|
|
3651
|
+
}
|
|
3652
|
+
console.log(formatDiff(diff));
|
|
3653
|
+
console.log(chalk14.gray(` ${chalk14.green(`+${stats.added}`)} ${chalk14.red(`-${stats.removed}`)} lines`));
|
|
3654
|
+
}
|
|
3655
|
+
} catch (error) {
|
|
3656
|
+
spinner?.stop();
|
|
3657
|
+
if (error instanceof ApiRequestError) {
|
|
3658
|
+
console.log(chalk14.red(`\u2717 Could not fetch blueprint: ${error.message}`));
|
|
3659
|
+
} else {
|
|
3660
|
+
console.log(chalk14.red("\u2717 Failed to compare"));
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
async function diffWithBlueprintId(cwd, blueprintId) {
|
|
3535
3665
|
if (!isAuthenticated()) {
|
|
3536
3666
|
console.log(chalk14.yellow("\u26A0 Not logged in. Some blueprints may not be accessible."));
|
|
3537
3667
|
console.log(chalk14.gray("Run 'lynxp login' to authenticate."));
|
|
@@ -3563,7 +3693,7 @@ async function diffCommand(blueprintId, options = {}) {
|
|
|
3563
3693
|
const fullPath = join11(cwd, path2);
|
|
3564
3694
|
if (existsSync8(fullPath)) {
|
|
3565
3695
|
try {
|
|
3566
|
-
localContent = await
|
|
3696
|
+
localContent = await readFile9(fullPath, "utf-8");
|
|
3567
3697
|
localPath = path2;
|
|
3568
3698
|
break;
|
|
3569
3699
|
} catch {
|
|
@@ -3625,7 +3755,7 @@ async function diffLocal(cwd) {
|
|
|
3625
3755
|
}
|
|
3626
3756
|
let rulesContent;
|
|
3627
3757
|
try {
|
|
3628
|
-
rulesContent = await
|
|
3758
|
+
rulesContent = await readFile9(rulesPath, "utf-8");
|
|
3629
3759
|
} catch {
|
|
3630
3760
|
console.log(chalk14.red("\u2717 Could not read .lynxprompt/rules/agents.md"));
|
|
3631
3761
|
return;
|
|
@@ -3639,7 +3769,7 @@ async function diffLocal(cwd) {
|
|
|
3639
3769
|
const filePath = join11(cwd, file.path);
|
|
3640
3770
|
if (existsSync8(filePath)) {
|
|
3641
3771
|
try {
|
|
3642
|
-
const exportedContent = await
|
|
3772
|
+
const exportedContent = await readFile9(filePath, "utf-8");
|
|
3643
3773
|
let compareContent = exportedContent;
|
|
3644
3774
|
if (file.path.endsWith(".mdc")) {
|
|
3645
3775
|
const frontmatterEnd = exportedContent.indexOf("---", 3);
|
|
@@ -3691,33 +3821,53 @@ function getSourceFromVisibility2(visibility) {
|
|
|
3691
3821
|
return "marketplace";
|
|
3692
3822
|
}
|
|
3693
3823
|
}
|
|
3694
|
-
async function linkCommand(
|
|
3824
|
+
async function linkCommand(fileArg, blueprintIdArg, options = {}) {
|
|
3695
3825
|
const cwd = process.cwd();
|
|
3696
3826
|
if (options.list) {
|
|
3697
3827
|
await listTrackedBlueprints(cwd);
|
|
3698
3828
|
return;
|
|
3699
3829
|
}
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3830
|
+
console.log();
|
|
3831
|
+
console.log(chalk15.cyan("\u{1F431} Link File to Blueprint"));
|
|
3832
|
+
console.log();
|
|
3833
|
+
let file;
|
|
3834
|
+
let blueprintId = blueprintIdArg;
|
|
3835
|
+
if (!fileArg) {
|
|
3836
|
+
const configFiles = [
|
|
3837
|
+
"AGENTS.md",
|
|
3838
|
+
"CLAUDE.md",
|
|
3839
|
+
".cursor/rules/project.mdc",
|
|
3840
|
+
".github/copilot-instructions.md",
|
|
3841
|
+
".windsurfrules",
|
|
3842
|
+
".zed/instructions.md",
|
|
3843
|
+
".clinerules"
|
|
3844
|
+
];
|
|
3845
|
+
const foundFiles = configFiles.filter((f) => existsSync9(join12(cwd, f)));
|
|
3846
|
+
if (foundFiles.length === 0) {
|
|
3847
|
+
console.log(chalk15.yellow("No AI configuration files found in this directory."));
|
|
3848
|
+
console.log();
|
|
3849
|
+
console.log(chalk15.gray("Create one first:"));
|
|
3850
|
+
console.log(chalk15.gray(" lynxp wizard Generate a new config file"));
|
|
3851
|
+
console.log(chalk15.gray(" lynxp pull <id> Download from marketplace"));
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
const { selectedFile } = await prompts7({
|
|
3855
|
+
type: "select",
|
|
3856
|
+
name: "selectedFile",
|
|
3857
|
+
message: "Which file do you want to link to a cloud blueprint?",
|
|
3858
|
+
choices: foundFiles.map((f) => ({
|
|
3859
|
+
title: f,
|
|
3860
|
+
value: f,
|
|
3861
|
+
description: "Local file exists"
|
|
3862
|
+
}))
|
|
3863
|
+
});
|
|
3864
|
+
if (!selectedFile) {
|
|
3865
|
+
console.log(chalk15.gray("Cancelled."));
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
file = selectedFile;
|
|
3869
|
+
} else {
|
|
3870
|
+
file = fileArg;
|
|
3721
3871
|
}
|
|
3722
3872
|
const filePath = join12(cwd, file);
|
|
3723
3873
|
if (!existsSync9(filePath)) {
|
|
@@ -3726,7 +3876,9 @@ async function linkCommand(file, blueprintId, options = {}) {
|
|
|
3726
3876
|
}
|
|
3727
3877
|
const existing = await findBlueprintByFile(cwd, file);
|
|
3728
3878
|
if (existing) {
|
|
3729
|
-
console.log(chalk15.yellow(
|
|
3879
|
+
console.log(chalk15.yellow(`This file is already linked to: ${existing.name}`));
|
|
3880
|
+
console.log(chalk15.gray(` ID: ${existing.id}`));
|
|
3881
|
+
console.log();
|
|
3730
3882
|
const { proceed } = await prompts7({
|
|
3731
3883
|
type: "confirm",
|
|
3732
3884
|
name: "proceed",
|
|
@@ -3738,11 +3890,120 @@ async function linkCommand(file, blueprintId, options = {}) {
|
|
|
3738
3890
|
return;
|
|
3739
3891
|
}
|
|
3740
3892
|
}
|
|
3741
|
-
if (!
|
|
3742
|
-
|
|
3743
|
-
chalk15.yellow("
|
|
3744
|
-
|
|
3745
|
-
|
|
3893
|
+
if (!blueprintId) {
|
|
3894
|
+
if (!isAuthenticated()) {
|
|
3895
|
+
console.log(chalk15.yellow("You need to login to access your blueprints."));
|
|
3896
|
+
const { doLogin } = await prompts7({
|
|
3897
|
+
type: "confirm",
|
|
3898
|
+
name: "doLogin",
|
|
3899
|
+
message: "Login now?",
|
|
3900
|
+
initial: true
|
|
3901
|
+
});
|
|
3902
|
+
if (doLogin) {
|
|
3903
|
+
console.log(chalk15.gray("Run 'lynxp login' in another terminal, then come back here."));
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
console.log(chalk15.gray("Cancelled."));
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
const { searchMethod } = await prompts7({
|
|
3910
|
+
type: "select",
|
|
3911
|
+
name: "searchMethod",
|
|
3912
|
+
message: "How do you want to find the blueprint?",
|
|
3913
|
+
choices: [
|
|
3914
|
+
{ title: "\u{1F4CB} From my blueprints", value: "list" },
|
|
3915
|
+
{ title: "\u{1F50D} Search marketplace", value: "search" },
|
|
3916
|
+
{ title: "\u{1F522} Enter ID directly", value: "manual" }
|
|
3917
|
+
]
|
|
3918
|
+
});
|
|
3919
|
+
if (!searchMethod) {
|
|
3920
|
+
console.log(chalk15.gray("Cancelled."));
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3923
|
+
if (searchMethod === "list") {
|
|
3924
|
+
const spinner2 = ora12("Fetching your blueprints...").start();
|
|
3925
|
+
try {
|
|
3926
|
+
const { blueprints } = await api.listBlueprints();
|
|
3927
|
+
spinner2.stop();
|
|
3928
|
+
if (!blueprints || blueprints.length === 0) {
|
|
3929
|
+
console.log(chalk15.yellow("You don't have any blueprints yet."));
|
|
3930
|
+
console.log(chalk15.gray("Create one with 'lynxp push' or search the marketplace."));
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
const { selected } = await prompts7({
|
|
3934
|
+
type: "select",
|
|
3935
|
+
name: "selected",
|
|
3936
|
+
message: "Select a blueprint:",
|
|
3937
|
+
choices: blueprints.map((b) => ({
|
|
3938
|
+
title: b.name,
|
|
3939
|
+
value: b.id,
|
|
3940
|
+
description: b.description?.substring(0, 50) || ""
|
|
3941
|
+
}))
|
|
3942
|
+
});
|
|
3943
|
+
if (!selected) {
|
|
3944
|
+
console.log(chalk15.gray("Cancelled."));
|
|
3945
|
+
return;
|
|
3946
|
+
}
|
|
3947
|
+
blueprintId = selected;
|
|
3948
|
+
} catch {
|
|
3949
|
+
spinner2.stop();
|
|
3950
|
+
console.log(chalk15.red("\u2717 Could not fetch blueprints"));
|
|
3951
|
+
return;
|
|
3952
|
+
}
|
|
3953
|
+
} else if (searchMethod === "search") {
|
|
3954
|
+
const { query } = await prompts7({
|
|
3955
|
+
type: "text",
|
|
3956
|
+
name: "query",
|
|
3957
|
+
message: "Search for:"
|
|
3958
|
+
});
|
|
3959
|
+
if (!query) {
|
|
3960
|
+
console.log(chalk15.gray("Cancelled."));
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
const spinner2 = ora12(`Searching for "${query}"...`).start();
|
|
3964
|
+
try {
|
|
3965
|
+
const results = await api.searchBlueprints(query, 10);
|
|
3966
|
+
spinner2.stop();
|
|
3967
|
+
if (!results.templates || results.templates.length === 0) {
|
|
3968
|
+
console.log(chalk15.yellow(`No blueprints found for "${query}"`));
|
|
3969
|
+
return;
|
|
3970
|
+
}
|
|
3971
|
+
const { selected } = await prompts7({
|
|
3972
|
+
type: "select",
|
|
3973
|
+
name: "selected",
|
|
3974
|
+
message: "Select a blueprint:",
|
|
3975
|
+
choices: results.templates.map((b) => ({
|
|
3976
|
+
title: `${b.name} (\u2605 ${b.likes})`,
|
|
3977
|
+
value: b.id,
|
|
3978
|
+
description: b.author ? `by ${b.author}` : ""
|
|
3979
|
+
}))
|
|
3980
|
+
});
|
|
3981
|
+
if (!selected) {
|
|
3982
|
+
console.log(chalk15.gray("Cancelled."));
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3985
|
+
blueprintId = selected;
|
|
3986
|
+
} catch {
|
|
3987
|
+
spinner2.stop();
|
|
3988
|
+
console.log(chalk15.red("\u2717 Search failed"));
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
} else {
|
|
3992
|
+
const { manualId } = await prompts7({
|
|
3993
|
+
type: "text",
|
|
3994
|
+
name: "manualId",
|
|
3995
|
+
message: "Enter blueprint ID:"
|
|
3996
|
+
});
|
|
3997
|
+
if (!manualId) {
|
|
3998
|
+
console.log(chalk15.gray("Cancelled."));
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
blueprintId = manualId;
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
if (!blueprintId) {
|
|
4005
|
+
console.log(chalk15.red("\u2717 No blueprint ID provided."));
|
|
4006
|
+
return;
|
|
3746
4007
|
}
|
|
3747
4008
|
const spinner = ora12(`Fetching blueprint ${chalk15.cyan(blueprintId)}...`).start();
|
|
3748
4009
|
try {
|
|
@@ -3778,11 +4039,10 @@ async function linkCommand(file, blueprintId, options = {}) {
|
|
|
3778
4039
|
console.log(chalk15.green(`\u2705 Linked: ${file} \u2192 ${blueprint.id}`));
|
|
3779
4040
|
console.log();
|
|
3780
4041
|
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`));
|
|
4042
|
+
console.log(chalk15.gray(` \u2022 Run 'lynxp diff' to see differences`));
|
|
3783
4043
|
console.log(chalk15.gray(` \u2022 Run 'lynxp status' to see all tracked blueprints`));
|
|
3784
4044
|
if (!isMarketplace) {
|
|
3785
|
-
console.log(chalk15.gray(` \u2022 Run 'lynxp push
|
|
4045
|
+
console.log(chalk15.gray(` \u2022 Run 'lynxp push' to push local changes to cloud`));
|
|
3786
4046
|
}
|
|
3787
4047
|
console.log();
|
|
3788
4048
|
} catch (error) {
|
|
@@ -3801,23 +4061,43 @@ async function linkCommand(file, blueprintId, options = {}) {
|
|
|
3801
4061
|
}
|
|
3802
4062
|
}
|
|
3803
4063
|
}
|
|
3804
|
-
async function unlinkCommand(
|
|
4064
|
+
async function unlinkCommand(fileArg) {
|
|
3805
4065
|
const cwd = process.cwd();
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
4066
|
+
console.log();
|
|
4067
|
+
console.log(chalk15.cyan("\u{1F431} Unlink File from Blueprint"));
|
|
4068
|
+
console.log();
|
|
4069
|
+
let file;
|
|
4070
|
+
if (!fileArg) {
|
|
4071
|
+
const status = await checkSyncStatus(cwd);
|
|
4072
|
+
if (status.length === 0) {
|
|
4073
|
+
console.log(chalk15.yellow("No files are currently linked to blueprints."));
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
const { selectedFile } = await prompts7({
|
|
4077
|
+
type: "select",
|
|
4078
|
+
name: "selectedFile",
|
|
4079
|
+
message: "Which file do you want to unlink?",
|
|
4080
|
+
choices: status.map(({ blueprint }) => ({
|
|
4081
|
+
title: blueprint.file,
|
|
4082
|
+
value: blueprint.file,
|
|
4083
|
+
description: `${blueprint.name} (${blueprint.source})`
|
|
4084
|
+
}))
|
|
4085
|
+
});
|
|
4086
|
+
if (!selectedFile) {
|
|
4087
|
+
console.log(chalk15.gray("Cancelled."));
|
|
4088
|
+
return;
|
|
4089
|
+
}
|
|
4090
|
+
file = selectedFile;
|
|
4091
|
+
} else {
|
|
4092
|
+
file = fileArg;
|
|
3812
4093
|
}
|
|
3813
4094
|
const tracked = await findBlueprintByFile(cwd, file);
|
|
3814
4095
|
if (!tracked) {
|
|
3815
|
-
console.log(chalk15.yellow(
|
|
4096
|
+
console.log(chalk15.yellow(`File is not linked to any blueprint: ${file}`));
|
|
3816
4097
|
return;
|
|
3817
4098
|
}
|
|
3818
|
-
console.log();
|
|
3819
|
-
console.log(chalk15.
|
|
3820
|
-
console.log(chalk15.gray(` Name: ${tracked.name}`));
|
|
4099
|
+
console.log(chalk15.gray(`Currently linked to: ${tracked.name}`));
|
|
4100
|
+
console.log(chalk15.gray(` ID: ${tracked.id}`));
|
|
3821
4101
|
console.log(chalk15.gray(` Source: ${tracked.source}`));
|
|
3822
4102
|
console.log();
|
|
3823
4103
|
const { confirm } = await prompts7({
|
|
@@ -3834,8 +4114,7 @@ async function unlinkCommand(file) {
|
|
|
3834
4114
|
if (success) {
|
|
3835
4115
|
console.log();
|
|
3836
4116
|
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."));
|
|
4117
|
+
console.log(chalk15.gray(" The file is now standalone. Changes won't sync with the cloud."));
|
|
3839
4118
|
console.log();
|
|
3840
4119
|
} else {
|
|
3841
4120
|
console.log(chalk15.red("\u2717 Failed to unlink file."));
|
|
@@ -3851,7 +4130,7 @@ async function listTrackedBlueprints(cwd) {
|
|
|
3851
4130
|
console.log();
|
|
3852
4131
|
console.log(chalk15.gray("To track a blueprint:"));
|
|
3853
4132
|
console.log(chalk15.gray(" lynxp pull <blueprint-id> Download and track a blueprint"));
|
|
3854
|
-
console.log(chalk15.gray(" lynxp link
|
|
4133
|
+
console.log(chalk15.gray(" lynxp link Link an existing file to a blueprint"));
|
|
3855
4134
|
return;
|
|
3856
4135
|
}
|
|
3857
4136
|
for (const { blueprint, localModified, fileExists: fileExists2 } of status) {
|
|
@@ -3888,8 +4167,8 @@ program.command("search <query>").description("Search public blueprints in the m
|
|
|
3888
4167
|
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
4168
|
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
4169
|
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 [
|
|
4170
|
+
program.command("unlink [file]").description("Disconnect a local file from its cloud blueprint").action(unlinkCommand);
|
|
4171
|
+
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
4172
|
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
4173
|
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
4174
|
program.command("agents [action] [agent]").description("Manage AI agents (list, enable, disable, detect)").option("-i, --interactive", "Interactive agent selection").action(agentsCommand);
|