lynxprompt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1324 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk9 from "chalk";
6
+
7
+ // src/commands/login.ts
8
+ import chalk from "chalk";
9
+ import ora from "ora";
10
+
11
+ // src/config.ts
12
+ import Conf from "conf";
13
+ var config = new Conf({
14
+ projectName: "lynxprompt",
15
+ schema: {
16
+ token: {
17
+ type: "string"
18
+ },
19
+ apiUrl: {
20
+ type: "string",
21
+ default: "https://lynxprompt.com"
22
+ },
23
+ user: {
24
+ type: "object",
25
+ properties: {
26
+ id: { type: "string" },
27
+ email: { type: "string" },
28
+ name: { type: ["string", "null"] },
29
+ plan: { type: "string" }
30
+ }
31
+ }
32
+ },
33
+ defaults: {
34
+ apiUrl: "https://lynxprompt.com"
35
+ }
36
+ });
37
+ function getToken() {
38
+ const envToken = process.env.LYNXPROMPT_TOKEN;
39
+ if (envToken) {
40
+ return envToken;
41
+ }
42
+ return config.get("token");
43
+ }
44
+ function setToken(token) {
45
+ config.set("token", token);
46
+ }
47
+ function clearToken() {
48
+ config.delete("token");
49
+ config.delete("user");
50
+ }
51
+ function getApiUrl() {
52
+ const envUrl = process.env.LYNXPROMPT_API_URL;
53
+ if (envUrl) {
54
+ return envUrl;
55
+ }
56
+ return config.get("apiUrl");
57
+ }
58
+ function getUser() {
59
+ return config.get("user");
60
+ }
61
+ function setUser(user) {
62
+ config.set("user", user);
63
+ }
64
+ function isAuthenticated() {
65
+ return !!getToken();
66
+ }
67
+
68
+ // src/api.ts
69
+ var ApiClient = class {
70
+ getHeaders() {
71
+ const token = getToken();
72
+ const headers = {
73
+ "Content-Type": "application/json"
74
+ };
75
+ if (token) {
76
+ headers["Authorization"] = `Bearer ${token}`;
77
+ }
78
+ return headers;
79
+ }
80
+ async request(endpoint, options = {}) {
81
+ const baseUrl = getApiUrl();
82
+ const url = `${baseUrl}${endpoint}`;
83
+ const response = await fetch(url, {
84
+ ...options,
85
+ headers: {
86
+ ...this.getHeaders(),
87
+ ...options.headers
88
+ }
89
+ });
90
+ const data = await response.json();
91
+ if (!response.ok) {
92
+ const error = data;
93
+ throw new ApiRequestError(
94
+ error.message || error.error || "Request failed",
95
+ response.status,
96
+ error
97
+ );
98
+ }
99
+ return data;
100
+ }
101
+ // Auth endpoints
102
+ async initCliSession() {
103
+ return this.request("/api/cli-auth/init", {
104
+ method: "POST"
105
+ });
106
+ }
107
+ async pollCliSession(sessionId) {
108
+ return this.request(
109
+ `/api/cli-auth/poll?session=${sessionId}`
110
+ );
111
+ }
112
+ // User endpoints
113
+ async getUser() {
114
+ return this.request("/api/v1/user");
115
+ }
116
+ // Blueprint endpoints
117
+ async listBlueprints(options = {}) {
118
+ const params = new URLSearchParams();
119
+ if (options.limit) params.set("limit", options.limit.toString());
120
+ if (options.offset) params.set("offset", options.offset.toString());
121
+ if (options.visibility) params.set("visibility", options.visibility);
122
+ const query = params.toString();
123
+ return this.request(
124
+ `/api/v1/blueprints${query ? `?${query}` : ""}`
125
+ );
126
+ }
127
+ async getBlueprint(id) {
128
+ const apiId = id.startsWith("bp_") ? id : `bp_${id}`;
129
+ return this.request(`/api/v1/blueprints/${apiId}`);
130
+ }
131
+ async searchBlueprints(query, limit = 20) {
132
+ const params = new URLSearchParams({
133
+ q: query,
134
+ limit: limit.toString()
135
+ });
136
+ return this.request(`/api/blueprints?${params}`);
137
+ }
138
+ };
139
+ var ApiRequestError = class extends Error {
140
+ constructor(message, statusCode, response) {
141
+ super(message);
142
+ this.statusCode = statusCode;
143
+ this.response = response;
144
+ this.name = "ApiRequestError";
145
+ }
146
+ };
147
+ var api = new ApiClient();
148
+
149
+ // src/commands/login.ts
150
+ async function loginCommand() {
151
+ if (isAuthenticated()) {
152
+ console.log(
153
+ chalk.yellow(
154
+ "You are already logged in. Use 'lynxprompt logout' first to switch accounts."
155
+ )
156
+ );
157
+ return;
158
+ }
159
+ const spinner = ora("Initializing authentication...").start();
160
+ try {
161
+ const session = await api.initCliSession();
162
+ spinner.stop();
163
+ console.log();
164
+ console.log(chalk.cyan("\u{1F510} Opening browser to authenticate..."));
165
+ console.log(chalk.gray(` ${session.auth_url}`));
166
+ console.log();
167
+ const openBrowser = await tryOpenBrowser(session.auth_url);
168
+ if (!openBrowser) {
169
+ console.log(
170
+ chalk.yellow("Could not open browser automatically. Please open the URL above manually.")
171
+ );
172
+ }
173
+ const pollSpinner = ora("Waiting for authentication...").start();
174
+ const maxWaitTime = 5 * 60 * 1e3;
175
+ const pollInterval = 2e3;
176
+ const startTime = Date.now();
177
+ while (Date.now() - startTime < maxWaitTime) {
178
+ await sleep(pollInterval);
179
+ try {
180
+ const result = await api.pollCliSession(session.session_id);
181
+ if (result.status === "completed" && result.token && result.user) {
182
+ pollSpinner.succeed("Authentication successful!");
183
+ setToken(result.token);
184
+ setUser(result.user);
185
+ console.log();
186
+ console.log(chalk.green(`\u2705 Logged in as ${chalk.bold(result.user.email)}`));
187
+ console.log(chalk.gray(` Plan: ${result.user.plan}`));
188
+ console.log(chalk.gray(` Token stored securely in config`));
189
+ console.log();
190
+ console.log(chalk.cyan("You're ready to use LynxPrompt CLI!"));
191
+ return;
192
+ }
193
+ if (result.status === "expired") {
194
+ pollSpinner.fail("Authentication session expired. Please try again.");
195
+ return;
196
+ }
197
+ } catch (error) {
198
+ }
199
+ }
200
+ pollSpinner.fail("Authentication timed out. Please try again.");
201
+ } catch (error) {
202
+ spinner.fail("Failed to initialize authentication");
203
+ if (error instanceof ApiRequestError) {
204
+ console.error(chalk.red(`Error: ${error.message}`));
205
+ if (error.statusCode === 503) {
206
+ console.error(chalk.gray("The server may be temporarily unavailable. Please try again later."));
207
+ }
208
+ } else {
209
+ console.error(chalk.red("An unexpected error occurred."));
210
+ console.error(chalk.gray("Make sure you have internet connectivity and try again."));
211
+ }
212
+ process.exit(1);
213
+ }
214
+ }
215
+ async function tryOpenBrowser(url) {
216
+ try {
217
+ const { default: open } = await import("open");
218
+ await open(url);
219
+ return true;
220
+ } catch {
221
+ const { exec } = await import("child_process");
222
+ const { promisify } = await import("util");
223
+ const execAsync = promisify(exec);
224
+ const platform = process.platform;
225
+ let command;
226
+ if (platform === "darwin") {
227
+ command = `open "${url}"`;
228
+ } else if (platform === "win32") {
229
+ command = `start "" "${url}"`;
230
+ } else {
231
+ command = `xdg-open "${url}"`;
232
+ }
233
+ try {
234
+ await execAsync(command);
235
+ return true;
236
+ } catch {
237
+ return false;
238
+ }
239
+ }
240
+ }
241
+ function sleep(ms) {
242
+ return new Promise((resolve) => setTimeout(resolve, ms));
243
+ }
244
+
245
+ // src/commands/logout.ts
246
+ import chalk2 from "chalk";
247
+ async function logoutCommand() {
248
+ if (!isAuthenticated()) {
249
+ console.log(chalk2.yellow("You are not currently logged in."));
250
+ return;
251
+ }
252
+ const user = getUser();
253
+ const email = user?.email || "unknown";
254
+ clearToken();
255
+ console.log(chalk2.green(`\u2713 Logged out from ${chalk2.bold(email)}`));
256
+ console.log(chalk2.gray(" Removed stored credentials"));
257
+ }
258
+
259
+ // src/commands/whoami.ts
260
+ import chalk3 from "chalk";
261
+ import ora2 from "ora";
262
+ async function whoamiCommand() {
263
+ if (!isAuthenticated()) {
264
+ console.log(chalk3.yellow("Not logged in. Run 'lynxprompt login' to authenticate."));
265
+ process.exit(1);
266
+ }
267
+ const spinner = ora2("Fetching user info...").start();
268
+ try {
269
+ const { user } = await api.getUser();
270
+ spinner.stop();
271
+ setUser({
272
+ id: user.id,
273
+ email: user.email,
274
+ name: user.name,
275
+ plan: user.subscription.plan
276
+ });
277
+ console.log();
278
+ console.log(chalk3.cyan("\u{1F431} LynxPrompt Account"));
279
+ console.log();
280
+ console.log(` ${chalk3.gray("Email:")} ${chalk3.bold(user.email)}`);
281
+ if (user.name) {
282
+ console.log(` ${chalk3.gray("Name:")} ${user.name}`);
283
+ }
284
+ if (user.display_name) {
285
+ console.log(` ${chalk3.gray("Display:")} ${user.display_name}`);
286
+ }
287
+ console.log(` ${chalk3.gray("Plan:")} ${formatPlan(user.subscription.plan)}`);
288
+ if (user.subscription.status) {
289
+ console.log(` ${chalk3.gray("Status:")} ${user.subscription.status}`);
290
+ }
291
+ if (user.subscription.current_period_end) {
292
+ const endDate = new Date(user.subscription.current_period_end);
293
+ console.log(` ${chalk3.gray("Renews:")} ${endDate.toLocaleDateString()}`);
294
+ }
295
+ console.log();
296
+ console.log(` ${chalk3.gray("Blueprints:")} ${user.stats.blueprints_count}`);
297
+ console.log(` ${chalk3.gray("Member since:")} ${new Date(user.created_at).toLocaleDateString()}`);
298
+ console.log();
299
+ } catch (error) {
300
+ spinner.fail("Failed to fetch user info");
301
+ if (error instanceof ApiRequestError) {
302
+ if (error.statusCode === 401) {
303
+ console.error(chalk3.red("Your session has expired. Please run 'lynxprompt login' again."));
304
+ } else if (error.statusCode === 403) {
305
+ console.error(chalk3.red("API access requires Pro, Max, or Teams subscription."));
306
+ console.error(chalk3.gray("Upgrade at https://lynxprompt.com/pricing"));
307
+ } else {
308
+ console.error(chalk3.red(`Error: ${error.message}`));
309
+ }
310
+ } else {
311
+ console.error(chalk3.red("An unexpected error occurred."));
312
+ }
313
+ process.exit(1);
314
+ }
315
+ }
316
+ function formatPlan(plan) {
317
+ const planColors = {
318
+ FREE: chalk3.gray,
319
+ PRO: chalk3.blue,
320
+ MAX: chalk3.magenta,
321
+ TEAMS: chalk3.cyan
322
+ };
323
+ const colorFn = planColors[plan] || chalk3.white;
324
+ return colorFn(plan);
325
+ }
326
+
327
+ // src/commands/list.ts
328
+ import chalk4 from "chalk";
329
+ import ora3 from "ora";
330
+ async function listCommand(options) {
331
+ if (!isAuthenticated()) {
332
+ console.log(chalk4.yellow("Not logged in. Run 'lynxprompt login' to authenticate."));
333
+ process.exit(1);
334
+ }
335
+ const spinner = ora3("Fetching your blueprints...").start();
336
+ try {
337
+ const limit = parseInt(options.limit, 10) || 20;
338
+ const { blueprints, total, has_more } = await api.listBlueprints({
339
+ limit,
340
+ visibility: options.visibility
341
+ });
342
+ spinner.stop();
343
+ if (blueprints.length === 0) {
344
+ console.log();
345
+ console.log(chalk4.yellow("No blueprints found."));
346
+ console.log(chalk4.gray("Create your first blueprint at https://lynxprompt.com/blueprints/create"));
347
+ console.log(chalk4.gray("Or run 'lynxprompt init' to generate one from the CLI!"));
348
+ return;
349
+ }
350
+ console.log();
351
+ console.log(chalk4.cyan(`\u{1F431} Your Blueprints (${total} total)`));
352
+ console.log();
353
+ for (const bp of blueprints) {
354
+ printBlueprint(bp);
355
+ }
356
+ if (has_more) {
357
+ console.log(chalk4.gray(`Showing ${blueprints.length} of ${total}. Use --limit to see more.`));
358
+ }
359
+ } catch (error) {
360
+ spinner.fail("Failed to fetch blueprints");
361
+ handleApiError(error);
362
+ }
363
+ }
364
+ function printBlueprint(bp) {
365
+ const visibilityIcon = {
366
+ PRIVATE: "\u{1F512}",
367
+ TEAM: "\u{1F465}",
368
+ PUBLIC: "\u{1F310}"
369
+ }[bp.visibility] || "\u{1F4C4}";
370
+ const tierBadge = {
371
+ SIMPLE: chalk4.gray("[Basic]"),
372
+ INTERMEDIATE: chalk4.blue("[Pro]"),
373
+ ADVANCED: chalk4.magenta("[Max]")
374
+ }[bp.tier] || "";
375
+ console.log(` ${visibilityIcon} ${chalk4.bold(bp.name)} ${tierBadge}`);
376
+ console.log(` ${chalk4.cyan(bp.id)}`);
377
+ if (bp.description) {
378
+ console.log(` ${chalk4.gray(truncate(bp.description, 60))}`);
379
+ }
380
+ console.log(` ${chalk4.gray(`\u2193${bp.downloads}`)} ${chalk4.gray(`\u2665${bp.favorites}`)} ${formatTags(bp.tags)}`);
381
+ console.log();
382
+ }
383
+ function formatTags(tags) {
384
+ if (!tags || tags.length === 0) return "";
385
+ return tags.slice(0, 3).map((t) => chalk4.gray(`#${t}`)).join(" ");
386
+ }
387
+ function truncate(str, maxLength) {
388
+ if (str.length <= maxLength) return str;
389
+ return str.slice(0, maxLength - 3) + "...";
390
+ }
391
+ function handleApiError(error) {
392
+ if (error instanceof ApiRequestError) {
393
+ if (error.statusCode === 401) {
394
+ console.error(chalk4.red("Your session has expired. Please run 'lynxprompt login' again."));
395
+ } else if (error.statusCode === 403) {
396
+ console.error(chalk4.red("API access requires Pro, Max, or Teams subscription."));
397
+ console.error(chalk4.gray("Upgrade at https://lynxprompt.com/pricing"));
398
+ } else {
399
+ console.error(chalk4.red(`Error: ${error.message}`));
400
+ }
401
+ } else {
402
+ console.error(chalk4.red("An unexpected error occurred."));
403
+ }
404
+ process.exit(1);
405
+ }
406
+
407
+ // src/commands/pull.ts
408
+ import chalk5 from "chalk";
409
+ import ora4 from "ora";
410
+ import prompts from "prompts";
411
+ import { writeFile, access, mkdir } from "fs/promises";
412
+ import { join, dirname } from "path";
413
+ var TYPE_TO_FILENAME = {
414
+ AGENTS_MD: "AGENTS.md",
415
+ CURSOR_RULES: ".cursorrules",
416
+ COPILOT_INSTRUCTIONS: ".github/copilot-instructions.md",
417
+ WINDSURF_RULES: ".windsurfrules",
418
+ ZED_INSTRUCTIONS: ".zed/instructions.md",
419
+ CLAUDE_MD: "CLAUDE.md",
420
+ GENERIC: "ai-config.md"
421
+ };
422
+ async function pullCommand(id, options) {
423
+ if (!isAuthenticated()) {
424
+ console.log(
425
+ chalk5.yellow("Not logged in. Run 'lynxprompt login' to authenticate.")
426
+ );
427
+ process.exit(1);
428
+ }
429
+ const spinner = ora4(`Fetching blueprint ${chalk5.cyan(id)}...`).start();
430
+ try {
431
+ const { blueprint } = await api.getBlueprint(id);
432
+ spinner.stop();
433
+ if (!blueprint.content) {
434
+ console.error(chalk5.red("Blueprint has no content."));
435
+ process.exit(1);
436
+ }
437
+ const filename = TYPE_TO_FILENAME[blueprint.type] || "ai-config.md";
438
+ const outputPath = join(options.output, filename);
439
+ let fileExists2 = false;
440
+ try {
441
+ await access(outputPath);
442
+ fileExists2 = true;
443
+ } catch {
444
+ }
445
+ if (fileExists2 && !options.yes) {
446
+ const response = await prompts({
447
+ type: "confirm",
448
+ name: "overwrite",
449
+ message: `File ${chalk5.cyan(outputPath)} already exists. Overwrite?`,
450
+ initial: false
451
+ });
452
+ if (!response.overwrite) {
453
+ console.log(chalk5.yellow("Aborted."));
454
+ return;
455
+ }
456
+ }
457
+ const dir = dirname(outputPath);
458
+ if (dir !== ".") {
459
+ await mkdir(dir, { recursive: true });
460
+ }
461
+ await writeFile(outputPath, blueprint.content, "utf-8");
462
+ console.log();
463
+ console.log(chalk5.green(`\u2705 Downloaded ${chalk5.bold(blueprint.name)}`));
464
+ console.log(` ${chalk5.gray("File:")} ${chalk5.cyan(outputPath)}`);
465
+ console.log(` ${chalk5.gray("Type:")} ${blueprint.type}`);
466
+ console.log(` ${chalk5.gray("Tier:")} ${blueprint.tier}`);
467
+ console.log();
468
+ const editorHint = getEditorHint(blueprint.type);
469
+ if (editorHint) {
470
+ console.log(chalk5.gray(`\u{1F4A1} ${editorHint}`));
471
+ }
472
+ } catch (error) {
473
+ spinner.fail("Failed to pull blueprint");
474
+ handleApiError2(error);
475
+ }
476
+ }
477
+ function getEditorHint(type) {
478
+ switch (type) {
479
+ case "CURSOR_RULES":
480
+ return "This file will be automatically read by Cursor.";
481
+ case "COPILOT_INSTRUCTIONS":
482
+ return "This file will be read by GitHub Copilot in VS Code.";
483
+ case "AGENTS_MD":
484
+ return "This AGENTS.md file works with Claude Code, Cursor, and other AI agents.";
485
+ case "WINDSURF_RULES":
486
+ return "This file will be automatically read by Windsurf.";
487
+ case "ZED_INSTRUCTIONS":
488
+ return "This file will be read by Zed's AI assistant.";
489
+ case "CLAUDE_MD":
490
+ return "This CLAUDE.md file will be read by Claude Code.";
491
+ default:
492
+ return null;
493
+ }
494
+ }
495
+ function handleApiError2(error) {
496
+ if (error instanceof ApiRequestError) {
497
+ if (error.statusCode === 401) {
498
+ console.error(
499
+ chalk5.red("Your session has expired. Please run 'lynxprompt login' again.")
500
+ );
501
+ } else if (error.statusCode === 403) {
502
+ console.error(
503
+ chalk5.red("You don't have access to this blueprint.")
504
+ );
505
+ console.error(
506
+ chalk5.gray(
507
+ "This might be a private blueprint or require a higher subscription tier."
508
+ )
509
+ );
510
+ } else if (error.statusCode === 404) {
511
+ console.error(chalk5.red("Blueprint not found."));
512
+ console.error(
513
+ chalk5.gray(
514
+ "Make sure you have the correct blueprint ID. Use 'lynxprompt list' to see your blueprints."
515
+ )
516
+ );
517
+ } else {
518
+ console.error(chalk5.red(`Error: ${error.message}`));
519
+ }
520
+ } else {
521
+ console.error(chalk5.red("An unexpected error occurred."));
522
+ }
523
+ process.exit(1);
524
+ }
525
+
526
+ // src/commands/init.ts
527
+ import chalk6 from "chalk";
528
+ import prompts2 from "prompts";
529
+ import ora5 from "ora";
530
+ import { writeFile as writeFile2, mkdir as mkdir2, access as access3 } from "fs/promises";
531
+ import { join as join3, dirname as dirname2 } from "path";
532
+
533
+ // src/utils/detect.ts
534
+ import { readFile, access as access2 } from "fs/promises";
535
+ import { join as join2 } from "path";
536
+ async function detectProject(cwd) {
537
+ const detected = {
538
+ name: null,
539
+ stack: [],
540
+ commands: {},
541
+ packageManager: null
542
+ };
543
+ const packageJsonPath = join2(cwd, "package.json");
544
+ if (await fileExists(packageJsonPath)) {
545
+ try {
546
+ const content = await readFile(packageJsonPath, "utf-8");
547
+ const pkg = JSON.parse(content);
548
+ detected.name = pkg.name || null;
549
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
550
+ if (allDeps["typescript"]) detected.stack.push("typescript");
551
+ if (allDeps["react"]) detected.stack.push("react");
552
+ if (allDeps["next"]) detected.stack.push("nextjs");
553
+ if (allDeps["vue"]) detected.stack.push("vue");
554
+ if (allDeps["@angular/core"]) detected.stack.push("angular");
555
+ if (allDeps["svelte"]) detected.stack.push("svelte");
556
+ if (allDeps["express"]) detected.stack.push("express");
557
+ if (allDeps["fastify"]) detected.stack.push("fastify");
558
+ if (allDeps["prisma"]) detected.stack.push("prisma");
559
+ if (allDeps["tailwindcss"]) detected.stack.push("tailwind");
560
+ if (pkg.scripts) {
561
+ detected.commands.build = pkg.scripts.build;
562
+ detected.commands.test = pkg.scripts.test;
563
+ detected.commands.lint = pkg.scripts.lint;
564
+ detected.commands.dev = pkg.scripts.dev || pkg.scripts.start;
565
+ }
566
+ if (await fileExists(join2(cwd, "pnpm-lock.yaml"))) {
567
+ detected.packageManager = "pnpm";
568
+ } else if (await fileExists(join2(cwd, "yarn.lock"))) {
569
+ detected.packageManager = "yarn";
570
+ } else if (await fileExists(join2(cwd, "bun.lockb"))) {
571
+ detected.packageManager = "bun";
572
+ } else if (await fileExists(join2(cwd, "package-lock.json"))) {
573
+ detected.packageManager = "npm";
574
+ }
575
+ return detected;
576
+ } catch {
577
+ }
578
+ }
579
+ const pyprojectPath = join2(cwd, "pyproject.toml");
580
+ if (await fileExists(pyprojectPath)) {
581
+ try {
582
+ const content = await readFile(pyprojectPath, "utf-8");
583
+ detected.stack.push("python");
584
+ const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
585
+ if (nameMatch) detected.name = nameMatch[1];
586
+ if (content.includes("fastapi")) detected.stack.push("fastapi");
587
+ if (content.includes("django")) detected.stack.push("django");
588
+ if (content.includes("flask")) detected.stack.push("flask");
589
+ detected.commands.test = "pytest";
590
+ detected.commands.lint = "ruff check";
591
+ return detected;
592
+ } catch {
593
+ }
594
+ }
595
+ const requirementsPath = join2(cwd, "requirements.txt");
596
+ if (await fileExists(requirementsPath)) {
597
+ try {
598
+ const content = await readFile(requirementsPath, "utf-8");
599
+ detected.stack.push("python");
600
+ if (content.includes("fastapi")) detected.stack.push("fastapi");
601
+ if (content.includes("django")) detected.stack.push("django");
602
+ if (content.includes("flask")) detected.stack.push("flask");
603
+ detected.commands.test = "pytest";
604
+ detected.commands.lint = "ruff check";
605
+ return detected;
606
+ } catch {
607
+ }
608
+ }
609
+ const cargoPath = join2(cwd, "Cargo.toml");
610
+ if (await fileExists(cargoPath)) {
611
+ try {
612
+ const content = await readFile(cargoPath, "utf-8");
613
+ detected.stack.push("rust");
614
+ const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
615
+ if (nameMatch) detected.name = nameMatch[1];
616
+ detected.commands.build = "cargo build";
617
+ detected.commands.test = "cargo test";
618
+ detected.commands.lint = "cargo clippy";
619
+ return detected;
620
+ } catch {
621
+ }
622
+ }
623
+ const goModPath = join2(cwd, "go.mod");
624
+ if (await fileExists(goModPath)) {
625
+ try {
626
+ const content = await readFile(goModPath, "utf-8");
627
+ detected.stack.push("go");
628
+ const moduleMatch = content.match(/module\s+(\S+)/);
629
+ if (moduleMatch) {
630
+ const parts = moduleMatch[1].split("/");
631
+ detected.name = parts[parts.length - 1];
632
+ }
633
+ detected.commands.build = "go build";
634
+ detected.commands.test = "go test ./...";
635
+ detected.commands.lint = "golangci-lint run";
636
+ return detected;
637
+ } catch {
638
+ }
639
+ }
640
+ const makefilePath = join2(cwd, "Makefile");
641
+ if (await fileExists(makefilePath)) {
642
+ try {
643
+ const content = await readFile(makefilePath, "utf-8");
644
+ if (content.includes("build:")) detected.commands.build = "make build";
645
+ if (content.includes("test:")) detected.commands.test = "make test";
646
+ if (content.includes("lint:")) detected.commands.lint = "make lint";
647
+ if (Object.keys(detected.commands).length > 0) {
648
+ return detected;
649
+ }
650
+ } catch {
651
+ }
652
+ }
653
+ return detected.stack.length > 0 || detected.name ? detected : null;
654
+ }
655
+ async function fileExists(path) {
656
+ try {
657
+ await access2(path);
658
+ return true;
659
+ } catch {
660
+ return false;
661
+ }
662
+ }
663
+
664
+ // src/utils/generator.ts
665
+ var PLATFORM_FILES = {
666
+ cursor: ".cursorrules",
667
+ claude: "AGENTS.md",
668
+ copilot: ".github/copilot-instructions.md",
669
+ windsurf: ".windsurfrules",
670
+ zed: ".zed/instructions.md"
671
+ };
672
+ var PERSONA_DESCRIPTIONS = {
673
+ backend: "a senior backend developer specializing in APIs, databases, and microservices architecture",
674
+ frontend: "a senior frontend developer specializing in UI components, styling, and user experience",
675
+ fullstack: "a senior full-stack developer capable of working across the entire application stack",
676
+ devops: "a DevOps engineer specializing in infrastructure, CI/CD pipelines, and containerization",
677
+ data: "a data engineer specializing in data pipelines, ETL processes, and database optimization",
678
+ security: "a security engineer focused on secure coding practices and vulnerability prevention"
679
+ };
680
+ var STACK_NAMES = {
681
+ typescript: "TypeScript",
682
+ javascript: "JavaScript",
683
+ python: "Python",
684
+ go: "Go",
685
+ rust: "Rust",
686
+ java: "Java",
687
+ csharp: "C#/.NET",
688
+ ruby: "Ruby",
689
+ php: "PHP",
690
+ swift: "Swift",
691
+ react: "React",
692
+ nextjs: "Next.js",
693
+ vue: "Vue.js",
694
+ angular: "Angular",
695
+ svelte: "Svelte",
696
+ express: "Express.js",
697
+ fastapi: "FastAPI",
698
+ django: "Django",
699
+ flask: "Flask",
700
+ spring: "Spring Boot",
701
+ rails: "Ruby on Rails",
702
+ laravel: "Laravel",
703
+ prisma: "Prisma",
704
+ tailwind: "Tailwind CSS",
705
+ fastify: "Fastify"
706
+ };
707
+ var BOUNDARIES = {
708
+ conservative: {
709
+ always: ["Read any file in the project", "Run lint and format commands"],
710
+ askFirst: [
711
+ "Modify any source file",
712
+ "Add new dependencies",
713
+ "Create new files",
714
+ "Run test commands",
715
+ "Modify configuration files"
716
+ ],
717
+ never: [
718
+ "Delete any files",
719
+ "Modify .env or secret files",
720
+ "Push to git or make commits",
721
+ "Access external APIs or services"
722
+ ]
723
+ },
724
+ standard: {
725
+ always: [
726
+ "Read any file in the project",
727
+ "Modify files in src/ or lib/",
728
+ "Run build, test, and lint commands",
729
+ "Create test files",
730
+ "Fix linting errors automatically"
731
+ ],
732
+ askFirst: [
733
+ "Add new dependencies to package.json",
734
+ "Modify configuration files at root level",
735
+ "Create new modules or directories",
736
+ "Refactor code structure significantly"
737
+ ],
738
+ never: [
739
+ "Modify .env files or secrets",
740
+ "Delete critical files without backup",
741
+ "Force push to git",
742
+ "Expose sensitive information in logs"
743
+ ]
744
+ },
745
+ permissive: {
746
+ always: [
747
+ "Modify any file in src/ or lib/",
748
+ "Run any build, test, or dev scripts",
749
+ "Add or update dependencies",
750
+ "Create new files and directories",
751
+ "Refactor and reorganize code"
752
+ ],
753
+ askFirst: [
754
+ "Modify root-level configuration files",
755
+ "Delete directories",
756
+ "Make breaking changes to public APIs"
757
+ ],
758
+ never: [
759
+ "Modify .env files directly",
760
+ "Commit secrets or credentials",
761
+ "Access production databases"
762
+ ]
763
+ }
764
+ };
765
+ function generateConfig(options) {
766
+ const files = {};
767
+ for (const platform of options.platforms) {
768
+ const filename = PLATFORM_FILES[platform];
769
+ if (filename) {
770
+ files[filename] = generateFileContent(options, platform);
771
+ }
772
+ }
773
+ return files;
774
+ }
775
+ function generateFileContent(options, platform) {
776
+ const sections = [];
777
+ const isMarkdown = platform !== "cursor" && platform !== "windsurf";
778
+ if (isMarkdown) {
779
+ sections.push(`# ${options.name} - AI Assistant Configuration`);
780
+ sections.push("");
781
+ }
782
+ const personaDesc = PERSONA_DESCRIPTIONS[options.persona] || options.persona;
783
+ if (isMarkdown) {
784
+ sections.push("## Persona");
785
+ sections.push("");
786
+ sections.push(`You are ${personaDesc}. You assist developers working on ${options.name}.`);
787
+ } else {
788
+ sections.push(`You are ${personaDesc}. You assist developers working on ${options.name}.`);
789
+ }
790
+ if (options.description) {
791
+ sections.push("");
792
+ sections.push(`Project description: ${options.description}`);
793
+ }
794
+ sections.push("");
795
+ if (options.stack.length > 0) {
796
+ if (isMarkdown) {
797
+ sections.push("## Tech Stack");
798
+ sections.push("");
799
+ } else {
800
+ sections.push("Tech Stack:");
801
+ }
802
+ const stackList = options.stack.map((s) => STACK_NAMES[s] || s);
803
+ if (isMarkdown) {
804
+ for (const tech of stackList) {
805
+ sections.push(`- ${tech}`);
806
+ }
807
+ } else {
808
+ sections.push(stackList.join(", "));
809
+ }
810
+ sections.push("");
811
+ }
812
+ const hasCommands = Object.values(options.commands).some(Boolean);
813
+ if (hasCommands) {
814
+ if (isMarkdown) {
815
+ sections.push("## Commands");
816
+ sections.push("");
817
+ sections.push("Use these commands for common tasks:");
818
+ sections.push("");
819
+ sections.push("```bash");
820
+ } else {
821
+ sections.push("Commands:");
822
+ }
823
+ if (options.commands.build) {
824
+ sections.push(isMarkdown ? `# Build: ${options.commands.build}` : `- Build: ${options.commands.build}`);
825
+ }
826
+ if (options.commands.test) {
827
+ sections.push(isMarkdown ? `# Test: ${options.commands.test}` : `- Test: ${options.commands.test}`);
828
+ }
829
+ if (options.commands.lint) {
830
+ sections.push(isMarkdown ? `# Lint: ${options.commands.lint}` : `- Lint: ${options.commands.lint}`);
831
+ }
832
+ if (options.commands.dev) {
833
+ sections.push(isMarkdown ? `# Dev: ${options.commands.dev}` : `- Dev: ${options.commands.dev}`);
834
+ }
835
+ if (isMarkdown) {
836
+ sections.push("```");
837
+ }
838
+ sections.push("");
839
+ }
840
+ const boundaries = BOUNDARIES[options.boundaries];
841
+ if (boundaries) {
842
+ if (isMarkdown) {
843
+ sections.push("## Boundaries");
844
+ sections.push("");
845
+ sections.push("### \u2705 Always (do without asking)");
846
+ sections.push("");
847
+ for (const item of boundaries.always) {
848
+ sections.push(`- ${item}`);
849
+ }
850
+ sections.push("");
851
+ sections.push("### \u26A0\uFE0F Ask First");
852
+ sections.push("");
853
+ for (const item of boundaries.askFirst) {
854
+ sections.push(`- ${item}`);
855
+ }
856
+ sections.push("");
857
+ sections.push("### \u{1F6AB} Never");
858
+ sections.push("");
859
+ for (const item of boundaries.never) {
860
+ sections.push(`- ${item}`);
861
+ }
862
+ } else {
863
+ sections.push("Boundaries:");
864
+ sections.push("");
865
+ sections.push("ALWAYS (do without asking):");
866
+ for (const item of boundaries.always) {
867
+ sections.push(`- ${item}`);
868
+ }
869
+ sections.push("");
870
+ sections.push("ASK FIRST:");
871
+ for (const item of boundaries.askFirst) {
872
+ sections.push(`- ${item}`);
873
+ }
874
+ sections.push("");
875
+ sections.push("NEVER:");
876
+ for (const item of boundaries.never) {
877
+ sections.push(`- ${item}`);
878
+ }
879
+ }
880
+ sections.push("");
881
+ }
882
+ if (isMarkdown) {
883
+ sections.push("## Code Style");
884
+ sections.push("");
885
+ sections.push("Follow these conventions:");
886
+ sections.push("");
887
+ if (options.stack.includes("typescript") || options.stack.includes("javascript")) {
888
+ sections.push("- Use TypeScript strict mode when available");
889
+ sections.push("- Prefer const over let, avoid var");
890
+ sections.push("- Use async/await over raw promises");
891
+ sections.push("- Use descriptive variable and function names");
892
+ }
893
+ if (options.stack.includes("react") || options.stack.includes("nextjs")) {
894
+ sections.push("- Use functional components with hooks");
895
+ sections.push("- Keep components small and focused");
896
+ sections.push("- Colocate related files (component, styles, tests)");
897
+ }
898
+ if (options.stack.includes("python")) {
899
+ sections.push("- Follow PEP 8 style guidelines");
900
+ sections.push("- Use type hints for function signatures");
901
+ sections.push("- Prefer f-strings for string formatting");
902
+ }
903
+ if (options.stack.includes("go")) {
904
+ sections.push("- Follow Go conventions (gofmt, golint)");
905
+ sections.push("- Use meaningful package names");
906
+ sections.push("- Handle errors explicitly");
907
+ }
908
+ if (options.stack.includes("rust")) {
909
+ sections.push("- Follow Rust conventions (clippy)");
910
+ sections.push("- Use idiomatic Rust patterns");
911
+ sections.push("- Prefer Result over panic");
912
+ }
913
+ sections.push("- Write self-documenting code");
914
+ sections.push("- Add comments for complex logic only");
915
+ sections.push("- Keep functions focused and testable");
916
+ sections.push("");
917
+ }
918
+ if (isMarkdown) {
919
+ sections.push("---");
920
+ sections.push("");
921
+ sections.push(`*Generated by [LynxPrompt](https://lynxprompt.com) CLI*`);
922
+ }
923
+ return sections.join("\n");
924
+ }
925
+
926
+ // src/commands/init.ts
927
+ var TECH_STACKS = [
928
+ { title: "TypeScript", value: "typescript" },
929
+ { title: "JavaScript", value: "javascript" },
930
+ { title: "Python", value: "python" },
931
+ { title: "Go", value: "go" },
932
+ { title: "Rust", value: "rust" },
933
+ { title: "Java", value: "java" },
934
+ { title: "C#/.NET", value: "csharp" },
935
+ { title: "Ruby", value: "ruby" },
936
+ { title: "PHP", value: "php" },
937
+ { title: "Swift", value: "swift" }
938
+ ];
939
+ var FRAMEWORKS = [
940
+ { title: "React", value: "react" },
941
+ { title: "Next.js", value: "nextjs" },
942
+ { title: "Vue.js", value: "vue" },
943
+ { title: "Angular", value: "angular" },
944
+ { title: "Svelte", value: "svelte" },
945
+ { title: "Express", value: "express" },
946
+ { title: "FastAPI", value: "fastapi" },
947
+ { title: "Django", value: "django" },
948
+ { title: "Flask", value: "flask" },
949
+ { title: "Spring Boot", value: "spring" },
950
+ { title: "Rails", value: "rails" },
951
+ { title: "Laravel", value: "laravel" }
952
+ ];
953
+ var PLATFORMS = [
954
+ { title: "Cursor (.cursorrules)", value: "cursor", filename: ".cursorrules" },
955
+ { title: "Claude Code (AGENTS.md)", value: "claude", filename: "AGENTS.md" },
956
+ { title: "GitHub Copilot", value: "copilot", filename: ".github/copilot-instructions.md" },
957
+ { title: "Windsurf (.windsurfrules)", value: "windsurf", filename: ".windsurfrules" },
958
+ { title: "Zed", value: "zed", filename: ".zed/instructions.md" }
959
+ ];
960
+ var PERSONAS = [
961
+ { title: "Backend Developer - APIs, databases, microservices", value: "backend" },
962
+ { title: "Frontend Developer - UI, components, styling", value: "frontend" },
963
+ { title: "Full-Stack Developer - Complete application setups", value: "fullstack" },
964
+ { title: "DevOps Engineer - Infrastructure, CI/CD, containers", value: "devops" },
965
+ { title: "Data Engineer - Pipelines, ETL, databases", value: "data" },
966
+ { title: "Security Engineer - Secure code, vulnerabilities", value: "security" },
967
+ { title: "Custom...", value: "custom" }
968
+ ];
969
+ var BOUNDARY_PRESETS = [
970
+ {
971
+ title: "Conservative - Ask before most changes",
972
+ value: "conservative",
973
+ always: ["Read any file", "Run lint/format commands"],
974
+ askFirst: ["Modify any file", "Add dependencies", "Create files", "Run tests"],
975
+ never: ["Delete files", "Modify .env", "Push to git"]
976
+ },
977
+ {
978
+ title: "Standard - Balance of freedom and safety",
979
+ value: "standard",
980
+ always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
981
+ askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
982
+ never: ["Delete production data", "Modify .env secrets", "Force push"]
983
+ },
984
+ {
985
+ title: "Permissive - AI can modify freely within src/",
986
+ value: "permissive",
987
+ always: ["Modify any file in src/", "Run any script", "Add dependencies", "Create files"],
988
+ askFirst: ["Modify root configs", "Delete directories"],
989
+ never: ["Modify .env", "Access external APIs without confirmation"]
990
+ }
991
+ ];
992
+ async function initCommand(options) {
993
+ console.log();
994
+ console.log(chalk6.cyan("\u{1F431} Welcome to LynxPrompt!"));
995
+ console.log();
996
+ const detected = await detectProject(process.cwd());
997
+ if (detected) {
998
+ console.log(chalk6.gray("Detected project configuration:"));
999
+ if (detected.name) console.log(chalk6.gray(` Name: ${detected.name}`));
1000
+ if (detected.stack.length > 0) console.log(chalk6.gray(` Stack: ${detected.stack.join(", ")}`));
1001
+ if (detected.commands.build) console.log(chalk6.gray(` Build: ${detected.commands.build}`));
1002
+ if (detected.commands.test) console.log(chalk6.gray(` Test: ${detected.commands.test}`));
1003
+ console.log();
1004
+ }
1005
+ let config2;
1006
+ if (options.yes) {
1007
+ config2 = {
1008
+ name: options.name || detected?.name || "my-project",
1009
+ description: options.description || "",
1010
+ stack: options.stack?.split(",").map((s) => s.trim()) || detected?.stack || [],
1011
+ platforms: options.platforms?.split(",").map((s) => s.trim()) || ["cursor", "claude"],
1012
+ persona: options.persona || "fullstack",
1013
+ boundaries: options.boundaries || "standard",
1014
+ commands: detected?.commands || {}
1015
+ };
1016
+ } else {
1017
+ config2 = await runInteractiveWizard(options, detected);
1018
+ }
1019
+ const spinner = ora5("Generating configuration files...").start();
1020
+ try {
1021
+ const files = generateConfig(config2);
1022
+ spinner.stop();
1023
+ console.log();
1024
+ console.log(chalk6.green("\u2705 Generated files:"));
1025
+ for (const [filename, content] of Object.entries(files)) {
1026
+ const outputPath = join3(process.cwd(), filename);
1027
+ let exists = false;
1028
+ try {
1029
+ await access3(outputPath);
1030
+ exists = true;
1031
+ } catch {
1032
+ }
1033
+ if (exists && !options.yes) {
1034
+ const response = await prompts2({
1035
+ type: "confirm",
1036
+ name: "overwrite",
1037
+ message: `${filename} already exists. Overwrite?`,
1038
+ initial: false
1039
+ });
1040
+ if (!response.overwrite) {
1041
+ console.log(chalk6.yellow(` Skipped: ${filename}`));
1042
+ continue;
1043
+ }
1044
+ }
1045
+ const dir = dirname2(outputPath);
1046
+ if (dir !== ".") {
1047
+ await mkdir2(dir, { recursive: true });
1048
+ }
1049
+ await writeFile2(outputPath, content, "utf-8");
1050
+ console.log(` ${chalk6.cyan(filename)}`);
1051
+ }
1052
+ console.log();
1053
+ console.log(chalk6.gray("Your AI IDE configuration is ready!"));
1054
+ console.log(chalk6.gray("The AI assistant in your IDE will now follow these instructions."));
1055
+ console.log();
1056
+ } catch (error) {
1057
+ spinner.fail("Failed to generate files");
1058
+ console.error(chalk6.red("An error occurred while generating configuration files."));
1059
+ if (error instanceof Error) {
1060
+ console.error(chalk6.gray(error.message));
1061
+ }
1062
+ process.exit(1);
1063
+ }
1064
+ }
1065
+ async function runInteractiveWizard(options, detected) {
1066
+ const answers = {};
1067
+ const nameResponse = await prompts2({
1068
+ type: "text",
1069
+ name: "name",
1070
+ message: "What's your project name?",
1071
+ initial: options.name || detected?.name || "my-project"
1072
+ });
1073
+ answers.name = nameResponse.name;
1074
+ const descResponse = await prompts2({
1075
+ type: "text",
1076
+ name: "description",
1077
+ message: "Describe your project in one sentence:",
1078
+ initial: options.description || ""
1079
+ });
1080
+ answers.description = descResponse.description;
1081
+ const allStackOptions = [...TECH_STACKS, ...FRAMEWORKS];
1082
+ const stackResponse = await prompts2({
1083
+ type: "multiselect",
1084
+ name: "stack",
1085
+ message: "Select your tech stack:",
1086
+ choices: allStackOptions,
1087
+ hint: "- Space to select, Enter to confirm"
1088
+ });
1089
+ answers.stack = stackResponse.stack || [];
1090
+ const platformResponse = await prompts2({
1091
+ type: "multiselect",
1092
+ name: "platforms",
1093
+ message: "Which AI IDEs do you use?",
1094
+ choices: PLATFORMS,
1095
+ hint: "- Space to select, Enter to confirm",
1096
+ min: 1
1097
+ });
1098
+ answers.platforms = platformResponse.platforms || ["cursor"];
1099
+ const personaResponse = await prompts2({
1100
+ type: "select",
1101
+ name: "persona",
1102
+ message: "What's the AI's persona/role?",
1103
+ choices: PERSONAS,
1104
+ initial: 2
1105
+ // Full-stack by default
1106
+ });
1107
+ if (personaResponse.persona === "custom") {
1108
+ const customPersona = await prompts2({
1109
+ type: "text",
1110
+ name: "value",
1111
+ message: "Describe the custom persona:"
1112
+ });
1113
+ answers.persona = customPersona.value || "fullstack";
1114
+ } else {
1115
+ answers.persona = personaResponse.persona;
1116
+ }
1117
+ if (detected?.commands && Object.keys(detected.commands).length > 0) {
1118
+ console.log();
1119
+ console.log(chalk6.gray("Auto-detected commands:"));
1120
+ if (detected.commands.build) console.log(chalk6.gray(` Build: ${detected.commands.build}`));
1121
+ if (detected.commands.test) console.log(chalk6.gray(` Test: ${detected.commands.test}`));
1122
+ if (detected.commands.lint) console.log(chalk6.gray(` Lint: ${detected.commands.lint}`));
1123
+ if (detected.commands.dev) console.log(chalk6.gray(` Dev: ${detected.commands.dev}`));
1124
+ const editCommands = await prompts2({
1125
+ type: "confirm",
1126
+ name: "edit",
1127
+ message: "Edit these commands?",
1128
+ initial: false
1129
+ });
1130
+ if (editCommands.edit) {
1131
+ const commandsResponse = await prompts2([
1132
+ { type: "text", name: "build", message: "Build command:", initial: detected.commands.build },
1133
+ { type: "text", name: "test", message: "Test command:", initial: detected.commands.test },
1134
+ { type: "text", name: "lint", message: "Lint command:", initial: detected.commands.lint },
1135
+ { type: "text", name: "dev", message: "Dev command:", initial: detected.commands.dev }
1136
+ ]);
1137
+ answers.commands = commandsResponse;
1138
+ } else {
1139
+ answers.commands = detected.commands;
1140
+ }
1141
+ } else {
1142
+ answers.commands = {};
1143
+ }
1144
+ const boundaryResponse = await prompts2({
1145
+ type: "select",
1146
+ name: "boundaries",
1147
+ message: "Select boundary preset:",
1148
+ choices: BOUNDARY_PRESETS.map((b) => ({ title: b.title, value: b.value })),
1149
+ initial: 1
1150
+ // Standard by default
1151
+ });
1152
+ answers.boundaries = boundaryResponse.boundaries || "standard";
1153
+ return {
1154
+ name: answers.name,
1155
+ description: answers.description,
1156
+ stack: answers.stack,
1157
+ platforms: answers.platforms,
1158
+ persona: answers.persona,
1159
+ boundaries: answers.boundaries,
1160
+ commands: answers.commands
1161
+ };
1162
+ }
1163
+
1164
+ // src/commands/search.ts
1165
+ import chalk7 from "chalk";
1166
+ import ora6 from "ora";
1167
+ async function searchCommand(query, options) {
1168
+ const spinner = ora6(`Searching for "${query}"...`).start();
1169
+ try {
1170
+ const limit = parseInt(options.limit, 10) || 20;
1171
+ const { templates, total, hasMore } = await api.searchBlueprints(query, limit);
1172
+ spinner.stop();
1173
+ if (templates.length === 0) {
1174
+ console.log();
1175
+ console.log(chalk7.yellow(`No blueprints found for "${query}".`));
1176
+ console.log(chalk7.gray("Try different keywords or browse at https://lynxprompt.com/blueprints"));
1177
+ return;
1178
+ }
1179
+ console.log();
1180
+ console.log(chalk7.cyan(`\u{1F50D} Search Results for "${query}" (${total} found)`));
1181
+ console.log();
1182
+ for (const result of templates) {
1183
+ printSearchResult(result);
1184
+ }
1185
+ if (hasMore) {
1186
+ console.log(chalk7.gray(`Showing ${templates.length} of ${total}. Use --limit to see more.`));
1187
+ }
1188
+ console.log();
1189
+ console.log(chalk7.gray("Use 'lynxprompt pull <id>' to download a blueprint."));
1190
+ } catch (error) {
1191
+ spinner.fail("Search failed");
1192
+ handleApiError3(error);
1193
+ }
1194
+ }
1195
+ function printSearchResult(result) {
1196
+ const priceInfo = result.price ? chalk7.yellow(`\u20AC${(result.price / 100).toFixed(2)}`) : chalk7.green("Free");
1197
+ const officialBadge = result.isOfficial ? chalk7.magenta(" \u2605 Official") : "";
1198
+ console.log(` ${chalk7.bold(result.name)}${officialBadge}`);
1199
+ console.log(` ${chalk7.cyan(result.id)} \u2022 ${priceInfo}`);
1200
+ if (result.description) {
1201
+ console.log(` ${chalk7.gray(truncate2(result.description, 60))}`);
1202
+ }
1203
+ console.log(` ${chalk7.gray(`by ${result.author}`)} \u2022 ${chalk7.gray(`\u2193${result.downloads}`)} ${chalk7.gray(`\u2665${result.likes}`)}`);
1204
+ if (result.tags && result.tags.length > 0) {
1205
+ console.log(` ${formatTags2(result.tags)}`);
1206
+ }
1207
+ console.log();
1208
+ }
1209
+ function formatTags2(tags) {
1210
+ return tags.slice(0, 4).map((t) => chalk7.gray(`#${t}`)).join(" ");
1211
+ }
1212
+ function truncate2(str, maxLength) {
1213
+ if (str.length <= maxLength) return str;
1214
+ return str.slice(0, maxLength - 3) + "...";
1215
+ }
1216
+ function handleApiError3(error) {
1217
+ if (error instanceof ApiRequestError) {
1218
+ console.error(chalk7.red(`Error: ${error.message}`));
1219
+ } else {
1220
+ console.error(chalk7.red("An unexpected error occurred."));
1221
+ }
1222
+ process.exit(1);
1223
+ }
1224
+
1225
+ // src/commands/status.ts
1226
+ import chalk8 from "chalk";
1227
+ import { access as access4, readFile as readFile3 } from "fs/promises";
1228
+ import { join as join4 } from "path";
1229
+ var CONFIG_FILES = [
1230
+ { path: "AGENTS.md", name: "AGENTS.md", platform: "Claude Code, Cursor, AI Agents" },
1231
+ { path: "CLAUDE.md", name: "CLAUDE.md", platform: "Claude Code" },
1232
+ { path: ".cursorrules", name: ".cursorrules", platform: "Cursor" },
1233
+ { path: ".github/copilot-instructions.md", name: "Copilot Instructions", platform: "GitHub Copilot" },
1234
+ { path: ".windsurfrules", name: ".windsurfrules", platform: "Windsurf" },
1235
+ { path: ".zed/instructions.md", name: "Zed Instructions", platform: "Zed" }
1236
+ ];
1237
+ async function statusCommand() {
1238
+ const cwd = process.cwd();
1239
+ console.log();
1240
+ console.log(chalk8.cyan("\u{1F431} AI Config Status"));
1241
+ console.log(chalk8.gray(` Directory: ${cwd}`));
1242
+ console.log();
1243
+ let foundAny = false;
1244
+ for (const config2 of CONFIG_FILES) {
1245
+ const filePath = join4(cwd, config2.path);
1246
+ try {
1247
+ await access4(filePath);
1248
+ const content = await readFile3(filePath, "utf-8");
1249
+ const lines = content.split("\n").length;
1250
+ const size = formatBytes(content.length);
1251
+ foundAny = true;
1252
+ console.log(` ${chalk8.green("\u2713")} ${chalk8.bold(config2.name)}`);
1253
+ console.log(` ${chalk8.gray(`Platform: ${config2.platform}`)}`);
1254
+ console.log(` ${chalk8.gray(`Size: ${size} (${lines} lines)`)}`);
1255
+ const preview = getPreview(content);
1256
+ if (preview) {
1257
+ console.log(` ${chalk8.gray(`Preview: ${preview}`)}`);
1258
+ }
1259
+ console.log();
1260
+ } catch {
1261
+ }
1262
+ }
1263
+ if (!foundAny) {
1264
+ console.log(chalk8.yellow(" No AI configuration files found in this directory."));
1265
+ console.log();
1266
+ console.log(chalk8.gray(" Run 'lynxprompt init' to create a configuration."));
1267
+ console.log(chalk8.gray(" Or run 'lynxprompt pull <id>' to download an existing blueprint."));
1268
+ } else {
1269
+ console.log(chalk8.gray("Run 'lynxprompt init' to update or create additional configs."));
1270
+ }
1271
+ console.log();
1272
+ }
1273
+ function getPreview(content) {
1274
+ const lines = content.split("\n");
1275
+ for (const line of lines) {
1276
+ const trimmed = line.trim();
1277
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !trimmed.startsWith("<!--")) {
1278
+ return truncate3(trimmed, 50);
1279
+ }
1280
+ }
1281
+ return null;
1282
+ }
1283
+ function truncate3(str, maxLength) {
1284
+ if (str.length <= maxLength) return str;
1285
+ return str.slice(0, maxLength - 3) + "...";
1286
+ }
1287
+ function formatBytes(bytes) {
1288
+ if (bytes < 1024) return `${bytes} B`;
1289
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1290
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1291
+ }
1292
+
1293
+ // src/index.ts
1294
+ var program = new Command();
1295
+ program.name("lynxprompt").description("CLI for LynxPrompt - Generate AI IDE configuration files").version("0.1.0");
1296
+ program.command("login").description("Authenticate with LynxPrompt (opens browser)").action(loginCommand);
1297
+ program.command("logout").description("Log out and remove stored credentials").action(logoutCommand);
1298
+ program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
1299
+ program.command("init").description("Interactive wizard to generate AI IDE configuration").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("-s, --stack <stack>", "Tech stack (comma-separated)").option("-p, --platforms <platforms>", "Target platforms (comma-separated)").option("--persona <persona>", "AI persona/role").option("--boundaries <level>", "Boundary preset (conservative, standard, permissive)").option("--preset <preset>", "Use an agent preset (test-agent, docs-agent, etc.)").option("-y, --yes", "Skip prompts and use defaults").action(initCommand);
1300
+ program.command("list").description("List your blueprints").option("-l, --limit <number>", "Number of results", "20").option("-v, --visibility <visibility>", "Filter by visibility (PRIVATE, TEAM, PUBLIC, all)").action(listCommand);
1301
+ program.command("pull <id>").description("Download a blueprint to the current directory").option("-o, --output <path>", "Output directory", ".").option("-y, --yes", "Overwrite existing files without prompting").action(pullCommand);
1302
+ program.command("search <query>").description("Search public blueprints").option("-l, --limit <number>", "Number of results", "20").action(searchCommand);
1303
+ program.command("status").description("Show current AI config status in this directory").action(statusCommand);
1304
+ program.addHelpText(
1305
+ "beforeAll",
1306
+ `
1307
+ ${chalk9.cyan("\u{1F431} LynxPrompt CLI")}
1308
+ ${chalk9.gray("Generate AI IDE configuration files from your terminal")}
1309
+ `
1310
+ );
1311
+ program.addHelpText(
1312
+ "after",
1313
+ `
1314
+ ${chalk9.gray("Examples:")}
1315
+ ${chalk9.cyan("$ lynxprompt init")} ${chalk9.gray("Start interactive wizard")}
1316
+ ${chalk9.cyan("$ lynxprompt pull bp_abc123")} ${chalk9.gray("Download a blueprint")}
1317
+ ${chalk9.cyan("$ lynxprompt list")} ${chalk9.gray("List your blueprints")}
1318
+ ${chalk9.cyan("$ lynxprompt search nextjs")} ${chalk9.gray("Search public blueprints")}
1319
+
1320
+ ${chalk9.gray("Documentation: https://lynxprompt.com/docs/cli")}
1321
+ `
1322
+ );
1323
+ program.parse();
1324
+ //# sourceMappingURL=index.js.map