lynxprompt 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -92
- package/dist/index.js +1503 -260
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk15 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/commands/login.ts
|
|
8
8
|
import chalk from "chalk";
|
|
@@ -408,67 +408,310 @@ function handleApiError(error) {
|
|
|
408
408
|
import chalk5 from "chalk";
|
|
409
409
|
import ora4 from "ora";
|
|
410
410
|
import prompts from "prompts";
|
|
411
|
-
import { writeFile
|
|
411
|
+
import { writeFile as writeFile2, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
412
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
413
|
+
import { existsSync } from "fs";
|
|
414
|
+
|
|
415
|
+
// src/utils/blueprint-tracker.ts
|
|
416
|
+
import { readFile, writeFile, mkdir, access } from "fs/promises";
|
|
412
417
|
import { join, dirname } from "path";
|
|
418
|
+
import { createHash } from "crypto";
|
|
419
|
+
import * as yaml from "yaml";
|
|
420
|
+
var BLUEPRINTS_FILE = ".lynxprompt/blueprints.yml";
|
|
421
|
+
function calculateChecksum(content) {
|
|
422
|
+
return createHash("sha256").update(content).digest("hex").substring(0, 16);
|
|
423
|
+
}
|
|
424
|
+
async function loadBlueprints(cwd) {
|
|
425
|
+
const filePath = join(cwd, BLUEPRINTS_FILE);
|
|
426
|
+
try {
|
|
427
|
+
await access(filePath);
|
|
428
|
+
const content = await readFile(filePath, "utf-8");
|
|
429
|
+
const config2 = yaml.parse(content);
|
|
430
|
+
return config2 || { version: "1", blueprints: [] };
|
|
431
|
+
} catch {
|
|
432
|
+
return { version: "1", blueprints: [] };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async function saveBlueprints(cwd, config2) {
|
|
436
|
+
const filePath = join(cwd, BLUEPRINTS_FILE);
|
|
437
|
+
const dir = dirname(filePath);
|
|
438
|
+
await mkdir(dir, { recursive: true });
|
|
439
|
+
const content = yaml.stringify(config2, {
|
|
440
|
+
lineWidth: 0,
|
|
441
|
+
singleQuote: false
|
|
442
|
+
});
|
|
443
|
+
await writeFile(filePath, content, "utf-8");
|
|
444
|
+
}
|
|
445
|
+
async function trackBlueprint(cwd, blueprint) {
|
|
446
|
+
const config2 = await loadBlueprints(cwd);
|
|
447
|
+
config2.blueprints = config2.blueprints.filter((b) => b.file !== blueprint.file);
|
|
448
|
+
const editable = blueprint.source !== "marketplace";
|
|
449
|
+
const canPull = true;
|
|
450
|
+
config2.blueprints.push({
|
|
451
|
+
id: blueprint.id,
|
|
452
|
+
source: blueprint.source,
|
|
453
|
+
file: blueprint.file,
|
|
454
|
+
name: blueprint.name,
|
|
455
|
+
pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
456
|
+
checksum: calculateChecksum(blueprint.content),
|
|
457
|
+
version: blueprint.version,
|
|
458
|
+
editable,
|
|
459
|
+
canPull
|
|
460
|
+
});
|
|
461
|
+
await saveBlueprints(cwd, config2);
|
|
462
|
+
}
|
|
463
|
+
async function findBlueprintByFile(cwd, file) {
|
|
464
|
+
const config2 = await loadBlueprints(cwd);
|
|
465
|
+
return config2.blueprints.find((b) => b.file === file) || null;
|
|
466
|
+
}
|
|
467
|
+
async function hasLocalChanges(cwd, tracked) {
|
|
468
|
+
try {
|
|
469
|
+
const filePath = join(cwd, tracked.file);
|
|
470
|
+
const content = await readFile(filePath, "utf-8");
|
|
471
|
+
const currentChecksum = calculateChecksum(content);
|
|
472
|
+
return currentChecksum !== tracked.checksum;
|
|
473
|
+
} catch {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async function untrackBlueprint(cwd, file) {
|
|
478
|
+
const config2 = await loadBlueprints(cwd);
|
|
479
|
+
const initialCount = config2.blueprints.length;
|
|
480
|
+
config2.blueprints = config2.blueprints.filter((b) => b.file !== file);
|
|
481
|
+
if (config2.blueprints.length < initialCount) {
|
|
482
|
+
await saveBlueprints(cwd, config2);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
async function linkBlueprint(cwd, file, blueprintId, blueprintName, source) {
|
|
488
|
+
try {
|
|
489
|
+
const filePath = join(cwd, file);
|
|
490
|
+
const content = await readFile(filePath, "utf-8");
|
|
491
|
+
await trackBlueprint(cwd, {
|
|
492
|
+
id: blueprintId,
|
|
493
|
+
name: blueprintName,
|
|
494
|
+
file,
|
|
495
|
+
content,
|
|
496
|
+
source
|
|
497
|
+
});
|
|
498
|
+
} catch (error) {
|
|
499
|
+
throw new Error(`Could not read file ${file} to link`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async function checkSyncStatus(cwd) {
|
|
503
|
+
const config2 = await loadBlueprints(cwd);
|
|
504
|
+
const results = [];
|
|
505
|
+
for (const blueprint of config2.blueprints) {
|
|
506
|
+
const filePath = join(cwd, blueprint.file);
|
|
507
|
+
let fileExists2 = false;
|
|
508
|
+
let localModified = false;
|
|
509
|
+
try {
|
|
510
|
+
await access(filePath);
|
|
511
|
+
fileExists2 = true;
|
|
512
|
+
localModified = await hasLocalChanges(cwd, blueprint);
|
|
513
|
+
} catch {
|
|
514
|
+
fileExists2 = false;
|
|
515
|
+
}
|
|
516
|
+
results.push({ blueprint, localModified, fileExists: fileExists2 });
|
|
517
|
+
}
|
|
518
|
+
return results;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/commands/pull.ts
|
|
413
522
|
var TYPE_TO_FILENAME = {
|
|
414
523
|
AGENTS_MD: "AGENTS.md",
|
|
415
|
-
CURSOR_RULES: ".
|
|
524
|
+
CURSOR_RULES: ".cursor/rules/project.mdc",
|
|
416
525
|
COPILOT_INSTRUCTIONS: ".github/copilot-instructions.md",
|
|
417
526
|
WINDSURF_RULES: ".windsurfrules",
|
|
418
527
|
ZED_INSTRUCTIONS: ".zed/instructions.md",
|
|
419
528
|
CLAUDE_MD: "CLAUDE.md",
|
|
420
529
|
GENERIC: "ai-config.md"
|
|
421
530
|
};
|
|
531
|
+
function getSourceFromVisibility(visibility) {
|
|
532
|
+
switch (visibility) {
|
|
533
|
+
case "PUBLIC":
|
|
534
|
+
return "marketplace";
|
|
535
|
+
case "TEAM":
|
|
536
|
+
return "team";
|
|
537
|
+
case "PRIVATE":
|
|
538
|
+
return "private";
|
|
539
|
+
default:
|
|
540
|
+
return "marketplace";
|
|
541
|
+
}
|
|
542
|
+
}
|
|
422
543
|
async function pullCommand(id, options) {
|
|
423
544
|
if (!isAuthenticated()) {
|
|
424
545
|
console.log(
|
|
425
|
-
chalk5.yellow("Not logged in. Run '
|
|
546
|
+
chalk5.yellow("Not logged in. Run 'lynxp login' to authenticate.")
|
|
426
547
|
);
|
|
427
548
|
process.exit(1);
|
|
428
549
|
}
|
|
550
|
+
const cwd = process.cwd();
|
|
429
551
|
const spinner = ora4(`Fetching blueprint ${chalk5.cyan(id)}...`).start();
|
|
430
552
|
try {
|
|
431
553
|
const { blueprint } = await api.getBlueprint(id);
|
|
432
554
|
spinner.stop();
|
|
433
555
|
if (!blueprint.content) {
|
|
434
|
-
console.error(chalk5.red("Blueprint has no content."));
|
|
556
|
+
console.error(chalk5.red("\u2717 Blueprint has no content."));
|
|
435
557
|
process.exit(1);
|
|
436
558
|
}
|
|
559
|
+
const source = getSourceFromVisibility(blueprint.visibility);
|
|
560
|
+
const isMarketplace = source === "marketplace";
|
|
561
|
+
console.log();
|
|
562
|
+
console.log(chalk5.cyan(`\u{1F431} Blueprint: ${chalk5.bold(blueprint.name)}`));
|
|
563
|
+
if (blueprint.description) {
|
|
564
|
+
console.log(chalk5.gray(` ${blueprint.description}`));
|
|
565
|
+
}
|
|
566
|
+
console.log(chalk5.gray(` Type: ${blueprint.type} \u2022 Tier: ${blueprint.tier} \u2022 Visibility: ${blueprint.visibility}`));
|
|
567
|
+
if (isMarketplace) {
|
|
568
|
+
console.log(chalk5.yellow(` \u{1F4E6} Marketplace blueprint (read-only - changes won't sync back)`));
|
|
569
|
+
} else if (source === "team") {
|
|
570
|
+
console.log(chalk5.blue(` \u{1F465} Team blueprint (can sync changes)`));
|
|
571
|
+
} else if (source === "private") {
|
|
572
|
+
console.log(chalk5.green(` \u{1F512} Private blueprint (can sync changes)`));
|
|
573
|
+
}
|
|
574
|
+
console.log();
|
|
575
|
+
if (options.preview) {
|
|
576
|
+
console.log(chalk5.gray("\u2500".repeat(60)));
|
|
577
|
+
console.log();
|
|
578
|
+
const lines = blueprint.content.split("\n");
|
|
579
|
+
const previewLines = lines.slice(0, 50);
|
|
580
|
+
for (const line of previewLines) {
|
|
581
|
+
if (line.startsWith("#")) {
|
|
582
|
+
console.log(chalk5.cyan(line));
|
|
583
|
+
} else if (line.startsWith(">")) {
|
|
584
|
+
console.log(chalk5.gray(line));
|
|
585
|
+
} else if (line.startsWith("- ") || line.startsWith("* ")) {
|
|
586
|
+
console.log(chalk5.white(line));
|
|
587
|
+
} else if (line.startsWith("```")) {
|
|
588
|
+
console.log(chalk5.yellow(line));
|
|
589
|
+
} else {
|
|
590
|
+
console.log(line);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (lines.length > 50) {
|
|
594
|
+
console.log();
|
|
595
|
+
console.log(chalk5.gray(`... ${lines.length - 50} more lines`));
|
|
596
|
+
}
|
|
597
|
+
console.log();
|
|
598
|
+
console.log(chalk5.gray("\u2500".repeat(60)));
|
|
599
|
+
console.log();
|
|
600
|
+
console.log(chalk5.gray("Run without --preview to download this blueprint."));
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
437
603
|
const filename = TYPE_TO_FILENAME[blueprint.type] || "ai-config.md";
|
|
438
|
-
const outputPath =
|
|
439
|
-
let
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
604
|
+
const outputPath = join2(options.output, filename);
|
|
605
|
+
let localContent = null;
|
|
606
|
+
if (existsSync(outputPath)) {
|
|
607
|
+
try {
|
|
608
|
+
localContent = await readFile2(outputPath, "utf-8");
|
|
609
|
+
} catch {
|
|
610
|
+
}
|
|
444
611
|
}
|
|
445
|
-
|
|
612
|
+
const existingTracked = await findBlueprintByFile(cwd, filename);
|
|
613
|
+
if (existingTracked && existingTracked.id !== id) {
|
|
614
|
+
console.log(chalk5.yellow(`\u26A0 This file is already linked to a different blueprint: ${existingTracked.id}`));
|
|
615
|
+
if (!options.yes) {
|
|
616
|
+
const { proceed } = await prompts({
|
|
617
|
+
type: "confirm",
|
|
618
|
+
name: "proceed",
|
|
619
|
+
message: "Replace the link with the new blueprint?",
|
|
620
|
+
initial: false
|
|
621
|
+
});
|
|
622
|
+
if (!proceed) {
|
|
623
|
+
console.log(chalk5.gray("Cancelled."));
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (localContent && !options.yes) {
|
|
629
|
+
const localLines = localContent.split("\n").length;
|
|
630
|
+
const remoteLines = blueprint.content.split("\n").length;
|
|
631
|
+
console.log(chalk5.yellow(`\u26A0 File exists: ${outputPath}`));
|
|
632
|
+
console.log(chalk5.gray(` Local: ${localLines} lines`));
|
|
633
|
+
console.log(chalk5.gray(` Remote: ${remoteLines} lines`));
|
|
634
|
+
console.log();
|
|
446
635
|
const response = await prompts({
|
|
447
|
-
type: "
|
|
448
|
-
name: "
|
|
449
|
-
message:
|
|
450
|
-
|
|
636
|
+
type: "select",
|
|
637
|
+
name: "action",
|
|
638
|
+
message: "What would you like to do?",
|
|
639
|
+
choices: [
|
|
640
|
+
{ title: "Overwrite with remote version", value: "overwrite" },
|
|
641
|
+
{ title: "Preview remote content first", value: "preview" },
|
|
642
|
+
{ title: "Cancel", value: "cancel" }
|
|
643
|
+
]
|
|
451
644
|
});
|
|
452
|
-
if (!response.
|
|
453
|
-
console.log(chalk5.
|
|
645
|
+
if (response.action === "cancel" || !response.action) {
|
|
646
|
+
console.log(chalk5.gray("Cancelled."));
|
|
454
647
|
return;
|
|
455
648
|
}
|
|
649
|
+
if (response.action === "preview") {
|
|
650
|
+
console.log();
|
|
651
|
+
console.log(chalk5.gray("\u2500".repeat(60)));
|
|
652
|
+
console.log();
|
|
653
|
+
const lines = blueprint.content.split("\n").slice(0, 30);
|
|
654
|
+
for (const line of lines) {
|
|
655
|
+
if (line.startsWith("#")) {
|
|
656
|
+
console.log(chalk5.cyan(line));
|
|
657
|
+
} else {
|
|
658
|
+
console.log(line);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (blueprint.content.split("\n").length > 30) {
|
|
662
|
+
console.log(chalk5.gray(`... ${blueprint.content.split("\n").length - 30} more lines`));
|
|
663
|
+
}
|
|
664
|
+
console.log();
|
|
665
|
+
console.log(chalk5.gray("\u2500".repeat(60)));
|
|
666
|
+
console.log();
|
|
667
|
+
const confirmResponse = await prompts({
|
|
668
|
+
type: "confirm",
|
|
669
|
+
name: "confirm",
|
|
670
|
+
message: "Download and overwrite local file?",
|
|
671
|
+
initial: false
|
|
672
|
+
});
|
|
673
|
+
if (!confirmResponse.confirm) {
|
|
674
|
+
console.log(chalk5.gray("Cancelled."));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
456
678
|
}
|
|
457
|
-
const dir =
|
|
679
|
+
const dir = dirname2(outputPath);
|
|
458
680
|
if (dir !== ".") {
|
|
459
|
-
await
|
|
681
|
+
await mkdir2(dir, { recursive: true });
|
|
682
|
+
}
|
|
683
|
+
await writeFile2(outputPath, blueprint.content, "utf-8");
|
|
684
|
+
if (options.track !== false) {
|
|
685
|
+
await trackBlueprint(cwd, {
|
|
686
|
+
id: blueprint.id,
|
|
687
|
+
name: blueprint.name,
|
|
688
|
+
file: filename,
|
|
689
|
+
content: blueprint.content,
|
|
690
|
+
source
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
console.log(chalk5.green(`\u2705 Downloaded: ${chalk5.bold(outputPath)}`));
|
|
694
|
+
if (options.track !== false) {
|
|
695
|
+
console.log(chalk5.gray(` Linked to: ${blueprint.id}`));
|
|
696
|
+
if (isMarketplace) {
|
|
697
|
+
console.log(chalk5.gray(` Updates: Run 'lynxp pull ${id}' to sync updates`));
|
|
698
|
+
} else {
|
|
699
|
+
console.log(chalk5.gray(` Sync: Run 'lynxp push ${filename}' to push changes`));
|
|
700
|
+
}
|
|
460
701
|
}
|
|
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
702
|
console.log();
|
|
468
703
|
const editorHint = getEditorHint(blueprint.type);
|
|
469
704
|
if (editorHint) {
|
|
470
705
|
console.log(chalk5.gray(`\u{1F4A1} ${editorHint}`));
|
|
706
|
+
console.log();
|
|
707
|
+
}
|
|
708
|
+
console.log(chalk5.gray("Tips:"));
|
|
709
|
+
console.log(chalk5.gray(` \u2022 Run 'lynxp status' to see tracked blueprints`));
|
|
710
|
+
console.log(chalk5.gray(` \u2022 Run 'lynxp diff ${id}' to see changes between local and remote`));
|
|
711
|
+
if (isMarketplace) {
|
|
712
|
+
console.log(chalk5.gray(` \u2022 Run 'lynxp unlink ${filename}' to disconnect and make editable`));
|
|
471
713
|
}
|
|
714
|
+
console.log();
|
|
472
715
|
} catch (error) {
|
|
473
716
|
spinner.fail("Failed to pull blueprint");
|
|
474
717
|
handleApiError2(error);
|
|
@@ -496,29 +739,29 @@ function handleApiError2(error) {
|
|
|
496
739
|
if (error instanceof ApiRequestError) {
|
|
497
740
|
if (error.statusCode === 401) {
|
|
498
741
|
console.error(
|
|
499
|
-
chalk5.red("Your session has expired. Please run '
|
|
742
|
+
chalk5.red("\u2717 Your session has expired. Please run 'lynxp login' again.")
|
|
500
743
|
);
|
|
501
744
|
} else if (error.statusCode === 403) {
|
|
502
745
|
console.error(
|
|
503
|
-
chalk5.red("You don't have access to this blueprint.")
|
|
746
|
+
chalk5.red("\u2717 You don't have access to this blueprint.")
|
|
504
747
|
);
|
|
505
748
|
console.error(
|
|
506
749
|
chalk5.gray(
|
|
507
|
-
"This might be a private blueprint or require a higher subscription tier."
|
|
750
|
+
" This might be a private blueprint or require a higher subscription tier."
|
|
508
751
|
)
|
|
509
752
|
);
|
|
510
753
|
} else if (error.statusCode === 404) {
|
|
511
|
-
console.error(chalk5.red("Blueprint not found."));
|
|
754
|
+
console.error(chalk5.red("\u2717 Blueprint not found."));
|
|
512
755
|
console.error(
|
|
513
756
|
chalk5.gray(
|
|
514
|
-
"Make sure you have the correct blueprint ID. Use '
|
|
757
|
+
" Make sure you have the correct blueprint ID. Use 'lynxp list' to see your blueprints."
|
|
515
758
|
)
|
|
516
759
|
);
|
|
517
760
|
} else {
|
|
518
|
-
console.error(chalk5.red(
|
|
761
|
+
console.error(chalk5.red(`\u2717 Error: ${error.message}`));
|
|
519
762
|
}
|
|
520
763
|
} else {
|
|
521
|
-
console.error(chalk5.red("An unexpected error occurred."));
|
|
764
|
+
console.error(chalk5.red("\u2717 An unexpected error occurred."));
|
|
522
765
|
}
|
|
523
766
|
process.exit(1);
|
|
524
767
|
}
|
|
@@ -527,14 +770,14 @@ function handleApiError2(error) {
|
|
|
527
770
|
import chalk6 from "chalk";
|
|
528
771
|
import prompts2 from "prompts";
|
|
529
772
|
import ora5 from "ora";
|
|
530
|
-
import { writeFile as
|
|
531
|
-
import { join as
|
|
532
|
-
import { existsSync as
|
|
533
|
-
import * as
|
|
773
|
+
import { writeFile as writeFile3, mkdir as mkdir3, readFile as readFile4 } from "fs/promises";
|
|
774
|
+
import { join as join5, dirname as dirname3, basename } from "path";
|
|
775
|
+
import { existsSync as existsSync3 } from "fs";
|
|
776
|
+
import * as yaml2 from "yaml";
|
|
534
777
|
|
|
535
778
|
// src/utils/agent-detector.ts
|
|
536
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
537
|
-
import { join as
|
|
779
|
+
import { existsSync as existsSync2, readdirSync, readFileSync, statSync } from "fs";
|
|
780
|
+
import { join as join3 } from "path";
|
|
538
781
|
|
|
539
782
|
// src/utils/agents.ts
|
|
540
783
|
var AGENTS = [
|
|
@@ -832,8 +1075,8 @@ function detectAgent(cwd, agent) {
|
|
|
832
1075
|
let hasContent = false;
|
|
833
1076
|
let ruleCount = 0;
|
|
834
1077
|
for (const pattern of agent.patterns) {
|
|
835
|
-
const fullPath =
|
|
836
|
-
if (!
|
|
1078
|
+
const fullPath = join3(cwd, pattern);
|
|
1079
|
+
if (!existsSync2(fullPath)) {
|
|
837
1080
|
continue;
|
|
838
1081
|
}
|
|
839
1082
|
const stats = statSync(fullPath);
|
|
@@ -867,7 +1110,7 @@ function scanDirectory(dirPath, format) {
|
|
|
867
1110
|
const name = entry.name.toLowerCase();
|
|
868
1111
|
const isRuleFile = format === "mdc" && name.endsWith(".mdc") || format === "markdown" && name.endsWith(".md") || format === "json" && name.endsWith(".json") || format === "yaml" && (name.endsWith(".yml") || name.endsWith(".yaml"));
|
|
869
1112
|
if (!isRuleFile) continue;
|
|
870
|
-
const filePath =
|
|
1113
|
+
const filePath = join3(dirPath, entry.name);
|
|
871
1114
|
const content = safeReadFile(filePath);
|
|
872
1115
|
if (content && content.trim().length > 0) {
|
|
873
1116
|
results.push({
|
|
@@ -915,130 +1158,230 @@ function formatDetectionResults(result) {
|
|
|
915
1158
|
}
|
|
916
1159
|
|
|
917
1160
|
// src/utils/detect.ts
|
|
918
|
-
import { readFile, access as
|
|
919
|
-
import { join as
|
|
1161
|
+
import { readFile as readFile3, access as access3 } from "fs/promises";
|
|
1162
|
+
import { join as join4 } from "path";
|
|
1163
|
+
var JS_FRAMEWORK_PATTERNS = {
|
|
1164
|
+
nextjs: ["next"],
|
|
1165
|
+
react: ["react", "react-dom"],
|
|
1166
|
+
vue: ["vue"],
|
|
1167
|
+
angular: ["@angular/core"],
|
|
1168
|
+
svelte: ["svelte", "@sveltejs/kit"],
|
|
1169
|
+
solid: ["solid-js"],
|
|
1170
|
+
remix: ["@remix-run/react"],
|
|
1171
|
+
astro: ["astro"],
|
|
1172
|
+
nuxt: ["nuxt"],
|
|
1173
|
+
gatsby: ["gatsby"]
|
|
1174
|
+
};
|
|
1175
|
+
var JS_TOOL_PATTERNS = {
|
|
1176
|
+
typescript: ["typescript"],
|
|
1177
|
+
tailwind: ["tailwindcss"],
|
|
1178
|
+
prisma: ["prisma", "@prisma/client"],
|
|
1179
|
+
drizzle: ["drizzle-orm"],
|
|
1180
|
+
express: ["express"],
|
|
1181
|
+
fastify: ["fastify"],
|
|
1182
|
+
hono: ["hono"],
|
|
1183
|
+
elysia: ["elysia"],
|
|
1184
|
+
trpc: ["@trpc/server"],
|
|
1185
|
+
graphql: ["graphql", "@apollo/server"],
|
|
1186
|
+
jest: ["jest"],
|
|
1187
|
+
vitest: ["vitest"],
|
|
1188
|
+
playwright: ["@playwright/test"],
|
|
1189
|
+
cypress: ["cypress"],
|
|
1190
|
+
eslint: ["eslint"],
|
|
1191
|
+
biome: ["@biomejs/biome"],
|
|
1192
|
+
prettier: ["prettier"],
|
|
1193
|
+
vite: ["vite"],
|
|
1194
|
+
webpack: ["webpack"],
|
|
1195
|
+
turbo: ["turbo"]
|
|
1196
|
+
};
|
|
920
1197
|
async function detectProject(cwd) {
|
|
921
1198
|
const detected = {
|
|
922
1199
|
name: null,
|
|
923
1200
|
stack: [],
|
|
924
1201
|
commands: {},
|
|
925
|
-
packageManager: null
|
|
1202
|
+
packageManager: null,
|
|
1203
|
+
type: "unknown"
|
|
926
1204
|
};
|
|
927
|
-
const packageJsonPath =
|
|
1205
|
+
const packageJsonPath = join4(cwd, "package.json");
|
|
928
1206
|
if (await fileExists(packageJsonPath)) {
|
|
929
1207
|
try {
|
|
930
|
-
const content = await
|
|
1208
|
+
const content = await readFile3(packageJsonPath, "utf-8");
|
|
931
1209
|
const pkg = JSON.parse(content);
|
|
932
1210
|
detected.name = pkg.name || null;
|
|
1211
|
+
detected.description = pkg.description;
|
|
1212
|
+
if (pkg.workspaces || await fileExists(join4(cwd, "pnpm-workspace.yaml"))) {
|
|
1213
|
+
detected.type = "monorepo";
|
|
1214
|
+
} else if (pkg.main || pkg.exports) {
|
|
1215
|
+
detected.type = "library";
|
|
1216
|
+
} else {
|
|
1217
|
+
detected.type = "application";
|
|
1218
|
+
}
|
|
933
1219
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1220
|
+
for (const [framework, deps] of Object.entries(JS_FRAMEWORK_PATTERNS)) {
|
|
1221
|
+
if (deps.some((dep) => allDeps[dep])) {
|
|
1222
|
+
detected.stack.push(framework);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
for (const [tool, deps] of Object.entries(JS_TOOL_PATTERNS)) {
|
|
1226
|
+
if (deps.some((dep) => allDeps[dep])) {
|
|
1227
|
+
detected.stack.push(tool);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
if (detected.stack.length === 0 || detected.stack.length === 1 && detected.stack[0] === "typescript") {
|
|
1231
|
+
detected.stack.unshift("javascript");
|
|
1232
|
+
}
|
|
944
1233
|
if (pkg.scripts) {
|
|
945
1234
|
detected.commands.build = pkg.scripts.build;
|
|
946
1235
|
detected.commands.test = pkg.scripts.test;
|
|
947
|
-
detected.commands.lint = pkg.scripts.lint;
|
|
948
|
-
detected.commands.dev = pkg.scripts.dev || pkg.scripts.start;
|
|
1236
|
+
detected.commands.lint = pkg.scripts.lint || pkg.scripts["lint:check"];
|
|
1237
|
+
detected.commands.dev = pkg.scripts.dev || pkg.scripts.start || pkg.scripts.serve;
|
|
1238
|
+
detected.commands.format = pkg.scripts.format || pkg.scripts.prettier;
|
|
949
1239
|
}
|
|
950
|
-
if (await fileExists(
|
|
1240
|
+
if (await fileExists(join4(cwd, "pnpm-lock.yaml"))) {
|
|
951
1241
|
detected.packageManager = "pnpm";
|
|
952
|
-
} else if (await fileExists(
|
|
1242
|
+
} else if (await fileExists(join4(cwd, "yarn.lock"))) {
|
|
953
1243
|
detected.packageManager = "yarn";
|
|
954
|
-
} else if (await fileExists(
|
|
1244
|
+
} else if (await fileExists(join4(cwd, "bun.lockb"))) {
|
|
955
1245
|
detected.packageManager = "bun";
|
|
956
|
-
} else if (await fileExists(
|
|
1246
|
+
} else if (await fileExists(join4(cwd, "package-lock.json"))) {
|
|
957
1247
|
detected.packageManager = "npm";
|
|
958
1248
|
}
|
|
1249
|
+
if (detected.packageManager && detected.packageManager !== "npm") {
|
|
1250
|
+
const pm = detected.packageManager;
|
|
1251
|
+
for (const [key, value] of Object.entries(detected.commands)) {
|
|
1252
|
+
if (value && !value.startsWith(pm) && !value.startsWith("npx")) {
|
|
1253
|
+
detected.commands[key] = `${pm} run ${value}`;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
} else if (detected.commands) {
|
|
1257
|
+
for (const [key, value] of Object.entries(detected.commands)) {
|
|
1258
|
+
if (value && !value.startsWith("npm") && !value.startsWith("npx")) {
|
|
1259
|
+
detected.commands[key] = `npm run ${value}`;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
if (pkg.scripts) {
|
|
1264
|
+
detected.commands.build = pkg.scripts.build ? "build" : void 0;
|
|
1265
|
+
detected.commands.test = pkg.scripts.test ? "test" : void 0;
|
|
1266
|
+
detected.commands.lint = pkg.scripts.lint ? "lint" : pkg.scripts["lint:check"] ? "lint:check" : void 0;
|
|
1267
|
+
detected.commands.dev = pkg.scripts.dev ? "dev" : pkg.scripts.start ? "start" : pkg.scripts.serve ? "serve" : void 0;
|
|
1268
|
+
}
|
|
959
1269
|
return detected;
|
|
960
1270
|
} catch {
|
|
961
1271
|
}
|
|
962
1272
|
}
|
|
963
|
-
const pyprojectPath =
|
|
1273
|
+
const pyprojectPath = join4(cwd, "pyproject.toml");
|
|
964
1274
|
if (await fileExists(pyprojectPath)) {
|
|
965
1275
|
try {
|
|
966
|
-
const content = await
|
|
1276
|
+
const content = await readFile3(pyprojectPath, "utf-8");
|
|
967
1277
|
detected.stack.push("python");
|
|
1278
|
+
detected.type = "application";
|
|
968
1279
|
const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
|
|
969
1280
|
if (nameMatch) detected.name = nameMatch[1];
|
|
970
1281
|
if (content.includes("fastapi")) detected.stack.push("fastapi");
|
|
971
1282
|
if (content.includes("django")) detected.stack.push("django");
|
|
972
1283
|
if (content.includes("flask")) detected.stack.push("flask");
|
|
1284
|
+
if (content.includes("pydantic")) detected.stack.push("pydantic");
|
|
1285
|
+
if (content.includes("sqlalchemy")) detected.stack.push("sqlalchemy");
|
|
1286
|
+
if (content.includes("pytest")) detected.stack.push("pytest");
|
|
1287
|
+
if (content.includes("ruff")) detected.stack.push("ruff");
|
|
1288
|
+
if (content.includes("mypy")) detected.stack.push("mypy");
|
|
973
1289
|
detected.commands.test = "pytest";
|
|
974
|
-
detected.commands.lint = "ruff check";
|
|
1290
|
+
detected.commands.lint = "ruff check .";
|
|
1291
|
+
if (content.includes("[tool.poetry]")) {
|
|
1292
|
+
detected.packageManager = "yarn";
|
|
1293
|
+
detected.commands.dev = "poetry run python -m uvicorn main:app --reload";
|
|
1294
|
+
} else if (await fileExists(join4(cwd, "uv.lock"))) {
|
|
1295
|
+
detected.commands.dev = "uv run python main.py";
|
|
1296
|
+
}
|
|
975
1297
|
return detected;
|
|
976
1298
|
} catch {
|
|
977
1299
|
}
|
|
978
1300
|
}
|
|
979
|
-
const requirementsPath =
|
|
1301
|
+
const requirementsPath = join4(cwd, "requirements.txt");
|
|
980
1302
|
if (await fileExists(requirementsPath)) {
|
|
981
1303
|
try {
|
|
982
|
-
const content = await
|
|
1304
|
+
const content = await readFile3(requirementsPath, "utf-8");
|
|
983
1305
|
detected.stack.push("python");
|
|
984
|
-
|
|
985
|
-
if (content.includes("
|
|
986
|
-
if (content.includes("
|
|
1306
|
+
detected.type = "application";
|
|
1307
|
+
if (content.toLowerCase().includes("fastapi")) detected.stack.push("fastapi");
|
|
1308
|
+
if (content.toLowerCase().includes("django")) detected.stack.push("django");
|
|
1309
|
+
if (content.toLowerCase().includes("flask")) detected.stack.push("flask");
|
|
987
1310
|
detected.commands.test = "pytest";
|
|
988
|
-
detected.commands.lint = "ruff check";
|
|
1311
|
+
detected.commands.lint = "ruff check .";
|
|
989
1312
|
return detected;
|
|
990
1313
|
} catch {
|
|
991
1314
|
}
|
|
992
1315
|
}
|
|
993
|
-
const cargoPath =
|
|
1316
|
+
const cargoPath = join4(cwd, "Cargo.toml");
|
|
994
1317
|
if (await fileExists(cargoPath)) {
|
|
995
1318
|
try {
|
|
996
|
-
const content = await
|
|
1319
|
+
const content = await readFile3(cargoPath, "utf-8");
|
|
997
1320
|
detected.stack.push("rust");
|
|
1321
|
+
detected.type = "application";
|
|
998
1322
|
const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
|
|
999
1323
|
if (nameMatch) detected.name = nameMatch[1];
|
|
1324
|
+
if (content.includes("actix-web")) detected.stack.push("actix");
|
|
1325
|
+
if (content.includes("axum")) detected.stack.push("axum");
|
|
1326
|
+
if (content.includes("tokio")) detected.stack.push("tokio");
|
|
1327
|
+
if (content.includes("serde")) detected.stack.push("serde");
|
|
1328
|
+
if (content.includes("sqlx")) detected.stack.push("sqlx");
|
|
1000
1329
|
detected.commands.build = "cargo build";
|
|
1001
1330
|
detected.commands.test = "cargo test";
|
|
1002
1331
|
detected.commands.lint = "cargo clippy";
|
|
1332
|
+
detected.commands.dev = "cargo run";
|
|
1003
1333
|
return detected;
|
|
1004
1334
|
} catch {
|
|
1005
1335
|
}
|
|
1006
1336
|
}
|
|
1007
|
-
const goModPath =
|
|
1337
|
+
const goModPath = join4(cwd, "go.mod");
|
|
1008
1338
|
if (await fileExists(goModPath)) {
|
|
1009
1339
|
try {
|
|
1010
|
-
const content = await
|
|
1340
|
+
const content = await readFile3(goModPath, "utf-8");
|
|
1011
1341
|
detected.stack.push("go");
|
|
1342
|
+
detected.type = "application";
|
|
1012
1343
|
const moduleMatch = content.match(/module\s+(\S+)/);
|
|
1013
1344
|
if (moduleMatch) {
|
|
1014
1345
|
const parts = moduleMatch[1].split("/");
|
|
1015
1346
|
detected.name = parts[parts.length - 1];
|
|
1016
1347
|
}
|
|
1348
|
+
if (content.includes("gin-gonic/gin")) detected.stack.push("gin");
|
|
1349
|
+
if (content.includes("gofiber/fiber")) detected.stack.push("fiber");
|
|
1350
|
+
if (content.includes("labstack/echo")) detected.stack.push("echo");
|
|
1351
|
+
if (content.includes("gorm.io/gorm")) detected.stack.push("gorm");
|
|
1017
1352
|
detected.commands.build = "go build";
|
|
1018
1353
|
detected.commands.test = "go test ./...";
|
|
1019
1354
|
detected.commands.lint = "golangci-lint run";
|
|
1355
|
+
detected.commands.dev = "go run .";
|
|
1020
1356
|
return detected;
|
|
1021
1357
|
} catch {
|
|
1022
1358
|
}
|
|
1023
1359
|
}
|
|
1024
|
-
const makefilePath =
|
|
1360
|
+
const makefilePath = join4(cwd, "Makefile");
|
|
1025
1361
|
if (await fileExists(makefilePath)) {
|
|
1026
1362
|
try {
|
|
1027
|
-
const content = await
|
|
1363
|
+
const content = await readFile3(makefilePath, "utf-8");
|
|
1028
1364
|
if (content.includes("build:")) detected.commands.build = "make build";
|
|
1029
1365
|
if (content.includes("test:")) detected.commands.test = "make test";
|
|
1030
1366
|
if (content.includes("lint:")) detected.commands.lint = "make lint";
|
|
1367
|
+
if (content.includes("dev:")) detected.commands.dev = "make dev";
|
|
1368
|
+
if (content.includes("run:")) detected.commands.dev = detected.commands.dev || "make run";
|
|
1031
1369
|
if (Object.keys(detected.commands).length > 0) {
|
|
1370
|
+
detected.type = "application";
|
|
1032
1371
|
return detected;
|
|
1033
1372
|
}
|
|
1034
1373
|
} catch {
|
|
1035
1374
|
}
|
|
1036
1375
|
}
|
|
1376
|
+
if (await fileExists(join4(cwd, "Dockerfile")) || await fileExists(join4(cwd, "docker-compose.yml"))) {
|
|
1377
|
+
detected.stack.push("docker");
|
|
1378
|
+
detected.type = "application";
|
|
1379
|
+
}
|
|
1037
1380
|
return detected.stack.length > 0 || detected.name ? detected : null;
|
|
1038
1381
|
}
|
|
1039
1382
|
async function fileExists(path) {
|
|
1040
1383
|
try {
|
|
1041
|
-
await
|
|
1384
|
+
await access3(path);
|
|
1042
1385
|
return true;
|
|
1043
1386
|
} catch {
|
|
1044
1387
|
return false;
|
|
@@ -1049,13 +1392,28 @@ async function fileExists(path) {
|
|
|
1049
1392
|
var LYNXPROMPT_DIR = ".lynxprompt";
|
|
1050
1393
|
var LYNXPROMPT_CONFIG = ".lynxprompt/conf.yml";
|
|
1051
1394
|
var LYNXPROMPT_RULES = ".lynxprompt/rules";
|
|
1395
|
+
var AGENT_FILES = [
|
|
1396
|
+
{ name: "AGENTS.md", agent: "Universal (AGENTS.md)" },
|
|
1397
|
+
{ name: "CLAUDE.md", agent: "Claude Code" },
|
|
1398
|
+
{ name: ".windsurfrules", agent: "Windsurf" },
|
|
1399
|
+
{ name: ".clinerules", agent: "Cline" },
|
|
1400
|
+
{ name: ".goosehints", agent: "Goose" },
|
|
1401
|
+
{ name: "AIDER.md", agent: "Aider" },
|
|
1402
|
+
{ name: ".github/copilot-instructions.md", agent: "GitHub Copilot" },
|
|
1403
|
+
{ name: ".zed/instructions.md", agent: "Zed" }
|
|
1404
|
+
];
|
|
1405
|
+
var AGENT_DIRS = [
|
|
1406
|
+
{ path: ".cursor/rules", agent: "Cursor" },
|
|
1407
|
+
{ path: ".amazonq/rules", agent: "Amazon Q" },
|
|
1408
|
+
{ path: ".augment/rules", agent: "Augment Code" }
|
|
1409
|
+
];
|
|
1052
1410
|
async function scanForExistingFiles(cwd) {
|
|
1053
1411
|
const detected = [];
|
|
1054
1412
|
for (const file of AGENT_FILES) {
|
|
1055
|
-
const filePath =
|
|
1056
|
-
if (
|
|
1413
|
+
const filePath = join5(cwd, file.name);
|
|
1414
|
+
if (existsSync3(filePath)) {
|
|
1057
1415
|
try {
|
|
1058
|
-
const content = await
|
|
1416
|
+
const content = await readFile4(filePath, "utf-8");
|
|
1059
1417
|
detected.push({ path: file.name, agent: file.agent, content });
|
|
1060
1418
|
} catch {
|
|
1061
1419
|
detected.push({ path: file.name, agent: file.agent });
|
|
@@ -1063,8 +1421,8 @@ async function scanForExistingFiles(cwd) {
|
|
|
1063
1421
|
}
|
|
1064
1422
|
}
|
|
1065
1423
|
for (const dir of AGENT_DIRS) {
|
|
1066
|
-
const dirPath =
|
|
1067
|
-
if (
|
|
1424
|
+
const dirPath = join5(cwd, dir.path);
|
|
1425
|
+
if (existsSync3(dirPath)) {
|
|
1068
1426
|
detected.push({ path: dir.path, agent: dir.agent });
|
|
1069
1427
|
}
|
|
1070
1428
|
}
|
|
@@ -1152,13 +1510,16 @@ function createDefaultConfig(exporters = ["agents"]) {
|
|
|
1152
1510
|
}
|
|
1153
1511
|
]
|
|
1154
1512
|
};
|
|
1155
|
-
return
|
|
1513
|
+
return yaml2.stringify(config2);
|
|
1156
1514
|
}
|
|
1157
1515
|
function createLynxpromptReadme() {
|
|
1158
1516
|
return `# .lynxprompt
|
|
1159
1517
|
|
|
1160
1518
|
This directory contains your LynxPrompt configuration and rules.
|
|
1161
1519
|
|
|
1520
|
+
> **Note**: This is an advanced setup for managing rules across multiple AI editors.
|
|
1521
|
+
> Most users should use \`lynxp wizard\` instead for simple, direct file generation.
|
|
1522
|
+
|
|
1162
1523
|
## Directory structure
|
|
1163
1524
|
|
|
1164
1525
|
- **\`rules/\`** - Your AI rules. Edit files here, then sync to agents.
|
|
@@ -1182,7 +1543,7 @@ After editing rules, run:
|
|
|
1182
1543
|
lynxp sync
|
|
1183
1544
|
\`\`\`
|
|
1184
1545
|
|
|
1185
|
-
This exports your rules to the configured agent formats (AGENTS.md, .
|
|
1546
|
+
This exports your rules to the configured agent formats (AGENTS.md, .cursor/rules/, etc.)
|
|
1186
1547
|
|
|
1187
1548
|
## More information
|
|
1188
1549
|
|
|
@@ -1193,13 +1554,31 @@ This exports your rules to the configured agent formats (AGENTS.md, .cursorrules
|
|
|
1193
1554
|
async function initCommand(options) {
|
|
1194
1555
|
console.log();
|
|
1195
1556
|
console.log(chalk6.cyan("\u{1F431} LynxPrompt Init"));
|
|
1557
|
+
console.log(chalk6.gray("Advanced mode: Multi-editor rule management"));
|
|
1196
1558
|
console.log();
|
|
1559
|
+
if (!options.yes && !options.force) {
|
|
1560
|
+
console.log(chalk6.yellow("\u{1F4A1} Tip: Most users should use 'lynxp wizard' instead."));
|
|
1561
|
+
console.log(chalk6.gray(" The wizard generates files directly without the .lynxprompt/ folder."));
|
|
1562
|
+
console.log();
|
|
1563
|
+
const { proceed } = await prompts2({
|
|
1564
|
+
type: "confirm",
|
|
1565
|
+
name: "proceed",
|
|
1566
|
+
message: "Continue with advanced setup?",
|
|
1567
|
+
initial: false
|
|
1568
|
+
});
|
|
1569
|
+
if (!proceed) {
|
|
1570
|
+
console.log();
|
|
1571
|
+
console.log(chalk6.gray("Run 'lynxp wizard' for simple file generation."));
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
console.log();
|
|
1575
|
+
}
|
|
1197
1576
|
const cwd = process.cwd();
|
|
1198
1577
|
const projectName = basename(cwd);
|
|
1199
|
-
const lynxpromptDir =
|
|
1200
|
-
const configPath =
|
|
1201
|
-
const rulesDir =
|
|
1202
|
-
if (
|
|
1578
|
+
const lynxpromptDir = join5(cwd, LYNXPROMPT_DIR);
|
|
1579
|
+
const configPath = join5(cwd, LYNXPROMPT_CONFIG);
|
|
1580
|
+
const rulesDir = join5(cwd, LYNXPROMPT_RULES);
|
|
1581
|
+
if (existsSync3(configPath) && !options.force) {
|
|
1203
1582
|
console.log(chalk6.yellow("LynxPrompt is already initialized in this project."));
|
|
1204
1583
|
console.log(chalk6.gray(`Config: ${LYNXPROMPT_CONFIG}`));
|
|
1205
1584
|
console.log(chalk6.gray(`Rules: ${LYNXPROMPT_RULES}/`));
|
|
@@ -1216,23 +1595,22 @@ async function initCommand(options) {
|
|
|
1216
1595
|
const existingFiles = await scanForExistingFiles(cwd);
|
|
1217
1596
|
spinner.stop();
|
|
1218
1597
|
if (projectInfo) {
|
|
1219
|
-
console.log(chalk6.
|
|
1598
|
+
console.log(chalk6.green("\u2713 Detected project:"));
|
|
1220
1599
|
if (projectInfo.name) console.log(chalk6.gray(` Name: ${projectInfo.name}`));
|
|
1221
1600
|
if (projectInfo.stack.length > 0) console.log(chalk6.gray(` Stack: ${projectInfo.stack.join(", ")}`));
|
|
1222
1601
|
if (projectInfo.packageManager) console.log(chalk6.gray(` Package manager: ${projectInfo.packageManager}`));
|
|
1223
1602
|
console.log();
|
|
1224
1603
|
}
|
|
1225
1604
|
if (agentDetection.detected.length > 0) {
|
|
1226
|
-
console.log(chalk6.green(
|
|
1605
|
+
console.log(chalk6.green(`\u2713 Detected ${agentDetection.detected.length} AI agent${agentDetection.detected.length === 1 ? "" : "s"}:`));
|
|
1227
1606
|
for (const detected of agentDetection.detected) {
|
|
1228
1607
|
const rules = detected.ruleCount > 0 ? chalk6.gray(` (${detected.ruleCount} sections)`) : "";
|
|
1229
|
-
console.log(` ${chalk6.cyan("\
|
|
1608
|
+
console.log(` ${chalk6.cyan("\u2022")} ${detected.agent.name}${rules}`);
|
|
1230
1609
|
}
|
|
1231
1610
|
console.log();
|
|
1232
1611
|
}
|
|
1233
1612
|
if (existingFiles.length > 0) {
|
|
1234
|
-
console.log(chalk6.green("Found existing AI configuration files:"));
|
|
1235
|
-
console.log();
|
|
1613
|
+
console.log(chalk6.green("\u2713 Found existing AI configuration files:"));
|
|
1236
1614
|
for (const file of existingFiles) {
|
|
1237
1615
|
console.log(` ${chalk6.cyan(file.path)} ${chalk6.gray(`(${file.agent})`)}`);
|
|
1238
1616
|
}
|
|
@@ -1253,35 +1631,35 @@ async function initCommand(options) {
|
|
|
1253
1631
|
return;
|
|
1254
1632
|
}
|
|
1255
1633
|
if (action === "import") {
|
|
1256
|
-
await
|
|
1634
|
+
await mkdir3(rulesDir, { recursive: true });
|
|
1257
1635
|
let importedCount = 0;
|
|
1258
1636
|
for (const file of existingFiles) {
|
|
1259
1637
|
if (file.content) {
|
|
1260
1638
|
const ruleName = file.path.replace(/^\./, "").replace(/\//g, "-").replace(/\.md$/, "") + ".md";
|
|
1261
|
-
const rulePath =
|
|
1262
|
-
await
|
|
1639
|
+
const rulePath = join5(rulesDir, ruleName);
|
|
1640
|
+
await writeFile3(rulePath, file.content, "utf-8");
|
|
1263
1641
|
console.log(chalk6.gray(` Imported: ${file.path} \u2192 .lynxprompt/rules/${ruleName}`));
|
|
1264
1642
|
importedCount++;
|
|
1265
1643
|
}
|
|
1266
1644
|
}
|
|
1267
1645
|
if (importedCount === 0) {
|
|
1268
|
-
const starterPath =
|
|
1269
|
-
await
|
|
1646
|
+
const starterPath = join5(rulesDir, "agents.md");
|
|
1647
|
+
await writeFile3(starterPath, createStarterAgentsMd(projectName), "utf-8");
|
|
1270
1648
|
console.log(chalk6.gray(" Created starter: .lynxprompt/rules/agents.md"));
|
|
1271
1649
|
}
|
|
1272
1650
|
} else {
|
|
1273
|
-
await
|
|
1274
|
-
const starterPath =
|
|
1275
|
-
await
|
|
1651
|
+
await mkdir3(rulesDir, { recursive: true });
|
|
1652
|
+
const starterPath = join5(rulesDir, "agents.md");
|
|
1653
|
+
await writeFile3(starterPath, createStarterAgentsMd(projectName), "utf-8");
|
|
1276
1654
|
console.log(chalk6.gray("Created starter: .lynxprompt/rules/agents.md"));
|
|
1277
1655
|
}
|
|
1278
1656
|
} else {
|
|
1279
|
-
await
|
|
1657
|
+
await mkdir3(rulesDir, { recursive: true });
|
|
1280
1658
|
for (const file of existingFiles) {
|
|
1281
1659
|
if (file.content) {
|
|
1282
1660
|
const ruleName = file.path.replace(/^\./, "").replace(/\//g, "-").replace(/\.md$/, "") + ".md";
|
|
1283
|
-
const rulePath =
|
|
1284
|
-
await
|
|
1661
|
+
const rulePath = join5(rulesDir, ruleName);
|
|
1662
|
+
await writeFile3(rulePath, file.content, "utf-8");
|
|
1285
1663
|
}
|
|
1286
1664
|
}
|
|
1287
1665
|
}
|
|
@@ -1292,7 +1670,7 @@ async function initCommand(options) {
|
|
|
1292
1670
|
const { create } = await prompts2({
|
|
1293
1671
|
type: "confirm",
|
|
1294
1672
|
name: "create",
|
|
1295
|
-
message: "Create a starter
|
|
1673
|
+
message: "Create a starter template?",
|
|
1296
1674
|
initial: true
|
|
1297
1675
|
});
|
|
1298
1676
|
if (!create) {
|
|
@@ -1300,9 +1678,9 @@ async function initCommand(options) {
|
|
|
1300
1678
|
return;
|
|
1301
1679
|
}
|
|
1302
1680
|
}
|
|
1303
|
-
await
|
|
1304
|
-
const starterPath =
|
|
1305
|
-
await
|
|
1681
|
+
await mkdir3(rulesDir, { recursive: true });
|
|
1682
|
+
const starterPath = join5(rulesDir, "agents.md");
|
|
1683
|
+
await writeFile3(starterPath, createStarterAgentsMd(projectName), "utf-8");
|
|
1306
1684
|
console.log(chalk6.gray("Created: .lynxprompt/rules/agents.md"));
|
|
1307
1685
|
}
|
|
1308
1686
|
let exporters = [];
|
|
@@ -1346,16 +1724,16 @@ async function initCommand(options) {
|
|
|
1346
1724
|
}
|
|
1347
1725
|
}
|
|
1348
1726
|
console.log(chalk6.gray(`Enabling ${exporters.length} exporter${exporters.length === 1 ? "" : "s"}: ${exporters.join(", ")}`));
|
|
1349
|
-
await
|
|
1350
|
-
await
|
|
1351
|
-
const readmePath =
|
|
1352
|
-
await
|
|
1353
|
-
const gitignorePath =
|
|
1727
|
+
await mkdir3(dirname3(configPath), { recursive: true });
|
|
1728
|
+
await writeFile3(configPath, createDefaultConfig(exporters), "utf-8");
|
|
1729
|
+
const readmePath = join5(lynxpromptDir, "README.md");
|
|
1730
|
+
await writeFile3(readmePath, createLynxpromptReadme(), "utf-8");
|
|
1731
|
+
const gitignorePath = join5(lynxpromptDir, ".gitignore");
|
|
1354
1732
|
const gitignoreContent = `# Local state files
|
|
1355
1733
|
.cache/
|
|
1356
1734
|
.backups/
|
|
1357
1735
|
`;
|
|
1358
|
-
await
|
|
1736
|
+
await writeFile3(gitignorePath, gitignoreContent, "utf-8");
|
|
1359
1737
|
console.log();
|
|
1360
1738
|
console.log(chalk6.green("\u2705 LynxPrompt initialized!"));
|
|
1361
1739
|
console.log();
|
|
@@ -1374,13 +1752,14 @@ async function initCommand(options) {
|
|
|
1374
1752
|
import chalk7 from "chalk";
|
|
1375
1753
|
import prompts3 from "prompts";
|
|
1376
1754
|
import ora6 from "ora";
|
|
1377
|
-
import { writeFile as
|
|
1378
|
-
import { join as
|
|
1755
|
+
import { writeFile as writeFile4, mkdir as mkdir4, access as access5 } from "fs/promises";
|
|
1756
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
1379
1757
|
|
|
1380
1758
|
// src/utils/generator.ts
|
|
1381
1759
|
var PLATFORM_FILES = {
|
|
1382
|
-
|
|
1383
|
-
|
|
1760
|
+
agents: "AGENTS.md",
|
|
1761
|
+
cursor: ".cursor/rules/project.mdc",
|
|
1762
|
+
claude: "CLAUDE.md",
|
|
1384
1763
|
copilot: ".github/copilot-instructions.md",
|
|
1385
1764
|
windsurf: ".windsurfrules",
|
|
1386
1765
|
zed: ".zed/instructions.md"
|
|
@@ -1490,13 +1869,25 @@ function generateConfig(options) {
|
|
|
1490
1869
|
}
|
|
1491
1870
|
function generateFileContent(options, platform) {
|
|
1492
1871
|
const sections = [];
|
|
1493
|
-
const
|
|
1872
|
+
const isMdc = platform === "cursor";
|
|
1873
|
+
const isPlainText = platform === "windsurf";
|
|
1874
|
+
const isMarkdown = !isMdc && !isPlainText;
|
|
1875
|
+
if (isMdc) {
|
|
1876
|
+
sections.push("---");
|
|
1877
|
+
sections.push(`description: "${options.name} - AI coding rules"`);
|
|
1878
|
+
sections.push('globs: ["**/*"]');
|
|
1879
|
+
sections.push("alwaysApply: true");
|
|
1880
|
+
sections.push("---");
|
|
1881
|
+
sections.push("");
|
|
1882
|
+
sections.push(`# ${options.name} - AI Assistant Configuration`);
|
|
1883
|
+
sections.push("");
|
|
1884
|
+
}
|
|
1494
1885
|
if (isMarkdown) {
|
|
1495
1886
|
sections.push(`# ${options.name} - AI Assistant Configuration`);
|
|
1496
1887
|
sections.push("");
|
|
1497
1888
|
}
|
|
1498
1889
|
const personaDesc = PERSONA_DESCRIPTIONS[options.persona] || options.persona;
|
|
1499
|
-
if (isMarkdown) {
|
|
1890
|
+
if (isMarkdown || isMdc) {
|
|
1500
1891
|
sections.push("## Persona");
|
|
1501
1892
|
sections.push("");
|
|
1502
1893
|
sections.push(`You are ${personaDesc}. You assist developers working on ${options.name}.`);
|
|
@@ -1509,14 +1900,14 @@ function generateFileContent(options, platform) {
|
|
|
1509
1900
|
}
|
|
1510
1901
|
sections.push("");
|
|
1511
1902
|
if (options.stack.length > 0) {
|
|
1512
|
-
if (isMarkdown) {
|
|
1903
|
+
if (isMarkdown || isMdc) {
|
|
1513
1904
|
sections.push("## Tech Stack");
|
|
1514
1905
|
sections.push("");
|
|
1515
1906
|
} else {
|
|
1516
1907
|
sections.push("Tech Stack:");
|
|
1517
1908
|
}
|
|
1518
1909
|
const stackList = options.stack.map((s) => STACK_NAMES[s] || s);
|
|
1519
|
-
if (isMarkdown) {
|
|
1910
|
+
if (isMarkdown || isMdc) {
|
|
1520
1911
|
for (const tech of stackList) {
|
|
1521
1912
|
sections.push(`- ${tech}`);
|
|
1522
1913
|
}
|
|
@@ -1527,7 +1918,7 @@ function generateFileContent(options, platform) {
|
|
|
1527
1918
|
}
|
|
1528
1919
|
const hasCommands = Object.values(options.commands).some(Boolean);
|
|
1529
1920
|
if (hasCommands) {
|
|
1530
|
-
if (isMarkdown) {
|
|
1921
|
+
if (isMarkdown || isMdc) {
|
|
1531
1922
|
sections.push("## Commands");
|
|
1532
1923
|
sections.push("");
|
|
1533
1924
|
sections.push("Use these commands for common tasks:");
|
|
@@ -1537,25 +1928,25 @@ function generateFileContent(options, platform) {
|
|
|
1537
1928
|
sections.push("Commands:");
|
|
1538
1929
|
}
|
|
1539
1930
|
if (options.commands.build) {
|
|
1540
|
-
sections.push(isMarkdown ? `# Build: ${options.commands.build}` : `- Build: ${options.commands.build}`);
|
|
1931
|
+
sections.push(isMarkdown || isMdc ? `# Build: ${options.commands.build}` : `- Build: ${options.commands.build}`);
|
|
1541
1932
|
}
|
|
1542
1933
|
if (options.commands.test) {
|
|
1543
|
-
sections.push(isMarkdown ? `# Test: ${options.commands.test}` : `- Test: ${options.commands.test}`);
|
|
1934
|
+
sections.push(isMarkdown || isMdc ? `# Test: ${options.commands.test}` : `- Test: ${options.commands.test}`);
|
|
1544
1935
|
}
|
|
1545
1936
|
if (options.commands.lint) {
|
|
1546
|
-
sections.push(isMarkdown ? `# Lint: ${options.commands.lint}` : `- Lint: ${options.commands.lint}`);
|
|
1937
|
+
sections.push(isMarkdown || isMdc ? `# Lint: ${options.commands.lint}` : `- Lint: ${options.commands.lint}`);
|
|
1547
1938
|
}
|
|
1548
1939
|
if (options.commands.dev) {
|
|
1549
|
-
sections.push(isMarkdown ? `# Dev: ${options.commands.dev}` : `- Dev: ${options.commands.dev}`);
|
|
1940
|
+
sections.push(isMarkdown || isMdc ? `# Dev: ${options.commands.dev}` : `- Dev: ${options.commands.dev}`);
|
|
1550
1941
|
}
|
|
1551
|
-
if (isMarkdown) {
|
|
1942
|
+
if (isMarkdown || isMdc) {
|
|
1552
1943
|
sections.push("```");
|
|
1553
1944
|
}
|
|
1554
1945
|
sections.push("");
|
|
1555
1946
|
}
|
|
1556
1947
|
const boundaries = BOUNDARIES[options.boundaries];
|
|
1557
1948
|
if (boundaries) {
|
|
1558
|
-
if (isMarkdown) {
|
|
1949
|
+
if (isMarkdown || isMdc) {
|
|
1559
1950
|
sections.push("## Boundaries");
|
|
1560
1951
|
sections.push("");
|
|
1561
1952
|
sections.push("### \u2705 Always (do without asking)");
|
|
@@ -1595,7 +1986,7 @@ function generateFileContent(options, platform) {
|
|
|
1595
1986
|
}
|
|
1596
1987
|
sections.push("");
|
|
1597
1988
|
}
|
|
1598
|
-
if (isMarkdown) {
|
|
1989
|
+
if (isMarkdown || isMdc) {
|
|
1599
1990
|
sections.push("## Code Style");
|
|
1600
1991
|
sections.push("");
|
|
1601
1992
|
sections.push("Follow these conventions:");
|
|
@@ -1631,7 +2022,7 @@ function generateFileContent(options, platform) {
|
|
|
1631
2022
|
sections.push("- Keep functions focused and testable");
|
|
1632
2023
|
sections.push("");
|
|
1633
2024
|
}
|
|
1634
|
-
if (isMarkdown) {
|
|
2025
|
+
if (isMarkdown || isMdc) {
|
|
1635
2026
|
sections.push("---");
|
|
1636
2027
|
sections.push("");
|
|
1637
2028
|
sections.push(`*Generated by [LynxPrompt](https://lynxprompt.com) CLI*`);
|
|
@@ -1640,6 +2031,24 @@ function generateFileContent(options, platform) {
|
|
|
1640
2031
|
}
|
|
1641
2032
|
|
|
1642
2033
|
// src/commands/wizard.ts
|
|
2034
|
+
var OUTPUT_FORMATS = [
|
|
2035
|
+
{
|
|
2036
|
+
title: "AGENTS.md (Universal)",
|
|
2037
|
+
value: "agents",
|
|
2038
|
+
description: "Works with Claude Code, GitHub Copilot, Aider, and most AI editors",
|
|
2039
|
+
recommended: true
|
|
2040
|
+
},
|
|
2041
|
+
{
|
|
2042
|
+
title: "Cursor (.cursor/rules/)",
|
|
2043
|
+
value: "cursor",
|
|
2044
|
+
description: "Cursor IDE with MDC format"
|
|
2045
|
+
},
|
|
2046
|
+
{
|
|
2047
|
+
title: "Multiple formats",
|
|
2048
|
+
value: "multiple",
|
|
2049
|
+
description: "Select multiple AI editors to generate for"
|
|
2050
|
+
}
|
|
2051
|
+
];
|
|
1643
2052
|
var TECH_STACKS = [
|
|
1644
2053
|
{ title: "TypeScript", value: "typescript" },
|
|
1645
2054
|
{ title: "JavaScript", value: "javascript" },
|
|
@@ -1667,22 +2076,30 @@ var FRAMEWORKS = [
|
|
|
1667
2076
|
{ title: "Laravel", value: "laravel" }
|
|
1668
2077
|
];
|
|
1669
2078
|
var PLATFORMS = [
|
|
1670
|
-
{ title: "
|
|
1671
|
-
{ title: "
|
|
2079
|
+
{ title: "AGENTS.md (Universal)", value: "agents", filename: "AGENTS.md" },
|
|
2080
|
+
{ title: "Cursor (.cursor/rules/)", value: "cursor", filename: ".cursor/rules/project.mdc" },
|
|
2081
|
+
{ title: "Claude Code (CLAUDE.md)", value: "claude", filename: "CLAUDE.md" },
|
|
1672
2082
|
{ title: "GitHub Copilot", value: "copilot", filename: ".github/copilot-instructions.md" },
|
|
1673
2083
|
{ title: "Windsurf (.windsurfrules)", value: "windsurf", filename: ".windsurfrules" },
|
|
1674
2084
|
{ title: "Zed", value: "zed", filename: ".zed/instructions.md" }
|
|
1675
2085
|
];
|
|
1676
2086
|
var PERSONAS = [
|
|
2087
|
+
{ title: "Full-Stack Developer - Complete application setups", value: "fullstack" },
|
|
1677
2088
|
{ title: "Backend Developer - APIs, databases, microservices", value: "backend" },
|
|
1678
2089
|
{ title: "Frontend Developer - UI, components, styling", value: "frontend" },
|
|
1679
|
-
{ title: "Full-Stack Developer - Complete application setups", value: "fullstack" },
|
|
1680
2090
|
{ title: "DevOps Engineer - Infrastructure, CI/CD, containers", value: "devops" },
|
|
1681
2091
|
{ title: "Data Engineer - Pipelines, ETL, databases", value: "data" },
|
|
1682
2092
|
{ title: "Security Engineer - Secure code, vulnerabilities", value: "security" },
|
|
1683
2093
|
{ title: "Custom...", value: "custom" }
|
|
1684
2094
|
];
|
|
1685
2095
|
var BOUNDARY_PRESETS = [
|
|
2096
|
+
{
|
|
2097
|
+
title: "Standard - Balance of freedom and safety (recommended)",
|
|
2098
|
+
value: "standard",
|
|
2099
|
+
always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
|
|
2100
|
+
askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
|
|
2101
|
+
never: ["Delete production data", "Modify .env secrets", "Force push"]
|
|
2102
|
+
},
|
|
1686
2103
|
{
|
|
1687
2104
|
title: "Conservative - Ask before most changes",
|
|
1688
2105
|
value: "conservative",
|
|
@@ -1690,13 +2107,6 @@ var BOUNDARY_PRESETS = [
|
|
|
1690
2107
|
askFirst: ["Modify any file", "Add dependencies", "Create files", "Run tests"],
|
|
1691
2108
|
never: ["Delete files", "Modify .env", "Push to git"]
|
|
1692
2109
|
},
|
|
1693
|
-
{
|
|
1694
|
-
title: "Standard - Balance of freedom and safety",
|
|
1695
|
-
value: "standard",
|
|
1696
|
-
always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
|
|
1697
|
-
askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
|
|
1698
|
-
never: ["Delete production data", "Modify .env secrets", "Force push"]
|
|
1699
|
-
},
|
|
1700
2110
|
{
|
|
1701
2111
|
title: "Permissive - AI can modify freely within src/",
|
|
1702
2112
|
value: "permissive",
|
|
@@ -1707,24 +2117,34 @@ var BOUNDARY_PRESETS = [
|
|
|
1707
2117
|
];
|
|
1708
2118
|
async function wizardCommand(options) {
|
|
1709
2119
|
console.log();
|
|
1710
|
-
console.log(chalk7.cyan("\u{1F431}
|
|
2120
|
+
console.log(chalk7.cyan("\u{1F431} LynxPrompt Wizard"));
|
|
2121
|
+
console.log(chalk7.gray("Generate AI IDE configuration in seconds"));
|
|
1711
2122
|
console.log();
|
|
1712
2123
|
const detected = await detectProject(process.cwd());
|
|
1713
2124
|
if (detected) {
|
|
1714
|
-
console.log(chalk7.
|
|
2125
|
+
console.log(chalk7.green("\u2713 Detected project:"));
|
|
1715
2126
|
if (detected.name) console.log(chalk7.gray(` Name: ${detected.name}`));
|
|
1716
2127
|
if (detected.stack.length > 0) console.log(chalk7.gray(` Stack: ${detected.stack.join(", ")}`));
|
|
2128
|
+
if (detected.packageManager) console.log(chalk7.gray(` Package manager: ${detected.packageManager}`));
|
|
1717
2129
|
if (detected.commands.build) console.log(chalk7.gray(` Build: ${detected.commands.build}`));
|
|
1718
2130
|
if (detected.commands.test) console.log(chalk7.gray(` Test: ${detected.commands.test}`));
|
|
1719
2131
|
console.log();
|
|
1720
2132
|
}
|
|
1721
2133
|
let config2;
|
|
1722
2134
|
if (options.yes) {
|
|
2135
|
+
let platforms;
|
|
2136
|
+
if (options.format) {
|
|
2137
|
+
platforms = options.format.split(",").map((f) => f.trim());
|
|
2138
|
+
} else if (options.platforms) {
|
|
2139
|
+
platforms = options.platforms.split(",").map((p) => p.trim());
|
|
2140
|
+
} else {
|
|
2141
|
+
platforms = ["agents"];
|
|
2142
|
+
}
|
|
1723
2143
|
config2 = {
|
|
1724
2144
|
name: options.name || detected?.name || "my-project",
|
|
1725
2145
|
description: options.description || "",
|
|
1726
2146
|
stack: options.stack?.split(",").map((s) => s.trim()) || detected?.stack || [],
|
|
1727
|
-
platforms
|
|
2147
|
+
platforms,
|
|
1728
2148
|
persona: options.persona || "fullstack",
|
|
1729
2149
|
boundaries: options.boundaries || "standard",
|
|
1730
2150
|
commands: detected?.commands || {}
|
|
@@ -1732,17 +2152,17 @@ async function wizardCommand(options) {
|
|
|
1732
2152
|
} else {
|
|
1733
2153
|
config2 = await runInteractiveWizard(options, detected);
|
|
1734
2154
|
}
|
|
1735
|
-
const spinner = ora6("Generating configuration
|
|
2155
|
+
const spinner = ora6("Generating configuration...").start();
|
|
1736
2156
|
try {
|
|
1737
2157
|
const files = generateConfig(config2);
|
|
1738
2158
|
spinner.stop();
|
|
1739
2159
|
console.log();
|
|
1740
|
-
console.log(chalk7.green("\u2705 Generated
|
|
2160
|
+
console.log(chalk7.green("\u2705 Generated:"));
|
|
1741
2161
|
for (const [filename, content] of Object.entries(files)) {
|
|
1742
|
-
const outputPath =
|
|
2162
|
+
const outputPath = join6(process.cwd(), filename);
|
|
1743
2163
|
let exists = false;
|
|
1744
2164
|
try {
|
|
1745
|
-
await
|
|
2165
|
+
await access5(outputPath);
|
|
1746
2166
|
exists = true;
|
|
1747
2167
|
} catch {
|
|
1748
2168
|
}
|
|
@@ -1758,66 +2178,98 @@ async function wizardCommand(options) {
|
|
|
1758
2178
|
continue;
|
|
1759
2179
|
}
|
|
1760
2180
|
}
|
|
1761
|
-
const dir =
|
|
2181
|
+
const dir = dirname4(outputPath);
|
|
1762
2182
|
if (dir !== ".") {
|
|
1763
|
-
await
|
|
2183
|
+
await mkdir4(dir, { recursive: true });
|
|
1764
2184
|
}
|
|
1765
|
-
await
|
|
2185
|
+
await writeFile4(outputPath, content, "utf-8");
|
|
1766
2186
|
console.log(` ${chalk7.cyan(filename)}`);
|
|
1767
2187
|
}
|
|
1768
2188
|
console.log();
|
|
1769
|
-
console.log(chalk7.gray("Your AI
|
|
1770
|
-
console.log(
|
|
2189
|
+
console.log(chalk7.gray("Your AI assistant will now follow these instructions."));
|
|
2190
|
+
console.log();
|
|
2191
|
+
console.log(chalk7.gray("Tips:"));
|
|
2192
|
+
console.log(chalk7.gray(" \u2022 Edit the generated file anytime to customize rules"));
|
|
2193
|
+
console.log(chalk7.gray(" \u2022 Run 'lynxp wizard' again to regenerate"));
|
|
2194
|
+
console.log(chalk7.gray(" \u2022 Run 'lynxp check' to validate your configuration"));
|
|
1771
2195
|
console.log();
|
|
1772
2196
|
} catch (error) {
|
|
1773
2197
|
spinner.fail("Failed to generate files");
|
|
1774
|
-
console.error(chalk7.red("An error occurred while generating configuration files."));
|
|
2198
|
+
console.error(chalk7.red("\n\u2717 An error occurred while generating configuration files."));
|
|
1775
2199
|
if (error instanceof Error) {
|
|
1776
|
-
console.error(chalk7.gray(error.message));
|
|
2200
|
+
console.error(chalk7.gray(` ${error.message}`));
|
|
1777
2201
|
}
|
|
2202
|
+
console.error(chalk7.gray("\nTry running with --yes flag for default settings."));
|
|
1778
2203
|
process.exit(1);
|
|
1779
2204
|
}
|
|
1780
2205
|
}
|
|
1781
2206
|
async function runInteractiveWizard(options, detected) {
|
|
1782
2207
|
const answers = {};
|
|
2208
|
+
let platforms;
|
|
2209
|
+
if (options.format) {
|
|
2210
|
+
platforms = options.format.split(",").map((f) => f.trim());
|
|
2211
|
+
} else {
|
|
2212
|
+
const formatResponse = await prompts3({
|
|
2213
|
+
type: "select",
|
|
2214
|
+
name: "format",
|
|
2215
|
+
message: "Select output format:",
|
|
2216
|
+
choices: OUTPUT_FORMATS.map((f) => ({
|
|
2217
|
+
title: f.recommended ? `${f.title} ${chalk7.green("(recommended)")}` : f.title,
|
|
2218
|
+
value: f.value,
|
|
2219
|
+
description: f.description
|
|
2220
|
+
})),
|
|
2221
|
+
initial: 0
|
|
2222
|
+
// AGENTS.md is default
|
|
2223
|
+
});
|
|
2224
|
+
if (formatResponse.format === "multiple") {
|
|
2225
|
+
const platformResponse = await prompts3({
|
|
2226
|
+
type: "multiselect",
|
|
2227
|
+
name: "platforms",
|
|
2228
|
+
message: "Select AI editors:",
|
|
2229
|
+
choices: PLATFORMS.map((p) => ({ title: p.title, value: p.value })),
|
|
2230
|
+
hint: "- Space to select, Enter to confirm",
|
|
2231
|
+
min: 1
|
|
2232
|
+
});
|
|
2233
|
+
platforms = platformResponse.platforms || ["agents"];
|
|
2234
|
+
} else {
|
|
2235
|
+
platforms = [formatResponse.format || "agents"];
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
answers.platforms = platforms;
|
|
1783
2239
|
const nameResponse = await prompts3({
|
|
1784
2240
|
type: "text",
|
|
1785
2241
|
name: "name",
|
|
1786
|
-
message: "
|
|
2242
|
+
message: "Project name:",
|
|
1787
2243
|
initial: options.name || detected?.name || "my-project"
|
|
1788
2244
|
});
|
|
1789
|
-
answers.name = nameResponse.name;
|
|
2245
|
+
answers.name = nameResponse.name || "my-project";
|
|
1790
2246
|
const descResponse = await prompts3({
|
|
1791
2247
|
type: "text",
|
|
1792
2248
|
name: "description",
|
|
1793
|
-
message: "
|
|
2249
|
+
message: "Brief description (optional):",
|
|
1794
2250
|
initial: options.description || ""
|
|
1795
2251
|
});
|
|
1796
|
-
answers.description = descResponse.description;
|
|
2252
|
+
answers.description = descResponse.description || "";
|
|
1797
2253
|
const allStackOptions = [...TECH_STACKS, ...FRAMEWORKS];
|
|
2254
|
+
const detectedStackSet = new Set(detected?.stack || []);
|
|
1798
2255
|
const stackResponse = await prompts3({
|
|
1799
2256
|
type: "multiselect",
|
|
1800
2257
|
name: "stack",
|
|
1801
|
-
message: "
|
|
1802
|
-
choices: allStackOptions
|
|
2258
|
+
message: "Tech stack:",
|
|
2259
|
+
choices: allStackOptions.map((s) => ({
|
|
2260
|
+
title: s.title,
|
|
2261
|
+
value: s.value,
|
|
2262
|
+
selected: detectedStackSet.has(s.value)
|
|
2263
|
+
})),
|
|
1803
2264
|
hint: "- Space to select, Enter to confirm"
|
|
1804
2265
|
});
|
|
1805
2266
|
answers.stack = stackResponse.stack || [];
|
|
1806
|
-
const platformResponse = await prompts3({
|
|
1807
|
-
type: "multiselect",
|
|
1808
|
-
name: "platforms",
|
|
1809
|
-
message: "Which AI IDEs do you use?",
|
|
1810
|
-
choices: PLATFORMS,
|
|
1811
|
-
hint: "- Space to select, Enter to confirm",
|
|
1812
|
-
min: 1
|
|
1813
|
-
});
|
|
1814
|
-
answers.platforms = platformResponse.platforms || ["cursor"];
|
|
1815
2267
|
const personaResponse = await prompts3({
|
|
1816
2268
|
type: "select",
|
|
1817
2269
|
name: "persona",
|
|
1818
|
-
message: "
|
|
2270
|
+
message: "AI persona:",
|
|
1819
2271
|
choices: PERSONAS,
|
|
1820
|
-
initial:
|
|
2272
|
+
initial: 0
|
|
1821
2273
|
// Full-stack by default
|
|
1822
2274
|
});
|
|
1823
2275
|
if (personaResponse.persona === "custom") {
|
|
@@ -1828,8 +2280,17 @@ async function runInteractiveWizard(options, detected) {
|
|
|
1828
2280
|
});
|
|
1829
2281
|
answers.persona = customPersona.value || "fullstack";
|
|
1830
2282
|
} else {
|
|
1831
|
-
answers.persona = personaResponse.persona;
|
|
2283
|
+
answers.persona = personaResponse.persona || "fullstack";
|
|
1832
2284
|
}
|
|
2285
|
+
const boundaryResponse = await prompts3({
|
|
2286
|
+
type: "select",
|
|
2287
|
+
name: "boundaries",
|
|
2288
|
+
message: "AI boundaries:",
|
|
2289
|
+
choices: BOUNDARY_PRESETS.map((b) => ({ title: b.title, value: b.value })),
|
|
2290
|
+
initial: 0
|
|
2291
|
+
// Standard by default
|
|
2292
|
+
});
|
|
2293
|
+
answers.boundaries = boundaryResponse.boundaries || "standard";
|
|
1833
2294
|
if (detected?.commands && Object.keys(detected.commands).length > 0) {
|
|
1834
2295
|
console.log();
|
|
1835
2296
|
console.log(chalk7.gray("Auto-detected commands:"));
|
|
@@ -1840,15 +2301,15 @@ async function runInteractiveWizard(options, detected) {
|
|
|
1840
2301
|
const editCommands = await prompts3({
|
|
1841
2302
|
type: "confirm",
|
|
1842
2303
|
name: "edit",
|
|
1843
|
-
message: "Edit
|
|
2304
|
+
message: "Edit commands?",
|
|
1844
2305
|
initial: false
|
|
1845
2306
|
});
|
|
1846
2307
|
if (editCommands.edit) {
|
|
1847
2308
|
const commandsResponse = await prompts3([
|
|
1848
|
-
{ type: "text", name: "build", message: "Build
|
|
1849
|
-
{ type: "text", name: "test", message: "Test
|
|
1850
|
-
{ type: "text", name: "lint", message: "Lint
|
|
1851
|
-
{ type: "text", name: "dev", message: "Dev
|
|
2309
|
+
{ type: "text", name: "build", message: "Build:", initial: detected.commands.build },
|
|
2310
|
+
{ type: "text", name: "test", message: "Test:", initial: detected.commands.test },
|
|
2311
|
+
{ type: "text", name: "lint", message: "Lint:", initial: detected.commands.lint },
|
|
2312
|
+
{ type: "text", name: "dev", message: "Dev:", initial: detected.commands.dev }
|
|
1852
2313
|
]);
|
|
1853
2314
|
answers.commands = commandsResponse;
|
|
1854
2315
|
} else {
|
|
@@ -1857,15 +2318,6 @@ async function runInteractiveWizard(options, detected) {
|
|
|
1857
2318
|
} else {
|
|
1858
2319
|
answers.commands = {};
|
|
1859
2320
|
}
|
|
1860
|
-
const boundaryResponse = await prompts3({
|
|
1861
|
-
type: "select",
|
|
1862
|
-
name: "boundaries",
|
|
1863
|
-
message: "Select boundary preset:",
|
|
1864
|
-
choices: BOUNDARY_PRESETS.map((b) => ({ title: b.title, value: b.value })),
|
|
1865
|
-
initial: 1
|
|
1866
|
-
// Standard by default
|
|
1867
|
-
});
|
|
1868
|
-
answers.boundaries = boundaryResponse.boundaries || "standard";
|
|
1869
2321
|
return {
|
|
1870
2322
|
name: answers.name,
|
|
1871
2323
|
description: answers.description,
|
|
@@ -1940,32 +2392,87 @@ function handleApiError3(error) {
|
|
|
1940
2392
|
|
|
1941
2393
|
// src/commands/status.ts
|
|
1942
2394
|
import chalk9 from "chalk";
|
|
1943
|
-
import { access as
|
|
1944
|
-
import { join as
|
|
2395
|
+
import { access as access6, readFile as readFile6, readdir as readdir3 } from "fs/promises";
|
|
2396
|
+
import { join as join7 } from "path";
|
|
2397
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1945
2398
|
var CONFIG_FILES = [
|
|
1946
2399
|
{ path: "AGENTS.md", name: "AGENTS.md", platform: "Claude Code, Cursor, AI Agents" },
|
|
1947
2400
|
{ path: "CLAUDE.md", name: "CLAUDE.md", platform: "Claude Code" },
|
|
1948
|
-
{ path: ".cursorrules", name: ".cursorrules", platform: "Cursor" },
|
|
1949
2401
|
{ path: ".github/copilot-instructions.md", name: "Copilot Instructions", platform: "GitHub Copilot" },
|
|
1950
2402
|
{ path: ".windsurfrules", name: ".windsurfrules", platform: "Windsurf" },
|
|
1951
|
-
{ path: ".zed/instructions.md", name: "Zed Instructions", platform: "Zed" }
|
|
2403
|
+
{ path: ".zed/instructions.md", name: "Zed Instructions", platform: "Zed" },
|
|
2404
|
+
{ path: ".clinerules", name: ".clinerules", platform: "Cline" },
|
|
2405
|
+
{ path: ".goosehints", name: ".goosehints", platform: "Goose" },
|
|
2406
|
+
{ path: "AIDER.md", name: "AIDER.md", platform: "Aider" }
|
|
2407
|
+
];
|
|
2408
|
+
var CONFIG_DIRS = [
|
|
2409
|
+
{ path: ".cursor/rules", name: ".cursor/rules/", platform: "Cursor" },
|
|
2410
|
+
{ path: ".amazonq/rules", name: ".amazonq/rules/", platform: "Amazon Q" },
|
|
2411
|
+
{ path: ".augment/rules", name: ".augment/rules/", platform: "Augment Code" }
|
|
1952
2412
|
];
|
|
1953
2413
|
async function statusCommand() {
|
|
1954
2414
|
const cwd = process.cwd();
|
|
1955
2415
|
console.log();
|
|
1956
|
-
console.log(chalk9.cyan("\u{1F431}
|
|
2416
|
+
console.log(chalk9.cyan("\u{1F431} LynxPrompt Status"));
|
|
1957
2417
|
console.log(chalk9.gray(` Directory: ${cwd}`));
|
|
1958
2418
|
console.log();
|
|
2419
|
+
const lynxpromptExists = existsSync4(join7(cwd, ".lynxprompt"));
|
|
2420
|
+
if (lynxpromptExists) {
|
|
2421
|
+
console.log(chalk9.green("\u2713 LynxPrompt initialized"));
|
|
2422
|
+
const configPath = join7(cwd, ".lynxprompt/conf.yml");
|
|
2423
|
+
if (existsSync4(configPath)) {
|
|
2424
|
+
try {
|
|
2425
|
+
const content = await readFile6(configPath, "utf-8");
|
|
2426
|
+
const { parse: parse5 } = await import("yaml");
|
|
2427
|
+
const config2 = parse5(content);
|
|
2428
|
+
if (config2?.exporters?.length > 0) {
|
|
2429
|
+
console.log(chalk9.gray(` Exporters: ${config2.exporters.join(", ")}`));
|
|
2430
|
+
}
|
|
2431
|
+
} catch {
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
console.log();
|
|
2435
|
+
}
|
|
2436
|
+
const trackedStatus = await checkSyncStatus(cwd);
|
|
2437
|
+
if (trackedStatus.length > 0) {
|
|
2438
|
+
console.log(chalk9.cyan("\u{1F4E6} Tracked Blueprints"));
|
|
2439
|
+
console.log();
|
|
2440
|
+
for (const { blueprint, localModified, fileExists: fileExists2 } of trackedStatus) {
|
|
2441
|
+
const statusIcon = !fileExists2 ? chalk9.red("\u2717") : localModified ? chalk9.yellow("\u25CF") : chalk9.green("\u2713");
|
|
2442
|
+
const sourceLabel = {
|
|
2443
|
+
marketplace: chalk9.gray("[marketplace]"),
|
|
2444
|
+
team: chalk9.blue("[team]"),
|
|
2445
|
+
private: chalk9.green("[private]"),
|
|
2446
|
+
local: chalk9.gray("[local]")
|
|
2447
|
+
}[blueprint.source];
|
|
2448
|
+
console.log(` ${statusIcon} ${chalk9.bold(blueprint.file)} ${sourceLabel}`);
|
|
2449
|
+
console.log(` ${chalk9.gray(`ID: ${blueprint.id} \u2022 ${blueprint.name}`)}`);
|
|
2450
|
+
if (!fileExists2) {
|
|
2451
|
+
console.log(chalk9.red(` \u26A0 File missing - run 'lynxp pull ${blueprint.id}' to restore`));
|
|
2452
|
+
} else if (localModified) {
|
|
2453
|
+
if (blueprint.source === "marketplace") {
|
|
2454
|
+
console.log(chalk9.yellow(` \u26A0 Local changes (marketplace = read-only, won't sync back)`));
|
|
2455
|
+
} else {
|
|
2456
|
+
console.log(chalk9.yellow(` \u26A0 Local changes - run 'lynxp push ${blueprint.file}' to sync`));
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
console.log();
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
console.log(chalk9.cyan("\u{1F4C4} AI Config Files"));
|
|
2463
|
+
console.log();
|
|
1959
2464
|
let foundAny = false;
|
|
1960
2465
|
for (const config2 of CONFIG_FILES) {
|
|
1961
|
-
const filePath =
|
|
2466
|
+
const filePath = join7(cwd, config2.path);
|
|
1962
2467
|
try {
|
|
1963
|
-
await
|
|
1964
|
-
const content = await
|
|
2468
|
+
await access6(filePath);
|
|
2469
|
+
const content = await readFile6(filePath, "utf-8");
|
|
1965
2470
|
const lines = content.split("\n").length;
|
|
1966
2471
|
const size = formatBytes(content.length);
|
|
1967
2472
|
foundAny = true;
|
|
1968
|
-
|
|
2473
|
+
const tracked = trackedStatus.find((t) => t.blueprint.file === config2.path);
|
|
2474
|
+
const trackedLabel = tracked ? chalk9.cyan(" (tracked)") : "";
|
|
2475
|
+
console.log(` ${chalk9.green("\u2713")} ${chalk9.bold(config2.name)}${trackedLabel}`);
|
|
1969
2476
|
console.log(` ${chalk9.gray(`Platform: ${config2.platform}`)}`);
|
|
1970
2477
|
console.log(` ${chalk9.gray(`Size: ${size} (${lines} lines)`)}`);
|
|
1971
2478
|
const preview = getPreview(content);
|
|
@@ -1976,13 +2483,41 @@ async function statusCommand() {
|
|
|
1976
2483
|
} catch {
|
|
1977
2484
|
}
|
|
1978
2485
|
}
|
|
2486
|
+
for (const config2 of CONFIG_DIRS) {
|
|
2487
|
+
const dirPath = join7(cwd, config2.path);
|
|
2488
|
+
if (existsSync4(dirPath)) {
|
|
2489
|
+
try {
|
|
2490
|
+
const files = await readdir3(dirPath);
|
|
2491
|
+
const ruleFiles = files.filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
2492
|
+
if (ruleFiles.length > 0) {
|
|
2493
|
+
foundAny = true;
|
|
2494
|
+
console.log(` ${chalk9.green("\u2713")} ${chalk9.bold(config2.name)}`);
|
|
2495
|
+
console.log(` ${chalk9.gray(`Platform: ${config2.platform}`)}`);
|
|
2496
|
+
console.log(` ${chalk9.gray(`Rules: ${ruleFiles.length} file${ruleFiles.length === 1 ? "" : "s"}`)}`);
|
|
2497
|
+
for (const file of ruleFiles.slice(0, 3)) {
|
|
2498
|
+
console.log(` ${chalk9.gray(` \u2022 ${file}`)}`);
|
|
2499
|
+
}
|
|
2500
|
+
if (ruleFiles.length > 3) {
|
|
2501
|
+
console.log(` ${chalk9.gray(` ... and ${ruleFiles.length - 3} more`)}`);
|
|
2502
|
+
}
|
|
2503
|
+
console.log();
|
|
2504
|
+
}
|
|
2505
|
+
} catch {
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
1979
2509
|
if (!foundAny) {
|
|
1980
|
-
console.log(chalk9.yellow(" No AI configuration files found
|
|
2510
|
+
console.log(chalk9.yellow(" No AI configuration files found."));
|
|
1981
2511
|
console.log();
|
|
1982
|
-
console.log(chalk9.gray("
|
|
1983
|
-
console.log(chalk9.gray("
|
|
2512
|
+
console.log(chalk9.gray(" Get started:"));
|
|
2513
|
+
console.log(chalk9.gray(" lynxp wizard Generate a configuration"));
|
|
2514
|
+
console.log(chalk9.gray(" lynxp pull <id> Download from marketplace"));
|
|
2515
|
+
console.log(chalk9.gray(" lynxp search <query> Search for blueprints"));
|
|
1984
2516
|
} else {
|
|
1985
|
-
console.log(chalk9.gray("
|
|
2517
|
+
console.log(chalk9.gray("Commands:"));
|
|
2518
|
+
console.log(chalk9.gray(" lynxp wizard Regenerate configuration"));
|
|
2519
|
+
console.log(chalk9.gray(" lynxp check Validate files"));
|
|
2520
|
+
console.log(chalk9.gray(" lynxp link --list Show tracked blueprints"));
|
|
1986
2521
|
}
|
|
1987
2522
|
console.log();
|
|
1988
2523
|
}
|
|
@@ -1990,7 +2525,7 @@ function getPreview(content) {
|
|
|
1990
2525
|
const lines = content.split("\n");
|
|
1991
2526
|
for (const line of lines) {
|
|
1992
2527
|
const trimmed = line.trim();
|
|
1993
|
-
if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !trimmed.startsWith("<!--")) {
|
|
2528
|
+
if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !trimmed.startsWith("<!--") && !trimmed.startsWith("---") && !trimmed.startsWith(">")) {
|
|
1994
2529
|
return truncate3(trimmed, 50);
|
|
1995
2530
|
}
|
|
1996
2531
|
}
|
|
@@ -2010,10 +2545,10 @@ function formatBytes(bytes) {
|
|
|
2010
2545
|
import chalk10 from "chalk";
|
|
2011
2546
|
import ora8 from "ora";
|
|
2012
2547
|
import prompts4 from "prompts";
|
|
2013
|
-
import { readFile as
|
|
2014
|
-
import { join as
|
|
2015
|
-
import { existsSync as
|
|
2016
|
-
import * as
|
|
2548
|
+
import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5, readdir as readdir4 } from "fs/promises";
|
|
2549
|
+
import { join as join8, dirname as dirname5 } from "path";
|
|
2550
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2551
|
+
import * as yaml3 from "yaml";
|
|
2017
2552
|
var CONFIG_FILE = ".lynxprompt/conf.yml";
|
|
2018
2553
|
var RULES_DIR = ".lynxprompt/rules";
|
|
2019
2554
|
async function syncCommand(options = {}) {
|
|
@@ -2021,8 +2556,8 @@ async function syncCommand(options = {}) {
|
|
|
2021
2556
|
console.log(chalk10.cyan("\u{1F431} LynxPrompt Sync"));
|
|
2022
2557
|
console.log();
|
|
2023
2558
|
const cwd = process.cwd();
|
|
2024
|
-
const configPath =
|
|
2025
|
-
if (!
|
|
2559
|
+
const configPath = join8(cwd, CONFIG_FILE);
|
|
2560
|
+
if (!existsSync5(configPath)) {
|
|
2026
2561
|
console.log(chalk10.yellow("LynxPrompt is not initialized in this project."));
|
|
2027
2562
|
console.log();
|
|
2028
2563
|
console.log(chalk10.gray("Run 'lynxp init' first to set up LynxPrompt."));
|
|
@@ -2031,8 +2566,8 @@ async function syncCommand(options = {}) {
|
|
|
2031
2566
|
const spinner = ora8("Loading configuration...").start();
|
|
2032
2567
|
let config2;
|
|
2033
2568
|
try {
|
|
2034
|
-
const configContent = await
|
|
2035
|
-
config2 =
|
|
2569
|
+
const configContent = await readFile7(configPath, "utf-8");
|
|
2570
|
+
config2 = yaml3.parse(configContent);
|
|
2036
2571
|
spinner.succeed("Configuration loaded");
|
|
2037
2572
|
} catch (error) {
|
|
2038
2573
|
spinner.fail("Failed to load configuration");
|
|
@@ -2064,8 +2599,8 @@ async function syncCommand(options = {}) {
|
|
|
2064
2599
|
}
|
|
2065
2600
|
console.log(chalk10.gray(`Exporters: ${validExporters.map((e) => e.name).join(", ")}`));
|
|
2066
2601
|
console.log();
|
|
2067
|
-
const rulesPath =
|
|
2068
|
-
if (!
|
|
2602
|
+
const rulesPath = join8(cwd, RULES_DIR);
|
|
2603
|
+
if (!existsSync5(rulesPath)) {
|
|
2069
2604
|
console.log(chalk10.yellow("No rules found."));
|
|
2070
2605
|
console.log(chalk10.gray(`Create rules in ${RULES_DIR}/ to sync them.`));
|
|
2071
2606
|
return;
|
|
@@ -2128,12 +2663,12 @@ async function syncCommand(options = {}) {
|
|
|
2128
2663
|
async function loadRules(rulesPath) {
|
|
2129
2664
|
const files = [];
|
|
2130
2665
|
try {
|
|
2131
|
-
const entries = await
|
|
2666
|
+
const entries = await readdir4(rulesPath, { withFileTypes: true });
|
|
2132
2667
|
for (const entry of entries) {
|
|
2133
2668
|
if (!entry.isFile()) continue;
|
|
2134
2669
|
if (!entry.name.endsWith(".md")) continue;
|
|
2135
|
-
const filePath =
|
|
2136
|
-
const content = await
|
|
2670
|
+
const filePath = join8(rulesPath, entry.name);
|
|
2671
|
+
const content = await readFile7(filePath, "utf-8");
|
|
2137
2672
|
if (content.trim()) {
|
|
2138
2673
|
files.push({ name: entry.name, content: content.trim() });
|
|
2139
2674
|
}
|
|
@@ -2148,26 +2683,26 @@ async function loadRules(rulesPath) {
|
|
|
2148
2683
|
return { combined, files, fileCount: files.length };
|
|
2149
2684
|
}
|
|
2150
2685
|
async function syncToAgent(cwd, agent, content) {
|
|
2151
|
-
const outputPath =
|
|
2686
|
+
const outputPath = join8(cwd, agent.output);
|
|
2152
2687
|
if (agent.output.endsWith("/")) {
|
|
2153
2688
|
await syncToDirectory(cwd, agent, content);
|
|
2154
2689
|
return;
|
|
2155
2690
|
}
|
|
2156
2691
|
const formatted = formatForAgent(agent, content);
|
|
2157
|
-
const dir =
|
|
2692
|
+
const dir = dirname5(outputPath);
|
|
2158
2693
|
if (dir !== ".") {
|
|
2159
|
-
await
|
|
2694
|
+
await mkdir5(dir, { recursive: true });
|
|
2160
2695
|
}
|
|
2161
|
-
await
|
|
2696
|
+
await writeFile5(outputPath, formatted, "utf-8");
|
|
2162
2697
|
}
|
|
2163
2698
|
async function syncToDirectory(cwd, agent, content) {
|
|
2164
|
-
const outputDir =
|
|
2165
|
-
await
|
|
2699
|
+
const outputDir = join8(cwd, agent.output);
|
|
2700
|
+
await mkdir5(outputDir, { recursive: true });
|
|
2166
2701
|
const extension = agent.format === "mdc" ? ".mdc" : ".md";
|
|
2167
2702
|
const filename = `lynxprompt-rules${extension}`;
|
|
2168
|
-
const outputPath =
|
|
2703
|
+
const outputPath = join8(outputDir, filename);
|
|
2169
2704
|
const formatted = formatForAgent(agent, content);
|
|
2170
|
-
await
|
|
2705
|
+
await writeFile5(outputPath, formatted, "utf-8");
|
|
2171
2706
|
}
|
|
2172
2707
|
function formatForAgent(agent, content) {
|
|
2173
2708
|
switch (agent.format) {
|
|
@@ -2184,7 +2719,7 @@ function formatForAgent(agent, content) {
|
|
|
2184
2719
|
}
|
|
2185
2720
|
}
|
|
2186
2721
|
function formatAsMdc(content, agent) {
|
|
2187
|
-
const frontmatter =
|
|
2722
|
+
const frontmatter = yaml3.stringify({
|
|
2188
2723
|
description: "LynxPrompt rules - AI coding guidelines",
|
|
2189
2724
|
globs: ["**/*"],
|
|
2190
2725
|
alwaysApply: true
|
|
@@ -2221,10 +2756,10 @@ function formatAsJson(content, agent) {
|
|
|
2221
2756
|
// src/commands/agents.ts
|
|
2222
2757
|
import chalk11 from "chalk";
|
|
2223
2758
|
import prompts5 from "prompts";
|
|
2224
|
-
import { readFile as
|
|
2225
|
-
import { join as
|
|
2226
|
-
import { existsSync as
|
|
2227
|
-
import * as
|
|
2759
|
+
import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
|
|
2760
|
+
import { join as join9 } from "path";
|
|
2761
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2762
|
+
import * as yaml4 from "yaml";
|
|
2228
2763
|
var CONFIG_FILE2 = ".lynxprompt/conf.yml";
|
|
2229
2764
|
async function agentsCommand(action, agentId, options = {}) {
|
|
2230
2765
|
console.log();
|
|
@@ -2291,8 +2826,8 @@ async function listAgents() {
|
|
|
2291
2826
|
}
|
|
2292
2827
|
async function enableAgent(agentId, options = {}) {
|
|
2293
2828
|
const cwd = process.cwd();
|
|
2294
|
-
const configPath =
|
|
2295
|
-
if (!
|
|
2829
|
+
const configPath = join9(cwd, CONFIG_FILE2);
|
|
2830
|
+
if (!existsSync6(configPath)) {
|
|
2296
2831
|
console.log(chalk11.yellow("LynxPrompt not initialized. Run 'lynxp init' first."));
|
|
2297
2832
|
return;
|
|
2298
2833
|
}
|
|
@@ -2361,8 +2896,8 @@ async function disableAgent(agentId) {
|
|
|
2361
2896
|
return;
|
|
2362
2897
|
}
|
|
2363
2898
|
const cwd = process.cwd();
|
|
2364
|
-
const configPath =
|
|
2365
|
-
if (!
|
|
2899
|
+
const configPath = join9(cwd, CONFIG_FILE2);
|
|
2900
|
+
if (!existsSync6(configPath)) {
|
|
2366
2901
|
console.log(chalk11.yellow("LynxPrompt not initialized. Run 'lynxp init' first."));
|
|
2367
2902
|
return;
|
|
2368
2903
|
}
|
|
@@ -2418,57 +2953,765 @@ async function detectAgentsInProject() {
|
|
|
2418
2953
|
}
|
|
2419
2954
|
async function loadConfig() {
|
|
2420
2955
|
const cwd = process.cwd();
|
|
2421
|
-
const configPath =
|
|
2422
|
-
if (!
|
|
2956
|
+
const configPath = join9(cwd, CONFIG_FILE2);
|
|
2957
|
+
if (!existsSync6(configPath)) {
|
|
2423
2958
|
return null;
|
|
2424
2959
|
}
|
|
2425
2960
|
try {
|
|
2426
|
-
const content = await
|
|
2427
|
-
return
|
|
2961
|
+
const content = await readFile8(configPath, "utf-8");
|
|
2962
|
+
return yaml4.parse(content);
|
|
2428
2963
|
} catch {
|
|
2429
2964
|
return null;
|
|
2430
2965
|
}
|
|
2431
2966
|
}
|
|
2432
2967
|
async function saveConfig(config2) {
|
|
2433
2968
|
const cwd = process.cwd();
|
|
2434
|
-
const configPath =
|
|
2435
|
-
const content =
|
|
2436
|
-
await
|
|
2969
|
+
const configPath = join9(cwd, CONFIG_FILE2);
|
|
2970
|
+
const content = yaml4.stringify(config2);
|
|
2971
|
+
await writeFile6(configPath, content, "utf-8");
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
// src/commands/check.ts
|
|
2975
|
+
import chalk12 from "chalk";
|
|
2976
|
+
import ora9 from "ora";
|
|
2977
|
+
import { readFile as readFile9, readdir as readdir5, stat as stat2 } from "fs/promises";
|
|
2978
|
+
import { join as join10 } from "path";
|
|
2979
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2980
|
+
import * as yaml5 from "yaml";
|
|
2981
|
+
var CONFIG_FILES2 = [
|
|
2982
|
+
{ path: "AGENTS.md", name: "AGENTS.md" },
|
|
2983
|
+
{ path: "CLAUDE.md", name: "CLAUDE.md" },
|
|
2984
|
+
{ path: ".github/copilot-instructions.md", name: "GitHub Copilot" },
|
|
2985
|
+
{ path: ".windsurfrules", name: "Windsurf" },
|
|
2986
|
+
{ path: ".clinerules", name: "Cline" },
|
|
2987
|
+
{ path: ".goosehints", name: "Goose" },
|
|
2988
|
+
{ path: ".zed/instructions.md", name: "Zed" }
|
|
2989
|
+
];
|
|
2990
|
+
var CONFIG_DIRS2 = [
|
|
2991
|
+
{ path: ".cursor/rules", name: "Cursor" },
|
|
2992
|
+
{ path: ".lynxprompt", name: "LynxPrompt" }
|
|
2993
|
+
];
|
|
2994
|
+
function validateMarkdown(content, filename) {
|
|
2995
|
+
const errors = [];
|
|
2996
|
+
const warnings = [];
|
|
2997
|
+
if (!content.trim()) {
|
|
2998
|
+
errors.push(`${filename}: File is empty`);
|
|
2999
|
+
return { errors, warnings };
|
|
3000
|
+
}
|
|
3001
|
+
if (content.trim().length < 50) {
|
|
3002
|
+
warnings.push(`${filename}: Content seems too short (< 50 chars)`);
|
|
3003
|
+
}
|
|
3004
|
+
if (!content.includes("#")) {
|
|
3005
|
+
warnings.push(`${filename}: No markdown headers found`);
|
|
3006
|
+
}
|
|
3007
|
+
const placeholders = [
|
|
3008
|
+
"TODO",
|
|
3009
|
+
"FIXME",
|
|
3010
|
+
"YOUR_",
|
|
3011
|
+
"REPLACE_",
|
|
3012
|
+
"[INSERT",
|
|
3013
|
+
"example.com"
|
|
3014
|
+
];
|
|
3015
|
+
for (const placeholder of placeholders) {
|
|
3016
|
+
if (content.includes(placeholder)) {
|
|
3017
|
+
warnings.push(`${filename}: Contains placeholder text "${placeholder}"`);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
const secretPatterns = [
|
|
3021
|
+
/sk[_-][a-zA-Z0-9]{20,}/,
|
|
3022
|
+
// Stripe-like keys
|
|
3023
|
+
/ghp_[a-zA-Z0-9]{36}/,
|
|
3024
|
+
// GitHub tokens
|
|
3025
|
+
/api[_-]?key[_-]?=\s*[a-zA-Z0-9]{20,}/i
|
|
3026
|
+
];
|
|
3027
|
+
for (const pattern of secretPatterns) {
|
|
3028
|
+
if (pattern.test(content)) {
|
|
3029
|
+
errors.push(`${filename}: Potential secret/API key detected - DO NOT commit secrets!`);
|
|
3030
|
+
break;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
return { errors, warnings };
|
|
3034
|
+
}
|
|
3035
|
+
async function validateLynxPromptConfig(cwd) {
|
|
3036
|
+
const errors = [];
|
|
3037
|
+
const warnings = [];
|
|
3038
|
+
const configPath = join10(cwd, ".lynxprompt/conf.yml");
|
|
3039
|
+
if (!existsSync7(configPath)) {
|
|
3040
|
+
return { errors, warnings };
|
|
3041
|
+
}
|
|
3042
|
+
try {
|
|
3043
|
+
const content = await readFile9(configPath, "utf-8");
|
|
3044
|
+
const config2 = yaml5.parse(content);
|
|
3045
|
+
if (!config2.version) {
|
|
3046
|
+
warnings.push(".lynxprompt/conf.yml: Missing 'version' field");
|
|
3047
|
+
}
|
|
3048
|
+
if (!config2.exporters || !Array.isArray(config2.exporters)) {
|
|
3049
|
+
errors.push(".lynxprompt/conf.yml: Missing or invalid 'exporters' field");
|
|
3050
|
+
} else if (config2.exporters.length === 0) {
|
|
3051
|
+
warnings.push(".lynxprompt/conf.yml: No exporters configured");
|
|
3052
|
+
}
|
|
3053
|
+
if (!config2.sources || !Array.isArray(config2.sources)) {
|
|
3054
|
+
errors.push(".lynxprompt/conf.yml: Missing or invalid 'sources' field");
|
|
3055
|
+
} else {
|
|
3056
|
+
for (const source of config2.sources) {
|
|
3057
|
+
if (source.type === "local" && source.path) {
|
|
3058
|
+
const sourcePath = join10(cwd, source.path);
|
|
3059
|
+
if (!existsSync7(sourcePath)) {
|
|
3060
|
+
errors.push(`.lynxprompt/conf.yml: Source path not found: ${source.path}`);
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
} catch (error) {
|
|
3066
|
+
errors.push(`.lynxprompt/conf.yml: Invalid YAML syntax - ${error instanceof Error ? error.message : "parse error"}`);
|
|
3067
|
+
}
|
|
3068
|
+
return { errors, warnings };
|
|
3069
|
+
}
|
|
3070
|
+
function validateMdc(content, filename) {
|
|
3071
|
+
const errors = [];
|
|
3072
|
+
const warnings = [];
|
|
3073
|
+
if (!content.startsWith("---")) {
|
|
3074
|
+
warnings.push(`${filename}: Missing YAML frontmatter`);
|
|
3075
|
+
} else {
|
|
3076
|
+
const frontmatterEnd = content.indexOf("---", 3);
|
|
3077
|
+
if (frontmatterEnd === -1) {
|
|
3078
|
+
errors.push(`${filename}: Unclosed YAML frontmatter`);
|
|
3079
|
+
} else {
|
|
3080
|
+
const frontmatter = content.substring(3, frontmatterEnd).trim();
|
|
3081
|
+
try {
|
|
3082
|
+
yaml5.parse(frontmatter);
|
|
3083
|
+
} catch {
|
|
3084
|
+
errors.push(`${filename}: Invalid YAML frontmatter`);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
const bodyStart = content.indexOf("---", 3);
|
|
3089
|
+
if (bodyStart !== -1) {
|
|
3090
|
+
const body = content.substring(bodyStart + 3).trim();
|
|
3091
|
+
const mdResult = validateMarkdown(body, filename);
|
|
3092
|
+
warnings.push(...mdResult.warnings);
|
|
3093
|
+
}
|
|
3094
|
+
return { errors, warnings };
|
|
3095
|
+
}
|
|
3096
|
+
async function checkCommand(options = {}) {
|
|
3097
|
+
const isCi = options.ci;
|
|
3098
|
+
const cwd = process.cwd();
|
|
3099
|
+
if (!isCi) {
|
|
3100
|
+
console.log();
|
|
3101
|
+
console.log(chalk12.cyan("\u{1F431} LynxPrompt Check"));
|
|
3102
|
+
console.log();
|
|
3103
|
+
}
|
|
3104
|
+
const result = {
|
|
3105
|
+
valid: true,
|
|
3106
|
+
errors: [],
|
|
3107
|
+
warnings: [],
|
|
3108
|
+
files: []
|
|
3109
|
+
};
|
|
3110
|
+
const spinner = !isCi ? ora9("Scanning for configuration files...").start() : null;
|
|
3111
|
+
for (const file of CONFIG_FILES2) {
|
|
3112
|
+
const filePath = join10(cwd, file.path);
|
|
3113
|
+
if (existsSync7(filePath)) {
|
|
3114
|
+
result.files.push(file.path);
|
|
3115
|
+
try {
|
|
3116
|
+
const content = await readFile9(filePath, "utf-8");
|
|
3117
|
+
const validation = validateMarkdown(content, file.path);
|
|
3118
|
+
result.errors.push(...validation.errors);
|
|
3119
|
+
result.warnings.push(...validation.warnings);
|
|
3120
|
+
} catch (error) {
|
|
3121
|
+
result.errors.push(`${file.path}: Could not read file`);
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
for (const dir of CONFIG_DIRS2) {
|
|
3126
|
+
const dirPath = join10(cwd, dir.path);
|
|
3127
|
+
if (existsSync7(dirPath)) {
|
|
3128
|
+
try {
|
|
3129
|
+
const files = await readdir5(dirPath);
|
|
3130
|
+
for (const file of files) {
|
|
3131
|
+
const filePath = join10(dirPath, file);
|
|
3132
|
+
const fileStat = await stat2(filePath);
|
|
3133
|
+
if (fileStat.isFile()) {
|
|
3134
|
+
result.files.push(`${dir.path}/${file}`);
|
|
3135
|
+
const content = await readFile9(filePath, "utf-8");
|
|
3136
|
+
if (file.endsWith(".mdc")) {
|
|
3137
|
+
const validation = validateMdc(content, `${dir.path}/${file}`);
|
|
3138
|
+
result.errors.push(...validation.errors);
|
|
3139
|
+
result.warnings.push(...validation.warnings);
|
|
3140
|
+
} else if (file.endsWith(".md")) {
|
|
3141
|
+
const validation = validateMarkdown(content, `${dir.path}/${file}`);
|
|
3142
|
+
result.errors.push(...validation.errors);
|
|
3143
|
+
result.warnings.push(...validation.warnings);
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
} catch {
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
const lynxpromptValidation = await validateLynxPromptConfig(cwd);
|
|
3152
|
+
result.errors.push(...lynxpromptValidation.errors);
|
|
3153
|
+
result.warnings.push(...lynxpromptValidation.warnings);
|
|
3154
|
+
spinner?.stop();
|
|
3155
|
+
result.valid = result.errors.length === 0;
|
|
3156
|
+
if (isCi) {
|
|
3157
|
+
if (!result.valid) {
|
|
3158
|
+
console.error("\u2717 Validation failed");
|
|
3159
|
+
for (const error of result.errors) {
|
|
3160
|
+
console.error(` ${error}`);
|
|
3161
|
+
}
|
|
3162
|
+
process.exit(1);
|
|
3163
|
+
} else if (result.files.length === 0) {
|
|
3164
|
+
console.error("\u2717 No configuration files found");
|
|
3165
|
+
process.exit(1);
|
|
3166
|
+
} else {
|
|
3167
|
+
console.log("\u2713 Validation passed");
|
|
3168
|
+
if (result.warnings.length > 0) {
|
|
3169
|
+
console.log(` (${result.warnings.length} warning${result.warnings.length === 1 ? "" : "s"})`);
|
|
3170
|
+
}
|
|
3171
|
+
process.exit(0);
|
|
3172
|
+
}
|
|
3173
|
+
} else {
|
|
3174
|
+
if (result.files.length === 0) {
|
|
3175
|
+
console.log(chalk12.yellow("\u26A0 No AI configuration files found."));
|
|
3176
|
+
console.log();
|
|
3177
|
+
console.log(chalk12.gray("Run 'lynxp wizard' to create a configuration."));
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
console.log(chalk12.green(`\u2713 Found ${result.files.length} configuration file${result.files.length === 1 ? "" : "s"}:`));
|
|
3181
|
+
for (const file of result.files) {
|
|
3182
|
+
console.log(chalk12.gray(` ${file}`));
|
|
3183
|
+
}
|
|
3184
|
+
console.log();
|
|
3185
|
+
if (result.errors.length > 0) {
|
|
3186
|
+
console.log(chalk12.red(`\u2717 ${result.errors.length} error${result.errors.length === 1 ? "" : "s"}:`));
|
|
3187
|
+
for (const error of result.errors) {
|
|
3188
|
+
console.log(chalk12.red(` ${error}`));
|
|
3189
|
+
}
|
|
3190
|
+
console.log();
|
|
3191
|
+
}
|
|
3192
|
+
if (result.warnings.length > 0) {
|
|
3193
|
+
console.log(chalk12.yellow(`\u26A0 ${result.warnings.length} warning${result.warnings.length === 1 ? "" : "s"}:`));
|
|
3194
|
+
for (const warning of result.warnings) {
|
|
3195
|
+
console.log(chalk12.yellow(` ${warning}`));
|
|
3196
|
+
}
|
|
3197
|
+
console.log();
|
|
3198
|
+
}
|
|
3199
|
+
if (result.valid) {
|
|
3200
|
+
console.log(chalk12.green("\u2705 Validation passed!"));
|
|
3201
|
+
} else {
|
|
3202
|
+
console.log(chalk12.red("\u274C Validation failed. Fix the errors above."));
|
|
3203
|
+
}
|
|
3204
|
+
console.log();
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
// src/commands/diff.ts
|
|
3209
|
+
import chalk13 from "chalk";
|
|
3210
|
+
import ora10 from "ora";
|
|
3211
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
3212
|
+
import { join as join11 } from "path";
|
|
3213
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3214
|
+
function computeDiff(oldText, newText) {
|
|
3215
|
+
const oldLines = oldText.split("\n");
|
|
3216
|
+
const newLines = newText.split("\n");
|
|
3217
|
+
const result = [];
|
|
3218
|
+
const lcs = longestCommonSubsequence(oldLines, newLines);
|
|
3219
|
+
let oldIndex = 0;
|
|
3220
|
+
let newIndex = 0;
|
|
3221
|
+
let lcsIndex = 0;
|
|
3222
|
+
while (oldIndex < oldLines.length || newIndex < newLines.length) {
|
|
3223
|
+
if (lcsIndex < lcs.length && oldIndex < oldLines.length && oldLines[oldIndex] === lcs[lcsIndex]) {
|
|
3224
|
+
if (newIndex < newLines.length && newLines[newIndex] === lcs[lcsIndex]) {
|
|
3225
|
+
result.push({ type: "same", line: oldLines[oldIndex] });
|
|
3226
|
+
oldIndex++;
|
|
3227
|
+
newIndex++;
|
|
3228
|
+
lcsIndex++;
|
|
3229
|
+
} else {
|
|
3230
|
+
result.push({ type: "add", line: newLines[newIndex] });
|
|
3231
|
+
newIndex++;
|
|
3232
|
+
}
|
|
3233
|
+
} else if (oldIndex < oldLines.length && (lcsIndex >= lcs.length || oldLines[oldIndex] !== lcs[lcsIndex])) {
|
|
3234
|
+
result.push({ type: "remove", line: oldLines[oldIndex] });
|
|
3235
|
+
oldIndex++;
|
|
3236
|
+
} else if (newIndex < newLines.length) {
|
|
3237
|
+
result.push({ type: "add", line: newLines[newIndex] });
|
|
3238
|
+
newIndex++;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
return result;
|
|
3242
|
+
}
|
|
3243
|
+
function longestCommonSubsequence(a, b) {
|
|
3244
|
+
const m = a.length;
|
|
3245
|
+
const n = b.length;
|
|
3246
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
3247
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
3248
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
3249
|
+
if (a[i2 - 1] === b[j2 - 1]) {
|
|
3250
|
+
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
3251
|
+
} else {
|
|
3252
|
+
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
const lcs = [];
|
|
3257
|
+
let i = m;
|
|
3258
|
+
let j = n;
|
|
3259
|
+
while (i > 0 && j > 0) {
|
|
3260
|
+
if (a[i - 1] === b[j - 1]) {
|
|
3261
|
+
lcs.unshift(a[i - 1]);
|
|
3262
|
+
i--;
|
|
3263
|
+
j--;
|
|
3264
|
+
} else if (dp[i - 1][j] > dp[i][j - 1]) {
|
|
3265
|
+
i--;
|
|
3266
|
+
} else {
|
|
3267
|
+
j--;
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
return lcs;
|
|
3271
|
+
}
|
|
3272
|
+
function formatDiff(diff, contextLines = 3) {
|
|
3273
|
+
const output = [];
|
|
3274
|
+
let lastPrintedIndex = -1;
|
|
3275
|
+
let inHunk = false;
|
|
3276
|
+
const changeIndices = diff.map((d, i) => d.type !== "same" ? i : -1).filter((i) => i !== -1);
|
|
3277
|
+
if (changeIndices.length === 0) {
|
|
3278
|
+
return chalk13.gray(" (no changes)");
|
|
3279
|
+
}
|
|
3280
|
+
for (let i = 0; i < diff.length; i++) {
|
|
3281
|
+
const item = diff[i];
|
|
3282
|
+
const nearChange = changeIndices.some((ci) => Math.abs(ci - i) <= contextLines);
|
|
3283
|
+
if (nearChange) {
|
|
3284
|
+
if (lastPrintedIndex !== -1 && i - lastPrintedIndex > 1) {
|
|
3285
|
+
output.push(chalk13.gray(" ..."));
|
|
3286
|
+
}
|
|
3287
|
+
if (item.type === "add") {
|
|
3288
|
+
output.push(chalk13.green(`+ ${item.line}`));
|
|
3289
|
+
} else if (item.type === "remove") {
|
|
3290
|
+
output.push(chalk13.red(`- ${item.line}`));
|
|
3291
|
+
} else {
|
|
3292
|
+
output.push(chalk13.gray(` ${item.line}`));
|
|
3293
|
+
}
|
|
3294
|
+
lastPrintedIndex = i;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
return output.join("\n");
|
|
3298
|
+
}
|
|
3299
|
+
function getDiffStats(diff) {
|
|
3300
|
+
return {
|
|
3301
|
+
added: diff.filter((d) => d.type === "add").length,
|
|
3302
|
+
removed: diff.filter((d) => d.type === "remove").length,
|
|
3303
|
+
unchanged: diff.filter((d) => d.type === "same").length
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
async function diffCommand(blueprintId, options = {}) {
|
|
3307
|
+
console.log();
|
|
3308
|
+
console.log(chalk13.cyan("\u{1F431} LynxPrompt Diff"));
|
|
3309
|
+
console.log();
|
|
3310
|
+
const cwd = process.cwd();
|
|
3311
|
+
if (options.local) {
|
|
3312
|
+
await diffLocal(cwd);
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
if (!blueprintId) {
|
|
3316
|
+
console.log(chalk13.red("\u2717 Please provide a blueprint ID to compare with."));
|
|
3317
|
+
console.log();
|
|
3318
|
+
console.log(chalk13.gray("Usage:"));
|
|
3319
|
+
console.log(chalk13.gray(" lynxp diff <blueprint-id> Compare local with remote blueprint"));
|
|
3320
|
+
console.log(chalk13.gray(" lynxp diff --local Compare .lynxprompt/rules/ with exports"));
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
if (!isAuthenticated()) {
|
|
3324
|
+
console.log(chalk13.yellow("\u26A0 Not logged in. Some blueprints may not be accessible."));
|
|
3325
|
+
console.log(chalk13.gray("Run 'lynxp login' to authenticate."));
|
|
3326
|
+
console.log();
|
|
3327
|
+
}
|
|
3328
|
+
const spinner = ora10("Fetching blueprint...").start();
|
|
3329
|
+
try {
|
|
3330
|
+
const { blueprint } = await api.getBlueprint(blueprintId);
|
|
3331
|
+
spinner.stop();
|
|
3332
|
+
if (!blueprint || !blueprint.content) {
|
|
3333
|
+
console.log(chalk13.red(`\u2717 Blueprint not found or has no content: ${blueprintId}`));
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
console.log(chalk13.green(`\u2713 Blueprint: ${blueprint.name || blueprintId}`));
|
|
3337
|
+
if (blueprint.description) {
|
|
3338
|
+
console.log(chalk13.gray(` ${blueprint.description}`));
|
|
3339
|
+
}
|
|
3340
|
+
console.log();
|
|
3341
|
+
const localPaths = [
|
|
3342
|
+
"AGENTS.md",
|
|
3343
|
+
"CLAUDE.md",
|
|
3344
|
+
".cursor/rules/project.mdc",
|
|
3345
|
+
".github/copilot-instructions.md",
|
|
3346
|
+
".windsurfrules"
|
|
3347
|
+
];
|
|
3348
|
+
let localContent = null;
|
|
3349
|
+
let localPath = null;
|
|
3350
|
+
for (const path of localPaths) {
|
|
3351
|
+
const fullPath = join11(cwd, path);
|
|
3352
|
+
if (existsSync8(fullPath)) {
|
|
3353
|
+
try {
|
|
3354
|
+
localContent = await readFile10(fullPath, "utf-8");
|
|
3355
|
+
localPath = path;
|
|
3356
|
+
break;
|
|
3357
|
+
} catch {
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
if (!localContent) {
|
|
3362
|
+
console.log(chalk13.yellow("\u26A0 No local AI configuration file found."));
|
|
3363
|
+
console.log(chalk13.gray("Run 'lynxp wizard' to create one, or 'lynxp pull' to download the blueprint."));
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
console.log(chalk13.gray(`Comparing with: ${localPath}`));
|
|
3367
|
+
console.log();
|
|
3368
|
+
const diff = computeDiff(blueprint.content, localContent);
|
|
3369
|
+
const stats = getDiffStats(diff);
|
|
3370
|
+
if (stats.added === 0 && stats.removed === 0) {
|
|
3371
|
+
console.log(chalk13.green("\u2713 Files are identical!"));
|
|
3372
|
+
} else {
|
|
3373
|
+
console.log(chalk13.gray("Changes (remote \u2192 local):"));
|
|
3374
|
+
console.log();
|
|
3375
|
+
console.log(formatDiff(diff));
|
|
3376
|
+
console.log();
|
|
3377
|
+
console.log(chalk13.gray(`Summary: ${chalk13.green(`+${stats.added}`)} ${chalk13.red(`-${stats.removed}`)} lines changed`));
|
|
3378
|
+
}
|
|
3379
|
+
console.log();
|
|
3380
|
+
} catch (error) {
|
|
3381
|
+
spinner.stop();
|
|
3382
|
+
if (error instanceof ApiRequestError) {
|
|
3383
|
+
if (error.statusCode === 401) {
|
|
3384
|
+
console.log(chalk13.red("\u2717 Authentication required. Run 'lynxp login' first."));
|
|
3385
|
+
} else if (error.statusCode === 404) {
|
|
3386
|
+
console.log(chalk13.red(`\u2717 Blueprint not found: ${blueprintId}`));
|
|
3387
|
+
} else if (error.statusCode === 403) {
|
|
3388
|
+
console.log(chalk13.red("\u2717 Access denied to this blueprint."));
|
|
3389
|
+
} else {
|
|
3390
|
+
console.log(chalk13.red(`\u2717 API error: ${error.message}`));
|
|
3391
|
+
}
|
|
3392
|
+
} else {
|
|
3393
|
+
console.log(chalk13.red("\u2717 Failed to fetch blueprint"));
|
|
3394
|
+
if (error instanceof Error) {
|
|
3395
|
+
console.log(chalk13.gray(` ${error.message}`));
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
async function diffLocal(cwd) {
|
|
3401
|
+
const rulesDir = join11(cwd, ".lynxprompt/rules");
|
|
3402
|
+
if (!existsSync8(rulesDir)) {
|
|
3403
|
+
console.log(chalk13.yellow("\u26A0 No .lynxprompt/rules/ directory found."));
|
|
3404
|
+
console.log(chalk13.gray("Run 'lynxp init' to set up the advanced workflow, or 'lynxp wizard' for simple file generation."));
|
|
3405
|
+
return;
|
|
3406
|
+
}
|
|
3407
|
+
console.log(chalk13.gray("Comparing .lynxprompt/rules/ with exported files..."));
|
|
3408
|
+
console.log();
|
|
3409
|
+
const rulesPath = join11(rulesDir, "agents.md");
|
|
3410
|
+
if (!existsSync8(rulesPath)) {
|
|
3411
|
+
console.log(chalk13.yellow("\u26A0 No rules files found in .lynxprompt/rules/"));
|
|
3412
|
+
return;
|
|
3413
|
+
}
|
|
3414
|
+
let rulesContent;
|
|
3415
|
+
try {
|
|
3416
|
+
rulesContent = await readFile10(rulesPath, "utf-8");
|
|
3417
|
+
} catch {
|
|
3418
|
+
console.log(chalk13.red("\u2717 Could not read .lynxprompt/rules/agents.md"));
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
const exportedFiles = [
|
|
3422
|
+
{ path: "AGENTS.md", name: "AGENTS.md" },
|
|
3423
|
+
{ path: ".cursor/rules/lynxprompt-rules.mdc", name: "Cursor" }
|
|
3424
|
+
];
|
|
3425
|
+
let hasChanges = false;
|
|
3426
|
+
for (const file of exportedFiles) {
|
|
3427
|
+
const filePath = join11(cwd, file.path);
|
|
3428
|
+
if (existsSync8(filePath)) {
|
|
3429
|
+
try {
|
|
3430
|
+
const exportedContent = await readFile10(filePath, "utf-8");
|
|
3431
|
+
let compareContent = exportedContent;
|
|
3432
|
+
if (file.path.endsWith(".mdc")) {
|
|
3433
|
+
const frontmatterEnd = exportedContent.indexOf("---", 3);
|
|
3434
|
+
if (frontmatterEnd !== -1) {
|
|
3435
|
+
compareContent = exportedContent.substring(frontmatterEnd + 3).trim();
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
compareContent = compareContent.replace(/^# AI Coding Rules\n\n> Generated by \[LynxPrompt\].*\n\n/m, "").trim();
|
|
3439
|
+
const diff = computeDiff(rulesContent.trim(), compareContent);
|
|
3440
|
+
const stats = getDiffStats(diff);
|
|
3441
|
+
if (stats.added > 0 || stats.removed > 0) {
|
|
3442
|
+
hasChanges = true;
|
|
3443
|
+
console.log(chalk13.yellow(`\u26A0 ${file.name} differs from source:`));
|
|
3444
|
+
console.log(formatDiff(diff));
|
|
3445
|
+
console.log(chalk13.gray(` ${chalk13.green(`+${stats.added}`)} ${chalk13.red(`-${stats.removed}`)} lines`));
|
|
3446
|
+
console.log();
|
|
3447
|
+
} else {
|
|
3448
|
+
console.log(chalk13.green(`\u2713 ${file.name} is in sync`));
|
|
3449
|
+
}
|
|
3450
|
+
} catch {
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
if (!hasChanges) {
|
|
3455
|
+
console.log();
|
|
3456
|
+
console.log(chalk13.green("\u2713 All exported files are in sync with .lynxprompt/rules/"));
|
|
3457
|
+
} else {
|
|
3458
|
+
console.log();
|
|
3459
|
+
console.log(chalk13.gray("Run 'lynxp sync' to update exported files from .lynxprompt/rules/"));
|
|
3460
|
+
}
|
|
3461
|
+
console.log();
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
// src/commands/link.ts
|
|
3465
|
+
import chalk14 from "chalk";
|
|
3466
|
+
import ora11 from "ora";
|
|
3467
|
+
import prompts6 from "prompts";
|
|
3468
|
+
import { join as join12 } from "path";
|
|
3469
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3470
|
+
function getSourceFromVisibility2(visibility) {
|
|
3471
|
+
switch (visibility) {
|
|
3472
|
+
case "PUBLIC":
|
|
3473
|
+
return "marketplace";
|
|
3474
|
+
case "TEAM":
|
|
3475
|
+
return "team";
|
|
3476
|
+
case "PRIVATE":
|
|
3477
|
+
return "private";
|
|
3478
|
+
default:
|
|
3479
|
+
return "marketplace";
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
async function linkCommand(file, blueprintId, options = {}) {
|
|
3483
|
+
const cwd = process.cwd();
|
|
3484
|
+
if (options.list) {
|
|
3485
|
+
await listTrackedBlueprints(cwd);
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3488
|
+
if (!file) {
|
|
3489
|
+
console.log(chalk14.red("\u2717 Please provide a file path to link."));
|
|
3490
|
+
console.log();
|
|
3491
|
+
console.log(chalk14.gray("Usage:"));
|
|
3492
|
+
console.log(chalk14.gray(" lynxp link <file> <blueprint-id> Link a local file to a cloud blueprint"));
|
|
3493
|
+
console.log(chalk14.gray(" lynxp link --list List all tracked blueprints"));
|
|
3494
|
+
console.log();
|
|
3495
|
+
console.log(chalk14.gray("Example:"));
|
|
3496
|
+
console.log(chalk14.gray(" lynxp link AGENTS.md bp_abc123"));
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
if (!blueprintId) {
|
|
3500
|
+
console.log(chalk14.red("\u2717 Please provide a blueprint ID to link to."));
|
|
3501
|
+
console.log();
|
|
3502
|
+
console.log(chalk14.gray("Usage: lynxp link <file> <blueprint-id>"));
|
|
3503
|
+
console.log(chalk14.gray("Example: lynxp link AGENTS.md bp_abc123"));
|
|
3504
|
+
console.log();
|
|
3505
|
+
console.log(chalk14.gray("To find blueprint IDs:"));
|
|
3506
|
+
console.log(chalk14.gray(" lynxp list - Show your blueprints"));
|
|
3507
|
+
console.log(chalk14.gray(" lynxp search <query> - Search marketplace"));
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3510
|
+
const filePath = join12(cwd, file);
|
|
3511
|
+
if (!existsSync9(filePath)) {
|
|
3512
|
+
console.log(chalk14.red(`\u2717 File not found: ${file}`));
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
const existing = await findBlueprintByFile(cwd, file);
|
|
3516
|
+
if (existing) {
|
|
3517
|
+
console.log(chalk14.yellow(`\u26A0 This file is already linked to: ${existing.id}`));
|
|
3518
|
+
const { proceed } = await prompts6({
|
|
3519
|
+
type: "confirm",
|
|
3520
|
+
name: "proceed",
|
|
3521
|
+
message: "Replace the existing link?",
|
|
3522
|
+
initial: false
|
|
3523
|
+
});
|
|
3524
|
+
if (!proceed) {
|
|
3525
|
+
console.log(chalk14.gray("Cancelled."));
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
if (!isAuthenticated()) {
|
|
3530
|
+
console.log(
|
|
3531
|
+
chalk14.yellow("Not logged in. Run 'lynxp login' to authenticate.")
|
|
3532
|
+
);
|
|
3533
|
+
process.exit(1);
|
|
3534
|
+
}
|
|
3535
|
+
const spinner = ora11(`Fetching blueprint ${chalk14.cyan(blueprintId)}...`).start();
|
|
3536
|
+
try {
|
|
3537
|
+
const { blueprint } = await api.getBlueprint(blueprintId);
|
|
3538
|
+
spinner.stop();
|
|
3539
|
+
const source = getSourceFromVisibility2(blueprint.visibility);
|
|
3540
|
+
const isMarketplace = source === "marketplace";
|
|
3541
|
+
console.log();
|
|
3542
|
+
console.log(chalk14.cyan(`\u{1F431} Blueprint: ${chalk14.bold(blueprint.name)}`));
|
|
3543
|
+
if (blueprint.description) {
|
|
3544
|
+
console.log(chalk14.gray(` ${blueprint.description}`));
|
|
3545
|
+
}
|
|
3546
|
+
console.log(chalk14.gray(` Visibility: ${blueprint.visibility}`));
|
|
3547
|
+
console.log();
|
|
3548
|
+
if (isMarketplace) {
|
|
3549
|
+
console.log(chalk14.yellow("\u26A0 This is a marketplace blueprint."));
|
|
3550
|
+
console.log(chalk14.gray(" Your local changes will NOT sync back to the cloud."));
|
|
3551
|
+
console.log(chalk14.gray(" To make changes, you'll need to create your own copy."));
|
|
3552
|
+
console.log();
|
|
3553
|
+
}
|
|
3554
|
+
const { confirm } = await prompts6({
|
|
3555
|
+
type: "confirm",
|
|
3556
|
+
name: "confirm",
|
|
3557
|
+
message: `Link ${chalk14.cyan(file)} to ${chalk14.cyan(blueprint.name)}?`,
|
|
3558
|
+
initial: true
|
|
3559
|
+
});
|
|
3560
|
+
if (!confirm) {
|
|
3561
|
+
console.log(chalk14.gray("Cancelled."));
|
|
3562
|
+
return;
|
|
3563
|
+
}
|
|
3564
|
+
await linkBlueprint(cwd, file, blueprint.id, blueprint.name, source);
|
|
3565
|
+
console.log();
|
|
3566
|
+
console.log(chalk14.green(`\u2705 Linked: ${file} \u2192 ${blueprint.id}`));
|
|
3567
|
+
console.log();
|
|
3568
|
+
console.log(chalk14.gray("Next steps:"));
|
|
3569
|
+
console.log(chalk14.gray(` \u2022 Run 'lynxp pull ${blueprintId}' to update local file from cloud`));
|
|
3570
|
+
console.log(chalk14.gray(` \u2022 Run 'lynxp diff ${blueprintId}' to see differences`));
|
|
3571
|
+
console.log(chalk14.gray(` \u2022 Run 'lynxp status' to see all tracked blueprints`));
|
|
3572
|
+
if (!isMarketplace) {
|
|
3573
|
+
console.log(chalk14.gray(` \u2022 Run 'lynxp push ${file}' to push local changes to cloud`));
|
|
3574
|
+
}
|
|
3575
|
+
console.log();
|
|
3576
|
+
} catch (error) {
|
|
3577
|
+
spinner.stop();
|
|
3578
|
+
if (error instanceof ApiRequestError) {
|
|
3579
|
+
if (error.statusCode === 404) {
|
|
3580
|
+
console.log(chalk14.red(`\u2717 Blueprint not found: ${blueprintId}`));
|
|
3581
|
+
console.log(chalk14.gray(" Make sure the ID is correct. Use 'lynxp list' or 'lynxp search' to find blueprints."));
|
|
3582
|
+
} else if (error.statusCode === 403) {
|
|
3583
|
+
console.log(chalk14.red("\u2717 You don't have access to this blueprint."));
|
|
3584
|
+
} else {
|
|
3585
|
+
console.log(chalk14.red(`\u2717 Error: ${error.message}`));
|
|
3586
|
+
}
|
|
3587
|
+
} else {
|
|
3588
|
+
console.log(chalk14.red("\u2717 An unexpected error occurred."));
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
async function unlinkCommand(file) {
|
|
3593
|
+
const cwd = process.cwd();
|
|
3594
|
+
if (!file) {
|
|
3595
|
+
console.log(chalk14.red("\u2717 Please provide a file path to unlink."));
|
|
3596
|
+
console.log();
|
|
3597
|
+
console.log(chalk14.gray("Usage: lynxp unlink <file>"));
|
|
3598
|
+
console.log(chalk14.gray("Example: lynxp unlink AGENTS.md"));
|
|
3599
|
+
return;
|
|
3600
|
+
}
|
|
3601
|
+
const tracked = await findBlueprintByFile(cwd, file);
|
|
3602
|
+
if (!tracked) {
|
|
3603
|
+
console.log(chalk14.yellow(`\u26A0 File is not linked to any blueprint: ${file}`));
|
|
3604
|
+
return;
|
|
3605
|
+
}
|
|
3606
|
+
console.log();
|
|
3607
|
+
console.log(chalk14.cyan(`Currently linked to: ${tracked.id}`));
|
|
3608
|
+
console.log(chalk14.gray(` Name: ${tracked.name}`));
|
|
3609
|
+
console.log(chalk14.gray(` Source: ${tracked.source}`));
|
|
3610
|
+
console.log();
|
|
3611
|
+
const { confirm } = await prompts6({
|
|
3612
|
+
type: "confirm",
|
|
3613
|
+
name: "confirm",
|
|
3614
|
+
message: `Unlink ${chalk14.cyan(file)} from ${chalk14.cyan(tracked.name)}?`,
|
|
3615
|
+
initial: true
|
|
3616
|
+
});
|
|
3617
|
+
if (!confirm) {
|
|
3618
|
+
console.log(chalk14.gray("Cancelled."));
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
const success = await untrackBlueprint(cwd, file);
|
|
3622
|
+
if (success) {
|
|
3623
|
+
console.log();
|
|
3624
|
+
console.log(chalk14.green(`\u2705 Unlinked: ${file}`));
|
|
3625
|
+
console.log(chalk14.gray(" The file is now a standalone local file."));
|
|
3626
|
+
console.log(chalk14.gray(" It will no longer receive updates from the cloud blueprint."));
|
|
3627
|
+
console.log();
|
|
3628
|
+
} else {
|
|
3629
|
+
console.log(chalk14.red("\u2717 Failed to unlink file."));
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
async function listTrackedBlueprints(cwd) {
|
|
3633
|
+
console.log();
|
|
3634
|
+
console.log(chalk14.cyan("\u{1F431} Tracked Blueprints"));
|
|
3635
|
+
console.log();
|
|
3636
|
+
const status = await checkSyncStatus(cwd);
|
|
3637
|
+
if (status.length === 0) {
|
|
3638
|
+
console.log(chalk14.gray("No blueprints are currently tracked."));
|
|
3639
|
+
console.log();
|
|
3640
|
+
console.log(chalk14.gray("To track a blueprint:"));
|
|
3641
|
+
console.log(chalk14.gray(" lynxp pull <blueprint-id> Download and track a blueprint"));
|
|
3642
|
+
console.log(chalk14.gray(" lynxp link <file> <id> Link an existing file to a blueprint"));
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3645
|
+
for (const { blueprint, localModified, fileExists: fileExists2 } of status) {
|
|
3646
|
+
const statusIcon = !fileExists2 ? chalk14.red("\u2717") : localModified ? chalk14.yellow("\u25CF") : chalk14.green("\u2713");
|
|
3647
|
+
const sourceLabel = {
|
|
3648
|
+
marketplace: chalk14.gray("[marketplace]"),
|
|
3649
|
+
team: chalk14.blue("[team]"),
|
|
3650
|
+
private: chalk14.green("[private]"),
|
|
3651
|
+
local: chalk14.gray("[local]")
|
|
3652
|
+
}[blueprint.source];
|
|
3653
|
+
console.log(`${statusIcon} ${chalk14.cyan(blueprint.file)}`);
|
|
3654
|
+
console.log(` ${sourceLabel} ${blueprint.name}`);
|
|
3655
|
+
console.log(` ${chalk14.gray(`ID: ${blueprint.id}`)}`);
|
|
3656
|
+
if (!fileExists2) {
|
|
3657
|
+
console.log(chalk14.red(` \u26A0 File not found`));
|
|
3658
|
+
} else if (localModified) {
|
|
3659
|
+
console.log(chalk14.yellow(` \u26A0 Local changes detected`));
|
|
3660
|
+
}
|
|
3661
|
+
console.log();
|
|
3662
|
+
}
|
|
3663
|
+
console.log(chalk14.gray("Legend:"));
|
|
3664
|
+
console.log(chalk14.gray(` ${chalk14.green("\u2713")} In sync ${chalk14.yellow("\u25CF")} Modified locally ${chalk14.red("\u2717")} Missing`));
|
|
3665
|
+
console.log();
|
|
2437
3666
|
}
|
|
2438
3667
|
|
|
2439
3668
|
// src/index.ts
|
|
2440
3669
|
var program = new Command();
|
|
2441
|
-
program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version("0.
|
|
3670
|
+
program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version("0.2.0");
|
|
3671
|
+
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);
|
|
3672
|
+
program.command("check").description("Validate AI configuration files (for CI/CD)").option("--ci", "CI mode - exit codes only (0=pass, 1=fail)").action(checkCommand);
|
|
3673
|
+
program.command("status").description("Show current AI configuration and tracked blueprints").action(statusCommand);
|
|
3674
|
+
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);
|
|
3675
|
+
program.command("search <query>").description("Search public blueprints in the marketplace").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
|
|
3676
|
+
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);
|
|
3677
|
+
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);
|
|
3678
|
+
program.command("unlink <file>").description("Disconnect a local file from its cloud blueprint").action(unlinkCommand);
|
|
3679
|
+
program.command("diff [blueprint-id]").description("Show changes between local and remote blueprint").option("--local", "Compare .lynxprompt/rules/ with exported files").action(diffCommand);
|
|
3680
|
+
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);
|
|
3681
|
+
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);
|
|
3682
|
+
program.command("agents [action] [agent]").description("Manage AI agents (list, enable, disable, detect)").option("-i, --interactive", "Interactive agent selection").action(agentsCommand);
|
|
2442
3683
|
program.command("login").description("Authenticate with LynxPrompt (opens browser)").action(loginCommand);
|
|
2443
3684
|
program.command("logout").description("Log out and remove stored credentials").action(logoutCommand);
|
|
2444
3685
|
program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
|
|
2445
|
-
program.command("init").description("Initialize LynxPrompt in this directory (auto-detects existing files)").option("-y, --yes", "Skip prompts and use defaults").option("-f, --force", "Re-initialize even if already initialized").action(initCommand);
|
|
2446
|
-
program.command("wizard").description("Interactive wizard to generate AI IDE configuration").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("-s, --stack <stack>", "Tech stack (comma-separated)").option("-p, --platforms <platforms>", "Target platforms (comma-separated)").option("--persona <persona>", "AI persona/role").option("--boundaries <level>", "Boundary preset (conservative, standard, permissive)").option("--preset <preset>", "Use an agent preset (test-agent, docs-agent, etc.)").option("-y, --yes", "Skip prompts and use defaults").action(wizardCommand);
|
|
2447
|
-
program.command("list").description("List your blueprints").option("-l, --limit <number>", "Number of results", "20").option("-v, --visibility <visibility>", "Filter by visibility (PRIVATE, TEAM, PUBLIC, all)").action(listCommand);
|
|
2448
|
-
program.command("pull <id>").description("Download a blueprint to the current directory").option("-o, --output <path>", "Output directory", ".").option("-y, --yes", "Overwrite existing files without prompting").action(pullCommand);
|
|
2449
|
-
program.command("search <query>").description("Search public blueprints").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
|
|
2450
|
-
program.command("status").description("Show current AI config status in this directory").action(statusCommand);
|
|
2451
|
-
program.command("sync").description("Sync rules to all configured AI agents").option("--dry-run", "Preview changes without writing files").option("-f, --force", "Skip prompts (for CI/automation)").action(syncCommand);
|
|
2452
|
-
program.command("agents [action] [agent]").description("Manage AI agents (list, enable, disable, detect)").option("-i, --interactive", "Interactive agent selection").action(agentsCommand);
|
|
2453
3686
|
program.addHelpText(
|
|
2454
3687
|
"beforeAll",
|
|
2455
3688
|
`
|
|
2456
|
-
${
|
|
2457
|
-
${
|
|
3689
|
+
${chalk15.cyan("\u{1F431} LynxPrompt CLI")} ${chalk15.gray("(also available as: lynxp)")}
|
|
3690
|
+
${chalk15.gray("Generate AI IDE configuration files from your terminal")}
|
|
2458
3691
|
`
|
|
2459
3692
|
);
|
|
2460
3693
|
program.addHelpText(
|
|
2461
3694
|
"after",
|
|
2462
3695
|
`
|
|
2463
|
-
${
|
|
2464
|
-
${
|
|
2465
|
-
${
|
|
2466
|
-
${
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
${
|
|
2470
|
-
|
|
2471
|
-
${
|
|
3696
|
+
${chalk15.cyan("Quick Start:")}
|
|
3697
|
+
${chalk15.white("$ lynxp wizard")} ${chalk15.gray("Generate config interactively")}
|
|
3698
|
+
${chalk15.white("$ lynxp wizard -y")} ${chalk15.gray("Generate AGENTS.md with defaults")}
|
|
3699
|
+
${chalk15.white("$ lynxp wizard -f cursor")} ${chalk15.gray("Generate .cursor/rules/")}
|
|
3700
|
+
|
|
3701
|
+
${chalk15.cyan("Marketplace:")}
|
|
3702
|
+
${chalk15.white("$ lynxp search nextjs")} ${chalk15.gray("Search blueprints")}
|
|
3703
|
+
${chalk15.white("$ lynxp pull bp_abc123")} ${chalk15.gray("Download and track a blueprint")}
|
|
3704
|
+
${chalk15.white("$ lynxp link --list")} ${chalk15.gray("Show tracked blueprints")}
|
|
3705
|
+
|
|
3706
|
+
${chalk15.cyan("Blueprint Tracking:")}
|
|
3707
|
+
${chalk15.white("$ lynxp link AGENTS.md bp_xyz")} ${chalk15.gray("Link existing file to blueprint")}
|
|
3708
|
+
${chalk15.white("$ lynxp unlink AGENTS.md")} ${chalk15.gray("Disconnect from cloud")}
|
|
3709
|
+
${chalk15.white("$ lynxp diff bp_abc123")} ${chalk15.gray("Show changes vs cloud version")}
|
|
3710
|
+
|
|
3711
|
+
${chalk15.cyan("CI/CD:")}
|
|
3712
|
+
${chalk15.white("$ lynxp check --ci")} ${chalk15.gray("Validate config (exit code)")}
|
|
3713
|
+
|
|
3714
|
+
${chalk15.gray("Docs: https://lynxprompt.com/docs/cli")}
|
|
2472
3715
|
`
|
|
2473
3716
|
);
|
|
2474
3717
|
program.parse();
|