lynxprompt 0.1.0 → 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 chalk9 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
+ }
444
633
  }
445
- if (fileExists2 && !options.yes) {
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
+ }
649
+ }
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,175 +761,1208 @@ 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, access as access3 } from "fs/promises";
531
- import { join as join3, dirname as dirname2 } from "path";
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";
980
+
981
+ // src/utils/agent-detector.ts
982
+ import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
983
+ import { join as join3 } from "path";
984
+
985
+ // src/utils/agents.ts
986
+ var AGENTS = [
987
+ // === POPULAR AGENTS ===
988
+ {
989
+ id: "cursor",
990
+ name: "Cursor",
991
+ description: "AI-powered code editor with .cursor/rules/ support",
992
+ patterns: [".cursor/rules/"],
993
+ output: ".cursor/rules/",
994
+ format: "mdc",
995
+ category: "popular",
996
+ popular: true
997
+ },
998
+ {
999
+ id: "agents",
1000
+ name: "AGENTS.md",
1001
+ description: "Universal format for Claude Code, GitHub Copilot, Aider, and others",
1002
+ patterns: ["AGENTS.md"],
1003
+ output: "AGENTS.md",
1004
+ format: "markdown",
1005
+ category: "popular",
1006
+ popular: true
1007
+ },
1008
+ {
1009
+ id: "claude",
1010
+ name: "Claude Code",
1011
+ description: "Anthropic's Claude with CLAUDE.md support",
1012
+ patterns: ["CLAUDE.md"],
1013
+ output: "CLAUDE.md",
1014
+ format: "markdown",
1015
+ category: "popular",
1016
+ popular: true
1017
+ },
1018
+ {
1019
+ id: "copilot",
1020
+ name: "GitHub Copilot",
1021
+ description: "GitHub's AI pair programmer",
1022
+ patterns: [".github/copilot-instructions.md"],
1023
+ output: ".github/copilot-instructions.md",
1024
+ format: "markdown",
1025
+ category: "popular",
1026
+ popular: true
1027
+ },
1028
+ {
1029
+ id: "windsurf",
1030
+ name: "Windsurf",
1031
+ description: "Codeium's AI IDE with .windsurfrules support",
1032
+ patterns: [".windsurfrules", ".windsurf/rules/"],
1033
+ output: ".windsurfrules",
1034
+ format: "text",
1035
+ category: "popular",
1036
+ popular: true
1037
+ },
1038
+ // === MARKDOWN FORMAT AGENTS ===
1039
+ {
1040
+ id: "gemini",
1041
+ name: "Gemini",
1042
+ description: "Google's Gemini AI assistant",
1043
+ patterns: ["GEMINI.md"],
1044
+ output: "GEMINI.md",
1045
+ format: "markdown",
1046
+ category: "markdown"
1047
+ },
1048
+ {
1049
+ id: "warp",
1050
+ name: "Warp AI",
1051
+ description: "Warp terminal's AI assistant",
1052
+ patterns: ["WARP.md"],
1053
+ output: "WARP.md",
1054
+ format: "markdown",
1055
+ category: "markdown"
1056
+ },
1057
+ {
1058
+ id: "zed",
1059
+ name: "Zed",
1060
+ description: "High-performance code editor with AI features",
1061
+ patterns: [".zed/instructions.md", "ZED.md"],
1062
+ output: ".zed/instructions.md",
1063
+ format: "markdown",
1064
+ category: "markdown"
1065
+ },
1066
+ {
1067
+ id: "crush",
1068
+ name: "Crush",
1069
+ description: "AI coding assistant",
1070
+ patterns: ["CRUSH.md"],
1071
+ output: "CRUSH.md",
1072
+ format: "markdown",
1073
+ category: "markdown"
1074
+ },
1075
+ {
1076
+ id: "junie",
1077
+ name: "Junie",
1078
+ description: "JetBrains' AI coding assistant",
1079
+ patterns: [".junie/guidelines.md"],
1080
+ output: ".junie/guidelines.md",
1081
+ format: "markdown",
1082
+ category: "markdown"
1083
+ },
1084
+ {
1085
+ id: "openhands",
1086
+ name: "OpenHands",
1087
+ description: "Open-source AI coding agent",
1088
+ patterns: [".openhands/microagents/repo.md"],
1089
+ output: ".openhands/microagents/repo.md",
1090
+ format: "markdown",
1091
+ category: "markdown"
1092
+ },
1093
+ // === PLAIN TEXT FORMAT ===
1094
+ {
1095
+ id: "cline",
1096
+ name: "Cline",
1097
+ description: "VS Code AI assistant extension",
1098
+ patterns: [".clinerules"],
1099
+ output: ".clinerules",
1100
+ format: "text",
1101
+ category: "config"
1102
+ },
1103
+ {
1104
+ id: "goose",
1105
+ name: "Goose",
1106
+ description: "Block's AI coding assistant",
1107
+ patterns: [".goosehints"],
1108
+ output: ".goosehints",
1109
+ format: "text",
1110
+ category: "config"
1111
+ },
1112
+ {
1113
+ id: "aider",
1114
+ name: "Aider",
1115
+ description: "AI pair programming in your terminal",
1116
+ patterns: [".aider.conf.yml", "AIDER.md"],
1117
+ output: "AIDER.md",
1118
+ format: "markdown",
1119
+ category: "config"
1120
+ },
1121
+ // === DIRECTORY-BASED AGENTS ===
1122
+ {
1123
+ id: "amazonq",
1124
+ name: "Amazon Q",
1125
+ description: "AWS's AI coding assistant",
1126
+ patterns: [".amazonq/rules/"],
1127
+ output: ".amazonq/rules/",
1128
+ format: "mdc",
1129
+ category: "directory"
1130
+ },
1131
+ {
1132
+ id: "augmentcode",
1133
+ name: "Augment Code",
1134
+ description: "AI code augmentation tool",
1135
+ patterns: [".augment/rules/"],
1136
+ output: ".augment/rules/",
1137
+ format: "mdc",
1138
+ category: "directory"
1139
+ },
1140
+ {
1141
+ id: "kilocode",
1142
+ name: "Kilocode",
1143
+ description: "AI-powered code generation",
1144
+ patterns: [".kilocode/rules/"],
1145
+ output: ".kilocode/rules/",
1146
+ format: "mdc",
1147
+ category: "directory"
1148
+ },
1149
+ {
1150
+ id: "kiro",
1151
+ name: "Kiro",
1152
+ description: "AWS's spec-driven AI coding agent",
1153
+ patterns: [".kiro/steering/"],
1154
+ output: ".kiro/steering/",
1155
+ format: "mdc",
1156
+ category: "directory"
1157
+ },
1158
+ {
1159
+ id: "trae-ai",
1160
+ name: "Trae AI",
1161
+ description: "ByteDance's AI coding assistant",
1162
+ patterns: [".trae/rules/"],
1163
+ output: ".trae/rules/",
1164
+ format: "mdc",
1165
+ category: "directory"
1166
+ },
1167
+ {
1168
+ id: "firebase-studio",
1169
+ name: "Firebase Studio",
1170
+ description: "Google's Firebase development environment",
1171
+ patterns: [".idx/"],
1172
+ output: ".idx/",
1173
+ format: "mdc",
1174
+ category: "directory"
1175
+ },
1176
+ {
1177
+ id: "roocode",
1178
+ name: "Roo Code",
1179
+ description: "AI coding assistant for VS Code",
1180
+ patterns: [".roo/rules/"],
1181
+ output: ".roo/rules/",
1182
+ format: "mdc",
1183
+ category: "directory"
1184
+ },
1185
+ // === JSON/CONFIG FORMAT ===
1186
+ {
1187
+ id: "firebender",
1188
+ name: "Firebender",
1189
+ description: "AI code transformation tool",
1190
+ patterns: ["firebender.json"],
1191
+ output: "firebender.json",
1192
+ format: "json",
1193
+ category: "config"
1194
+ },
1195
+ {
1196
+ id: "opencode",
1197
+ name: "Open Code",
1198
+ description: "Open-source AI coding tool",
1199
+ patterns: ["opencode.json"],
1200
+ output: "opencode.json",
1201
+ format: "json",
1202
+ category: "config"
1203
+ },
1204
+ // === MCP (Model Context Protocol) AGENTS ===
1205
+ {
1206
+ id: "vscode-mcp",
1207
+ name: "VS Code MCP",
1208
+ description: "VS Code with Model Context Protocol",
1209
+ patterns: [".vscode/mcp.json"],
1210
+ output: ".vscode/mcp.json",
1211
+ format: "json",
1212
+ category: "mcp"
1213
+ },
1214
+ {
1215
+ id: "cursor-mcp",
1216
+ name: "Cursor MCP",
1217
+ description: "Cursor with Model Context Protocol",
1218
+ patterns: [".cursor/mcp.json"],
1219
+ output: ".cursor/mcp.json",
1220
+ format: "json",
1221
+ category: "mcp"
1222
+ },
1223
+ {
1224
+ id: "root-mcp",
1225
+ name: "Root MCP",
1226
+ description: "Root-level MCP config for Claude Code, Aider",
1227
+ patterns: [".mcp.json"],
1228
+ output: ".mcp.json",
1229
+ format: "json",
1230
+ category: "mcp"
1231
+ },
1232
+ {
1233
+ id: "windsurf-mcp",
1234
+ name: "Windsurf MCP",
1235
+ description: "Windsurf with Model Context Protocol",
1236
+ patterns: [".windsurf/mcp_config.json"],
1237
+ output: ".windsurf/mcp_config.json",
1238
+ format: "json",
1239
+ category: "mcp"
1240
+ }
1241
+ ];
1242
+ function getAgent(id) {
1243
+ return AGENTS.find((a) => a.id === id);
1244
+ }
1245
+ function getPopularAgents() {
1246
+ return AGENTS.filter((a) => a.popular);
1247
+ }
1248
+ function getAgentDisplayName(id) {
1249
+ const agent = getAgent(id);
1250
+ return agent?.name ?? id;
1251
+ }
1252
+
1253
+ // src/utils/agent-detector.ts
1254
+ function detectAgents(cwd = process.cwd()) {
1255
+ const detected = [];
1256
+ for (const agent of AGENTS) {
1257
+ const result = detectAgent(cwd, agent);
1258
+ if (result) {
1259
+ detected.push(result);
1260
+ }
1261
+ }
1262
+ const popularDetected = detected.filter((d) => d.agent.popular);
1263
+ const importable = detected.filter((d) => d.hasContent);
1264
+ let summary;
1265
+ if (detected.length === 0) {
1266
+ summary = "No AI agent configuration files detected";
1267
+ } else if (detected.length === 1) {
1268
+ summary = `Found ${getAgentDisplayName(detected[0].agent.id)} configuration`;
1269
+ } else {
1270
+ const names = detected.slice(0, 3).map((d) => d.agent.name);
1271
+ const more = detected.length > 3 ? ` +${detected.length - 3} more` : "";
1272
+ summary = `Found ${detected.length} agents: ${names.join(", ")}${more}`;
1273
+ }
1274
+ return { detected, popularDetected, importable, summary };
1275
+ }
1276
+ function detectAgent(cwd, agent) {
1277
+ const files = [];
1278
+ let hasContent = false;
1279
+ let ruleCount = 0;
1280
+ for (const pattern of agent.patterns) {
1281
+ const fullPath = join3(cwd, pattern);
1282
+ if (!existsSync2(fullPath)) {
1283
+ continue;
1284
+ }
1285
+ const stats = statSync(fullPath);
1286
+ if (stats.isDirectory()) {
1287
+ const dirFiles = scanDirectory(fullPath, agent.format);
1288
+ if (dirFiles.length > 0) {
1289
+ files.push(pattern);
1290
+ hasContent = true;
1291
+ ruleCount += dirFiles.reduce((sum, f) => sum + f.sections, 0);
1292
+ }
1293
+ } else if (stats.isFile()) {
1294
+ files.push(pattern);
1295
+ const content = safeReadFile(fullPath);
1296
+ if (content && content.trim().length > 0) {
1297
+ hasContent = true;
1298
+ ruleCount += countSections(content);
1299
+ }
1300
+ }
1301
+ }
1302
+ if (files.length === 0) {
1303
+ return null;
1304
+ }
1305
+ return { agent, files, hasContent, ruleCount };
1306
+ }
1307
+ function scanDirectory(dirPath, format) {
1308
+ const results = [];
1309
+ try {
1310
+ const entries = readdirSync(dirPath, { withFileTypes: true });
1311
+ for (const entry of entries) {
1312
+ if (!entry.isFile()) continue;
1313
+ const name = entry.name.toLowerCase();
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"));
1315
+ if (!isRuleFile) continue;
1316
+ const filePath = join3(dirPath, entry.name);
1317
+ const content = safeReadFile(filePath);
1318
+ if (content && content.trim().length > 0) {
1319
+ results.push({
1320
+ path: filePath,
1321
+ sections: countSections(content)
1322
+ });
1323
+ }
1324
+ }
1325
+ } catch {
1326
+ }
1327
+ return results;
1328
+ }
1329
+ function countSections(content) {
1330
+ const headings = content.match(/^#{1,6}\s+.+$/gm);
1331
+ return headings ? headings.length : content.trim().length > 0 ? 1 : 0;
1332
+ }
1333
+ function safeReadFile(path2) {
1334
+ try {
1335
+ return readFileSync(path2, "utf-8");
1336
+ } catch {
1337
+ return null;
1338
+ }
1339
+ }
1340
+ function formatDetectionResults(result) {
1341
+ if (result.detected.length === 0) {
1342
+ return "No AI agent configuration files found.\n\nRun 'lynxp wizard' to create your first configuration.";
1343
+ }
1344
+ const lines = [
1345
+ `Found ${result.detected.length} AI agent${result.detected.length === 1 ? "" : "s"}:`,
1346
+ ""
1347
+ ];
1348
+ for (const detected of result.detected) {
1349
+ const icon = detected.hasContent ? "\u2713" : "\u25CB";
1350
+ const rules = detected.ruleCount > 0 ? ` (${detected.ruleCount} sections)` : "";
1351
+ lines.push(` ${icon} ${detected.agent.name}${rules}`);
1352
+ for (const file of detected.files) {
1353
+ lines.push(` \u2514\u2500 ${file}`);
1354
+ }
1355
+ }
1356
+ if (result.importable.length > 0) {
1357
+ lines.push("");
1358
+ lines.push(`${result.importable.length} can be imported into LynxPrompt.`);
1359
+ }
1360
+ return lines.join("\n");
1361
+ }
532
1362
 
533
1363
  // src/utils/detect.ts
534
- import { readFile, access as access2 } from "fs/promises";
535
- import { join as join2 } 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
+ };
536
1400
  async function detectProject(cwd) {
537
1401
  const detected = {
538
1402
  name: null,
539
1403
  stack: [],
540
1404
  commands: {},
541
- packageManager: null
1405
+ packageManager: null,
1406
+ type: "unknown"
542
1407
  };
543
- const packageJsonPath = join2(cwd, "package.json");
1408
+ const packageJsonPath = join4(cwd, "package.json");
544
1409
  if (await fileExists(packageJsonPath)) {
545
1410
  try {
546
- const content = await readFile(packageJsonPath, "utf-8");
1411
+ const content = await readFile3(packageJsonPath, "utf-8");
547
1412
  const pkg = JSON.parse(content);
548
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
+ }
549
1422
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
550
- if (allDeps["typescript"]) detected.stack.push("typescript");
551
- if (allDeps["react"]) detected.stack.push("react");
552
- if (allDeps["next"]) detected.stack.push("nextjs");
553
- if (allDeps["vue"]) detected.stack.push("vue");
554
- if (allDeps["@angular/core"]) detected.stack.push("angular");
555
- if (allDeps["svelte"]) detected.stack.push("svelte");
556
- if (allDeps["express"]) detected.stack.push("express");
557
- if (allDeps["fastify"]) detected.stack.push("fastify");
558
- if (allDeps["prisma"]) detected.stack.push("prisma");
559
- 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
+ }
560
1436
  if (pkg.scripts) {
561
1437
  detected.commands.build = pkg.scripts.build;
562
1438
  detected.commands.test = pkg.scripts.test;
563
- detected.commands.lint = pkg.scripts.lint;
564
- 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;
565
1442
  }
566
- if (await fileExists(join2(cwd, "pnpm-lock.yaml"))) {
1443
+ if (await fileExists(join4(cwd, "pnpm-lock.yaml"))) {
567
1444
  detected.packageManager = "pnpm";
568
- } else if (await fileExists(join2(cwd, "yarn.lock"))) {
1445
+ } else if (await fileExists(join4(cwd, "yarn.lock"))) {
569
1446
  detected.packageManager = "yarn";
570
- } else if (await fileExists(join2(cwd, "bun.lockb"))) {
1447
+ } else if (await fileExists(join4(cwd, "bun.lockb"))) {
571
1448
  detected.packageManager = "bun";
572
- } else if (await fileExists(join2(cwd, "package-lock.json"))) {
1449
+ } else if (await fileExists(join4(cwd, "package-lock.json"))) {
573
1450
  detected.packageManager = "npm";
574
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
+ }
575
1472
  return detected;
576
1473
  } catch {
577
1474
  }
578
1475
  }
