lynxprompt 2.1.40 → 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
@@ -278,12 +278,17 @@ var ApiClient = class {
278
278
  `/api/v1/hierarchies${query ? `?${query}` : ""}`
279
279
  );
280
280
  }
281
- 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 } = {}) {
282
286
  let apiId = id;
283
287
  if (!id.startsWith("ha_")) {
284
288
  apiId = `ha_${id}`;
285
289
  }
286
- return this.request(`/api/v1/hierarchies/${apiId}`);
290
+ const query = includeContent ? "?include=content" : "";
291
+ return this.request(`/api/v1/hierarchies/${apiId}${query}`);
287
292
  }
288
293
  async createHierarchy(data) {
289
294
  return this.request("/api/v1/hierarchies", {
@@ -533,7 +538,7 @@ function printOtherLogins() {
533
538
  // src/commands/whoami.ts
534
539
  import chalk3 from "chalk";
535
540
  import ora2 from "ora";
536
- async function whoamiCommand() {
541
+ async function whoamiCommand(options = {}) {
537
542
  if (!isAuthenticated()) {
538
543
  console.log(chalk3.yellow("Not logged in. Run 'lynxprompt login' to authenticate."));
539
544
  process.exit(1);
@@ -547,6 +552,10 @@ async function whoamiCommand() {
547
552
  email: user.email,
548
553
  name: user.name
549
554
  });
555
+ if (options.json) {
556
+ console.log(JSON.stringify({ user }, null, 2));
557
+ return;
558
+ }
550
559
  console.log();
551
560
  console.log(chalk3.cyan("\u{1F431} LynxPrompt Account"));
552
561
  console.log();
@@ -589,11 +598,16 @@ async function listCommand(options) {
589
598
  const spinner = ora3("Fetching your blueprints...").start();
590
599
  try {
591
600
  const limit = parseInt(options.limit, 10) || 20;
592
- const { blueprints, total, has_more } = await api.listBlueprints({
601
+ const response = await api.listBlueprints({
593
602
  limit,
594
603
  visibility: options.visibility
595
604
  });
596
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;
597
611
  if (blueprints.length === 0) {
598
612
  console.log();
599
613
  console.log(chalk4.yellow("No blueprints found."));
@@ -853,6 +867,7 @@ var PLATFORMS = [
853
867
  name: "Claude Code",
854
868
  file: "CLAUDE.md",
855
869
  blueprintType: "CLAUDE_MD",
870
+ importsFiles: true,
856
871
  icon: "\u{1F9E0}",
857
872
  gradient: "from-orange-500 to-amber-500",
858
873
  note: "Anthropic's agentic coding tool",
@@ -1146,6 +1161,7 @@ var PLATFORMS = [
1146
1161
  name: "Gemini CLI",
1147
1162
  file: "GEMINI.md",
1148
1163
  blueprintType: "GEMINI_MD",
1164
+ importsFiles: true,
1149
1165
  icon: "\u{1F48E}",
1150
1166
  gradient: "from-blue-500 to-cyan-500",
1151
1167
  note: "Google's Gemini in terminal",
@@ -1190,6 +1206,18 @@ var PLATFORMS = [
1190
1206
  category: "other",
1191
1207
  format: "markdown"
1192
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
+ },
1193
1221
  {
1194
1222
  id: "firebender",
1195
1223
  name: "Firebender",
@@ -1345,6 +1373,53 @@ var COMMANDS = [
1345
1373
  note: "Custom commands for OpenCode TUI"
1346
1374
  }
1347
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
+ }
1348
1423
  function getPlatform(id) {
1349
1424
  return PLATFORMS.find((p) => p.id === id);
1350
1425
  }
@@ -1371,6 +1446,17 @@ var BLUEPRINT_TYPE_ALIASES = {
1371
1446
  function platformForBlueprintType(type) {
1372
1447
  return PLATFORMS.find((p) => p.blueprintType === type)?.id ?? BLUEPRINT_TYPE_ALIASES[type];
1373
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
+ }
1374
1460
  var COMMAND_COUNT = COMMANDS.length;
1375
1461
 
1376
1462
  // src/utils/generator.ts
@@ -1860,11 +1946,11 @@ var STATIC_FILE_PATHS = {
1860
1946
  };
1861
1947
  function generateConfig(options) {
1862
1948
  const files = {};
1863
- for (const platform2 of options.platforms) {
1864
- const filename = PLATFORM_FILES2[platform2];
1865
- if (filename) {
1866
- files[filename] = generateFileContent(options, platform2);
1867
- }
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);
1868
1954
  }
1869
1955
  const createLocalStaticFiles = options.staticFileHandling !== "config_only";
1870
1956
  if (options.staticFiles && options.staticFiles.length > 0 && createLocalStaticFiles) {
@@ -1927,6 +2013,8 @@ function generateFileContent(options, platform2) {
1927
2013
  sections.push("---", "inclusion: always", "---", "");
1928
2014
  } else if (platform2 === "augment") {
1929
2015
  sections.push("---", "type: always_apply", "---", "");
2016
+ } else if (platform2 === "devin") {
2017
+ sections.push("---", "trigger: always_on", "---", "");
1930
2018
  }
1931
2019
  sections.push(`# ${projectName} - AI Assistant Configuration`);
1932
2020
  sections.push("");
@@ -2919,6 +3007,8 @@ function slugify(name) {
2919
3007
  }
2920
3008
  function blueprintFilename(bp) {
2921
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;
2922
3012
  const platform2 = platformForBlueprintType(bp.type);
2923
3013
  if (platform2 && PLATFORM_FILES2[platform2]) return PLATFORM_FILES2[platform2];
2924
3014
  const command = COMMANDS.find((c) => c.id === platform2);
@@ -2947,11 +3037,28 @@ var atPath = (relativePath, file) => {
2947
3037
  const p = normPath(relativePath);
2948
3038
  return p === file || p.endsWith(`/${file}`);
2949
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
+ };
2950
3045
  var inFolder = (relativePath, folder) => {
2951
3046
  const p = normPath(relativePath);
2952
3047
  return p.startsWith(folder) || p.includes(`/${folder}`);
2953
3048
  };
2954
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
+ })),
2955
3062
  // Commands: one file per command in the tool's folder
2956
3063
  ...COMMANDS.map((c) => ({
2957
3064
  match: (p) => inFolder(p, `${c.directory}/`) && p.endsWith(c.extension),
@@ -3032,24 +3139,26 @@ function getSourceFromVisibility(visibility) {
3032
3139
  function isHierarchyId(id) {
3033
3140
  return id.startsWith("ha_");
3034
3141
  }
3035
- async function pullCommand(id, options) {
3142
+ async function pullCommand(ids, options) {
3036
3143
  if (!isAuthenticated()) {
3037
3144
  console.log(
3038
3145
  chalk5.yellow("Not logged in. Run 'lynxp login' to authenticate.")
3039
3146
  );
3040
3147
  process.exit(1);
3041
3148
  }
3042
- if (isHierarchyId(id)) {
3043
- await pullHierarchy(id, options);
3044
- } else {
3045
- 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
+ }
3046
3155
  }
3047
3156
  }
3048
3157
  async function pullHierarchy(id, options) {
3049
3158
  const cwd = process.cwd();
3050
3159
  const spinner = ora4(`Fetching hierarchy ${chalk5.cyan(id)}...`).start();
3051
3160
  try {
3052
- const response = await api.getHierarchy(id);
3161
+ const response = await api.getHierarchy(id, { includeContent: !options.preview });
3053
3162
  const { hierarchy, blueprints } = response;
3054
3163
  spinner.stop();
3055
3164
  console.log();
@@ -3112,7 +3221,7 @@ async function pullHierarchy(id, options) {
3112
3221
  continue;
3113
3222
  }
3114
3223
  }
3115
- const { blueprint } = await api.getBlueprint(bp.id);
3224
+ const blueprint = typeof bp.content === "string" ? bp : (await api.getBlueprint(bp.id)).blueprint;
3116
3225
  if (!blueprint.content) {
3117
3226
  console.log(chalk5.yellow(` \u26A0 No content: ${filename}`));
3118
3227
  skipped++;
@@ -4111,19 +4220,19 @@ function inferBlueprintType(filePath) {
4111
4220
  function confirmSensitive(options, spinner) {
4112
4221
  return async (matches) => {
4113
4222
  spinner?.stop();
4114
- 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:"));
4115
4224
  for (const m of matches) {
4116
4225
  console.log(chalk6.gray(` line ${m.line}: ${m.type}${m.snippet ? ` (${m.snippet})` : ""}`));
4117
4226
  }
4118
4227
  let publish = false;
4119
4228
  if (options.allowSensitive) {
4120
- console.log(chalk6.gray(" Publishing anyway (--allow-sensitive)."));
4229
+ console.log(chalk6.gray(" Pushing anyway (--allow-sensitive)."));
4121
4230
  publish = true;
4122
4231
  } else if (!options.yes && process.stdin.isTTY) {
4123
4232
  const answer = await prompts2({
4124
4233
  type: "confirm",
4125
4234
  name: "publish",
4126
- message: "Publish it anyway?",
4235
+ message: "Push it anyway?",
4127
4236
  initial: false
4128
4237
  });
4129
4238
  publish = answer.publish === true;
@@ -4132,7 +4241,7 @@ function confirmSensitive(options, spinner) {
4132
4241
  spinner?.start();
4133
4242
  } else {
4134
4243
  console.log(
4135
- 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.")
4136
4245
  );
4137
4246
  }
4138
4247
  return publish;
@@ -6652,7 +6761,7 @@ var TEST_FRAMEWORKS2 = TEST_FRAMEWORKS;
6652
6761
 
6653
6762
  // src/commands/wizard.ts
6654
6763
  var DRAFTS_DIR = ".lynxprompt/drafts";
6655
- var CLI_VERSION = "2.1.40";
6764
+ var CLI_VERSION = "2.1.41";
6656
6765
  async function saveDraftLocally(name, config2, stepReached) {
6657
6766
  const draftsPath = join3(process.cwd(), DRAFTS_DIR);
6658
6767
  await mkdir3(draftsPath, { recursive: true });
@@ -7790,8 +7899,11 @@ async function runWizardWithDraftProtection(options) {
7790
7899
  ...config2,
7791
7900
  blueprintMode: options.blueprint || config2.blueprintMode || false,
7792
7901
  variables,
7793
- enableAutoUpdate: false
7902
+ enableAutoUpdate: false,
7794
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
7795
7907
  };
7796
7908
  let files = generateConfig({ ...finalConfig, apiUrl: getApiUrl() });
7797
7909
  let savedBlueprintId = null;
@@ -7858,7 +7970,8 @@ async function runWizardWithDraftProtection(options) {
7858
7970
  await mkdir3(dir, { recursive: true });
7859
7971
  }
7860
7972
  await writeFile3(outputPath, content, "utf-8");
7861
- 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}`);
7862
7975
  if (savedBlueprintId && syncedFile && syncedFile.file === filename) {
7863
7976
  await trackBlueprint(process.cwd(), {
7864
7977
  id: savedBlueprintId,
@@ -10027,8 +10140,13 @@ async function searchCommand(query, options) {
10027
10140
  const spinner = ora7(`Searching for "${query}"...`).start();
10028
10141
  try {
10029
10142
  const limit = parseInt(options.limit, 10) || 20;
10030
- const { templates, total, hasMore } = await api.searchBlueprints(query, limit);
10143
+ const response = await api.searchBlueprints(query, limit);
10031
10144
  spinner.stop();
10145
+ if (options.json) {
10146
+ console.log(JSON.stringify(response, null, 2));
10147
+ return;
10148
+ }
10149
+ const { templates, total, hasMore } = response;
10032
10150
  if (templates.length === 0) {
10033
10151
  console.log();
10034
10152
  console.log(chalk8.yellow(`No blueprints found for "${query}".`));
@@ -10259,6 +10377,94 @@ import { readFile as readFile6, readdir as readdir2, stat } from "fs/promises";
10259
10377
  import { join as join5 } from "path";
10260
10378
  import { existsSync as existsSync3 } from "fs";
10261
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
10262
10468
  var CONFIG_FILES2 = [
10263
10469
  { path: "AGENTS.md", name: "AGENTS.md" },
10264
10470
  { path: "CLAUDE.md", name: "CLAUDE.md" },
@@ -10285,6 +10491,7 @@ var CONFIG_DIRS2 = [
10285
10491
  { path: ".trae/rules", name: "Trae" },
10286
10492
  { path: ".augment/rules", name: "Augment" },
10287
10493
  { path: ".roo/rules", name: "Roo Code" },
10494
+ { path: ".devin/rules", name: "Devin" },
10288
10495
  { path: ".kilocode/rules", name: "Kilo Code" },
10289
10496
  { path: ".amazonq/rules", name: "Amazon Q" },
10290
10497
  { path: ".idx/rules", name: "Firebase Studio" },
@@ -10329,6 +10536,10 @@ function validateMarkdown(content, filename) {
10329
10536
  }
10330
10537
  return { errors, warnings };
10331
10538
  }
10539
+ function validateHiddenUnicode(content, filename) {
10540
+ const found = findHiddenUnicode(content);
10541
+ return found.length > 0 ? [describeHiddenUnicode(filename, found)] : [];
10542
+ }
10332
10543
  async function validateLynxPromptConfig(cwd) {
10333
10544
  const errors = [];
10334
10545
  const warnings = [];
@@ -10392,8 +10603,9 @@ function validateMdc(content, filename) {
10392
10603
  }
10393
10604
  async function checkCommand(options = {}) {
10394
10605
  const isCi = options.ci;
10606
+ const isJson = options.json;
10395
10607
  const cwd = process.cwd();
10396
- if (!isCi) {
10608
+ if (!isCi && !isJson) {
10397
10609
  console.log();
10398
10610
  console.log(chalk10.cyan("\u{1F431} LynxPrompt Check"));
10399
10611
  console.log();
@@ -10404,7 +10616,7 @@ async function checkCommand(options = {}) {
10404
10616
  warnings: [],
10405
10617
  files: []
10406
10618
  };
10407
- const spinner = !isCi ? ora8("Scanning for configuration files...").start() : null;
10619
+ const spinner = !isCi && !isJson ? ora8("Scanning for configuration files...").start() : null;
10408
10620
  for (const file of CONFIG_FILES2) {
10409
10621
  const filePath = join5(cwd, file.path);
10410
10622
  if (existsSync3(filePath) && (await stat(filePath)).isFile()) {
@@ -10412,7 +10624,7 @@ async function checkCommand(options = {}) {
10412
10624
  try {
10413
10625
  const content = await readFile6(filePath, "utf-8");
10414
10626
  const validation = validateMarkdown(content, file.path);
10415
- result.errors.push(...validation.errors);
10627
+ result.errors.push(...validation.errors, ...validateHiddenUnicode(content, file.path));
10416
10628
  result.warnings.push(...validation.warnings);
10417
10629
  } catch (error) {
10418
10630
  result.errors.push(`${file.path}: Could not read file`);
@@ -10430,6 +10642,7 @@ async function checkCommand(options = {}) {
10430
10642
  if (fileStat.isFile()) {
10431
10643
  result.files.push(`${dir.path}/${file}`);
10432
10644
  const content = await readFile6(filePath, "utf-8");
10645
+ result.errors.push(...validateHiddenUnicode(content, `${dir.path}/${file}`));
10433
10646
  if (file.endsWith(".mdc")) {
10434
10647
  const validation = validateMdc(content, `${dir.path}/${file}`);
10435
10648
  result.errors.push(...validation.errors);
@@ -10450,7 +10663,10 @@ async function checkCommand(options = {}) {
10450
10663
  result.warnings.push(...lynxpromptValidation.warnings);
10451
10664
  spinner?.stop();
10452
10665
  result.valid = result.errors.length === 0;
10453
- 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) {
10454
10670
  if (!result.valid) {
10455
10671
  console.error("\u2717 Validation failed");
10456
10672
  for (const error of result.errors) {
@@ -11509,7 +11725,9 @@ var PLATFORM_FRONTMATTER = {
11509
11725
  firebender: cursorFrontmatter,
11510
11726
  // Firebender reads Cursor-style .mdc rules
11511
11727
  kiro: () => ["inclusion: always"],
11512
- augment: (description) => ["type: always_apply", `description: ${yamlString(description)}`]
11728
+ augment: (description) => ["type: always_apply", `description: ${yamlString(description)}`],
11729
+ devin: () => ["trigger: always_on"]
11730
+ // .devin/rules/ uses Windsurf's rule frontmatter
11513
11731
  };
11514
11732
  var FRONTMATTER_PATTERN = /^---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n)+/;
11515
11733
  function frontmatterLines(platform2, description) {
@@ -11631,6 +11849,7 @@ var PLATFORM_NAMES = {
11631
11849
  codex: "Codex",
11632
11850
  openhands: "OpenHands",
11633
11851
  crush: "Crush",
11852
+ devin: "Devin",
11634
11853
  firebender: "Firebender",
11635
11854
  "cursor-command": "Cursor Command",
11636
11855
  "claude-command": "Claude Code Command",
@@ -11659,6 +11878,16 @@ async function convertCommand(first, second, options) {
11659
11878
  console.log(chalk16.gray(" Convert AI IDE configuration between formats"));
11660
11879
  console.log();
11661
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
+ }
11662
11891
  let sourcePath;
11663
11892
  let sourcePlatform;
11664
11893
  if (source) {
@@ -11702,6 +11931,9 @@ async function convertCommand(first, second, options) {
11702
11931
  if (isStructuredPlatform(key)) continue;
11703
11932
  console.log(chalk16.gray(` \u2022 ${key.padEnd(15)} \u2192 ${name}`));
11704
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
+ }
11705
11937
  process.exit(1);
11706
11938
  }
11707
11939
  const structured = [sourcePlatform, normalizedTarget].find(isStructuredPlatform);
@@ -11756,6 +11988,42 @@ async function convertCommand(first, second, options) {
11756
11988
  process.exit(1);
11757
11989
  }
11758
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}`));
12004
+ process.exit(1);
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();
12026
+ }
11759
12027
 
11760
12028
  // src/commands/merge.ts
11761
12029
  import chalk17 from "chalk";
@@ -12449,20 +12717,20 @@ async function configCommand(action, valueArg) {
12449
12717
  }
12450
12718
 
12451
12719
  // src/index.ts
12452
- var CLI_VERSION2 = "2.1.40";
12720
+ var CLI_VERSION2 = "2.1.41";
12453
12721
  var program = new Command();
12454
12722
  program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version(CLI_VERSION2);
12455
- 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);
12456
- 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);
12457
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);
12458
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);
12459
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);
12460
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);
12461
12729
  program.command("status").description("Show current AI configuration and tracked blueprints").action(statusCommand);
12462
- 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").option("--vars <values>", "Fill [[VARIABLES]]: VAR1=value1,VAR2=value2 (others get their defaults)").action(pullCommand);
12463
- program.command("search <query>").description("Search public blueprints in the marketplace").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
12464
- 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);
12465
- 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);
12466
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);
12467
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);
12468
12736
  program.command("unlink [file]").description("Disconnect a local file from its cloud blueprint").action(unlinkCommand);
@@ -12470,7 +12738,7 @@ program.command("diff [file-or-id]").description("Compare tracked files with the
12470
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);
12471
12739
  program.command("login").description("Authenticate with LynxPrompt (opens browser)").action(loginCommand);
12472
12740
  program.command("logout").description("Log out and remove stored credentials").action(logoutCommand);
12473
- 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);
12474
12742
  program.command("config [action] [value]").description("View or change CLI settings (set-url, reset-url, path)").action(configCommand);
12475
12743
  program.addHelpText(
12476
12744
  "beforeAll",
@@ -12500,6 +12768,7 @@ ${chalk21.cyan("Marketplace:")}
12500
12768
  ${chalk21.white("$ lynxp search nextjs")} ${chalk21.gray("Search blueprints")}
12501
12769
  ${chalk21.white("$ lynxp pull bp_abc123")} ${chalk21.gray("Download and track a blueprint")}
12502
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")}
12503
12772
  ${chalk21.white("$ lynxp push")} ${chalk21.gray("Push local file to cloud")}
12504
12773
  ${chalk21.white("$ lynxp push --all")} ${chalk21.gray("Push ALL config files recursively")}
12505
12774
  ${chalk21.white("$ lynxp hierarchies")} ${chalk21.gray("List your hierarchies")}
@@ -12514,6 +12783,7 @@ ${chalk21.cyan("Blueprint Tracking:")}
12514
12783
 
12515
12784
  ${chalk21.cyan("CI/CD:")}
12516
12785
  ${chalk21.white("$ lynxp check --ci")} ${chalk21.gray("Validate config (exit code)")}
12786
+ ${chalk21.white("$ lynxp check --json")} ${chalk21.gray("Validation result as JSON")}
12517
12787
 
12518
12788
  ${chalk21.cyan("Configuration:")}
12519
12789
  ${chalk21.white("$ lynxp config")} ${chalk21.gray("Show current settings")}