lynxprompt 2.1.39 → 2.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk20 from "chalk";
5
+ import chalk21 from "chalk";
6
6
 
7
7
  // src/commands/login.ts
8
8
  import chalk from "chalk";
@@ -32,8 +32,7 @@ var config = new Conf({
32
32
  properties: {
33
33
  id: { type: "string" },
34
34
  email: { type: "string" },
35
- name: { type: ["string", "null"] },
36
- plan: { type: "string" }
35
+ name: { type: ["string", "null"] }
37
36
  }
38
37
  }
39
38
  },
@@ -279,12 +278,17 @@ var ApiClient = class {
279
278
  `/api/v1/hierarchies${query ? `?${query}` : ""}`
280
279
  );
281
280
  }
282
- async getHierarchy(id) {
281
+ /**
282
+ * A hierarchy and its blueprints. With includeContent the server returns each
283
+ * blueprint's content in the list; a server older than that leaves it out.
284
+ */
285
+ async getHierarchy(id, { includeContent = false } = {}) {
283
286
  let apiId = id;
284
287
  if (!id.startsWith("ha_")) {
285
288
  apiId = `ha_${id}`;
286
289
  }
287
- return this.request(`/api/v1/hierarchies/${apiId}`);
290
+ const query = includeContent ? "?include=content" : "";
291
+ return this.request(`/api/v1/hierarchies/${apiId}${query}`);
288
292
  }
289
293
  async createHierarchy(data) {
290
294
  return this.request("/api/v1/hierarchies", {
@@ -534,7 +538,7 @@ function printOtherLogins() {
534
538
  // src/commands/whoami.ts
535
539
  import chalk3 from "chalk";
536
540
  import ora2 from "ora";
537
- async function whoamiCommand() {
541
+ async function whoamiCommand(options = {}) {
538
542
  if (!isAuthenticated()) {
539
543
  console.log(chalk3.yellow("Not logged in. Run 'lynxprompt login' to authenticate."));
540
544
  process.exit(1);
@@ -546,9 +550,12 @@ async function whoamiCommand() {
546
550
  setUser({
547
551
  id: user.id,
548
552
  email: user.email,
549
- name: user.name,
550
- plan: user.plan
553
+ name: user.name
551
554
  });
555
+ if (options.json) {
556
+ console.log(JSON.stringify({ user }, null, 2));
557
+ return;
558
+ }
552
559
  console.log();
553
560
  console.log(chalk3.cyan("\u{1F431} LynxPrompt Account"));
554
561
  console.log();
@@ -591,11 +598,16 @@ async function listCommand(options) {
591
598
  const spinner = ora3("Fetching your blueprints...").start();
592
599
  try {
593
600
  const limit = parseInt(options.limit, 10) || 20;
594
- const { blueprints, total, has_more } = await api.listBlueprints({
601
+ const response = await api.listBlueprints({
595
602
  limit,
596
603
  visibility: options.visibility
597
604
  });
598
605
  spinner.stop();
606
+ if (options.json) {
607
+ console.log(JSON.stringify(response, null, 2));
608
+ return;
609
+ }
610
+ const { blueprints, total, has_more } = response;
599
611
  if (blueprints.length === 0) {
600
612
  console.log();
601
613
  console.log(chalk4.yellow("No blueprints found."));
@@ -735,7 +747,11 @@ async function trackBlueprint(cwd, blueprint) {
735
747
  canPull,
736
748
  hierarchyId: blueprint.hierarchyId,
737
749
  hierarchyName: blueprint.hierarchyName,
738
- repositoryPath: blueprint.repositoryPath
750
+ repositoryPath: blueprint.repositoryPath,
751
+ ...blueprint.variables ? {
752
+ variables: blueprint.variables,
753
+ localChecksum: calculateChecksum(blueprint.localContent ?? blueprint.content)
754
+ } : {}
739
755
  });
740
756
  await saveBlueprints(cwd, config2);
741
757
  }
@@ -748,7 +764,7 @@ async function hasLocalChanges(cwd, tracked) {
748
764
  const filePath = join(cwd, tracked.file);
749
765
  const content = await readFile(filePath, "utf-8");
750
766
  const currentChecksum = calculateChecksum(content);
751
- return currentChecksum !== tracked.checksum;
767
+ return currentChecksum !== (tracked.localChecksum ?? tracked.checksum);
752
768
  } catch {
753
769
  return false;
754
770
  }
@@ -758,10 +774,15 @@ async function updateChecksum(cwd, file, content) {
758
774
  const blueprint = config2.blueprints.find((b) => sameFile(cwd, b.file, file));
759
775
  if (blueprint) {
760
776
  blueprint.checksum = calculateChecksum(content);
777
+ delete blueprint.variables;
778
+ delete blueprint.localChecksum;
761
779
  blueprint.pulledAt = (/* @__PURE__ */ new Date()).toISOString();
762
780
  await saveBlueprints(cwd, config2);
763
781
  }
764
782
  }
783
+ function conflictFilePath(cwd, file) {
784
+ return join(cwd, `${file}.remote`);
785
+ }
765
786
  async function untrackBlueprint(cwd, file) {
766
787
  const config2 = await loadBlueprints(cwd);
767
788
  const initialCount = config2.blueprints.length;
@@ -846,6 +867,7 @@ var PLATFORMS = [
846
867
  name: "Claude Code",
847
868
  file: "CLAUDE.md",
848
869
  blueprintType: "CLAUDE_MD",
870
+ importsFiles: true,
849
871
  icon: "\u{1F9E0}",
850
872
  gradient: "from-orange-500 to-amber-500",
851
873
  note: "Anthropic's agentic coding tool",
@@ -1139,6 +1161,7 @@ var PLATFORMS = [
1139
1161
  name: "Gemini CLI",
1140
1162
  file: "GEMINI.md",
1141
1163
  blueprintType: "GEMINI_MD",
1164
+ importsFiles: true,
1142
1165
  icon: "\u{1F48E}",
1143
1166
  gradient: "from-blue-500 to-cyan-500",
1144
1167
  note: "Google's Gemini in terminal",
@@ -1183,6 +1206,18 @@ var PLATFORMS = [
1183
1206
  category: "other",
1184
1207
  format: "markdown"
1185
1208
  },
1209
+ {
1210
+ id: "devin",
1211
+ name: "Devin",
1212
+ file: ".devin/rules/project.md",
1213
+ rulesDirectory: ".devin/rules/",
1214
+ icon: "\u{1F6E0}\uFE0F",
1215
+ gradient: "from-emerald-600 to-teal-700",
1216
+ note: "Cognition's AI software engineer",
1217
+ category: "other",
1218
+ format: "markdown",
1219
+ url: "https://devin.ai"
1220
+ },
1186
1221
  {
1187
1222
  id: "firebender",
1188
1223
  name: "Firebender",
@@ -1338,6 +1373,53 @@ var COMMANDS = [
1338
1373
  note: "Custom commands for OpenCode TUI"
1339
1374
  }
1340
1375
  ];
1376
+ var SKILL_BLUEPRINT_TYPE = "AGENT_SKILL";
1377
+ var SKILL_DIRECTORIES = [
1378
+ ".claude/skills",
1379
+ ".agents/skills",
1380
+ ".github/skills",
1381
+ ".cursor/skills",
1382
+ ".gemini/skills",
1383
+ ".opencode/skills"
1384
+ ];
1385
+ function skillFolder(bp) {
1386
+ const lines = (bp.content ?? "").split("\n").map((l) => l.trimEnd());
1387
+ const end = lines[0] === "---" ? lines.indexOf("---", 1) : -1;
1388
+ const nameLine = end > 0 ? lines.slice(1, end).find((l) => l.startsWith("name:")) : void 0;
1389
+ const name = nameLine?.slice("name:".length).trim().replace(/^["']|["']$/g, "");
1390
+ const slug = (name || bp.name || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").split("-").filter(Boolean).join("-");
1391
+ return slug || "skill";
1392
+ }
1393
+ var MCP_BLUEPRINT_TYPE = "MCP_CONFIG";
1394
+ var MCP_CONFIGS = [
1395
+ { id: "claude-mcp", name: "Claude Code", file: ".mcp.json", key: "mcpServers" },
1396
+ { id: "cursor-mcp", name: "Cursor", file: ".cursor/mcp.json", key: "mcpServers" },
1397
+ { id: "vscode-mcp", name: "VS Code (Copilot)", file: ".vscode/mcp.json", key: "servers" },
1398
+ { id: "kiro-mcp", name: "Kiro", file: ".kiro/settings/mcp.json", key: "mcpServers" }
1399
+ ];
1400
+ function convertMcpConfig(content, targetId) {
1401
+ const target = MCP_CONFIGS.find((c) => c.id === targetId);
1402
+ if (!target) throw new Error(`Unknown MCP target: ${targetId}`);
1403
+ const source = JSON.parse(content);
1404
+ const servers = source.mcpServers ?? source.servers;
1405
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
1406
+ throw new Error('No "mcpServers" or "servers" object in the MCP config');
1407
+ }
1408
+ const out = {};
1409
+ if (target.key === "servers") {
1410
+ out.servers = Object.fromEntries(
1411
+ Object.entries(servers).map(([name, server]) => [
1412
+ name,
1413
+ server.type ? server : { type: server.url ? "http" : "stdio", ...server }
1414
+ ])
1415
+ );
1416
+ if (source.inputs) out.inputs = source.inputs;
1417
+ } else {
1418
+ out[target.key] = servers;
1419
+ }
1420
+ return `${JSON.stringify(out, null, 2)}
1421
+ `;
1422
+ }
1341
1423
  function getPlatform(id) {
1342
1424
  return PLATFORMS.find((p) => p.id === id);
1343
1425
  }
@@ -1364,6 +1446,17 @@ var BLUEPRINT_TYPE_ALIASES = {
1364
1446
  function platformForBlueprintType(type) {
1365
1447
  return PLATFORMS.find((p) => p.blueprintType === type)?.id ?? BLUEPRINT_TYPE_ALIASES[type];
1366
1448
  }
1449
+ var AGENTS_MD_POINTER = "@AGENTS.md\n";
1450
+ function agentsMdPointerFiles(targets) {
1451
+ const pointers = /* @__PURE__ */ new Set();
1452
+ if (!targets.some((t) => t.file === "AGENTS.md")) return pointers;
1453
+ for (const { file } of targets) {
1454
+ if (file === "AGENTS.md") continue;
1455
+ const readers = targets.filter((t) => t.file === file);
1456
+ if (readers.every((t) => getPlatform(t.platform)?.importsFiles)) pointers.add(file);
1457
+ }
1458
+ return pointers;
1459
+ }
1367
1460
  var COMMAND_COUNT = COMMANDS.length;
1368
1461
 
1369
1462
  // src/utils/generator.ts
@@ -1853,11 +1946,11 @@ var STATIC_FILE_PATHS = {
1853
1946
  };
1854
1947
  function generateConfig(options) {
1855
1948
  const files = {};
1856
- for (const platform2 of options.platforms) {
1857
- const filename = PLATFORM_FILES2[platform2];
1858
- if (filename) {
1859
- files[filename] = generateFileContent(options, platform2);
1860
- }
1949
+ const platforms = options.platforms.filter((platform2) => PLATFORM_FILES2[platform2]);
1950
+ const pointers = options.pointerFiles ? agentsMdPointerFiles(platforms.map((platform2) => ({ platform: platform2, file: PLATFORM_FILES2[platform2] }))) : /* @__PURE__ */ new Set();
1951
+ for (const platform2 of platforms) {
1952
+ const file = PLATFORM_FILES2[platform2];
1953
+ files[file] = pointers.has(file) ? AGENTS_MD_POINTER : generateFileContent(options, platform2);
1861
1954
  }
1862
1955
  const createLocalStaticFiles = options.staticFileHandling !== "config_only";
1863
1956
  if (options.staticFiles && options.staticFiles.length > 0 && createLocalStaticFiles) {
@@ -1920,6 +2013,8 @@ function generateFileContent(options, platform2) {
1920
2013
  sections.push("---", "inclusion: always", "---", "");
1921
2014
  } else if (platform2 === "augment") {
1922
2015
  sections.push("---", "type: always_apply", "---", "");
2016
+ } else if (platform2 === "devin") {
2017
+ sections.push("---", "trigger: always_on", "---", "");
1923
2018
  }
1924
2019
  sections.push(`# ${projectName} - AI Assistant Configuration`);
1925
2020
  sections.push("");
@@ -2912,6 +3007,8 @@ function slugify(name) {
2912
3007
  }
2913
3008
  function blueprintFilename(bp) {
2914
3009
  if (bp.repository_path) return bp.repository_path;
3010
+ if (bp.type === SKILL_BLUEPRINT_TYPE) return `${SKILL_DIRECTORIES[0]}/${skillFolder(bp)}/SKILL.md`;
3011
+ if (bp.type === MCP_BLUEPRINT_TYPE) return MCP_CONFIGS[0].file;
2915
3012
  const platform2 = platformForBlueprintType(bp.type);
2916
3013
  if (platform2 && PLATFORM_FILES2[platform2]) return PLATFORM_FILES2[platform2];
2917
3014
  const command = COMMANDS.find((c) => c.id === platform2);
@@ -2940,11 +3037,28 @@ var atPath = (relativePath, file) => {
2940
3037
  const p = normPath(relativePath);
2941
3038
  return p === file || p.endsWith(`/${file}`);
2942
3039
  };
3040
+ var inSkillFolder = (relativePath, dir) => {
3041
+ const parts = normPath(relativePath).split("/");
3042
+ const n = parts.length;
3043
+ return n >= 4 && parts[n - 1] === "skill.md" && parts.slice(n - 4, n - 2).join("/") === dir;
3044
+ };
2943
3045
  var inFolder = (relativePath, folder) => {
2944
3046
  const p = normPath(relativePath);
2945
3047
  return p.startsWith(folder) || p.includes(`/${folder}`);
2946
3048
  };
2947
3049
  var CONFIG_PATTERNS = [
3050
+ // Agent Skills: <tool>/skills/<skill name>/SKILL.md
3051
+ ...SKILL_DIRECTORIES.map((dir) => ({
3052
+ match: (p) => inSkillFolder(p, dir),
3053
+ type: SKILL_BLUEPRINT_TYPE,
3054
+ label: "Agent Skill"
3055
+ })),
3056
+ // MCP server configs
3057
+ ...MCP_CONFIGS.map((c) => ({
3058
+ match: (p) => atPath(p, c.file),
3059
+ type: MCP_BLUEPRINT_TYPE,
3060
+ label: `MCP Servers (${c.name})`
3061
+ })),
2948
3062
  // Commands: one file per command in the tool's folder
2949
3063
  ...COMMANDS.map((c) => ({
2950
3064
  match: (p) => inFolder(p, `${c.directory}/`) && p.endsWith(c.extension),
@@ -2981,7 +3095,35 @@ function matchConfigFile(relativePath) {
2981
3095
  return null;
2982
3096
  }
2983
3097
 
3098
+ // src/utils/variables.ts
3099
+ var VARIABLE_PATTERN_SOURCE = String.raw`\[\[([A-Za-z_][A-Za-z0-9_]*)(?:\|((?:(?!\[\[)[\s\S])*?)\]\](?!\])|\]\])`;
3100
+ function fillBlueprintVariables(content, values) {
3101
+ const missing = /* @__PURE__ */ new Set();
3102
+ const filled = content.replace(
3103
+ new RegExp(VARIABLE_PATTERN_SOURCE, "g"),
3104
+ (match, name, fallback) => {
3105
+ const key = name.toUpperCase();
3106
+ if (Object.prototype.hasOwnProperty.call(values, key)) return values[key];
3107
+ if (fallback !== void 0) return fallback;
3108
+ missing.add(key);
3109
+ return match;
3110
+ }
3111
+ );
3112
+ return { content: filled, missing: [...missing] };
3113
+ }
3114
+
2984
3115
  // src/commands/pull.ts
3116
+ function contentToWrite(content, options, label) {
3117
+ if (options.vars === void 0) return content;
3118
+ const { content: filled, missing } = fillBlueprintVariables(content, parseVariablesString(options.vars));
3119
+ if (missing.length > 0) {
3120
+ console.log(chalk5.yellow(` \u26A0 ${label}: no value for ${missing.join(", ")} (left as [[...]])`));
3121
+ }
3122
+ return filled;
3123
+ }
3124
+ function pulledVariables(options, written, content) {
3125
+ return options.vars === void 0 || written === content ? void 0 : parseVariablesString(options.vars);
3126
+ }
2985
3127
  function getSourceFromVisibility(visibility) {
2986
3128
  switch (visibility) {
2987
3129
  case "PUBLIC":
@@ -2997,24 +3139,26 @@ function getSourceFromVisibility(visibility) {
2997
3139
  function isHierarchyId(id) {
2998
3140
  return id.startsWith("ha_");
2999
3141
  }
3000
- async function pullCommand(id, options) {
3142
+ async function pullCommand(ids, options) {
3001
3143
  if (!isAuthenticated()) {
3002
3144
  console.log(
3003
3145
  chalk5.yellow("Not logged in. Run 'lynxp login' to authenticate.")
3004
3146
  );
3005
3147
  process.exit(1);
3006
3148
  }
3007
- if (isHierarchyId(id)) {
3008
- await pullHierarchy(id, options);
3009
- } else {
3010
- await pullBlueprint(id, options);
3149
+ for (const id of Array.isArray(ids) ? ids : [ids]) {
3150
+ if (isHierarchyId(id)) {
3151
+ await pullHierarchy(id, options);
3152
+ } else {
3153
+ await pullBlueprint(id, options);
3154
+ }
3011
3155
  }
3012
3156
  }
3013
3157
  async function pullHierarchy(id, options) {
3014
3158
  const cwd = process.cwd();
3015
3159
  const spinner = ora4(`Fetching hierarchy ${chalk5.cyan(id)}...`).start();
3016
3160
  try {
3017
- const response = await api.getHierarchy(id);
3161
+ const response = await api.getHierarchy(id, { includeContent: !options.preview });
3018
3162
  const { hierarchy, blueprints } = response;
3019
3163
  spinner.stop();
3020
3164
  console.log();
@@ -3077,7 +3221,7 @@ async function pullHierarchy(id, options) {
3077
3221
  continue;
3078
3222
  }
3079
3223
  }
3080
- const { blueprint } = await api.getBlueprint(bp.id);
3224
+ const blueprint = typeof bp.content === "string" ? bp : (await api.getBlueprint(bp.id)).blueprint;
3081
3225
  if (!blueprint.content) {
3082
3226
  console.log(chalk5.yellow(` \u26A0 No content: ${filename}`));
3083
3227
  skipped++;
@@ -3087,7 +3231,8 @@ async function pullHierarchy(id, options) {
3087
3231
  if (dir !== "." && dir !== options.output) {
3088
3232
  await mkdir2(dir, { recursive: true });
3089
3233
  }
3090
- await writeFile2(outputPath, blueprint.content, "utf-8");
3234
+ const written = contentToWrite(blueprint.content, options, filename);
3235
+ await writeFile2(outputPath, written, "utf-8");
3091
3236
  if (options.track !== false) {
3092
3237
  const source = getSourceFromVisibility(blueprint.visibility);
3093
3238
  await trackBlueprint(cwd, {
@@ -3099,7 +3244,9 @@ async function pullHierarchy(id, options) {
3099
3244
  source,
3100
3245
  hierarchyId: id,
3101
3246
  hierarchyName: hierarchy.name,
3102
- repositoryPath: bp.repository_path || void 0
3247
+ repositoryPath: bp.repository_path || void 0,
3248
+ localContent: written,
3249
+ variables: pulledVariables(options, written, blueprint.content)
3103
3250
  });
3104
3251
  }
3105
3252
  console.log(chalk5.green(` \u2713 ${filename}`));
@@ -3268,7 +3415,8 @@ async function pullBlueprint(id, options) {
3268
3415
  if (dir !== ".") {
3269
3416
  await mkdir2(dir, { recursive: true });
3270
3417
  }
3271
- await writeFile2(outputPath, blueprint.content, "utf-8");
3418
+ const written = contentToWrite(blueprint.content, options, filename);
3419
+ await writeFile2(outputPath, written, "utf-8");
3272
3420
  if (options.track !== false) {
3273
3421
  await trackBlueprint(cwd, {
3274
3422
  id: blueprint.id,
@@ -3277,7 +3425,9 @@ async function pullBlueprint(id, options) {
3277
3425
  content: blueprint.content,
3278
3426
  source,
3279
3427
  hierarchyId: blueprint.hierarchy_id || void 0,
3280
- repositoryPath: blueprint.repository_path || void 0
3428
+ repositoryPath: blueprint.repository_path || void 0,
3429
+ localContent: written,
3430
+ variables: pulledVariables(options, written, blueprint.content)
3281
3431
  });
3282
3432
  }
3283
3433
  console.log(chalk5.green(`\u2705 Downloaded: ${chalk5.bold(outputPath)}`));
@@ -3286,8 +3436,8 @@ async function pullBlueprint(id, options) {
3286
3436
  if (blueprint.content_checksum) {
3287
3437
  console.log(chalk5.gray(` Checksum: ${blueprint.content_checksum}`));
3288
3438
  }
3289
- if (isMarketplace) {
3290
- console.log(chalk5.gray(` Updates: Run 'lynxp pull ${id}' to sync updates`));
3439
+ if (isMarketplace || options.vars !== void 0) {
3440
+ console.log(chalk5.gray(` Updates: Run 'lynxp sync ${trackedFile}' to get cloud updates`));
3291
3441
  } else {
3292
3442
  console.log(chalk5.gray(` Sync: Run 'lynxp push ${trackedFile}' to push changes`));
3293
3443
  }
@@ -3629,11 +3779,26 @@ async function pushCommand(fileArg, options) {
3629
3779
  const filename = path2.basename(file);
3630
3780
  const linked = await findBlueprintByFile(cwd, file);
3631
3781
  if (linked) {
3782
+ const refusal = refusePush(cwd, linked, options);
3783
+ if (refusal) {
3784
+ console.log(chalk6.yellow(`\u26A0 Not pushed: ${refusal}`));
3785
+ process.exit(1);
3786
+ }
3632
3787
  await updateBlueprint(cwd, file, linked.id, content, options, linked.checksum);
3633
3788
  } else {
3634
3789
  await createOrLinkBlueprint(cwd, file, filename, content, options, fileArg !== void 0);
3635
3790
  }
3636
3791
  }
3792
+ function refusePush(cwd, tracked, options) {
3793
+ if (options.force) return null;
3794
+ if (fs.existsSync(conflictFilePath(cwd, tracked.file))) {
3795
+ return `${tracked.file} has an unresolved sync conflict. Merge ${tracked.file}.remote into it and delete ${tracked.file}.remote first, or use --force.`;
3796
+ }
3797
+ if (tracked.variables) {
3798
+ return `${tracked.file} was pulled with --vars, so pushing it would replace the blueprint's variables with your values. Use --force to push it anyway.`;
3799
+ }
3800
+ return null;
3801
+ }
3637
3802
  async function updateBlueprint(cwd, file, blueprintId, content, options, expectedChecksum) {
3638
3803
  console.log(chalk6.cyan(`
3639
3804
  \u{1F4E4} Updating blueprint ${chalk6.bold(blueprintId)}...`));
@@ -3924,6 +4089,11 @@ async function pushHierarchy(cwd, files, options) {
3924
4089
  const content = fs.readFileSync(file.absolutePath, "utf-8");
3925
4090
  const tracked = await findBlueprintByFile(cwd, file.path);
3926
4091
  if (tracked) {
4092
+ const refusal = refusePush(cwd, tracked, options);
4093
+ if (refusal) {
4094
+ spinner.warn(`${file.path} \u2192 skipped: ${refusal}`);
4095
+ continue;
4096
+ }
3927
4097
  const updateData = { content };
3928
4098
  if (tracked.checksum && !options.force) {
3929
4099
  updateData.expected_checksum = tracked.checksum;
@@ -4050,19 +4220,19 @@ function inferBlueprintType(filePath) {
4050
4220
  function confirmSensitive(options, spinner) {
4051
4221
  return async (matches) => {
4052
4222
  spinner?.stop();
4053
- console.log(chalk6.yellow("\n\u26A0 This blueprint is public and may contain secrets:"));
4223
+ console.log(chalk6.yellow("\n\u26A0 This blueprint may contain secrets:"));
4054
4224
  for (const m of matches) {
4055
4225
  console.log(chalk6.gray(` line ${m.line}: ${m.type}${m.snippet ? ` (${m.snippet})` : ""}`));
4056
4226
  }
4057
4227
  let publish = false;
4058
4228
  if (options.allowSensitive) {
4059
- console.log(chalk6.gray(" Publishing anyway (--allow-sensitive)."));
4229
+ console.log(chalk6.gray(" Pushing anyway (--allow-sensitive)."));
4060
4230
  publish = true;
4061
4231
  } else if (!options.yes && process.stdin.isTTY) {
4062
4232
  const answer = await prompts2({
4063
4233
  type: "confirm",
4064
4234
  name: "publish",
4065
- message: "Publish it anyway?",
4235
+ message: "Push it anyway?",
4066
4236
  initial: false
4067
4237
  });
4068
4238
  publish = answer.publish === true;
@@ -4071,7 +4241,7 @@ function confirmSensitive(options, spinner) {
4071
4241
  spinner?.start();
4072
4242
  } else {
4073
4243
  console.log(
4074
- chalk6.gray(" Remove them, keep the blueprint private, or run again with --allow-sensitive to publish anyway.")
4244
+ chalk6.gray(" Remove them, or run again with --allow-sensitive to push anyway.")
4075
4245
  );
4076
4246
  }
4077
4247
  return publish;
@@ -6591,7 +6761,7 @@ var TEST_FRAMEWORKS2 = TEST_FRAMEWORKS;
6591
6761
 
6592
6762
  // src/commands/wizard.ts
6593
6763
  var DRAFTS_DIR = ".lynxprompt/drafts";
6594
- var CLI_VERSION = "2.1.39";
6764
+ var CLI_VERSION = "2.1.41";
6595
6765
  async function saveDraftLocally(name, config2, stepReached) {
6596
6766
  const draftsPath = join3(process.cwd(), DRAFTS_DIR);
6597
6767
  await mkdir3(draftsPath, { recursive: true });
@@ -7729,8 +7899,11 @@ async function runWizardWithDraftProtection(options) {
7729
7899
  ...config2,
7730
7900
  blueprintMode: options.blueprint || config2.blueprintMode || false,
7731
7901
  variables,
7732
- enableAutoUpdate: false
7902
+ enableAutoUpdate: false,
7733
7903
  // Don't generate curl header yet
7904
+ // One rule set: CLAUDE.md/GEMINI.md import AGENTS.md. Not with Cloud
7905
+ // Sync, which uploads one of the files and must upload real rules.
7906
+ pointerFiles: options.pointers !== false && !config2.enableAutoUpdate
7734
7907
  };
7735
7908
  let files = generateConfig({ ...finalConfig, apiUrl: getApiUrl() });
7736
7909
  let savedBlueprintId = null;
@@ -7797,7 +7970,8 @@ async function runWizardWithDraftProtection(options) {
7797
7970
  await mkdir3(dir, { recursive: true });
7798
7971
  }
7799
7972
  await writeFile3(outputPath, content, "utf-8");
7800
- console.log(` ${chalk7.cyan("\u2192")} ${chalk7.bold(filename)}`);
7973
+ const note = content === AGENTS_MD_POINTER ? chalk7.gray(" (imports AGENTS.md; --no-pointers for a full copy)") : "";
7974
+ console.log(` ${chalk7.cyan("\u2192")} ${chalk7.bold(filename)}${note}`);
7801
7975
  if (savedBlueprintId && syncedFile && syncedFile.file === filename) {
7802
7976
  await trackBlueprint(process.cwd(), {
7803
7977
  id: savedBlueprintId,
@@ -9966,8 +10140,13 @@ async function searchCommand(query, options) {
9966
10140
  const spinner = ora7(`Searching for "${query}"...`).start();
9967
10141
  try {
9968
10142
  const limit = parseInt(options.limit, 10) || 20;
9969
- const { templates, total, hasMore } = await api.searchBlueprints(query, limit);
10143
+ const response = await api.searchBlueprints(query, limit);
9970
10144
  spinner.stop();
10145
+ if (options.json) {
10146
+ console.log(JSON.stringify(response, null, 2));
10147
+ return;
10148
+ }
10149
+ const { templates, total, hasMore } = response;
9971
10150
  if (templates.length === 0) {
9972
10151
  console.log();
9973
10152
  console.log(chalk8.yellow(`No blueprints found for "${query}".`));
@@ -10106,6 +10285,8 @@ async function statusCommand() {
10106
10285
  console.log();
10107
10286
  }
10108
10287
  }
10288
+ console.log(chalk9.gray(" \u2713 means no local edits. Run 'lynxp sync --dry-run' to check the cloud too."));
10289
+ console.log();
10109
10290
  }
10110
10291
  console.log(chalk9.cyan("\u{1F4C4} AI Config Files"));
10111
10292
  console.log();
@@ -10196,6 +10377,94 @@ import { readFile as readFile6, readdir as readdir2, stat } from "fs/promises";
10196
10377
  import { join as join5 } from "path";
10197
10378
  import { existsSync as existsSync3 } from "fs";
10198
10379
  import * as yaml2 from "yaml";
10380
+
10381
+ // src/utils/hidden-unicode.ts
10382
+ var NAMES = {
10383
+ 1564: "ARABIC LETTER MARK",
10384
+ 6158: "MONGOLIAN VOWEL SEPARATOR",
10385
+ 8203: "ZERO WIDTH SPACE",
10386
+ 8204: "ZERO WIDTH NON-JOINER",
10387
+ 8205: "ZERO WIDTH JOINER",
10388
+ 8206: "LEFT-TO-RIGHT MARK",
10389
+ 8207: "RIGHT-TO-LEFT MARK",
10390
+ 8234: "LEFT-TO-RIGHT EMBEDDING",
10391
+ 8235: "RIGHT-TO-LEFT EMBEDDING",
10392
+ 8236: "POP DIRECTIONAL FORMATTING",
10393
+ 8237: "LEFT-TO-RIGHT OVERRIDE",
10394
+ 8238: "RIGHT-TO-LEFT OVERRIDE",
10395
+ 8288: "WORD JOINER",
10396
+ 8289: "FUNCTION APPLICATION",
10397
+ 8290: "INVISIBLE TIMES",
10398
+ 8291: "INVISIBLE SEPARATOR",
10399
+ 8292: "INVISIBLE PLUS",
10400
+ 8294: "LEFT-TO-RIGHT ISOLATE",
10401
+ 8295: "RIGHT-TO-LEFT ISOLATE",
10402
+ 8296: "FIRST STRONG ISOLATE",
10403
+ 8297: "POP DIRECTIONAL ISOLATE",
10404
+ 65279: "ZERO WIDTH NO-BREAK SPACE"
10405
+ };
10406
+ var CONTEXTUAL = /* @__PURE__ */ new Set([1564, 8204, 8205, 8206, 8207]);
10407
+ var BLACK_FLAG = 127988;
10408
+ var CANCEL_TAG = 917631;
10409
+ var isTag = (cp) => cp >= 917504 && cp <= 917631;
10410
+ var isVariationSelectorSupplement = (cp) => cp >= 917760 && cp <= 917999;
10411
+ function isHidden(cp) {
10412
+ return cp in NAMES || isTag(cp) || isVariationSelectorSupplement(cp);
10413
+ }
10414
+ function nameOf(cp) {
10415
+ if (cp in NAMES) return NAMES[cp];
10416
+ if (isTag(cp)) return "TAG CHARACTER";
10417
+ return "VARIATION SELECTOR";
10418
+ }
10419
+ var RGI_SUBDIVISIONS = /* @__PURE__ */ new Set(["gbeng", "gbsct", "gbwls"]);
10420
+ function flagTagRunLength(cps, i) {
10421
+ if (cps[i - 1] !== BLACK_FLAG) return 0;
10422
+ let j = i;
10423
+ while (j < cps.length && cps[j] >= 917536 && cps[j] < CANCEL_TAG) j++;
10424
+ if (cps[j] !== CANCEL_TAG) return 0;
10425
+ const code = String.fromCharCode(...cps.slice(i, j).map((cp) => cp - 917504));
10426
+ return RGI_SUBDIVISIONS.has(code) ? j - i + 1 : 0;
10427
+ }
10428
+ var isVisibleNonAscii = (cp) => cp !== void 0 && cp > 127 && !isHidden(cp);
10429
+ function findHiddenUnicode(content) {
10430
+ const cps = Array.from(content, (c) => c.codePointAt(0));
10431
+ const found = [];
10432
+ let line = 1;
10433
+ let column = 0;
10434
+ for (let i = 0; i < cps.length; i++) {
10435
+ const cp = cps[i];
10436
+ if (cp === 10) {
10437
+ line++;
10438
+ column = 0;
10439
+ continue;
10440
+ }
10441
+ column++;
10442
+ if (!isHidden(cp)) continue;
10443
+ if (cp === 65279 && i === 0) continue;
10444
+ if (CONTEXTUAL.has(cp) && isVisibleNonAscii(cps[i - 1]) && isVisibleNonAscii(cps[i + 1])) continue;
10445
+ const flagRun = flagTagRunLength(cps, i);
10446
+ if (flagRun > 0) {
10447
+ i += flagRun - 1;
10448
+ column += flagRun - 1;
10449
+ continue;
10450
+ }
10451
+ found.push({
10452
+ line,
10453
+ column,
10454
+ codePoint: `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`,
10455
+ name: nameOf(cp)
10456
+ });
10457
+ }
10458
+ return found;
10459
+ }
10460
+ function describeHiddenUnicode(filename, found, shown = 5) {
10461
+ const where = found.slice(0, shown).map((h) => `line ${h.line} col ${h.column} ${h.codePoint} ${h.name}`).join("; ");
10462
+ const more = found.length > shown ? `; and ${found.length - shown} more` : "";
10463
+ const n = found.length;
10464
+ return `${filename}: ${n} hidden Unicode character${n === 1 ? "" : "s"} that can carry instructions a reviewer cannot see (${where}${more})`;
10465
+ }
10466
+
10467
+ // src/commands/check.ts
10199
10468
  var CONFIG_FILES2 = [
10200
10469
  { path: "AGENTS.md", name: "AGENTS.md" },
10201
10470
  { path: "CLAUDE.md", name: "CLAUDE.md" },
@@ -10222,6 +10491,7 @@ var CONFIG_DIRS2 = [
10222
10491
  { path: ".trae/rules", name: "Trae" },
10223
10492
  { path: ".augment/rules", name: "Augment" },
10224
10493
  { path: ".roo/rules", name: "Roo Code" },
10494
+ { path: ".devin/rules", name: "Devin" },
10225
10495
  { path: ".kilocode/rules", name: "Kilo Code" },
10226
10496
  { path: ".amazonq/rules", name: "Amazon Q" },
10227
10497
  { path: ".idx/rules", name: "Firebase Studio" },
@@ -10266,6 +10536,10 @@ function validateMarkdown(content, filename) {
10266
10536
  }
10267
10537
  return { errors, warnings };
10268
10538
  }
10539
+ function validateHiddenUnicode(content, filename) {
10540
+ const found = findHiddenUnicode(content);
10541
+ return found.length > 0 ? [describeHiddenUnicode(filename, found)] : [];
10542
+ }
10269
10543
  async function validateLynxPromptConfig(cwd) {
10270
10544
  const errors = [];
10271
10545
  const warnings = [];
@@ -10329,8 +10603,9 @@ function validateMdc(content, filename) {
10329
10603
  }
10330
10604
  async function checkCommand(options = {}) {
10331
10605
  const isCi = options.ci;
10606
+ const isJson = options.json;
10332
10607
  const cwd = process.cwd();
10333
- if (!isCi) {
10608
+ if (!isCi && !isJson) {
10334
10609
  console.log();
10335
10610
  console.log(chalk10.cyan("\u{1F431} LynxPrompt Check"));
10336
10611
  console.log();
@@ -10341,7 +10616,7 @@ async function checkCommand(options = {}) {
10341
10616
  warnings: [],
10342
10617
  files: []
10343
10618
  };
10344
- const spinner = !isCi ? ora8("Scanning for configuration files...").start() : null;
10619
+ const spinner = !isCi && !isJson ? ora8("Scanning for configuration files...").start() : null;
10345
10620
  for (const file of CONFIG_FILES2) {
10346
10621
  const filePath = join5(cwd, file.path);
10347
10622
  if (existsSync3(filePath) && (await stat(filePath)).isFile()) {
@@ -10349,7 +10624,7 @@ async function checkCommand(options = {}) {
10349
10624
  try {
10350
10625
  const content = await readFile6(filePath, "utf-8");
10351
10626
  const validation = validateMarkdown(content, file.path);
10352
- result.errors.push(...validation.errors);
10627
+ result.errors.push(...validation.errors, ...validateHiddenUnicode(content, file.path));
10353
10628
  result.warnings.push(...validation.warnings);
10354
10629
  } catch (error) {
10355
10630
  result.errors.push(`${file.path}: Could not read file`);
@@ -10367,6 +10642,7 @@ async function checkCommand(options = {}) {
10367
10642
  if (fileStat.isFile()) {
10368
10643
  result.files.push(`${dir.path}/${file}`);
10369
10644
  const content = await readFile6(filePath, "utf-8");
10645
+ result.errors.push(...validateHiddenUnicode(content, `${dir.path}/${file}`));
10370
10646
  if (file.endsWith(".mdc")) {
10371
10647
  const validation = validateMdc(content, `${dir.path}/${file}`);
10372
10648
  result.errors.push(...validation.errors);
@@ -10387,7 +10663,10 @@ async function checkCommand(options = {}) {
10387
10663
  result.warnings.push(...lynxpromptValidation.warnings);
10388
10664
  spinner?.stop();
10389
10665
  result.valid = result.errors.length === 0;
10390
- if (isCi) {
10666
+ if (isJson) {
10667
+ console.log(JSON.stringify(result, null, 2));
10668
+ if (!result.valid || isCi && result.files.length === 0) process.exitCode = 1;
10669
+ } else if (isCi) {
10391
10670
  if (!result.valid) {
10392
10671
  console.error("\u2717 Validation failed");
10393
10672
  for (const error of result.errors) {
@@ -10555,7 +10834,7 @@ async function diffCommand(fileOrId, options = {}) {
10555
10834
  if (fileOrId) {
10556
10835
  const tracked = await findBlueprintByFile(cwd, fileOrId);
10557
10836
  if (tracked) {
10558
- await diffFileWithBlueprint(cwd, fileOrId, tracked.id);
10837
+ await diffFileWithBlueprint(cwd, fileOrId, tracked.id, false, tracked.variables);
10559
10838
  return;
10560
10839
  }
10561
10840
  await diffWithBlueprintId(cwd, fileOrId);
@@ -10581,7 +10860,7 @@ async function diffCommand(fileOrId, options = {}) {
10581
10860
  }
10582
10861
  console.log(chalk12.cyan(`\u{1F4C4} ${blueprint.file}`));
10583
10862
  console.log(chalk12.gray(` Linked to: ${blueprint.name} (${blueprint.id})`));
10584
- const result = await diffFileWithBlueprint(cwd, blueprint.file, blueprint.id, true);
10863
+ const result = await diffFileWithBlueprint(cwd, blueprint.file, blueprint.id, true, blueprint.variables);
10585
10864
  if (result === "changed") {
10586
10865
  hasChanges = true;
10587
10866
  } else if (result === "same") {
@@ -10596,12 +10875,11 @@ async function diffCommand(fileOrId, options = {}) {
10596
10875
  } else if (!hasChanges) {
10597
10876
  console.log(chalk12.green("\u2713 All tracked files are in sync with their cloud blueprints!"));
10598
10877
  } else {
10599
- console.log(chalk12.gray("To push local changes: lynxp push"));
10600
- console.log(chalk12.gray("To pull cloud changes: lynxp pull <id>"));
10878
+ console.log(chalk12.gray("To bring both sides together: lynxp sync"));
10601
10879
  }
10602
10880
  console.log();
10603
10881
  }
10604
- async function diffFileWithBlueprint(cwd, file, blueprintId, compact = false) {
10882
+ async function diffFileWithBlueprint(cwd, file, blueprintId, compact = false, variables) {
10605
10883
  const filePath = join6(cwd, file);
10606
10884
  if (!existsSync4(filePath)) {
10607
10885
  console.log(chalk12.red(`\u2717 File not found: ${file}`));
@@ -10616,7 +10894,8 @@ async function diffFileWithBlueprint(cwd, file, blueprintId, compact = false) {
10616
10894
  return "error";
10617
10895
  }
10618
10896
  const localContent = await readFile7(filePath, "utf-8");
10619
- const diff = computeDiff(blueprint.content, localContent);
10897
+ const cloudContent = variables ? fillBlueprintVariables(blueprint.content, variables).content : blueprint.content;
10898
+ const diff = computeDiff(cloudContent, localContent);
10620
10899
  const stats = getDiffStats(diff);
10621
10900
  if (stats.added === 0 && stats.removed === 0) {
10622
10901
  if (!compact) {
@@ -10783,12 +11062,162 @@ async function diffLocal(cwd) {
10783
11062
  console.log();
10784
11063
  }
10785
11064
 
10786
- // src/commands/link.ts
11065
+ // src/commands/sync.ts
10787
11066
  import chalk13 from "chalk";
11067
+ import { readFile as readFile8, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
11068
+ import { existsSync as existsSync5 } from "fs";
11069
+ import { dirname as dirname4, join as join7 } from "path";
11070
+ async function syncCommand(fileArg, options = {}) {
11071
+ const cwd = process.cwd();
11072
+ if (!isAuthenticated()) {
11073
+ console.log(chalk13.yellow("Not logged in. Run 'lynxp login' to authenticate."));
11074
+ process.exit(1);
11075
+ }
11076
+ const { blueprints } = await loadBlueprints(cwd);
11077
+ const targets = fileArg ? blueprints.filter((b) => toTrackedPath(cwd, b.file) === toTrackedPath(cwd, fileArg)) : blueprints;
11078
+ if (targets.length === 0) {
11079
+ console.log(
11080
+ chalk13.yellow(fileArg ? `${fileArg} is not tracked.` : "No tracked blueprints found.")
11081
+ );
11082
+ console.log(chalk13.gray("Track a file with 'lynxp pull <id>' or 'lynxp link <file> <id>'."));
11083
+ if (fileArg) process.exit(1);
11084
+ return;
11085
+ }
11086
+ console.log();
11087
+ console.log(chalk13.cyan(options.dryRun ? "\u{1F431} LynxPrompt Sync (dry run)" : "\u{1F431} LynxPrompt Sync"));
11088
+ console.log();
11089
+ const counts = {
11090
+ "in-sync": 0,
11091
+ pulled: 0,
11092
+ pushed: 0,
11093
+ kept: 0,
11094
+ conflict: 0,
11095
+ error: 0
11096
+ };
11097
+ for (const tracked of targets) {
11098
+ counts[await syncOne(cwd, tracked, options)]++;
11099
+ }
11100
+ console.log();
11101
+ const parts = [
11102
+ `${counts.pulled} pulled`,
11103
+ `${counts.pushed} pushed`,
11104
+ `${counts["in-sync"]} in sync`
11105
+ ];
11106
+ if (counts.kept) parts.push(`${counts.kept} local only`);
11107
+ if (counts.conflict) parts.push(`${counts.conflict} conflict`);
11108
+ if (counts.error) parts.push(`${counts.error} failed`);
11109
+ console.log(chalk13.gray(`${options.dryRun ? "Would be: " : ""}${parts.join(", ")}`));
11110
+ console.log();
11111
+ if (counts.conflict || counts.error) process.exitCode = 1;
11112
+ }
11113
+ async function syncOne(cwd, tracked, options) {
11114
+ const file = tracked.file;
11115
+ const filePath = join7(cwd, file);
11116
+ const remoteFile = conflictFilePath(cwd, file);
11117
+ const dry = options.dryRun === true;
11118
+ if (existsSync5(remoteFile)) {
11119
+ console.log(chalk13.red(`\u2717 ${file}: unresolved conflict`));
11120
+ console.log(chalk13.gray(` Merge ${file}.remote into ${file}, delete ${file}.remote, then run 'lynxp sync' again.`));
11121
+ return "conflict";
11122
+ }
11123
+ if (!existsSync5(filePath)) {
11124
+ console.log(chalk13.red(`\u2717 ${file}: file not found`));
11125
+ console.log(chalk13.gray(` Run 'lynxp pull ${tracked.id}' to restore it or 'lynxp unlink ${file}' to stop tracking it.`));
11126
+ return "error";
11127
+ }
11128
+ let remote;
11129
+ try {
11130
+ const { blueprint } = await api.getBlueprint(tracked.id);
11131
+ if (!blueprint?.content) {
11132
+ console.log(chalk13.red(`\u2717 ${file}: the cloud blueprint ${tracked.id} has no content`));
11133
+ return "error";
11134
+ }
11135
+ remote = blueprint.content;
11136
+ } catch (error) {
11137
+ console.log(chalk13.red(`\u2717 ${file}: could not fetch ${tracked.id}: ${errorText(error)}`));
11138
+ return "error";
11139
+ }
11140
+ const local = await readFile8(filePath, "utf-8");
11141
+ const rendered = tracked.variables ? fillBlueprintVariables(remote, tracked.variables).content : remote;
11142
+ const localChanged = calculateChecksum(local) !== (tracked.localChecksum ?? tracked.checksum);
11143
+ const remoteChanged = calculateChecksum(remote) !== tracked.checksum;
11144
+ if (calculateChecksum(local) === calculateChecksum(rendered)) {
11145
+ if (!dry && (localChanged || remoteChanged)) await record(cwd, tracked, remote, local);
11146
+ console.log(chalk13.green(`\u2713 ${file}: in sync`));
11147
+ return "in-sync";
11148
+ }
11149
+ if (!localChanged) {
11150
+ if (!dry) {
11151
+ await writeFile4(filePath, rendered, "utf-8");
11152
+ await record(cwd, tracked, remote, rendered);
11153
+ }
11154
+ console.log(chalk13.cyan(`\u2193 ${file}: ${dry ? "would update from the cloud" : "updated from the cloud"}`));
11155
+ return "pulled";
11156
+ }
11157
+ if (!remoteChanged) {
11158
+ const reason = tracked.variables ? "it was pulled with --vars, so pushing it would replace the blueprint's variables with your values" : !tracked.editable ? "marketplace blueprints are read-only" : null;
11159
+ if (reason) {
11160
+ console.log(chalk13.yellow(`\u25CF ${file}: local changes kept, not pushed (${reason})`));
11161
+ return "kept";
11162
+ }
11163
+ if (dry) {
11164
+ console.log(chalk13.cyan(`\u2191 ${file}: would push local changes`));
11165
+ return "pushed";
11166
+ }
11167
+ try {
11168
+ await api.updateBlueprint(tracked.id, { content: local, expected_checksum: tracked.checksum });
11169
+ await updateChecksum(cwd, file, local);
11170
+ console.log(chalk13.cyan(`\u2191 ${file}: pushed local changes`));
11171
+ return "pushed";
11172
+ } catch (error) {
11173
+ if (error instanceof ApiRequestError && error.statusCode === 409) {
11174
+ console.log(chalk13.red(`\u2717 ${file}: the cloud changed while syncing; run 'lynxp sync' again`));
11175
+ } else {
11176
+ console.log(chalk13.red(`\u2717 ${file}: push failed: ${errorText(error)}`));
11177
+ console.log(chalk13.gray(` Try 'lynxp push ${file}' for the full prompts.`));
11178
+ }
11179
+ return "error";
11180
+ }
11181
+ }
11182
+ if (!dry) {
11183
+ await mkdir4(dirname4(remoteFile), { recursive: true });
11184
+ await writeFile4(remoteFile, rendered, "utf-8");
11185
+ await record(cwd, tracked, remote, rendered);
11186
+ }
11187
+ console.log(chalk13.red(`\u2717 ${file}: changed both locally and in the cloud`));
11188
+ console.log(
11189
+ chalk13.gray(
11190
+ dry ? ` Sync would write the cloud version to ${file}.remote and leave ${file} as it is.` : ` Cloud version written to ${file}.remote; ${file} was not touched.
11191
+ Merge what you need into ${file}, delete ${file}.remote, then run 'lynxp sync' again.`
11192
+ )
11193
+ );
11194
+ return "conflict";
11195
+ }
11196
+ async function record(cwd, t, remote, written) {
11197
+ await trackBlueprint(cwd, {
11198
+ id: t.id,
11199
+ name: t.name,
11200
+ file: t.file,
11201
+ content: remote,
11202
+ source: t.source,
11203
+ version: t.version,
11204
+ hierarchyId: t.hierarchyId,
11205
+ hierarchyName: t.hierarchyName,
11206
+ repositoryPath: t.repositoryPath,
11207
+ variables: t.variables,
11208
+ localContent: t.variables ? written : void 0
11209
+ });
11210
+ }
11211
+ function errorText(error) {
11212
+ return error instanceof Error ? error.message : String(error);
11213
+ }
11214
+
11215
+ // src/commands/link.ts
11216
+ import chalk14 from "chalk";
10788
11217
  import ora10 from "ora";
10789
11218
  import prompts4 from "prompts";
10790
- import { join as join7 } from "path";
10791
- import { existsSync as existsSync5 } from "fs";
11219
+ import { join as join8 } from "path";
11220
+ import { existsSync as existsSync6 } from "fs";
10792
11221
  function getSourceFromVisibility2(visibility) {
10793
11222
  switch (visibility) {
10794
11223
  case "PUBLIC":
@@ -10808,7 +11237,7 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10808
11237
  return;
10809
11238
  }
10810
11239
  console.log();
10811
- console.log(chalk13.cyan("\u{1F431} Link File to Blueprint"));
11240
+ console.log(chalk14.cyan("\u{1F431} Link File to Blueprint"));
10812
11241
  console.log();
10813
11242
  let file;
10814
11243
  let blueprintId = blueprintIdArg;
@@ -10822,13 +11251,13 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10822
11251
  ".rules",
10823
11252
  ".clinerules"
10824
11253
  ];
10825
- const foundFiles = configFiles.filter((f) => existsSync5(join7(cwd, f)));
11254
+ const foundFiles = configFiles.filter((f) => existsSync6(join8(cwd, f)));
10826
11255
  if (foundFiles.length === 0) {
10827
- console.log(chalk13.yellow("No AI configuration files found in this directory."));
11256
+ console.log(chalk14.yellow("No AI configuration files found in this directory."));
10828
11257
  console.log();
10829
- console.log(chalk13.gray("Create one first:"));
10830
- console.log(chalk13.gray(" lynxp wizard Generate a new config file"));
10831
- console.log(chalk13.gray(" lynxp pull <id> Download from marketplace"));
11258
+ console.log(chalk14.gray("Create one first:"));
11259
+ console.log(chalk14.gray(" lynxp wizard Generate a new config file"));
11260
+ console.log(chalk14.gray(" lynxp pull <id> Download from marketplace"));
10832
11261
  return;
10833
11262
  }
10834
11263
  const { selectedFile } = await prompts4({
@@ -10842,22 +11271,22 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10842
11271
  }))
10843
11272
  });
10844
11273
  if (!selectedFile) {
10845
- console.log(chalk13.gray("Cancelled."));
11274
+ console.log(chalk14.gray("Cancelled."));
10846
11275
  return;
10847
11276
  }
10848
11277
  file = selectedFile;
10849
11278
  } else {
10850
11279
  file = fileArg;
10851
11280
  }
10852
- const filePath = join7(cwd, file);
10853
- if (!existsSync5(filePath)) {
10854
- console.log(chalk13.red(`\u2717 File not found: ${file}`));
11281
+ const filePath = join8(cwd, file);
11282
+ if (!existsSync6(filePath)) {
11283
+ console.log(chalk14.red(`\u2717 File not found: ${file}`));
10855
11284
  return;
10856
11285
  }
10857
11286
  const existing = await findBlueprintByFile(cwd, file);
10858
11287
  if (existing) {
10859
- console.log(chalk13.yellow(`This file is already linked to: ${existing.name}`));
10860
- console.log(chalk13.gray(` ID: ${existing.id}`));
11288
+ console.log(chalk14.yellow(`This file is already linked to: ${existing.name}`));
11289
+ console.log(chalk14.gray(` ID: ${existing.id}`));
10861
11290
  console.log();
10862
11291
  const { proceed } = await prompts4({
10863
11292
  type: "confirm",
@@ -10866,13 +11295,13 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10866
11295
  initial: false
10867
11296
  });
10868
11297
  if (!proceed) {
10869
- console.log(chalk13.gray("Cancelled."));
11298
+ console.log(chalk14.gray("Cancelled."));
10870
11299
  return;
10871
11300
  }
10872
11301
  }
10873
11302
  if (!blueprintId) {
10874
11303
  if (!isAuthenticated()) {
10875
- console.log(chalk13.yellow("You need to login to access your blueprints."));
11304
+ console.log(chalk14.yellow("You need to login to access your blueprints."));
10876
11305
  const { doLogin } = await prompts4({
10877
11306
  type: "confirm",
10878
11307
  name: "doLogin",
@@ -10880,10 +11309,10 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10880
11309
  initial: true
10881
11310
  });
10882
11311
  if (doLogin) {
10883
- console.log(chalk13.gray("Run 'lynxp login' in another terminal, then come back here."));
11312
+ console.log(chalk14.gray("Run 'lynxp login' in another terminal, then come back here."));
10884
11313
  return;
10885
11314
  }
10886
- console.log(chalk13.gray("Cancelled."));
11315
+ console.log(chalk14.gray("Cancelled."));
10887
11316
  return;
10888
11317
  }
10889
11318
  const { searchMethod } = await prompts4({
@@ -10897,7 +11326,7 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10897
11326
  ]
10898
11327
  });
10899
11328
  if (!searchMethod) {
10900
- console.log(chalk13.gray("Cancelled."));
11329
+ console.log(chalk14.gray("Cancelled."));
10901
11330
  return;
10902
11331
  }
10903
11332
  if (searchMethod === "list") {
@@ -10906,8 +11335,8 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10906
11335
  const blueprints = await api.listAllBlueprints();
10907
11336
  spinner2.stop();
10908
11337
  if (!blueprints || blueprints.length === 0) {
10909
- console.log(chalk13.yellow("You don't have any blueprints yet."));
10910
- console.log(chalk13.gray("Create one with 'lynxp push' or search the marketplace."));
11338
+ console.log(chalk14.yellow("You don't have any blueprints yet."));
11339
+ console.log(chalk14.gray("Create one with 'lynxp push' or search the marketplace."));
10911
11340
  return;
10912
11341
  }
10913
11342
  const { selected } = await prompts4({
@@ -10921,13 +11350,13 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10921
11350
  }))
10922
11351
  });
10923
11352
  if (!selected) {
10924
- console.log(chalk13.gray("Cancelled."));
11353
+ console.log(chalk14.gray("Cancelled."));
10925
11354
  return;
10926
11355
  }
10927
11356
  blueprintId = selected;
10928
11357
  } catch {
10929
11358
  spinner2.stop();
10930
- console.log(chalk13.red("\u2717 Could not fetch blueprints"));
11359
+ console.log(chalk14.red("\u2717 Could not fetch blueprints"));
10931
11360
  return;
10932
11361
  }
10933
11362
  } else if (searchMethod === "search") {
@@ -10937,7 +11366,7 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10937
11366
  message: "Search for:"
10938
11367
  });
10939
11368
  if (!query) {
10940
- console.log(chalk13.gray("Cancelled."));
11369
+ console.log(chalk14.gray("Cancelled."));
10941
11370
  return;
10942
11371
  }
10943
11372
  const spinner2 = ora10(`Searching for "${query}"...`).start();
@@ -10945,7 +11374,7 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10945
11374
  const results = await api.searchBlueprints(query, 10);
10946
11375
  spinner2.stop();
10947
11376
  if (!results.templates || results.templates.length === 0) {
10948
- console.log(chalk13.yellow(`No blueprints found for "${query}"`));
11377
+ console.log(chalk14.yellow(`No blueprints found for "${query}"`));
10949
11378
  return;
10950
11379
  }
10951
11380
  const { selected } = await prompts4({
@@ -10959,13 +11388,13 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10959
11388
  }))
10960
11389
  });
10961
11390
  if (!selected) {
10962
- console.log(chalk13.gray("Cancelled."));
11391
+ console.log(chalk14.gray("Cancelled."));
10963
11392
  return;
10964
11393
  }
10965
11394
  blueprintId = selected;
10966
11395
  } catch {
10967
11396
  spinner2.stop();
10968
- console.log(chalk13.red("\u2717 Search failed"));
11397
+ console.log(chalk14.red("\u2717 Search failed"));
10969
11398
  return;
10970
11399
  }
10971
11400
  } else {
@@ -10975,82 +11404,82 @@ async function linkCommand(fileArg, blueprintIdArg, options = {}) {
10975
11404
  message: "Enter blueprint ID:"
10976
11405
  });
10977
11406
  if (!manualId) {
10978
- console.log(chalk13.gray("Cancelled."));
11407
+ console.log(chalk14.gray("Cancelled."));
10979
11408
  return;
10980
11409
  }
10981
11410
  blueprintId = manualId;
10982
11411
  }
10983
11412
  }
10984
11413
  if (!blueprintId) {
10985
- console.log(chalk13.red("\u2717 No blueprint ID provided."));
11414
+ console.log(chalk14.red("\u2717 No blueprint ID provided."));
10986
11415
  return;
10987
11416
  }
10988
- const spinner = ora10(`Fetching blueprint ${chalk13.cyan(blueprintId)}...`).start();
11417
+ const spinner = ora10(`Fetching blueprint ${chalk14.cyan(blueprintId)}...`).start();
10989
11418
  try {
10990
11419
  const { blueprint } = await api.getBlueprint(blueprintId);
10991
11420
  spinner.stop();
10992
11421
  const source = getSourceFromVisibility2(blueprint.visibility);
10993
11422
  const isMarketplace = source === "marketplace";
10994
11423
  console.log();
10995
- console.log(chalk13.cyan(`\u{1F431} Blueprint: ${chalk13.bold(safeText(blueprint.name))}`));
11424
+ console.log(chalk14.cyan(`\u{1F431} Blueprint: ${chalk14.bold(safeText(blueprint.name))}`));
10996
11425
  if (blueprint.description) {
10997
- console.log(chalk13.gray(` ${safeText(blueprint.description)}`));
11426
+ console.log(chalk14.gray(` ${safeText(blueprint.description)}`));
10998
11427
  }
10999
- console.log(chalk13.gray(` Visibility: ${blueprint.visibility}`));
11428
+ console.log(chalk14.gray(` Visibility: ${blueprint.visibility}`));
11000
11429
  console.log();
11001
11430
  if (isMarketplace) {
11002
- console.log(chalk13.yellow("\u26A0 This is a marketplace blueprint."));
11003
- console.log(chalk13.gray(" Your local changes will NOT sync back to the cloud."));
11004
- console.log(chalk13.gray(" To make changes, you'll need to create your own copy."));
11431
+ console.log(chalk14.yellow("\u26A0 This is a marketplace blueprint."));
11432
+ console.log(chalk14.gray(" Your local changes will NOT sync back to the cloud."));
11433
+ console.log(chalk14.gray(" To make changes, you'll need to create your own copy."));
11005
11434
  console.log();
11006
11435
  }
11007
11436
  const { confirm } = await prompts4({
11008
11437
  type: "confirm",
11009
11438
  name: "confirm",
11010
- message: `Link ${chalk13.cyan(file)} to ${chalk13.cyan(blueprint.name)}?`,
11439
+ message: `Link ${chalk14.cyan(file)} to ${chalk14.cyan(blueprint.name)}?`,
11011
11440
  initial: true
11012
11441
  });
11013
11442
  if (!confirm) {
11014
- console.log(chalk13.gray("Cancelled."));
11443
+ console.log(chalk14.gray("Cancelled."));
11015
11444
  return;
11016
11445
  }
11017
11446
  await linkBlueprint(cwd, file, blueprint.id, blueprint.name, source, blueprint.content);
11018
11447
  console.log();
11019
- console.log(chalk13.green(`\u2705 Linked: ${file} \u2192 ${blueprint.id}`));
11448
+ console.log(chalk14.green(`\u2705 Linked: ${file} \u2192 ${blueprint.id}`));
11020
11449
  console.log();
11021
- console.log(chalk13.gray("Next steps:"));
11022
- console.log(chalk13.gray(` \u2022 Run 'lynxp diff' to see differences`));
11023
- console.log(chalk13.gray(` \u2022 Run 'lynxp status' to see all tracked blueprints`));
11450
+ console.log(chalk14.gray("Next steps:"));
11451
+ console.log(chalk14.gray(` \u2022 Run 'lynxp diff' to see differences`));
11452
+ console.log(chalk14.gray(` \u2022 Run 'lynxp status' to see all tracked blueprints`));
11024
11453
  if (!isMarketplace) {
11025
- console.log(chalk13.gray(` \u2022 Run 'lynxp push' to push local changes to cloud`));
11454
+ console.log(chalk14.gray(` \u2022 Run 'lynxp push' to push local changes to cloud`));
11026
11455
  }
11027
11456
  console.log();
11028
11457
  } catch (error) {
11029
11458
  spinner.stop();
11030
11459
  if (error instanceof ApiRequestError) {
11031
11460
  if (error.statusCode === 404) {
11032
- console.log(chalk13.red(`\u2717 Blueprint not found: ${blueprintId}`));
11033
- console.log(chalk13.gray(" Make sure the ID is correct. Use 'lynxp list' or 'lynxp search' to find blueprints."));
11461
+ console.log(chalk14.red(`\u2717 Blueprint not found: ${blueprintId}`));
11462
+ console.log(chalk14.gray(" Make sure the ID is correct. Use 'lynxp list' or 'lynxp search' to find blueprints."));
11034
11463
  } else if (error.statusCode === 403) {
11035
- console.log(chalk13.red("\u2717 You don't have access to this blueprint."));
11464
+ console.log(chalk14.red("\u2717 You don't have access to this blueprint."));
11036
11465
  } else {
11037
- console.log(chalk13.red(`\u2717 Error: ${error.message}`));
11466
+ console.log(chalk14.red(`\u2717 Error: ${error.message}`));
11038
11467
  }
11039
11468
  } else {
11040
- console.log(chalk13.red("\u2717 An unexpected error occurred."));
11469
+ console.log(chalk14.red("\u2717 An unexpected error occurred."));
11041
11470
  }
11042
11471
  }
11043
11472
  }
11044
11473
  async function unlinkCommand(fileArg) {
11045
11474
  const cwd = process.cwd();
11046
11475
  console.log();
11047
- console.log(chalk13.cyan("\u{1F431} Unlink File from Blueprint"));
11476
+ console.log(chalk14.cyan("\u{1F431} Unlink File from Blueprint"));
11048
11477
  console.log();
11049
11478
  let file;
11050
11479
  if (!fileArg) {
11051
11480
  const status = await checkSyncStatus(cwd);
11052
11481
  if (status.length === 0) {
11053
- console.log(chalk13.yellow("No files are currently linked to blueprints."));
11482
+ console.log(chalk14.yellow("No files are currently linked to blueprints."));
11054
11483
  return;
11055
11484
  }
11056
11485
  const { selectedFile } = await prompts4({
@@ -11064,7 +11493,7 @@ async function unlinkCommand(fileArg) {
11064
11493
  }))
11065
11494
  });
11066
11495
  if (!selectedFile) {
11067
- console.log(chalk13.gray("Cancelled."));
11496
+ console.log(chalk14.gray("Cancelled."));
11068
11497
  return;
11069
11498
  }
11070
11499
  file = selectedFile;
@@ -11073,77 +11502,77 @@ async function unlinkCommand(fileArg) {
11073
11502
  }
11074
11503
  const tracked = await findBlueprintByFile(cwd, file);
11075
11504
  if (!tracked) {
11076
- console.log(chalk13.yellow(`File is not linked to any blueprint: ${file}`));
11505
+ console.log(chalk14.yellow(`File is not linked to any blueprint: ${file}`));
11077
11506
  return;
11078
11507
  }
11079
- console.log(chalk13.gray(`Currently linked to: ${tracked.name}`));
11080
- console.log(chalk13.gray(` ID: ${tracked.id}`));
11081
- console.log(chalk13.gray(` Source: ${tracked.source}`));
11508
+ console.log(chalk14.gray(`Currently linked to: ${tracked.name}`));
11509
+ console.log(chalk14.gray(` ID: ${tracked.id}`));
11510
+ console.log(chalk14.gray(` Source: ${tracked.source}`));
11082
11511
  console.log();
11083
11512
  const { confirm } = await prompts4({
11084
11513
  type: "confirm",
11085
11514
  name: "confirm",
11086
- message: `Unlink ${chalk13.cyan(file)} from ${chalk13.cyan(tracked.name)}?`,
11515
+ message: `Unlink ${chalk14.cyan(file)} from ${chalk14.cyan(tracked.name)}?`,
11087
11516
  initial: true
11088
11517
  });
11089
11518
  if (!confirm) {
11090
- console.log(chalk13.gray("Cancelled."));
11519
+ console.log(chalk14.gray("Cancelled."));
11091
11520
  return;
11092
11521
  }
11093
11522
  const success = await untrackBlueprint(cwd, file);
11094
11523
  if (success) {
11095
11524
  console.log();
11096
- console.log(chalk13.green(`\u2705 Unlinked: ${file}`));
11097
- console.log(chalk13.gray(" The file is now standalone. Changes won't sync with the cloud."));
11525
+ console.log(chalk14.green(`\u2705 Unlinked: ${file}`));
11526
+ console.log(chalk14.gray(" The file is now standalone. Changes won't sync with the cloud."));
11098
11527
  console.log();
11099
11528
  } else {
11100
- console.log(chalk13.red("\u2717 Failed to unlink file."));
11529
+ console.log(chalk14.red("\u2717 Failed to unlink file."));
11101
11530
  }
11102
11531
  }
11103
11532
  async function listTrackedBlueprints(cwd) {
11104
11533
  console.log();
11105
- console.log(chalk13.cyan("\u{1F431} Tracked Blueprints"));
11534
+ console.log(chalk14.cyan("\u{1F431} Tracked Blueprints"));
11106
11535
  console.log();
11107
11536
  const status = await checkSyncStatus(cwd);
11108
11537
  if (status.length === 0) {
11109
- console.log(chalk13.gray("No blueprints are currently tracked."));
11538
+ console.log(chalk14.gray("No blueprints are currently tracked."));
11110
11539
  console.log();
11111
- console.log(chalk13.gray("To track a blueprint:"));
11112
- console.log(chalk13.gray(" lynxp pull <blueprint-id> Download and track a blueprint"));
11113
- console.log(chalk13.gray(" lynxp link Link an existing file to a blueprint"));
11540
+ console.log(chalk14.gray("To track a blueprint:"));
11541
+ console.log(chalk14.gray(" lynxp pull <blueprint-id> Download and track a blueprint"));
11542
+ console.log(chalk14.gray(" lynxp link Link an existing file to a blueprint"));
11114
11543
  return;
11115
11544
  }
11116
11545
  for (const { blueprint, localModified, fileExists: fileExists2 } of status) {
11117
- const statusIcon = !fileExists2 ? chalk13.red("\u2717") : localModified ? chalk13.yellow("\u25CF") : chalk13.green("\u2713");
11546
+ const statusIcon = !fileExists2 ? chalk14.red("\u2717") : localModified ? chalk14.yellow("\u25CF") : chalk14.green("\u2713");
11118
11547
  const sourceLabel = {
11119
- marketplace: chalk13.gray("[marketplace]"),
11120
- team: chalk13.blue("[team]"),
11121
- private: chalk13.green("[private]"),
11122
- local: chalk13.gray("[local]")
11548
+ marketplace: chalk14.gray("[marketplace]"),
11549
+ team: chalk14.blue("[team]"),
11550
+ private: chalk14.green("[private]"),
11551
+ local: chalk14.gray("[local]")
11123
11552
  }[blueprint.source];
11124
- console.log(`${statusIcon} ${chalk13.cyan(blueprint.file)}`);
11553
+ console.log(`${statusIcon} ${chalk14.cyan(blueprint.file)}`);
11125
11554
  console.log(` ${sourceLabel} ${blueprint.name}`);
11126
- console.log(` ${chalk13.gray(`ID: ${blueprint.id}`)}`);
11555
+ console.log(` ${chalk14.gray(`ID: ${blueprint.id}`)}`);
11127
11556
  if (!fileExists2) {
11128
- console.log(chalk13.red(` \u26A0 File not found`));
11557
+ console.log(chalk14.red(` \u26A0 File not found`));
11129
11558
  } else if (localModified) {
11130
- console.log(chalk13.yellow(` \u26A0 Local changes detected`));
11559
+ console.log(chalk14.yellow(` \u26A0 Local changes detected`));
11131
11560
  }
11132
11561
  console.log();
11133
11562
  }
11134
- console.log(chalk13.gray("Legend:"));
11135
- console.log(chalk13.gray(` ${chalk13.green("\u2713")} In sync ${chalk13.yellow("\u25CF")} Modified locally ${chalk13.red("\u2717")} Missing`));
11563
+ console.log(chalk14.gray("Legend:"));
11564
+ console.log(chalk14.gray(` ${chalk14.green("\u2713")} In sync ${chalk14.yellow("\u25CF")} Modified locally ${chalk14.red("\u2717")} Missing`));
11136
11565
  console.log();
11137
11566
  }
11138
11567
 
11139
11568
  // src/commands/analyze.ts
11140
- import chalk14 from "chalk";
11569
+ import chalk15 from "chalk";
11141
11570
  import ora11 from "ora";
11142
11571
  async function analyzeCommand(options) {
11143
11572
  console.log();
11144
11573
  if (!options.json) {
11145
- console.log(chalk14.cyan.bold(" \u{1F50D} LynxPrompt Analyzer"));
11146
- console.log(chalk14.gray(" Detect project configuration and tech stack"));
11574
+ console.log(chalk15.cyan.bold(" \u{1F50D} LynxPrompt Analyzer"));
11575
+ console.log(chalk15.gray(" Detect project configuration and tech stack"));
11147
11576
  console.log();
11148
11577
  }
11149
11578
  let detected;
@@ -11151,7 +11580,7 @@ async function analyzeCommand(options) {
11151
11580
  if (options.remote) {
11152
11581
  if (!isGitUrl(options.remote)) {
11153
11582
  if (!options.json) {
11154
- console.log(chalk14.red(" \u2717 Invalid Git URL provided"));
11583
+ console.log(chalk15.red(" \u2717 Invalid Git URL provided"));
11155
11584
  }
11156
11585
  process.exit(1);
11157
11586
  }
@@ -11164,7 +11593,7 @@ async function analyzeCommand(options) {
11164
11593
  spinner.succeed("Remote repository analyzed");
11165
11594
  } else {
11166
11595
  spinner.fail("Could not analyze repository");
11167
- console.log(chalk14.gray(" The repository may be private or inaccessible."));
11596
+ console.log(chalk15.gray(" The repository may be private or inaccessible."));
11168
11597
  process.exit(1);
11169
11598
  }
11170
11599
  } else {
@@ -11197,57 +11626,57 @@ async function analyzeCommand(options) {
11197
11626
  }
11198
11627
  console.log();
11199
11628
  if (!detected) {
11200
- console.log(chalk14.yellow(" No project configuration detected."));
11629
+ console.log(chalk15.yellow(" No project configuration detected."));
11201
11630
  console.log();
11202
- console.log(chalk14.gray(" Tips:"));
11203
- console.log(chalk14.gray(" \u2022 Make sure you're in a project directory with a package.json, pyproject.toml, etc."));
11204
- console.log(chalk14.gray(" \u2022 Use --remote <url> to analyze a remote repository"));
11631
+ console.log(chalk15.gray(" Tips:"));
11632
+ console.log(chalk15.gray(" \u2022 Make sure you're in a project directory with a package.json, pyproject.toml, etc."));
11633
+ console.log(chalk15.gray(" \u2022 Use --remote <url> to analyze a remote repository"));
11205
11634
  console.log();
11206
11635
  return;
11207
11636
  }
11208
- console.log(chalk14.green.bold(" \u{1F4CA} Project Analysis"));
11637
+ console.log(chalk15.green.bold(" \u{1F4CA} Project Analysis"));
11209
11638
  console.log();
11210
- console.log(chalk14.white(" Basic Info"));
11211
- console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11212
- console.log(` ${chalk14.dim("Name:")} ${chalk14.white(detected.name || "unknown")}`);
11639
+ console.log(chalk15.white(" Basic Info"));
11640
+ console.log(chalk15.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11641
+ console.log(` ${chalk15.dim("Name:")} ${chalk15.white(detected.name || "unknown")}`);
11213
11642
  if (detected.description) {
11214
- console.log(` ${chalk14.dim("Description:")} ${chalk14.gray(detected.description)}`);
11643
+ console.log(` ${chalk15.dim("Description:")} ${chalk15.gray(detected.description)}`);
11215
11644
  }
11216
- console.log(` ${chalk14.dim("Type:")} ${chalk14.white(detected.type || "application")}`);
11645
+ console.log(` ${chalk15.dim("Type:")} ${chalk15.white(detected.type || "application")}`);
11217
11646
  if (detected.license) {
11218
- console.log(` ${chalk14.dim("License:")} ${chalk14.white(detected.license.toUpperCase())}`);
11647
+ console.log(` ${chalk15.dim("License:")} ${chalk15.white(detected.license.toUpperCase())}`);
11219
11648
  }
11220
11649
  console.log();
11221
- console.log(chalk14.white(" Tech Stack"));
11222
- console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11650
+ console.log(chalk15.white(" Tech Stack"));
11651
+ console.log(chalk15.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11223
11652
  if (detected.stack.length > 0) {
11224
11653
  for (const tech of detected.stack) {
11225
- console.log(` ${chalk14.cyan("\u2022")} ${tech}`);
11654
+ console.log(` ${chalk15.cyan("\u2022")} ${tech}`);
11226
11655
  }
11227
11656
  } else {
11228
- console.log(chalk14.gray(" No tech stack detected"));
11657
+ console.log(chalk15.gray(" No tech stack detected"));
11229
11658
  }
11230
11659
  console.log();
11231
- console.log(chalk14.white(" Infrastructure"));
11232
- console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11233
- console.log(` ${chalk14.dim("Package Manager:")} ${detected.packageManager || chalk14.gray("none detected")}`);
11234
- console.log(` ${chalk14.dim("Repo Host:")} ${detected.repoHost || chalk14.gray("none detected")}`);
11235
- console.log(` ${chalk14.dim("CI/CD:")} ${detected.cicd || chalk14.gray("none detected")}`);
11236
- console.log(` ${chalk14.dim("Docker:")} ${detected.hasDocker ? chalk14.green("yes") : chalk14.gray("no")}`);
11660
+ console.log(chalk15.white(" Infrastructure"));
11661
+ console.log(chalk15.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11662
+ console.log(` ${chalk15.dim("Package Manager:")} ${detected.packageManager || chalk15.gray("none detected")}`);
11663
+ console.log(` ${chalk15.dim("Repo Host:")} ${detected.repoHost || chalk15.gray("none detected")}`);
11664
+ console.log(` ${chalk15.dim("CI/CD:")} ${detected.cicd || chalk15.gray("none detected")}`);
11665
+ console.log(` ${chalk15.dim("Docker:")} ${detected.hasDocker ? chalk15.green("yes") : chalk15.gray("no")}`);
11237
11666
  console.log();
11238
11667
  if (detected.commands && Object.keys(detected.commands).length > 0) {
11239
- console.log(chalk14.white(" Commands"));
11240
- console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11668
+ console.log(chalk15.white(" Commands"));
11669
+ console.log(chalk15.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11241
11670
  for (const [key, value] of Object.entries(detected.commands)) {
11242
11671
  if (value) {
11243
11672
  const cmdValue = Array.isArray(value) ? value.join(", ") : value;
11244
- console.log(` ${chalk14.dim(key.padEnd(10))} ${chalk14.yellow(cmdValue)}`);
11673
+ console.log(` ${chalk15.dim(key.padEnd(10))} ${chalk15.yellow(cmdValue)}`);
11245
11674
  }
11246
11675
  }
11247
11676
  console.log();
11248
11677
  }
11249
- console.log(chalk14.white(" \u{1F4A1} Recommendations"));
11250
- console.log(chalk14.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11678
+ console.log(chalk15.white(" \u{1F4A1} Recommendations"));
11679
+ console.log(chalk15.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11251
11680
  const recommendations = [];
11252
11681
  if (!detected.cicd) {
11253
11682
  recommendations.push("Add CI/CD configuration (GitHub Actions, GitLab CI, etc.)");
@@ -11266,122 +11695,83 @@ async function analyzeCommand(options) {
11266
11695
  }
11267
11696
  if (recommendations.length > 0) {
11268
11697
  for (const rec of recommendations) {
11269
- console.log(` ${chalk14.yellow("\u2022")} ${rec}`);
11698
+ console.log(` ${chalk15.yellow("\u2022")} ${rec}`);
11270
11699
  }
11271
11700
  } else {
11272
- console.log(chalk14.green(" \u2713 Project looks well-configured!"));
11701
+ console.log(chalk15.green(" \u2713 Project looks well-configured!"));
11273
11702
  }
11274
11703
  console.log();
11275
- console.log(chalk14.gray(" Run 'lynxp wizard' to generate AI IDE configuration"));
11704
+ console.log(chalk15.gray(" Run 'lynxp wizard' to generate AI IDE configuration"));
11276
11705
  console.log();
11277
11706
  }
11278
11707
 
11279
11708
  // src/commands/convert.ts
11280
- import chalk15 from "chalk";
11281
- import { readFile as readFile8, writeFile as writeFile4, access as access5 } from "fs/promises";
11282
- import { join as join8, basename as basename2, resolve as resolve2, dirname as dirname4 } from "path";
11709
+ import chalk16 from "chalk";
11710
+ import { readFile as readFile9, writeFile as writeFile5, access as access6 } from "fs/promises";
11711
+ import { basename as basename2, resolve as resolve2, dirname as dirname5 } from "path";
11283
11712
  import ora12 from "ora";
11284
11713
 
11285
11714
  // src/utils/convert-format.ts
11286
- var isCommandTarget = (target) => target.includes("-command") || target.includes("-prompt") || target.includes("-workflow");
11715
+ import { access as access5 } from "fs/promises";
11716
+ import { join as join9 } from "path";
11717
+
11718
+ // ../packages/shared/src/rules-format.ts
11719
+ function yamlString(value) {
11720
+ return JSON.stringify(value);
11721
+ }
11722
+ var cursorFrontmatter = (description) => [`description: ${yamlString(description)}`, "alwaysApply: true"];
11723
+ var PLATFORM_FRONTMATTER = {
11724
+ cursor: cursorFrontmatter,
11725
+ firebender: cursorFrontmatter,
11726
+ // Firebender reads Cursor-style .mdc rules
11727
+ kiro: () => ["inclusion: always"],
11728
+ augment: (description) => ["type: always_apply", `description: ${yamlString(description)}`],
11729
+ devin: () => ["trigger: always_on"]
11730
+ // .devin/rules/ uses Windsurf's rule frontmatter
11731
+ };
11732
+ var FRONTMATTER_PATTERN = /^---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n)+/;
11733
+ function frontmatterLines(platform2, description) {
11734
+ const keys = PLATFORM_FRONTMATTER[platform2];
11735
+ return keys ? ["---", ...keys(description), "---", ""] : [];
11736
+ }
11287
11737
  function splitFrontmatter(content) {
11288
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/);
11289
- if (!match) return { frontmatter: null, body: content };
11290
- return { frontmatter: match[1], body: content.slice(match[0].length).replace(/^\s*\n/, "") };
11291
- }
11292
- function parseMarkdownConfig(content) {
11293
- const config2 = {};
11294
- const sections = content.split(/^##\s+/m);
11295
- for (const section of sections) {
11296
- const lines = section.trim().split("\n");
11297
- if (lines.length === 0) continue;
11298
- const title = lines[0].toLowerCase().trim();
11299
- const body2 = lines.slice(1).join("\n").trim();
11300
- config2[title] = body2;
11301
- }
11302
- config2._raw = content;
11738
+ const match = content.match(FRONTMATTER_PATTERN);
11739
+ if (!match) return { frontmatter: "", body: content };
11740
+ return { frontmatter: match[0], body: content.slice(match[0].length) };
11741
+ }
11742
+ function adaptRulesContent(content, fromPlatform, toPlatform, description) {
11303
11743
  const { frontmatter, body } = splitFrontmatter(content);
11304
- config2._body = body;
11305
- if (frontmatter !== null) config2._frontmatter = frontmatter;
11306
- return config2;
11307
- }
11308
- var DEFAULT_MDC_FRONTMATTER = `description: "AI coding rules"
11309
- globs: ["**/*"]
11310
- alwaysApply: true`;
11311
- function generateTargetContent(config2, targetPlatform) {
11312
- const rawContent = config2._body ?? config2._raw ?? "";
11313
- const sourceFrontmatter = config2._frontmatter;
11314
- const mdcFrontmatter = sourceFrontmatter && /^(alwaysApply|globs)\s*:/m.test(sourceFrontmatter) ? sourceFrontmatter : DEFAULT_MDC_FRONTMATTER;
11315
- if (isCommandTarget(targetPlatform)) {
11316
- return config2._raw || "";
11317
- }
11318
- switch (targetPlatform) {
11319
- case "cursor":
11320
- case "firebender":
11321
- return `---
11322
- ${mdcFrontmatter}
11323
- ---
11324
-
11325
- ${rawContent}`;
11326
- case "kiro":
11327
- return `---
11328
- inclusion: always
11329
- ---
11330
-
11331
- ${rawContent}`;
11332
- case "augment":
11333
- return `---
11334
- type: always_apply
11335
- ---
11336
-
11337
- ${rawContent}`;
11338
- case "cursor_legacy":
11339
- return rawContent.replace(/^#\s+/gm, "").replace(/^##\s+/gm, "\n").replace(/^###\s+/gm, "").trim();
11340
- case "windsurf":
11341
- case "cline":
11342
- case "goose":
11343
- case "trae":
11344
- return rawContent;
11345
- case "tabnine":
11346
- return `# Tabnine configuration
11347
- # Converted from AI IDE configuration
11348
- instructions: |
11349
- ${rawContent.split("\n").map((line) => ` ${line}`).join("\n")}
11350
- `;
11351
- case "void":
11352
- case "continue":
11353
- case "cody":
11354
- case "codegpt":
11355
- case "supermaven":
11356
- return JSON.stringify({ instructions: rawContent }, null, 2) + "\n";
11357
- case "copilot":
11358
- return `# GitHub Copilot Instructions
11359
-
11360
- ${rawContent}
11361
- `;
11362
- case "agents":
11363
- case "universal":
11364
- case "claude":
11365
- case "codex":
11366
- case "antigravity":
11367
- case "gemini-cli":
11368
- case "zed":
11369
- case "amazonq":
11370
- case "junie":
11371
- case "warp":
11372
- case "openhands":
11373
- case "crush":
11374
- case "aider":
11375
- case "opencode":
11376
- case "firebase":
11377
- case "roocode":
11378
- case "kilocode":
11379
- default:
11380
- return rawContent;
11381
- }
11744
+ const target = PLATFORM_FRONTMATTER[toPlatform];
11745
+ if (fromPlatform === toPlatform && (frontmatter || !target)) return content;
11746
+ if (frontmatter && target && PLATFORM_FRONTMATTER[fromPlatform] === target) return content;
11747
+ const lines = frontmatterLines(toPlatform, description);
11748
+ return lines.length ? `${lines.join("\n")}
11749
+ ${body}` : body;
11382
11750
  }
11383
11751
 
11384
- // src/commands/convert.ts
11752
+ // src/utils/convert-format.ts
11753
+ var isCommandTarget = (target) => target.includes("-command") || target.includes("-prompt") || target.includes("-workflow");
11754
+ var RULES_DESCRIPTION = "AI rules for this project";
11755
+ function isStructuredPlatform(platform2) {
11756
+ const format = getPlatform(platform2)?.format;
11757
+ return format === "json" || format === "yaml";
11758
+ }
11759
+ function platformForPath(filePath) {
11760
+ const normalized = filePath.replace(/\\/g, "/").toLowerCase();
11761
+ const rules = PLATFORMS.filter((p) => !p.isCommand);
11762
+ const inFolder2 = rules.find((p) => p.rulesDirectory && `/${normalized}`.includes(`/${p.rulesDirectory.toLowerCase()}`));
11763
+ if (inFolder2) return inFolder2.id;
11764
+ const file = rules.find((p) => {
11765
+ const own = p.file.toLowerCase();
11766
+ return normalized === own || normalized.endsWith(`/${own}`);
11767
+ });
11768
+ if (file) return file.id;
11769
+ return normalized.endsWith(".mdc") ? "cursor" : void 0;
11770
+ }
11771
+ function convertContent(content, sourcePlatform, targetPlatform) {
11772
+ if (isCommandTarget(targetPlatform)) return content;
11773
+ return adaptRulesContent(content, sourcePlatform, targetPlatform, RULES_DESCRIPTION);
11774
+ }
11385
11775
  var SOURCE_FILES = {
11386
11776
  "agents.md": "agents",
11387
11777
  "claude.md": "claude",
@@ -11393,8 +11783,33 @@ var SOURCE_FILES = {
11393
11783
  "copilot-instructions.md": "copilot",
11394
11784
  ".windsurfrules": "windsurf",
11395
11785
  ".clinerules": "cline",
11396
- ".aider.conf.yml": "aider"
11786
+ // Real case: auto-detect looks the name up as is, and Linux disks are case-sensitive
11787
+ "CONVENTIONS.md": "aider"
11397
11788
  };
11789
+ var sourceFilePlatform = (name) => Object.entries(SOURCE_FILES).find(([file]) => file.toLowerCase() === name.toLowerCase())?.[1];
11790
+ async function detectSourceFile(cwd) {
11791
+ for (const [pattern, platform2] of Object.entries(SOURCE_FILES)) {
11792
+ try {
11793
+ const fullPath = join9(cwd, pattern);
11794
+ await access5(fullPath);
11795
+ return { path: fullPath, platform: platform2 };
11796
+ } catch {
11797
+ }
11798
+ }
11799
+ const uppercaseVariants = ["AGENTS.md", "CLAUDE.md"];
11800
+ for (const variant of uppercaseVariants) {
11801
+ try {
11802
+ const fullPath = join9(cwd, variant);
11803
+ await access5(fullPath);
11804
+ const platform2 = variant.toLowerCase().replace(".md", "");
11805
+ return { path: fullPath, platform: platform2 };
11806
+ } catch {
11807
+ }
11808
+ }
11809
+ return null;
11810
+ }
11811
+
11812
+ // src/commands/convert.ts
11398
11813
  var TARGET_FILES = {
11399
11814
  ...PLATFORM_FILES2,
11400
11815
  cursor_legacy: ".cursorrules",
@@ -11434,6 +11849,7 @@ var PLATFORM_NAMES = {
11434
11849
  codex: "Codex",
11435
11850
  openhands: "OpenHands",
11436
11851
  crush: "Crush",
11852
+ devin: "Devin",
11437
11853
  firebender: "Firebender",
11438
11854
  "cursor-command": "Cursor Command",
11439
11855
  "claude-command": "Claude Code Command",
@@ -11446,27 +11862,6 @@ var COMMAND_DIRS = {
11446
11862
  ...Object.fromEntries(COMMANDS.map((c) => [c.directory, c.id])),
11447
11863
  ".copilot/prompts": "copilot-prompt"
11448
11864
  };
11449
- async function detectSourceFile(cwd) {
11450
- for (const [pattern, platform2] of Object.entries(SOURCE_FILES)) {
11451
- try {
11452
- const fullPath = join8(cwd, pattern);
11453
- await access5(fullPath);
11454
- return { path: fullPath, platform: platform2 };
11455
- } catch {
11456
- }
11457
- }
11458
- const uppercaseVariants = ["AGENTS.md", "CLAUDE.md"];
11459
- for (const variant of uppercaseVariants) {
11460
- try {
11461
- const fullPath = join8(cwd, variant);
11462
- await access5(fullPath);
11463
- const platform2 = variant.toLowerCase().replace(".md", "");
11464
- return { path: fullPath, platform: platform2 };
11465
- } catch {
11466
- }
11467
- }
11468
- return null;
11469
- }
11470
11865
  function detectCommandPlatform(sourcePath) {
11471
11866
  const normalized = sourcePath.replace(/\\/g, "/").toLowerCase();
11472
11867
  for (const [dir, platform2] of Object.entries(COMMAND_DIRS)) {
@@ -11479,10 +11874,20 @@ function detectCommandPlatform(sourcePath) {
11479
11874
  async function convertCommand(first, second, options) {
11480
11875
  const [source, target] = second === void 0 ? [void 0, first] : [first, second];
11481
11876
  console.log();
11482
- console.log(chalk15.cyan.bold(" \u{1F504} LynxPrompt Convert"));
11483
- console.log(chalk15.gray(" Convert AI IDE configuration between formats"));
11877
+ console.log(chalk16.cyan.bold(" \u{1F504} LynxPrompt Convert"));
11878
+ console.log(chalk16.gray(" Convert AI IDE configuration between formats"));
11484
11879
  console.log();
11485
11880
  const cwd = process.cwd();
11881
+ const mcpTarget = MCP_CONFIGS.find((c) => c.id === target.toLowerCase());
11882
+ const mcpSource = source !== void 0 && matchConfigFile(source)?.type === "MCP_CONFIG";
11883
+ if (mcpTarget || mcpSource) {
11884
+ if (!mcpTarget) {
11885
+ console.log(chalk16.red(` \u2717 An MCP config converts only to another tool's MCP config: ${MCP_CONFIGS.map((c) => c.id).join(", ")}`));
11886
+ process.exit(1);
11887
+ }
11888
+ await convertMcp(cwd, source, mcpTarget, options);
11889
+ return;
11890
+ }
11486
11891
  let sourcePath;
11487
11892
  let sourcePlatform;
11488
11893
  if (source) {
@@ -11491,27 +11896,26 @@ async function convertCommand(first, second, options) {
11491
11896
  if (cmdPlatform) {
11492
11897
  sourcePlatform = cmdPlatform;
11493
11898
  } else {
11494
- const sourceBasename = basename2(source).toLowerCase();
11495
- sourcePlatform = SOURCE_FILES[sourceBasename] || "unknown";
11899
+ sourcePlatform = platformForPath(source) || sourceFilePlatform(basename2(source)) || "unknown";
11496
11900
  }
11497
11901
  } else {
11498
11902
  const detected = await detectSourceFile(cwd);
11499
11903
  if (!detected) {
11500
- console.log(chalk15.red(" \u2717 No AI configuration file found in current directory"));
11904
+ console.log(chalk16.red(" \u2717 No AI configuration file found in current directory"));
11501
11905
  console.log();
11502
- console.log(chalk15.gray(" Supported source files:"));
11906
+ console.log(chalk16.gray(" Supported source files:"));
11503
11907
  for (const file of Object.keys(SOURCE_FILES)) {
11504
- console.log(chalk15.gray(` \u2022 ${file}`));
11908
+ console.log(chalk16.gray(` \u2022 ${file}`));
11505
11909
  }
11506
11910
  console.log();
11507
- console.log(chalk15.gray(" Command directories (for slash commands):"));
11911
+ console.log(chalk16.gray(" Command directories (for slash commands):"));
11508
11912
  for (const dir of Object.keys(COMMAND_DIRS)) {
11509
- console.log(chalk15.gray(` \u2022 ${dir}/*.md`));
11913
+ console.log(chalk16.gray(` \u2022 ${dir}/*.md`));
11510
11914
  }
11511
11915
  console.log();
11512
- console.log(chalk15.gray(" Usage: lynxp convert <source> <target>"));
11513
- console.log(chalk15.gray(" Example: lynxp convert AGENTS.md cursor"));
11514
- console.log(chalk15.gray(" Example: lynxp convert .cursor/commands/deploy.md claude-command"));
11916
+ console.log(chalk16.gray(" Usage: lynxp convert <source> <target>"));
11917
+ console.log(chalk16.gray(" Example: lynxp convert AGENTS.md cursor"));
11918
+ console.log(chalk16.gray(" Example: lynxp convert .cursor/commands/deploy.md claude-command"));
11515
11919
  process.exit(1);
11516
11920
  }
11517
11921
  sourcePath = detected.path;
@@ -11520,27 +11924,36 @@ async function convertCommand(first, second, options) {
11520
11924
  const targetLower = target.toLowerCase();
11521
11925
  const normalizedTarget = TARGET_FILES[targetLower] ? targetLower : targetLower.replace(/-/g, "_");
11522
11926
  if (!TARGET_FILES[normalizedTarget]) {
11523
- console.log(chalk15.red(` \u2717 Unknown target format: ${target}`));
11927
+ console.log(chalk16.red(` \u2717 Unknown target format: ${target}`));
11524
11928
  console.log();
11525
- console.log(chalk15.gray(" Supported target formats:"));
11929
+ console.log(chalk16.gray(" Supported target formats:"));
11526
11930
  for (const [key, name] of Object.entries(PLATFORM_NAMES)) {
11527
- console.log(chalk15.gray(` \u2022 ${key.padEnd(15)} \u2192 ${name}`));
11931
+ if (isStructuredPlatform(key)) continue;
11932
+ console.log(chalk16.gray(` \u2022 ${key.padEnd(15)} \u2192 ${name}`));
11528
11933
  }
11934
+ for (const c of MCP_CONFIGS) {
11935
+ console.log(chalk16.gray(` \u2022 ${c.id.padEnd(15)} \u2192 MCP servers for ${c.name} (${c.file})`));
11936
+ }
11937
+ process.exit(1);
11938
+ }
11939
+ const structured = [sourcePlatform, normalizedTarget].find(isStructuredPlatform);
11940
+ if (structured) {
11941
+ console.log(chalk16.red(` \u2717 ${PLATFORM_NAMES[structured] || structured} uses a JSON/YAML config, not a rules file, so it cannot be converted`));
11942
+ console.log(chalk16.gray(` Run 'lynxp wizard' and pick ${PLATFORM_NAMES[structured] || structured} to generate one`));
11529
11943
  process.exit(1);
11530
11944
  }
11531
11945
  let sourceContent;
11532
11946
  try {
11533
- sourceContent = await readFile8(sourcePath, "utf-8");
11947
+ sourceContent = await readFile9(sourcePath, "utf-8");
11534
11948
  } catch {
11535
- console.log(chalk15.red(` \u2717 Could not read source file: ${sourcePath}`));
11949
+ console.log(chalk16.red(` \u2717 Could not read source file: ${sourcePath}`));
11536
11950
  process.exit(1);
11537
11951
  }
11538
- console.log(chalk15.white(` Source: ${basename2(sourcePath)} (${PLATFORM_NAMES[sourcePlatform] || sourcePlatform})`));
11539
- console.log(chalk15.white(` Target: ${PLATFORM_NAMES[normalizedTarget]}`));
11952
+ console.log(chalk16.white(` Source: ${basename2(sourcePath)} (${PLATFORM_NAMES[sourcePlatform] || sourcePlatform})`));
11953
+ console.log(chalk16.white(` Target: ${PLATFORM_NAMES[normalizedTarget]}`));
11540
11954
  console.log();
11541
11955
  const spinner = ora12("Converting configuration...").start();
11542
- const config2 = parseMarkdownConfig(sourceContent);
11543
- const targetContent = generateTargetContent(config2, normalizedTarget);
11956
+ const targetContent = convertContent(sourceContent, sourcePlatform, normalizedTarget);
11544
11957
  spinner.stop();
11545
11958
  let outputFilename = options.output || TARGET_FILES[normalizedTarget];
11546
11959
  const targetCommand = COMMANDS.find((c) => c.id === normalizedTarget);
@@ -11550,35 +11963,71 @@ async function convertCommand(first, second, options) {
11550
11963
  }
11551
11964
  const outputPath = resolve2(cwd, outputFilename);
11552
11965
  try {
11553
- await access5(outputPath);
11966
+ await access6(outputPath);
11554
11967
  if (!options.force) {
11555
- console.log(chalk15.yellow(` \u26A0\uFE0F File already exists: ${outputFilename}`));
11556
- console.log(chalk15.gray(" Use --force to overwrite"));
11968
+ console.log(chalk16.yellow(` \u26A0\uFE0F File already exists: ${outputFilename}`));
11969
+ console.log(chalk16.gray(" Use --force to overwrite"));
11557
11970
  process.exit(1);
11558
11971
  }
11559
11972
  } catch {
11560
11973
  }
11561
11974
  try {
11562
- const { mkdir: mkdir4 } = await import("fs/promises");
11563
- const outputDir = dirname4(outputPath);
11975
+ const { mkdir: mkdir5 } = await import("fs/promises");
11976
+ const outputDir = dirname5(outputPath);
11564
11977
  if (outputDir !== cwd) {
11565
- await mkdir4(outputDir, { recursive: true });
11978
+ await mkdir5(outputDir, { recursive: true });
11566
11979
  }
11567
- await writeFile4(outputPath, targetContent, "utf-8");
11568
- console.log(chalk15.green(` \u2713 Converted to ${outputFilename}`));
11980
+ await writeFile5(outputPath, targetContent, "utf-8");
11981
+ console.log(chalk16.green(` \u2713 Converted to ${outputFilename}`));
11569
11982
  console.log();
11570
- console.log(chalk15.gray(` Lines: ${targetContent.split("\n").length}`));
11571
- console.log(chalk15.gray(` Size: ${targetContent.length} bytes`));
11983
+ console.log(chalk16.gray(` Lines: ${targetContent.split("\n").length}`));
11984
+ console.log(chalk16.gray(` Size: ${targetContent.length} bytes`));
11572
11985
  console.log();
11573
11986
  } catch (error) {
11574
- console.log(chalk15.red(` \u2717 Could not write output: ${error instanceof Error ? error.message : "unknown error"}`));
11987
+ console.log(chalk16.red(` \u2717 Could not write output: ${error instanceof Error ? error.message : "unknown error"}`));
11988
+ process.exit(1);
11989
+ }
11990
+ }
11991
+ async function convertMcp(cwd, source, target, options) {
11992
+ let sourceFile = source;
11993
+ if (sourceFile === void 0) {
11994
+ for (const c of MCP_CONFIGS) {
11995
+ if (c.id !== target.id && await access6(resolve2(cwd, c.file)).then(() => true, () => false)) {
11996
+ sourceFile = c.file;
11997
+ break;
11998
+ }
11999
+ }
12000
+ }
12001
+ if (sourceFile === void 0) {
12002
+ console.log(chalk16.red(" \u2717 No MCP config found. Looked for:"));
12003
+ for (const c of MCP_CONFIGS) console.log(chalk16.gray(` \u2022 ${c.file}`));
11575
12004
  process.exit(1);
11576
12005
  }
12006
+ let converted;
12007
+ try {
12008
+ converted = convertMcpConfig(await readFile9(resolve2(cwd, sourceFile), "utf-8"), target.id);
12009
+ } catch (error) {
12010
+ console.log(chalk16.red(` \u2717 Could not convert ${sourceFile}: ${error instanceof Error ? error.message : "unreadable"}`));
12011
+ process.exit(1);
12012
+ }
12013
+ const outputFile = options.output || target.file;
12014
+ const outputPath = resolve2(cwd, outputFile);
12015
+ const exists = await access6(outputPath).then(() => true, () => false);
12016
+ if (exists && !options.force) {
12017
+ console.log(chalk16.yellow(` \u26A0\uFE0F File already exists: ${outputFile}`));
12018
+ console.log(chalk16.gray(" Use --force to overwrite"));
12019
+ process.exit(1);
12020
+ }
12021
+ const { mkdir: mkdir5 } = await import("fs/promises");
12022
+ await mkdir5(dirname5(outputPath), { recursive: true });
12023
+ await writeFile5(outputPath, converted, "utf-8");
12024
+ console.log(chalk16.green(` \u2713 Converted ${sourceFile} to ${outputFile} (MCP servers for ${target.name})`));
12025
+ console.log();
11577
12026
  }
11578
12027
 
11579
12028
  // src/commands/merge.ts
11580
- import chalk16 from "chalk";
11581
- import { readFile as readFile9, writeFile as writeFile5, access as access6 } from "fs/promises";
12029
+ import chalk17 from "chalk";
12030
+ import { readFile as readFile10, writeFile as writeFile6, access as access7 } from "fs/promises";
11582
12031
  import { basename as basename3, resolve as resolve3 } from "path";
11583
12032
  import ora13 from "ora";
11584
12033
  import prompts5 from "prompts";
@@ -11675,43 +12124,43 @@ ${s.content.trim()}`).join("\n\n");
11675
12124
  // src/commands/merge.ts
11676
12125
  async function mergeCommand(files, options) {
11677
12126
  console.log();
11678
- console.log(chalk16.cyan.bold(" \u{1F500} LynxPrompt Merge"));
11679
- console.log(chalk16.gray(" Merge multiple AI IDE configuration files"));
12127
+ console.log(chalk17.cyan.bold(" \u{1F500} LynxPrompt Merge"));
12128
+ console.log(chalk17.gray(" Merge multiple AI IDE configuration files"));
11680
12129
  console.log();
11681
12130
  if (files.length < 2) {
11682
- console.log(chalk16.red(" \u2717 Please provide at least 2 files to merge"));
12131
+ console.log(chalk17.red(" \u2717 Please provide at least 2 files to merge"));
11683
12132
  console.log();
11684
- console.log(chalk16.gray(" Usage: lynxp merge <file1> <file2> [...files]"));
11685
- console.log(chalk16.gray(" Example: lynxp merge AGENTS.md team-rules.md --output merged.md"));
12133
+ console.log(chalk17.gray(" Usage: lynxp merge <file1> <file2> [...files]"));
12134
+ console.log(chalk17.gray(" Example: lynxp merge AGENTS.md team-rules.md --output merged.md"));
11686
12135
  console.log();
11687
- console.log(chalk16.gray(" Options:"));
11688
- console.log(chalk16.gray(" --output <file> Output filename (default: merged.md)"));
11689
- console.log(chalk16.gray(" --strategy <type> Merge strategy: concat, sections, smart (default: smart)"));
11690
- console.log(chalk16.gray(" --force Overwrite existing output file"));
11691
- console.log(chalk16.gray(" --interactive Review and select sections to include"));
12136
+ console.log(chalk17.gray(" Options:"));
12137
+ console.log(chalk17.gray(" --output <file> Output filename (default: merged.md)"));
12138
+ console.log(chalk17.gray(" --strategy <type> Merge strategy: concat, sections, smart (default: smart)"));
12139
+ console.log(chalk17.gray(" --force Overwrite existing output file"));
12140
+ console.log(chalk17.gray(" --interactive Review and select sections to include"));
11692
12141
  process.exit(1);
11693
12142
  }
11694
12143
  const cwd = process.cwd();
11695
12144
  const strategy = options.strategy || "smart";
11696
- console.log(chalk16.white(` Files to merge: ${files.length}`));
11697
- console.log(chalk16.white(` Strategy: ${strategy}`));
12145
+ console.log(chalk17.white(` Files to merge: ${files.length}`));
12146
+ console.log(chalk17.white(` Strategy: ${strategy}`));
11698
12147
  console.log();
11699
12148
  const allSections = [];
11700
12149
  for (const file of files) {
11701
12150
  const filePath = resolve3(cwd, file);
11702
12151
  try {
11703
- await access6(filePath);
12152
+ await access7(filePath);
11704
12153
  } catch {
11705
- console.log(chalk16.red(` \u2717 File not found: ${file}`));
12154
+ console.log(chalk17.red(` \u2717 File not found: ${file}`));
11706
12155
  process.exit(1);
11707
12156
  }
11708
12157
  try {
11709
- const content = await readFile9(filePath, "utf-8");
12158
+ const content = await readFile10(filePath, "utf-8");
11710
12159
  const sections = parseMarkdownSections(content, basename3(file));
11711
12160
  allSections.push(sections);
11712
- console.log(chalk16.gray(` \u2713 Read ${file} (${sections.length} sections)`));
12161
+ console.log(chalk17.gray(` \u2713 Read ${file} (${sections.length} sections)`));
11713
12162
  } catch (error) {
11714
- console.log(chalk16.red(` \u2717 Could not read ${file}: ${error instanceof Error ? error.message : "unknown"}`));
12163
+ console.log(chalk17.red(` \u2717 Could not read ${file}: ${error instanceof Error ? error.message : "unknown"}`));
11715
12164
  process.exit(1);
11716
12165
  }
11717
12166
  }
@@ -11734,7 +12183,7 @@ async function mergeCommand(files, options) {
11734
12183
  hint: "- Space to toggle, Enter to confirm"
11735
12184
  });
11736
12185
  if (!response.sections || response.sections.length === 0) {
11737
- console.log(chalk16.yellow(" No sections selected, aborting."));
12186
+ console.log(chalk17.yellow(" No sections selected, aborting."));
11738
12187
  process.exit(0);
11739
12188
  }
11740
12189
  const selectedSections = response.sections.map((i) => flatSections[i]);
@@ -11757,34 +12206,34 @@ ${mergedContent}
11757
12206
  const outputFilename = options.output || "merged.md";
11758
12207
  const outputPath = resolve3(cwd, outputFilename);
11759
12208
  try {
11760
- await access6(outputPath);
12209
+ await access7(outputPath);
11761
12210
  if (!options.force) {
11762
- console.log(chalk16.yellow(` \u26A0\uFE0F File already exists: ${outputFilename}`));
11763
- console.log(chalk16.gray(" Use --force to overwrite"));
12211
+ console.log(chalk17.yellow(` \u26A0\uFE0F File already exists: ${outputFilename}`));
12212
+ console.log(chalk17.gray(" Use --force to overwrite"));
11764
12213
  process.exit(1);
11765
12214
  }
11766
12215
  } catch {
11767
12216
  }
11768
12217
  try {
11769
- await writeFile5(outputPath, finalContent, "utf-8");
11770
- console.log(chalk16.green(` \u2713 Merged to ${outputFilename}`));
12218
+ await writeFile6(outputPath, finalContent, "utf-8");
12219
+ console.log(chalk17.green(` \u2713 Merged to ${outputFilename}`));
11771
12220
  console.log();
11772
- console.log(chalk16.gray(` Sources: ${files.length} files`));
11773
- console.log(chalk16.gray(` Lines: ${finalContent.split("\n").length}`));
11774
- console.log(chalk16.gray(` Size: ${finalContent.length} bytes`));
12221
+ console.log(chalk17.gray(` Sources: ${files.length} files`));
12222
+ console.log(chalk17.gray(` Lines: ${finalContent.split("\n").length}`));
12223
+ console.log(chalk17.gray(` Size: ${finalContent.length} bytes`));
11775
12224
  console.log();
11776
12225
  } catch (error) {
11777
- console.log(chalk16.red(` \u2717 Could not write output: ${error instanceof Error ? error.message : "unknown error"}`));
12226
+ console.log(chalk17.red(` \u2717 Could not write output: ${error instanceof Error ? error.message : "unknown error"}`));
11778
12227
  process.exit(1);
11779
12228
  }
11780
12229
  }
11781
12230
 
11782
12231
  // src/commands/import.ts
11783
- import chalk17 from "chalk";
12232
+ import chalk18 from "chalk";
11784
12233
  import prompts6 from "prompts";
11785
12234
  import ora14 from "ora";
11786
- import { readFile as readFile10, access as access7, readdir as readdir3 } from "fs/promises";
11787
- import { join as join9, relative as relative3, dirname as dirname5, basename as basename4, resolve as resolve4 } from "path";
12235
+ import { readFile as readFile11, access as access8, readdir as readdir3 } from "fs/promises";
12236
+ import { join as join10, relative as relative3, dirname as dirname6, basename as basename4, resolve as resolve4 } from "path";
11788
12237
  var CONFIG_FILE_PATTERNS = [
11789
12238
  "AGENTS.md",
11790
12239
  "CLAUDE.md",
@@ -11801,7 +12250,7 @@ async function scanDirectory(rootPath, options = {}) {
11801
12250
  try {
11802
12251
  const entries = await readdir3(dir, { withFileTypes: true });
11803
12252
  for (const entry of entries) {
11804
- const fullPath = join9(dir, entry.name);
12253
+ const fullPath = join10(dir, entry.name);
11805
12254
  if (entry.isDirectory()) {
11806
12255
  const skipDirs = [
11807
12256
  "node_modules",
@@ -11827,13 +12276,13 @@ async function scanDirectory(rootPath, options = {}) {
11827
12276
  } else if (entry.isFile()) {
11828
12277
  if (patterns.includes(entry.name)) {
11829
12278
  try {
11830
- const content = await readFile10(fullPath, "utf-8");
12279
+ const content = await readFile11(fullPath, "utf-8");
11831
12280
  const relativePath = relative3(rootPath, fullPath);
11832
12281
  files.push({
11833
12282
  path: fullPath,
11834
12283
  relativePath,
11835
12284
  depth,
11836
- name: extractProjectName(content, dirname5(fullPath)),
12285
+ name: extractProjectName(content, dirname6(fullPath)),
11837
12286
  content,
11838
12287
  sections: parseMarkdownSections2(content)
11839
12288
  });
@@ -11900,13 +12349,13 @@ function buildHierarchy(files, _rootPath) {
11900
12349
  const potentialRoots = sorted.filter((f) => f.depth <= 1);
11901
12350
  if (potentialRoots.length === 0 && files.length > 0) {
11902
12351
  return files.map((f) => ({
11903
- rootPath: dirname5(f.path),
12352
+ rootPath: dirname6(f.path),
11904
12353
  root: f,
11905
12354
  children: []
11906
12355
  }));
11907
12356
  }
11908
12357
  for (const root of potentialRoots) {
11909
- const rootDir = dirname5(root.path);
12358
+ const rootDir = dirname6(root.path);
11910
12359
  const children = files.filter((f) => {
11911
12360
  if (f === root) return false;
11912
12361
  return f.path.startsWith(rootDir + "/") && f.depth > root.depth;
@@ -11924,7 +12373,7 @@ function buildHierarchy(files, _rootPath) {
11924
12373
  const orphans = files.filter((f) => !assignedPaths.has(f.path));
11925
12374
  for (const orphan of orphans) {
11926
12375
  hierarchies.push({
11927
- rootPath: dirname5(orphan.path),
12376
+ rootPath: dirname6(orphan.path),
11928
12377
  root: orphan,
11929
12378
  children: []
11930
12379
  });
@@ -11937,33 +12386,33 @@ function displayResults(result, options) {
11937
12386
  return;
11938
12387
  }
11939
12388
  console.log();
11940
- console.log(chalk17.cyan.bold(" \u{1F4E5} Import Results"));
12389
+ console.log(chalk18.cyan.bold(" \u{1F4E5} Import Results"));
11941
12390
  console.log();
11942
12391
  if (result.totalFound === 0) {
11943
- console.log(chalk17.yellow(" No AGENTS.md or compatible files found."));
11944
- console.log(chalk17.gray(" Try running from your project root, or use --pattern to specify custom filenames."));
12392
+ console.log(chalk18.yellow(" No AGENTS.md or compatible files found."));
12393
+ console.log(chalk18.gray(" Try running from your project root, or use --pattern to specify custom filenames."));
11945
12394
  return;
11946
12395
  }
11947
- console.log(chalk17.green(` Found ${result.totalFound} configuration file(s)`));
12396
+ console.log(chalk18.green(` Found ${result.totalFound} configuration file(s)`));
11948
12397
  console.log();
11949
12398
  for (const hierarchy of result.hierarchy) {
11950
12399
  const isMonorepo = hierarchy.children.length > 0;
11951
12400
  if (hierarchy.root) {
11952
12401
  const icon = isMonorepo ? "\u{1F3E2}" : "\u{1F4C4}";
11953
- const typeLabel = isMonorepo ? chalk17.magenta("[Monorepo Root]") : chalk17.blue("[Standalone]");
11954
- console.log(` ${icon} ${chalk17.white.bold(hierarchy.root.name)} ${typeLabel}`);
11955
- console.log(chalk17.gray(` ${hierarchy.root.relativePath}`));
12402
+ const typeLabel = isMonorepo ? chalk18.magenta("[Monorepo Root]") : chalk18.blue("[Standalone]");
12403
+ console.log(` ${icon} ${chalk18.white.bold(hierarchy.root.name)} ${typeLabel}`);
12404
+ console.log(chalk18.gray(` ${hierarchy.root.relativePath}`));
11956
12405
  if (options.verbose && hierarchy.root.sections.length > 0) {
11957
12406
  const sectionNames = hierarchy.root.sections.slice(0, 5).map((s) => s.title);
11958
- console.log(chalk17.gray(` Sections: ${sectionNames.join(", ")}${hierarchy.root.sections.length > 5 ? "..." : ""}`));
12407
+ console.log(chalk18.gray(` Sections: ${sectionNames.join(", ")}${hierarchy.root.sections.length > 5 ? "..." : ""}`));
11959
12408
  }
11960
12409
  }
11961
12410
  for (const child of hierarchy.children) {
11962
- console.log(` \u2514\u2500 \u{1F4C4} ${chalk17.white(child.name)}`);
11963
- console.log(chalk17.gray(` ${child.relativePath}`));
12411
+ console.log(` \u2514\u2500 \u{1F4C4} ${chalk18.white(child.name)}`);
12412
+ console.log(chalk18.gray(` ${child.relativePath}`));
11964
12413
  if (options.verbose && child.sections.length > 0) {
11965
12414
  const sectionNames = child.sections.slice(0, 3).map((s) => s.title);
11966
- console.log(chalk17.gray(` Sections: ${sectionNames.join(", ")}${child.sections.length > 3 ? "..." : ""}`));
12415
+ console.log(chalk18.gray(` Sections: ${sectionNames.join(", ")}${child.sections.length > 3 ? "..." : ""}`));
11967
12416
  }
11968
12417
  }
11969
12418
  console.log();
@@ -11971,16 +12420,16 @@ function displayResults(result, options) {
11971
12420
  const monorepoCount = result.hierarchy.filter((h) => h.children.length > 0).length;
11972
12421
  const standaloneCount = result.hierarchy.filter((h) => h.children.length === 0).length;
11973
12422
  if (monorepoCount > 0) {
11974
- console.log(chalk17.cyan(` \u{1F4CA} ${monorepoCount} monorepo(s) detected with hierarchical configs`));
12423
+ console.log(chalk18.cyan(` \u{1F4CA} ${monorepoCount} monorepo(s) detected with hierarchical configs`));
11975
12424
  }
11976
12425
  if (standaloneCount > 0) {
11977
- console.log(chalk17.cyan(` \u{1F4CA} ${standaloneCount} standalone config(s)`));
12426
+ console.log(chalk18.cyan(` \u{1F4CA} ${standaloneCount} standalone config(s)`));
11978
12427
  }
11979
12428
  if (result.errors.length > 0) {
11980
12429
  console.log();
11981
- console.log(chalk17.yellow(" \u26A0\uFE0F Warnings:"));
12430
+ console.log(chalk18.yellow(" \u26A0\uFE0F Warnings:"));
11982
12431
  for (const error of result.errors) {
11983
- console.log(chalk17.gray(` ${error}`));
12432
+ console.log(chalk18.gray(` ${error}`));
11984
12433
  }
11985
12434
  }
11986
12435
  }
@@ -11988,15 +12437,15 @@ async function importCommand(path3 = ".", options) {
11988
12437
  const json = Boolean(options.json);
11989
12438
  if (!json) {
11990
12439
  console.log();
11991
- console.log(chalk17.cyan.bold(" \u{1F4E5} LynxPrompt Import"));
11992
- console.log(chalk17.gray(" Scan and import AGENTS.md files and AI commands from your repository"));
12440
+ console.log(chalk18.cyan.bold(" \u{1F4E5} LynxPrompt Import"));
12441
+ console.log(chalk18.gray(" Scan and import AGENTS.md files and AI commands from your repository"));
11993
12442
  console.log();
11994
12443
  }
11995
12444
  const rootPath = resolve4(process.cwd(), path3);
11996
12445
  try {
11997
- await access7(rootPath);
12446
+ await access8(rootPath);
11998
12447
  } catch {
11999
- console.error(chalk17.red(` \u2717 Path not found: ${rootPath}`));
12448
+ console.error(chalk18.red(` \u2717 Path not found: ${rootPath}`));
12000
12449
  process.exit(1);
12001
12450
  }
12002
12451
  const spinner = ora14({ text: "Scanning for configuration files and commands...", isSilent: json }).start();
@@ -12011,10 +12460,10 @@ async function importCommand(path3 = ".", options) {
12011
12460
  }
12012
12461
  spinner.warn("No configuration files or commands found");
12013
12462
  console.log();
12014
- console.log(chalk17.gray(" Looking for:"));
12015
- console.log(chalk17.gray(" \u2022 Rules: AGENTS.md, CLAUDE.md, .cursorrules, .windsurfrules"));
12016
- console.log(chalk17.gray(" \u2022 Commands: .cursor/commands/*.md, .claude/commands/*.md"));
12017
- console.log(chalk17.gray(" Try specifying a different path or use --pattern for custom filenames"));
12463
+ console.log(chalk18.gray(" Looking for:"));
12464
+ console.log(chalk18.gray(" \u2022 Rules: AGENTS.md, CLAUDE.md, .cursorrules, .windsurfrules"));
12465
+ console.log(chalk18.gray(" \u2022 Commands: .cursor/commands/*.md, .claude/commands/*.md"));
12466
+ console.log(chalk18.gray(" Try specifying a different path or use --pattern for custom filenames"));
12018
12467
  return;
12019
12468
  }
12020
12469
  if (json) spinner.stop();
@@ -12031,31 +12480,31 @@ async function importCommand(path3 = ".", options) {
12031
12480
  if (json) return;
12032
12481
  if (commands.length > 0) {
12033
12482
  console.log();
12034
- console.log(chalk17.cyan.bold(" \u26A1 AI Agent Commands"));
12483
+ console.log(chalk18.cyan.bold(" \u26A1 AI Agent Commands"));
12035
12484
  console.log();
12036
12485
  const cursorCommands = commands.filter((c) => c.type === "cursor-command");
12037
12486
  const claudeCommands = commands.filter((c) => c.type === "claude-command");
12038
12487
  if (cursorCommands.length > 0) {
12039
- console.log(chalk17.blue(` Cursor Commands (${cursorCommands.length}):`));
12488
+ console.log(chalk18.blue(` Cursor Commands (${cursorCommands.length}):`));
12040
12489
  for (const cmd of cursorCommands) {
12041
- console.log(chalk17.gray(` \u26A1 ${cmd.name}`));
12042
- console.log(chalk17.gray(` ${relative3(rootPath, cmd.path)}`));
12490
+ console.log(chalk18.gray(` \u26A1 ${cmd.name}`));
12491
+ console.log(chalk18.gray(` ${relative3(rootPath, cmd.path)}`));
12043
12492
  }
12044
12493
  console.log();
12045
12494
  }
12046
12495
  if (claudeCommands.length > 0) {
12047
- console.log(chalk17.yellow(` Claude Commands (${claudeCommands.length}):`));
12496
+ console.log(chalk18.yellow(` Claude Commands (${claudeCommands.length}):`));
12048
12497
  for (const cmd of claudeCommands) {
12049
- console.log(chalk17.gray(` \u{1F9E0} ${cmd.name}`));
12050
- console.log(chalk17.gray(` ${relative3(rootPath, cmd.path)}`));
12498
+ console.log(chalk18.gray(` \u{1F9E0} ${cmd.name}`));
12499
+ console.log(chalk18.gray(` ${relative3(rootPath, cmd.path)}`));
12051
12500
  }
12052
12501
  console.log();
12053
12502
  }
12054
- console.log(chalk17.cyan(" \u{1F4A1} Push commands with: lynxp push .cursor/commands/my-command.md"));
12503
+ console.log(chalk18.cyan(" \u{1F4A1} Push commands with: lynxp push .cursor/commands/my-command.md"));
12055
12504
  }
12056
12505
  if (options.dryRun) {
12057
- console.log(chalk17.yellow(" \u{1F4CB} Dry run complete - no changes made"));
12058
- console.log(chalk17.gray(" Remove --dry-run to proceed with import"));
12506
+ console.log(chalk18.yellow(" \u{1F4CB} Dry run complete - no changes made"));
12507
+ console.log(chalk18.gray(" Remove --dry-run to proceed with import"));
12059
12508
  return;
12060
12509
  }
12061
12510
  if (!options.json) {
@@ -12074,15 +12523,15 @@ async function importCommand(path3 = ".", options) {
12074
12523
  });
12075
12524
  switch (action) {
12076
12525
  case "convert":
12077
- console.log(chalk17.cyan("\n \u{1F4A1} Use 'lynxp convert <file> agents' to convert individual files"));
12078
- console.log(chalk17.cyan(" or 'lynxp merge <files...>' to combine multiple configs\n"));
12526
+ console.log(chalk18.cyan("\n \u{1F4A1} Use 'lynxp convert <file> agents' to convert individual files"));
12527
+ console.log(chalk18.cyan(" or 'lynxp merge <files...>' to combine multiple configs\n"));
12079
12528
  break;
12080
12529
  case "save":
12081
12530
  await saveHierarchyInfo(rootPath, result);
12082
12531
  break;
12083
12532
  case "push":
12084
- console.log(chalk17.cyan("\n \u{1F4A1} Use 'lynxp push <file>' to push files individually"));
12085
- console.log(chalk17.cyan(" Requires login: 'lynxp login'\n"));
12533
+ console.log(chalk18.cyan("\n \u{1F4A1} Use 'lynxp push <file>' to push files individually"));
12534
+ console.log(chalk18.cyan(" Requires login: 'lynxp login'\n"));
12086
12535
  break;
12087
12536
  default:
12088
12537
  break;
@@ -12090,15 +12539,15 @@ async function importCommand(path3 = ".", options) {
12090
12539
  }
12091
12540
  } catch (error) {
12092
12541
  spinner.fail("Import failed");
12093
- console.error(chalk17.red(` Error: ${error instanceof Error ? error.message : "Unknown error"}`));
12542
+ console.error(chalk18.red(` Error: ${error instanceof Error ? error.message : "Unknown error"}`));
12094
12543
  process.exit(1);
12095
12544
  }
12096
12545
  }
12097
12546
  async function saveHierarchyInfo(rootPath, result) {
12098
- const { writeFile: writeFile6, mkdir: mkdir4 } = await import("fs/promises");
12099
- const configDir = join9(rootPath, ".lynxprompt");
12547
+ const { writeFile: writeFile7, mkdir: mkdir5 } = await import("fs/promises");
12548
+ const configDir = join10(rootPath, ".lynxprompt");
12100
12549
  try {
12101
- await mkdir4(configDir, { recursive: true });
12550
+ await mkdir5(configDir, { recursive: true });
12102
12551
  const hierarchyInfo = {
12103
12552
  scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
12104
12553
  rootPath,
@@ -12114,25 +12563,25 @@ async function saveHierarchyInfo(rootPath, result) {
12114
12563
  }))
12115
12564
  }))
12116
12565
  };
12117
- await writeFile6(
12118
- join9(configDir, "hierarchy.json"),
12566
+ await writeFile7(
12567
+ join10(configDir, "hierarchy.json"),
12119
12568
  JSON.stringify(hierarchyInfo, null, 2),
12120
12569
  "utf-8"
12121
12570
  );
12122
- console.log(chalk17.green("\n \u2713 Hierarchy saved to .lynxprompt/hierarchy.json"));
12123
- console.log(chalk17.gray(" The wizard will use this to understand your monorepo structure"));
12571
+ console.log(chalk18.green("\n \u2713 Hierarchy saved to .lynxprompt/hierarchy.json"));
12572
+ console.log(chalk18.gray(" The wizard will use this to understand your monorepo structure"));
12124
12573
  } catch (error) {
12125
- console.error(chalk17.red(` \u2717 Failed to save hierarchy: ${error instanceof Error ? error.message : "Unknown"}`));
12574
+ console.error(chalk18.red(` \u2717 Failed to save hierarchy: ${error instanceof Error ? error.message : "Unknown"}`));
12126
12575
  }
12127
12576
  }
12128
12577
 
12129
12578
  // src/commands/hierarchies.ts
12130
- import chalk18 from "chalk";
12579
+ import chalk19 from "chalk";
12131
12580
  import ora15 from "ora";
12132
12581
  async function hierarchiesCommand(options) {
12133
12582
  if (!isAuthenticated()) {
12134
- console.log(chalk18.yellow("You need to be logged in to view hierarchies."));
12135
- console.log(chalk18.gray("Run 'lynxp login' to authenticate."));
12583
+ console.log(chalk19.yellow("You need to be logged in to view hierarchies."));
12584
+ console.log(chalk19.gray("Run 'lynxp login' to authenticate."));
12136
12585
  process.exit(1);
12137
12586
  }
12138
12587
  const spinner = ora15("Fetching hierarchies...").start();
@@ -12148,32 +12597,32 @@ async function hierarchiesCommand(options) {
12148
12597
  const { hierarchies, total } = response;
12149
12598
  if (hierarchies.length === 0) {
12150
12599
  console.log();
12151
- console.log(chalk18.yellow("No hierarchies found."));
12600
+ console.log(chalk19.yellow("No hierarchies found."));
12152
12601
  console.log();
12153
- console.log(chalk18.gray("Hierarchies are created automatically when you push AGENTS.md files from a repository."));
12154
- console.log(chalk18.gray("Run 'lynxp push AGENTS.md' in a git repository to create a hierarchy."));
12602
+ console.log(chalk19.gray("Hierarchies are created automatically when you push AGENTS.md files from a repository."));
12603
+ console.log(chalk19.gray("Run 'lynxp push AGENTS.md' in a git repository to create a hierarchy."));
12155
12604
  console.log();
12156
12605
  return;
12157
12606
  }
12158
12607
  console.log();
12159
- console.log(chalk18.cyan(`\u{1F4C1} Your Hierarchies (${total} total)`));
12608
+ console.log(chalk19.cyan(`\u{1F4C1} Your Hierarchies (${total} total)`));
12160
12609
  console.log();
12161
12610
  for (const hierarchy of hierarchies) {
12162
- console.log(chalk18.bold(` ${hierarchy.name}`));
12163
- console.log(chalk18.gray(` ID: ${hierarchy.id}`));
12611
+ console.log(chalk19.bold(` ${hierarchy.name}`));
12612
+ console.log(chalk19.gray(` ID: ${hierarchy.id}`));
12164
12613
  if (hierarchy.description) {
12165
- console.log(chalk18.gray(` ${hierarchy.description}`));
12614
+ console.log(chalk19.gray(` ${hierarchy.description}`));
12166
12615
  }
12167
- console.log(chalk18.gray(` Blueprints: ${hierarchy.blueprint_count || 0}`));
12168
- console.log(chalk18.gray(` Repository: ${hierarchy.repository_root.slice(0, 16)}...`));
12616
+ console.log(chalk19.gray(` Blueprints: ${hierarchy.blueprint_count || 0}`));
12617
+ console.log(chalk19.gray(` Repository: ${hierarchy.repository_root.slice(0, 16)}...`));
12169
12618
  console.log();
12170
12619
  }
12171
- console.log(chalk18.gray("\u2500".repeat(50)));
12620
+ console.log(chalk19.gray("\u2500".repeat(50)));
12172
12621
  console.log();
12173
- console.log(chalk18.gray("Tips:"));
12174
- console.log(chalk18.gray(` \u2022 Pull entire hierarchy: lynxp pull <hierarchy_id>`));
12175
- console.log(chalk18.gray(` \u2022 Preview a hierarchy: lynxp pull <hierarchy_id> --preview`));
12176
- console.log(chalk18.gray(` \u2022 Push local changes: lynxp push --all`));
12622
+ console.log(chalk19.gray("Tips:"));
12623
+ console.log(chalk19.gray(` \u2022 Pull entire hierarchy: lynxp pull <hierarchy_id>`));
12624
+ console.log(chalk19.gray(` \u2022 Preview a hierarchy: lynxp pull <hierarchy_id> --preview`));
12625
+ console.log(chalk19.gray(` \u2022 Push local changes: lynxp push --all`));
12177
12626
  console.log();
12178
12627
  } catch (error) {
12179
12628
  spinner.fail("Failed to fetch hierarchies");
@@ -12182,46 +12631,46 @@ async function hierarchiesCommand(options) {
12182
12631
  }
12183
12632
  function handleError2(error) {
12184
12633
  if (error instanceof ApiRequestError) {
12185
- console.error(chalk18.red(`Error: ${error.message}`));
12634
+ console.error(chalk19.red(`Error: ${error.message}`));
12186
12635
  if (error.statusCode === 401) {
12187
- console.error(chalk18.gray("Your session may have expired. Run 'lynxp login' to re-authenticate."));
12636
+ console.error(chalk19.gray("Your session may have expired. Run 'lynxp login' to re-authenticate."));
12188
12637
  } else if (error.statusCode === 403) {
12189
- console.error(chalk18.gray("You don't have permission to access hierarchies."));
12638
+ console.error(chalk19.gray("You don't have permission to access hierarchies."));
12190
12639
  }
12191
12640
  } else {
12192
- console.error(chalk18.red("An unexpected error occurred."));
12641
+ console.error(chalk19.red("An unexpected error occurred."));
12193
12642
  }
12194
12643
  process.exit(1);
12195
12644
  }
12196
12645
 
12197
12646
  // src/commands/config.ts
12198
- import chalk19 from "chalk";
12647
+ import chalk20 from "chalk";
12199
12648
  function printLoginState() {
12200
12649
  if (isAuthenticated()) {
12201
12650
  const email = getUser()?.email;
12202
- console.log(chalk19.gray(` Logged in${email ? ` as ${email}` : ""} on this instance.`));
12651
+ console.log(chalk20.gray(` Logged in${email ? ` as ${email}` : ""} on this instance.`));
12203
12652
  } else {
12204
- console.log(chalk19.gray(" Run 'lynxp login' to authenticate with this instance."));
12653
+ console.log(chalk20.gray(" Run 'lynxp login' to authenticate with this instance."));
12205
12654
  }
12206
12655
  }
12207
12656
  async function configCommand(action, valueArg) {
12208
12657
  if (!action || action === "show") {
12209
12658
  console.log();
12210
- console.log(chalk19.cyan("\u2699\uFE0F LynxPrompt CLI Configuration"));
12659
+ console.log(chalk20.cyan("\u2699\uFE0F LynxPrompt CLI Configuration"));
12211
12660
  console.log();
12212
12661
  console.log(
12213
- chalk19.white(" API URL: ") + chalk19.green(getApiUrl())
12662
+ chalk20.white(" API URL: ") + chalk20.green(getApiUrl())
12214
12663
  );
12215
12664
  const loggedIn = isAuthenticated();
12216
12665
  console.log(
12217
- chalk19.white(" Login: ") + (loggedIn ? chalk19.green(getUser()?.email ?? "token set") : chalk19.gray("not logged in"))
12666
+ chalk20.white(" Login: ") + (loggedIn ? chalk20.green(getUser()?.email ?? "token set") : chalk20.gray("not logged in"))
12218
12667
  );
12219
12668
  console.log(
12220
- chalk19.white(" Config: ") + chalk19.gray(getConfigPath())
12669
+ chalk20.white(" Config: ") + chalk20.gray(getConfigPath())
12221
12670
  );
12222
12671
  if (process.env.LYNXPROMPT_API_URL) {
12223
12672
  console.log(
12224
- chalk19.gray(
12673
+ chalk20.gray(
12225
12674
  " (API URL overridden by LYNXPROMPT_API_URL env var)"
12226
12675
  )
12227
12676
  );
@@ -12232,20 +12681,20 @@ async function configCommand(action, valueArg) {
12232
12681
  if (action === "set-url") {
12233
12682
  if (!valueArg) {
12234
12683
  console.error(
12235
- chalk19.red("Usage: lynxp config set-url <url>")
12684
+ chalk20.red("Usage: lynxp config set-url <url>")
12236
12685
  );
12237
12686
  process.exit(1);
12238
12687
  }
12239
12688
  try {
12240
12689
  new URL(valueArg);
12241
12690
  } catch {
12242
- console.error(chalk19.red(`Invalid URL: ${valueArg}`));
12691
+ console.error(chalk20.red(`Invalid URL: ${valueArg}`));
12243
12692
  process.exit(1);
12244
12693
  }
12245
12694
  const clean = valueArg.replace(/\/+$/, "");
12246
12695
  setApiUrl(clean);
12247
12696
  console.log(
12248
- chalk19.green("\u2713") + ` API URL set to ${chalk19.cyan(clean)}`
12697
+ chalk20.green("\u2713") + ` API URL set to ${chalk20.cyan(clean)}`
12249
12698
  );
12250
12699
  printLoginState();
12251
12700
  return;
@@ -12253,7 +12702,7 @@ async function configCommand(action, valueArg) {
12253
12702
  if (action === "reset-url") {
12254
12703
  const url = resetApiUrl();
12255
12704
  console.log(
12256
- chalk19.green("\u2713") + " API URL reset to " + chalk19.cyan(url)
12705
+ chalk20.green("\u2713") + " API URL reset to " + chalk20.cyan(url)
12257
12706
  );
12258
12707
  printLoginState();
12259
12708
  return;
@@ -12262,82 +12711,87 @@ async function configCommand(action, valueArg) {
12262
12711
  console.log(getConfigPath());
12263
12712
  return;
12264
12713
  }
12265
- console.error(chalk19.red(`Unknown config action: ${action}`));
12266
- console.error(chalk19.gray("Available: show, set-url <url>, reset-url, path"));
12714
+ console.error(chalk20.red(`Unknown config action: ${action}`));
12715
+ console.error(chalk20.gray("Available: show, set-url <url>, reset-url, path"));
12267
12716
  process.exit(1);
12268
12717
  }
12269
12718
 
12270
12719
  // src/index.ts
12271
- var CLI_VERSION2 = "2.1.39";
12720
+ var CLI_VERSION2 = "2.1.41";
12272
12721
  var program = new Command();
12273
12722
  program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version(CLI_VERSION2);
12274
- 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)").option("-o, --output <dir>", "Output directory (default: current directory)").option("--repo-url <url>", "Analyze remote repository URL (GitHub/GitLab supported)").option("--blueprint", "Generate with [[VARIABLE|default]] placeholders for templates").option("--license <type>", "License type (mit, apache-2.0, gpl-3.0, etc.)").option("--ci-cd <platform>", "CI/CD platform (github_actions, gitlab_ci, jenkins, etc.)").option("--project-type <type>", "Project type (work, leisure, opensource, learning)").option("--detect-only", "Only detect project info, don't generate files").option("--load-draft <name>", "Load a saved wizard draft").option("--save-draft <name>", "Save wizard state as a draft (auto-saves at end)").option("--vars <values>", "Fill variables: VAR1=value1,VAR2=value2").action(wizardCommand);
12275
- program.command("check").description("Validate AI configuration files (for CI/CD)").option("--ci", "CI mode - exit codes only (0=pass, 1=fail)").action(checkCommand);
12723
+ 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)").option("-o, --output <dir>", "Output directory (default: current directory)").option("--repo-url <url>", "Analyze remote repository URL (GitHub/GitLab supported)").option("--blueprint", "Generate with [[VARIABLE|default]] placeholders for templates").option("--license <type>", "License type (mit, apache-2.0, gpl-3.0, etc.)").option("--ci-cd <platform>", "CI/CD platform (github_actions, gitlab_ci, jenkins, etc.)").option("--project-type <type>", "Project type (work, leisure, opensource, learning)").option("--detect-only", "Only detect project info, don't generate files").option("--load-draft <name>", "Load a saved wizard draft").option("--save-draft <name>", "Save wizard state as a draft (auto-saves at end)").option("--vars <values>", "Fill variables: VAR1=value1,VAR2=value2").option("--no-pointers", "Write CLAUDE.md/GEMINI.md as full copies instead of importing AGENTS.md").action(wizardCommand);
12724
+ program.command("check").description("Validate AI configuration files (for CI/CD)").option("--ci", "CI mode - exit codes only (0=pass, 1=fail)").option("-j, --json", "Output as JSON (for scripting)").action(checkCommand);
12276
12725
  program.command("analyze").description("Analyze project configuration without generating files").option("-r, --remote <url>", "Analyze a remote repository (GitHub/GitLab)").option("-j, --json", "Output as JSON (for scripting)").action(analyzeCommand);
12277
12726
  program.command("convert <source> [target]").description("Convert AI config between formats (e.g., AGENTS.md \u2192 cursor); with one argument it is the target").option("-o, --output <file>", "Output filename").option("-f, --force", "Overwrite existing output file").action(convertCommand);
12278
12727
  program.command("merge <files...>").description("Merge multiple AI configuration files into one").option("-o, --output <file>", "Output filename (default: merged.md)").option("-s, --strategy <type>", "Merge strategy: concat, sections, smart (default: smart)").option("-f, --force", "Overwrite existing output file").option("-i, --interactive", "Review and select sections to include").action(mergeCommand);
12279
12728
  program.command("import [path]").description("Scan and import AGENTS.md files from a repository").option("--dry-run", "Preview what would be imported without changes").option("--no-recursive", "Don't scan subdirectories").option("--depth <n>", "Max directory depth to scan", "10").option("--pattern <file>", "Custom config filename to look for").option("-v, --verbose", "Show detailed section information").option("-j, --json", "Output as JSON").action(importCommand);
12280
12729
  program.command("status").description("Show current AI configuration and tracked blueprints").action(statusCommand);
12281
- program.command("pull <id>").description("Download a blueprint (bp_xxx) or entire hierarchy (ha_xxx)").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);
12282
- program.command("search <query>").description("Search public blueprints in the marketplace").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
12283
- 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);
12284
- program.command("push [file]").description("Push local file to LynxPrompt cloud as a blueprint").option("-a, --all", "Scan recursively and push ALL AI config files (40+ types)").option("-n, --name <name>", "Blueprint name").option("-d, --description <desc>", "Blueprint description").option("-v, --visibility <vis>", "Visibility: PRIVATE, TEAM, or PUBLIC (default: PRIVATE)").option("-t, --tags <tags>", "Tags (comma-separated)").option("-y, --yes", "Skip prompts").option("-f, --force", "Force push (overwrite remote changes)").option("--allow-sensitive", "Publish public content even if it looks like it holds secrets").action(pushCommand);
12730
+ program.command("pull <ids...>").description("Download blueprints (bp_xxx) or entire hierarchies (ha_xxx), one or several").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").option("--vars <values>", "Fill [[VARIABLES]]: VAR1=value1,VAR2=value2 (others get their defaults)").action(pullCommand);
12731
+ program.command("search <query>").description("Search public blueprints in the marketplace").option("-l, --limit <number>", "Number of results", "20").option("-j, --json", "Output as JSON (for scripting)").action(searchCommand);
12732
+ 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").option("-j, --json", "Output as JSON (for scripting)").action(listCommand);
12733
+ program.command("push [file]").description("Push local file to LynxPrompt cloud as a blueprint").option("-a, --all", "Scan recursively and push ALL AI config files (40+ types)").option("-n, --name <name>", "Blueprint name").option("-d, --description <desc>", "Blueprint description").option("-v, --visibility <vis>", "Visibility: PRIVATE, TEAM, or PUBLIC (default: PRIVATE)").option("-t, --tags <tags>", "Tags (comma-separated)").option("-y, --yes", "Skip prompts").option("-f, --force", "Force push (overwrite remote changes)").option("--allow-sensitive", "Push public content or MCP configs even if they look like they hold secrets").action(pushCommand);
12285
12734
  program.command("hierarchies").description("List your blueprint hierarchies (monorepo groupings)").option("-l, --limit <number>", "Number of results", "50").option("-j, --json", "Output as JSON").action(hierarchiesCommand);
12286
12735
  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);
12287
12736
  program.command("unlink [file]").description("Disconnect a local file from its cloud blueprint").action(unlinkCommand);
12288
12737
  program.command("diff [file-or-id]").description("Compare tracked files with their cloud blueprints").option("--local", "Compare .lynxprompt/rules/ with exported files").action(diffCommand);
12738
+ program.command("sync [file]").description("Pull cloud changes and push local ones for tracked files; conflicts go to <file>.remote").option("--dry-run", "Show what would change without writing or pushing").action(syncCommand);
12289
12739
  program.command("login").description("Authenticate with LynxPrompt (opens browser)").action(loginCommand);
12290
12740
  program.command("logout").description("Log out and remove stored credentials").action(logoutCommand);
12291
- program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
12741
+ program.command("whoami").description("Show current authenticated user").option("-j, --json", "Output as JSON (for scripting)").action(whoamiCommand);
12292
12742
  program.command("config [action] [value]").description("View or change CLI settings (set-url, reset-url, path)").action(configCommand);
12293
12743
  program.addHelpText(
12294
12744
  "beforeAll",
12295
12745
  `
12296
- ${chalk20.cyan("\u{1F431} LynxPrompt CLI")} ${chalk20.gray("(also available as: lynxp)")}
12297
- ${chalk20.gray("Generate AI IDE configuration files from your terminal")}
12746
+ ${chalk21.cyan("\u{1F431} LynxPrompt CLI")} ${chalk21.gray("(also available as: lynxp)")}
12747
+ ${chalk21.gray("Generate AI IDE configuration files from your terminal")}
12298
12748
  `
12299
12749
  );
12300
12750
  program.addHelpText(
12301
12751
  "after",
12302
12752
  `
12303
- ${chalk20.cyan("Quick Start:")}
12304
- ${chalk20.white("$ lynxp wizard")} ${chalk20.gray("Generate config interactively")}
12305
- ${chalk20.white("$ lynxp wizard -y")} ${chalk20.gray("Generate AGENTS.md with defaults")}
12306
- ${chalk20.white("$ lynxp wizard -f cursor")} ${chalk20.gray("Generate .cursor/rules/")}
12307
- ${chalk20.white("$ lynxp wizard --blueprint")} ${chalk20.gray("Generate with [[VAR|default]] placeholders")}
12308
-
12309
- ${chalk20.cyan("Analysis & Tools:")}
12310
- ${chalk20.white("$ lynxp analyze")} ${chalk20.gray("Analyze project tech stack")}
12311
- ${chalk20.white("$ lynxp analyze -r <url>")} ${chalk20.gray("Analyze remote repository")}
12312
- ${chalk20.white("$ lynxp import")} ${chalk20.gray("Scan repo for AGENTS.md files")}
12313
- ${chalk20.white("$ lynxp import --dry-run")} ${chalk20.gray("Preview monorepo hierarchy")}
12314
- ${chalk20.white("$ lynxp convert AGENTS.md cursor")} ${chalk20.gray("Convert to Cursor format")}
12315
- ${chalk20.white("$ lynxp merge a.md b.md -o out.md")} ${chalk20.gray("Merge multiple configs")}
12316
-
12317
- ${chalk20.cyan("Marketplace:")}
12318
- ${chalk20.white("$ lynxp search nextjs")} ${chalk20.gray("Search blueprints")}
12319
- ${chalk20.white("$ lynxp pull bp_abc123")} ${chalk20.gray("Download and track a blueprint")}
12320
- ${chalk20.white("$ lynxp pull ha_xyz789")} ${chalk20.gray("Download entire hierarchy")}
12321
- ${chalk20.white("$ lynxp push")} ${chalk20.gray("Push local file to cloud")}
12322
- ${chalk20.white("$ lynxp push --all")} ${chalk20.gray("Push ALL config files recursively")}
12323
- ${chalk20.white("$ lynxp hierarchies")} ${chalk20.gray("List your hierarchies")}
12324
- ${chalk20.white("$ lynxp link --list")} ${chalk20.gray("Show tracked blueprints")}
12325
-
12326
- ${chalk20.cyan("Blueprint Tracking:")}
12327
- ${chalk20.white("$ lynxp link AGENTS.md bp_xyz")} ${chalk20.gray("Link existing file to blueprint")}
12328
- ${chalk20.white("$ lynxp unlink AGENTS.md")} ${chalk20.gray("Disconnect from cloud")}
12329
- ${chalk20.white("$ lynxp diff bp_abc123")} ${chalk20.gray("Show changes vs cloud version")}
12330
-
12331
- ${chalk20.cyan("CI/CD:")}
12332
- ${chalk20.white("$ lynxp check --ci")} ${chalk20.gray("Validate config (exit code)")}
12333
-
12334
- ${chalk20.cyan("Configuration:")}
12335
- ${chalk20.white("$ lynxp config")} ${chalk20.gray("Show current settings")}
12336
- ${chalk20.white("$ lynxp config set-url <url>")} ${chalk20.gray("Point CLI to a self-hosted instance")}
12337
- ${chalk20.white("$ lynxp config reset-url")} ${chalk20.gray("Reset to https://lynxprompt.com")}
12338
- ${chalk20.white("$ lynxp config path")} ${chalk20.gray("Show config file location")}
12339
-
12340
- ${chalk20.gray("Docs: https://lynxprompt.com/docs/cli")}
12753
+ ${chalk21.cyan("Quick Start:")}
12754
+ ${chalk21.white("$ lynxp wizard")} ${chalk21.gray("Generate config interactively")}
12755
+ ${chalk21.white("$ lynxp wizard -y")} ${chalk21.gray("Generate AGENTS.md with defaults")}
12756
+ ${chalk21.white("$ lynxp wizard -f cursor")} ${chalk21.gray("Generate .cursor/rules/")}
12757
+ ${chalk21.white("$ lynxp wizard --blueprint")} ${chalk21.gray("Generate with [[VAR|default]] placeholders")}
12758
+
12759
+ ${chalk21.cyan("Analysis & Tools:")}
12760
+ ${chalk21.white("$ lynxp analyze")} ${chalk21.gray("Analyze project tech stack")}
12761
+ ${chalk21.white("$ lynxp analyze -r <url>")} ${chalk21.gray("Analyze remote repository")}
12762
+ ${chalk21.white("$ lynxp import")} ${chalk21.gray("Scan repo for AGENTS.md files")}
12763
+ ${chalk21.white("$ lynxp import --dry-run")} ${chalk21.gray("Preview monorepo hierarchy")}
12764
+ ${chalk21.white("$ lynxp convert AGENTS.md cursor")} ${chalk21.gray("Convert to Cursor format")}
12765
+ ${chalk21.white("$ lynxp merge a.md b.md -o out.md")} ${chalk21.gray("Merge multiple configs")}
12766
+
12767
+ ${chalk21.cyan("Marketplace:")}
12768
+ ${chalk21.white("$ lynxp search nextjs")} ${chalk21.gray("Search blueprints")}
12769
+ ${chalk21.white("$ lynxp pull bp_abc123")} ${chalk21.gray("Download and track a blueprint")}
12770
+ ${chalk21.white("$ lynxp pull ha_xyz789")} ${chalk21.gray("Download entire hierarchy")}
12771
+ ${chalk21.white("$ lynxp pull bp_a bp_b")} ${chalk21.gray("Download several at once")}
12772
+ ${chalk21.white("$ lynxp push")} ${chalk21.gray("Push local file to cloud")}
12773
+ ${chalk21.white("$ lynxp push --all")} ${chalk21.gray("Push ALL config files recursively")}
12774
+ ${chalk21.white("$ lynxp hierarchies")} ${chalk21.gray("List your hierarchies")}
12775
+ ${chalk21.white("$ lynxp link --list")} ${chalk21.gray("Show tracked blueprints")}
12776
+
12777
+ ${chalk21.cyan("Blueprint Tracking:")}
12778
+ ${chalk21.white("$ lynxp link AGENTS.md bp_xyz")} ${chalk21.gray("Link existing file to blueprint")}
12779
+ ${chalk21.white("$ lynxp unlink AGENTS.md")} ${chalk21.gray("Disconnect from cloud")}
12780
+ ${chalk21.white("$ lynxp diff bp_abc123")} ${chalk21.gray("Show changes vs cloud version")}
12781
+ ${chalk21.white("$ lynxp sync")} ${chalk21.gray("Pull cloud changes, push local ones")}
12782
+ ${chalk21.white("$ lynxp pull bp_x --vars A=1")} ${chalk21.gray("Download with [[A]] filled in")}
12783
+
12784
+ ${chalk21.cyan("CI/CD:")}
12785
+ ${chalk21.white("$ lynxp check --ci")} ${chalk21.gray("Validate config (exit code)")}
12786
+ ${chalk21.white("$ lynxp check --json")} ${chalk21.gray("Validation result as JSON")}
12787
+
12788
+ ${chalk21.cyan("Configuration:")}
12789
+ ${chalk21.white("$ lynxp config")} ${chalk21.gray("Show current settings")}
12790
+ ${chalk21.white("$ lynxp config set-url <url>")} ${chalk21.gray("Point CLI to a self-hosted instance")}
12791
+ ${chalk21.white("$ lynxp config reset-url")} ${chalk21.gray("Reset to https://lynxprompt.com")}
12792
+ ${chalk21.white("$ lynxp config path")} ${chalk21.gray("Show config file location")}
12793
+
12794
+ ${chalk21.gray("Docs: https://lynxprompt.com/docs/cli")}
12341
12795
  `
12342
12796
  );
12343
12797
  program.parse();