579
- const pyprojectPath = join2(cwd, "pyproject.toml");
1476
+ const pyprojectPath = join4(cwd, "pyproject.toml");
580
1477
  if (await fileExists(pyprojectPath)) {
581
1478
  try {
582
- const content = await readFile(pyprojectPath, "utf-8");
1479
+ const content = await readFile3(pyprojectPath, "utf-8");
583
1480
  detected.stack.push("python");
1481
+ detected.type = "application";
584
1482
  const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
585
1483
  if (nameMatch) detected.name = nameMatch[1];
586
1484
  if (content.includes("fastapi")) detected.stack.push("fastapi");
587
1485
  if (content.includes("django")) detected.stack.push("django");
588
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");
589
1492
  detected.commands.test = "pytest";
590
- 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
+ }
591
1500
  return detected;
592
1501
  } catch {
593
1502
  }
594
1503
  }
595
- const requirementsPath = join2(cwd, "requirements.txt");
1504
+ const requirementsPath = join4(cwd, "requirements.txt");
596
1505
  if (await fileExists(requirementsPath)) {
597
1506
  try {
598
- const content = await readFile(requirementsPath, "utf-8");
1507
+ const content = await readFile3(requirementsPath, "utf-8");
599
1508
  detected.stack.push("python");
600
- if (content.includes("fastapi")) detected.stack.push("fastapi");
601
- if (content.includes("django")) detected.stack.push("django");
602
- 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");
603
1513
  detected.commands.test = "pytest";
604
- detected.commands.lint = "ruff check";
1514
+ detected.commands.lint = "ruff check .";
605
1515
  return detected;
606
1516
  } catch {
607
1517
  }
608
1518
  }
609
- const cargoPath = join2(cwd, "Cargo.toml");
1519
+ const cargoPath = join4(cwd, "Cargo.toml");
610
1520
  if (await fileExists(cargoPath)) {
611
1521
  try {
612
- const content = await readFile(cargoPath, "utf-8");
1522
+ const content = await readFile3(cargoPath, "utf-8");
613
1523
  detected.stack.push("rust");
1524
+ detected.type = "application";
614
1525
  const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
615
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");
616
1532
  detected.commands.build = "cargo build";
617
1533
  detected.commands.test = "cargo test";
618
1534
  detected.commands.lint = "cargo clippy";
1535
+ detected.commands.dev = "cargo run";
619
1536
  return detected;
620
1537
  } catch {
621
1538
  }
622
1539
  }
