lynxprompt 2.1.36 → 2.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,15 +7,22 @@ import chalk19 from "chalk";
7
7
  // src/commands/login.ts
8
8
  import chalk from "chalk";
9
9
  import ora from "ora";
10
+ import { createHash, randomBytes } from "crypto";
10
11
 
11
12
  // src/config.ts
12
13
  import Conf from "conf";
14
+ import { chmodSync } from "fs";
13
15
  var config = new Conf({
14
16
  projectName: "lynxprompt",
17
+ // The file holds the API token: readable by its owner only.
18
+ configFileMode: 384,
15
19
  schema: {
16
20
  token: {
17
21
  type: "string"
18
22
  },
23
+ tokenApiUrl: {
24
+ type: "string"
25
+ },
19
26
  apiUrl: {
20
27
  type: "string",
21
28
  default: "https://lynxprompt.com"
@@ -34,18 +41,44 @@ var config = new Conf({
34
41
  apiUrl: "https://lynxprompt.com"
35
42
  }
36
43
  });
44
+ try {
45
+ chmodSync(config.path, 384);
46
+ } catch {
47
+ }
48
+ function normalizeUrl(url) {
49
+ return url.replace(/\/+$/, "");
50
+ }
51
+ if (config.get("token") && !config.get("tokenApiUrl")) {
52
+ config.set("tokenApiUrl", normalizeUrl(config.get("apiUrl")));
53
+ }
54
+ function storedTokenMatchesApiUrl() {
55
+ return config.get("tokenApiUrl") === normalizeUrl(getApiUrl());
56
+ }
37
57
  function getToken() {
38
58
  const envToken = process.env.LYNXPROMPT_TOKEN;
39
59
  if (envToken) {
40
60
  return envToken;
41
61
  }
62
+ if (!storedTokenMatchesApiUrl()) {
63
+ return void 0;
64
+ }
42
65
  return config.get("token");
43
66
  }
44
67
  function setToken(token) {
45
68
  config.set("token", token);
69
+ config.set("tokenApiUrl", normalizeUrl(getApiUrl()));
70
+ }
71
+ function getStoredCredentials() {
72
+ const token = config.get("token");
73
+ const apiUrl = config.get("tokenApiUrl");
74
+ if (!token || !apiUrl) {
75
+ return void 0;
76
+ }
77
+ return { token, apiUrl, email: config.get("user")?.email };
46
78
  }
47
79
  function clearToken() {
48
80
  config.delete("token");
81
+ config.delete("tokenApiUrl");
49
82
  config.delete("user");
50
83
  }
51
84
  function getApiUrl() {
@@ -59,6 +92,9 @@ function setApiUrl(url) {
59
92
  config.set("apiUrl", url);
60
93
  }
61
94
  function getUser() {
95
+ if (!process.env.LYNXPROMPT_TOKEN && !storedTokenMatchesApiUrl()) {
96
+ return void 0;
97
+ }
62
98
  return config.get("user");
63
99
  }
64
100
  function setUser(user) {
@@ -105,16 +141,37 @@ var ApiClient = class {
105
141
  return data;
106
142
  }
107
143
  // Auth endpoints
108
- async initCliSession() {
144
+ // codeChallenge is the SHA-256 of a verifier that never leaves this process;
145
+ // polling sends the verifier, so the session ID in the sign-in URL is not
146
+ // enough to collect the token.
147
+ async initCliSession(codeChallenge) {
109
148
  return this.request("/api/cli-auth/init", {
110
- method: "POST"
149
+ method: "POST",
150
+ body: JSON.stringify({ code_challenge: codeChallenge })
111
151
  });
112
152
  }
113
- async pollCliSession(sessionId) {
153
+ async pollCliSession(sessionId, verifier) {
114
154
  return this.request(
115
- `/api/cli-auth/poll?session=${sessionId}`
155
+ `/api/cli-auth/poll?session=${encodeURIComponent(sessionId)}`,
156
+ { headers: { "X-CLI-Verifier": verifier } }
116
157
  );
117
158
  }
159
+ /**
160
+ * Revoke a stored token on the server that issued it. Best effort: logout
161
+ * clears the local copy whatever this returns.
162
+ */
163
+ async revokeToken(apiUrl, token) {
164
+ try {
165
+ const response = await fetch(`${apiUrl}/api/cli-auth/revoke`, {
166
+ method: "POST",
167
+ headers: { Authorization: `Bearer ${token}` },
168
+ signal: AbortSignal.timeout(1e4)
169
+ });
170
+ return response.ok;
171
+ } catch {
172
+ return false;
173
+ }
174
+ }
118
175
  // User endpoints
119
176
  async getUser() {
120
177
  return this.request("/api/v1/user");
@@ -281,7 +338,9 @@ async function loginCommand() {
281
338
  }
282
339
  const spinner = ora("Initializing authentication...").start();
283
340
  try {
284
- const session = await api.initCliSession();
341
+ const verifier = randomBytes(32).toString("base64url");
342
+ const codeChallenge = createHash("sha256").update(verifier).digest("base64url");
343
+ const session = await api.initCliSession(codeChallenge);
285
344
  spinner.stop();
286
345
  console.log();
287
346
  console.log(chalk.cyan("\u{1F510} Opening browser to authenticate..."));
@@ -300,7 +359,7 @@ async function loginCommand() {
300
359
  while (Date.now() - startTime < maxWaitTime) {
301
360
  await sleep(pollInterval);
302
361
  try {
303
- const result = await api.pollCliSession(session.session_id);
362
+ const result = await api.pollCliSession(session.session_id, verifier);
304
363
  if (result.status === "completed" && result.token && result.user) {
305
364
  pollSpinner.succeed("Authentication successful!");
306
365
  setToken(result.token);
@@ -391,15 +450,23 @@ function displayWelcome(user) {
391
450
  // src/commands/logout.ts
392
451
  import chalk2 from "chalk";
393
452
  async function logoutCommand() {
394
- if (!isAuthenticated()) {
453
+ const stored = getStoredCredentials();
454
+ if (!stored) {
395
455
  console.log(chalk2.yellow("You are not currently logged in."));
396
456
  return;
397
457
  }
398
- const user = getUser();
399
- const email = user?.email || "unknown";
458
+ const revoked = await api.revokeToken(stored.apiUrl, stored.token);
400
459
  clearToken();
401
- console.log(chalk2.green(`\u2713 Logged out from ${chalk2.bold(email)}`));
460
+ const who = stored.email ? `${stored.email} at ${stored.apiUrl}` : stored.apiUrl;
461
+ console.log(chalk2.green(`\u2713 Logged out from ${chalk2.bold(who)}`));
402
462
  console.log(chalk2.gray(" Removed stored credentials"));
463
+ if (!revoked) {
464
+ console.log(
465
+ chalk2.yellow(
466
+ ` Could not revoke the token on the server. Revoke it in Settings > API Tokens at ${stored.apiUrl}.`
467
+ )
468
+ );
469
+ }
403
470
  }
404
471
 
405
472
  // src/commands/whoami.ts
@@ -542,11 +609,11 @@ import { existsSync } from "fs";
542
609
  // src/utils/blueprint-tracker.ts
543
610
  import { readFile, writeFile, mkdir, access } from "fs/promises";
544
611
  import { join, dirname } from "path";
545
- import { createHash } from "crypto";
612
+ import { createHash as createHash2 } from "crypto";
546
613
  import * as yaml from "yaml";
547
614
  var BLUEPRINTS_FILE = ".lynxprompt/blueprints.yml";
548
615
  function calculateChecksum(content) {
549
- return createHash("sha256").update(content).digest("hex").substring(0, 16);
616
+ return createHash2("sha256").update(content).digest("hex").substring(0, 16);
550
617
  }
551
618
  async function loadBlueprints(cwd) {
552
619
  const filePath = join(cwd, BLUEPRINTS_FILE);
@@ -1055,8 +1122,28 @@ import chalk6 from "chalk";
1055
1122
  import ora5 from "ora";
1056
1123
  import fs from "fs";
1057
1124
  import path from "path";
1058
- import { createHash as createHash2 } from "crypto";
1125
+ import { createHash as createHash3 } from "crypto";
1059
1126
  import prompts2 from "prompts";
1127
+
1128
+ // src/utils/sensitive-ack.ts
1129
+ function sensitiveMatchesFrom(error) {
1130
+ if (!(error instanceof Error)) return null;
1131
+ const { statusCode, response } = error;
1132
+ if (statusCode !== 422 || response?.requires_acknowledgment !== true) return null;
1133
+ return Array.isArray(response.sensitive_data) ? response.sensitive_data : [];
1134
+ }
1135
+ async function withSensitiveDataAck(send, confirm) {
1136
+ try {
1137
+ return await send(false);
1138
+ } catch (error) {
1139
+ const matches = sensitiveMatchesFrom(error);
1140
+ if (!matches) throw error;
1141
+ if (!await confirm(matches)) throw error;
1142
+ return send(true);
1143
+ }
1144
+ }
1145
+
1146
+ // src/commands/push.ts
1060
1147
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1061
1148
  "node_modules",
1062
1149
  ".git",
@@ -1090,7 +1177,7 @@ var CONFIG_PATTERNS = [
1090
1177
  // Directory-based rules
1091
1178
  { match: (p) => normPath(p).includes(".cursor/rules/") && p.endsWith(".mdc"), type: "CURSOR_RULES", label: "Cursor Rules" },
1092
1179
  { match: (p) => normPath(p).includes(".trae/rules/") && p.endsWith(".mdc"), type: "TRAE_RULES", label: "Trae Rules" },
1093
- { match: (p) => normPath(p).includes(".idx/") && p.endsWith(".mdc"), type: "FIREBASE_RULES", label: "Firebase Rules" },
1180
+ { match: (p) => normPath(p).includes(".idx/") && p.endsWith(".mdc"), type: "IDX_CONFIG", label: "Firebase Rules" },
1094
1181
  { match: (p) => normPath(p).includes(".roo/rules/") && p.endsWith(".mdc"), type: "ROO_RULES", label: "Roo Rules" },
1095
1182
  { match: (p) => normPath(p).includes(".amazonq/rules/") && p.endsWith(".mdc"), type: "AMAZONQ_RULES", label: "Amazon Q Rules" },
1096
1183
  { match: (p) => normPath(p).includes(".augment/rules/") && p.endsWith(".mdc"), type: "AUGMENT_RULES", label: "Augment Rules" },
@@ -1099,26 +1186,26 @@ var CONFIG_PATTERNS = [
1099
1186
  // Path-based configs
1100
1187
  { match: (p) => normPath(p).includes(".github/copilot-instructions.md"), type: "COPILOT_INSTRUCTIONS", label: "Copilot Instructions" },
1101
1188
  { match: (p) => normPath(p).includes(".zed/instructions.md"), type: "ZED_INSTRUCTIONS", label: "Zed Instructions" },
1102
- { match: (p) => normPath(p).includes(".openhands/microagents/repo.md"), type: "OPENHANDS_CONFIG", label: "OpenHands Config" },
1189
+ { match: (p) => normPath(p).includes(".openhands/microagents/repo.md"), type: "OPENHANDS_MD", label: "OpenHands Config" },
1103
1190
  { match: (p) => normPath(p).includes(".junie/guidelines.md"), type: "JUNIE_GUIDELINES", label: "Junie Guidelines" },
1104
1191
  { match: (p) => normPath(p).includes(".void/config.json"), type: "VOID_CONFIG", label: "Void Config" },
1105
1192
  { match: (p) => normPath(p).includes(".continue/config.json"), type: "CONTINUE_CONFIG", label: "Continue Config" },
1106
1193
  { match: (p) => normPath(p).includes(".cody/config.json"), type: "CODY_CONFIG", label: "Cody Config" },
1107
- { match: (p) => normPath(p).includes(".supermaven/config.json"), type: "SUPERMAVEN_CONFIG", label: "Supermaven Config" },
1108
- { match: (p) => normPath(p).includes(".codegpt/config.json"), type: "CODEGPT_CONFIG", label: "CodeGPT Config" },
1194
+ { match: (p) => normPath(p).includes(".supermaven/config.json"), type: "CUSTOM", label: "Supermaven Config" },
1195
+ { match: (p) => normPath(p).includes(".codegpt/config.json"), type: "CUSTOM", label: "CodeGPT Config" },
1109
1196
  // Basename rules
1110
1197
  { match: (p) => path.basename(p) === "AGENTS.md", type: "AGENTS_MD", label: "AGENTS.md" },
1111
1198
  { match: (p) => path.basename(p) === "CLAUDE.md", type: "CLAUDE_MD", label: "CLAUDE.md" },
1112
1199
  { match: (p) => path.basename(p) === "AIDER.md", type: "AIDER_MD", label: "AIDER.md" },
1113
1200
  { match: (p) => path.basename(p) === "GEMINI.md", type: "GEMINI_MD", label: "GEMINI.md" },
1114
1201
  { match: (p) => path.basename(p) === "WARP.md", type: "WARP_MD", label: "WARP.md" },
1115
- { match: (p) => path.basename(p) === "CRUSH.md", type: "CRUSH_MD", label: "CRUSH.md" },
1202
+ { match: (p) => path.basename(p) === "CRUSH.md", type: "CUSTOM", label: "CRUSH.md" },
1116
1203
  { match: (p) => path.basename(p) === ".windsurfrules", type: "WINDSURF_RULES", label: "Windsurf Rules" },
1117
1204
  { match: (p) => path.basename(p) === ".clinerules", type: "CLINE_RULES", label: "Cline Rules" },
1118
- { match: (p) => path.basename(p) === ".goosehints", type: "GOOSE_HINTS", label: "Goose Hints" },
1205
+ { match: (p) => path.basename(p) === ".goosehints", type: "GOOSEHINTS", label: "Goose Hints" },
1119
1206
  { match: (p) => path.basename(p) === ".tabnine.yaml", type: "TABNINE_CONFIG", label: "Tabnine Config" },
1120
- { match: (p) => path.basename(p) === "opencode.json", type: "OPENCODE_CONFIG", label: "OpenCode Config" },
1121
- { match: (p) => path.basename(p) === "firebender.json", type: "FIREBENDER_CONFIG", label: "Firebender Config" }
1207
+ { match: (p) => path.basename(p) === "opencode.json", type: "OPENCODE_JSON", label: "OpenCode Config" },
1208
+ { match: (p) => path.basename(p) === "firebender.json", type: "CUSTOM", label: "Firebender Config" }
1122
1209
  ];
1123
1210
  function matchConfigFile(relativePath) {
1124
1211
  for (const pattern of CONFIG_PATTERNS) {
@@ -1277,12 +1364,12 @@ function createRepositoryRoot(rootPath) {
1277
1364
  const gitConfig = fs.readFileSync(gitConfigPath, "utf-8");
1278
1365
  const urlMatch = gitConfig.match(/url = (.+)/);
1279
1366
  if (urlMatch) {
1280
- return createHash2("sha256").update(urlMatch[1].trim()).digest("hex").substring(0, 16);
1367
+ return createHash3("sha256").update(urlMatch[1].trim()).digest("hex").substring(0, 16);
1281
1368
  }
1282
1369
  }
1283
1370
  } catch {
1284
1371
  }
1285
- return createHash2("sha256").update(path.resolve(rootPath)).digest("hex").substring(0, 16);
1372
+ return createHash3("sha256").update(path.resolve(rootPath)).digest("hex").substring(0, 16);
1286
1373
  }
1287
1374
  async function pushCommand(fileArg, options) {
1288
1375
  const cwd = process.cwd();
@@ -1361,7 +1448,13 @@ async function updateBlueprint(cwd, file, blueprintId, content, options, expecte
1361
1448
  if (expectedChecksum && !options.force) {
1362
1449
  updateData.expected_checksum = expectedChecksum;
1363
1450
  }
1364
- const result = await api.updateBlueprint(blueprintId, updateData);
1451
+ const result = await withSensitiveDataAck(
1452
+ (acknowledged) => api.updateBlueprint(
1453
+ blueprintId,
1454
+ acknowledged ? { ...updateData, sensitive_data_acknowledged: true } : updateData
1455
+ ),
1456
+ confirmSensitive(options, spinner)
1457
+ );
1365
1458
  spinner.succeed("Blueprint updated!");
1366
1459
  await updateChecksum(cwd, file, content);
1367
1460
  console.log();
@@ -1519,7 +1612,7 @@ async function createOrLinkBlueprint(cwd, file, filename, content, options) {
1519
1612
  }
1520
1613
  const spinner = ora5("Creating blueprint...").start();
1521
1614
  try {
1522
- const result = await api.createBlueprint({
1615
+ const createData = {
1523
1616
  name,
1524
1617
  description: description || "",
1525
1618
  content,
@@ -1531,7 +1624,11 @@ async function createOrLinkBlueprint(cwd, file, filename, content, options) {
1531
1624
  hierarchy_id: hierarchyId,
1532
1625
  parent_id: hierarchyInfo.parentId,
1533
1626
  repository_path: hierarchyInfo.repositoryPath
1534
- });
1627
+ };
1628
+ const result = await withSensitiveDataAck(
1629
+ (acknowledged) => api.createBlueprint(acknowledged ? { ...createData, sensitive_data_acknowledged: true } : createData),
1630
+ confirmSensitive(options, spinner)
1631
+ );
1535
1632
  spinner.succeed("Blueprint created!");
1536
1633
  await trackBlueprint(cwd, {
1537
1634
  id: result.blueprint.id,
@@ -1629,7 +1726,13 @@ async function pushHierarchy(cwd, files, options) {
1629
1726
  if (tracked.checksum && !options.force) {
1630
1727
  updateData.expected_checksum = tracked.checksum;
1631
1728
  }
1632
- await api.updateBlueprint(tracked.id, updateData);
1729
+ await withSensitiveDataAck(
1730
+ (acknowledged) => api.updateBlueprint(
1731
+ tracked.id,
1732
+ acknowledged ? { ...updateData, sensitive_data_acknowledged: true } : updateData
1733
+ ),
1734
+ confirmSensitive(options, spinner)
1735
+ );
1633
1736
  await updateChecksum(cwd, file.path, content);
1634
1737
  if (file.isRoot) rootBlueprintId = tracked.id;
1635
1738
  spinner.succeed(`${file.path} \u2192 updated (${tracked.id})`);
@@ -1638,7 +1741,13 @@ async function pushHierarchy(cwd, files, options) {
1638
1741
  }
1639
1742
  const existing = await findExistingBlueprintOnServer(file.path, hierarchyId);
1640
1743
  if (existing) {
1641
- await api.updateBlueprint(existing.id, { content });
1744
+ await withSensitiveDataAck(
1745
+ (acknowledged) => api.updateBlueprint(
1746
+ existing.id,
1747
+ acknowledged ? { content, sensitive_data_acknowledged: true } : { content }
1748
+ ),
1749
+ confirmSensitive(options, spinner)
1750
+ );
1642
1751
  await trackBlueprint(cwd, {
1643
1752
  id: existing.id,
1644
1753
  name: existing.name,
@@ -1656,7 +1765,7 @@ async function pushHierarchy(cwd, files, options) {
1656
1765
  }
1657
1766
  const blueprintName = file.isRoot ? hierarchyName : path.basename(file.path) === "AGENTS.md" ? path.dirname(file.path).replace(/[/\\]/g, " / ") : file.path.replace(/\\/g, "/");
1658
1767
  const parentId = !file.isRoot && file.type === "AGENTS_MD" && rootBlueprintId ? rootBlueprintId : null;
1659
- const result = await api.createBlueprint({
1768
+ const createData = {
1660
1769
  name: blueprintName,
1661
1770
  description: "",
1662
1771
  content,
@@ -1666,7 +1775,11 @@ async function pushHierarchy(cwd, files, options) {
1666
1775
  hierarchy_id: hierarchyId,
1667
1776
  parent_id: parentId,
1668
1777
  repository_path: file.path
1669
- });
1778
+ };
1779
+ const result = await withSensitiveDataAck(
1780
+ (acknowledged) => api.createBlueprint(acknowledged ? { ...createData, sensitive_data_acknowledged: true } : createData),
1781
+ confirmSensitive(options, spinner)
1782
+ );
1670
1783
  if (file.isRoot) rootBlueprintId = result.blueprint.id;
1671
1784
  await trackBlueprint(cwd, {
1672
1785
  id: result.blueprint.id,
@@ -1727,6 +1840,36 @@ function inferBlueprintType(filePath) {
1727
1840
  if (filePath.endsWith(".md") || filePath.endsWith(".mdc")) return "AGENTS_MD";
1728
1841
  return "CUSTOM";
1729
1842
  }
1843
+ function confirmSensitive(options, spinner) {
1844
+ return async (matches) => {
1845
+ spinner?.stop();
1846
+ console.log(chalk6.yellow("\n\u26A0 This blueprint is public and may contain secrets:"));
1847
+ for (const m of matches) {
1848
+ console.log(chalk6.gray(` line ${m.line}: ${m.type}${m.snippet ? ` (${m.snippet})` : ""}`));
1849
+ }
1850
+ let publish = false;
1851
+ if (options.allowSensitive) {
1852
+ console.log(chalk6.gray(" Publishing anyway (--allow-sensitive)."));
1853
+ publish = true;
1854
+ } else if (!options.yes && process.stdin.isTTY) {
1855
+ const answer = await prompts2({
1856
+ type: "confirm",
1857
+ name: "publish",
1858
+ message: "Publish it anyway?",
1859
+ initial: false
1860
+ });
1861
+ publish = answer.publish === true;
1862
+ }
1863
+ if (publish) {
1864
+ spinner?.start();
1865
+ } else {
1866
+ console.log(
1867
+ chalk6.gray(" Remove them, keep the blueprint private, or run again with --allow-sensitive to publish anyway.")
1868
+ );
1869
+ }
1870
+ return publish;
1871
+ };
1872
+ }
1730
1873
  function handleError(error) {
1731
1874
  if (error instanceof ApiRequestError) {
1732
1875
  console.error(chalk6.red(`Error: ${error.message}`));
@@ -3427,9 +3570,9 @@ function generateFileContent(options, platform2) {
3427
3570
  if (preferCli) {
3428
3571
  sections.push("");
3429
3572
  sections.push("# Using LynxPrompt CLI (recommended):");
3430
- sections.push("lynxp push # Upload local changes to cloud");
3431
- sections.push("lynxp pull # Download cloud changes to local");
3432
- sections.push("lynxp diff # Compare local vs cloud versions");
3573
+ sections.push(`lynxp push # Upload local changes to cloud`);
3574
+ sections.push(`lynxp pull ${bpId} # Download cloud changes to local`);
3575
+ sections.push(`lynxp diff # Compare local vs cloud versions`);
3433
3576
  sections.push("");
3434
3577
  sections.push("# Install CLI: npm install -g lynxprompt");
3435
3578
  sections.push("# Login: lynxp login");
@@ -3438,21 +3581,19 @@ function generateFileContent(options, platform2) {
3438
3581
  sections.push("");
3439
3582
  sections.push(`# Using curl with environment variable (token stored in $${tokenEnvVar}):`);
3440
3583
  sections.push("");
3584
+ const apiUrl = getApiUrl().replace(/\/+$/, "");
3585
+ const url = `${apiUrl}/api/v1/blueprints/${bpId}`;
3441
3586
  sections.push("# Push local changes to cloud:");
3442
- sections.push(`curl -X PUT "https://api.lynxprompt.com/api/v1/blueprints/${bpId}" \\`);
3443
- sections.push(` -H "Authorization: Bearer $${tokenEnvVar}" \\`);
3444
- sections.push(' -H "Content-Type: application/json" \\');
3445
- sections.push(` -d "{\\"content\\": \\"$(cat ${fileName} | jq -Rs .)\\"}"`);
3587
+ sections.push(`jq -Rs '{content: .}' ${fileName} | curl -sf -X PUT "${url}" -H "Authorization: Bearer $${tokenEnvVar}" -H "Content-Type: application/json" --data-binary @-`);
3446
3588
  sections.push("");
3447
3589
  sections.push("# Pull cloud changes to local:");
3448
- sections.push(`curl -s "https://api.lynxprompt.com/api/v1/blueprints/${bpId}" \\`);
3449
- sections.push(` -H "Authorization: Bearer $${tokenEnvVar}" | jq -r '.content' > ${fileName}`);
3590
+ sections.push(`curl -sf "${url}" -H "Authorization: Bearer $${tokenEnvVar}" | jq -er '.blueprint.content' > ${fileName}.new && mv ${fileName}.new ${fileName}`);
3450
3591
  sections.push("");
3451
3592
  sections.push(`# Set your token: export ${tokenEnvVar}="your_token_here"`);
3452
- sections.push("# Generate token at: https://lynxprompt.com/settings");
3593
+ sections.push(`# Generate token at: ${apiUrl}/settings`);
3453
3594
  }
3454
3595
  sections.push("");
3455
- sections.push("Docs: https://lynxprompt.com/docs/api");
3596
+ sections.push(`Docs: ${getApiUrl().replace(/\/+$/, "")}/docs/api`);
3456
3597
  sections.push("-->");
3457
3598
  sections.push("");
3458
3599
  }
@@ -5726,7 +5867,7 @@ var TEST_FRAMEWORKS2 = TEST_FRAMEWORKS;
5726
5867
 
5727
5868
  // src/commands/wizard.ts
5728
5869
  var DRAFTS_DIR = ".lynxprompt/drafts";
5729
- var CLI_VERSION = "2.1.36";
5870
+ var CLI_VERSION = "2.1.37";
5730
5871
  async function saveDraftLocally(name, config2, stepReached) {
5731
5872
  const draftsPath = join3(process.cwd(), DRAFTS_DIR);
5732
5873
  await mkdir3(draftsPath, { recursive: true });
@@ -11378,7 +11519,7 @@ async function configCommand(action, valueArg) {
11378
11519
  }
11379
11520
 
11380
11521
  // src/index.ts
11381
- var CLI_VERSION2 = "2.1.36";
11522
+ var CLI_VERSION2 = "2.1.37";
11382
11523
  var program = new Command();
11383
11524
  program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version(CLI_VERSION2);
11384
11525
  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);
@@ -11391,7 +11532,7 @@ program.command("status").description("Show current AI configuration and tracked
11391
11532
  program.command("pull <id>").description("Download a blueprint (bp_xxx) or entire hierarchy (ha_xxx)").option("-o, --output <path>", "Output directory", ".").option("-y, --yes", "Overwrite existing files without prompting").option("--preview", "Preview content without downloading").option("--no-track", "Don't track the blueprint for future syncs").action(pullCommand);
11392
11533
  program.command("search <query>").description("Search public blueprints in the marketplace").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
11393
11534
  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);
11394
- 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", "PRIVATE").option("-t, --tags <tags>", "Tags (comma-separated)").option("-y, --yes", "Skip prompts").option("-f, --force", "Force push (overwrite remote changes)").action(pushCommand);
11535
+ 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", "PRIVATE").option("-t, --tags <tags>", "Tags (comma-separated)").option("-y, --yes", "Skip prompts").option("-f, --force", "Force push (overwrite remote changes)").option("--allow-sensitive", "Publish public content even if it looks like it holds secrets").action(pushCommand);
11395
11536
  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);
11396
11537
  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);
11397
11538
  program.command("unlink [file]").description("Disconnect a local file from its cloud blueprint").action(unlinkCommand);