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 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 access3 } from "fs/promises";
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 access3(path2);
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 access5 } from "fs/promises";
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 (Universal)",
2248
+ title: "\u{1F310} AGENTS.md",
2249
2249
  value: "agents",
2250
- description: "Works with Claude Code, GitHub Copilot, Aider, and most AI editors",
2250
+ description: "Universal format - Claude, Copilot, Aider, & more",
2251
2251
  recommended: true
2252
2252
  },
2253
2253
  {
2254
- title: "Cursor (.cursor/rules/)",
2254
+ title: "\u{1F5B1}\uFE0F Cursor",
2255
2255
  value: "cursor",
2256
- description: "Cursor IDE with MDC format"
2256
+ description: ".cursor/rules/ with MDC format"
2257
2257
  },
2258
2258
  {
2259
- title: "Multiple formats",
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: "Select multiple AI editors to generate for"
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 Boot", value: "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 (.cursor/rules/)", value: "cursor", filename: ".cursor/rules/project.mdc" },
2293
- { title: "Claude Code (CLAUDE.md)", value: "claude", filename: "CLAUDE.md" },
2294
- { title: "GitHub Copilot", value: "copilot", filename: ".github/copilot-instructions.md" },
2295
- { title: "Windsurf (.windsurfrules)", value: "windsurf", filename: ".windsurfrules" },
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 - Complete application setups", value: "fullstack" },
2300
- { title: "Backend Developer - APIs, databases, microservices", value: "backend" },
2301
- { title: "Frontend Developer - UI, components, styling", value: "frontend" },
2302
- { title: "DevOps Engineer - Infrastructure, CI/CD, containers", value: "devops" },
2303
- { title: "Data Engineer - Pipelines, ETL, databases", value: "data" },
2304
- { title: "Security Engineer - Secure code, vulnerabilities", value: "security" },
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 - Balance of freedom and safety (recommended)",
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 - Ask before most changes",
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 - AI can modify freely within src/",
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
- console.log(chalk8.green("\u2713 Detected project:"));
2338
- if (detected.name) console.log(chalk8.gray(` Name: ${detected.name}`));
2339
- if (detected.stack.length > 0) console.log(chalk8.gray(` Stack: ${detected.stack.join(", ")}`));
2340
- if (detected.packageManager) console.log(chalk8.gray(` Package manager: ${detected.packageManager}`));
2341
- if (detected.commands.build) console.log(chalk8.gray(` Build: ${detected.commands.build}`));
2342
- if (detected.commands.test) console.log(chalk8.gray(` Test: ${detected.commands.test}`));
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 access5(outputPath);
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(` Skipped: ${filename}`));
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(` ${chalk8.cyan(filename)}`);
2435
+ console.log(` ${chalk8.cyan("\u2192")} ${chalk8.bold(filename)}`);
2399
2436
  }
2400
2437
  console.log();