623
- const goModPath = join2(cwd, "go.mod");
1540
+ const goModPath = join4(cwd, "go.mod");
624
1541
  if (await fileExists(goModPath)) {
625
1542
  try {
626
- const content = await readFile(goModPath, "utf-8");
1543
+ const content = await readFile3(goModPath, "utf-8");
627
1544
  detected.stack.push("go");
1545
+ detected.type = "application";
628
1546
  const moduleMatch = content.match(/module\s+(\S+)/);
629
1547
  if (moduleMatch) {
630
1548
  const parts = moduleMatch[1].split("/");
631
1549
  detected.name = parts[parts.length - 1];
632
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");
633
1555
  detected.commands.build = "go build";
634
1556
  detected.commands.test = "go test ./...";
635
1557
  detected.commands.lint = "golangci-lint run";
1558
+ detected.commands.dev = "go run .";
636
1559
  return detected;
637
1560
  } catch {
638
1561
  }
639
1562
  }
640
- const makefilePath = join2(cwd, "Makefile");
1563
+ const makefilePath = join4(cwd, "Makefile");
641
1564
  if (await fileExists(makefilePath)) {
642
1565
  try {
643
- const content = await readFile(makefilePath, "utf-8");
1566
+ const content = await readFile3(makefilePath, "utf-8");
644
1567
  if (content.includes("build:")) detected.commands.build = "make build";
645
1568
  if (content.includes("test:")) detected.commands.test = "make test";
646
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";
647
1572
  if (Object.keys(detected.commands).length > 0) {
1573
+ detected.type = "application";
648
1574
  return detected;
649
1575
  }
650
1576
  } catch {
651
1577
  }
652
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
+ }
653
1583
  return detected.stack.length > 0 || detected.name ? detected : null;
654
1584
  }
655
- async function fileExists(path) {
1585
+ async function fileExists(path2) {
656
1586
  try {
657
- await access2(path);
1587
+ await access3(path2);
658
1588
  return true;
659
1589
  } catch {
660
1590
  return false;
661
1591
  }
662
1592
  }
663
1593
 
1594
+ // src/commands/init.ts
1595
+ var LYNXPROMPT_DIR = ".lynxprompt";
1596
+ var LYNXPROMPT_CONFIG = ".lynxprompt/conf.yml";
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
+ ];
1613
+ async function scanForExistingFiles(cwd) {
1614
+ const detected = [];
1615
+ for (const file of AGENT_FILES) {
1616
+ const filePath = join5(cwd, file.name);
1617
+ if (existsSync3(filePath)) {
1618
+ try {
1619
+ const content = await readFile4(filePath, "utf-8");
1620
+ detected.push({ path: file.name, agent: file.agent, content });
1621
+ } catch {
1622
+ detected.push({ path: file.name, agent: file.agent });
1623
+ }
1624
+ }
1625
+ }
1626
+ for (const dir of AGENT_DIRS) {
1627
+ const dirPath = join5(cwd, dir.path);
1628
+ if (existsSync3(dirPath)) {
1629
+ detected.push({ path: dir.path, agent: dir.agent });
1630
+ }
1631
+ }
1632
+ return detected;
1633
+ }
1634
+ function createStarterAgentsMd(projectName) {
1635
+ return `# ${projectName} - AI Agent Instructions
1636
+
1637
+ > Edit this file to customize how AI agents work with your codebase.
1638
+ > This is the source of truth for all AI assistants in this project.
1639
+
1640
+ ## Project Overview
1641
+
1642
+ Describe your project here. What does it do? What are its main features?
1643
+
1644
+ ## Tech Stack
1645
+
1646
+ List the technologies used in this project:
1647
+
1648
+ - Language: (e.g., TypeScript, Python, Go)
1649
+ - Framework: (e.g., Next.js, FastAPI, Rails)
1650
+ - Database: (e.g., PostgreSQL, MongoDB)
1651
+ - Other tools: (e.g., Docker, Kubernetes)
1652
+
1653
+ ## Code Style
1654
+
1655
+ Follow these conventions:
1656
+
1657
+ - Write clean, readable code
1658
+ - Use descriptive variable and function names
1659
+ - Keep functions focused and testable
1660
+ - Add comments for complex logic only
1661
+
1662
+ ## Commands
1663
+
1664
+ \`\`\`bash
1665
+ # Build
1666
+ npm run build
1667
+
1668
+ # Test
1669
+ npm test
1670
+
1671
+ # Lint
1672
+ npm run lint
1673
+
1674
+ # Dev server
1675
+ npm run dev
1676
+ \`\`\`
1677
+
1678
+ ## Boundaries
1679
+
1680
+ ### \u2705 Always (do without asking)
1681
+
1682
+ - Read any file in the project
1683
+ - Modify files in src/ or lib/
1684
+ - Run build, test, and lint commands
1685
+ - Create test files
1686
+
1687
+ ### \u26A0\uFE0F Ask First
1688
+
1689
+ - Add new dependencies
1690
+ - Modify configuration files
1691
+ - Create new modules or directories
1692
+
1693
+ ### \u{1F6AB} Never
1694
+
1695
+ - Modify .env files or secrets
1696
+ - Delete critical files without backup
1697
+ - Force push to git
1698
+ - Expose sensitive information
1699
+
1700
+ ---
1701
+
1702
+ *Managed by [LynxPrompt](https://lynxprompt.com)*
1703
+ `;
1704
+ }
1705
+ function createDefaultConfig(exporters = ["agents"]) {
1706
+ const config2 = {
1707
+ version: "1",
1708
+ exporters,
1709
+ sources: [
1710
+ {
1711
+ type: "local",
1712
+ path: ".lynxprompt/rules"
1713
+ }
1714
+ ]
1715
+ };
1716
+ return yaml2.stringify(config2);
1717
+ }
1718
+ function createLynxpromptReadme() {
1719
+ return `# .lynxprompt
1720
+
1721
+ This directory contains your LynxPrompt configuration and rules.
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
+
1726
+ ## Directory structure
1727
+
1728
+ - **\`rules/\`** - Your AI rules. Edit files here, then sync to agents.
1729
+ - **\`conf.yml\`** - Configuration file (exporters, sources, options)
1730
+
1731
+ ## Editing rules
1732
+
1733
+ Add markdown files to \`rules/\`:
1734
+
1735
+ \`\`\`markdown
1736
+ # My Rule
1737
+
1738
+ Description of what this rule does...
1739
+ \`\`\`
1740
+
1741
+ ## Syncing
1742
+
1743
+ After editing rules, run:
1744
+
1745
+ \`\`\`bash
1746
+ lynxp sync
1747
+ \`\`\`
1748
+
1749
+ This exports your rules to the configured agent formats (AGENTS.md, .cursor/rules/, etc.)
1750
+
1751
+ ## More information
1752
+
1753
+ - Docs: https://lynxprompt.com/docs/cli
1754
+ - Support: https://lynxprompt.com/support
1755
+ `;
1756
+ }
1757
+ async function initCommand(options) {
1758
+ console.log();
1759
+ console.log(chalk7.cyan("\u{1F431} LynxPrompt Init"));
1760
+ console.log(chalk7.gray("Advanced mode: Multi-editor rule management"));
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
+ }
1779
+ const cwd = process.cwd();
1780
+ const projectName = basename(cwd);
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."));
1791
+ return;
1792
+ }
1793
+ const spinner = ora6("Scanning project...").start();
1794
+ const [projectInfo, agentDetection] = await Promise.all([
1795
+ detectProject(cwd),
1796
+ Promise.resolve(detectAgents(cwd))
1797
+ ]);
1798
+ const existingFiles = await scanForExistingFiles(cwd);
1799
+ spinner.stop();
1800
+ if (projectInfo) {
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}`));
1805
+ console.log();
1806
+ }
1807
+ if (agentDetection.detected.length > 0) {
1808
+ console.log(chalk7.green(`\u2713 Detected ${agentDetection.detected.length} AI agent${agentDetection.detected.length === 1 ? "" : "s"}:`));
1809
+ for (const detected of agentDetection.detected) {
1810
+ const rules = detected.ruleCount > 0 ? chalk7.gray(` (${detected.ruleCount} sections)`) : "";
1811
+ console.log(` ${chalk7.cyan("\u2022")} ${detected.agent.name}${rules}`);
1812
+ }
1813
+ console.log();
1814
+ }
1815
+ if (existingFiles.length > 0) {
1816
+ console.log(chalk7.green("\u2713 Found existing AI configuration files:"));
1817
+ for (const file of existingFiles) {
1818
+ console.log(` ${chalk7.cyan(file.path)} ${chalk7.gray(`(${file.agent})`)}`);
1819
+ }
1820
+ console.log();
1821
+ if (!options.yes) {
1822
+ const { action } = await prompts3({
1823
+ type: "select",
1824
+ name: "action",
1825
+ message: "What would you like to do?",
1826
+ choices: [
1827
+ { title: "Import existing files to .lynxprompt/rules/", value: "import" },
1828
+ { title: "Start fresh (keep existing files, create new rules)", value: "fresh" },
1829
+ { title: "Cancel", value: "cancel" }
1830
+ ]
1831
+ });
1832
+ if (action === "cancel" || !action) {
1833
+ console.log(chalk7.gray("Cancelled."));
1834
+ return;
1835
+ }
1836
+ if (action === "import") {
1837
+ await mkdir3(rulesDir, { recursive: true });
1838
+ let importedCount = 0;
1839
+ for (const file of existingFiles) {
1840
+ if (file.content) {
1841
+ const ruleName = file.path.replace(/^\./, "").replace(/\//g, "-").replace(/\.md$/, "") + ".md";
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}`));
1845
+ importedCount++;
1846
+ }
1847
+ }
1848
+ if (importedCount === 0) {
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"));
1852
+ }
1853
+ } else {
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"));
1858
+ }
1859
+ } else {
1860
+ await mkdir3(rulesDir, { recursive: true });
1861
+ for (const file of existingFiles) {
1862
+ if (file.content) {
1863
+ const ruleName = file.path.replace(/^\./, "").replace(/\//g, "-").replace(/\.md$/, "") + ".md";
1864
+ const rulePath = join5(rulesDir, ruleName);
1865
+ await writeFile3(rulePath, file.content, "utf-8");
1866
+ }
1867
+ }
1868
+ }
1869
+ } else {
1870
+ console.log(chalk7.gray("No existing AI configuration files found."));
1871
+ console.log();
1872
+ if (!options.yes) {
1873
+ const { create } = await prompts3({
1874
+ type: "confirm",
1875
+ name: "create",
1876
+ message: "Create a starter template?",
1877
+ initial: true
1878
+ });
1879
+ if (!create) {
1880
+ console.log(chalk7.gray("Cancelled."));
1881
+ return;
1882
+ }
1883
+ }
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"));
1888
+ }
1889
+ let exporters = [];
1890
+ if (agentDetection.detected.length > 0) {
1891
+ exporters = agentDetection.detected.map((d) => d.agent.id);
1892
+ if (agentDetection.detected.length > 3 && !options.yes) {
1893
+ const { selected } = await prompts3({
1894
+ type: "multiselect",
1895
+ name: "selected",
1896
+ message: "Select agents to enable:",
1897
+ choices: agentDetection.detected.map((d) => ({
1898
+ title: d.agent.name,
1899
+ value: d.agent.id,
1900
+ selected: true
1901
+ })),
1902
+ hint: "- Space to toggle, Enter to confirm"
1903
+ });
1904
+ if (selected && selected.length > 0) {
1905
+ exporters = selected;
1906
+ }
1907
+ }
1908
+ } else {
1909
+ exporters = ["agents"];
1910
+ if (!options.yes) {
1911
+ const popular = getPopularAgents();
1912
+ const { selected } = await prompts3({
1913
+ type: "multiselect",
1914
+ name: "selected",
1915
+ message: "Select AI agents to sync to:",
1916
+ choices: popular.map((a) => ({
1917
+ title: `${a.name} - ${a.description}`,
1918
+ value: a.id,
1919
+ selected: a.id === "agents"
1920
+ // Default select AGENTS.md
1921
+ })),
1922
+ hint: "- Space to toggle, Enter to confirm"
1923
+ });
1924
+ if (selected && selected.length > 0) {
1925
+ exporters = selected;
1926
+ }
1927
+ }
1928
+ }
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");
1935
+ const gitignoreContent = `# Local state files
1936
+ .cache/
1937
+ .backups/
1938
+ `;
1939
+ await writeFile3(gitignorePath, gitignoreContent, "utf-8");
1940
+ console.log();
1941
+ console.log(chalk7.green("\u2705 LynxPrompt initialized!"));
1942
+ console.log();
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)`));
1946
+ console.log();
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"));
1951
+ console.log();
1952
+ }
1953
+
1954
+ // src/commands/wizard.ts
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";
1960
+
664
1961
  // src/utils/generator.ts
665
1962
  var PLATFORM_FILES = {
666
- cursor: ".cursorrules",
667
- claude: "AGENTS.md",
1963
+ agents: "AGENTS.md",
1964
+ cursor: ".cursor/rules/project.mdc",
1965
+ claude: "CLAUDE.md",
668
1966
  copilot: ".github/copilot-instructions.md",
669
1967
  windsurf: ".windsurfrules",
670
1968
  zed: ".zed/instructions.md"
@@ -774,13 +2072,25 @@ function generateConfig(options) {
774
2072
  }
775
2073
  function generateFileContent(options, platform) {
776
2074
  const sections = [];
777
- 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
+ }
778
2088
  if (isMarkdown) {
779
2089
  sections.push(`# ${options.name} - AI Assistant Configuration`);
