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/README.md +59 -1
- package/dist/index.js +931 -477
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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(
|
|
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
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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("
|
|
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: "
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
10791
|
-
import { existsSync as
|
|
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(
|
|
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) =>
|
|
11254
|
+
const foundFiles = configFiles.filter((f) => existsSync6(join8(cwd, f)));
|
|
10826
11255
|
if (foundFiles.length === 0) {
|
|
10827
|
-
console.log(
|
|
11256
|
+
console.log(chalk14.yellow("No AI configuration files found in this directory."));
|
|
10828
11257
|
console.log();
|
|
10829
|
-
console.log(
|
|
10830
|
-
console.log(
|
|
10831
|
-
console.log(
|
|
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(
|
|
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 =
|
|
10853
|
-
if (!
|
|
10854
|
-
console.log(
|
|
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(
|
|
10860
|
-
console.log(
|
|
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(
|
|
11298
|
+
console.log(chalk14.gray("Cancelled."));
|
|
10870
11299
|
return;
|
|
10871
11300
|
}
|
|
10872
11301
|
}
|
|
10873
11302
|
if (!blueprintId) {
|
|
10874
11303
|
if (!isAuthenticated()) {
|
|
10875
|
-
console.log(
|
|
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(
|
|
11312
|
+
console.log(chalk14.gray("Run 'lynxp login' in another terminal, then come back here."));
|
|
10884
11313
|
return;
|
|
10885
11314
|
}
|
|
10886
|
-
console.log(
|
|
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(
|
|
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(
|
|
10910
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
11414
|
+
console.log(chalk14.red("\u2717 No blueprint ID provided."));
|
|
10986
11415
|
return;
|
|
10987
11416
|
}
|
|
10988
|
-
const spinner = ora10(`Fetching blueprint ${
|
|
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(
|
|
11424
|
+
console.log(chalk14.cyan(`\u{1F431} Blueprint: ${chalk14.bold(safeText(blueprint.name))}`));
|
|
10996
11425
|
if (blueprint.description) {
|
|
10997
|
-
console.log(
|
|
11426
|
+
console.log(chalk14.gray(` ${safeText(blueprint.description)}`));
|
|
10998
11427
|
}
|
|
10999
|
-
console.log(
|
|
11428
|
+
console.log(chalk14.gray(` Visibility: ${blueprint.visibility}`));
|
|
11000
11429
|
console.log();
|
|
11001
11430
|
if (isMarketplace) {
|
|
11002
|
-
console.log(
|
|
11003
|
-
console.log(
|
|
11004
|
-
console.log(
|
|
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 ${
|
|
11439
|
+
message: `Link ${chalk14.cyan(file)} to ${chalk14.cyan(blueprint.name)}?`,
|
|
11011
11440
|
initial: true
|
|
11012
11441
|
});
|
|
11013
11442
|
if (!confirm) {
|
|
11014
|
-
console.log(
|
|
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(
|
|
11448
|
+
console.log(chalk14.green(`\u2705 Linked: ${file} \u2192 ${blueprint.id}`));
|
|
11020
11449
|
console.log();
|
|
11021
|
-
console.log(
|
|
11022
|
-
console.log(
|
|
11023
|
-
console.log(
|
|
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(
|
|
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(
|
|
11033
|
-
console.log(
|
|
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(
|
|
11464
|
+
console.log(chalk14.red("\u2717 You don't have access to this blueprint."));
|
|
11036
11465
|
} else {
|
|
11037
|
-
console.log(
|
|
11466
|
+
console.log(chalk14.red(`\u2717 Error: ${error.message}`));
|
|
11038
11467
|
}
|
|
11039
11468
|
} else {
|
|
11040
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
11505
|
+
console.log(chalk14.yellow(`File is not linked to any blueprint: ${file}`));
|
|
11077
11506
|
return;
|
|
11078
11507
|
}
|
|
11079
|
-
console.log(
|
|
11080
|
-
console.log(
|
|
11081
|
-
console.log(
|
|
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 ${
|
|
11515
|
+
message: `Unlink ${chalk14.cyan(file)} from ${chalk14.cyan(tracked.name)}?`,
|
|
11087
11516
|
initial: true
|
|
11088
11517
|
});
|
|
11089
11518
|
if (!confirm) {
|
|
11090
|
-
console.log(
|
|
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(
|
|
11097
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
11538
|
+
console.log(chalk14.gray("No blueprints are currently tracked."));
|
|
11110
11539
|
console.log();
|
|
11111
|
-
console.log(
|
|
11112
|
-
console.log(
|
|
11113
|
-
console.log(
|
|
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 ?
|
|
11546
|
+
const statusIcon = !fileExists2 ? chalk14.red("\u2717") : localModified ? chalk14.yellow("\u25CF") : chalk14.green("\u2713");
|
|
11118
11547
|
const sourceLabel = {
|
|
11119
|
-
marketplace:
|
|
11120
|
-
team:
|
|
11121
|
-
private:
|
|
11122
|
-
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} ${
|
|
11553
|
+
console.log(`${statusIcon} ${chalk14.cyan(blueprint.file)}`);
|
|
11125
11554
|
console.log(` ${sourceLabel} ${blueprint.name}`);
|
|
11126
|
-
console.log(` ${
|
|
11555
|
+
console.log(` ${chalk14.gray(`ID: ${blueprint.id}`)}`);
|
|
11127
11556
|
if (!fileExists2) {
|
|
11128
|
-
console.log(
|
|
11557
|
+
console.log(chalk14.red(` \u26A0 File not found`));
|
|
11129
11558
|
} else if (localModified) {
|
|
11130
|
-
console.log(
|
|
11559
|
+
console.log(chalk14.yellow(` \u26A0 Local changes detected`));
|
|
11131
11560
|
}
|
|
11132
11561
|
console.log();
|
|
11133
11562
|
}
|
|
11134
|
-
console.log(
|
|
11135
|
-
console.log(
|
|
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
|
|
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(
|
|
11146
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
11629
|
+
console.log(chalk15.yellow(" No project configuration detected."));
|
|
11201
11630
|
console.log();
|
|
11202
|
-
console.log(
|
|
11203
|
-
console.log(
|
|
11204
|
-
console.log(
|
|
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(
|
|
11637
|
+
console.log(chalk15.green.bold(" \u{1F4CA} Project Analysis"));
|
|
11209
11638
|
console.log();
|
|
11210
|
-
console.log(
|
|
11211
|
-
console.log(
|
|
11212
|
-
console.log(` ${
|
|
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(` ${
|
|
11643
|
+
console.log(` ${chalk15.dim("Description:")} ${chalk15.gray(detected.description)}`);
|
|
11215
11644
|
}
|
|
11216
|
-
console.log(` ${
|
|
11645
|
+
console.log(` ${chalk15.dim("Type:")} ${chalk15.white(detected.type || "application")}`);
|
|
11217
11646
|
if (detected.license) {
|
|
11218
|
-
console.log(` ${
|
|
11647
|
+
console.log(` ${chalk15.dim("License:")} ${chalk15.white(detected.license.toUpperCase())}`);
|
|
11219
11648
|
}
|
|
11220
11649
|
console.log();
|
|
11221
|
-
console.log(
|
|
11222
|
-
console.log(
|
|
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(` ${
|
|
11654
|
+
console.log(` ${chalk15.cyan("\u2022")} ${tech}`);
|
|
11226
11655
|
}
|
|
11227
11656
|
} else {
|
|
11228
|
-
console.log(
|
|
11657
|
+
console.log(chalk15.gray(" No tech stack detected"));
|
|
11229
11658
|
}
|
|
11230
11659
|
console.log();
|
|
11231
|
-
console.log(
|
|
11232
|
-
console.log(
|
|
11233
|
-
console.log(` ${
|
|
11234
|
-
console.log(` ${
|
|
11235
|
-
console.log(` ${
|
|
11236
|
-
console.log(` ${
|
|
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(
|
|
11240
|
-
console.log(
|
|
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(` ${
|
|
11673
|
+
console.log(` ${chalk15.dim(key.padEnd(10))} ${chalk15.yellow(cmdValue)}`);
|
|
11245
11674
|
}
|
|
11246
11675
|
}
|
|
11247
11676
|
console.log();
|
|
11248
11677
|
}
|
|
11249
|
-
console.log(
|
|
11250
|
-
console.log(
|
|
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(` ${
|
|
11698
|
+
console.log(` ${chalk15.yellow("\u2022")} ${rec}`);
|
|
11270
11699
|
}
|
|
11271
11700
|
} else {
|
|
11272
|
-
console.log(
|
|
11701
|
+
console.log(chalk15.green(" \u2713 Project looks well-configured!"));
|
|
11273
11702
|
}
|
|
11274
11703
|
console.log();
|
|
11275
|
-
console.log(
|
|
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
|
|
11281
|
-
import { readFile as
|
|
11282
|
-
import {
|
|
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
|
-
|
|
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(
|
|
11289
|
-
if (!match) return { frontmatter:
|
|
11290
|
-
return { frontmatter: match[
|
|
11291
|
-
}
|
|
11292
|
-
function
|
|
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
|
-
|
|
11305
|
-
if (frontmatter
|
|
11306
|
-
return
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
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/
|
|
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
|
-
|
|
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(
|
|
11483
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
11904
|
+
console.log(chalk16.red(" \u2717 No AI configuration file found in current directory"));
|
|
11501
11905
|
console.log();
|
|
11502
|
-
console.log(
|
|
11906
|
+
console.log(chalk16.gray(" Supported source files:"));
|
|
11503
11907
|
for (const file of Object.keys(SOURCE_FILES)) {
|
|
11504
|
-
console.log(
|
|
11908
|
+
console.log(chalk16.gray(` \u2022 ${file}`));
|
|
11505
11909
|
}
|
|
11506
11910
|
console.log();
|
|
11507
|
-
console.log(
|
|
11911
|
+
console.log(chalk16.gray(" Command directories (for slash commands):"));
|
|
11508
11912
|
for (const dir of Object.keys(COMMAND_DIRS)) {
|
|
11509
|
-
console.log(
|
|
11913
|
+
console.log(chalk16.gray(` \u2022 ${dir}/*.md`));
|
|
11510
11914
|
}
|
|
11511
11915
|
console.log();
|
|
11512
|
-
console.log(
|
|
11513
|
-
console.log(
|
|
11514
|
-
console.log(
|
|
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(
|
|
11927
|
+
console.log(chalk16.red(` \u2717 Unknown target format: ${target}`));
|
|
11524
11928
|
console.log();
|
|
11525
|
-
console.log(
|
|
11929
|
+
console.log(chalk16.gray(" Supported target formats:"));
|
|
11526
11930
|
for (const [key, name] of Object.entries(PLATFORM_NAMES)) {
|
|
11527
|
-
|
|
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
|
|
11947
|
+
sourceContent = await readFile9(sourcePath, "utf-8");
|
|
11534
11948
|
} catch {
|
|
11535
|
-
console.log(
|
|
11949
|
+
console.log(chalk16.red(` \u2717 Could not read source file: ${sourcePath}`));
|
|
11536
11950
|
process.exit(1);
|
|
11537
11951
|
}
|
|
11538
|
-
console.log(
|
|
11539
|
-
console.log(
|
|
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
|
|
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
|
|
11966
|
+
await access6(outputPath);
|
|
11554
11967
|
if (!options.force) {
|
|
11555
|
-
console.log(
|
|
11556
|
-
console.log(
|
|
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:
|
|
11563
|
-
const outputDir =
|
|
11975
|
+
const { mkdir: mkdir5 } = await import("fs/promises");
|
|
11976
|
+
const outputDir = dirname5(outputPath);
|
|
11564
11977
|
if (outputDir !== cwd) {
|
|
11565
|
-
await
|
|
11978
|
+
await mkdir5(outputDir, { recursive: true });
|
|
11566
11979
|
}
|
|
11567
|
-
await
|
|
11568
|
-
console.log(
|
|
11980
|
+
await writeFile5(outputPath, targetContent, "utf-8");
|
|
11981
|
+
console.log(chalk16.green(` \u2713 Converted to ${outputFilename}`));
|
|
11569
11982
|
console.log();
|
|
11570
|
-
console.log(
|
|
11571
|
-
console.log(
|
|
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(
|
|
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
|
|
11581
|
-
import { readFile as
|
|
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(
|
|
11679
|
-
console.log(
|
|
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(
|
|
12131
|
+
console.log(chalk17.red(" \u2717 Please provide at least 2 files to merge"));
|
|
11683
12132
|
console.log();
|
|
11684
|
-
console.log(
|
|
11685
|
-
console.log(
|
|
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(
|
|
11688
|
-
console.log(
|
|
11689
|
-
console.log(
|
|
11690
|
-
console.log(
|
|
11691
|
-
console.log(
|
|
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(
|
|
11697
|
-
console.log(
|
|
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
|
|
12152
|
+
await access7(filePath);
|
|
11704
12153
|
} catch {
|
|
11705
|
-
console.log(
|
|
12154
|
+
console.log(chalk17.red(` \u2717 File not found: ${file}`));
|
|
11706
12155
|
process.exit(1);
|
|
11707
12156
|
}
|
|
11708
12157
|
try {
|
|
11709
|
-
const content = await
|
|
12158
|
+
const content = await readFile10(filePath, "utf-8");
|
|
11710
12159
|
const sections = parseMarkdownSections(content, basename3(file));
|
|
11711
12160
|
allSections.push(sections);
|
|
11712
|
-
console.log(
|
|
12161
|
+
console.log(chalk17.gray(` \u2713 Read ${file} (${sections.length} sections)`));
|
|
11713
12162
|
} catch (error) {
|
|
11714
|
-
console.log(
|
|
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(
|
|
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
|
|
12209
|
+
await access7(outputPath);
|
|
11761
12210
|
if (!options.force) {
|
|
11762
|
-
console.log(
|
|
11763
|
-
console.log(
|
|
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
|
|
11770
|
-
console.log(
|
|
12218
|
+
await writeFile6(outputPath, finalContent, "utf-8");
|
|
12219
|
+
console.log(chalk17.green(` \u2713 Merged to ${outputFilename}`));
|
|
11771
12220
|
console.log();
|
|
11772
|
-
console.log(
|
|
11773
|
-
console.log(
|
|
11774
|
-
console.log(
|
|
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(
|
|
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
|
|
12232
|
+
import chalk18 from "chalk";
|
|
11784
12233
|
import prompts6 from "prompts";
|
|
11785
12234
|
import ora14 from "ora";
|
|
11786
|
-
import { readFile as
|
|
11787
|
-
import { join as
|
|
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 =
|
|
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
|
|
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,
|
|
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:
|
|
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 =
|
|
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:
|
|
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(
|
|
12389
|
+
console.log(chalk18.cyan.bold(" \u{1F4E5} Import Results"));
|
|
11941
12390
|
console.log();
|
|
11942
12391
|
if (result.totalFound === 0) {
|
|
11943
|
-
console.log(
|
|
11944
|
-
console.log(
|
|
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(
|
|
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 ?
|
|
11954
|
-
console.log(` ${icon} ${
|
|
11955
|
-
console.log(
|
|
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(
|
|
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} ${
|
|
11963
|
-
console.log(
|
|
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(
|
|
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(
|
|
12423
|
+
console.log(chalk18.cyan(` \u{1F4CA} ${monorepoCount} monorepo(s) detected with hierarchical configs`));
|
|
11975
12424
|
}
|
|
11976
12425
|
if (standaloneCount > 0) {
|
|
11977
|
-
console.log(
|
|
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(
|
|
12430
|
+
console.log(chalk18.yellow(" \u26A0\uFE0F Warnings:"));
|
|
11982
12431
|
for (const error of result.errors) {
|
|
11983
|
-
console.log(
|
|
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(
|
|
11992
|
-
console.log(
|
|
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
|
|
12446
|
+
await access8(rootPath);
|
|
11998
12447
|
} catch {
|
|
11999
|
-
console.error(
|
|
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(
|
|
12015
|
-
console.log(
|
|
12016
|
-
console.log(
|
|
12017
|
-
console.log(
|
|
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(
|
|
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(
|
|
12488
|
+
console.log(chalk18.blue(` Cursor Commands (${cursorCommands.length}):`));
|
|
12040
12489
|
for (const cmd of cursorCommands) {
|
|
12041
|
-
console.log(
|
|
12042
|
-
console.log(
|
|
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(
|
|
12496
|
+
console.log(chalk18.yellow(` Claude Commands (${claudeCommands.length}):`));
|
|
12048
12497
|
for (const cmd of claudeCommands) {
|
|
12049
|
-
console.log(
|
|
12050
|
-
console.log(
|
|
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(
|
|
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(
|
|
12058
|
-
console.log(
|
|
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(
|
|
12078
|
-
console.log(
|
|
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(
|
|
12085
|
-
console.log(
|
|
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(
|
|
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:
|
|
12099
|
-
const configDir =
|
|
12547
|
+
const { writeFile: writeFile7, mkdir: mkdir5 } = await import("fs/promises");
|
|
12548
|
+
const configDir = join10(rootPath, ".lynxprompt");
|
|
12100
12549
|
try {
|
|
12101
|
-
await
|
|
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
|
|
12118
|
-
|
|
12566
|
+
await writeFile7(
|
|
12567
|
+
join10(configDir, "hierarchy.json"),
|
|
12119
12568
|
JSON.stringify(hierarchyInfo, null, 2),
|
|
12120
12569
|
"utf-8"
|
|
12121
12570
|
);
|
|
12122
|
-
console.log(
|
|
12123
|
-
console.log(
|
|
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(
|
|
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
|
|
12579
|
+
import chalk19 from "chalk";
|
|
12131
12580
|
import ora15 from "ora";
|
|
12132
12581
|
async function hierarchiesCommand(options) {
|
|
12133
12582
|
if (!isAuthenticated()) {
|
|
12134
|
-
console.log(
|
|
12135
|
-
console.log(
|
|
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(
|
|
12600
|
+
console.log(chalk19.yellow("No hierarchies found."));
|
|
12152
12601
|
console.log();
|
|
12153
|
-
console.log(
|
|
12154
|
-
console.log(
|
|
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(
|
|
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(
|
|
12163
|
-
console.log(
|
|
12611
|
+
console.log(chalk19.bold(` ${hierarchy.name}`));
|
|
12612
|
+
console.log(chalk19.gray(` ID: ${hierarchy.id}`));
|
|
12164
12613
|
if (hierarchy.description) {
|
|
12165
|
-
console.log(
|
|
12614
|
+
console.log(chalk19.gray(` ${hierarchy.description}`));
|
|
12166
12615
|
}
|
|
12167
|
-
console.log(
|
|
12168
|
-
console.log(
|
|
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(
|
|
12620
|
+
console.log(chalk19.gray("\u2500".repeat(50)));
|
|
12172
12621
|
console.log();
|
|
12173
|
-
console.log(
|
|
12174
|
-
console.log(
|
|
12175
|
-
console.log(
|
|
12176
|
-
console.log(
|
|
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(
|
|
12634
|
+
console.error(chalk19.red(`Error: ${error.message}`));
|
|
12186
12635
|
if (error.statusCode === 401) {
|
|
12187
|
-
console.error(
|
|
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(
|
|
12638
|
+
console.error(chalk19.gray("You don't have permission to access hierarchies."));
|
|
12190
12639
|
}
|
|
12191
12640
|
} else {
|
|
12192
|
-
console.error(
|
|
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
|
|
12647
|
+
import chalk20 from "chalk";
|
|
12199
12648
|
function printLoginState() {
|
|
12200
12649
|
if (isAuthenticated()) {
|
|
12201
12650
|
const email = getUser()?.email;
|
|
12202
|
-
console.log(
|
|
12651
|
+
console.log(chalk20.gray(` Logged in${email ? ` as ${email}` : ""} on this instance.`));
|
|
12203
12652
|
} else {
|
|
12204
|
-
console.log(
|
|
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(
|
|
12659
|
+
console.log(chalk20.cyan("\u2699\uFE0F LynxPrompt CLI Configuration"));
|
|
12211
12660
|
console.log();
|
|
12212
12661
|
console.log(
|
|
12213
|
-
|
|
12662
|
+
chalk20.white(" API URL: ") + chalk20.green(getApiUrl())
|
|
12214
12663
|
);
|
|
12215
12664
|
const loggedIn = isAuthenticated();
|
|
12216
12665
|
console.log(
|
|
12217
|
-
|
|
12666
|
+
chalk20.white(" Login: ") + (loggedIn ? chalk20.green(getUser()?.email ?? "token set") : chalk20.gray("not logged in"))
|
|
12218
12667
|
);
|
|
12219
12668
|
console.log(
|
|
12220
|
-
|
|
12669
|
+
chalk20.white(" Config: ") + chalk20.gray(getConfigPath())
|
|
12221
12670
|
);
|
|
12222
12671
|
if (process.env.LYNXPROMPT_API_URL) {
|
|
12223
12672
|
console.log(
|
|
12224
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
12266
|
-
console.error(
|
|
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.
|
|
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 <
|
|
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", "
|
|
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
|
-
${
|
|
12297
|
-
${
|
|
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
|
-
${
|
|
12304
|
-
${
|
|
12305
|
-
${
|
|
12306
|
-
${
|
|
12307
|
-
${
|
|
12308
|
-
|
|
12309
|
-
${
|
|
12310
|
-
${
|
|
12311
|
-
${
|
|
12312
|
-
${
|
|
12313
|
-
${
|
|
12314
|
-
${
|
|
12315
|
-
${
|
|
12316
|
-
|
|
12317
|
-
${
|
|
12318
|
-
${
|
|
12319
|
-
${
|
|
12320
|
-
${
|
|
12321
|
-
${
|
|
12322
|
-
${
|
|
12323
|
-
${
|
|
12324
|
-
${
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
${
|
|
12329
|
-
${
|
|
12330
|
-
|
|
12331
|
-
${
|
|
12332
|
-
${
|
|
12333
|
-
|
|
12334
|
-
${
|
|
12335
|
-
${
|
|
12336
|
-
${
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
${
|
|
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();
|