2401
- console.log(chalk8.gray("Your AI assistant will now follow these instructions."));
2402
- console.log();
2403
- console.log(chalk8.gray("Tips:"));
2404
- console.log(chalk8.gray(" \u2022 Edit the generated file anytime to customize rules"));
2405
- console.log(chalk8.gray(" \u2022 Run 'lynxp wizard' again to regenerate"));
2406
- console.log(chalk8.gray(" \u2022 Run 'lynxp check' to validate your configuration"));
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: "Select output format:",
2475
+ message: chalk8.white("Where will you use this?"),
2428
2476
  choices: OUTPUT_FORMATS.map((f) => ({
2429
- title: f.recommended ? `${f.title} ${chalk8.green("(recommended)")}` : f.title,
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
- // AGENTS.md is default
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) => ({ title: p.title, value: p.value })),
2442
- hint: "- Space to select, Enter to confirm",
2443
- min: 1
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 (optional):",
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: "- Space to select, Enter to confirm"
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 persona:",
2483
- choices: PERSONAS,
2484
- initial: 0
2485
- // Full-stack by default
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 boundaries:",
2501
- choices: BOUNDARY_PRESETS.map((b) => ({ title: b.title, value: b.value })),
2502
- initial: 0
2503
- // Standard by default
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
- if (detected?.commands && Object.keys(detected.commands).length > 0) {
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 = {};
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 { access as access6, readFile as readFile6, readdir as readdir3 } from "fs/promises";
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 readFile6(configPath, "utf-8");
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 access6(filePath);
2681
- const content = await readFile6(filePath, "utf-8");
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 readdir3(dirPath);
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 readFile7, writeFile as writeFile5, mkdir as mkdir5, readdir as readdir4 } from "fs/promises";
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 readFile7(configPath, "utf-8");
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 readdir4(rulesPath, { withFileTypes: true });
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 readFile7(filePath, "utf-8");
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, agent) {
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, agent) {
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, agent) {
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 readFile8, writeFile as writeFile6 } from "fs/promises";
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 readFile8(configPath, "utf-8");
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 readFile9, readdir as readdir5, stat as stat2 } from "fs/promises";
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 readFile9(configPath, "utf-8");
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 readFile9(filePath, "utf-8");
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 readdir5(dirPath);
3396
+ const files = await readdir3(dirPath);
3342
3397
  for (const file of files) {
3343
3398
  const filePath = join10(dirPath, file);
3344
- const fileStat = await stat2(filePath);
3399
+ const fileStat = await stat(filePath);
3345
3400
  if (fileStat.isFile()) {
3346
3401
  result.files.push(`${dir.path}/${file}`);
3347
- const content = await readFile9(filePath, "utf-8");
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 readFile10 } from "fs/promises";
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(blueprintId, options = {}) {
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
- if (!blueprintId) {
3528
- console.log(chalk14.red("\u2717 Please provide a blueprint ID to compare with."));
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
- console.log(chalk14.gray("Usage:"));
3531
- console.log(chalk14.gray(" lynxp diff <blueprint-id> Compare local with remote blueprint"));
3532
- console.log(chalk14.gray(" lynxp diff --local Compare .lynxprompt/rules/ with exports"));
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 readFile10(fullPath, "utf-8");
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 readFile10(rulesPath, "utf-8");
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 readFile10(filePath, "utf-8");
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(file, blueprintId, options = {}) {
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
- if (!file) {
3701
- console.log(chalk15.red("\u2717 Please provide a file path to link."));
3702
- console.log();
3703
- console.log(chalk15.gray("Usage:"));
3704
- console.log(chalk15.gray(" lynxp link <file> <blueprint-id> Link a local file to a cloud blueprint"));
3705
- console.log(chalk15.gray(" lynxp link --list List all tracked blueprints"));
3706
- console.log();
3707
- console.log(chalk15.gray("Example:"));
3708
- console.log(chalk15.gray(" lynxp link AGENTS.md bp_abc123"));
3709
- return;
3710
- }
3711
- if (!blueprintId) {
3712
- console.log(chalk15.red("\u2717 Please provide a blueprint ID to link to."));
3713
- console.log();
3714
- console.log(chalk15.gray("Usage: lynxp link <file> <blueprint-id>"));
3715
- console.log(chalk15.gray("Example: lynxp link AGENTS.md bp_abc123"));
3716
- console.log();
3717
- console.log(chalk15.gray("To find blueprint IDs:"));
3718
- console.log(chalk15.gray(" lynxp list - Show your blueprints"));
3719
- console.log(chalk15.gray(" lynxp search <query> - Search marketplace"));
3720
- return;
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(`\u26A0 This file is already linked to: ${existing.id}`));
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 (!isAuthenticated()) {
3742
- console.log(
3743
- chalk15.yellow("Not logged in. Run 'lynxp login' to authenticate.")
3744
- );
3745
- process.exit(1);
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 pull ${blueprintId}' to update local file from cloud`));
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 ${file}' to push local changes to cloud`));
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(file) {
4064
+ async function unlinkCommand(fileArg) {
3805
4065
  const cwd = process.cwd();
3806
- if (!file) {
3807
- console.log(chalk15.red("\u2717 Please provide a file path to unlink."));
3808
- console.log();
3809
- console.log(chalk15.gray("Usage: lynxp unlink <file>"));
3810
- console.log(chalk15.gray("Example: lynxp unlink AGENTS.md"));
3811
- return;
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(`\u26A0 File is not linked to any blueprint: ${file}`));
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.cyan(`Currently linked to: ${tracked.id}`));
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 a standalone local file."));
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 <file> <id> Link an existing file to a blueprint"));
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 <file>").description("Disconnect a local file from its cloud blueprint").action(unlinkCommand);
3892
- program.command("diff [blueprint-id]").description("Show changes between local and remote blueprint").option("--local", "Compare .lynxprompt/rules/ with exported files").action(diffCommand);
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);