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