780
2090
  sections.push("");
781
2091
  }
782
2092
  const personaDesc = PERSONA_DESCRIPTIONS[options.persona] || options.persona;
783
- if (isMarkdown) {
2093
+ if (isMarkdown || isMdc) {
784
2094
  sections.push("## Persona");
785
2095
  sections.push("");
786
2096
  sections.push(`You are ${personaDesc}. You assist developers working on ${options.name}.`);
@@ -793,14 +2103,14 @@ function generateFileContent(options, platform) {
793
2103
  }
794
2104
  sections.push("");
795
2105
  if (options.stack.length > 0) {
796
- if (isMarkdown) {
2106
+ if (isMarkdown || isMdc) {
797
2107
  sections.push("## Tech Stack");
798
2108
  sections.push("");
799
2109
  } else {
800
2110
  sections.push("Tech Stack:");
801
2111
  }
802
2112
  const stackList = options.stack.map((s) => STACK_NAMES[s] || s);
803
- if (isMarkdown) {
2113
+ if (isMarkdown || isMdc) {
804
2114
  for (const tech of stackList) {
805
2115
  sections.push(`- ${tech}`);
806
2116
  }
@@ -811,7 +2121,7 @@ function generateFileContent(options, platform) {
811
2121
  }
812
2122
  const hasCommands = Object.values(options.commands).some(Boolean);
813
2123
  if (hasCommands) {
814
- if (isMarkdown) {
2124
+ if (isMarkdown || isMdc) {
815
2125
  sections.push("## Commands");
816
2126
  sections.push("");
817
2127
  sections.push("Use these commands for common tasks:");
@@ -821,25 +2131,25 @@ function generateFileContent(options, platform) {
821
2131
  sections.push("Commands:");
822
2132
  }
823
2133
  if (options.commands.build) {
824
- sections.push(isMarkdown ? `# Build: ${options.commands.build}` : `- Build: ${options.commands.build}`);
2134
+ sections.push(isMarkdown || isMdc ? `# Build: ${options.commands.build}` : `- Build: ${options.commands.build}`);
825
2135
  }
826
2136
  if (options.commands.test) {
827
- sections.push(isMarkdown ? `# Test: ${options.commands.test}` : `- Test: ${options.commands.test}`);
2137
+ sections.push(isMarkdown || isMdc ? `# Test: ${options.commands.test}` : `- Test: ${options.commands.test}`);
828
2138
  }
829
2139
  if (options.commands.lint) {
830
- sections.push(isMarkdown ? `# Lint: ${options.commands.lint}` : `- Lint: ${options.commands.lint}`);
2140
+ sections.push(isMarkdown || isMdc ? `# Lint: ${options.commands.lint}` : `- Lint: ${options.commands.lint}`);
831
2141
  }
832
2142
  if (options.commands.dev) {
833
- sections.push(isMarkdown ? `# Dev: ${options.commands.dev}` : `- Dev: ${options.commands.dev}`);
2143
+ sections.push(isMarkdown || isMdc ? `# Dev: ${options.commands.dev}` : `- Dev: ${options.commands.dev}`);
834
2144
  }
835
- if (isMarkdown) {
2145
+ if (isMarkdown || isMdc) {
836
2146
  sections.push("```");
837
2147
  }
838
2148
  sections.push("");
839
2149
  }
840
2150
  const boundaries = BOUNDARIES[options.boundaries];
841
2151
  if (boundaries) {
842
- if (isMarkdown) {
2152
+ if (isMarkdown || isMdc) {
843
2153
  sections.push("## Boundaries");
844
2154
  sections.push("");
845
2155
  sections.push("### \u2705 Always (do without asking)");
@@ -879,7 +2189,7 @@ function generateFileContent(options, platform) {
879
2189
  }
880
2190
  sections.push("");
881
2191
  }
882
- if (isMarkdown) {
2192
+ if (isMarkdown || isMdc) {
883
2193
  sections.push("## Code Style");
884
2194
  sections.push("");
885
2195
  sections.push("Follow these conventions:");
@@ -915,7 +2225,7 @@ function generateFileContent(options, platform) {
915
2225
  sections.push("- Keep functions focused and testable");
916
2226
  sections.push("");
917
2227
  }
918
- if (isMarkdown) {
2228
+ if (isMarkdown || isMdc) {
919
2229
  sections.push("---");
920
2230
  sections.push("");
921
2231
  sections.push(`*Generated by [LynxPrompt](https://lynxprompt.com) CLI*`);
@@ -923,7 +2233,25 @@ function generateFileContent(options, platform) {
923
2233
  return sections.join("\n");
924
2234
  }
925
2235
 
926
- // src/commands/init.ts
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
+ ];
927
2255
  var TECH_STACKS = [
928
2256
  { title: "TypeScript", value: "typescript" },
929
2257
  { title: "JavaScript", value: "javascript" },
@@ -951,22 +2279,30 @@ var FRAMEWORKS = [
951
2279
  { title: "Laravel", value: "laravel" }
952
2280
  ];
953
2281
  var PLATFORMS = [
954
- { title: "Cursor (.cursorrules)", value: "cursor", filename: ".cursorrules" },
955
- { 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" },
956
2285
  { title: "GitHub Copilot", value: "copilot", filename: ".github/copilot-instructions.md" },
957
2286
  { title: "Windsurf (.windsurfrules)", value: "windsurf", filename: ".windsurfrules" },
958
2287
  { title: "Zed", value: "zed", filename: ".zed/instructions.md" }
959
2288
  ];
960
2289
  var PERSONAS = [
2290
+ { title: "Full-Stack Developer - Complete application setups", value: "fullstack" },
961
2291
  { title: "Backend Developer - APIs, databases, microservices", value: "backend" },
962
2292
  { title: "Frontend Developer - UI, components, styling", value: "frontend" },
963
- { title: "Full-Stack Developer - Complete application setups", value: "fullstack" },
964
2293
  { title: "DevOps Engineer - Infrastructure, CI/CD, containers", value: "devops" },
965
2294
  { title: "Data Engineer - Pipelines, ETL, databases", value: "data" },
966
2295
  { title: "Security Engineer - Secure code, vulnerabilities", value: "security" },
967
2296
  { title: "Custom...", value: "custom" }
968
2297
  ];
969
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
+ },
970
2306
  {
971
2307
  title: "Conservative - Ask before most changes",
972
2308
  value: "conservative",
@@ -974,13 +2310,6 @@ var BOUNDARY_PRESETS = [
974
2310
  askFirst: ["Modify any file", "Add dependencies", "Create files", "Run tests"],
975
2311
  never: ["Delete files", "Modify .env", "Push to git"]
976
2312
  },
977
- {
978
- title: "Standard - Balance of freedom and safety",
979
- value: "standard",
980
- always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
981
- askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
982
- never: ["Delete production data", "Modify .env secrets", "Force push"]
983
- },
984
2313
  {
985
2314
  title: "Permissive - AI can modify freely within src/",
986
2315
  value: "permissive",
@@ -989,26 +2318,36 @@ var BOUNDARY_PRESETS = [
989
2318
  never: ["Modify .env", "Access external APIs without confirmation"]
990
2319
  }
991
2320
  ];
992
- async function initCommand(options) {
2321
+ async function wizardCommand(options) {
993
2322
  console.log();
994
- console.log(chalk6.cyan("\u{1F431} Welcome to LynxPrompt!"));
2323
+ console.log(chalk8.cyan("\u{1F431} LynxPrompt Wizard"));
2324
+ console.log(chalk8.gray("Generate AI IDE configuration in seconds"));
995
2325
  console.log();
996
2326
  const detected = await detectProject(process.cwd());
997
2327
  if (detected) {
998
- console.log(chalk6.gray("Detected project configuration:"));
999
- if (detected.name) console.log(chalk6.gray(` Name: ${detected.name}`));
1000
- if (detected.stack.length > 0) console.log(chalk6.gray(` Stack: ${detected.stack.join(", ")}`));
1001
- if (detected.commands.build) console.log(chalk6.gray(` Build: ${detected.commands.build}`));
1002
- if (detected.commands.test) console.log(chalk6.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}`));
1003
2334
  console.log();
1004
2335
  }
1005
2336
  let config2;
1006
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
+ }
1007
2346
  config2 = {
1008
2347
  name: options.name || detected?.name || "my-project",
1009
2348
  description: options.description || "",
1010
2349
  stack: options.stack?.split(",").map((s) => s.trim()) || detected?.stack || [],
1011
- platforms: options.platforms?.split(",").map((s) => s.trim()) || ["cursor", "claude"],
2350
+ platforms,
1012
2351
  persona: options.persona || "fullstack",
1013
2352
  boundaries: options.boundaries || "standard",
1014
2353
  commands: detected?.commands || {}
@@ -1016,123 +2355,164 @@ async function initCommand(options) {
1016
2355
  } else {
1017
2356
  config2 = await runInteractiveWizard(options, detected);
1018
2357
  }
1019
- const spinner = ora5("Generating configuration files...").start();
2358
+ const spinner = ora7("Generating configuration...").start();
1020
2359
  try {
1021
2360
  const files = generateConfig(config2);
1022
2361
  spinner.stop();
1023
2362
  console.log();
1024
- console.log(chalk6.green("\u2705 Generated files:"));
2363
+ console.log(chalk8.green("\u2705 Generated:"));
1025
2364
  for (const [filename, content] of Object.entries(files)) {
1026
- const outputPath = join3(process.cwd(), filename);
2365
+ const outputPath = join6(process.cwd(), filename);
1027
2366
  let exists = false;
1028
2367
  try {
1029
- await access3(outputPath);
2368
+ await access5(outputPath);
1030
2369
  exists = true;
1031
2370
  } catch {
1032
2371
  }
1033
2372
  if (exists && !options.yes) {
1034
- const response = await prompts2({
2373
+ const response = await prompts4({
1035
2374
  type: "confirm",
1036
2375
  name: "overwrite",
1037
2376
  message: `${filename} already exists. Overwrite?`,
1038
2377
  initial: false
1039
2378
  });
1040
2379
  if (!response.overwrite) {
1041
- console.log(chalk6.yellow(` Skipped: ${filename}`));
2380
+ console.log(chalk8.yellow(` Skipped: ${filename}`));
1042
2381
  continue;
1043
2382
  }
1044
2383
  }
1045
- const dir = dirname2(outputPath);
2384
+ const dir = dirname4(outputPath);
1046
2385
  if (dir !== ".") {
1047
- await mkdir2(dir, { recursive: true });
2386
+ await mkdir4(dir, { recursive: true });
1048
2387
  }
1049
- await writeFile2(outputPath, content, "utf-8");
1050
- console.log(` ${chalk6.cyan(filename)}`);
2388
+ await writeFile4(outputPath, content, "utf-8");
2389
+ console.log(` ${chalk8.cyan(filename)}`);
1051
2390
  }
1052
2391
  console.log();
1053
- console.log(chalk6.gray("Your AI IDE configuration is ready!"));
1054
- console.log(chalk6.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"));
1055
2398
  console.log();
1056
2399
  } catch (error) {
1057
2400
  spinner.fail("Failed to generate files");
1058
- console.error(chalk6.red("An error occurred while generating configuration files."));
2401
+ console.error(chalk8.red("\n\u2717 An error occurred while generating configuration files."));
1059
2402
  if (error instanceof Error) {
1060
- console.error(chalk6.gray(error.message));
2403
+ console.error(chalk8.gray(` ${error.message}`));
1061
2404
  }
2405
+ console.error(chalk8.gray("\nTry running with --yes flag for default settings."));
1062
2406
  process.exit(1);
1063
2407
  }
1064
2408
  }
1065
2409
  async function runInteractiveWizard(options, detected) {
1066
2410
  const answers = {};
1067
- const nameResponse = await prompts2({
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({
1068
2443
  type: "text",
1069
2444
  name: "name",
1070
- message: "What's your project name?",
2445
+ message: "Project name:",
1071
2446
  initial: options.name || detected?.name || "my-project"
1072
2447
  });
1073
- answers.name = nameResponse.name;
1074
- const descResponse = await prompts2({
2448
+ answers.name = nameResponse.name || "my-project";
2449
+ const descResponse = await prompts4({
1075
2450
  type: "text",
1076
2451
  name: "description",
1077
- message: "Describe your project in one sentence:",
2452
+ message: "Brief description (optional):",
1078
2453
  initial: options.description || ""
1079
2454
  });
1080
- answers.description = descResponse.description;
2455
+ answers.description = descResponse.description || "";
1081
2456
  const allStackOptions = [...TECH_STACKS, ...FRAMEWORKS];
1082
- const stackResponse = await prompts2({
2457
+ const detectedStackSet = new Set(detected?.stack || []);
2458
+ const stackResponse = await prompts4({
1083
2459
  type: "multiselect",
1084
2460
  name: "stack",
1085
- message: "Select your tech stack:",
1086
- 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
+ })),
1087
2467
  hint: "- Space to select, Enter to confirm"
1088
2468
  });
1089
2469
  answers.stack = stackResponse.stack || [];
1090
- const platformResponse = await prompts2({
1091
- type: "multiselect",
1092
- name: "platforms",
1093
- message: "Which AI IDEs do you use?",
1094
- choices: PLATFORMS,
1095
- hint: "- Space to select, Enter to confirm",
1096
- min: 1
1097
- });
1098
- answers.platforms = platformResponse.platforms || ["cursor"];
1099
- const personaResponse = await prompts2({
2470
+ const personaResponse = await prompts4({
1100
2471
  type: "select",
1101
2472
  name: "persona",
1102
- message: "What's the AI's persona/role?",
2473
+ message: "AI persona:",
1103
2474
  choices: PERSONAS,
1104
- initial: 2
2475
+ initial: 0
1105
2476
  // Full-stack by default
1106
2477
  });
1107
2478
  if (personaResponse.persona === "custom") {
1108
- const customPersona = await prompts2({
2479
+ const customPersona = await prompts4({
1109
2480
  type: "text",
1110
2481
  name: "value",
1111
2482
  message: "Describe the custom persona:"
1112
2483
  });
1113
2484
  answers.persona = customPersona.value || "fullstack";
1114
2485
  } else {
1115
- answers.persona = personaResponse.persona;
2486
+ answers.persona = personaResponse.persona || "fullstack";
1116
2487
  }
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";
1117
2497
  if (detected?.commands && Object.keys(detected.commands).length > 0) {
1118
2498
  console.log();
1119
- console.log(chalk6.gray("Auto-detected commands:"));
1120
- if (detected.commands.build) console.log(chalk6.gray(` Build: ${detected.commands.build}`));
1121
- if (detected.commands.test) console.log(chalk6.gray(` Test: ${detected.commands.test}`));
1122
- if (detected.commands.lint) console.log(chalk6.gray(` Lint: ${detected.commands.lint}`));
1123
- if (detected.commands.dev) console.log(chalk6.gray(` Dev: ${detected.commands.dev}`));
1124
- const editCommands = await prompts2({
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({
1125
2505
  type: "confirm",
1126
2506
  name: "edit",
1127
- message: "Edit these commands?",
2507
+ message: "Edit commands?",
1128
2508
  initial: false
1129
2509
  });
1130
2510
  if (editCommands.edit) {
1131
- const commandsResponse = await prompts2([
1132
- { type: "text", name: "build", message: "Build command:", initial: detected.commands.build },
1133
- { type: "text", name: "test", message: "Test command:", initial: detected.commands.test },
1134
- { type: "text", name: "lint", message: "Lint command:", initial: detected.commands.lint },
1135
- { 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 }
1136
2516
  ]);
1137
2517
  answers.commands = commandsResponse;
1138
2518
  } else {
@@ -1141,15 +2521,6 @@ async function runInteractiveWizard(options, detected) {
1141
2521
  } else {
1142
2522
  answers.commands = {};
1143
2523
  }
1144
- const boundaryResponse = await prompts2({
1145
- type: "select",
1146
- name: "boundaries",
1147
- message: "Select boundary preset:",
1148
- choices: BOUNDARY_PRESETS.map((b) => ({ title: b.title, value: b.value })),
1149
- initial: 1
1150
- // Standard by default
1151
- });
1152
- answers.boundaries = boundaryResponse.boundaries || "standard";
1153
2524
  return {
1154
2525
  name: answers.name,
1155
2526
  description: answers.description,
@@ -1162,52 +2533,52 @@ async function runInteractiveWizard(options, detected) {
1162
2533
  }
1163
2534
 
1164
2535
  // src/commands/search.ts
1165
- import chalk7 from "chalk";
1166
- import ora6 from "ora";
2536
+ import chalk9 from "chalk";
2537
+ import ora8 from "ora";
1167
2538
  async function searchCommand(query, options) {
1168
- const spinner = ora6(`Searching for "${query}"...`).start();
2539
+ const spinner = ora8(`Searching for "${query}"...`).start();
1169
2540
  try {
1170
2541
  const limit = parseInt(options.limit, 10) || 20;
1171
2542
  const { templates, total, hasMore } = await api.searchBlueprints(query, limit);
1172
2543
  spinner.stop();
1173
2544
  if (templates.length === 0) {
1174
2545
  console.log();
1175
- console.log(chalk7.yellow(`No blueprints found for "${query}".`));
1176
- console.log(chalk7.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"));
1177
2548
  return;
1178
2549
  }
1179
2550
  console.log();
1180
- console.log(chalk7.cyan(`\u{1F50D} Search Results for "${query}" (${total} found)`));
2551
+ console.log(chalk9.cyan(`\u{1F50D} Search Results for "${query}" (${total} found)`));
1181
2552
  console.log();
1182
2553
  for (const result of templates) {
1183
2554
  printSearchResult(result);
1184
2555
  }
1185
2556
  if (hasMore) {
1186
- console.log(chalk7.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.`));
1187
2558
  }
1188
2559
  console.log();
1189
- console.log(chalk7.gray("Use 'lynxprompt pull <id>' to download a blueprint."));
2560
+ console.log(chalk9.gray("Use 'lynxprompt pull <id>' to download a blueprint."));
1190
2561
  } catch (error) {
1191
2562
  spinner.fail("Search failed");
1192
2563
  handleApiError3(error);
1193
2564
  }
1194
2565
  }
1195
2566
  function printSearchResult(result) {
1196
- const priceInfo = result.price ? chalk7.yellow(`\u20AC${(result.price / 100).toFixed(2)}`) : chalk7.green("Free");
1197
- const officialBadge = result.isOfficial ? chalk7.magenta(" \u2605 Official") : "";
1198
- console.log(` ${chalk7.bold(result.name)}${officialBadge}`);
1199
- console.log(` ${chalk7.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}`);
1200
2571
  if (result.description) {
1201
- console.log(` ${chalk7.gray(truncate2(result.description, 60))}`);
2572
+ console.log(` ${chalk9.gray(truncate2(result.description, 60))}`);
1202
2573
  }
1203
- console.log(` ${chalk7.gray(`by ${result.author}`)} \u2022 ${chalk7.gray(`\u2193${result.downloads}`)} ${chalk7.gray(`\u2665${result.likes}`)}`);
2574
+ console.log(` ${chalk9.gray(`by ${result.author}`)} \u2022 ${chalk9.gray(`\u2193${result.downloads}`)} ${chalk9.gray(`\u2665${result.likes}`)}`);
1204
2575
  if (result.tags && result.tags.length > 0) {
1205
2576
  console.log(` ${formatTags2(result.tags)}`);
1206
2577
  }
1207
2578
  console.log();
1208
2579
  }
1209
2580
  function formatTags2(tags) {
1210
- return tags.slice(0, 4).map((t) => chalk7.gray(`#${t}`)).join(" ");
2581
+ return tags.slice(0, 4).map((t) => chalk9.gray(`#${t}`)).join(" ");
1211
2582
  }
1212
2583
  function truncate2(str, maxLength) {
1213
2584
  if (str.length <= maxLength) return str;
@@ -1215,58 +2586,141 @@ function truncate2(str, maxLength) {
1215
2586
  }
1216
2587
  function handleApiError3(error) {
1217
2588
  if (error instanceof ApiRequestError) {
1218
- console.error(chalk7.red(`Error: ${error.message}`));
2589
+ console.error(chalk9.red(`Error: ${error.message}`));
1219
2590
  } else {
1220
- console.error(chalk7.red("An unexpected error occurred."));
2591
+ console.error(chalk9.red("An unexpected error occurred."));
1221
2592
  }
1222
2593
  process.exit(1);
1223
2594
  }
1224
2595
 
1225
2596
  // src/commands/status.ts
1226
- import chalk8 from "chalk";
1227
- import { access as access4, readFile as readFile3 } from "fs/promises";
1228
- import { join as join4 } 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";
1229
2601
  var CONFIG_FILES = [
1230
2602
  { path: "AGENTS.md", name: "AGENTS.md", platform: "Claude Code, Cursor, AI Agents" },
1231
2603
  { path: "CLAUDE.md", name: "CLAUDE.md", platform: "Claude Code" },
1232
- { path: ".cursorrules", name: ".cursorrules", platform: "Cursor" },
1233
2604
  { path: ".github/copilot-instructions.md", name: "Copilot Instructions", platform: "GitHub Copilot" },
1234
2605
  { path: ".windsurfrules", name: ".windsurfrules", platform: "Windsurf" },
1235
- { 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" }
1236
2615
  ];
1237
2616
  async function statusCommand() {
1238
2617
  const cwd = process.cwd();
1239
2618
  console.log();
1240
- console.log(chalk8.cyan("\u{1F431} AI Config Status"));
1241
- console.log(chalk8.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"));
1242
2666
  console.log();
1243
2667
  let foundAny = false;
1244
2668
  for (const config2 of CONFIG_FILES) {
1245
- const filePath = join4(cwd, config2.path);
2669
+ const filePath = join7(cwd, config2.path);
1246
2670
  try {
1247
- await access4(filePath);
1248
- const content = await readFile3(filePath, "utf-8");
2671
+ await access6(filePath);
2672
+ const content = await readFile6(filePath, "utf-8");
1249
2673
  const lines = content.split("\n").length;
1250
2674
  const size = formatBytes(content.length);
1251
2675
  foundAny = true;
1252
- console.log(` ${chalk8.green("\u2713")} ${chalk8.bold(config2.name)}`);
1253
- console.log(` ${chalk8.gray(`Platform: ${config2.platform}`)}`);
1254
- console.log(` ${chalk8.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)`)}`);
1255
2681
  const preview = getPreview(content);
1256
2682
  if (preview) {
1257
- console.log(` ${chalk8.gray(`Preview: ${preview}`)}`);
2683
+ console.log(` ${chalk10.gray(`Preview: ${preview}`)}`);
1258
2684
  }
1259
2685
  console.log();
1260
2686
  } catch {
1261
2687
  }
1262
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
+ }
1263
2712
  if (!foundAny) {
1264
- console.log(chalk8.yellow(" No AI configuration files found in this directory."));
2713
+ console.log(chalk10.yellow(" No AI configuration files found."));
1265
2714
  console.log();
1266
- console.log(chalk8.gray(" Run 'lynxprompt init' to create a configuration."));
1267
- console.log(chalk8.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"));
1268
2719
  } else {
1269
- console.log(chalk8.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"));
1270
2724
  }
1271
2725
  console.log();
1272
2726
  }
@@ -1274,7 +2728,7 @@ function getPreview(content) {
1274
2728
  const lines = content.split("\n");
1275
2729
  for (const line of lines) {
1276
2730
  const trimmed = line.trim();
1277
- if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !trimmed.startsWith("<!--")) {
2731
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !trimmed.startsWith("<!--") && !trimmed.startsWith("---") && !trimmed.startsWith(">")) {
1278
2732
  return truncate3(trimmed, 50);
1279
2733
  }
1280
2734
  }
@@ -1290,34 +2744,1179 @@ function formatBytes(bytes) {
1290
2744
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1291
2745
  }
1292
2746
 
1293
- // src/index.ts
1294
- var program = new Command();
1295
- program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version("0.1.0");
1296
- program.command("login").description("Authenticate with LynxPrompt (opens browser)").action(loginCommand);
1297
- program.command("logout").description("Log out and remove stored credentials").action(logoutCommand);
1298
- program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
1299
- program.command("init").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(initCommand);
1300
- 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);
1301
- 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);
1302
- program.command("search <query>").description("Search public blueprints").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
1303
- program.command("status").description("Show current AI config status in this directory").action(statusCommand);
1304
- program.addHelpText(
1305
- "beforeAll",
1306
- `
1307
- ${chalk9.cyan("\u{1F431} LynxPrompt CLI")}
1308
- ${chalk9.gray("Generate AI IDE configuration files from your terminal")}
1309
- `
1310
- );
1311
- program.addHelpText(
1312
- "after",
1313
- `
1314
- ${chalk9.gray("Examples:")}
1315
- ${chalk9.cyan("$ lynxprompt init")} ${chalk9.gray("Start interactive wizard")}
1316
- ${chalk9.cyan("$ lynxprompt pull bp_abc123")} ${chalk9.gray("Download a blueprint")}
1317
- ${chalk9.cyan("$ lynxprompt list")} ${chalk9.gray("List your blueprints")}
1318
- ${chalk9.cyan("$ lynxprompt search nextjs")} ${chalk9.gray("Search public blueprints")}
2747
+ // src/commands/sync.ts
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";
2755
+ var CONFIG_FILE = ".lynxprompt/conf.yml";
2756
+ var RULES_DIR = ".lynxprompt/rules";
2757
+ async function syncCommand(options = {}) {
2758
+ console.log();
2759
+ console.log(chalk11.cyan("\u{1F431} LynxPrompt Sync"));
2760
+ console.log();
2761
+ const cwd = process.cwd();
2762
+ const configPath = join8(cwd, CONFIG_FILE);
2763
+ if (!existsSync5(configPath)) {
2764
+ console.log(chalk11.yellow("LynxPrompt is not initialized in this project."));
2765
+ console.log();
2766
+ console.log(chalk11.gray("Run 'lynxp init' first to set up LynxPrompt."));
2767
+ return;
2768
+ }
2769
+ const spinner = ora9("Loading configuration...").start();
2770
+ let config2;
2771
+ try {
2772
+ const configContent = await readFile7(configPath, "utf-8");
2773
+ config2 = yaml3.parse(configContent);
2774
+ spinner.succeed("Configuration loaded");
2775
+ } catch (error) {
2776
+ spinner.fail("Failed to load configuration");
2777
+ console.log(chalk11.red("Could not parse .lynxprompt/conf.yml"));
2778
+ return;
2779
+ }
2780
+ if (!config2.exporters || config2.exporters.length === 0) {
2781
+ console.log(chalk11.yellow("No exporters configured."));
2782
+ console.log();
2783
+ console.log(chalk11.gray("Add exporters to .lynxprompt/conf.yml or run 'lynxp agents enable <agent>'"));
2784
+ return;
2785
+ }
2786
+ const validExporters = [];
2787
+ const invalidExporters = [];
2788
+ for (const exporterId of config2.exporters) {
2789
+ const agent = getAgent(exporterId);
2790
+ if (agent) {
2791
+ validExporters.push(agent);
2792
+ } else {
2793
+ invalidExporters.push(exporterId);
2794
+ }
2795
+ }
2796
+ if (invalidExporters.length > 0) {
2797
+ console.log(chalk11.yellow(`Unknown exporters: ${invalidExporters.join(", ")}`));
2798
+ }
2799
+ if (validExporters.length === 0) {
2800
+ console.log(chalk11.red("No valid exporters configured."));
2801
+ return;
2802
+ }
2803
+ console.log(chalk11.gray(`Exporters: ${validExporters.map((e) => e.name).join(", ")}`));
2804
+ console.log();
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.`));
2809
+ return;
2810
+ }
2811
+ const rulesContent = await loadRules(rulesPath);
2812
+ if (!rulesContent) {
2813
+ console.log(chalk11.yellow("No rule files found in .lynxprompt/rules/"));
2814
+ return;
2815
+ }
2816
+ console.log(chalk11.gray(`Loaded ${rulesContent.fileCount} rule file${rulesContent.fileCount === 1 ? "" : "s"}`));
2817
+ console.log();
2818
+ if (options.dryRun) {
2819
+ console.log(chalk11.cyan("Dry run - no files will be written"));
2820
+ console.log();
2821
+ console.log("Would write:");
2822
+ for (const exporter of validExporters) {
2823
+ console.log(chalk11.gray(` ${exporter.output}`));
2824
+ }
2825
+ console.log();
2826
+ return;
2827
+ }
2828
+ if (!options.force) {
2829
+ const { confirm } = await prompts5({
2830
+ type: "confirm",
2831
+ name: "confirm",
2832
+ message: `Sync to ${validExporters.length} agent${validExporters.length === 1 ? "" : "s"}?`,
2833
+ initial: true
2834
+ });
2835
+ if (!confirm) {
2836
+ console.log(chalk11.gray("Cancelled."));
2837
+ return;
2838
+ }
2839
+ }
2840
+ const result = { written: [], skipped: [], errors: [] };
2841
+ const syncSpinner = ora9("Syncing rules...").start();
2842
+ for (const exporter of validExporters) {
2843
+ try {
2844
+ await syncToAgent(cwd, exporter, rulesContent.combined);
2845
+ result.written.push(exporter.output);
2846
+ } catch (error) {
2847
+ result.errors.push(`${exporter.id}: ${error instanceof Error ? error.message : "Unknown error"}`);
2848
+ }
2849
+ }
2850
+ syncSpinner.stop();
2851
+ if (result.written.length > 0) {
2852
+ console.log(chalk11.green(`\u2713 Synced to ${result.written.length} agent${result.written.length === 1 ? "" : "s"}:`));
2853
+ for (const file of result.written) {
2854
+ console.log(chalk11.gray(` ${file}`));
2855
+ }
2856
+ }
2857
+ if (result.errors.length > 0) {
2858
+ console.log();
2859
+ console.log(chalk11.red("Errors:"));
2860
+ for (const error of result.errors) {
2861
+ console.log(chalk11.red(` ${error}`));
2862
+ }
2863
+ }
2864
+ console.log();
2865
+ }
2866
+ async function loadRules(rulesPath) {
2867
+ const files = [];
2868
+ try {
2869
+ const entries = await readdir4(rulesPath, { withFileTypes: true });
2870
+ for (const entry of entries) {
2871
+ if (!entry.isFile()) continue;
2872
+ if (!entry.name.endsWith(".md")) continue;
2873
+ const filePath = join8(rulesPath, entry.name);
2874
+ const content = await readFile7(filePath, "utf-8");
2875
+ if (content.trim()) {
2876
+ files.push({ name: entry.name, content: content.trim() });
2877
+ }
2878
+ }
2879
+ } catch {
2880
+ return null;
2881
+ }
2882
+ if (files.length === 0) {
2883
+ return null;
2884
+ }
2885
+ const combined = files.map((f) => f.content).join("\n\n---\n\n");
2886
+ return { combined, files, fileCount: files.length };
2887
+ }
2888
+ async function syncToAgent(cwd, agent, content) {
2889
+ const outputPath = join8(cwd, agent.output);
2890
+ if (agent.output.endsWith("/")) {
2891
+ await syncToDirectory(cwd, agent, content);
2892
+ return;
2893
+ }
2894
+ const formatted = formatForAgent(agent, content);
2895
+ const dir = dirname5(outputPath);
2896
+ if (dir !== ".") {
2897
+ await mkdir5(dir, { recursive: true });
2898
+ }
2899
+ await writeFile5(outputPath, formatted, "utf-8");
2900
+ }
2901
+ async function syncToDirectory(cwd, agent, content) {
2902
+ const outputDir = join8(cwd, agent.output);
2903
+ await mkdir5(outputDir, { recursive: true });
2904
+ const extension = agent.format === "mdc" ? ".mdc" : ".md";
2905
+ const filename = `lynxprompt-rules${extension}`;
2906
+ const outputPath = join8(outputDir, filename);
2907
+ const formatted = formatForAgent(agent, content);
2908
+ await writeFile5(outputPath, formatted, "utf-8");
2909
+ }
2910
+ function formatForAgent(agent, content) {
2911
+ switch (agent.format) {
2912
+ case "mdc":
2913
+ return formatAsMdc(content, agent);
2914
+ case "markdown":
2915
+ return formatAsMarkdown(content, agent);
2916
+ case "json":
2917
+ return formatAsJson(content, agent);
2918
+ case "text":
2919
+ return content;
2920
+ default:
2921
+ return content;
2922
+ }
2923
+ }
2924
+ function formatAsMdc(content, agent) {
2925
+ const frontmatter = yaml3.stringify({
2926
+ description: "LynxPrompt rules - AI coding guidelines",
2927
+ globs: ["**/*"],
2928
+ alwaysApply: true
2929
+ });
2930
+ return `---
2931
+ ${frontmatter}---
2932
+
2933
+ ${content}`;
2934
+ }
2935
+ function formatAsMarkdown(content, agent) {
2936
+ const header = `# AI Coding Rules
2937
+
2938
+ > Generated by [LynxPrompt](https://lynxprompt.com)
2939
+
2940
+ `;
2941
+ return header + content;
2942
+ }
2943
+ function formatAsJson(content, agent) {
2944
+ return JSON.stringify(
2945
+ {
2946
+ $schema: "https://lynxprompt.com/schemas/rules.json",
2947
+ version: "1.0",
2948
+ rules: content,
2949
+ meta: {
2950
+ generator: "lynxprompt",
2951
+ url: "https://lynxprompt.com"
2952
+ }
2953
+ },
2954
+ null,
2955
+ 2
2956
+ );
2957
+ }
2958
+
2959
+ // src/commands/agents.ts
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";
2966
+ var CONFIG_FILE2 = ".lynxprompt/conf.yml";
2967
+ async function agentsCommand(action, agentId, options = {}) {
2968
+ console.log();
2969
+ switch (action) {
2970
+ case "enable":
2971
+ await enableAgent(agentId, options);
2972
+ break;
2973
+ case "disable":
2974
+ await disableAgent(agentId);
2975
+ break;
2976
+ case "detect":
2977
+ await detectAgentsInProject();
2978
+ break;
2979
+ case "list":
2980
+ default:
2981
+ await listAgents();
2982
+ break;
2983
+ }
2984
+ }
2985
+ async function listAgents() {
2986
+ console.log(chalk12.cyan("\u{1F431} LynxPrompt Agents"));
2987
+ console.log();
2988
+ const config2 = await loadConfig();
2989
+ const enabledSet = new Set(config2?.exporters ?? []);
2990
+ const detection = detectAgents();
2991
+ if (enabledSet.size > 0) {
2992
+ console.log(chalk12.green("Enabled:"));
2993
+ for (const id of enabledSet) {
2994
+ const agent = getAgent(id);
2995
+ const detected = detection.detected.find((d) => d.agent.id === id);
2996
+ const status = detected ? chalk12.gray("(detected)") : "";
2997
+ console.log(` ${chalk12.green("\u2713")} ${agent?.name ?? id} ${status}`);
2998
+ }
2999
+ console.log();
3000
+ }
3001
+ const detectedNotEnabled = detection.detected.filter(
3002
+ (d) => !enabledSet.has(d.agent.id)
3003
+ );
3004
+ if (detectedNotEnabled.length > 0) {
3005
+ console.log(chalk12.yellow("Detected (not enabled):"));
3006
+ for (const detected of detectedNotEnabled) {
3007
+ const rules = detected.ruleCount > 0 ? chalk12.gray(` (${detected.ruleCount} rules)`) : "";
3008
+ console.log(` ${chalk12.yellow("\u25CB")} ${detected.agent.name}${rules}`);
3009
+ }
3010
+ console.log();
3011
+ }
3012
+ const popular = getPopularAgents().filter(
3013
+ (a) => !enabledSet.has(a.id) && !detectedNotEnabled.some((d) => d.agent.id === a.id)
3014
+ );
3015
+ if (popular.length > 0) {
3016
+ console.log(chalk12.gray("Popular (available):"));
3017
+ for (const agent of popular) {
3018
+ console.log(` ${chalk12.gray("-")} ${agent.name} - ${agent.description}`);
3019
+ }
3020
+ console.log();
3021
+ }
3022
+ console.log(chalk12.gray(`Total: ${AGENTS.length} agents supported`));
3023
+ console.log();
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"));
3028
+ console.log();
3029
+ }
3030
+ async function enableAgent(agentId, options = {}) {
3031
+ const cwd = process.cwd();
3032
+ const configPath = join9(cwd, CONFIG_FILE2);
3033
+ if (!existsSync6(configPath)) {
3034
+ console.log(chalk12.yellow("LynxPrompt not initialized. Run 'lynxp init' first."));
3035
+ return;
3036
+ }
3037
+ let config2 = await loadConfig();
3038
+ if (!config2) {
3039
+ console.log(chalk12.red("Could not load configuration."));
3040
+ return;
3041
+ }
3042
+ if (!agentId || options.interactive) {
3043
+ const enabledSet = new Set(config2.exporters ?? []);
3044
+ const choices = AGENTS.map((agent2) => ({
3045
+ title: `${agent2.name} - ${agent2.description}`,
3046
+ value: agent2.id,
3047
+ selected: enabledSet.has(agent2.id)
3048
+ }));
3049
+ const { selected } = await prompts6({
3050
+ type: "multiselect",
3051
+ name: "selected",
3052
+ message: "Select agents to enable:",
3053
+ choices,
3054
+ hint: "- Space to select, Enter to confirm"
3055
+ });
3056
+ if (!selected || selected.length === 0) {
3057
+ console.log(chalk12.yellow("No agents selected."));
3058
+ return;
3059
+ }
3060
+ config2.exporters = selected;
3061
+ await saveConfig(config2);
3062
+ console.log(chalk12.green(`\u2713 Enabled ${selected.length} agent${selected.length === 1 ? "" : "s"}`));
3063
+ console.log();
3064
+ console.log(chalk12.gray("Run 'lynxp sync' to sync your rules."));
3065
+ return;
3066
+ }
3067
+ const agent = getAgent(agentId);
3068
+ if (!agent) {
3069
+ const similar = AGENTS.filter(
3070
+ (a) => a.id.includes(agentId.toLowerCase()) || a.name.toLowerCase().includes(agentId.toLowerCase())
3071
+ );
3072
+ console.log(chalk12.red(`Unknown agent: ${agentId}`));
3073
+ if (similar.length > 0) {
3074
+ console.log();
3075
+ console.log(chalk12.gray("Did you mean:"));
3076
+ for (const a of similar.slice(0, 5)) {
3077
+ console.log(chalk12.gray(` ${a.id} - ${a.name}`));
3078
+ }
3079
+ }
3080
+ return;
3081
+ }
3082
+ if (!config2.exporters) {
3083
+ config2.exporters = [];
3084
+ }
3085
+ if (config2.exporters.includes(agent.id)) {
3086
+ console.log(chalk12.yellow(`${agent.name} is already enabled.`));
3087
+ return;
3088
+ }
3089
+ config2.exporters.push(agent.id);
3090
+ await saveConfig(config2);
3091
+ console.log(chalk12.green(`\u2713 Enabled ${agent.name}`));
3092
+ console.log();
3093
+ console.log(chalk12.gray(`Output: ${agent.output}`));
3094
+ console.log(chalk12.gray("Run 'lynxp sync' to sync your rules."));
3095
+ }
3096
+ async function disableAgent(agentId) {
3097
+ if (!agentId) {
3098
+ console.log(chalk12.yellow("Usage: lynxp agents disable <agent>"));
3099
+ return;
3100
+ }
3101
+ const cwd = process.cwd();
3102
+ const configPath = join9(cwd, CONFIG_FILE2);
3103
+ if (!existsSync6(configPath)) {
3104
+ console.log(chalk12.yellow("LynxPrompt not initialized. Run 'lynxp init' first."));
3105
+ return;
3106
+ }
3107
+ const config2 = await loadConfig();
3108
+ if (!config2) {
3109
+ console.log(chalk12.red("Could not load configuration."));
3110
+ return;
3111
+ }
3112
+ if (!config2.exporters || !config2.exporters.includes(agentId)) {
3113
+ const agent2 = getAgent(agentId);
3114
+ console.log(chalk12.yellow(`${agent2?.name ?? agentId} is not enabled.`));
3115
+ return;
3116
+ }
3117
+ if (config2.exporters.length === 1) {
3118
+ console.log(chalk12.yellow("Cannot disable the last agent. At least one must be enabled."));
3119
+ return;
3120
+ }
3121
+ config2.exporters = config2.exporters.filter((e) => e !== agentId);
3122
+ await saveConfig(config2);
3123
+ const agent = getAgent(agentId);
3124
+ console.log(chalk12.green(`\u2713 Disabled ${agent?.name ?? agentId}`));
3125
+ }
3126
+ async function detectAgentsInProject() {
3127
+ console.log(chalk12.cyan("\u{1F50D} Detecting AI agents..."));
3128
+ console.log();
3129
+ const detection = detectAgents();
3130
+ console.log(formatDetectionResults(detection));
3131
+ console.log();
3132
+ if (detection.detected.length === 0) {
3133
+ return;
3134
+ }
3135
+ const config2 = await loadConfig();
3136
+ const enabledSet = new Set(config2?.exporters ?? []);
3137
+ const newAgents = detection.detected.filter((d) => !enabledSet.has(d.agent.id));
3138
+ if (newAgents.length === 0) {
3139
+ console.log(chalk12.gray("All detected agents are already enabled."));
3140
+ return;
3141
+ }
3142
+ const { enable } = await prompts6({
3143
+ type: "confirm",
3144
+ name: "enable",
3145
+ message: `Enable ${newAgents.length} detected agent${newAgents.length === 1 ? "" : "s"}?`,
3146
+ initial: true
3147
+ });
3148
+ if (enable && config2) {
3149
+ config2.exporters = [
3150
+ ...config2.exporters ?? [],
3151
+ ...newAgents.map((d) => d.agent.id)
3152
+ ];
3153
+ await saveConfig(config2);
3154
+ console.log(chalk12.green(`\u2713 Enabled ${newAgents.length} agent${newAgents.length === 1 ? "" : "s"}`));
3155
+ }
3156
+ }
3157
+ async function loadConfig() {
3158
+ const cwd = process.cwd();
3159
+ const configPath = join9(cwd, CONFIG_FILE2);
3160
+ if (!existsSync6(configPath)) {
3161
+ return null;
3162
+ }
3163
+ try {
3164
+ const content = await readFile8(configPath, "utf-8");
3165
+ return yaml4.parse(content);
3166
+ } catch {
3167
+ return null;
3168
+ }
3169
+ }
3170
+ async function saveConfig(config2) {
3171
+ const cwd = process.cwd();
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();
3869
+ }
3870
+
3871
+ // src/index.ts
3872
+ var program = new Command();
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);
3887
+ program.command("login").description("Authenticate with LynxPrompt (opens browser)").action(loginCommand);
3888
+ program.command("logout").description("Log out and remove stored credentials").action(logoutCommand);
3889
+ program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
3890
+ program.addHelpText(
3891
+ "beforeAll",
3892
+ `
3893
+ ${chalk16.cyan("\u{1F431} LynxPrompt CLI")} ${chalk16.gray("(also available as: lynxp)")}
3894
+ ${chalk16.gray("Generate AI IDE configuration files from your terminal")}
3895
+ `
3896
+ );
3897
+ program.addHelpText(
3898
+ "after",
3899
+ `
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)")}
1319
3918
 
1320
- ${chalk9.gray("Documentation: https://lynxprompt.com/docs/cli")}
3919
+ ${chalk16.gray("Docs: https://lynxprompt.com/docs/cli")}
1321
3920
  `
1322
3921
  );
1323
3922
  program.parse();