lynxprompt 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk12 from "chalk";
5
+ import chalk16 from "chalk";
6
6
 
7
7
  // src/commands/login.ts
8
8
  import chalk from "chalk";
@@ -135,6 +135,19 @@ var ApiClient = class {
135
135
  });
136
136
  return this.request(`/api/blueprints?${params}`);
137
137
  }
138
+ async createBlueprint(data) {
139
+ return this.request("/api/v1/blueprints", {
140
+ method: "POST",
141
+ body: JSON.stringify(data)
142
+ });
143
+ }
144
+ async updateBlueprint(id, data) {
145
+ const apiId = id.startsWith("bp_") ? id : `bp_${id}`;
146
+ return this.request(`/api/v1/blueprints/${apiId}`, {
147
+ method: "PUT",
148
+ body: JSON.stringify(data)
149
+ });
150
+ }
138
151
  };
139
152
  var ApiRequestError = class extends Error {
140
153
  constructor(message, statusCode, response) {
@@ -408,67 +421,319 @@ function handleApiError(error) {
408
421
  import chalk5 from "chalk";
409
422
  import ora4 from "ora";
410
423
  import prompts from "prompts";
411
- import { writeFile, access, mkdir } from "fs/promises";
424
+ import { writeFile as writeFile2, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
425
+ import { join as join2, dirname as dirname2 } from "path";
426
+ import { existsSync } from "fs";
427
+
428
+ // src/utils/blueprint-tracker.ts
429
+ import { readFile, writeFile, mkdir, access } from "fs/promises";
412
430
  import { join, dirname } from "path";
431
+ import { createHash } from "crypto";
432
+ import * as yaml from "yaml";
433
+ var BLUEPRINTS_FILE = ".lynxprompt/blueprints.yml";
434
+ function calculateChecksum(content) {
435
+ return createHash("sha256").update(content).digest("hex").substring(0, 16);
436
+ }
437
+ async function loadBlueprints(cwd) {
438
+ const filePath = join(cwd, BLUEPRINTS_FILE);
439
+ try {
440
+ await access(filePath);
441
+ const content = await readFile(filePath, "utf-8");
442
+ const config2 = yaml.parse(content);
443
+ return config2 || { version: "1", blueprints: [] };
444
+ } catch {
445
+ return { version: "1", blueprints: [] };
446
+ }
447
+ }
448
+ async function saveBlueprints(cwd, config2) {
449
+ const filePath = join(cwd, BLUEPRINTS_FILE);
450
+ const dir = dirname(filePath);
451
+ await mkdir(dir, { recursive: true });
452
+ const content = yaml.stringify(config2, {
453
+ lineWidth: 0,
454
+ singleQuote: false
455
+ });
456
+ await writeFile(filePath, content, "utf-8");
457
+ }
458
+ async function trackBlueprint(cwd, blueprint) {
459
+ const config2 = await loadBlueprints(cwd);
460
+ config2.blueprints = config2.blueprints.filter((b) => b.file !== blueprint.file);
461
+ const editable = blueprint.source !== "marketplace";
462
+ const canPull = true;
463
+ config2.blueprints.push({
464
+ id: blueprint.id,
465
+ source: blueprint.source,
466
+ file: blueprint.file,
467
+ name: blueprint.name,
468
+ pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
469
+ checksum: calculateChecksum(blueprint.content),
470
+ version: blueprint.version,
471
+ editable,
472
+ canPull
473
+ });
474
+ await saveBlueprints(cwd, config2);
475
+ }
476
+ async function findBlueprintByFile(cwd, file) {
477
+ const config2 = await loadBlueprints(cwd);
478
+ return config2.blueprints.find((b) => b.file === file) || null;
479
+ }
480
+ async function hasLocalChanges(cwd, tracked) {
481
+ try {
482
+ const filePath = join(cwd, tracked.file);
483
+ const content = await readFile(filePath, "utf-8");
484
+ const currentChecksum = calculateChecksum(content);
485
+ return currentChecksum !== tracked.checksum;
486
+ } catch {
487
+ return false;
488
+ }
489
+ }
490
+ async function updateChecksum(cwd, file, content) {
491
+ const config2 = await loadBlueprints(cwd);
492
+ const blueprint = config2.blueprints.find((b) => b.file === file);
493
+ if (blueprint) {
494
+ blueprint.checksum = calculateChecksum(content);
495
+ blueprint.pulledAt = (/* @__PURE__ */ new Date()).toISOString();
496
+ await saveBlueprints(cwd, config2);
497
+ }
498
+ }
499
+ async function untrackBlueprint(cwd, file) {
500
+ const config2 = await loadBlueprints(cwd);
501
+ const initialCount = config2.blueprints.length;
502
+ config2.blueprints = config2.blueprints.filter((b) => b.file !== file);
503
+ if (config2.blueprints.length < initialCount) {
504
+ await saveBlueprints(cwd, config2);
505
+ return true;
506
+ }
507
+ return false;
508
+ }
509
+ async function linkBlueprint(cwd, file, blueprintId, blueprintName, source) {
510
+ try {
511
+ const filePath = join(cwd, file);
512
+ const content = await readFile(filePath, "utf-8");
513
+ await trackBlueprint(cwd, {
514
+ id: blueprintId,
515
+ name: blueprintName,
516
+ file,
517
+ content,
518
+ source
519
+ });
520
+ } catch (error) {
521
+ throw new Error(`Could not read file ${file} to link`);
522
+ }
523
+ }
524
+ async function checkSyncStatus(cwd) {
525
+ const config2 = await loadBlueprints(cwd);
526
+ const results = [];
527
+ for (const blueprint of config2.blueprints) {
528
+ const filePath = join(cwd, blueprint.file);
529
+ let fileExists2 = false;
530
+ let localModified = false;
531
+ try {
532
+ await access(filePath);
533
+ fileExists2 = true;
534
+ localModified = await hasLocalChanges(cwd, blueprint);
535
+ } catch {
536
+ fileExists2 = false;
537
+ }
538
+ results.push({ blueprint, localModified, fileExists: fileExists2 });
539
+ }
540
+ return results;
541
+ }
542
+
543
+ // src/commands/pull.ts
413
544
  var TYPE_TO_FILENAME = {
414
545
  AGENTS_MD: "AGENTS.md",
415
- CURSOR_RULES: ".cursorrules",
546
+ CURSOR_RULES: ".cursor/rules/project.mdc",
416
547
  COPILOT_INSTRUCTIONS: ".github/copilot-instructions.md",
417
548
  WINDSURF_RULES: ".windsurfrules",
418
549
  ZED_INSTRUCTIONS: ".zed/instructions.md",
419
550
  CLAUDE_MD: "CLAUDE.md",
420
551
  GENERIC: "ai-config.md"
421
552
  };
553
+ function getSourceFromVisibility(visibility) {
554
+ switch (visibility) {
555
+ case "PUBLIC":
556
+ return "marketplace";
557
+ case "TEAM":
558
+ return "team";
559
+ case "PRIVATE":
560
+ return "private";
561
+ default:
562
+ return "marketplace";
563
+ }
564
+ }
422
565
  async function pullCommand(id, options) {
423
566
  if (!isAuthenticated()) {
424
567
  console.log(
425
- chalk5.yellow("Not logged in. Run 'lynxprompt login' to authenticate.")
568
+ chalk5.yellow("Not logged in. Run 'lynxp login' to authenticate.")
426
569
  );
427
570
  process.exit(1);
428
571
  }
572
+ const cwd = process.cwd();
429
573
  const spinner = ora4(`Fetching blueprint ${chalk5.cyan(id)}...`).start();
430
574
  try {
431
575
  const { blueprint } = await api.getBlueprint(id);
432
576
  spinner.stop();
433
577
  if (!blueprint.content) {
434
- console.error(chalk5.red("Blueprint has no content."));
578
+ console.error(chalk5.red("\u2717 Blueprint has no content."));
435
579
  process.exit(1);
436
580
  }
581
+ const source = getSourceFromVisibility(blueprint.visibility);
582
+ const isMarketplace = source === "marketplace";
583
+ console.log();
584
+ console.log(chalk5.cyan(`\u{1F431} Blueprint: ${chalk5.bold(blueprint.name)}`));
585
+ if (blueprint.description) {
586
+ console.log(chalk5.gray(` ${blueprint.description}`));
587
+ }
588
+ console.log(chalk5.gray(` Type: ${blueprint.type} \u2022 Tier: ${blueprint.tier} \u2022 Visibility: ${blueprint.visibility}`));
589
+ if (isMarketplace) {
590
+ console.log(chalk5.yellow(` \u{1F4E6} Marketplace blueprint (read-only - changes won't sync back)`));
591
+ } else if (source === "team") {
592
+ console.log(chalk5.blue(` \u{1F465} Team blueprint (can sync changes)`));
593
+ } else if (source === "private") {
594
+ console.log(chalk5.green(` \u{1F512} Private blueprint (can sync changes)`));
595
+ }
596
+ console.log();
597
+ if (options.preview) {
598
+ console.log(chalk5.gray("\u2500".repeat(60)));
599
+ console.log();
600
+ const lines = blueprint.content.split("\n");
601
+ const previewLines = lines.slice(0, 50);
602
+ for (const line of previewLines) {
603
+ if (line.startsWith("#")) {
604
+ console.log(chalk5.cyan(line));
605
+ } else if (line.startsWith(">")) {
606
+ console.log(chalk5.gray(line));
607
+ } else if (line.startsWith("- ") || line.startsWith("* ")) {
608
+ console.log(chalk5.white(line));
609
+ } else if (line.startsWith("```")) {
610
+ console.log(chalk5.yellow(line));
611
+ } else {
612
+ console.log(line);
613
+ }
614
+ }
615
+ if (lines.length > 50) {
616
+ console.log();
617
+ console.log(chalk5.gray(`... ${lines.length - 50} more lines`));
618
+ }
619
+ console.log();
620
+ console.log(chalk5.gray("\u2500".repeat(60)));
621
+ console.log();
622
+ console.log(chalk5.gray("Run without --preview to download this blueprint."));
623
+ return;
624
+ }
437
625
  const filename = TYPE_TO_FILENAME[blueprint.type] || "ai-config.md";
438
- const outputPath = join(options.output, filename);
439
- let fileExists2 = false;
440
- try {
441
- await access(outputPath);
442
- fileExists2 = true;
443
- } catch {
626
+ const outputPath = join2(options.output, filename);
627
+ let localContent = null;
628
+ if (existsSync(outputPath)) {
629
+ try {
630
+ localContent = await readFile2(outputPath, "utf-8");
631
+ } catch {
632
+ }
633
+ }
634
+ const existingTracked = await findBlueprintByFile(cwd, filename);
635
+ if (existingTracked && existingTracked.id !== id) {
636
+ console.log(chalk5.yellow(`\u26A0 This file is already linked to a different blueprint: ${existingTracked.id}`));
637
+ if (!options.yes) {
638
+ const { proceed } = await prompts({
639
+ type: "confirm",
640
+ name: "proceed",
641
+ message: "Replace the link with the new blueprint?",
642
+ initial: false
643
+ });
644
+ if (!proceed) {
645
+ console.log(chalk5.gray("Cancelled."));
646
+ return;
647
+ }
648
+ }
444
649
  }
445
- if (fileExists2 && !options.yes) {
650
+ if (localContent && !options.yes) {
651
+ const localLines = localContent.split("\n").length;
652
+ const remoteLines = blueprint.content.split("\n").length;
653
+ console.log(chalk5.yellow(`\u26A0 File exists: ${outputPath}`));
654
+ console.log(chalk5.gray(` Local: ${localLines} lines`));
655
+ console.log(chalk5.gray(` Remote: ${remoteLines} lines`));
656
+ console.log();
446
657
  const response = await prompts({
447
- type: "confirm",
448
- name: "overwrite",
449
- message: `File ${chalk5.cyan(outputPath)} already exists. Overwrite?`,
450
- initial: false
658
+ type: "select",
659
+ name: "action",
660
+ message: "What would you like to do?",
661
+ choices: [
662
+ { title: "Overwrite with remote version", value: "overwrite" },
663
+ { title: "Preview remote content first", value: "preview" },
664
+ { title: "Cancel", value: "cancel" }
665
+ ]
451
666
  });
452
- if (!response.overwrite) {
453
- console.log(chalk5.yellow("Aborted."));
667
+ if (response.action === "cancel" || !response.action) {
668
+ console.log(chalk5.gray("Cancelled."));
454
669
  return;
455
670
  }
671
+ if (response.action === "preview") {
672
+ console.log();
673
+ console.log(chalk5.gray("\u2500".repeat(60)));
674
+ console.log();
675
+ const lines = blueprint.content.split("\n").slice(0, 30);
676
+ for (const line of lines) {
677
+ if (line.startsWith("#")) {
678
+ console.log(chalk5.cyan(line));
679
+ } else {
680
+ console.log(line);
681
+ }
682
+ }
683
+ if (blueprint.content.split("\n").length > 30) {
684
+ console.log(chalk5.gray(`... ${blueprint.content.split("\n").length - 30} more lines`));
685
+ }
686
+ console.log();
687
+ console.log(chalk5.gray("\u2500".repeat(60)));
688
+ console.log();
689
+ const confirmResponse = await prompts({
690
+ type: "confirm",
691
+ name: "confirm",
692
+ message: "Download and overwrite local file?",
693
+ initial: false
694
+ });
695
+ if (!confirmResponse.confirm) {
696
+ console.log(chalk5.gray("Cancelled."));
697
+ return;
698
+ }
699
+ }
456
700
  }
457
- const dir = dirname(outputPath);
701
+ const dir = dirname2(outputPath);
458
702
  if (dir !== ".") {
459
- await mkdir(dir, { recursive: true });
703
+ await mkdir2(dir, { recursive: true });
704
+ }
705
+ await writeFile2(outputPath, blueprint.content, "utf-8");
706
+ if (options.track !== false) {
707
+ await trackBlueprint(cwd, {
708
+ id: blueprint.id,
709
+ name: blueprint.name,
710
+ file: filename,
711
+ content: blueprint.content,
712
+ source
713
+ });
714
+ }
715
+ console.log(chalk5.green(`\u2705 Downloaded: ${chalk5.bold(outputPath)}`));
716
+ if (options.track !== false) {
717
+ console.log(chalk5.gray(` Linked to: ${blueprint.id}`));
718
+ if (isMarketplace) {
719
+ console.log(chalk5.gray(` Updates: Run 'lynxp pull ${id}' to sync updates`));
720
+ } else {
721
+ console.log(chalk5.gray(` Sync: Run 'lynxp push ${filename}' to push changes`));
722
+ }
460
723
  }
461
- await writeFile(outputPath, blueprint.content, "utf-8");
462
- console.log();
463
- console.log(chalk5.green(`\u2705 Downloaded ${chalk5.bold(blueprint.name)}`));
464
- console.log(` ${chalk5.gray("File:")} ${chalk5.cyan(outputPath)}`);
465
- console.log(` ${chalk5.gray("Type:")} ${blueprint.type}`);
466
- console.log(` ${chalk5.gray("Tier:")} ${blueprint.tier}`);
467
724
  console.log();
468
725
  const editorHint = getEditorHint(blueprint.type);
469
726
  if (editorHint) {
470
727
  console.log(chalk5.gray(`\u{1F4A1} ${editorHint}`));
728
+ console.log();
471
729
  }
730
+ console.log(chalk5.gray("Tips:"));
731
+ console.log(chalk5.gray(` \u2022 Run 'lynxp status' to see tracked blueprints`));
732
+ console.log(chalk5.gray(` \u2022 Run 'lynxp diff ${id}' to see changes between local and remote`));
733
+ if (isMarketplace) {
734
+ console.log(chalk5.gray(` \u2022 Run 'lynxp unlink ${filename}' to disconnect and make editable`));
735
+ }
736
+ console.log();
472
737
  } catch (error) {
473
738
  spinner.fail("Failed to pull blueprint");
474
739
  handleApiError2(error);
@@ -496,45 +761,226 @@ function handleApiError2(error) {
496
761
  if (error instanceof ApiRequestError) {
497
762
  if (error.statusCode === 401) {
498
763
  console.error(
499
- chalk5.red("Your session has expired. Please run 'lynxprompt login' again.")
764
+ chalk5.red("\u2717 Your session has expired. Please run 'lynxp login' again.")
500
765
  );
501
766
  } else if (error.statusCode === 403) {
502
767
  console.error(
503
- chalk5.red("You don't have access to this blueprint.")
768
+ chalk5.red("\u2717 You don't have access to this blueprint.")
504
769
  );
505
770
  console.error(
506
771
  chalk5.gray(
507
- "This might be a private blueprint or require a higher subscription tier."
772
+ " This might be a private blueprint or require a higher subscription tier."
508
773
  )
509
774
  );
510
775
  } else if (error.statusCode === 404) {
511
- console.error(chalk5.red("Blueprint not found."));
776
+ console.error(chalk5.red("\u2717 Blueprint not found."));
512
777
  console.error(
513
778
  chalk5.gray(
514
- "Make sure you have the correct blueprint ID. Use 'lynxprompt list' to see your blueprints."
779
+ " Make sure you have the correct blueprint ID. Use 'lynxp list' to see your blueprints."
515
780
  )
516
781
  );
517
782
  } else {
518
- console.error(chalk5.red(`Error: ${error.message}`));
783
+ console.error(chalk5.red(`\u2717 Error: ${error.message}`));
519
784
  }
520
785
  } else {
521
- console.error(chalk5.red("An unexpected error occurred."));
786
+ console.error(chalk5.red("\u2717 An unexpected error occurred."));
522
787
  }
523
788
  process.exit(1);
524
789
  }
525
790
 
526
- // src/commands/init.ts
791
+ // src/commands/push.ts
527
792
  import chalk6 from "chalk";
528
- import prompts2 from "prompts";
529
793
  import ora5 from "ora";
530
- import { writeFile as writeFile2, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
531
- import { join as join4, dirname as dirname2, basename } from "path";
532
- import { existsSync as existsSync2 } from "fs";
533
- import * as yaml from "yaml";
794
+ import fs from "fs";
795
+ import path from "path";
796
+ import prompts2 from "prompts";
797
+ async function pushCommand(fileArg, options) {
798
+ const cwd = process.cwd();
799
+ if (!isAuthenticated()) {
800
+ console.log(chalk6.yellow("You need to be logged in to push blueprints."));
801
+ console.log(chalk6.gray("Run 'lynxp login' to authenticate."));
802
+ process.exit(1);
803
+ }
804
+ const file = fileArg || findDefaultFile();
805
+ if (!file) {
806
+ console.log(chalk6.red("No AI configuration file found."));
807
+ console.log(
808
+ chalk6.gray("Specify a file or run in a directory with AGENTS.md, CLAUDE.md, etc.")
809
+ );
810
+ process.exit(1);
811
+ }
812
+ if (!fs.existsSync(file)) {
813
+ console.log(chalk6.red(`File not found: ${file}`));
814
+ process.exit(1);
815
+ }
816
+ const content = fs.readFileSync(file, "utf-8");
817
+ const filename = path.basename(file);
818
+ const linked = await findBlueprintByFile(cwd, file);
819
+ if (linked) {
820
+ await updateBlueprint(cwd, file, linked.id, content, options);
821
+ } else {
822
+ await createOrLinkBlueprint(cwd, file, filename, content, options);
823
+ }
824
+ }
825
+ async function updateBlueprint(cwd, file, blueprintId, content, options) {
826
+ console.log(chalk6.cyan(`
827
+ \u{1F4E4} Updating blueprint ${chalk6.bold(blueprintId)}...`));
828
+ console.log(chalk6.gray(` File: ${file}`));
829
+ if (!options.yes) {
830
+ const confirm = await prompts2({
831
+ type: "confirm",
832
+ name: "value",
833
+ message: `Push changes to ${blueprintId}?`,
834
+ initial: true
835
+ });
836
+ if (!confirm.value) {
837
+ console.log(chalk6.yellow("Push cancelled."));
838
+ return;
839
+ }
840
+ }
841
+ const spinner = ora5("Pushing changes...").start();
842
+ try {
843
+ const result = await api.updateBlueprint(blueprintId, { content });
844
+ spinner.succeed("Blueprint updated!");
845
+ await updateChecksum(cwd, file, content);
846
+ console.log();
847
+ console.log(chalk6.green(`\u2705 Successfully updated ${chalk6.bold(result.blueprint.name)}`));
848
+ console.log(chalk6.gray(` ID: ${blueprintId}`));
849
+ console.log(chalk6.gray(` View: https://lynxprompt.com/templates/${blueprintId.replace("bp_", "")}`));
850
+ } catch (error) {
851
+ spinner.fail("Failed to update blueprint");
852
+ handleError(error);
853
+ }
854
+ }
855
+ async function createOrLinkBlueprint(cwd, file, filename, content, options) {
856
+ console.log(chalk6.cyan("\n\u{1F4E4} Push new blueprint"));
857
+ console.log(chalk6.gray(` File: ${file}`));
858
+ let name = options.name;
859
+ let description = options.description;
860
+ let visibility = options.visibility || "PRIVATE";
861
+ let tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : [];
862
+ if (!options.yes) {
863
+ const responses = await prompts2([
864
+ {
865
+ type: name ? null : "text",
866
+ name: "name",
867
+ message: "Blueprint name:",
868
+ initial: filename.replace(/\.(md|mdc|json|yml|yaml)$/, ""),
869
+ validate: (v) => v.length > 0 || "Name is required"
870
+ },
871
+ {
872
+ type: description ? null : "text",
873
+ name: "description",
874
+ message: "Description:",
875
+ initial: ""
876
+ },
877
+ {
878
+ type: "select",
879
+ name: "visibility",
880
+ message: "Visibility:",
881
+ choices: [
882
+ { title: "Private (only you)", value: "PRIVATE" },
883
+ { title: "Team (your team members)", value: "TEAM" },
884
+ { title: "Public (visible to everyone)", value: "PUBLIC" }
885
+ ],
886
+ initial: 0
887
+ },
888
+ {
889
+ type: "text",
890
+ name: "tags",
891
+ message: "Tags (comma-separated):",
892
+ initial: ""
893
+ }
894
+ ]);
895
+ if (!responses.name && !name) {
896
+ console.log(chalk6.yellow("Push cancelled."));
897
+ return;
898
+ }
899
+ name = name || responses.name;
900
+ description = description || responses.description || "";
901
+ visibility = responses.visibility || visibility;
902
+ tags = responses.tags ? responses.tags.split(",").map((t) => t.trim()).filter(Boolean) : tags;
903
+ }
904
+ if (!name) {
905
+ name = filename.replace(/\.(md|mdc|json|yml|yaml)$/, "");
906
+ }
907
+ const spinner = ora5("Creating blueprint...").start();
908
+ try {
909
+ const result = await api.createBlueprint({
910
+ name,
911
+ description: description || "",
912
+ content,
913
+ visibility,
914
+ tags
915
+ });
916
+ spinner.succeed("Blueprint created!");
917
+ await trackBlueprint(cwd, {
918
+ id: result.blueprint.id,
919
+ name: result.blueprint.name,
920
+ file,
921
+ content,
922
+ source: "private"
923
+ });
924
+ console.log();
925
+ console.log(chalk6.green(`\u2705 Created blueprint ${chalk6.bold(result.blueprint.name)}`));
926
+ console.log(chalk6.gray(` ID: ${result.blueprint.id}`));
927
+ console.log(chalk6.gray(` Visibility: ${visibility}`));
928
+ if (visibility === "PUBLIC") {
929
+ console.log(chalk6.gray(` View: https://lynxprompt.com/templates/${result.blueprint.id.replace("bp_", "")}`));
930
+ }
931
+ console.log();
932
+ console.log(chalk6.cyan("The file is now linked. Future 'lynxp push' will update this blueprint."));
933
+ } catch (error) {
934
+ spinner.fail("Failed to create blueprint");
935
+ handleError(error);
936
+ }
937
+ }
938
+ function findDefaultFile() {
939
+ const candidates = [
940
+ "AGENTS.md",
941
+ "CLAUDE.md",
942
+ ".cursor/rules/project.mdc",
943
+ ".github/copilot-instructions.md",
944
+ ".windsurfrules",
945
+ "AIDER.md",
946
+ "GEMINI.md",
947
+ ".clinerules"
948
+ ];
949
+ for (const candidate of candidates) {
950
+ if (fs.existsSync(candidate)) {
951
+ return candidate;
952
+ }
953
+ }
954
+ return null;
955
+ }
956
+ function handleError(error) {
957
+ if (error instanceof ApiRequestError) {
958
+ console.error(chalk6.red(`Error: ${error.message}`));
959
+ if (error.statusCode === 401) {
960
+ console.error(chalk6.gray("Your session may have expired. Run 'lynxp login' to re-authenticate."));
961
+ } else if (error.statusCode === 403) {
962
+ console.error(chalk6.gray("You don't have permission to modify this blueprint."));
963
+ } else if (error.statusCode === 404) {
964
+ console.error(chalk6.gray("Blueprint not found. It may have been deleted."));
965
+ }
966
+ } else {
967
+ console.error(chalk6.red("An unexpected error occurred."));
968
+ }
969
+ process.exit(1);
970
+ }
971
+
972
+ // src/commands/init.ts
973
+ import chalk7 from "chalk";
974
+ import prompts3 from "prompts";
975
+ import ora6 from "ora";
976
+ import { writeFile as writeFile3, mkdir as mkdir3, readFile as readFile4 } from "fs/promises";
977
+ import { join as join5, dirname as dirname3, basename } from "path";
978
+ import { existsSync as existsSync3 } from "fs";
979
+ import * as yaml2 from "yaml";
534
980
 
535
981
  // src/utils/agent-detector.ts
536
- import { existsSync, readdirSync, readFileSync, statSync } from "fs";
537
- import { join as join2 } from "path";
982
+ import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
983
+ import { join as join3 } from "path";
538
984
 
539
985
  // src/utils/agents.ts
540
986
  var AGENTS = [
@@ -832,8 +1278,8 @@ function detectAgent(cwd, agent) {
832
1278
  let hasContent = false;
833
1279
  let ruleCount = 0;
834
1280
  for (const pattern of agent.patterns) {
835
- const fullPath = join2(cwd, pattern);
836
- if (!existsSync(fullPath)) {
1281
+ const fullPath = join3(cwd, pattern);
1282
+ if (!existsSync2(fullPath)) {
837
1283
  continue;
838
1284
  }
839
1285
  const stats = statSync(fullPath);
@@ -867,7 +1313,7 @@ function scanDirectory(dirPath, format) {
867
1313
  const name = entry.name.toLowerCase();
868
1314
  const isRuleFile = format === "mdc" && name.endsWith(".mdc") || format === "markdown" && name.endsWith(".md") || format === "json" && name.endsWith(".json") || format === "yaml" && (name.endsWith(".yml") || name.endsWith(".yaml"));
869
1315
  if (!isRuleFile) continue;
870
- const filePath = join2(dirPath, entry.name);
1316
+ const filePath = join3(dirPath, entry.name);
871
1317
  const content = safeReadFile(filePath);
872
1318
  if (content && content.trim().length > 0) {
873
1319
  results.push({
@@ -884,9 +1330,9 @@ function countSections(content) {
884
1330
  const headings = content.match(/^#{1,6}\s+.+$/gm);
885
1331
  return headings ? headings.length : content.trim().length > 0 ? 1 : 0;
886
1332
  }
887
- function safeReadFile(path) {
1333
+ function safeReadFile(path2) {
888
1334
  try {
889
- return readFileSync(path, "utf-8");
1335
+ return readFileSync(path2, "utf-8");
890
1336
  } catch {
891
1337
  return null;
892
1338
  }
@@ -915,130 +1361,230 @@ function formatDetectionResults(result) {
915
1361
  }
916
1362
 
917
1363
  // src/utils/detect.ts
918
- import { readFile, access as access2 } from "fs/promises";
919
- import { join as join3 } from "path";
1364
+ import { readFile as readFile3, access as access3 } from "fs/promises";
1365
+ import { join as join4 } from "path";
1366
+ var JS_FRAMEWORK_PATTERNS = {
1367
+ nextjs: ["next"],
1368
+ react: ["react", "react-dom"],
1369
+ vue: ["vue"],
1370
+ angular: ["@angular/core"],
1371
+ svelte: ["svelte", "@sveltejs/kit"],
1372
+ solid: ["solid-js"],
1373
+ remix: ["@remix-run/react"],
1374
+ astro: ["astro"],
1375
+ nuxt: ["nuxt"],
1376
+ gatsby: ["gatsby"]
1377
+ };
1378
+ var JS_TOOL_PATTERNS = {
1379
+ typescript: ["typescript"],
1380
+ tailwind: ["tailwindcss"],
1381
+ prisma: ["prisma", "@prisma/client"],
1382
+ drizzle: ["drizzle-orm"],
1383
+ express: ["express"],
1384
+ fastify: ["fastify"],
1385
+ hono: ["hono"],
1386
+ elysia: ["elysia"],
1387
+ trpc: ["@trpc/server"],
1388
+ graphql: ["graphql", "@apollo/server"],
1389
+ jest: ["jest"],
1390
+ vitest: ["vitest"],
1391
+ playwright: ["@playwright/test"],
1392
+ cypress: ["cypress"],
1393
+ eslint: ["eslint"],
1394
+ biome: ["@biomejs/biome"],
1395
+ prettier: ["prettier"],
1396
+ vite: ["vite"],
1397
+ webpack: ["webpack"],
1398
+ turbo: ["turbo"]
1399
+ };
920
1400
  async function detectProject(cwd) {
921
1401
  const detected = {
922
1402
  name: null,
923
1403
  stack: [],
924
1404
  commands: {},
925
- packageManager: null
1405
+ packageManager: null,
1406
+ type: "unknown"
926
1407
  };
927
- const packageJsonPath = join3(cwd, "package.json");
1408
+ const packageJsonPath = join4(cwd, "package.json");
928
1409
  if (await fileExists(packageJsonPath)) {
929
1410
  try {
930
- const content = await readFile(packageJsonPath, "utf-8");
1411
+ const content = await readFile3(packageJsonPath, "utf-8");
931
1412
  const pkg = JSON.parse(content);
932
1413
  detected.name = pkg.name || null;
1414
+ detected.description = pkg.description;
1415
+ if (pkg.workspaces || await fileExists(join4(cwd, "pnpm-workspace.yaml"))) {
1416
+ detected.type = "monorepo";
1417
+ } else if (pkg.main || pkg.exports) {
1418
+ detected.type = "library";
1419
+ } else {
1420
+ detected.type = "application";
1421
+ }
933
1422
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
934
- if (allDeps["typescript"]) detected.stack.push("typescript");
935
- if (allDeps["react"]) detected.stack.push("react");
936
- if (allDeps["next"]) detected.stack.push("nextjs");
937
- if (allDeps["vue"]) detected.stack.push("vue");
938
- if (allDeps["@angular/core"]) detected.stack.push("angular");
939
- if (allDeps["svelte"]) detected.stack.push("svelte");
940
- if (allDeps["express"]) detected.stack.push("express");
941
- if (allDeps["fastify"]) detected.stack.push("fastify");
942
- if (allDeps["prisma"]) detected.stack.push("prisma");
943
- if (allDeps["tailwindcss"]) detected.stack.push("tailwind");
1423
+ for (const [framework, deps] of Object.entries(JS_FRAMEWORK_PATTERNS)) {
1424
+ if (deps.some((dep) => allDeps[dep])) {
1425
+ detected.stack.push(framework);
1426
+ }
1427
+ }
1428
+ for (const [tool, deps] of Object.entries(JS_TOOL_PATTERNS)) {
1429
+ if (deps.some((dep) => allDeps[dep])) {
1430
+ detected.stack.push(tool);
1431
+ }
1432
+ }
1433
+ if (detected.stack.length === 0 || detected.stack.length === 1 && detected.stack[0] === "typescript") {
1434
+ detected.stack.unshift("javascript");
1435
+ }
944
1436
  if (pkg.scripts) {
945
1437
  detected.commands.build = pkg.scripts.build;
946
1438
  detected.commands.test = pkg.scripts.test;
947
- detected.commands.lint = pkg.scripts.lint;
948
- detected.commands.dev = pkg.scripts.dev || pkg.scripts.start;
1439
+ detected.commands.lint = pkg.scripts.lint || pkg.scripts["lint:check"];
1440
+ detected.commands.dev = pkg.scripts.dev || pkg.scripts.start || pkg.scripts.serve;
1441
+ detected.commands.format = pkg.scripts.format || pkg.scripts.prettier;
949
1442
  }
950
- if (await fileExists(join3(cwd, "pnpm-lock.yaml"))) {
1443
+ if (await fileExists(join4(cwd, "pnpm-lock.yaml"))) {
951
1444
  detected.packageManager = "pnpm";
952
- } else if (await fileExists(join3(cwd, "yarn.lock"))) {
1445
+ } else if (await fileExists(join4(cwd, "yarn.lock"))) {
953
1446
  detected.packageManager = "yarn";
954
- } else if (await fileExists(join3(cwd, "bun.lockb"))) {
1447
+ } else if (await fileExists(join4(cwd, "bun.lockb"))) {
955
1448
  detected.packageManager = "bun";
956
- } else if (await fileExists(join3(cwd, "package-lock.json"))) {
1449
+ } else if (await fileExists(join4(cwd, "package-lock.json"))) {
957
1450
  detected.packageManager = "npm";
958
1451
  }
1452
+ if (detected.packageManager && detected.packageManager !== "npm") {
1453
+ const pm = detected.packageManager;
1454
+ for (const [key, value] of Object.entries(detected.commands)) {
1455
+ if (value && !value.startsWith(pm) && !value.startsWith("npx")) {
1456
+ detected.commands[key] = `${pm} run ${value}`;
1457
+ }
1458
+ }
1459
+ } else if (detected.commands) {
1460
+ for (const [key, value] of Object.entries(detected.commands)) {
1461
+ if (value && !value.startsWith("npm") && !value.startsWith("npx")) {
1462
+ detected.commands[key] = `npm run ${value}`;
1463
+ }
1464
+ }
1465
+ }
1466
+ if (pkg.scripts) {
1467
+ detected.commands.build = pkg.scripts.build ? "build" : void 0;
1468
+ detected.commands.test = pkg.scripts.test ? "test" : void 0;
1469
+ detected.commands.lint = pkg.scripts.lint ? "lint" : pkg.scripts["lint:check"] ? "lint:check" : void 0;
1470
+ detected.commands.dev = pkg.scripts.dev ? "dev" : pkg.scripts.start ? "start" : pkg.scripts.serve ? "serve" : void 0;
1471
+ }
959
1472
  return detected;
960
1473
  } catch {
961
1474
  }
962
1475
  }
963
- const pyprojectPath = join3(cwd, "pyproject.toml");
1476
+ const pyprojectPath = join4(cwd, "pyproject.toml");
964
1477
  if (await fileExists(pyprojectPath)) {
965
1478
  try {
966
- const content = await readFile(pyprojectPath, "utf-8");
1479
+ const content = await readFile3(pyprojectPath, "utf-8");
967
1480
  detected.stack.push("python");
1481
+ detected.type = "application";
968
1482
  const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
969
1483
  if (nameMatch) detected.name = nameMatch[1];
970
1484
  if (content.includes("fastapi")) detected.stack.push("fastapi");
971
1485
  if (content.includes("django")) detected.stack.push("django");
972
1486
  if (content.includes("flask")) detected.stack.push("flask");
1487
+ if (content.includes("pydantic")) detected.stack.push("pydantic");
1488
+ if (content.includes("sqlalchemy")) detected.stack.push("sqlalchemy");
1489
+ if (content.includes("pytest")) detected.stack.push("pytest");
1490
+ if (content.includes("ruff")) detected.stack.push("ruff");
1491
+ if (content.includes("mypy")) detected.stack.push("mypy");
973
1492
  detected.commands.test = "pytest";
974
- detected.commands.lint = "ruff check";
1493
+ detected.commands.lint = "ruff check .";
1494
+ if (content.includes("[tool.poetry]")) {
1495
+ detected.packageManager = "yarn";
1496
+ detected.commands.dev = "poetry run python -m uvicorn main:app --reload";
1497
+ } else if (await fileExists(join4(cwd, "uv.lock"))) {
1498
+ detected.commands.dev = "uv run python main.py";
1499
+ }
975
1500
  return detected;
976
1501
  } catch {
977
1502
  }
978
1503
  }
979
- const requirementsPath = join3(cwd, "requirements.txt");
1504
+ const requirementsPath = join4(cwd, "requirements.txt");
980
1505
  if (await fileExists(requirementsPath)) {
981
1506
  try {
982
- const content = await readFile(requirementsPath, "utf-8");
1507
+ const content = await readFile3(requirementsPath, "utf-8");
983
1508
  detected.stack.push("python");
984
- if (content.includes("fastapi")) detected.stack.push("fastapi");
985
- if (content.includes("django")) detected.stack.push("django");
986
- if (content.includes("flask")) detected.stack.push("flask");
1509
+ detected.type = "application";
1510
+ if (content.toLowerCase().includes("fastapi")) detected.stack.push("fastapi");
1511
+ if (content.toLowerCase().includes("django")) detected.stack.push("django");
1512
+ if (content.toLowerCase().includes("flask")) detected.stack.push("flask");
987
1513
  detected.commands.test = "pytest";
988
- detected.commands.lint = "ruff check";
1514
+ detected.commands.lint = "ruff check .";
989
1515
  return detected;
990
1516
  } catch {
991
1517
  }
992
1518
  }
993
- const cargoPath = join3(cwd, "Cargo.toml");
1519
+ const cargoPath = join4(cwd, "Cargo.toml");
994
1520
  if (await fileExists(cargoPath)) {
995
1521
  try {
996
- const content = await readFile(cargoPath, "utf-8");
1522
+ const content = await readFile3(cargoPath, "utf-8");
997
1523
  detected.stack.push("rust");
1524
+ detected.type = "application";
998
1525
  const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
999
1526
  if (nameMatch) detected.name = nameMatch[1];
1527
+ if (content.includes("actix-web")) detected.stack.push("actix");
1528
+ if (content.includes("axum")) detected.stack.push("axum");
1529
+ if (content.includes("tokio")) detected.stack.push("tokio");
1530
+ if (content.includes("serde")) detected.stack.push("serde");
1531
+ if (content.includes("sqlx")) detected.stack.push("sqlx");
1000
1532
  detected.commands.build = "cargo build";
1001
1533
  detected.commands.test = "cargo test";
1002
1534
  detected.commands.lint = "cargo clippy";
1535
+ detected.commands.dev = "cargo run";
1003
1536
  return detected;
1004
1537
  } catch {
1005
1538
  }
1006
1539
  }
1007
- const goModPath = join3(cwd, "go.mod");
1540
+ const goModPath = join4(cwd, "go.mod");
1008
1541
  if (await fileExists(goModPath)) {
1009
1542
  try {
1010
- const content = await readFile(goModPath, "utf-8");
1543
+ const content = await readFile3(goModPath, "utf-8");
1011
1544
  detected.stack.push("go");
1545
+ detected.type = "application";
1012
1546
  const moduleMatch = content.match(/module\s+(\S+)/);
1013
1547
  if (moduleMatch) {
1014
1548
  const parts = moduleMatch[1].split("/");
1015
1549
  detected.name = parts[parts.length - 1];
1016
1550
  }
1551
+ if (content.includes("gin-gonic/gin")) detected.stack.push("gin");
1552
+ if (content.includes("gofiber/fiber")) detected.stack.push("fiber");
1553
+ if (content.includes("labstack/echo")) detected.stack.push("echo");
1554
+ if (content.includes("gorm.io/gorm")) detected.stack.push("gorm");
1017
1555
  detected.commands.build = "go build";
1018
1556
  detected.commands.test = "go test ./...";
1019
1557
  detected.commands.lint = "golangci-lint run";
1558
+ detected.commands.dev = "go run .";
1020
1559
  return detected;
1021
1560
  } catch {
1022
1561
  }
1023
1562
  }
1024
- const makefilePath = join3(cwd, "Makefile");
1563
+ const makefilePath = join4(cwd, "Makefile");
1025
1564
  if (await fileExists(makefilePath)) {
1026
1565
  try {
1027
- const content = await readFile(makefilePath, "utf-8");
1566
+ const content = await readFile3(makefilePath, "utf-8");
1028
1567
  if (content.includes("build:")) detected.commands.build = "make build";
1029
1568
  if (content.includes("test:")) detected.commands.test = "make test";
1030
1569
  if (content.includes("lint:")) detected.commands.lint = "make lint";
1570
+ if (content.includes("dev:")) detected.commands.dev = "make dev";
1571
+ if (content.includes("run:")) detected.commands.dev = detected.commands.dev || "make run";
1031
1572
  if (Object.keys(detected.commands).length > 0) {
1573
+ detected.type = "application";
1032
1574
  return detected;
1033
1575
  }
1034
1576
  } catch {
1035
1577
  }
1036
1578
  }
1579
+ if (await fileExists(join4(cwd, "Dockerfile")) || await fileExists(join4(cwd, "docker-compose.yml"))) {
1580
+ detected.stack.push("docker");
1581
+ detected.type = "application";
1582
+ }
1037
1583
  return detected.stack.length > 0 || detected.name ? detected : null;
1038
1584
  }
1039
- async function fileExists(path) {
1585
+ async function fileExists(path2) {
1040
1586
  try {
1041
- await access2(path);
1587
+ await access3(path2);
1042
1588
  return true;
1043
1589
  } catch {
1044
1590
  return false;
@@ -1049,13 +1595,28 @@ async function fileExists(path) {
1049
1595
  var LYNXPROMPT_DIR = ".lynxprompt";
1050
1596
  var LYNXPROMPT_CONFIG = ".lynxprompt/conf.yml";
1051
1597
  var LYNXPROMPT_RULES = ".lynxprompt/rules";
1598
+ var AGENT_FILES = [
1599
+ { name: "AGENTS.md", agent: "Universal (AGENTS.md)" },
1600
+ { name: "CLAUDE.md", agent: "Claude Code" },
1601
+ { name: ".windsurfrules", agent: "Windsurf" },
1602
+ { name: ".clinerules", agent: "Cline" },
1603
+ { name: ".goosehints", agent: "Goose" },
1604
+ { name: "AIDER.md", agent: "Aider" },
1605
+ { name: ".github/copilot-instructions.md", agent: "GitHub Copilot" },
1606
+ { name: ".zed/instructions.md", agent: "Zed" }
1607
+ ];
1608
+ var AGENT_DIRS = [
1609
+ { path: ".cursor/rules", agent: "Cursor" },
1610
+ { path: ".amazonq/rules", agent: "Amazon Q" },
1611
+ { path: ".augment/rules", agent: "Augment Code" }
1612
+ ];
1052
1613
  async function scanForExistingFiles(cwd) {
1053
1614
  const detected = [];
1054
1615
  for (const file of AGENT_FILES) {
1055
- const filePath = join4(cwd, file.name);
1056
- if (existsSync2(filePath)) {
1616
+ const filePath = join5(cwd, file.name);
1617
+ if (existsSync3(filePath)) {
1057
1618
  try {
1058
- const content = await readFile2(filePath, "utf-8");
1619
+ const content = await readFile4(filePath, "utf-8");
1059
1620
  detected.push({ path: file.name, agent: file.agent, content });
1060
1621
  } catch {
1061
1622
  detected.push({ path: file.name, agent: file.agent });
@@ -1063,8 +1624,8 @@ async function scanForExistingFiles(cwd) {
1063
1624
  }
1064
1625
  }
1065
1626
  for (const dir of AGENT_DIRS) {
1066
- const dirPath = join4(cwd, dir.path);
1067
- if (existsSync2(dirPath)) {
1627
+ const dirPath = join5(cwd, dir.path);
1628
+ if (existsSync3(dirPath)) {
1068
1629
  detected.push({ path: dir.path, agent: dir.agent });
1069
1630
  }
1070
1631
  }
@@ -1152,13 +1713,16 @@ function createDefaultConfig(exporters = ["agents"]) {
1152
1713
  }
1153
1714
  ]
1154
1715
  };
1155
- return yaml.stringify(config2);
1716
+ return yaml2.stringify(config2);
1156
1717
  }
1157
1718
  function createLynxpromptReadme() {
1158
1719
  return `# .lynxprompt
1159
1720
 
1160
1721
  This directory contains your LynxPrompt configuration and rules.
1161
1722
 
1723
+ > **Note**: This is an advanced setup for managing rules across multiple AI editors.
1724
+ > Most users should use \`lynxp wizard\` instead for simple, direct file generation.
1725
+
1162
1726
  ## Directory structure
1163
1727
 
1164
1728
  - **\`rules/\`** - Your AI rules. Edit files here, then sync to agents.
@@ -1182,7 +1746,7 @@ After editing rules, run:
1182
1746
  lynxp sync
1183
1747
  \`\`\`
1184
1748
 
1185
- This exports your rules to the configured agent formats (AGENTS.md, .cursorrules, etc.)
1749
+ This exports your rules to the configured agent formats (AGENTS.md, .cursor/rules/, etc.)
1186
1750
 
1187
1751
  ## More information
1188
1752
 
@@ -1192,23 +1756,41 @@ This exports your rules to the configured agent formats (AGENTS.md, .cursorrules
1192
1756
  }
1193
1757
  async function initCommand(options) {
1194
1758
  console.log();
1195
- console.log(chalk6.cyan("\u{1F431} LynxPrompt Init"));
1759
+ console.log(chalk7.cyan("\u{1F431} LynxPrompt Init"));
1760
+ console.log(chalk7.gray("Advanced mode: Multi-editor rule management"));
1196
1761
  console.log();
1762
+ if (!options.yes && !options.force) {
1763
+ console.log(chalk7.yellow("\u{1F4A1} Tip: Most users should use 'lynxp wizard' instead."));
1764
+ console.log(chalk7.gray(" The wizard generates files directly without the .lynxprompt/ folder."));
1765
+ console.log();
1766
+ const { proceed } = await prompts3({
1767
+ type: "confirm",
1768
+ name: "proceed",
1769
+ message: "Continue with advanced setup?",
1770
+ initial: false
1771
+ });
1772
+ if (!proceed) {
1773
+ console.log();
1774
+ console.log(chalk7.gray("Run 'lynxp wizard' for simple file generation."));
1775
+ return;
1776
+ }
1777
+ console.log();
1778
+ }
1197
1779
  const cwd = process.cwd();
1198
1780
  const projectName = basename(cwd);
1199
- const lynxpromptDir = join4(cwd, LYNXPROMPT_DIR);
1200
- const configPath = join4(cwd, LYNXPROMPT_CONFIG);
1201
- const rulesDir = join4(cwd, LYNXPROMPT_RULES);
1202
- if (existsSync2(configPath) && !options.force) {
1203
- console.log(chalk6.yellow("LynxPrompt is already initialized in this project."));
1204
- console.log(chalk6.gray(`Config: ${LYNXPROMPT_CONFIG}`));
1205
- console.log(chalk6.gray(`Rules: ${LYNXPROMPT_RULES}/`));
1206
- console.log();
1207
- console.log(chalk6.gray("Run 'lynxp sync' to export rules to your agents."));
1208
- console.log(chalk6.gray("Run 'lynxp wizard' to generate new configurations."));
1781
+ const lynxpromptDir = join5(cwd, LYNXPROMPT_DIR);
1782
+ const configPath = join5(cwd, LYNXPROMPT_CONFIG);
1783
+ const rulesDir = join5(cwd, LYNXPROMPT_RULES);
1784
+ if (existsSync3(configPath) && !options.force) {
1785
+ console.log(chalk7.yellow("LynxPrompt is already initialized in this project."));
1786
+ console.log(chalk7.gray(`Config: ${LYNXPROMPT_CONFIG}`));
1787
+ console.log(chalk7.gray(`Rules: ${LYNXPROMPT_RULES}/`));
1788
+ console.log();
1789
+ console.log(chalk7.gray("Run 'lynxp sync' to export rules to your agents."));
1790
+ console.log(chalk7.gray("Run 'lynxp wizard' to generate new configurations."));
1209
1791
  return;
1210
1792
  }
1211
- const spinner = ora5("Scanning project...").start();
1793
+ const spinner = ora6("Scanning project...").start();
1212
1794
  const [projectInfo, agentDetection] = await Promise.all([
1213
1795
  detectProject(cwd),
1214
1796
  Promise.resolve(detectAgents(cwd))
@@ -1216,29 +1798,28 @@ async function initCommand(options) {
1216
1798
  const existingFiles = await scanForExistingFiles(cwd);
1217
1799
  spinner.stop();
1218
1800
  if (projectInfo) {
1219
- console.log(chalk6.gray("Detected project:"));
1220
- if (projectInfo.name) console.log(chalk6.gray(` Name: ${projectInfo.name}`));
1221
- if (projectInfo.stack.length > 0) console.log(chalk6.gray(` Stack: ${projectInfo.stack.join(", ")}`));
1222
- if (projectInfo.packageManager) console.log(chalk6.gray(` Package manager: ${projectInfo.packageManager}`));
1801
+ console.log(chalk7.green("\u2713 Detected project:"));
1802
+ if (projectInfo.name) console.log(chalk7.gray(` Name: ${projectInfo.name}`));
1803
+ if (projectInfo.stack.length > 0) console.log(chalk7.gray(` Stack: ${projectInfo.stack.join(", ")}`));
1804
+ if (projectInfo.packageManager) console.log(chalk7.gray(` Package manager: ${projectInfo.packageManager}`));
1223
1805
  console.log();
1224
1806
  }
1225
1807
  if (agentDetection.detected.length > 0) {
1226
- console.log(chalk6.green(`Detected ${agentDetection.detected.length} AI agent${agentDetection.detected.length === 1 ? "" : "s"}:`));
1808
+ console.log(chalk7.green(`\u2713 Detected ${agentDetection.detected.length} AI agent${agentDetection.detected.length === 1 ? "" : "s"}:`));
1227
1809
  for (const detected of agentDetection.detected) {
1228
- const rules = detected.ruleCount > 0 ? chalk6.gray(` (${detected.ruleCount} sections)`) : "";
1229
- console.log(` ${chalk6.cyan("\u2713")} ${detected.agent.name}${rules}`);
1810
+ const rules = detected.ruleCount > 0 ? chalk7.gray(` (${detected.ruleCount} sections)`) : "";
1811
+ console.log(` ${chalk7.cyan("\u2022")} ${detected.agent.name}${rules}`);
1230
1812
  }
1231
1813
  console.log();
1232
1814
  }
1233
1815
  if (existingFiles.length > 0) {
1234
- console.log(chalk6.green("Found existing AI configuration files:"));
1235
- console.log();
1816
+ console.log(chalk7.green("\u2713 Found existing AI configuration files:"));
1236
1817
  for (const file of existingFiles) {
1237
- console.log(` ${chalk6.cyan(file.path)} ${chalk6.gray(`(${file.agent})`)}`);
1818
+ console.log(` ${chalk7.cyan(file.path)} ${chalk7.gray(`(${file.agent})`)}`);
1238
1819
  }
1239
1820
  console.log();
1240
1821
  if (!options.yes) {
1241
- const { action } = await prompts2({
1822
+ const { action } = await prompts3({
1242
1823
  type: "select",
1243
1824
  name: "action",
1244
1825
  message: "What would you like to do?",
@@ -1249,67 +1830,67 @@ async function initCommand(options) {
1249
1830
  ]
1250
1831
  });
1251
1832
  if (action === "cancel" || !action) {
1252
- console.log(chalk6.gray("Cancelled."));
1833
+ console.log(chalk7.gray("Cancelled."));
1253
1834
  return;
1254
1835
  }
1255
1836
  if (action === "import") {
1256
- await mkdir2(rulesDir, { recursive: true });
1837
+ await mkdir3(rulesDir, { recursive: true });
1257
1838
  let importedCount = 0;
1258
1839
  for (const file of existingFiles) {
1259
1840
  if (file.content) {
1260
1841
  const ruleName = file.path.replace(/^\./, "").replace(/\//g, "-").replace(/\.md$/, "") + ".md";
1261
- const rulePath = join4(rulesDir, ruleName);
1262
- await writeFile2(rulePath, file.content, "utf-8");
1263
- console.log(chalk6.gray(` Imported: ${file.path} \u2192 .lynxprompt/rules/${ruleName}`));
1842
+ const rulePath = join5(rulesDir, ruleName);
1843
+ await writeFile3(rulePath, file.content, "utf-8");
1844
+ console.log(chalk7.gray(` Imported: ${file.path} \u2192 .lynxprompt/rules/${ruleName}`));
1264
1845
  importedCount++;
1265
1846
  }
1266
1847
  }
1267
1848
  if (importedCount === 0) {
1268
- const starterPath = join4(rulesDir, "agents.md");
1269
- await writeFile2(starterPath, createStarterAgentsMd(projectName), "utf-8");
1270
- console.log(chalk6.gray(" Created starter: .lynxprompt/rules/agents.md"));
1849
+ const starterPath = join5(rulesDir, "agents.md");
1850
+ await writeFile3(starterPath, createStarterAgentsMd(projectName), "utf-8");
1851
+ console.log(chalk7.gray(" Created starter: .lynxprompt/rules/agents.md"));
1271
1852
  }
1272
1853
  } else {
1273
- await mkdir2(rulesDir, { recursive: true });
1274
- const starterPath = join4(rulesDir, "agents.md");
1275
- await writeFile2(starterPath, createStarterAgentsMd(projectName), "utf-8");
1276
- console.log(chalk6.gray("Created starter: .lynxprompt/rules/agents.md"));
1854
+ await mkdir3(rulesDir, { recursive: true });
1855
+ const starterPath = join5(rulesDir, "agents.md");
1856
+ await writeFile3(starterPath, createStarterAgentsMd(projectName), "utf-8");
1857
+ console.log(chalk7.gray("Created starter: .lynxprompt/rules/agents.md"));
1277
1858
  }
1278
1859
  } else {
1279
- await mkdir2(rulesDir, { recursive: true });
1860
+ await mkdir3(rulesDir, { recursive: true });
1280
1861
  for (const file of existingFiles) {
1281
1862
  if (file.content) {
1282
1863
  const ruleName = file.path.replace(/^\./, "").replace(/\//g, "-").replace(/\.md$/, "") + ".md";
1283
- const rulePath = join4(rulesDir, ruleName);
1284
- await writeFile2(rulePath, file.content, "utf-8");
1864
+ const rulePath = join5(rulesDir, ruleName);
1865
+ await writeFile3(rulePath, file.content, "utf-8");
1285
1866
  }
1286
1867
  }
1287
1868
  }
1288
1869
  } else {
1289
- console.log(chalk6.gray("No existing AI configuration files found."));
1870
+ console.log(chalk7.gray("No existing AI configuration files found."));
1290
1871
  console.log();
1291
1872
  if (!options.yes) {
1292
- const { create } = await prompts2({
1873
+ const { create } = await prompts3({
1293
1874
  type: "confirm",
1294
1875
  name: "create",
1295
- message: "Create a starter AGENTS.md template?",
1876
+ message: "Create a starter template?",
1296
1877
  initial: true
1297
1878
  });
1298
1879
  if (!create) {
1299
- console.log(chalk6.gray("Cancelled."));
1880
+ console.log(chalk7.gray("Cancelled."));
1300
1881
  return;
1301
1882
  }
1302
1883
  }
1303
- await mkdir2(rulesDir, { recursive: true });
1304
- const starterPath = join4(rulesDir, "agents.md");
1305
- await writeFile2(starterPath, createStarterAgentsMd(projectName), "utf-8");
1306
- console.log(chalk6.gray("Created: .lynxprompt/rules/agents.md"));
1884
+ await mkdir3(rulesDir, { recursive: true });
1885
+ const starterPath = join5(rulesDir, "agents.md");
1886
+ await writeFile3(starterPath, createStarterAgentsMd(projectName), "utf-8");
1887
+ console.log(chalk7.gray("Created: .lynxprompt/rules/agents.md"));
1307
1888
  }
1308
1889
  let exporters = [];
1309
1890
  if (agentDetection.detected.length > 0) {
1310
1891
  exporters = agentDetection.detected.map((d) => d.agent.id);
1311
1892
  if (agentDetection.detected.length > 3 && !options.yes) {
1312
- const { selected } = await prompts2({
1893
+ const { selected } = await prompts3({
1313
1894
  type: "multiselect",
1314
1895
  name: "selected",
1315
1896
  message: "Select agents to enable:",
@@ -1328,7 +1909,7 @@ async function initCommand(options) {
1328
1909
  exporters = ["agents"];
1329
1910
  if (!options.yes) {
1330
1911
  const popular = getPopularAgents();
1331
- const { selected } = await prompts2({
1912
+ const { selected } = await prompts3({
1332
1913
  type: "multiselect",
1333
1914
  name: "selected",
1334
1915
  message: "Select AI agents to sync to:",
@@ -1345,42 +1926,43 @@ async function initCommand(options) {
1345
1926
  }
1346
1927
  }
1347
1928
  }
1348
- console.log(chalk6.gray(`Enabling ${exporters.length} exporter${exporters.length === 1 ? "" : "s"}: ${exporters.join(", ")}`));
1349
- await mkdir2(dirname2(configPath), { recursive: true });
1350
- await writeFile2(configPath, createDefaultConfig(exporters), "utf-8");
1351
- const readmePath = join4(lynxpromptDir, "README.md");
1352
- await writeFile2(readmePath, createLynxpromptReadme(), "utf-8");
1353
- const gitignorePath = join4(lynxpromptDir, ".gitignore");
1929
+ console.log(chalk7.gray(`Enabling ${exporters.length} exporter${exporters.length === 1 ? "" : "s"}: ${exporters.join(", ")}`));
1930
+ await mkdir3(dirname3(configPath), { recursive: true });
1931
+ await writeFile3(configPath, createDefaultConfig(exporters), "utf-8");
1932
+ const readmePath = join5(lynxpromptDir, "README.md");
1933
+ await writeFile3(readmePath, createLynxpromptReadme(), "utf-8");
1934
+ const gitignorePath = join5(lynxpromptDir, ".gitignore");
1354
1935
  const gitignoreContent = `# Local state files
1355
1936
  .cache/
1356
1937
  .backups/
1357
1938
  `;
1358
- await writeFile2(gitignorePath, gitignoreContent, "utf-8");
1939
+ await writeFile3(gitignorePath, gitignoreContent, "utf-8");
1359
1940
  console.log();
1360
- console.log(chalk6.green("\u2705 LynxPrompt initialized!"));
1941
+ console.log(chalk7.green("\u2705 LynxPrompt initialized!"));
1361
1942
  console.log();
1362
- console.log(chalk6.gray("Created:"));
1363
- console.log(chalk6.gray(` ${LYNXPROMPT_CONFIG} - Configuration`));
1364
- console.log(chalk6.gray(` ${LYNXPROMPT_RULES}/ - Your rules (edit here)`));
1943
+ console.log(chalk7.gray("Created:"));
1944
+ console.log(chalk7.gray(` ${LYNXPROMPT_CONFIG} - Configuration`));
1945
+ console.log(chalk7.gray(` ${LYNXPROMPT_RULES}/ - Your rules (edit here)`));
1365
1946
  console.log();
1366
- console.log(chalk6.cyan("Next steps:"));
1367
- console.log(chalk6.gray(" 1. Edit your rules in .lynxprompt/rules/"));
1368
- console.log(chalk6.gray(" 2. Run 'lynxp sync' to export to your AI agents"));
1369
- console.log(chalk6.gray(" 3. Or run 'lynxp agents' to manage which agents to sync to"));
1947
+ console.log(chalk7.cyan("Next steps:"));
1948
+ console.log(chalk7.gray(" 1. Edit your rules in .lynxprompt/rules/"));
1949
+ console.log(chalk7.gray(" 2. Run 'lynxp sync' to export to your AI agents"));
1950
+ console.log(chalk7.gray(" 3. Or run 'lynxp agents' to manage which agents to sync to"));
1370
1951
  console.log();
1371
1952
  }
1372
1953
 
1373
1954
  // src/commands/wizard.ts
1374
- import chalk7 from "chalk";
1375
- import prompts3 from "prompts";
1376
- import ora6 from "ora";
1377
- import { writeFile as writeFile3, mkdir as mkdir3, access as access4 } from "fs/promises";
1378
- import { join as join5, dirname as dirname3 } from "path";
1955
+ import chalk8 from "chalk";
1956
+ import prompts4 from "prompts";
1957
+ import ora7 from "ora";
1958
+ import { writeFile as writeFile4, mkdir as mkdir4, access as access5 } from "fs/promises";
1959
+ import { join as join6, dirname as dirname4 } from "path";
1379
1960
 
1380
1961
  // src/utils/generator.ts
1381
1962
  var PLATFORM_FILES = {
1382
- cursor: ".cursorrules",
1383
- claude: "AGENTS.md",
1963
+ agents: "AGENTS.md",
1964
+ cursor: ".cursor/rules/project.mdc",
1965
+ claude: "CLAUDE.md",
1384
1966
  copilot: ".github/copilot-instructions.md",
1385
1967
  windsurf: ".windsurfrules",
1386
1968
  zed: ".zed/instructions.md"
@@ -1490,13 +2072,25 @@ function generateConfig(options) {
1490
2072
  }
1491
2073
  function generateFileContent(options, platform) {
1492
2074
  const sections = [];
1493
- const isMarkdown = platform !== "cursor" && platform !== "windsurf";
2075
+ const isMdc = platform === "cursor";
2076
+ const isPlainText = platform === "windsurf";
2077
+ const isMarkdown = !isMdc && !isPlainText;
2078
+ if (isMdc) {
2079
+ sections.push("---");
2080
+ sections.push(`description: "${options.name} - AI coding rules"`);
2081
+ sections.push('globs: ["**/*"]');
2082
+ sections.push("alwaysApply: true");
2083
+ sections.push("---");
2084
+ sections.push("");
2085
+ sections.push(`# ${options.name} - AI Assistant Configuration`);
2086
+ sections.push("");
2087
+ }
1494
2088
  if (isMarkdown) {
1495
2089
  sections.push(`# ${options.name} - AI Assistant Configuration`);
1496
2090
  sections.push("");
1497
2091
  }
1498
2092
  const personaDesc = PERSONA_DESCRIPTIONS[options.persona] || options.persona;
1499
- if (isMarkdown) {
2093
+ if (isMarkdown || isMdc) {
1500
2094
  sections.push("## Persona");
1501
2095
  sections.push("");
1502
2096
  sections.push(`You are ${personaDesc}. You assist developers working on ${options.name}.`);
@@ -1509,14 +2103,14 @@ function generateFileContent(options, platform) {
1509
2103
  }
1510
2104
  sections.push("");
1511
2105
  if (options.stack.length > 0) {
1512
- if (isMarkdown) {
2106
+ if (isMarkdown || isMdc) {
1513
2107
  sections.push("## Tech Stack");
1514
2108
  sections.push("");
1515
2109
  } else {
1516
2110
  sections.push("Tech Stack:");
1517
2111
  }
1518
2112
  const stackList = options.stack.map((s) => STACK_NAMES[s] || s);
1519
- if (isMarkdown) {
2113
+ if (isMarkdown || isMdc) {
1520
2114
  for (const tech of stackList) {
1521
2115
  sections.push(`- ${tech}`);
1522
2116
  }
@@ -1527,7 +2121,7 @@ function generateFileContent(options, platform) {
1527
2121
  }
1528
2122
  const hasCommands = Object.values(options.commands).some(Boolean);
1529
2123
  if (hasCommands) {
1530
- if (isMarkdown) {
2124
+ if (isMarkdown || isMdc) {
1531
2125
  sections.push("## Commands");
1532
2126
  sections.push("");
1533
2127
  sections.push("Use these commands for common tasks:");
@@ -1537,25 +2131,25 @@ function generateFileContent(options, platform) {
1537
2131
  sections.push("Commands:");
1538
2132
  }
1539
2133
  if (options.commands.build) {
1540
- sections.push(isMarkdown ? `# Build: ${options.commands.build}` : `- Build: ${options.commands.build}`);
2134
+ sections.push(isMarkdown || isMdc ? `# Build: ${options.commands.build}` : `- Build: ${options.commands.build}`);
1541
2135
  }
1542
2136
  if (options.commands.test) {
1543
- sections.push(isMarkdown ? `# Test: ${options.commands.test}` : `- Test: ${options.commands.test}`);
2137
+ sections.push(isMarkdown || isMdc ? `# Test: ${options.commands.test}` : `- Test: ${options.commands.test}`);
1544
2138
  }
1545
2139
  if (options.commands.lint) {
1546
- sections.push(isMarkdown ? `# Lint: ${options.commands.lint}` : `- Lint: ${options.commands.lint}`);
2140
+ sections.push(isMarkdown || isMdc ? `# Lint: ${options.commands.lint}` : `- Lint: ${options.commands.lint}`);
1547
2141
  }
1548
2142
  if (options.commands.dev) {
1549
- sections.push(isMarkdown ? `# Dev: ${options.commands.dev}` : `- Dev: ${options.commands.dev}`);
2143
+ sections.push(isMarkdown || isMdc ? `# Dev: ${options.commands.dev}` : `- Dev: ${options.commands.dev}`);
1550
2144
  }
1551
- if (isMarkdown) {
2145
+ if (isMarkdown || isMdc) {
1552
2146
  sections.push("```");
1553
2147
  }
1554
2148
  sections.push("");
1555
2149
  }
1556
2150
  const boundaries = BOUNDARIES[options.boundaries];
1557
2151
  if (boundaries) {
1558
- if (isMarkdown) {
2152
+ if (isMarkdown || isMdc) {
1559
2153
  sections.push("## Boundaries");
1560
2154
  sections.push("");
1561
2155
  sections.push("### \u2705 Always (do without asking)");
@@ -1595,7 +2189,7 @@ function generateFileContent(options, platform) {
1595
2189
  }
1596
2190
  sections.push("");
1597
2191
  }
1598
- if (isMarkdown) {
2192
+ if (isMarkdown || isMdc) {
1599
2193
  sections.push("## Code Style");
1600
2194
  sections.push("");
1601
2195
  sections.push("Follow these conventions:");
@@ -1631,7 +2225,7 @@ function generateFileContent(options, platform) {
1631
2225
  sections.push("- Keep functions focused and testable");
1632
2226
  sections.push("");
1633
2227
  }
1634
- if (isMarkdown) {
2228
+ if (isMarkdown || isMdc) {
1635
2229
  sections.push("---");
1636
2230
  sections.push("");
1637
2231
  sections.push(`*Generated by [LynxPrompt](https://lynxprompt.com) CLI*`);
@@ -1640,6 +2234,24 @@ function generateFileContent(options, platform) {
1640
2234
  }
1641
2235
 
1642
2236
  // src/commands/wizard.ts
2237
+ var OUTPUT_FORMATS = [
2238
+ {
2239
+ title: "AGENTS.md (Universal)",
2240
+ value: "agents",
2241
+ description: "Works with Claude Code, GitHub Copilot, Aider, and most AI editors",
2242
+ recommended: true
2243
+ },
2244
+ {
2245
+ title: "Cursor (.cursor/rules/)",
2246
+ value: "cursor",
2247
+ description: "Cursor IDE with MDC format"
2248
+ },
2249
+ {
2250
+ title: "Multiple formats",
2251
+ value: "multiple",
2252
+ description: "Select multiple AI editors to generate for"
2253
+ }
2254
+ ];
1643
2255
  var TECH_STACKS = [
1644
2256
  { title: "TypeScript", value: "typescript" },
1645
2257
  { title: "JavaScript", value: "javascript" },
@@ -1667,22 +2279,30 @@ var FRAMEWORKS = [
1667
2279
  { title: "Laravel", value: "laravel" }
1668
2280
  ];
1669
2281
  var PLATFORMS = [
1670
- { title: "Cursor (.cursorrules)", value: "cursor", filename: ".cursorrules" },
1671
- { title: "Claude Code (AGENTS.md)", value: "claude", filename: "AGENTS.md" },
2282
+ { title: "AGENTS.md (Universal)", value: "agents", filename: "AGENTS.md" },
2283
+ { title: "Cursor (.cursor/rules/)", value: "cursor", filename: ".cursor/rules/project.mdc" },
2284
+ { title: "Claude Code (CLAUDE.md)", value: "claude", filename: "CLAUDE.md" },
1672
2285
  { title: "GitHub Copilot", value: "copilot", filename: ".github/copilot-instructions.md" },
1673
2286
  { title: "Windsurf (.windsurfrules)", value: "windsurf", filename: ".windsurfrules" },
1674
2287
  { title: "Zed", value: "zed", filename: ".zed/instructions.md" }
1675
2288
  ];
1676
2289
  var PERSONAS = [
2290
+ { title: "Full-Stack Developer - Complete application setups", value: "fullstack" },
1677
2291
  { title: "Backend Developer - APIs, databases, microservices", value: "backend" },
1678
2292
  { title: "Frontend Developer - UI, components, styling", value: "frontend" },
1679
- { title: "Full-Stack Developer - Complete application setups", value: "fullstack" },
1680
2293
  { title: "DevOps Engineer - Infrastructure, CI/CD, containers", value: "devops" },
1681
2294
  { title: "Data Engineer - Pipelines, ETL, databases", value: "data" },
1682
2295
  { title: "Security Engineer - Secure code, vulnerabilities", value: "security" },
1683
2296
  { title: "Custom...", value: "custom" }
1684
2297
  ];
1685
2298
  var BOUNDARY_PRESETS = [
2299
+ {
2300
+ title: "Standard - Balance of freedom and safety (recommended)",
2301
+ value: "standard",
2302
+ always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
2303
+ askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
2304
+ never: ["Delete production data", "Modify .env secrets", "Force push"]
2305
+ },
1686
2306
  {
1687
2307
  title: "Conservative - Ask before most changes",
1688
2308
  value: "conservative",
@@ -1690,13 +2310,6 @@ var BOUNDARY_PRESETS = [
1690
2310
  askFirst: ["Modify any file", "Add dependencies", "Create files", "Run tests"],
1691
2311
  never: ["Delete files", "Modify .env", "Push to git"]
1692
2312
  },
1693
- {
1694
- title: "Standard - Balance of freedom and safety",
1695
- value: "standard",
1696
- always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
1697
- askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
1698
- never: ["Delete production data", "Modify .env secrets", "Force push"]
1699
- },
1700
2313
  {
1701
2314
  title: "Permissive - AI can modify freely within src/",
1702
2315
  value: "permissive",
@@ -1707,24 +2320,34 @@ var BOUNDARY_PRESETS = [
1707
2320
  ];
1708
2321
  async function wizardCommand(options) {
1709
2322
  console.log();
1710
- console.log(chalk7.cyan("\u{1F431} Welcome to LynxPrompt Wizard!"));
2323
+ console.log(chalk8.cyan("\u{1F431} LynxPrompt Wizard"));
2324
+ console.log(chalk8.gray("Generate AI IDE configuration in seconds"));
1711
2325
  console.log();
1712
2326
  const detected = await detectProject(process.cwd());
1713
2327
  if (detected) {
1714
- console.log(chalk7.gray("Detected project configuration:"));
1715
- if (detected.name) console.log(chalk7.gray(` Name: ${detected.name}`));
1716
- if (detected.stack.length > 0) console.log(chalk7.gray(` Stack: ${detected.stack.join(", ")}`));
1717
- if (detected.commands.build) console.log(chalk7.gray(` Build: ${detected.commands.build}`));
1718
- if (detected.commands.test) console.log(chalk7.gray(` Test: ${detected.commands.test}`));
2328
+ console.log(chalk8.green("\u2713 Detected project:"));
2329
+ if (detected.name) console.log(chalk8.gray(` Name: ${detected.name}`));
2330
+ if (detected.stack.length > 0) console.log(chalk8.gray(` Stack: ${detected.stack.join(", ")}`));
2331
+ if (detected.packageManager) console.log(chalk8.gray(` Package manager: ${detected.packageManager}`));
2332
+ if (detected.commands.build) console.log(chalk8.gray(` Build: ${detected.commands.build}`));
2333
+ if (detected.commands.test) console.log(chalk8.gray(` Test: ${detected.commands.test}`));
1719
2334
  console.log();
1720
2335
  }
1721
2336
  let config2;
1722
2337
  if (options.yes) {
2338
+ let platforms;
2339
+ if (options.format) {
2340
+ platforms = options.format.split(",").map((f) => f.trim());
2341
+ } else if (options.platforms) {
2342
+ platforms = options.platforms.split(",").map((p) => p.trim());
2343
+ } else {
2344
+ platforms = ["agents"];
2345
+ }
1723
2346
  config2 = {
1724
2347
  name: options.name || detected?.name || "my-project",
1725
2348
  description: options.description || "",
1726
2349
  stack: options.stack?.split(",").map((s) => s.trim()) || detected?.stack || [],
1727
- platforms: options.platforms?.split(",").map((s) => s.trim()) || ["cursor", "claude"],
2350
+ platforms,
1728
2351
  persona: options.persona || "fullstack",
1729
2352
  boundaries: options.boundaries || "standard",
1730
2353
  commands: detected?.commands || {}
@@ -1732,123 +2355,164 @@ async function wizardCommand(options) {
1732
2355
  } else {
1733
2356
  config2 = await runInteractiveWizard(options, detected);
1734
2357
  }
1735
- const spinner = ora6("Generating configuration files...").start();
2358
+ const spinner = ora7("Generating configuration...").start();
1736
2359
  try {
1737
2360
  const files = generateConfig(config2);
1738
2361
  spinner.stop();
1739
2362
  console.log();
1740
- console.log(chalk7.green("\u2705 Generated files:"));
2363
+ console.log(chalk8.green("\u2705 Generated:"));
1741
2364
  for (const [filename, content] of Object.entries(files)) {
1742
- const outputPath = join5(process.cwd(), filename);
2365
+ const outputPath = join6(process.cwd(), filename);
1743
2366
  let exists = false;
1744
2367
  try {
1745
- await access4(outputPath);
2368
+ await access5(outputPath);
1746
2369
  exists = true;
1747
2370
  } catch {
1748
2371
  }
1749
2372
  if (exists && !options.yes) {
1750
- const response = await prompts3({
2373
+ const response = await prompts4({
1751
2374
  type: "confirm",
1752
2375
  name: "overwrite",
1753
2376
  message: `${filename} already exists. Overwrite?`,
1754
2377
  initial: false
1755
2378
  });
1756
2379
  if (!response.overwrite) {
1757
- console.log(chalk7.yellow(` Skipped: ${filename}`));
2380
+ console.log(chalk8.yellow(` Skipped: ${filename}`));
1758
2381
  continue;
1759
2382
  }
1760
2383
  }
1761
- const dir = dirname3(outputPath);
2384
+ const dir = dirname4(outputPath);
1762
2385
  if (dir !== ".") {
1763
- await mkdir3(dir, { recursive: true });
2386
+ await mkdir4(dir, { recursive: true });
1764
2387
  }
1765
- await writeFile3(outputPath, content, "utf-8");
1766
- console.log(` ${chalk7.cyan(filename)}`);
2388
+ await writeFile4(outputPath, content, "utf-8");
2389
+ console.log(` ${chalk8.cyan(filename)}`);
1767
2390
  }
1768
2391
  console.log();
1769
- console.log(chalk7.gray("Your AI IDE configuration is ready!"));
1770
- console.log(chalk7.gray("The AI assistant in your IDE will now follow these instructions."));
2392
+ console.log(chalk8.gray("Your AI assistant will now follow these instructions."));
2393
+ console.log();
2394
+ console.log(chalk8.gray("Tips:"));
2395
+ console.log(chalk8.gray(" \u2022 Edit the generated file anytime to customize rules"));
2396
+ console.log(chalk8.gray(" \u2022 Run 'lynxp wizard' again to regenerate"));
2397
+ console.log(chalk8.gray(" \u2022 Run 'lynxp check' to validate your configuration"));
1771
2398
  console.log();
1772
2399
  } catch (error) {
1773
2400
  spinner.fail("Failed to generate files");
1774
- console.error(chalk7.red("An error occurred while generating configuration files."));
2401
+ console.error(chalk8.red("\n\u2717 An error occurred while generating configuration files."));
1775
2402
  if (error instanceof Error) {
1776
- console.error(chalk7.gray(error.message));
2403
+ console.error(chalk8.gray(` ${error.message}`));
1777
2404
  }
2405
+ console.error(chalk8.gray("\nTry running with --yes flag for default settings."));
1778
2406
  process.exit(1);
1779
2407
  }
1780
2408
  }
1781
2409
  async function runInteractiveWizard(options, detected) {
1782
2410
  const answers = {};
1783
- const nameResponse = await prompts3({
2411
+ let platforms;
2412
+ if (options.format) {
2413
+ platforms = options.format.split(",").map((f) => f.trim());
2414
+ } else {
2415
+ const formatResponse = await prompts4({
2416
+ type: "select",
2417
+ name: "format",
2418
+ message: "Select output format:",
2419
+ choices: OUTPUT_FORMATS.map((f) => ({
2420
+ title: f.recommended ? `${f.title} ${chalk8.green("(recommended)")}` : f.title,
2421
+ value: f.value,
2422
+ description: f.description
2423
+ })),
2424
+ initial: 0
2425
+ // AGENTS.md is default
2426
+ });
2427
+ if (formatResponse.format === "multiple") {
2428
+ const platformResponse = await prompts4({
2429
+ type: "multiselect",
2430
+ name: "platforms",
2431
+ message: "Select AI editors:",
2432
+ choices: PLATFORMS.map((p) => ({ title: p.title, value: p.value })),
2433
+ hint: "- Space to select, Enter to confirm",
2434
+ min: 1
2435
+ });
2436
+ platforms = platformResponse.platforms || ["agents"];
2437
+ } else {
2438
+ platforms = [formatResponse.format || "agents"];
2439
+ }
2440
+ }
2441
+ answers.platforms = platforms;
2442
+ const nameResponse = await prompts4({
1784
2443
  type: "text",
1785
2444
  name: "name",
1786
- message: "What's your project name?",
2445
+ message: "Project name:",
1787
2446
  initial: options.name || detected?.name || "my-project"
1788
2447
  });
1789
- answers.name = nameResponse.name;
1790
- const descResponse = await prompts3({
2448
+ answers.name = nameResponse.name || "my-project";
2449
+ const descResponse = await prompts4({
1791
2450
  type: "text",
1792
2451
  name: "description",
1793
- message: "Describe your project in one sentence:",
2452
+ message: "Brief description (optional):",
1794
2453
  initial: options.description || ""
1795
2454
  });
1796
- answers.description = descResponse.description;
2455
+ answers.description = descResponse.description || "";
1797
2456
  const allStackOptions = [...TECH_STACKS, ...FRAMEWORKS];
1798
- const stackResponse = await prompts3({
2457
+ const detectedStackSet = new Set(detected?.stack || []);
2458
+ const stackResponse = await prompts4({
1799
2459
  type: "multiselect",
1800
2460
  name: "stack",
1801
- message: "Select your tech stack:",
1802
- choices: allStackOptions,
2461
+ message: "Tech stack:",
2462
+ choices: allStackOptions.map((s) => ({
2463
+ title: s.title,
2464
+ value: s.value,
2465
+ selected: detectedStackSet.has(s.value)
2466
+ })),
1803
2467
  hint: "- Space to select, Enter to confirm"
1804
2468
  });
1805
2469
  answers.stack = stackResponse.stack || [];
1806
- const platformResponse = await prompts3({
1807
- type: "multiselect",
1808
- name: "platforms",
1809
- message: "Which AI IDEs do you use?",
1810
- choices: PLATFORMS,
1811
- hint: "- Space to select, Enter to confirm",
1812
- min: 1
1813
- });
1814
- answers.platforms = platformResponse.platforms || ["cursor"];
1815
- const personaResponse = await prompts3({
2470
+ const personaResponse = await prompts4({
1816
2471
  type: "select",
1817
2472
  name: "persona",
1818
- message: "What's the AI's persona/role?",
2473
+ message: "AI persona:",
1819
2474
  choices: PERSONAS,
1820
- initial: 2
2475
+ initial: 0
1821
2476
  // Full-stack by default
1822
2477
  });
1823
2478
  if (personaResponse.persona === "custom") {
1824
- const customPersona = await prompts3({
2479
+ const customPersona = await prompts4({
1825
2480
  type: "text",
1826
2481
  name: "value",
1827
2482
  message: "Describe the custom persona:"
1828
2483
  });
1829
2484
  answers.persona = customPersona.value || "fullstack";
1830
2485
  } else {
1831
- answers.persona = personaResponse.persona;
2486
+ answers.persona = personaResponse.persona || "fullstack";
1832
2487
  }
1833
- if (detected?.commands && Object.keys(detected.commands).length > 0) {
1834
- console.log();
1835
- console.log(chalk7.gray("Auto-detected commands:"));
1836
- if (detected.commands.build) console.log(chalk7.gray(` Build: ${detected.commands.build}`));
1837
- if (detected.commands.test) console.log(chalk7.gray(` Test: ${detected.commands.test}`));
1838
- if (detected.commands.lint) console.log(chalk7.gray(` Lint: ${detected.commands.lint}`));
1839
- if (detected.commands.dev) console.log(chalk7.gray(` Dev: ${detected.commands.dev}`));
1840
- const editCommands = await prompts3({
1841
- type: "confirm",
1842
- name: "edit",
1843
- message: "Edit these commands?",
1844
- initial: false
2488
+ const boundaryResponse = await prompts4({
2489
+ type: "select",
2490
+ name: "boundaries",
2491
+ message: "AI boundaries:",
2492
+ choices: BOUNDARY_PRESETS.map((b) => ({ title: b.title, value: b.value })),
2493
+ initial: 0
2494
+ // Standard by default
2495
+ });
2496
+ answers.boundaries = boundaryResponse.boundaries || "standard";
2497
+ if (detected?.commands && Object.keys(detected.commands).length > 0) {
2498
+ console.log();
2499
+ console.log(chalk8.gray("Auto-detected commands:"));
2500
+ if (detected.commands.build) console.log(chalk8.gray(` Build: ${detected.commands.build}`));
2501
+ if (detected.commands.test) console.log(chalk8.gray(` Test: ${detected.commands.test}`));
2502
+ if (detected.commands.lint) console.log(chalk8.gray(` Lint: ${detected.commands.lint}`));
2503
+ if (detected.commands.dev) console.log(chalk8.gray(` Dev: ${detected.commands.dev}`));
2504
+ const editCommands = await prompts4({
2505
+ type: "confirm",
2506
+ name: "edit",
2507
+ message: "Edit commands?",
2508
+ initial: false
1845
2509
  });
1846
2510
  if (editCommands.edit) {
1847
- const commandsResponse = await prompts3([
1848
- { type: "text", name: "build", message: "Build command:", initial: detected.commands.build },
1849
- { type: "text", name: "test", message: "Test command:", initial: detected.commands.test },
1850
- { type: "text", name: "lint", message: "Lint command:", initial: detected.commands.lint },
1851
- { type: "text", name: "dev", message: "Dev command:", initial: detected.commands.dev }
2511
+ const commandsResponse = await prompts4([
2512
+ { type: "text", name: "build", message: "Build:", initial: detected.commands.build },
2513
+ { type: "text", name: "test", message: "Test:", initial: detected.commands.test },
2514
+ { type: "text", name: "lint", message: "Lint:", initial: detected.commands.lint },
2515
+ { type: "text", name: "dev", message: "Dev:", initial: detected.commands.dev }
1852
2516
  ]);
1853
2517
  answers.commands = commandsResponse;
1854
2518
  } else {
@@ -1857,15 +2521,6 @@ async function runInteractiveWizard(options, detected) {
1857
2521
  } else {
1858
2522
  answers.commands = {};
1859
2523
  }
1860
- const boundaryResponse = await prompts3({
1861
- type: "select",
1862
- name: "boundaries",
1863
- message: "Select boundary preset:",
1864
- choices: BOUNDARY_PRESETS.map((b) => ({ title: b.title, value: b.value })),
1865
- initial: 1
1866
- // Standard by default
1867
- });
1868
- answers.boundaries = boundaryResponse.boundaries || "standard";
1869
2524
  return {
1870
2525
  name: answers.name,
1871
2526
  description: answers.description,
@@ -1878,52 +2533,52 @@ async function runInteractiveWizard(options, detected) {
1878
2533
  }
1879
2534
 
1880
2535
  // src/commands/search.ts
1881
- import chalk8 from "chalk";
1882
- import ora7 from "ora";
2536
+ import chalk9 from "chalk";
2537
+ import ora8 from "ora";
1883
2538
  async function searchCommand(query, options) {
1884
- const spinner = ora7(`Searching for "${query}"...`).start();
2539
+ const spinner = ora8(`Searching for "${query}"...`).start();
1885
2540
  try {
1886
2541
  const limit = parseInt(options.limit, 10) || 20;
1887
2542
  const { templates, total, hasMore } = await api.searchBlueprints(query, limit);
1888
2543
  spinner.stop();
1889
2544
  if (templates.length === 0) {
1890
2545
  console.log();
1891
- console.log(chalk8.yellow(`No blueprints found for "${query}".`));
1892
- console.log(chalk8.gray("Try different keywords or browse at https://lynxprompt.com/blueprints"));
2546
+ console.log(chalk9.yellow(`No blueprints found for "${query}".`));
2547
+ console.log(chalk9.gray("Try different keywords or browse at https://lynxprompt.com/blueprints"));
1893
2548
  return;
1894
2549
  }
1895
2550
  console.log();
1896
- console.log(chalk8.cyan(`\u{1F50D} Search Results for "${query}" (${total} found)`));
2551
+ console.log(chalk9.cyan(`\u{1F50D} Search Results for "${query}" (${total} found)`));
1897
2552
  console.log();
1898
2553
  for (const result of templates) {
1899
2554
  printSearchResult(result);
1900
2555
  }
1901
2556
  if (hasMore) {
1902
- console.log(chalk8.gray(`Showing ${templates.length} of ${total}. Use --limit to see more.`));
2557
+ console.log(chalk9.gray(`Showing ${templates.length} of ${total}. Use --limit to see more.`));
1903
2558
  }
1904
2559
  console.log();
1905
- console.log(chalk8.gray("Use 'lynxprompt pull <id>' to download a blueprint."));
2560
+ console.log(chalk9.gray("Use 'lynxprompt pull <id>' to download a blueprint."));
1906
2561
  } catch (error) {
1907
2562
  spinner.fail("Search failed");
1908
2563
  handleApiError3(error);
1909
2564
  }
1910
2565
  }
1911
2566
  function printSearchResult(result) {
1912
- const priceInfo = result.price ? chalk8.yellow(`\u20AC${(result.price / 100).toFixed(2)}`) : chalk8.green("Free");
1913
- const officialBadge = result.isOfficial ? chalk8.magenta(" \u2605 Official") : "";
1914
- console.log(` ${chalk8.bold(result.name)}${officialBadge}`);
1915
- console.log(` ${chalk8.cyan(result.id)} \u2022 ${priceInfo}`);
2567
+ const priceInfo = result.price ? chalk9.yellow(`\u20AC${(result.price / 100).toFixed(2)}`) : chalk9.green("Free");
2568
+ const officialBadge = result.isOfficial ? chalk9.magenta(" \u2605 Official") : "";
2569
+ console.log(` ${chalk9.bold(result.name)}${officialBadge}`);
2570
+ console.log(` ${chalk9.cyan(result.id)} \u2022 ${priceInfo}`);
1916
2571
  if (result.description) {
1917
- console.log(` ${chalk8.gray(truncate2(result.description, 60))}`);
2572
+ console.log(` ${chalk9.gray(truncate2(result.description, 60))}`);
1918
2573
  }
1919
- console.log(` ${chalk8.gray(`by ${result.author}`)} \u2022 ${chalk8.gray(`\u2193${result.downloads}`)} ${chalk8.gray(`\u2665${result.likes}`)}`);
2574
+ console.log(` ${chalk9.gray(`by ${result.author}`)} \u2022 ${chalk9.gray(`\u2193${result.downloads}`)} ${chalk9.gray(`\u2665${result.likes}`)}`);
1920
2575
  if (result.tags && result.tags.length > 0) {
1921
2576
  console.log(` ${formatTags2(result.tags)}`);
1922
2577
  }
1923
2578
  console.log();
1924
2579
  }
1925
2580
  function formatTags2(tags) {
1926
- return tags.slice(0, 4).map((t) => chalk8.gray(`#${t}`)).join(" ");
2581
+ return tags.slice(0, 4).map((t) => chalk9.gray(`#${t}`)).join(" ");
1927
2582
  }
1928
2583
  function truncate2(str, maxLength) {
1929
2584
  if (str.length <= maxLength) return str;
@@ -1931,58 +2586,141 @@ function truncate2(str, maxLength) {
1931
2586
  }
1932
2587
  function handleApiError3(error) {
1933
2588
  if (error instanceof ApiRequestError) {
1934
- console.error(chalk8.red(`Error: ${error.message}`));
2589
+ console.error(chalk9.red(`Error: ${error.message}`));
1935
2590
  } else {
1936
- console.error(chalk8.red("An unexpected error occurred."));
2591
+ console.error(chalk9.red("An unexpected error occurred."));
1937
2592
  }
1938
2593
  process.exit(1);
1939
2594
  }
1940
2595
 
1941
2596
  // src/commands/status.ts
1942
- import chalk9 from "chalk";
1943
- import { access as access5, readFile as readFile4 } from "fs/promises";
1944
- import { join as join6 } from "path";
2597
+ import chalk10 from "chalk";
2598
+ import { access as access6, readFile as readFile6, readdir as readdir3 } from "fs/promises";
2599
+ import { join as join7 } from "path";
2600
+ import { existsSync as existsSync4 } from "fs";
1945
2601
  var CONFIG_FILES = [
1946
2602
  { path: "AGENTS.md", name: "AGENTS.md", platform: "Claude Code, Cursor, AI Agents" },
1947
2603
  { path: "CLAUDE.md", name: "CLAUDE.md", platform: "Claude Code" },
1948
- { path: ".cursorrules", name: ".cursorrules", platform: "Cursor" },
1949
2604
  { path: ".github/copilot-instructions.md", name: "Copilot Instructions", platform: "GitHub Copilot" },
1950
2605
  { path: ".windsurfrules", name: ".windsurfrules", platform: "Windsurf" },
1951
- { path: ".zed/instructions.md", name: "Zed Instructions", platform: "Zed" }
2606
+ { path: ".zed/instructions.md", name: "Zed Instructions", platform: "Zed" },
2607
+ { path: ".clinerules", name: ".clinerules", platform: "Cline" },
2608
+ { path: ".goosehints", name: ".goosehints", platform: "Goose" },
2609
+ { path: "AIDER.md", name: "AIDER.md", platform: "Aider" }
2610
+ ];
2611
+ var CONFIG_DIRS = [
2612
+ { path: ".cursor/rules", name: ".cursor/rules/", platform: "Cursor" },
2613
+ { path: ".amazonq/rules", name: ".amazonq/rules/", platform: "Amazon Q" },
2614
+ { path: ".augment/rules", name: ".augment/rules/", platform: "Augment Code" }
1952
2615
  ];
1953
2616
  async function statusCommand() {
1954
2617
  const cwd = process.cwd();
1955
2618
  console.log();
1956
- console.log(chalk9.cyan("\u{1F431} AI Config Status"));
1957
- console.log(chalk9.gray(` Directory: ${cwd}`));
2619
+ console.log(chalk10.cyan("\u{1F431} LynxPrompt Status"));
2620
+ console.log(chalk10.gray(` Directory: ${cwd}`));
2621
+ console.log();
2622
+ const lynxpromptExists = existsSync4(join7(cwd, ".lynxprompt"));
2623
+ if (lynxpromptExists) {
2624
+ console.log(chalk10.green("\u2713 LynxPrompt initialized"));
2625
+ const configPath = join7(cwd, ".lynxprompt/conf.yml");
2626
+ if (existsSync4(configPath)) {
2627
+ try {
2628
+ const content = await readFile6(configPath, "utf-8");
2629
+ const { parse: parse5 } = await import("yaml");
2630
+ const config2 = parse5(content);
2631
+ if (config2?.exporters?.length > 0) {
2632
+ console.log(chalk10.gray(` Exporters: ${config2.exporters.join(", ")}`));
2633
+ }
2634
+ } catch {
2635
+ }
2636
+ }
2637
+ console.log();
2638
+ }
2639
+ const trackedStatus = await checkSyncStatus(cwd);
2640
+ if (trackedStatus.length > 0) {
2641
+ console.log(chalk10.cyan("\u{1F4E6} Tracked Blueprints"));
2642
+ console.log();
2643
+ for (const { blueprint, localModified, fileExists: fileExists2 } of trackedStatus) {
2644
+ const statusIcon = !fileExists2 ? chalk10.red("\u2717") : localModified ? chalk10.yellow("\u25CF") : chalk10.green("\u2713");
2645
+ const sourceLabel = {
2646
+ marketplace: chalk10.gray("[marketplace]"),
2647
+ team: chalk10.blue("[team]"),
2648
+ private: chalk10.green("[private]"),
2649
+ local: chalk10.gray("[local]")
2650
+ }[blueprint.source];
2651
+ console.log(` ${statusIcon} ${chalk10.bold(blueprint.file)} ${sourceLabel}`);
2652
+ console.log(` ${chalk10.gray(`ID: ${blueprint.id} \u2022 ${blueprint.name}`)}`);
2653
+ if (!fileExists2) {
2654
+ console.log(chalk10.red(` \u26A0 File missing - run 'lynxp pull ${blueprint.id}' to restore`));
2655
+ } else if (localModified) {
2656
+ if (blueprint.source === "marketplace") {
2657
+ console.log(chalk10.yellow(` \u26A0 Local changes (marketplace = read-only, won't sync back)`));
2658
+ } else {
2659
+ console.log(chalk10.yellow(` \u26A0 Local changes - run 'lynxp push ${blueprint.file}' to sync`));
2660
+ }
2661
+ }
2662
+ console.log();
2663
+ }
2664
+ }
2665
+ console.log(chalk10.cyan("\u{1F4C4} AI Config Files"));
1958
2666
  console.log();
1959
2667
  let foundAny = false;
1960
2668
  for (const config2 of CONFIG_FILES) {
1961
- const filePath = join6(cwd, config2.path);
2669
+ const filePath = join7(cwd, config2.path);
1962
2670
  try {
1963
- await access5(filePath);
1964
- const content = await readFile4(filePath, "utf-8");
2671
+ await access6(filePath);
2672
+ const content = await readFile6(filePath, "utf-8");
1965
2673
  const lines = content.split("\n").length;
1966
2674
  const size = formatBytes(content.length);
1967
2675
  foundAny = true;
1968
- console.log(` ${chalk9.green("\u2713")} ${chalk9.bold(config2.name)}`);
1969
- console.log(` ${chalk9.gray(`Platform: ${config2.platform}`)}`);
1970
- console.log(` ${chalk9.gray(`Size: ${size} (${lines} lines)`)}`);
2676
+ const tracked = trackedStatus.find((t) => t.blueprint.file === config2.path);
2677
+ const trackedLabel = tracked ? chalk10.cyan(" (tracked)") : "";
2678
+ console.log(` ${chalk10.green("\u2713")} ${chalk10.bold(config2.name)}${trackedLabel}`);
2679
+ console.log(` ${chalk10.gray(`Platform: ${config2.platform}`)}`);
2680
+ console.log(` ${chalk10.gray(`Size: ${size} (${lines} lines)`)}`);
1971
2681
  const preview = getPreview(content);
1972
2682
  if (preview) {
1973
- console.log(` ${chalk9.gray(`Preview: ${preview}`)}`);
2683
+ console.log(` ${chalk10.gray(`Preview: ${preview}`)}`);
1974
2684
  }
1975
2685
  console.log();
1976
2686
  } catch {
1977
2687
  }
1978
2688
  }
2689
+ for (const config2 of CONFIG_DIRS) {
2690
+ const dirPath = join7(cwd, config2.path);
2691
+ if (existsSync4(dirPath)) {
2692
+ try {
2693
+ const files = await readdir3(dirPath);
2694
+ const ruleFiles = files.filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
2695
+ if (ruleFiles.length > 0) {
2696
+ foundAny = true;
2697
+ console.log(` ${chalk10.green("\u2713")} ${chalk10.bold(config2.name)}`);
2698
+ console.log(` ${chalk10.gray(`Platform: ${config2.platform}`)}`);
2699
+ console.log(` ${chalk10.gray(`Rules: ${ruleFiles.length} file${ruleFiles.length === 1 ? "" : "s"}`)}`);
2700
+ for (const file of ruleFiles.slice(0, 3)) {
2701
+ console.log(` ${chalk10.gray(` \u2022 ${file}`)}`);
2702
+ }
2703
+ if (ruleFiles.length > 3) {
2704
+ console.log(` ${chalk10.gray(` ... and ${ruleFiles.length - 3} more`)}`);
2705
+ }
2706
+ console.log();
2707
+ }
2708
+ } catch {
2709
+ }
2710
+ }
2711
+ }
1979
2712
  if (!foundAny) {
1980
- console.log(chalk9.yellow(" No AI configuration files found in this directory."));
2713
+ console.log(chalk10.yellow(" No AI configuration files found."));
1981
2714
  console.log();
1982
- console.log(chalk9.gray(" Run 'lynxprompt init' to create a configuration."));
1983
- console.log(chalk9.gray(" Or run 'lynxprompt pull <id>' to download an existing blueprint."));
2715
+ console.log(chalk10.gray(" Get started:"));
2716
+ console.log(chalk10.gray(" lynxp wizard Generate a configuration"));
2717
+ console.log(chalk10.gray(" lynxp pull <id> Download from marketplace"));
2718
+ console.log(chalk10.gray(" lynxp search <query> Search for blueprints"));
1984
2719
  } else {
1985
- console.log(chalk9.gray("Run 'lynxprompt init' to update or create additional configs."));
2720
+ console.log(chalk10.gray("Commands:"));
2721
+ console.log(chalk10.gray(" lynxp wizard Regenerate configuration"));
2722
+ console.log(chalk10.gray(" lynxp check Validate files"));
2723
+ console.log(chalk10.gray(" lynxp link --list Show tracked blueprints"));
1986
2724
  }
1987
2725
  console.log();
1988
2726
  }
@@ -1990,7 +2728,7 @@ function getPreview(content) {
1990
2728
  const lines = content.split("\n");
1991
2729
  for (const line of lines) {
1992
2730
  const trimmed = line.trim();
1993
- if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !trimmed.startsWith("<!--")) {
2731
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !trimmed.startsWith("<!--") && !trimmed.startsWith("---") && !trimmed.startsWith(">")) {
1994
2732
  return truncate3(trimmed, 50);
1995
2733
  }
1996
2734
  }
@@ -2007,42 +2745,42 @@ function formatBytes(bytes) {
2007
2745
  }
2008
2746
 
2009
2747
  // src/commands/sync.ts
2010
- import chalk10 from "chalk";
2011
- import ora8 from "ora";
2012
- import prompts4 from "prompts";
2013
- import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4, readdir as readdir2 } from "fs/promises";
2014
- import { join as join7, dirname as dirname4 } from "path";
2015
- import { existsSync as existsSync3 } from "fs";
2016
- import * as yaml2 from "yaml";
2748
+ import chalk11 from "chalk";
2749
+ import ora9 from "ora";
2750
+ import prompts5 from "prompts";
2751
+ import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5, readdir as readdir4 } from "fs/promises";
2752
+ import { join as join8, dirname as dirname5 } from "path";
2753
+ import { existsSync as existsSync5 } from "fs";
2754
+ import * as yaml3 from "yaml";
2017
2755
  var CONFIG_FILE = ".lynxprompt/conf.yml";
2018
2756
  var RULES_DIR = ".lynxprompt/rules";
2019
2757
  async function syncCommand(options = {}) {
2020
2758
  console.log();
2021
- console.log(chalk10.cyan("\u{1F431} LynxPrompt Sync"));
2759
+ console.log(chalk11.cyan("\u{1F431} LynxPrompt Sync"));
2022
2760
  console.log();
2023
2761
  const cwd = process.cwd();
2024
- const configPath = join7(cwd, CONFIG_FILE);
2025
- if (!existsSync3(configPath)) {
2026
- console.log(chalk10.yellow("LynxPrompt is not initialized in this project."));
2762
+ const configPath = join8(cwd, CONFIG_FILE);
2763
+ if (!existsSync5(configPath)) {
2764
+ console.log(chalk11.yellow("LynxPrompt is not initialized in this project."));
2027
2765
  console.log();
2028
- console.log(chalk10.gray("Run 'lynxp init' first to set up LynxPrompt."));
2766
+ console.log(chalk11.gray("Run 'lynxp init' first to set up LynxPrompt."));
2029
2767
  return;
2030
2768
  }
2031
- const spinner = ora8("Loading configuration...").start();
2769
+ const spinner = ora9("Loading configuration...").start();
2032
2770
  let config2;
2033
2771
  try {
2034
- const configContent = await readFile5(configPath, "utf-8");
2035
- config2 = yaml2.parse(configContent);
2772
+ const configContent = await readFile7(configPath, "utf-8");
2773
+ config2 = yaml3.parse(configContent);
2036
2774
  spinner.succeed("Configuration loaded");
2037
2775
  } catch (error) {
2038
2776
  spinner.fail("Failed to load configuration");
2039
- console.log(chalk10.red("Could not parse .lynxprompt/conf.yml"));
2777
+ console.log(chalk11.red("Could not parse .lynxprompt/conf.yml"));
2040
2778
  return;
2041
2779
  }
2042
2780
  if (!config2.exporters || config2.exporters.length === 0) {
2043
- console.log(chalk10.yellow("No exporters configured."));
2781
+ console.log(chalk11.yellow("No exporters configured."));
2044
2782
  console.log();
2045
- console.log(chalk10.gray("Add exporters to .lynxprompt/conf.yml or run 'lynxp agents enable <agent>'"));
2783
+ console.log(chalk11.gray("Add exporters to .lynxprompt/conf.yml or run 'lynxp agents enable <agent>'"));
2046
2784
  return;
2047
2785
  }
2048
2786
  const validExporters = [];
@@ -2056,51 +2794,51 @@ async function syncCommand(options = {}) {
2056
2794
  }
2057
2795
  }
2058
2796
  if (invalidExporters.length > 0) {
2059
- console.log(chalk10.yellow(`Unknown exporters: ${invalidExporters.join(", ")}`));
2797
+ console.log(chalk11.yellow(`Unknown exporters: ${invalidExporters.join(", ")}`));
2060
2798
  }
2061
2799
  if (validExporters.length === 0) {
2062
- console.log(chalk10.red("No valid exporters configured."));
2800
+ console.log(chalk11.red("No valid exporters configured."));
2063
2801
  return;
2064
2802
  }
2065
- console.log(chalk10.gray(`Exporters: ${validExporters.map((e) => e.name).join(", ")}`));
2803
+ console.log(chalk11.gray(`Exporters: ${validExporters.map((e) => e.name).join(", ")}`));
2066
2804
  console.log();
2067
- const rulesPath = join7(cwd, RULES_DIR);
2068
- if (!existsSync3(rulesPath)) {
2069
- console.log(chalk10.yellow("No rules found."));
2070
- console.log(chalk10.gray(`Create rules in ${RULES_DIR}/ to sync them.`));
2805
+ const rulesPath = join8(cwd, RULES_DIR);
2806
+ if (!existsSync5(rulesPath)) {
2807
+ console.log(chalk11.yellow("No rules found."));
2808
+ console.log(chalk11.gray(`Create rules in ${RULES_DIR}/ to sync them.`));
2071
2809
  return;
2072
2810
  }
2073
2811
  const rulesContent = await loadRules(rulesPath);
2074
2812
  if (!rulesContent) {
2075
- console.log(chalk10.yellow("No rule files found in .lynxprompt/rules/"));
2813
+ console.log(chalk11.yellow("No rule files found in .lynxprompt/rules/"));
2076
2814
  return;
2077
2815
  }
2078
- console.log(chalk10.gray(`Loaded ${rulesContent.fileCount} rule file${rulesContent.fileCount === 1 ? "" : "s"}`));
2816
+ console.log(chalk11.gray(`Loaded ${rulesContent.fileCount} rule file${rulesContent.fileCount === 1 ? "" : "s"}`));
2079
2817
  console.log();
2080
2818
  if (options.dryRun) {
2081
- console.log(chalk10.cyan("Dry run - no files will be written"));
2819
+ console.log(chalk11.cyan("Dry run - no files will be written"));
2082
2820
  console.log();
2083
2821
  console.log("Would write:");
2084
2822
  for (const exporter of validExporters) {
2085
- console.log(chalk10.gray(` ${exporter.output}`));
2823
+ console.log(chalk11.gray(` ${exporter.output}`));
2086
2824
  }
2087
2825
  console.log();
2088
2826
  return;
2089
2827
  }
2090
2828
  if (!options.force) {
2091
- const { confirm } = await prompts4({
2829
+ const { confirm } = await prompts5({
2092
2830
  type: "confirm",
2093
2831
  name: "confirm",
2094
2832
  message: `Sync to ${validExporters.length} agent${validExporters.length === 1 ? "" : "s"}?`,
2095
2833
  initial: true
2096
2834
  });
2097
2835
  if (!confirm) {
2098
- console.log(chalk10.gray("Cancelled."));
2836
+ console.log(chalk11.gray("Cancelled."));
2099
2837
  return;
2100
2838
  }
2101
2839
  }
2102
2840
  const result = { written: [], skipped: [], errors: [] };
2103
- const syncSpinner = ora8("Syncing rules...").start();
2841
+ const syncSpinner = ora9("Syncing rules...").start();
2104
2842
  for (const exporter of validExporters) {
2105
2843
  try {
2106
2844
  await syncToAgent(cwd, exporter, rulesContent.combined);
@@ -2111,16 +2849,16 @@ async function syncCommand(options = {}) {
2111
2849
  }
2112
2850
  syncSpinner.stop();
2113
2851
  if (result.written.length > 0) {
2114
- console.log(chalk10.green(`\u2713 Synced to ${result.written.length} agent${result.written.length === 1 ? "" : "s"}:`));
2852
+ console.log(chalk11.green(`\u2713 Synced to ${result.written.length} agent${result.written.length === 1 ? "" : "s"}:`));
2115
2853
  for (const file of result.written) {
2116
- console.log(chalk10.gray(` ${file}`));
2854
+ console.log(chalk11.gray(` ${file}`));
2117
2855
  }
2118
2856
  }
2119
2857
  if (result.errors.length > 0) {
2120
2858
  console.log();
2121
- console.log(chalk10.red("Errors:"));
2859
+ console.log(chalk11.red("Errors:"));
2122
2860
  for (const error of result.errors) {
2123
- console.log(chalk10.red(` ${error}`));
2861
+ console.log(chalk11.red(` ${error}`));
2124
2862
  }
2125
2863
  }
2126
2864
  console.log();
@@ -2128,12 +2866,12 @@ async function syncCommand(options = {}) {
2128
2866
  async function loadRules(rulesPath) {
2129
2867
  const files = [];
2130
2868
  try {
2131
- const entries = await readdir2(rulesPath, { withFileTypes: true });
2869
+ const entries = await readdir4(rulesPath, { withFileTypes: true });
2132
2870
  for (const entry of entries) {
2133
2871
  if (!entry.isFile()) continue;
2134
2872
  if (!entry.name.endsWith(".md")) continue;
2135
- const filePath = join7(rulesPath, entry.name);
2136
- const content = await readFile5(filePath, "utf-8");
2873
+ const filePath = join8(rulesPath, entry.name);
2874
+ const content = await readFile7(filePath, "utf-8");
2137
2875
  if (content.trim()) {
2138
2876
  files.push({ name: entry.name, content: content.trim() });
2139
2877
  }
@@ -2148,26 +2886,26 @@ async function loadRules(rulesPath) {
2148
2886
  return { combined, files, fileCount: files.length };
2149
2887
  }
2150
2888
  async function syncToAgent(cwd, agent, content) {
2151
- const outputPath = join7(cwd, agent.output);
2889
+ const outputPath = join8(cwd, agent.output);
2152
2890
  if (agent.output.endsWith("/")) {
2153
2891
  await syncToDirectory(cwd, agent, content);
2154
2892
  return;
2155
2893
  }
2156
2894
  const formatted = formatForAgent(agent, content);
2157
- const dir = dirname4(outputPath);
2895
+ const dir = dirname5(outputPath);
2158
2896
  if (dir !== ".") {
2159
- await mkdir4(dir, { recursive: true });
2897
+ await mkdir5(dir, { recursive: true });
2160
2898
  }
2161
- await writeFile4(outputPath, formatted, "utf-8");
2899
+ await writeFile5(outputPath, formatted, "utf-8");
2162
2900
  }
2163
2901
  async function syncToDirectory(cwd, agent, content) {
2164
- const outputDir = join7(cwd, agent.output);
2165
- await mkdir4(outputDir, { recursive: true });
2902
+ const outputDir = join8(cwd, agent.output);
2903
+ await mkdir5(outputDir, { recursive: true });
2166
2904
  const extension = agent.format === "mdc" ? ".mdc" : ".md";
2167
2905
  const filename = `lynxprompt-rules${extension}`;
2168
- const outputPath = join7(outputDir, filename);
2906
+ const outputPath = join8(outputDir, filename);
2169
2907
  const formatted = formatForAgent(agent, content);
2170
- await writeFile4(outputPath, formatted, "utf-8");
2908
+ await writeFile5(outputPath, formatted, "utf-8");
2171
2909
  }
2172
2910
  function formatForAgent(agent, content) {
2173
2911
  switch (agent.format) {
@@ -2184,7 +2922,7 @@ function formatForAgent(agent, content) {
2184
2922
  }
2185
2923
  }
2186
2924
  function formatAsMdc(content, agent) {
2187
- const frontmatter = yaml2.stringify({
2925
+ const frontmatter = yaml3.stringify({
2188
2926
  description: "LynxPrompt rules - AI coding guidelines",
2189
2927
  globs: ["**/*"],
2190
2928
  alwaysApply: true
@@ -2219,12 +2957,12 @@ function formatAsJson(content, agent) {
2219
2957
  }
2220
2958
 
2221
2959
  // src/commands/agents.ts
2222
- import chalk11 from "chalk";
2223
- import prompts5 from "prompts";
2224
- import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
2225
- import { join as join8 } from "path";
2226
- import { existsSync as existsSync4 } from "fs";
2227
- import * as yaml3 from "yaml";
2960
+ import chalk12 from "chalk";
2961
+ import prompts6 from "prompts";
2962
+ import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
2963
+ import { join as join9 } from "path";
2964
+ import { existsSync as existsSync6 } from "fs";
2965
+ import * as yaml4 from "yaml";
2228
2966
  var CONFIG_FILE2 = ".lynxprompt/conf.yml";
2229
2967
  async function agentsCommand(action, agentId, options = {}) {
2230
2968
  console.log();
@@ -2245,18 +2983,18 @@ async function agentsCommand(action, agentId, options = {}) {
2245
2983
  }
2246
2984
  }
2247
2985
  async function listAgents() {
2248
- console.log(chalk11.cyan("\u{1F431} LynxPrompt Agents"));
2986
+ console.log(chalk12.cyan("\u{1F431} LynxPrompt Agents"));
2249
2987
  console.log();
2250
2988
  const config2 = await loadConfig();
2251
2989
  const enabledSet = new Set(config2?.exporters ?? []);
2252
2990
  const detection = detectAgents();
2253
2991
  if (enabledSet.size > 0) {
2254
- console.log(chalk11.green("Enabled:"));
2992
+ console.log(chalk12.green("Enabled:"));
2255
2993
  for (const id of enabledSet) {
2256
2994
  const agent = getAgent(id);
2257
2995
  const detected = detection.detected.find((d) => d.agent.id === id);
2258
- const status = detected ? chalk11.gray("(detected)") : "";
2259
- console.log(` ${chalk11.green("\u2713")} ${agent?.name ?? id} ${status}`);
2996
+ const status = detected ? chalk12.gray("(detected)") : "";
2997
+ console.log(` ${chalk12.green("\u2713")} ${agent?.name ?? id} ${status}`);
2260
2998
  }
2261
2999
  console.log();
2262
3000
  }
@@ -2264,10 +3002,10 @@ async function listAgents() {
2264
3002
  (d) => !enabledSet.has(d.agent.id)
2265
3003
  );
2266
3004
  if (detectedNotEnabled.length > 0) {
2267
- console.log(chalk11.yellow("Detected (not enabled):"));
3005
+ console.log(chalk12.yellow("Detected (not enabled):"));
2268
3006
  for (const detected of detectedNotEnabled) {
2269
- const rules = detected.ruleCount > 0 ? chalk11.gray(` (${detected.ruleCount} rules)`) : "";
2270
- console.log(` ${chalk11.yellow("\u25CB")} ${detected.agent.name}${rules}`);
3007
+ const rules = detected.ruleCount > 0 ? chalk12.gray(` (${detected.ruleCount} rules)`) : "";
3008
+ console.log(` ${chalk12.yellow("\u25CB")} ${detected.agent.name}${rules}`);
2271
3009
  }
2272
3010
  console.log();
2273
3011
  }
@@ -2275,30 +3013,30 @@ async function listAgents() {
2275
3013
  (a) => !enabledSet.has(a.id) && !detectedNotEnabled.some((d) => d.agent.id === a.id)
2276
3014
  );
2277
3015
  if (popular.length > 0) {
2278
- console.log(chalk11.gray("Popular (available):"));
3016
+ console.log(chalk12.gray("Popular (available):"));
2279
3017
  for (const agent of popular) {
2280
- console.log(` ${chalk11.gray("-")} ${agent.name} - ${agent.description}`);
3018
+ console.log(` ${chalk12.gray("-")} ${agent.name} - ${agent.description}`);
2281
3019
  }
2282
3020
  console.log();
2283
3021
  }
2284
- console.log(chalk11.gray(`Total: ${AGENTS.length} agents supported`));
3022
+ console.log(chalk12.gray(`Total: ${AGENTS.length} agents supported`));
2285
3023
  console.log();
2286
- console.log(chalk11.gray("Commands:"));
2287
- console.log(chalk11.gray(" lynxp agents enable <agent> Enable an agent"));
2288
- console.log(chalk11.gray(" lynxp agents disable <agent> Disable an agent"));
2289
- console.log(chalk11.gray(" lynxp agents detect Auto-detect agents"));
3024
+ console.log(chalk12.gray("Commands:"));
3025
+ console.log(chalk12.gray(" lynxp agents enable <agent> Enable an agent"));
3026
+ console.log(chalk12.gray(" lynxp agents disable <agent> Disable an agent"));
3027
+ console.log(chalk12.gray(" lynxp agents detect Auto-detect agents"));
2290
3028
  console.log();
2291
3029
  }
2292
3030
  async function enableAgent(agentId, options = {}) {
2293
3031
  const cwd = process.cwd();
2294
- const configPath = join8(cwd, CONFIG_FILE2);
2295
- if (!existsSync4(configPath)) {
2296
- console.log(chalk11.yellow("LynxPrompt not initialized. Run 'lynxp init' first."));
3032
+ const configPath = join9(cwd, CONFIG_FILE2);
3033
+ if (!existsSync6(configPath)) {
3034
+ console.log(chalk12.yellow("LynxPrompt not initialized. Run 'lynxp init' first."));
2297
3035
  return;
2298
3036
  }
2299
3037
  let config2 = await loadConfig();
2300
3038
  if (!config2) {
2301
- console.log(chalk11.red("Could not load configuration."));
3039
+ console.log(chalk12.red("Could not load configuration."));
2302
3040
  return;
2303
3041
  }
2304
3042
  if (!agentId || options.interactive) {
@@ -2308,7 +3046,7 @@ async function enableAgent(agentId, options = {}) {
2308
3046
  value: agent2.id,
2309
3047
  selected: enabledSet.has(agent2.id)
2310
3048
  }));
2311
- const { selected } = await prompts5({
3049
+ const { selected } = await prompts6({
2312
3050
  type: "multiselect",
2313
3051
  name: "selected",
2314
3052
  message: "Select agents to enable:",
@@ -2316,14 +3054,14 @@ async function enableAgent(agentId, options = {}) {
2316
3054
  hint: "- Space to select, Enter to confirm"
2317
3055
  });
2318
3056
  if (!selected || selected.length === 0) {
2319
- console.log(chalk11.yellow("No agents selected."));
3057
+ console.log(chalk12.yellow("No agents selected."));
2320
3058
  return;
2321
3059
  }
2322
3060
  config2.exporters = selected;
2323
3061
  await saveConfig(config2);
2324
- console.log(chalk11.green(`\u2713 Enabled ${selected.length} agent${selected.length === 1 ? "" : "s"}`));
3062
+ console.log(chalk12.green(`\u2713 Enabled ${selected.length} agent${selected.length === 1 ? "" : "s"}`));
2325
3063
  console.log();
2326
- console.log(chalk11.gray("Run 'lynxp sync' to sync your rules."));
3064
+ console.log(chalk12.gray("Run 'lynxp sync' to sync your rules."));
2327
3065
  return;
2328
3066
  }
2329
3067
  const agent = getAgent(agentId);
@@ -2331,12 +3069,12 @@ async function enableAgent(agentId, options = {}) {
2331
3069
  const similar = AGENTS.filter(
2332
3070
  (a) => a.id.includes(agentId.toLowerCase()) || a.name.toLowerCase().includes(agentId.toLowerCase())
2333
3071
  );
2334
- console.log(chalk11.red(`Unknown agent: ${agentId}`));
3072
+ console.log(chalk12.red(`Unknown agent: ${agentId}`));
2335
3073
  if (similar.length > 0) {
2336
3074
  console.log();
2337
- console.log(chalk11.gray("Did you mean:"));
3075
+ console.log(chalk12.gray("Did you mean:"));
2338
3076
  for (const a of similar.slice(0, 5)) {
2339
- console.log(chalk11.gray(` ${a.id} - ${a.name}`));
3077
+ console.log(chalk12.gray(` ${a.id} - ${a.name}`));
2340
3078
  }
2341
3079
  }
2342
3080
  return;
@@ -2345,48 +3083,48 @@ async function enableAgent(agentId, options = {}) {
2345
3083
  config2.exporters = [];
2346
3084
  }
2347
3085
  if (config2.exporters.includes(agent.id)) {
2348
- console.log(chalk11.yellow(`${agent.name} is already enabled.`));
3086
+ console.log(chalk12.yellow(`${agent.name} is already enabled.`));
2349
3087
  return;
2350
3088
  }
2351
3089
  config2.exporters.push(agent.id);
2352
3090
  await saveConfig(config2);
2353
- console.log(chalk11.green(`\u2713 Enabled ${agent.name}`));
3091
+ console.log(chalk12.green(`\u2713 Enabled ${agent.name}`));
2354
3092
  console.log();
2355
- console.log(chalk11.gray(`Output: ${agent.output}`));
2356
- console.log(chalk11.gray("Run 'lynxp sync' to sync your rules."));
3093
+ console.log(chalk12.gray(`Output: ${agent.output}`));
3094
+ console.log(chalk12.gray("Run 'lynxp sync' to sync your rules."));
2357
3095
  }
2358
3096
  async function disableAgent(agentId) {
2359
3097
  if (!agentId) {
2360
- console.log(chalk11.yellow("Usage: lynxp agents disable <agent>"));
3098
+ console.log(chalk12.yellow("Usage: lynxp agents disable <agent>"));
2361
3099
  return;
2362
3100
  }
2363
3101
  const cwd = process.cwd();
2364
- const configPath = join8(cwd, CONFIG_FILE2);
2365
- if (!existsSync4(configPath)) {
2366
- console.log(chalk11.yellow("LynxPrompt not initialized. Run 'lynxp init' first."));
3102
+ const configPath = join9(cwd, CONFIG_FILE2);
3103
+ if (!existsSync6(configPath)) {
3104
+ console.log(chalk12.yellow("LynxPrompt not initialized. Run 'lynxp init' first."));
2367
3105
  return;
2368
3106
  }
2369
3107
  const config2 = await loadConfig();
2370
3108
  if (!config2) {
2371
- console.log(chalk11.red("Could not load configuration."));
3109
+ console.log(chalk12.red("Could not load configuration."));
2372
3110
  return;
2373
3111
  }
2374
3112
  if (!config2.exporters || !config2.exporters.includes(agentId)) {
2375
3113
  const agent2 = getAgent(agentId);
2376
- console.log(chalk11.yellow(`${agent2?.name ?? agentId} is not enabled.`));
3114
+ console.log(chalk12.yellow(`${agent2?.name ?? agentId} is not enabled.`));
2377
3115
  return;
2378
3116
  }
2379
3117
  if (config2.exporters.length === 1) {
2380
- console.log(chalk11.yellow("Cannot disable the last agent. At least one must be enabled."));
3118
+ console.log(chalk12.yellow("Cannot disable the last agent. At least one must be enabled."));
2381
3119
  return;
2382
3120
  }
2383
3121
  config2.exporters = config2.exporters.filter((e) => e !== agentId);
2384
3122
  await saveConfig(config2);
2385
3123
  const agent = getAgent(agentId);
2386
- console.log(chalk11.green(`\u2713 Disabled ${agent?.name ?? agentId}`));
3124
+ console.log(chalk12.green(`\u2713 Disabled ${agent?.name ?? agentId}`));
2387
3125
  }
2388
3126
  async function detectAgentsInProject() {
2389
- console.log(chalk11.cyan("\u{1F50D} Detecting AI agents..."));
3127
+ console.log(chalk12.cyan("\u{1F50D} Detecting AI agents..."));
2390
3128
  console.log();
2391
3129
  const detection = detectAgents();
2392
3130
  console.log(formatDetectionResults(detection));
@@ -2398,10 +3136,10 @@ async function detectAgentsInProject() {
2398
3136
  const enabledSet = new Set(config2?.exporters ?? []);
2399
3137
  const newAgents = detection.detected.filter((d) => !enabledSet.has(d.agent.id));
2400
3138
  if (newAgents.length === 0) {
2401
- console.log(chalk11.gray("All detected agents are already enabled."));
3139
+ console.log(chalk12.gray("All detected agents are already enabled."));
2402
3140
  return;
2403
3141
  }
2404
- const { enable } = await prompts5({
3142
+ const { enable } = await prompts6({
2405
3143
  type: "confirm",
2406
3144
  name: "enable",
2407
3145
  message: `Enable ${newAgents.length} detected agent${newAgents.length === 1 ? "" : "s"}?`,
@@ -2413,62 +3151,772 @@ async function detectAgentsInProject() {
2413
3151
  ...newAgents.map((d) => d.agent.id)
2414
3152
  ];
2415
3153
  await saveConfig(config2);
2416
- console.log(chalk11.green(`\u2713 Enabled ${newAgents.length} agent${newAgents.length === 1 ? "" : "s"}`));
3154
+ console.log(chalk12.green(`\u2713 Enabled ${newAgents.length} agent${newAgents.length === 1 ? "" : "s"}`));
2417
3155
  }
2418
3156
  }
2419
3157
  async function loadConfig() {
2420
3158
  const cwd = process.cwd();
2421
- const configPath = join8(cwd, CONFIG_FILE2);
2422
- if (!existsSync4(configPath)) {
3159
+ const configPath = join9(cwd, CONFIG_FILE2);
3160
+ if (!existsSync6(configPath)) {
2423
3161
  return null;
2424
3162
  }
2425
3163
  try {
2426
- const content = await readFile6(configPath, "utf-8");
2427
- return yaml3.parse(content);
3164
+ const content = await readFile8(configPath, "utf-8");
3165
+ return yaml4.parse(content);
2428
3166
  } catch {
2429
3167
  return null;
2430
3168
  }
2431
3169
  }
2432
3170
  async function saveConfig(config2) {
2433
3171
  const cwd = process.cwd();
2434
- const configPath = join8(cwd, CONFIG_FILE2);
2435
- const content = yaml3.stringify(config2);
2436
- await writeFile5(configPath, content, "utf-8");
3172
+ const configPath = join9(cwd, CONFIG_FILE2);
3173
+ const content = yaml4.stringify(config2);
3174
+ await writeFile6(configPath, content, "utf-8");
3175
+ }
3176
+
3177
+ // src/commands/check.ts
3178
+ import chalk13 from "chalk";
3179
+ import ora10 from "ora";
3180
+ import { readFile as readFile9, readdir as readdir5, stat as stat2 } from "fs/promises";
3181
+ import { join as join10 } from "path";
3182
+ import { existsSync as existsSync7 } from "fs";
3183
+ import * as yaml5 from "yaml";
3184
+ var CONFIG_FILES2 = [
3185
+ { path: "AGENTS.md", name: "AGENTS.md" },
3186
+ { path: "CLAUDE.md", name: "CLAUDE.md" },
3187
+ { path: ".github/copilot-instructions.md", name: "GitHub Copilot" },
3188
+ { path: ".windsurfrules", name: "Windsurf" },
3189
+ { path: ".clinerules", name: "Cline" },
3190
+ { path: ".goosehints", name: "Goose" },
3191
+ { path: ".zed/instructions.md", name: "Zed" }
3192
+ ];
3193
+ var CONFIG_DIRS2 = [
3194
+ { path: ".cursor/rules", name: "Cursor" },
3195
+ { path: ".lynxprompt", name: "LynxPrompt" }
3196
+ ];
3197
+ function validateMarkdown(content, filename) {
3198
+ const errors = [];
3199
+ const warnings = [];
3200
+ if (!content.trim()) {
3201
+ errors.push(`${filename}: File is empty`);
3202
+ return { errors, warnings };
3203
+ }
3204
+ if (content.trim().length < 50) {
3205
+ warnings.push(`${filename}: Content seems too short (< 50 chars)`);
3206
+ }
3207
+ if (!content.includes("#")) {
3208
+ warnings.push(`${filename}: No markdown headers found`);
3209
+ }
3210
+ const placeholders = [
3211
+ "TODO",
3212
+ "FIXME",
3213
+ "YOUR_",
3214
+ "REPLACE_",
3215
+ "[INSERT",
3216
+ "example.com"
3217
+ ];
3218
+ for (const placeholder of placeholders) {
3219
+ if (content.includes(placeholder)) {
3220
+ warnings.push(`${filename}: Contains placeholder text "${placeholder}"`);
3221
+ }
3222
+ }
3223
+ const secretPatterns = [
3224
+ /sk[_-][a-zA-Z0-9]{20,}/,
3225
+ // Stripe-like keys
3226
+ /ghp_[a-zA-Z0-9]{36}/,
3227
+ // GitHub tokens
3228
+ /api[_-]?key[_-]?=\s*[a-zA-Z0-9]{20,}/i
3229
+ ];
3230
+ for (const pattern of secretPatterns) {
3231
+ if (pattern.test(content)) {
3232
+ errors.push(`${filename}: Potential secret/API key detected - DO NOT commit secrets!`);
3233
+ break;
3234
+ }
3235
+ }
3236
+ return { errors, warnings };
3237
+ }
3238
+ async function validateLynxPromptConfig(cwd) {
3239
+ const errors = [];
3240
+ const warnings = [];
3241
+ const configPath = join10(cwd, ".lynxprompt/conf.yml");
3242
+ if (!existsSync7(configPath)) {
3243
+ return { errors, warnings };
3244
+ }
3245
+ try {
3246
+ const content = await readFile9(configPath, "utf-8");
3247
+ const config2 = yaml5.parse(content);
3248
+ if (!config2.version) {
3249
+ warnings.push(".lynxprompt/conf.yml: Missing 'version' field");
3250
+ }
3251
+ if (!config2.exporters || !Array.isArray(config2.exporters)) {
3252
+ errors.push(".lynxprompt/conf.yml: Missing or invalid 'exporters' field");
3253
+ } else if (config2.exporters.length === 0) {
3254
+ warnings.push(".lynxprompt/conf.yml: No exporters configured");
3255
+ }
3256
+ if (!config2.sources || !Array.isArray(config2.sources)) {
3257
+ errors.push(".lynxprompt/conf.yml: Missing or invalid 'sources' field");
3258
+ } else {
3259
+ for (const source of config2.sources) {
3260
+ if (source.type === "local" && source.path) {
3261
+ const sourcePath = join10(cwd, source.path);
3262
+ if (!existsSync7(sourcePath)) {
3263
+ errors.push(`.lynxprompt/conf.yml: Source path not found: ${source.path}`);
3264
+ }
3265
+ }
3266
+ }
3267
+ }
3268
+ } catch (error) {
3269
+ errors.push(`.lynxprompt/conf.yml: Invalid YAML syntax - ${error instanceof Error ? error.message : "parse error"}`);
3270
+ }
3271
+ return { errors, warnings };
3272
+ }
3273
+ function validateMdc(content, filename) {
3274
+ const errors = [];
3275
+ const warnings = [];
3276
+ if (!content.startsWith("---")) {
3277
+ warnings.push(`${filename}: Missing YAML frontmatter`);
3278
+ } else {
3279
+ const frontmatterEnd = content.indexOf("---", 3);
3280
+ if (frontmatterEnd === -1) {
3281
+ errors.push(`${filename}: Unclosed YAML frontmatter`);
3282
+ } else {
3283
+ const frontmatter = content.substring(3, frontmatterEnd).trim();
3284
+ try {
3285
+ yaml5.parse(frontmatter);
3286
+ } catch {
3287
+ errors.push(`${filename}: Invalid YAML frontmatter`);
3288
+ }
3289
+ }
3290
+ }
3291
+ const bodyStart = content.indexOf("---", 3);
3292
+ if (bodyStart !== -1) {
3293
+ const body = content.substring(bodyStart + 3).trim();
3294
+ const mdResult = validateMarkdown(body, filename);
3295
+ warnings.push(...mdResult.warnings);
3296
+ }
3297
+ return { errors, warnings };
3298
+ }
3299
+ async function checkCommand(options = {}) {
3300
+ const isCi = options.ci;
3301
+ const cwd = process.cwd();
3302
+ if (!isCi) {
3303
+ console.log();
3304
+ console.log(chalk13.cyan("\u{1F431} LynxPrompt Check"));
3305
+ console.log();
3306
+ }
3307
+ const result = {
3308
+ valid: true,
3309
+ errors: [],
3310
+ warnings: [],
3311
+ files: []
3312
+ };
3313
+ const spinner = !isCi ? ora10("Scanning for configuration files...").start() : null;
3314
+ for (const file of CONFIG_FILES2) {
3315
+ const filePath = join10(cwd, file.path);
3316
+ if (existsSync7(filePath)) {
3317
+ result.files.push(file.path);
3318
+ try {
3319
+ const content = await readFile9(filePath, "utf-8");
3320
+ const validation = validateMarkdown(content, file.path);
3321
+ result.errors.push(...validation.errors);
3322
+ result.warnings.push(...validation.warnings);
3323
+ } catch (error) {
3324
+ result.errors.push(`${file.path}: Could not read file`);
3325
+ }
3326
+ }
3327
+ }
3328
+ for (const dir of CONFIG_DIRS2) {
3329
+ const dirPath = join10(cwd, dir.path);
3330
+ if (existsSync7(dirPath)) {
3331
+ try {
3332
+ const files = await readdir5(dirPath);
3333
+ for (const file of files) {
3334
+ const filePath = join10(dirPath, file);
3335
+ const fileStat = await stat2(filePath);
3336
+ if (fileStat.isFile()) {
3337
+ result.files.push(`${dir.path}/${file}`);
3338
+ const content = await readFile9(filePath, "utf-8");
3339
+ if (file.endsWith(".mdc")) {
3340
+ const validation = validateMdc(content, `${dir.path}/${file}`);
3341
+ result.errors.push(...validation.errors);
3342
+ result.warnings.push(...validation.warnings);
3343
+ } else if (file.endsWith(".md")) {
3344
+ const validation = validateMarkdown(content, `${dir.path}/${file}`);
3345
+ result.errors.push(...validation.errors);
3346
+ result.warnings.push(...validation.warnings);
3347
+ }
3348
+ }
3349
+ }
3350
+ } catch {
3351
+ }
3352
+ }
3353
+ }
3354
+ const lynxpromptValidation = await validateLynxPromptConfig(cwd);
3355
+ result.errors.push(...lynxpromptValidation.errors);
3356
+ result.warnings.push(...lynxpromptValidation.warnings);
3357
+ spinner?.stop();
3358
+ result.valid = result.errors.length === 0;
3359
+ if (isCi) {
3360
+ if (!result.valid) {
3361
+ console.error("\u2717 Validation failed");
3362
+ for (const error of result.errors) {
3363
+ console.error(` ${error}`);
3364
+ }
3365
+ process.exit(1);
3366
+ } else if (result.files.length === 0) {
3367
+ console.error("\u2717 No configuration files found");
3368
+ process.exit(1);
3369
+ } else {
3370
+ console.log("\u2713 Validation passed");
3371
+ if (result.warnings.length > 0) {
3372
+ console.log(` (${result.warnings.length} warning${result.warnings.length === 1 ? "" : "s"})`);
3373
+ }
3374
+ process.exit(0);
3375
+ }
3376
+ } else {
3377
+ if (result.files.length === 0) {
3378
+ console.log(chalk13.yellow("\u26A0 No AI configuration files found."));
3379
+ console.log();
3380
+ console.log(chalk13.gray("Run 'lynxp wizard' to create a configuration."));
3381
+ return;
3382
+ }
3383
+ console.log(chalk13.green(`\u2713 Found ${result.files.length} configuration file${result.files.length === 1 ? "" : "s"}:`));
3384
+ for (const file of result.files) {
3385
+ console.log(chalk13.gray(` ${file}`));
3386
+ }
3387
+ console.log();
3388
+ if (result.errors.length > 0) {
3389
+ console.log(chalk13.red(`\u2717 ${result.errors.length} error${result.errors.length === 1 ? "" : "s"}:`));
3390
+ for (const error of result.errors) {
3391
+ console.log(chalk13.red(` ${error}`));
3392
+ }
3393
+ console.log();
3394
+ }
3395
+ if (result.warnings.length > 0) {
3396
+ console.log(chalk13.yellow(`\u26A0 ${result.warnings.length} warning${result.warnings.length === 1 ? "" : "s"}:`));
3397
+ for (const warning of result.warnings) {
3398
+ console.log(chalk13.yellow(` ${warning}`));
3399
+ }
3400
+ console.log();
3401
+ }
3402
+ if (result.valid) {
3403
+ console.log(chalk13.green("\u2705 Validation passed!"));
3404
+ } else {
3405
+ console.log(chalk13.red("\u274C Validation failed. Fix the errors above."));
3406
+ }
3407
+ console.log();
3408
+ }
3409
+ }
3410
+
3411
+ // src/commands/diff.ts
3412
+ import chalk14 from "chalk";
3413
+ import ora11 from "ora";
3414
+ import { readFile as readFile10 } from "fs/promises";
3415
+ import { join as join11 } from "path";
3416
+ import { existsSync as existsSync8 } from "fs";
3417
+ function computeDiff(oldText, newText) {
3418
+ const oldLines = oldText.split("\n");
3419
+ const newLines = newText.split("\n");
3420
+ const result = [];
3421
+ const lcs = longestCommonSubsequence(oldLines, newLines);
3422
+ let oldIndex = 0;
3423
+ let newIndex = 0;
3424
+ let lcsIndex = 0;
3425
+ while (oldIndex < oldLines.length || newIndex < newLines.length) {
3426
+ if (lcsIndex < lcs.length && oldIndex < oldLines.length && oldLines[oldIndex] === lcs[lcsIndex]) {
3427
+ if (newIndex < newLines.length && newLines[newIndex] === lcs[lcsIndex]) {
3428
+ result.push({ type: "same", line: oldLines[oldIndex] });
3429
+ oldIndex++;
3430
+ newIndex++;
3431
+ lcsIndex++;
3432
+ } else {
3433
+ result.push({ type: "add", line: newLines[newIndex] });
3434
+ newIndex++;
3435
+ }
3436
+ } else if (oldIndex < oldLines.length && (lcsIndex >= lcs.length || oldLines[oldIndex] !== lcs[lcsIndex])) {
3437
+ result.push({ type: "remove", line: oldLines[oldIndex] });
3438
+ oldIndex++;
3439
+ } else if (newIndex < newLines.length) {
3440
+ result.push({ type: "add", line: newLines[newIndex] });
3441
+ newIndex++;
3442
+ }
3443
+ }
3444
+ return result;
3445
+ }
3446
+ function longestCommonSubsequence(a, b) {
3447
+ const m = a.length;
3448
+ const n = b.length;
3449
+ const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
3450
+ for (let i2 = 1; i2 <= m; i2++) {
3451
+ for (let j2 = 1; j2 <= n; j2++) {
3452
+ if (a[i2 - 1] === b[j2 - 1]) {
3453
+ dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
3454
+ } else {
3455
+ dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
3456
+ }
3457
+ }
3458
+ }
3459
+ const lcs = [];
3460
+ let i = m;
3461
+ let j = n;
3462
+ while (i > 0 && j > 0) {
3463
+ if (a[i - 1] === b[j - 1]) {
3464
+ lcs.unshift(a[i - 1]);
3465
+ i--;
3466
+ j--;
3467
+ } else if (dp[i - 1][j] > dp[i][j - 1]) {
3468
+ i--;
3469
+ } else {
3470
+ j--;
3471
+ }
3472
+ }
3473
+ return lcs;
3474
+ }
3475
+ function formatDiff(diff, contextLines = 3) {
3476
+ const output = [];
3477
+ let lastPrintedIndex = -1;
3478
+ let inHunk = false;
3479
+ const changeIndices = diff.map((d, i) => d.type !== "same" ? i : -1).filter((i) => i !== -1);
3480
+ if (changeIndices.length === 0) {
3481
+ return chalk14.gray(" (no changes)");
3482
+ }
3483
+ for (let i = 0; i < diff.length; i++) {
3484
+ const item = diff[i];
3485
+ const nearChange = changeIndices.some((ci) => Math.abs(ci - i) <= contextLines);
3486
+ if (nearChange) {
3487
+ if (lastPrintedIndex !== -1 && i - lastPrintedIndex > 1) {
3488
+ output.push(chalk14.gray(" ..."));
3489
+ }
3490
+ if (item.type === "add") {
3491
+ output.push(chalk14.green(`+ ${item.line}`));
3492
+ } else if (item.type === "remove") {
3493
+ output.push(chalk14.red(`- ${item.line}`));
3494
+ } else {
3495
+ output.push(chalk14.gray(` ${item.line}`));
3496
+ }
3497
+ lastPrintedIndex = i;
3498
+ }
3499
+ }
3500
+ return output.join("\n");
3501
+ }
3502
+ function getDiffStats(diff) {
3503
+ return {
3504
+ added: diff.filter((d) => d.type === "add").length,
3505
+ removed: diff.filter((d) => d.type === "remove").length,
3506
+ unchanged: diff.filter((d) => d.type === "same").length
3507
+ };
3508
+ }
3509
+ async function diffCommand(blueprintId, options = {}) {
3510
+ console.log();
3511
+ console.log(chalk14.cyan("\u{1F431} LynxPrompt Diff"));
3512
+ console.log();
3513
+ const cwd = process.cwd();
3514
+ if (options.local) {
3515
+ await diffLocal(cwd);
3516
+ return;
3517
+ }
3518
+ if (!blueprintId) {
3519
+ console.log(chalk14.red("\u2717 Please provide a blueprint ID to compare with."));
3520
+ console.log();
3521
+ console.log(chalk14.gray("Usage:"));
3522
+ console.log(chalk14.gray(" lynxp diff <blueprint-id> Compare local with remote blueprint"));
3523
+ console.log(chalk14.gray(" lynxp diff --local Compare .lynxprompt/rules/ with exports"));
3524
+ return;
3525
+ }
3526
+ if (!isAuthenticated()) {
3527
+ console.log(chalk14.yellow("\u26A0 Not logged in. Some blueprints may not be accessible."));
3528
+ console.log(chalk14.gray("Run 'lynxp login' to authenticate."));
3529
+ console.log();
3530
+ }
3531
+ const spinner = ora11("Fetching blueprint...").start();
3532
+ try {
3533
+ const { blueprint } = await api.getBlueprint(blueprintId);
3534
+ spinner.stop();
3535
+ if (!blueprint || !blueprint.content) {
3536
+ console.log(chalk14.red(`\u2717 Blueprint not found or has no content: ${blueprintId}`));
3537
+ return;
3538
+ }
3539
+ console.log(chalk14.green(`\u2713 Blueprint: ${blueprint.name || blueprintId}`));
3540
+ if (blueprint.description) {
3541
+ console.log(chalk14.gray(` ${blueprint.description}`));
3542
+ }
3543
+ console.log();
3544
+ const localPaths = [
3545
+ "AGENTS.md",
3546
+ "CLAUDE.md",
3547
+ ".cursor/rules/project.mdc",
3548
+ ".github/copilot-instructions.md",
3549
+ ".windsurfrules"
3550
+ ];
3551
+ let localContent = null;
3552
+ let localPath = null;
3553
+ for (const path2 of localPaths) {
3554
+ const fullPath = join11(cwd, path2);
3555
+ if (existsSync8(fullPath)) {
3556
+ try {
3557
+ localContent = await readFile10(fullPath, "utf-8");
3558
+ localPath = path2;
3559
+ break;
3560
+ } catch {
3561
+ }
3562
+ }
3563
+ }
3564
+ if (!localContent) {
3565
+ console.log(chalk14.yellow("\u26A0 No local AI configuration file found."));
3566
+ console.log(chalk14.gray("Run 'lynxp wizard' to create one, or 'lynxp pull' to download the blueprint."));
3567
+ return;
3568
+ }
3569
+ console.log(chalk14.gray(`Comparing with: ${localPath}`));
3570
+ console.log();
3571
+ const diff = computeDiff(blueprint.content, localContent);
3572
+ const stats = getDiffStats(diff);
3573
+ if (stats.added === 0 && stats.removed === 0) {
3574
+ console.log(chalk14.green("\u2713 Files are identical!"));
3575
+ } else {
3576
+ console.log(chalk14.gray("Changes (remote \u2192 local):"));
3577
+ console.log();
3578
+ console.log(formatDiff(diff));
3579
+ console.log();
3580
+ console.log(chalk14.gray(`Summary: ${chalk14.green(`+${stats.added}`)} ${chalk14.red(`-${stats.removed}`)} lines changed`));
3581
+ }
3582
+ console.log();
3583
+ } catch (error) {
3584
+ spinner.stop();
3585
+ if (error instanceof ApiRequestError) {
3586
+ if (error.statusCode === 401) {
3587
+ console.log(chalk14.red("\u2717 Authentication required. Run 'lynxp login' first."));
3588
+ } else if (error.statusCode === 404) {
3589
+ console.log(chalk14.red(`\u2717 Blueprint not found: ${blueprintId}`));
3590
+ } else if (error.statusCode === 403) {
3591
+ console.log(chalk14.red("\u2717 Access denied to this blueprint."));
3592
+ } else {
3593
+ console.log(chalk14.red(`\u2717 API error: ${error.message}`));
3594
+ }
3595
+ } else {
3596
+ console.log(chalk14.red("\u2717 Failed to fetch blueprint"));
3597
+ if (error instanceof Error) {
3598
+ console.log(chalk14.gray(` ${error.message}`));
3599
+ }
3600
+ }
3601
+ }
3602
+ }
3603
+ async function diffLocal(cwd) {
3604
+ const rulesDir = join11(cwd, ".lynxprompt/rules");
3605
+ if (!existsSync8(rulesDir)) {
3606
+ console.log(chalk14.yellow("\u26A0 No .lynxprompt/rules/ directory found."));
3607
+ console.log(chalk14.gray("Run 'lynxp init' to set up the advanced workflow, or 'lynxp wizard' for simple file generation."));
3608
+ return;
3609
+ }
3610
+ console.log(chalk14.gray("Comparing .lynxprompt/rules/ with exported files..."));
3611
+ console.log();
3612
+ const rulesPath = join11(rulesDir, "agents.md");
3613
+ if (!existsSync8(rulesPath)) {
3614
+ console.log(chalk14.yellow("\u26A0 No rules files found in .lynxprompt/rules/"));
3615
+ return;
3616
+ }
3617
+ let rulesContent;
3618
+ try {
3619
+ rulesContent = await readFile10(rulesPath, "utf-8");
3620
+ } catch {
3621
+ console.log(chalk14.red("\u2717 Could not read .lynxprompt/rules/agents.md"));
3622
+ return;
3623
+ }
3624
+ const exportedFiles = [
3625
+ { path: "AGENTS.md", name: "AGENTS.md" },
3626
+ { path: ".cursor/rules/lynxprompt-rules.mdc", name: "Cursor" }
3627
+ ];
3628
+ let hasChanges = false;
3629
+ for (const file of exportedFiles) {
3630
+ const filePath = join11(cwd, file.path);
3631
+ if (existsSync8(filePath)) {
3632
+ try {
3633
+ const exportedContent = await readFile10(filePath, "utf-8");
3634
+ let compareContent = exportedContent;
3635
+ if (file.path.endsWith(".mdc")) {
3636
+ const frontmatterEnd = exportedContent.indexOf("---", 3);
3637
+ if (frontmatterEnd !== -1) {
3638
+ compareContent = exportedContent.substring(frontmatterEnd + 3).trim();
3639
+ }
3640
+ }
3641
+ compareContent = compareContent.replace(/^# AI Coding Rules\n\n> Generated by \[LynxPrompt\].*\n\n/m, "").trim();
3642
+ const diff = computeDiff(rulesContent.trim(), compareContent);
3643
+ const stats = getDiffStats(diff);
3644
+ if (stats.added > 0 || stats.removed > 0) {
3645
+ hasChanges = true;
3646
+ console.log(chalk14.yellow(`\u26A0 ${file.name} differs from source:`));
3647
+ console.log(formatDiff(diff));
3648
+ console.log(chalk14.gray(` ${chalk14.green(`+${stats.added}`)} ${chalk14.red(`-${stats.removed}`)} lines`));
3649
+ console.log();
3650
+ } else {
3651
+ console.log(chalk14.green(`\u2713 ${file.name} is in sync`));
3652
+ }
3653
+ } catch {
3654
+ }
3655
+ }
3656
+ }
3657
+ if (!hasChanges) {
3658
+ console.log();
3659
+ console.log(chalk14.green("\u2713 All exported files are in sync with .lynxprompt/rules/"));
3660
+ } else {
3661
+ console.log();
3662
+ console.log(chalk14.gray("Run 'lynxp sync' to update exported files from .lynxprompt/rules/"));
3663
+ }
3664
+ console.log();
3665
+ }
3666
+
3667
+ // src/commands/link.ts
3668
+ import chalk15 from "chalk";
3669
+ import ora12 from "ora";
3670
+ import prompts7 from "prompts";
3671
+ import { join as join12 } from "path";
3672
+ import { existsSync as existsSync9 } from "fs";
3673
+ function getSourceFromVisibility2(visibility) {
3674
+ switch (visibility) {
3675
+ case "PUBLIC":
3676
+ return "marketplace";
3677
+ case "TEAM":
3678
+ return "team";
3679
+ case "PRIVATE":
3680
+ return "private";
3681
+ default:
3682
+ return "marketplace";
3683
+ }
3684
+ }
3685
+ async function linkCommand(file, blueprintId, options = {}) {
3686
+ const cwd = process.cwd();
3687
+ if (options.list) {
3688
+ await listTrackedBlueprints(cwd);
3689
+ return;
3690
+ }
3691
+ if (!file) {
3692
+ console.log(chalk15.red("\u2717 Please provide a file path to link."));
3693
+ console.log();
3694
+ console.log(chalk15.gray("Usage:"));
3695
+ console.log(chalk15.gray(" lynxp link <file> <blueprint-id> Link a local file to a cloud blueprint"));
3696
+ console.log(chalk15.gray(" lynxp link --list List all tracked blueprints"));
3697
+ console.log();
3698
+ console.log(chalk15.gray("Example:"));
3699
+ console.log(chalk15.gray(" lynxp link AGENTS.md bp_abc123"));
3700
+ return;
3701
+ }
3702
+ if (!blueprintId) {
3703
+ console.log(chalk15.red("\u2717 Please provide a blueprint ID to link to."));
3704
+ console.log();
3705
+ console.log(chalk15.gray("Usage: lynxp link <file> <blueprint-id>"));
3706
+ console.log(chalk15.gray("Example: lynxp link AGENTS.md bp_abc123"));
3707
+ console.log();
3708
+ console.log(chalk15.gray("To find blueprint IDs:"));
3709
+ console.log(chalk15.gray(" lynxp list - Show your blueprints"));
3710
+ console.log(chalk15.gray(" lynxp search <query> - Search marketplace"));
3711
+ return;
3712
+ }
3713
+ const filePath = join12(cwd, file);
3714
+ if (!existsSync9(filePath)) {
3715
+ console.log(chalk15.red(`\u2717 File not found: ${file}`));
3716
+ return;
3717
+ }
3718
+ const existing = await findBlueprintByFile(cwd, file);
3719
+ if (existing) {
3720
+ console.log(chalk15.yellow(`\u26A0 This file is already linked to: ${existing.id}`));
3721
+ const { proceed } = await prompts7({
3722
+ type: "confirm",
3723
+ name: "proceed",
3724
+ message: "Replace the existing link?",
3725
+ initial: false
3726
+ });
3727
+ if (!proceed) {
3728
+ console.log(chalk15.gray("Cancelled."));
3729
+ return;
3730
+ }
3731
+ }
3732
+ if (!isAuthenticated()) {
3733
+ console.log(
3734
+ chalk15.yellow("Not logged in. Run 'lynxp login' to authenticate.")
3735
+ );
3736
+ process.exit(1);
3737
+ }
3738
+ const spinner = ora12(`Fetching blueprint ${chalk15.cyan(blueprintId)}...`).start();
3739
+ try {
3740
+ const { blueprint } = await api.getBlueprint(blueprintId);
3741
+ spinner.stop();
3742
+ const source = getSourceFromVisibility2(blueprint.visibility);
3743
+ const isMarketplace = source === "marketplace";
3744
+ console.log();
3745
+ console.log(chalk15.cyan(`\u{1F431} Blueprint: ${chalk15.bold(blueprint.name)}`));
3746
+ if (blueprint.description) {
3747
+ console.log(chalk15.gray(` ${blueprint.description}`));
3748
+ }
3749
+ console.log(chalk15.gray(` Visibility: ${blueprint.visibility}`));
3750
+ console.log();
3751
+ if (isMarketplace) {
3752
+ console.log(chalk15.yellow("\u26A0 This is a marketplace blueprint."));
3753
+ console.log(chalk15.gray(" Your local changes will NOT sync back to the cloud."));
3754
+ console.log(chalk15.gray(" To make changes, you'll need to create your own copy."));
3755
+ console.log();
3756
+ }
3757
+ const { confirm } = await prompts7({
3758
+ type: "confirm",
3759
+ name: "confirm",
3760
+ message: `Link ${chalk15.cyan(file)} to ${chalk15.cyan(blueprint.name)}?`,
3761
+ initial: true
3762
+ });
3763
+ if (!confirm) {
3764
+ console.log(chalk15.gray("Cancelled."));
3765
+ return;
3766
+ }
3767
+ await linkBlueprint(cwd, file, blueprint.id, blueprint.name, source);
3768
+ console.log();
3769
+ console.log(chalk15.green(`\u2705 Linked: ${file} \u2192 ${blueprint.id}`));
3770
+ console.log();
3771
+ console.log(chalk15.gray("Next steps:"));
3772
+ console.log(chalk15.gray(` \u2022 Run 'lynxp pull ${blueprintId}' to update local file from cloud`));
3773
+ console.log(chalk15.gray(` \u2022 Run 'lynxp diff ${blueprintId}' to see differences`));
3774
+ console.log(chalk15.gray(` \u2022 Run 'lynxp status' to see all tracked blueprints`));
3775
+ if (!isMarketplace) {
3776
+ console.log(chalk15.gray(` \u2022 Run 'lynxp push ${file}' to push local changes to cloud`));
3777
+ }
3778
+ console.log();
3779
+ } catch (error) {
3780
+ spinner.stop();
3781
+ if (error instanceof ApiRequestError) {
3782
+ if (error.statusCode === 404) {
3783
+ console.log(chalk15.red(`\u2717 Blueprint not found: ${blueprintId}`));
3784
+ console.log(chalk15.gray(" Make sure the ID is correct. Use 'lynxp list' or 'lynxp search' to find blueprints."));
3785
+ } else if (error.statusCode === 403) {
3786
+ console.log(chalk15.red("\u2717 You don't have access to this blueprint."));
3787
+ } else {
3788
+ console.log(chalk15.red(`\u2717 Error: ${error.message}`));
3789
+ }
3790
+ } else {
3791
+ console.log(chalk15.red("\u2717 An unexpected error occurred."));
3792
+ }
3793
+ }
3794
+ }
3795
+ async function unlinkCommand(file) {
3796
+ const cwd = process.cwd();
3797
+ if (!file) {
3798
+ console.log(chalk15.red("\u2717 Please provide a file path to unlink."));
3799
+ console.log();
3800
+ console.log(chalk15.gray("Usage: lynxp unlink <file>"));
3801
+ console.log(chalk15.gray("Example: lynxp unlink AGENTS.md"));
3802
+ return;
3803
+ }
3804
+ const tracked = await findBlueprintByFile(cwd, file);
3805
+ if (!tracked) {
3806
+ console.log(chalk15.yellow(`\u26A0 File is not linked to any blueprint: ${file}`));
3807
+ return;
3808
+ }
3809
+ console.log();
3810
+ console.log(chalk15.cyan(`Currently linked to: ${tracked.id}`));
3811
+ console.log(chalk15.gray(` Name: ${tracked.name}`));
3812
+ console.log(chalk15.gray(` Source: ${tracked.source}`));
3813
+ console.log();
3814
+ const { confirm } = await prompts7({
3815
+ type: "confirm",
3816
+ name: "confirm",
3817
+ message: `Unlink ${chalk15.cyan(file)} from ${chalk15.cyan(tracked.name)}?`,
3818
+ initial: true
3819
+ });
3820
+ if (!confirm) {
3821
+ console.log(chalk15.gray("Cancelled."));
3822
+ return;
3823
+ }
3824
+ const success = await untrackBlueprint(cwd, file);
3825
+ if (success) {
3826
+ console.log();
3827
+ console.log(chalk15.green(`\u2705 Unlinked: ${file}`));
3828
+ console.log(chalk15.gray(" The file is now a standalone local file."));
3829
+ console.log(chalk15.gray(" It will no longer receive updates from the cloud blueprint."));
3830
+ console.log();
3831
+ } else {
3832
+ console.log(chalk15.red("\u2717 Failed to unlink file."));
3833
+ }
3834
+ }
3835
+ async function listTrackedBlueprints(cwd) {
3836
+ console.log();
3837
+ console.log(chalk15.cyan("\u{1F431} Tracked Blueprints"));
3838
+ console.log();
3839
+ const status = await checkSyncStatus(cwd);
3840
+ if (status.length === 0) {
3841
+ console.log(chalk15.gray("No blueprints are currently tracked."));
3842
+ console.log();
3843
+ console.log(chalk15.gray("To track a blueprint:"));
3844
+ console.log(chalk15.gray(" lynxp pull <blueprint-id> Download and track a blueprint"));
3845
+ console.log(chalk15.gray(" lynxp link <file> <id> Link an existing file to a blueprint"));
3846
+ return;
3847
+ }
3848
+ for (const { blueprint, localModified, fileExists: fileExists2 } of status) {
3849
+ const statusIcon = !fileExists2 ? chalk15.red("\u2717") : localModified ? chalk15.yellow("\u25CF") : chalk15.green("\u2713");
3850
+ const sourceLabel = {
3851
+ marketplace: chalk15.gray("[marketplace]"),
3852
+ team: chalk15.blue("[team]"),
3853
+ private: chalk15.green("[private]"),
3854
+ local: chalk15.gray("[local]")
3855
+ }[blueprint.source];
3856
+ console.log(`${statusIcon} ${chalk15.cyan(blueprint.file)}`);
3857
+ console.log(` ${sourceLabel} ${blueprint.name}`);
3858
+ console.log(` ${chalk15.gray(`ID: ${blueprint.id}`)}`);
3859
+ if (!fileExists2) {
3860
+ console.log(chalk15.red(` \u26A0 File not found`));
3861
+ } else if (localModified) {
3862
+ console.log(chalk15.yellow(` \u26A0 Local changes detected`));
3863
+ }
3864
+ console.log();
3865
+ }
3866
+ console.log(chalk15.gray("Legend:"));
3867
+ console.log(chalk15.gray(` ${chalk15.green("\u2713")} In sync ${chalk15.yellow("\u25CF")} Modified locally ${chalk15.red("\u2717")} Missing`));
3868
+ console.log();
2437
3869
  }
2438
3870
 
2439
3871
  // src/index.ts
2440
3872
  var program = new Command();
2441
- program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version("0.1.0");
3873
+ program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version("0.2.0");
3874
+ program.command("wizard").description("Generate AI IDE configuration (recommended for most users)").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("-s, --stack <stack>", "Tech stack (comma-separated)").option("-f, --format <format>", "Output format: agents, cursor, or comma-separated for multiple").option("-p, --platforms <platforms>", "Alias for --format (deprecated)").option("--persona <persona>", "AI persona (fullstack, backend, frontend, devops, data, security)").option("--boundaries <level>", "Boundary preset (conservative, standard, permissive)").option("-y, --yes", "Skip prompts, use defaults (generates AGENTS.md)").action(wizardCommand);
3875
+ program.command("check").description("Validate AI configuration files (for CI/CD)").option("--ci", "CI mode - exit codes only (0=pass, 1=fail)").action(checkCommand);
3876
+ program.command("status").description("Show current AI configuration and tracked blueprints").action(statusCommand);
3877
+ program.command("pull <id>").description("Download and track a blueprint from the marketplace").option("-o, --output <path>", "Output directory", ".").option("-y, --yes", "Overwrite existing files without prompting").option("--preview", "Preview content without downloading").option("--no-track", "Don't track the blueprint for future syncs").action(pullCommand);
3878
+ program.command("search <query>").description("Search public blueprints in the marketplace").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
3879
+ 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);
3880
+ 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);
3881
+ 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);
3882
+ program.command("unlink <file>").description("Disconnect a local file from its cloud blueprint").action(unlinkCommand);
3883
+ program.command("diff [blueprint-id]").description("Show changes between local and remote blueprint").option("--local", "Compare .lynxprompt/rules/ with exported files").action(diffCommand);
3884
+ 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);
3885
+ 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);
3886
+ program.command("agents [action] [agent]").description("Manage AI agents (list, enable, disable, detect)").option("-i, --interactive", "Interactive agent selection").action(agentsCommand);
2442
3887
  program.command("login").description("Authenticate with LynxPrompt (opens browser)").action(loginCommand);
2443
3888
  program.command("logout").description("Log out and remove stored credentials").action(logoutCommand);
2444
3889
  program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
2445
- program.command("init").description("Initialize LynxPrompt in this directory (auto-detects existing files)").option("-y, --yes", "Skip prompts and use defaults").option("-f, --force", "Re-initialize even if already initialized").action(initCommand);
2446
- program.command("wizard").description("Interactive wizard to generate AI IDE configuration").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("-s, --stack <stack>", "Tech stack (comma-separated)").option("-p, --platforms <platforms>", "Target platforms (comma-separated)").option("--persona <persona>", "AI persona/role").option("--boundaries <level>", "Boundary preset (conservative, standard, permissive)").option("--preset <preset>", "Use an agent preset (test-agent, docs-agent, etc.)").option("-y, --yes", "Skip prompts and use defaults").action(wizardCommand);
2447
- program.command("list").description("List your blueprints").option("-l, --limit <number>", "Number of results", "20").option("-v, --visibility <visibility>", "Filter by visibility (PRIVATE, TEAM, PUBLIC, all)").action(listCommand);
2448
- program.command("pull <id>").description("Download a blueprint to the current directory").option("-o, --output <path>", "Output directory", ".").option("-y, --yes", "Overwrite existing files without prompting").action(pullCommand);
2449
- program.command("search <query>").description("Search public blueprints").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
2450
- program.command("status").description("Show current AI config status in this directory").action(statusCommand);
2451
- program.command("sync").description("Sync rules to all configured AI agents").option("--dry-run", "Preview changes without writing files").option("-f, --force", "Skip prompts (for CI/automation)").action(syncCommand);
2452
- program.command("agents [action] [agent]").description("Manage AI agents (list, enable, disable, detect)").option("-i, --interactive", "Interactive agent selection").action(agentsCommand);
2453
3890
  program.addHelpText(
2454
3891
  "beforeAll",
2455
3892
  `
2456
- ${chalk12.cyan("\u{1F431} LynxPrompt CLI")} ${chalk12.gray("(also available as: lynxp)")}
2457
- ${chalk12.gray("Generate AI IDE configuration files from your terminal")}
3893
+ ${chalk16.cyan("\u{1F431} LynxPrompt CLI")} ${chalk16.gray("(also available as: lynxp)")}
3894
+ ${chalk16.gray("Generate AI IDE configuration files from your terminal")}
2458
3895
  `
2459
3896
  );
2460
3897
  program.addHelpText(
2461
3898
  "after",
2462
3899
  `
2463
- ${chalk12.gray("Examples:")}
2464
- ${chalk12.cyan("$ lynxp init")} ${chalk12.gray("Initialize LynxPrompt in this directory")}
2465
- ${chalk12.cyan("$ lynxp sync")} ${chalk12.gray("Sync rules to all configured agents")}
2466
- ${chalk12.cyan("$ lynxp agents")} ${chalk12.gray("List and manage AI agents")}
2467
- ${chalk12.cyan("$ lynxp wizard")} ${chalk12.gray("Start interactive configuration wizard")}
2468
- ${chalk12.cyan("$ lynxp pull bp_abc123")} ${chalk12.gray("Download a blueprint")}
2469
- ${chalk12.cyan("$ lynxp search nextjs")} ${chalk12.gray("Search public blueprints")}
2470
-
2471
- ${chalk12.gray("Documentation: https://lynxprompt.com/docs/cli")}
3900
+ ${chalk16.cyan("Quick Start:")}
3901
+ ${chalk16.white("$ lynxp wizard")} ${chalk16.gray("Generate config interactively")}
3902
+ ${chalk16.white("$ lynxp wizard -y")} ${chalk16.gray("Generate AGENTS.md with defaults")}
3903
+ ${chalk16.white("$ lynxp wizard -f cursor")} ${chalk16.gray("Generate .cursor/rules/")}
3904
+
3905
+ ${chalk16.cyan("Marketplace:")}
3906
+ ${chalk16.white("$ lynxp search nextjs")} ${chalk16.gray("Search blueprints")}
3907
+ ${chalk16.white("$ lynxp pull bp_abc123")} ${chalk16.gray("Download and track a blueprint")}
3908
+ ${chalk16.white("$ lynxp push")} ${chalk16.gray("Push local file to cloud")}
3909
+ ${chalk16.white("$ lynxp link --list")} ${chalk16.gray("Show tracked blueprints")}
3910
+
3911
+ ${chalk16.cyan("Blueprint Tracking:")}
3912
+ ${chalk16.white("$ lynxp link AGENTS.md bp_xyz")} ${chalk16.gray("Link existing file to blueprint")}
3913
+ ${chalk16.white("$ lynxp unlink AGENTS.md")} ${chalk16.gray("Disconnect from cloud")}
3914
+ ${chalk16.white("$ lynxp diff bp_abc123")} ${chalk16.gray("Show changes vs cloud version")}
3915
+
3916
+ ${chalk16.cyan("CI/CD:")}
3917
+ ${chalk16.white("$ lynxp check --ci")} ${chalk16.gray("Validate config (exit code)")}
3918
+
3919
+ ${chalk16.gray("Docs: https://lynxprompt.com/docs/cli")}
2472
3920
  `
2473
3921
  );
2474
3922
  program.parse();