fasterdev 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/cli.js ADDED
@@ -0,0 +1,1915 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/auth/login.ts
7
+ import chalk2 from "chalk";
8
+
9
+ // src/api.ts
10
+ import fetch from "node-fetch";
11
+ var DEFAULT_API_URL = "https://faster.dev/api/v1";
12
+ var APIError = class extends Error {
13
+ status;
14
+ body;
15
+ constructor(status, message, body) {
16
+ super(message);
17
+ this.status = status;
18
+ this.body = body;
19
+ }
20
+ };
21
+ var FasterAPI = class {
22
+ apiUrl;
23
+ authToken;
24
+ fetcher;
25
+ constructor(config, fetcher = fetch) {
26
+ this.apiUrl = config.apiUrl || DEFAULT_API_URL;
27
+ this.authToken = config.authToken;
28
+ this.fetcher = fetcher;
29
+ }
30
+ async request(endpoint, options = {}) {
31
+ const headers = {
32
+ "Content-Type": "application/json",
33
+ ...options.headers
34
+ };
35
+ if (this.authToken) {
36
+ headers["Authorization"] = `Bearer ${this.authToken}`;
37
+ }
38
+ const response = await this.fetcher(`${this.apiUrl}${endpoint}`, {
39
+ ...options,
40
+ headers
41
+ });
42
+ if (!response.ok) {
43
+ const errorBody = await response.text();
44
+ throw new APIError(
45
+ response.status,
46
+ `API error (${response.status}): ${errorBody || response.statusText}`,
47
+ errorBody
48
+ );
49
+ }
50
+ if (response.status === 204) {
51
+ return {};
52
+ }
53
+ return response.json();
54
+ }
55
+ /**
56
+ * Authenticate with faster.dev
57
+ */
58
+ async login(email, password) {
59
+ return this.request("/auth/login", {
60
+ method: "POST",
61
+ body: JSON.stringify({ email, password })
62
+ });
63
+ }
64
+ /**
65
+ * Start device authentication flow
66
+ */
67
+ async requestDeviceAuth() {
68
+ return this.request("/auth/device", {
69
+ method: "POST",
70
+ body: JSON.stringify({})
71
+ });
72
+ }
73
+ /**
74
+ * Check device authentication status
75
+ */
76
+ async checkDeviceAuth(deviceCode) {
77
+ return this.request(`/auth/device/${encodeURIComponent(deviceCode)}`);
78
+ }
79
+ /**
80
+ * Get current user
81
+ */
82
+ async me() {
83
+ return this.request("/auth/me");
84
+ }
85
+ /**
86
+ * Search for packages
87
+ */
88
+ async search(query, options) {
89
+ const params = new URLSearchParams({ q: query });
90
+ if (options?.type) params.set("type", options.type);
91
+ if (options?.tool) params.set("tool", options.tool);
92
+ return this.request(`/packages/search?${params}`);
93
+ }
94
+ /**
95
+ * Get package info
96
+ */
97
+ async getPackageInfo(name) {
98
+ return this.request(`/packages/${encodeURIComponent(name)}`);
99
+ }
100
+ /**
101
+ * Get package version info
102
+ */
103
+ async getPackageVersion(name, version) {
104
+ return this.request(
105
+ `/packages/${encodeURIComponent(name)}/versions/${encodeURIComponent(version)}`
106
+ );
107
+ }
108
+ /**
109
+ * Download a package
110
+ */
111
+ async downloadPackage(name, version) {
112
+ const endpoint = version ? `/packages/${encodeURIComponent(name)}/versions/${encodeURIComponent(version)}/download` : `/packages/${encodeURIComponent(name)}/download`;
113
+ return this.request(endpoint);
114
+ }
115
+ /**
116
+ * Publish a package (for package authors)
117
+ */
118
+ async publishPackage(pkg) {
119
+ if (!this.authToken) {
120
+ throw new Error("Authentication required to publish packages");
121
+ }
122
+ return this.request("/packages", {
123
+ method: "POST",
124
+ body: JSON.stringify(pkg)
125
+ });
126
+ }
127
+ /**
128
+ * Set auth token
129
+ */
130
+ setAuthToken(token) {
131
+ this.authToken = token;
132
+ }
133
+ };
134
+
135
+ // src/config.ts
136
+ import Conf from "conf";
137
+ var DEFAULT_CONFIG = {
138
+ apiUrl: "https://faster.dev/api/v1",
139
+ analytics: true
140
+ };
141
+ var store = new Conf({
142
+ projectName: "faster",
143
+ defaults: DEFAULT_CONFIG
144
+ });
145
+ function getConfig() {
146
+ const apiUrl = process.env.FASTER_API_URL || store.get("apiUrl", DEFAULT_CONFIG.apiUrl);
147
+ const authToken = process.env.FASTER_API_KEY || store.get("authToken");
148
+ const analyticsEnv = process.env.FASTER_NO_ANALYTICS;
149
+ return {
150
+ apiUrl,
151
+ authToken,
152
+ defaultTools: store.get("defaultTools"),
153
+ analytics: analyticsEnv ? false : store.get("analytics", DEFAULT_CONFIG.analytics)
154
+ };
155
+ }
156
+ function getAuthToken() {
157
+ return process.env.FASTER_API_KEY || store.get("authToken");
158
+ }
159
+ function setAuthToken(token) {
160
+ store.set("authToken", token);
161
+ }
162
+ function clearAuthToken() {
163
+ store.delete("authToken");
164
+ }
165
+ function setApiUrl(url) {
166
+ store.set("apiUrl", url);
167
+ }
168
+ function getDefaultTools() {
169
+ return store.get("defaultTools");
170
+ }
171
+ function setDefaultTools(tools) {
172
+ store.set("defaultTools", tools);
173
+ }
174
+ function getConfigPath() {
175
+ return store.path;
176
+ }
177
+
178
+ // src/utils.ts
179
+ import { spawn } from "child_process";
180
+ function parsePackageSpec(input) {
181
+ if (input.startsWith("@")) {
182
+ const match2 = input.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
183
+ if (match2) {
184
+ return { name: match2[1], version: match2[2] };
185
+ }
186
+ return { name: input };
187
+ }
188
+ const match = input.match(/^([^@]+)(?:@(.+))?$/);
189
+ if (match) {
190
+ return { name: match[1], version: match[2] };
191
+ }
192
+ return { name: input };
193
+ }
194
+ function resolveInstallType(asSkill) {
195
+ return asSkill ? "skill" : "rule";
196
+ }
197
+ function stringifyError(error, verbose) {
198
+ if (error instanceof Error) {
199
+ if (verbose && error.stack) return error.stack;
200
+ return error.message;
201
+ }
202
+ return String(error);
203
+ }
204
+ async function sleep(ms) {
205
+ await new Promise((resolve) => setTimeout(resolve, ms));
206
+ }
207
+ function openBrowser(url) {
208
+ try {
209
+ const platform = process.platform;
210
+ if (platform === "darwin") {
211
+ spawn("open", [url], { stdio: "ignore", detached: true }).unref();
212
+ return true;
213
+ }
214
+ if (platform === "win32") {
215
+ spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
216
+ return true;
217
+ }
218
+ spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
219
+ return true;
220
+ } catch {
221
+ return false;
222
+ }
223
+ }
224
+
225
+ // src/lib/command-utils.ts
226
+ import chalk from "chalk";
227
+ import ora from "ora";
228
+
229
+ // src/lib/exit-codes.ts
230
+ var EXIT_CODES = {
231
+ SUCCESS: 0,
232
+ ERROR: 1,
233
+ INVALID_ARGS: 2,
234
+ AUTH_REQUIRED: 3,
235
+ NOT_FOUND: 4,
236
+ NETWORK: 5,
237
+ PERMISSION: 6
238
+ };
239
+ function mapApiErrorToExitCode(error) {
240
+ if (error instanceof APIError) {
241
+ if (error.status === 401) return EXIT_CODES.AUTH_REQUIRED;
242
+ if (error.status === 403) return EXIT_CODES.PERMISSION;
243
+ if (error.status === 404) return EXIT_CODES.NOT_FOUND;
244
+ if (error.status >= 500) return EXIT_CODES.NETWORK;
245
+ }
246
+ return EXIT_CODES.ERROR;
247
+ }
248
+
249
+ // src/lib/command-utils.ts
250
+ var SpinnerManager = class {
251
+ spinner;
252
+ isJson;
253
+ constructor(message, isJson) {
254
+ this.isJson = isJson;
255
+ this.spinner = isJson ? null : ora(message).start();
256
+ }
257
+ text(message) {
258
+ if (this.spinner) {
259
+ this.spinner.text = message;
260
+ }
261
+ }
262
+ stop() {
263
+ if (this.spinner) {
264
+ this.spinner.stop();
265
+ }
266
+ }
267
+ succeed(message) {
268
+ if (this.spinner) {
269
+ this.spinner.succeed(chalk.green(message));
270
+ }
271
+ }
272
+ fail(message) {
273
+ if (this.spinner) {
274
+ this.spinner.fail(chalk.red(message));
275
+ }
276
+ }
277
+ info(message) {
278
+ if (this.spinner) {
279
+ this.spinner.info(message);
280
+ }
281
+ }
282
+ };
283
+ function outputJson(data) {
284
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
285
+ `);
286
+ }
287
+ function setExitCode(code) {
288
+ process.exitCode = code;
289
+ }
290
+
291
+ // src/commands/auth/login.ts
292
+ function registerLoginCommand(program2) {
293
+ program2.command("login").description("Authenticate with faster.dev").option("--no-browser", "Do not open a browser automatically").action(async (opts) => {
294
+ const globalOpts = program2.opts();
295
+ const { json, verbose } = globalOpts;
296
+ const spinner = new SpinnerManager("Starting device login...", json ?? false);
297
+ try {
298
+ const api = new FasterAPI(getConfig());
299
+ const device = await api.requestDeviceAuth();
300
+ spinner.stop();
301
+ if (!json) {
302
+ console.log();
303
+ console.log(chalk2.bold("Open the following URL in your browser:"));
304
+ console.log(chalk2.cyan(device.verification_url));
305
+ console.log();
306
+ console.log(chalk2.bold("Enter this code:"));
307
+ console.log(chalk2.green(device.user_code));
308
+ console.log();
309
+ }
310
+ if (!opts.browser) {
311
+ if (!json) console.log(chalk2.dim("Browser auto-open disabled."));
312
+ } else {
313
+ const opened = openBrowser(device.verification_url);
314
+ if (!opened && !json) {
315
+ console.log(chalk2.dim("Unable to open browser automatically."));
316
+ }
317
+ }
318
+ const pollIntervalMs = Math.max(1, device.interval) * 1e3;
319
+ const deadline = Date.now() + device.expires_in * 1e3;
320
+ const waitSpinner = new SpinnerManager("Waiting for approval...", json ?? false);
321
+ while (Date.now() < deadline) {
322
+ await sleep(pollIntervalMs);
323
+ try {
324
+ const status = await api.checkDeviceAuth(device.device_code);
325
+ if (status.status === "approved") {
326
+ setAuthToken(status.token);
327
+ waitSpinner.succeed("Logged in successfully");
328
+ if (json) outputJson({ ok: true, user: status.user });
329
+ return;
330
+ }
331
+ } catch (error) {
332
+ waitSpinner.fail(`Login failed: ${stringifyError(error, verbose)}`);
333
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
334
+ setExitCode(mapApiErrorToExitCode(error));
335
+ return;
336
+ }
337
+ }
338
+ waitSpinner.fail("Login timed out");
339
+ if (json) outputJson({ ok: false, error: "Login timed out" });
340
+ setExitCode(EXIT_CODES.ERROR);
341
+ } catch (error) {
342
+ spinner.fail(`Login failed: ${stringifyError(error, verbose)}`);
343
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
344
+ setExitCode(mapApiErrorToExitCode(error));
345
+ }
346
+ });
347
+ }
348
+
349
+ // src/commands/auth/logout.ts
350
+ import chalk3 from "chalk";
351
+ function registerLogoutCommand(program2) {
352
+ program2.command("logout").description("Log out from faster.dev").action(() => {
353
+ const { json } = program2.opts();
354
+ clearAuthToken();
355
+ if (json) {
356
+ outputJson({ ok: true });
357
+ } else {
358
+ console.log(chalk3.green("Logged out successfully"));
359
+ }
360
+ });
361
+ }
362
+
363
+ // src/commands/auth/whoami.ts
364
+ import chalk4 from "chalk";
365
+ function registerWhoamiCommand(program2) {
366
+ program2.command("whoami").description("Show current authentication status").action(async () => {
367
+ const { json, verbose } = program2.opts();
368
+ const token = getAuthToken();
369
+ if (!token) {
370
+ if (json) {
371
+ outputJson({ authenticated: false });
372
+ } else {
373
+ console.log(chalk4.yellow("Not logged in"));
374
+ console.log(chalk4.dim("Run `faster login` to authenticate"));
375
+ }
376
+ setExitCode(EXIT_CODES.AUTH_REQUIRED);
377
+ return;
378
+ }
379
+ try {
380
+ const api = new FasterAPI(getConfig());
381
+ const user = await api.me();
382
+ if (json) {
383
+ outputJson({ authenticated: true, user });
384
+ } else {
385
+ console.log(chalk4.green("Authenticated with faster.dev"));
386
+ console.log(chalk4.dim(`Email: ${user.email}`));
387
+ console.log(chalk4.dim(`Config: ${getConfigPath()}`));
388
+ }
389
+ } catch (error) {
390
+ if (json) {
391
+ outputJson({ authenticated: false, error: stringifyError(error, verbose) });
392
+ } else {
393
+ console.log(chalk4.red(`Auth check failed: ${stringifyError(error, verbose)}`));
394
+ }
395
+ setExitCode(mapApiErrorToExitCode(error));
396
+ }
397
+ });
398
+ }
399
+
400
+ // src/commands/auth/index.ts
401
+ function registerAuthCommands(program2) {
402
+ registerLoginCommand(program2);
403
+ registerLogoutCommand(program2);
404
+ registerWhoamiCommand(program2);
405
+ }
406
+
407
+ // src/commands/discovery/search.ts
408
+ import chalk5 from "chalk";
409
+ function registerSearchCommand(program2) {
410
+ program2.command("search <query>").description("Search for packages on faster.dev").option("--type <type>", "Filter by type: rule, skill, or both").action(async (query, opts) => {
411
+ const { json, verbose } = program2.opts();
412
+ const spinner = new SpinnerManager("Searching...", json ?? false);
413
+ try {
414
+ const api = new FasterAPI(getConfig());
415
+ const results = await api.search(query, { type: opts.type });
416
+ spinner.stop();
417
+ if (results.length === 0) {
418
+ if (json) {
419
+ outputJson({ ok: true, results: [] });
420
+ } else {
421
+ console.log(chalk5.yellow("No packages found"));
422
+ }
423
+ return;
424
+ }
425
+ if (json) {
426
+ outputJson({ ok: true, results });
427
+ return;
428
+ }
429
+ console.log();
430
+ for (const pkg of results) {
431
+ const typeColor = pkg.type === "skill" ? chalk5.blue : chalk5.green;
432
+ console.log(
433
+ chalk5.bold(pkg.name),
434
+ typeColor(`[${pkg.type}]`),
435
+ chalk5.dim(`v${pkg.version}`)
436
+ );
437
+ console.log(chalk5.dim(` ${pkg.description}`));
438
+ console.log();
439
+ }
440
+ } catch (error) {
441
+ spinner.fail(`Search failed: ${stringifyError(error, verbose)}`);
442
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
443
+ setExitCode(mapApiErrorToExitCode(error));
444
+ }
445
+ });
446
+ }
447
+
448
+ // src/commands/discovery/info.ts
449
+ import chalk6 from "chalk";
450
+ function registerInfoCommand(program2) {
451
+ program2.command("info <package>").description("Show package details").action(async (packageInput) => {
452
+ const { json, verbose } = program2.opts();
453
+ const { name: packageName } = parsePackageSpec(packageInput);
454
+ const spinner = new SpinnerManager("Fetching package info...", json ?? false);
455
+ try {
456
+ const api = new FasterAPI(getConfig());
457
+ const info = await api.getPackageInfo(packageName);
458
+ spinner.stop();
459
+ if (json) {
460
+ outputJson({ ok: true, info });
461
+ return;
462
+ }
463
+ console.log();
464
+ console.log(chalk6.bold(info.name));
465
+ console.log(chalk6.dim(info.description));
466
+ console.log();
467
+ console.log(`Type: ${info.type}`);
468
+ console.log(`Latest: ${info.latestVersion}`);
469
+ if (info.versions?.length) {
470
+ console.log(`Versions: ${info.versions.join(", ")}`);
471
+ }
472
+ if (info.repository) console.log(`Repository: ${info.repository}`);
473
+ if (info.homepage) console.log(`Homepage: ${info.homepage}`);
474
+ if (info.keywords?.length) console.log(`Keywords: ${info.keywords.join(", ")}`);
475
+ if (info.downloads !== void 0) console.log(`Downloads: ${info.downloads}`);
476
+ } catch (error) {
477
+ spinner.fail(`Info failed: ${stringifyError(error, verbose)}`);
478
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
479
+ setExitCode(mapApiErrorToExitCode(error));
480
+ }
481
+ });
482
+ }
483
+
484
+ // src/commands/discovery/detect.ts
485
+ import chalk7 from "chalk";
486
+
487
+ // src/detector.ts
488
+ import fs from "fs/promises";
489
+ import path2 from "path";
490
+
491
+ // src/tools.ts
492
+ import os from "os";
493
+ import path from "path";
494
+ var home = os.homedir();
495
+ var TOOL_CONFIGS = {
496
+ "claude-code": {
497
+ id: "claude-code",
498
+ name: "Claude Code",
499
+ detect: {
500
+ projectDirs: [".claude"],
501
+ globalDirs: [path.join(home, ".claude")],
502
+ configFiles: ["CLAUDE.md", ".claude/CLAUDE.md"]
503
+ },
504
+ rules: {
505
+ projectPath: ".claude/rules",
506
+ globalPath: path.join(home, ".claude", "rules"),
507
+ format: "markdown",
508
+ fileExtension: ".md"
509
+ },
510
+ skills: {
511
+ projectPath: ".claude/skills",
512
+ globalPath: path.join(home, ".claude", "skills")
513
+ }
514
+ },
515
+ codex: {
516
+ id: "codex",
517
+ name: "OpenAI Codex CLI",
518
+ detect: {
519
+ projectDirs: [".codex"],
520
+ globalDirs: [path.join(home, ".codex")],
521
+ configFiles: ["AGENTS.md", ".codex/config.toml"]
522
+ },
523
+ rules: {
524
+ projectPath: ".codex/rules",
525
+ globalPath: path.join(home, ".codex", "rules"),
526
+ format: "markdown",
527
+ fileExtension: ".md"
528
+ },
529
+ skills: {
530
+ projectPath: ".codex/skills",
531
+ globalPath: path.join(home, ".codex", "skills")
532
+ }
533
+ },
534
+ cursor: {
535
+ id: "cursor",
536
+ name: "Cursor",
537
+ detect: {
538
+ projectDirs: [".cursor"],
539
+ globalDirs: [path.join(home, ".cursor")],
540
+ configFiles: [".cursorrules", ".cursor/rules"]
541
+ },
542
+ rules: {
543
+ projectPath: ".cursor/rules",
544
+ globalPath: path.join(home, ".cursor", "rules"),
545
+ format: "mdc",
546
+ fileExtension: ".mdc"
547
+ },
548
+ skills: {
549
+ projectPath: ".cursor/skills",
550
+ globalPath: path.join(home, ".cursor", "skills")
551
+ }
552
+ },
553
+ cline: {
554
+ id: "cline",
555
+ name: "Cline",
556
+ detect: {
557
+ projectDirs: [".clinerules"],
558
+ globalDirs: [path.join(home, "Documents", "Cline", "Rules")],
559
+ configFiles: [".clinerules", "AGENTS.md"]
560
+ },
561
+ rules: {
562
+ projectPath: ".clinerules",
563
+ globalPath: path.join(home, "Documents", "Cline", "Rules"),
564
+ format: "markdown",
565
+ fileExtension: ".md"
566
+ },
567
+ skills: null
568
+ },
569
+ "roo-code": {
570
+ id: "roo-code",
571
+ name: "Roo Code",
572
+ detect: {
573
+ projectDirs: [".roo"],
574
+ globalDirs: [path.join(home, ".roo")],
575
+ configFiles: [".roorules", ".roomodes", "AGENTS.md"]
576
+ },
577
+ rules: {
578
+ projectPath: ".roo/rules",
579
+ globalPath: path.join(home, ".roo", "rules"),
580
+ format: "markdown",
581
+ fileExtension: ".md"
582
+ },
583
+ skills: null
584
+ },
585
+ continue: {
586
+ id: "continue",
587
+ name: "Continue.dev",
588
+ detect: {
589
+ projectDirs: [".continue"],
590
+ globalDirs: [path.join(home, ".continue")],
591
+ configFiles: [".continue/config.yaml", ".continue/config.json"]
592
+ },
593
+ rules: {
594
+ projectPath: ".continue/rules",
595
+ globalPath: path.join(home, ".continue", "rules"),
596
+ format: "markdown",
597
+ fileExtension: ".md"
598
+ },
599
+ skills: null
600
+ },
601
+ aider: {
602
+ id: "aider",
603
+ name: "Aider",
604
+ detect: {
605
+ projectDirs: [],
606
+ globalDirs: [],
607
+ configFiles: [".aider.conf.yml", "CONVENTIONS.md"]
608
+ },
609
+ rules: {
610
+ projectPath: ".aider",
611
+ globalPath: path.join(home, ".aider"),
612
+ format: "aider-config",
613
+ fileExtension: ".md"
614
+ },
615
+ skills: null
616
+ },
617
+ gemini: {
618
+ id: "gemini",
619
+ name: "Gemini CLI",
620
+ detect: {
621
+ projectDirs: [".gemini"],
622
+ globalDirs: [path.join(home, ".gemini")],
623
+ configFiles: ["GEMINI.md", ".gemini/settings.json"]
624
+ },
625
+ rules: {
626
+ projectPath: ".gemini/rules",
627
+ globalPath: path.join(home, ".gemini", "rules"),
628
+ format: "markdown",
629
+ fileExtension: ".md"
630
+ },
631
+ skills: null
632
+ },
633
+ amp: {
634
+ id: "amp",
635
+ name: "Amp (Sourcegraph)",
636
+ detect: {
637
+ projectDirs: [".amp", ".claude"],
638
+ globalDirs: [path.join(home, ".config", "amp"), path.join(home, ".claude")],
639
+ configFiles: ["AGENTS.md", "AGENT.md"]
640
+ },
641
+ rules: {
642
+ projectPath: ".amp/rules",
643
+ globalPath: path.join(home, ".config", "amp", "rules"),
644
+ format: "markdown",
645
+ fileExtension: ".md"
646
+ },
647
+ skills: {
648
+ projectPath: ".amp/skills",
649
+ globalPath: path.join(home, ".claude", "skills")
650
+ }
651
+ },
652
+ opencode: {
653
+ id: "opencode",
654
+ name: "OpenCode",
655
+ detect: {
656
+ projectDirs: [".opencode"],
657
+ globalDirs: [path.join(home, ".config", "opencode")],
658
+ configFiles: ["opencode.json", "AGENTS.md"]
659
+ },
660
+ rules: {
661
+ projectPath: ".opencode/rules",
662
+ globalPath: path.join(home, ".config", "opencode", "rules"),
663
+ format: "markdown",
664
+ fileExtension: ".md"
665
+ },
666
+ skills: {
667
+ projectPath: ".opencode/skill",
668
+ globalPath: path.join(home, ".config", "opencode", "skill")
669
+ }
670
+ }
671
+ };
672
+ var RULE_TOOLS = Object.keys(TOOL_CONFIGS);
673
+ var DEFAULT_TOOL_PRIORITY = [
674
+ "claude-code",
675
+ "cursor",
676
+ "codex",
677
+ "cline",
678
+ "roo-code",
679
+ "continue",
680
+ "aider",
681
+ "gemini",
682
+ "amp",
683
+ "opencode"
684
+ ];
685
+
686
+ // src/detector.ts
687
+ async function exists(p) {
688
+ try {
689
+ await fs.access(p);
690
+ return true;
691
+ } catch {
692
+ return false;
693
+ }
694
+ }
695
+ async function detectTools(projectRoot) {
696
+ const detected = [];
697
+ for (const toolId of DEFAULT_TOOL_PRIORITY) {
698
+ const config = TOOL_CONFIGS[toolId];
699
+ let projectPath = null;
700
+ let globalPath = null;
701
+ for (const dir of config.detect.projectDirs) {
702
+ const fullPath = path2.join(projectRoot, dir);
703
+ if (await exists(fullPath)) {
704
+ projectPath = fullPath;
705
+ break;
706
+ }
707
+ }
708
+ if (!projectPath) {
709
+ for (const file of config.detect.configFiles) {
710
+ const fullPath = path2.join(projectRoot, file);
711
+ if (await exists(fullPath)) {
712
+ projectPath = path2.dirname(fullPath);
713
+ if (projectPath === projectRoot) {
714
+ projectPath = path2.join(projectRoot, config.rules.projectPath);
715
+ }
716
+ break;
717
+ }
718
+ }
719
+ }
720
+ for (const dir of config.detect.globalDirs) {
721
+ if (await exists(dir)) {
722
+ globalPath = dir;
723
+ break;
724
+ }
725
+ }
726
+ if (projectPath || globalPath) {
727
+ detected.push({ config, projectPath, globalPath });
728
+ }
729
+ }
730
+ return detected;
731
+ }
732
+ function filterTools(detected, toolIds) {
733
+ const idSet = new Set(toolIds);
734
+ return detected.filter((t) => idSet.has(t.config.id));
735
+ }
736
+ function getSkillTools(detected) {
737
+ return detected.filter((t) => t.config.skills !== null);
738
+ }
739
+ function formatDetectedTools(detected) {
740
+ if (detected.length === 0) {
741
+ return "No AI coding tools detected in this project.";
742
+ }
743
+ const lines = ["Detected tools:"];
744
+ for (const tool of detected) {
745
+ const locations = [];
746
+ if (tool.projectPath) locations.push("project");
747
+ if (tool.globalPath) locations.push("global");
748
+ lines.push(` \u2022 ${tool.config.name} (${locations.join(", ")})`);
749
+ }
750
+ return lines.join("\n");
751
+ }
752
+
753
+ // src/commands/discovery/detect.ts
754
+ function registerDetectCommand(program2) {
755
+ program2.command("detect").description("Show detected AI coding tools").action(async () => {
756
+ const { json, verbose } = program2.opts();
757
+ const projectRoot = process.cwd();
758
+ const spinner = new SpinnerManager("Detecting tools...", json ?? false);
759
+ try {
760
+ const detectedTools = await detectTools(projectRoot);
761
+ spinner.stop();
762
+ if (json) {
763
+ outputJson({ ok: true, detected: detectedTools.map((t) => t.config.id) });
764
+ return;
765
+ }
766
+ console.log();
767
+ console.log(formatDetectedTools(detectedTools));
768
+ console.log();
769
+ if (detectedTools.length > 0) {
770
+ const skillTools = getSkillTools(detectedTools);
771
+ if (skillTools.length > 0) {
772
+ console.log(chalk7.dim("Tools supporting skills:"));
773
+ for (const tool of skillTools) {
774
+ console.log(chalk7.dim(` - ${tool.config.name}`));
775
+ }
776
+ }
777
+ }
778
+ } catch (error) {
779
+ spinner.fail(`Detection failed: ${stringifyError(error, verbose)}`);
780
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
781
+ setExitCode(mapApiErrorToExitCode(error));
782
+ }
783
+ });
784
+ }
785
+
786
+ // src/commands/discovery/index.ts
787
+ function registerDiscoveryCommands(program2) {
788
+ registerSearchCommand(program2);
789
+ registerInfoCommand(program2);
790
+ registerDetectCommand(program2);
791
+ }
792
+
793
+ // src/commands/package/install.ts
794
+ import chalk8 from "chalk";
795
+
796
+ // src/installer.ts
797
+ import fs2 from "fs/promises";
798
+ import path3 from "path";
799
+
800
+ // src/converter.ts
801
+ import YAML from "yaml";
802
+ function parseFrontmatter(content) {
803
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
804
+ if (!match) {
805
+ return { frontmatter: {}, body: content };
806
+ }
807
+ try {
808
+ const frontmatter = YAML.parse(match[1]) || {};
809
+ return { frontmatter, body: match[2].trim() };
810
+ } catch {
811
+ return { frontmatter: {}, body: content };
812
+ }
813
+ }
814
+ function serializeFrontmatter(frontmatter, body) {
815
+ if (Object.keys(frontmatter).length === 0) {
816
+ return body;
817
+ }
818
+ const yaml = YAML.stringify(frontmatter).trim();
819
+ return `---
820
+ ${yaml}
821
+ ---
822
+
823
+ ${body}`;
824
+ }
825
+ function toMDCFormat(content) {
826
+ const { frontmatter, body } = parseFrontmatter(content);
827
+ const mdcFrontmatter = {};
828
+ if (frontmatter.name) {
829
+ mdcFrontmatter.description = frontmatter.name;
830
+ }
831
+ if (frontmatter.description) {
832
+ mdcFrontmatter.description = frontmatter.description;
833
+ }
834
+ if (frontmatter.globs) {
835
+ mdcFrontmatter.globs = frontmatter.globs;
836
+ }
837
+ if (frontmatter.paths && !frontmatter.globs) {
838
+ const paths = Array.isArray(frontmatter.paths) ? frontmatter.paths : [frontmatter.paths];
839
+ mdcFrontmatter.globs = paths.join(",");
840
+ }
841
+ mdcFrontmatter.alwaysApply = frontmatter.alwaysApply ?? false;
842
+ return serializeFrontmatter(mdcFrontmatter, body);
843
+ }
844
+ function toClaudeCodeFormat(content) {
845
+ const { frontmatter, body } = parseFrontmatter(content);
846
+ const ccFrontmatter = {};
847
+ if (frontmatter.globs) {
848
+ const globs = typeof frontmatter.globs === "string" ? frontmatter.globs.split(",").map((g) => g.trim()) : frontmatter.globs;
849
+ ccFrontmatter.paths = globs;
850
+ }
851
+ if (frontmatter.paths) {
852
+ ccFrontmatter.paths = frontmatter.paths;
853
+ }
854
+ if (Object.keys(ccFrontmatter).length === 0) {
855
+ return body;
856
+ }
857
+ return serializeFrontmatter(ccFrontmatter, body);
858
+ }
859
+ function toContinueFormat(content) {
860
+ const { frontmatter, body } = parseFrontmatter(content);
861
+ const continueFrontmatter = {};
862
+ if (frontmatter.name) {
863
+ continueFrontmatter.name = frontmatter.name;
864
+ }
865
+ if (frontmatter.description) {
866
+ continueFrontmatter.description = frontmatter.description;
867
+ }
868
+ if (frontmatter.paths && !frontmatter.globs) {
869
+ const paths = Array.isArray(frontmatter.paths) ? frontmatter.paths : [frontmatter.paths];
870
+ continueFrontmatter.globs = paths.length === 1 ? paths[0] : paths;
871
+ } else if (frontmatter.globs) {
872
+ continueFrontmatter.globs = frontmatter.globs;
873
+ }
874
+ return serializeFrontmatter(continueFrontmatter, body);
875
+ }
876
+ function toPlainMarkdown(content) {
877
+ const { body } = parseFrontmatter(content);
878
+ return body;
879
+ }
880
+ function convertToToolFormat(content, toolConfig, packageName) {
881
+ switch (toolConfig.rules.format) {
882
+ case "mdc":
883
+ return toMDCFormat(content);
884
+ case "markdown":
885
+ if (toolConfig.id === "claude-code") {
886
+ return toClaudeCodeFormat(content);
887
+ }
888
+ if (toolConfig.id === "continue") {
889
+ return toContinueFormat(content);
890
+ }
891
+ return toPlainMarkdown(content);
892
+ case "aider-config":
893
+ return toPlainMarkdown(content);
894
+ default:
895
+ return content;
896
+ }
897
+ }
898
+
899
+ // src/installer.ts
900
+ import YAML2 from "yaml";
901
+ async function ensureDir(dir) {
902
+ await fs2.mkdir(dir, { recursive: true });
903
+ }
904
+ async function fileExists(p) {
905
+ try {
906
+ await fs2.access(p);
907
+ return true;
908
+ } catch {
909
+ return false;
910
+ }
911
+ }
912
+ async function readFile(p) {
913
+ try {
914
+ return await fs2.readFile(p, "utf-8");
915
+ } catch {
916
+ return null;
917
+ }
918
+ }
919
+ async function installPackage(pkg, detectedTools, projectRoot, options) {
920
+ const results = [];
921
+ const ruleFile = pkg.files.find(
922
+ (f) => f.path === "rule.md" || f.path.endsWith("/rule.md")
923
+ );
924
+ const skillFile = pkg.files.find(
925
+ (f) => f.path === "SKILL.md" || f.path.endsWith("/SKILL.md")
926
+ );
927
+ const installAsSkill = options.asSkill && skillFile;
928
+ const mainContent = installAsSkill ? skillFile?.content : ruleFile?.content;
929
+ if (!mainContent) {
930
+ throw new Error(`Package ${pkg.manifest.name} has no installable content`);
931
+ }
932
+ for (const tool of detectedTools) {
933
+ const toolId = tool.config.id;
934
+ const manifest = pkg.manifest;
935
+ if (installAsSkill) {
936
+ if (!manifest.compatibility.skills?.includes(toolId)) {
937
+ results.push({
938
+ tool: toolId,
939
+ toolName: tool.config.name,
940
+ type: "skill",
941
+ path: "",
942
+ success: false,
943
+ skipped: true,
944
+ skipReason: "Skills not supported by this tool"
945
+ });
946
+ continue;
947
+ }
948
+ if (!tool.config.skills) {
949
+ results.push({
950
+ tool: toolId,
951
+ toolName: tool.config.name,
952
+ type: "skill",
953
+ path: "",
954
+ success: false,
955
+ skipped: true,
956
+ skipReason: "Tool does not support skills"
957
+ });
958
+ continue;
959
+ }
960
+ } else {
961
+ if (!manifest.compatibility.rules?.includes(toolId)) {
962
+ results.push({
963
+ tool: toolId,
964
+ toolName: tool.config.name,
965
+ type: "rule",
966
+ path: "",
967
+ success: false,
968
+ skipped: true,
969
+ skipReason: "Tool not in compatibility list"
970
+ });
971
+ continue;
972
+ }
973
+ }
974
+ const override = manifest.install?.[toolId];
975
+ if (override?.disabled) {
976
+ results.push({
977
+ tool: toolId,
978
+ toolName: tool.config.name,
979
+ type: installAsSkill ? "skill" : "rule",
980
+ path: "",
981
+ success: false,
982
+ skipped: true,
983
+ skipReason: "Disabled for this tool"
984
+ });
985
+ continue;
986
+ }
987
+ let content = mainContent;
988
+ if (override?.file) {
989
+ const overrideFile = pkg.files.find((f) => f.path === override.file);
990
+ if (overrideFile) {
991
+ content = overrideFile.content;
992
+ }
993
+ }
994
+ try {
995
+ let result;
996
+ if (installAsSkill) {
997
+ result = await installSkill(
998
+ pkg,
999
+ tool,
1000
+ content,
1001
+ projectRoot,
1002
+ options
1003
+ );
1004
+ } else {
1005
+ result = await installRule(
1006
+ pkg,
1007
+ tool,
1008
+ content,
1009
+ projectRoot,
1010
+ options
1011
+ );
1012
+ }
1013
+ results.push(result);
1014
+ } catch (error) {
1015
+ results.push({
1016
+ tool: toolId,
1017
+ toolName: tool.config.name,
1018
+ type: installAsSkill ? "skill" : "rule",
1019
+ path: "",
1020
+ success: false,
1021
+ error: error instanceof Error ? error.message : String(error)
1022
+ });
1023
+ }
1024
+ }
1025
+ return results;
1026
+ }
1027
+ async function installRule(pkg, tool, content, projectRoot, options) {
1028
+ const toolId = tool.config.id;
1029
+ const rulesConfig = tool.config.rules;
1030
+ const basePath = options.global ? rulesConfig.globalPath : path3.join(projectRoot, rulesConfig.projectPath);
1031
+ const override = pkg.manifest.install?.[toolId];
1032
+ if (override?.action) {
1033
+ return handleSpecialAction(pkg, tool, content, projectRoot, options, override.action);
1034
+ }
1035
+ const convertedContent = convertToToolFormat(content, tool.config, pkg.manifest.name);
1036
+ const filename = `${pkg.manifest.name}${rulesConfig.fileExtension}`;
1037
+ const targetPath = path3.join(basePath, filename);
1038
+ if (!options.force && await fileExists(targetPath)) {
1039
+ return {
1040
+ tool: toolId,
1041
+ toolName: tool.config.name,
1042
+ type: "rule",
1043
+ path: targetPath,
1044
+ success: false,
1045
+ skipped: true,
1046
+ skipReason: "File already exists (use --force to overwrite)"
1047
+ };
1048
+ }
1049
+ if (options.dryRun) {
1050
+ return {
1051
+ tool: toolId,
1052
+ toolName: tool.config.name,
1053
+ type: "rule",
1054
+ path: targetPath,
1055
+ success: true,
1056
+ skipped: true,
1057
+ skipReason: "Dry run"
1058
+ };
1059
+ }
1060
+ await ensureDir(basePath);
1061
+ await fs2.writeFile(targetPath, convertedContent, "utf-8");
1062
+ return {
1063
+ tool: toolId,
1064
+ toolName: tool.config.name,
1065
+ type: "rule",
1066
+ path: targetPath,
1067
+ success: true
1068
+ };
1069
+ }
1070
+ async function installSkill(pkg, tool, content, projectRoot, options) {
1071
+ const toolId = tool.config.id;
1072
+ const skillsConfig = tool.config.skills;
1073
+ if (!skillsConfig) {
1074
+ return {
1075
+ tool: toolId,
1076
+ toolName: tool.config.name,
1077
+ type: "skill",
1078
+ path: "",
1079
+ success: false,
1080
+ skipped: true,
1081
+ skipReason: "Tool does not support skills"
1082
+ };
1083
+ }
1084
+ const basePath = options.global ? skillsConfig.globalPath : path3.join(projectRoot, skillsConfig.projectPath);
1085
+ const skillDir = path3.join(basePath, pkg.manifest.name);
1086
+ const skillPath = path3.join(skillDir, "SKILL.md");
1087
+ if (!options.force && await fileExists(skillPath)) {
1088
+ return {
1089
+ tool: toolId,
1090
+ toolName: tool.config.name,
1091
+ type: "skill",
1092
+ path: skillPath,
1093
+ success: false,
1094
+ skipped: true,
1095
+ skipReason: "Skill already exists (use --force to overwrite)"
1096
+ };
1097
+ }
1098
+ if (options.dryRun) {
1099
+ return {
1100
+ tool: toolId,
1101
+ toolName: tool.config.name,
1102
+ type: "skill",
1103
+ path: skillPath,
1104
+ success: true,
1105
+ skipped: true,
1106
+ skipReason: "Dry run"
1107
+ };
1108
+ }
1109
+ await ensureDir(skillDir);
1110
+ await fs2.writeFile(skillPath, content, "utf-8");
1111
+ for (const file of pkg.files) {
1112
+ if (file.path !== "SKILL.md" && file.path !== "rule.md" && file.path !== "manifest.json") {
1113
+ const targetFile = path3.join(skillDir, file.path);
1114
+ await ensureDir(path3.dirname(targetFile));
1115
+ await fs2.writeFile(targetFile, file.content, "utf-8");
1116
+ }
1117
+ }
1118
+ return {
1119
+ tool: toolId,
1120
+ toolName: tool.config.name,
1121
+ type: "skill",
1122
+ path: skillPath,
1123
+ success: true
1124
+ };
1125
+ }
1126
+ async function handleSpecialAction(pkg, tool, content, projectRoot, options, action) {
1127
+ const toolId = tool.config.id;
1128
+ const { body } = parseFrontmatter(content);
1129
+ switch (action) {
1130
+ case "append-to-agents-md": {
1131
+ const agentsPath = options.global ? path3.join(tool.config.rules.globalPath, "AGENTS.md") : path3.join(projectRoot, "AGENTS.md");
1132
+ if (options.dryRun) {
1133
+ return {
1134
+ tool: toolId,
1135
+ toolName: tool.config.name,
1136
+ type: "rule",
1137
+ path: agentsPath,
1138
+ success: true,
1139
+ skipped: true,
1140
+ skipReason: "Dry run - would append to AGENTS.md"
1141
+ };
1142
+ }
1143
+ const existing = await readFile(agentsPath) || "";
1144
+ const section = `
1145
+ ## ${pkg.manifest.name}
1146
+
1147
+ ${body}
1148
+ `;
1149
+ if (existing.includes(`## ${pkg.manifest.name}`)) {
1150
+ if (!options.force) {
1151
+ return {
1152
+ tool: toolId,
1153
+ toolName: tool.config.name,
1154
+ type: "rule",
1155
+ path: agentsPath,
1156
+ success: false,
1157
+ skipped: true,
1158
+ skipReason: "Section already exists in AGENTS.md"
1159
+ };
1160
+ }
1161
+ const regex = new RegExp(`
1162
+ ## ${pkg.manifest.name}
1163
+ [\\s\\S]*?(?=
1164
+ ## |$)`, "g");
1165
+ const updated = existing.replace(regex, "") + section;
1166
+ await fs2.writeFile(agentsPath, updated, "utf-8");
1167
+ } else {
1168
+ await fs2.writeFile(agentsPath, existing + section, "utf-8");
1169
+ }
1170
+ return {
1171
+ tool: toolId,
1172
+ toolName: tool.config.name,
1173
+ type: "rule",
1174
+ path: agentsPath,
1175
+ success: true
1176
+ };
1177
+ }
1178
+ case "append-to-gemini-md": {
1179
+ const geminiPath = options.global ? path3.join(tool.config.rules.globalPath, "GEMINI.md") : path3.join(projectRoot, "GEMINI.md");
1180
+ if (options.dryRun) {
1181
+ return {
1182
+ tool: toolId,
1183
+ toolName: tool.config.name,
1184
+ type: "rule",
1185
+ path: geminiPath,
1186
+ success: true,
1187
+ skipped: true,
1188
+ skipReason: "Dry run - would append to GEMINI.md"
1189
+ };
1190
+ }
1191
+ const existing = await readFile(geminiPath) || "";
1192
+ const section = `
1193
+ ## ${pkg.manifest.name}
1194
+
1195
+ ${body}
1196
+ `;
1197
+ if (existing.includes(`## ${pkg.manifest.name}`) && !options.force) {
1198
+ return {
1199
+ tool: toolId,
1200
+ toolName: tool.config.name,
1201
+ type: "rule",
1202
+ path: geminiPath,
1203
+ success: false,
1204
+ skipped: true,
1205
+ skipReason: "Section already exists in GEMINI.md"
1206
+ };
1207
+ }
1208
+ await fs2.writeFile(geminiPath, existing + section, "utf-8");
1209
+ return {
1210
+ tool: toolId,
1211
+ toolName: tool.config.name,
1212
+ type: "rule",
1213
+ path: geminiPath,
1214
+ success: true
1215
+ };
1216
+ }
1217
+ case "add-to-read-config": {
1218
+ const rulePath = path3.join(projectRoot, `${pkg.manifest.name}.md`);
1219
+ const configPath = path3.join(projectRoot, ".aider.conf.yml");
1220
+ if (options.dryRun) {
1221
+ return {
1222
+ tool: toolId,
1223
+ toolName: tool.config.name,
1224
+ type: "rule",
1225
+ path: rulePath,
1226
+ success: true,
1227
+ skipped: true,
1228
+ skipReason: "Dry run - would create file and update .aider.conf.yml"
1229
+ };
1230
+ }
1231
+ await fs2.writeFile(rulePath, body, "utf-8");
1232
+ const existingConfig = await readFile(configPath);
1233
+ let config = {};
1234
+ if (existingConfig) {
1235
+ try {
1236
+ config = YAML2.parse(existingConfig) || {};
1237
+ } catch {
1238
+ }
1239
+ }
1240
+ const readList = config.read;
1241
+ const fileName = `${pkg.manifest.name}.md`;
1242
+ if (Array.isArray(readList)) {
1243
+ if (!readList.includes(fileName)) {
1244
+ readList.push(fileName);
1245
+ }
1246
+ } else if (typeof readList === "string") {
1247
+ if (readList !== fileName) {
1248
+ config.read = [readList, fileName];
1249
+ }
1250
+ } else {
1251
+ config.read = fileName;
1252
+ }
1253
+ await fs2.writeFile(configPath, YAML2.stringify(config), "utf-8");
1254
+ return {
1255
+ tool: toolId,
1256
+ toolName: tool.config.name,
1257
+ type: "rule",
1258
+ path: rulePath,
1259
+ success: true
1260
+ };
1261
+ }
1262
+ default:
1263
+ return {
1264
+ tool: toolId,
1265
+ toolName: tool.config.name,
1266
+ type: "rule",
1267
+ path: "",
1268
+ success: false,
1269
+ error: `Unknown action: ${action}`
1270
+ };
1271
+ }
1272
+ }
1273
+ async function uninstallPackage(packageName, detectedTools, projectRoot, options) {
1274
+ const results = [];
1275
+ for (const tool of detectedTools) {
1276
+ const toolId = tool.config.id;
1277
+ const rulesConfig = tool.config.rules;
1278
+ const ruleBasePath = options.global ? rulesConfig.globalPath : path3.join(projectRoot, rulesConfig.projectPath);
1279
+ const ruleFilename = `${packageName}${rulesConfig.fileExtension}`;
1280
+ const rulePath = path3.join(ruleBasePath, ruleFilename);
1281
+ if (await fileExists(rulePath)) {
1282
+ if (!options.dryRun) {
1283
+ await fs2.unlink(rulePath);
1284
+ }
1285
+ results.push({
1286
+ tool: toolId,
1287
+ toolName: tool.config.name,
1288
+ type: "rule",
1289
+ path: rulePath,
1290
+ success: true,
1291
+ skipped: options.dryRun,
1292
+ skipReason: options.dryRun ? "Dry run" : void 0
1293
+ });
1294
+ }
1295
+ if (tool.config.skills) {
1296
+ const skillBasePath = options.global ? tool.config.skills.globalPath : path3.join(projectRoot, tool.config.skills.projectPath);
1297
+ const skillDir = path3.join(skillBasePath, packageName);
1298
+ if (await fileExists(skillDir)) {
1299
+ if (!options.dryRun) {
1300
+ await fs2.rm(skillDir, { recursive: true });
1301
+ }
1302
+ results.push({
1303
+ tool: toolId,
1304
+ toolName: tool.config.name,
1305
+ type: "skill",
1306
+ path: skillDir,
1307
+ success: true,
1308
+ skipped: options.dryRun,
1309
+ skipReason: options.dryRun ? "Dry run" : void 0
1310
+ });
1311
+ }
1312
+ }
1313
+ }
1314
+ return results;
1315
+ }
1316
+ async function listInstalled(detectedTools, projectRoot, options) {
1317
+ const installed = /* @__PURE__ */ new Map();
1318
+ for (const tool of detectedTools) {
1319
+ const toolId = tool.config.id;
1320
+ const rulesConfig = tool.config.rules;
1321
+ const rules = [];
1322
+ const skills = [];
1323
+ const ruleBasePath = options.global ? rulesConfig.globalPath : path3.join(projectRoot, rulesConfig.projectPath);
1324
+ try {
1325
+ const files = await fs2.readdir(ruleBasePath);
1326
+ for (const file of files) {
1327
+ if (file.endsWith(rulesConfig.fileExtension)) {
1328
+ rules.push(file.replace(rulesConfig.fileExtension, ""));
1329
+ }
1330
+ }
1331
+ } catch {
1332
+ }
1333
+ if (tool.config.skills) {
1334
+ const skillBasePath = options.global ? tool.config.skills.globalPath : path3.join(projectRoot, tool.config.skills.projectPath);
1335
+ try {
1336
+ const dirs = await fs2.readdir(skillBasePath);
1337
+ for (const dir of dirs) {
1338
+ const skillPath = path3.join(skillBasePath, dir, "SKILL.md");
1339
+ if (await fileExists(skillPath)) {
1340
+ skills.push(dir);
1341
+ }
1342
+ }
1343
+ } catch {
1344
+ }
1345
+ }
1346
+ if (rules.length > 0 || skills.length > 0) {
1347
+ installed.set(toolId, { rules, skills });
1348
+ }
1349
+ }
1350
+ return installed;
1351
+ }
1352
+
1353
+ // src/registry.ts
1354
+ import fs3 from "fs/promises";
1355
+ import path4 from "path";
1356
+ import os2 from "os";
1357
+ var SCHEMA_VERSION = 1;
1358
+ function emptyRegistry() {
1359
+ return { schemaVersion: SCHEMA_VERSION, packages: {} };
1360
+ }
1361
+ function registryKey(name, installType) {
1362
+ return `${name}::${installType}`;
1363
+ }
1364
+ function getRegistryPath(projectRoot, global) {
1365
+ if (global) {
1366
+ return path4.join(os2.homedir(), ".faster", "installed.json");
1367
+ }
1368
+ return path4.join(projectRoot, ".faster", "installed.json");
1369
+ }
1370
+ async function ensureDir2(dir) {
1371
+ await fs3.mkdir(dir, { recursive: true });
1372
+ }
1373
+ async function readRegistry(projectRoot, global) {
1374
+ const registryPath = getRegistryPath(projectRoot, global);
1375
+ try {
1376
+ const raw = await fs3.readFile(registryPath, "utf-8");
1377
+ const parsed = JSON.parse(raw);
1378
+ if (!parsed || parsed.schemaVersion !== SCHEMA_VERSION || typeof parsed.packages !== "object") {
1379
+ return emptyRegistry();
1380
+ }
1381
+ return parsed;
1382
+ } catch {
1383
+ return emptyRegistry();
1384
+ }
1385
+ }
1386
+ async function writeRegistry(projectRoot, global, registry) {
1387
+ const registryPath = getRegistryPath(projectRoot, global);
1388
+ await ensureDir2(path4.dirname(registryPath));
1389
+ await fs3.writeFile(registryPath, JSON.stringify(registry, null, 2), "utf-8");
1390
+ }
1391
+ function upsertInstalledPackage(registry, record) {
1392
+ registry.packages[registryKey(record.name, record.installType)] = record;
1393
+ }
1394
+ function removeInstalledPackage(registry, name, installType) {
1395
+ if (installType) {
1396
+ delete registry.packages[registryKey(name, installType)];
1397
+ return;
1398
+ }
1399
+ delete registry.packages[registryKey(name, "rule")];
1400
+ delete registry.packages[registryKey(name, "skill")];
1401
+ }
1402
+ function listInstalledPackages(registry) {
1403
+ return Object.values(registry.packages);
1404
+ }
1405
+
1406
+ // src/commands/shared/package-helpers.ts
1407
+ import path5 from "path";
1408
+ async function resolveDetectedTools(projectRoot, options, defaultTools) {
1409
+ let detectedTools = await detectTools(projectRoot);
1410
+ if (detectedTools.length === 0) {
1411
+ detectedTools = Object.values(TOOL_CONFIGS).map((config) => ({
1412
+ config,
1413
+ projectPath: path5.join(projectRoot, config.rules.projectPath),
1414
+ globalPath: config.rules.globalPath
1415
+ }));
1416
+ }
1417
+ const toolFilter = options.tools && options.tools.length > 0 ? options.tools : defaultTools && defaultTools.length > 0 ? defaultTools : void 0;
1418
+ if (toolFilter) {
1419
+ detectedTools = filterTools(detectedTools, toolFilter);
1420
+ if (detectedTools.length === 0) {
1421
+ throw new Error(`None of the specified tools were found: ${toolFilter.join(", ")}`);
1422
+ }
1423
+ }
1424
+ if (options.asSkill) {
1425
+ detectedTools = getSkillTools(detectedTools);
1426
+ if (detectedTools.length === 0) {
1427
+ throw new Error("No detected tools support skills");
1428
+ }
1429
+ }
1430
+ return detectedTools;
1431
+ }
1432
+
1433
+ // src/commands/package/install.ts
1434
+ import fs4 from "fs/promises";
1435
+ import path6 from "path";
1436
+ function registerInstallCommand(program2) {
1437
+ program2.command("install <package>").alias("add").description("Install a skill or rule from faster.dev").option("-g, --global", "Install globally instead of to project").option("-t, --tools <tools>", "Comma-separated list of tools to install to").option("--as-skill", "Install as a skill (where supported)").option("-f, --force", "Overwrite existing installations").option("--dry-run", "Show what would be installed without making changes").option("--from-file <path>", "Install from a local package directory").action(async (packageInput, opts) => {
1438
+ const { json, verbose } = program2.opts();
1439
+ const projectRoot = process.cwd();
1440
+ const { name: packageName, version } = parsePackageSpec(packageInput);
1441
+ const options = {
1442
+ global: opts.global ?? false,
1443
+ tools: opts.tools ? opts.tools.split(",") : void 0,
1444
+ asSkill: opts.asSkill ?? false,
1445
+ force: opts.force ?? false,
1446
+ dryRun: opts.dryRun ?? false
1447
+ };
1448
+ const defaultTools = getDefaultTools();
1449
+ const spinner = new SpinnerManager("Detecting tools...", json ?? false);
1450
+ try {
1451
+ const detectedTools = await resolveDetectedTools(projectRoot, options, defaultTools);
1452
+ spinner.text(`Fetching package: ${packageName}...`);
1453
+ let pkg;
1454
+ const isLocal = Boolean(opts.fromFile);
1455
+ if (opts.fromFile) {
1456
+ pkg = await loadLocalPackage(opts.fromFile);
1457
+ } else {
1458
+ const api = new FasterAPI(getConfig());
1459
+ pkg = await api.downloadPackage(packageName, version);
1460
+ }
1461
+ spinner.text(`Installing ${pkg.manifest.name}...`);
1462
+ const results = await installPackage(pkg, detectedTools, projectRoot, options);
1463
+ spinner.stop();
1464
+ const installType = resolveInstallType(options.asSkill);
1465
+ const successTools = results.filter((r) => r.success && !r.skipped).map((r) => r.tool);
1466
+ if (!options.dryRun && successTools.length > 0) {
1467
+ const registry = await readRegistry(projectRoot, options.global);
1468
+ upsertInstalledPackage(registry, {
1469
+ name: pkg.manifest.name,
1470
+ version: pkg.manifest.version,
1471
+ installType,
1472
+ tools: successTools,
1473
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1474
+ source: isLocal ? "local" : "registry",
1475
+ localPath: isLocal ? opts.fromFile : void 0
1476
+ });
1477
+ await writeRegistry(projectRoot, options.global, registry);
1478
+ }
1479
+ if (json) {
1480
+ outputJson({
1481
+ package: pkg.manifest,
1482
+ results
1483
+ });
1484
+ return;
1485
+ }
1486
+ printInstallResults(pkg, results);
1487
+ } catch (error) {
1488
+ spinner.fail(`Failed: ${stringifyError(error, verbose)}`);
1489
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
1490
+ setExitCode(mapApiErrorToExitCode(error));
1491
+ }
1492
+ });
1493
+ }
1494
+ function printInstallResults(pkg, results) {
1495
+ console.log();
1496
+ console.log(chalk8.bold(`\u{1F4E6} ${pkg.manifest.name} v${pkg.manifest.version}`));
1497
+ console.log(chalk8.dim(pkg.manifest.description));
1498
+ console.log();
1499
+ let successCount = 0;
1500
+ let skipCount = 0;
1501
+ let errorCount = 0;
1502
+ for (const result of results) {
1503
+ if (result.success && !result.skipped) {
1504
+ successCount++;
1505
+ console.log(
1506
+ chalk8.green(" \u2713"),
1507
+ chalk8.bold(result.toolName),
1508
+ chalk8.dim(`\u2192 ${result.path}`)
1509
+ );
1510
+ } else if (result.skipped) {
1511
+ skipCount++;
1512
+ console.log(
1513
+ chalk8.yellow(" \u2298"),
1514
+ chalk8.bold(result.toolName),
1515
+ chalk8.dim(`(${result.skipReason})`)
1516
+ );
1517
+ } else {
1518
+ errorCount++;
1519
+ console.log(
1520
+ chalk8.red(" \u2717"),
1521
+ chalk8.bold(result.toolName),
1522
+ chalk8.dim(`(${result.error})`)
1523
+ );
1524
+ }
1525
+ }
1526
+ console.log();
1527
+ if (successCount > 0) {
1528
+ console.log(chalk8.green(`Installed to ${successCount} tool(s)`));
1529
+ }
1530
+ if (skipCount > 0) {
1531
+ console.log(chalk8.yellow(`Skipped ${skipCount} tool(s)`));
1532
+ }
1533
+ if (errorCount > 0) {
1534
+ console.log(chalk8.red(`Failed for ${errorCount} tool(s)`));
1535
+ setExitCode(EXIT_CODES.ERROR);
1536
+ }
1537
+ }
1538
+ async function loadLocalPackage(dir) {
1539
+ const files = [];
1540
+ const manifestPath = path6.join(dir, "manifest.json");
1541
+ const manifestContent = await fs4.readFile(manifestPath, "utf-8");
1542
+ files.push({ path: "manifest.json", content: manifestContent });
1543
+ const manifest = JSON.parse(manifestContent);
1544
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
1545
+ for (const entry of entries) {
1546
+ if (entry.isFile() && entry.name !== "manifest.json") {
1547
+ if (entry.name.endsWith(".md") || entry.name.endsWith(".mdc") || entry.name.endsWith(".txt")) {
1548
+ const content = await fs4.readFile(path6.join(dir, entry.name), "utf-8");
1549
+ files.push({ path: entry.name, content });
1550
+ }
1551
+ }
1552
+ }
1553
+ const assetsDir = path6.join(dir, "assets");
1554
+ try {
1555
+ const assetEntries = await fs4.readdir(assetsDir, { withFileTypes: true });
1556
+ for (const entry of assetEntries) {
1557
+ if (entry.isFile()) {
1558
+ const content = await fs4.readFile(path6.join(assetsDir, entry.name), "utf-8");
1559
+ files.push({ path: `assets/${entry.name}`, content });
1560
+ }
1561
+ }
1562
+ } catch {
1563
+ }
1564
+ return { manifest, files };
1565
+ }
1566
+
1567
+ // src/commands/package/remove.ts
1568
+ import chalk9 from "chalk";
1569
+ function registerRemoveCommand(program2) {
1570
+ program2.command("remove <package>").alias("uninstall").description("Remove an installed skill or rule").option("-g, --global", "Remove from global installation").option("--dry-run", "Show what would be removed without making changes").action(async (packageInput, opts) => {
1571
+ const { json, verbose } = program2.opts();
1572
+ const projectRoot = process.cwd();
1573
+ const { name: packageName } = parsePackageSpec(packageInput);
1574
+ const spinner = new SpinnerManager("Detecting tools...", json ?? false);
1575
+ try {
1576
+ const detectedTools = await detectTools(projectRoot);
1577
+ if (detectedTools.length === 0) {
1578
+ spinner.fail("No AI coding tools detected");
1579
+ if (json) outputJson({ ok: false, error: "No AI coding tools detected" });
1580
+ setExitCode(EXIT_CODES.ERROR);
1581
+ return;
1582
+ }
1583
+ spinner.text(`Removing ${packageName}...`);
1584
+ const results = await uninstallPackage(packageName, detectedTools, projectRoot, {
1585
+ global: opts.global ?? false,
1586
+ dryRun: opts.dryRun ?? false
1587
+ });
1588
+ spinner.stop();
1589
+ if (!opts.dryRun && results.length > 0) {
1590
+ const registry = await readRegistry(projectRoot, opts.global ?? false);
1591
+ removeInstalledPackage(registry, packageName);
1592
+ await writeRegistry(projectRoot, opts.global ?? false, registry);
1593
+ }
1594
+ if (results.length === 0) {
1595
+ if (json) {
1596
+ outputJson({ ok: false, error: `Package ${packageName} not found in any tool` });
1597
+ } else {
1598
+ console.log(chalk9.yellow(`Package ${packageName} not found in any tool`));
1599
+ }
1600
+ setExitCode(EXIT_CODES.NOT_FOUND);
1601
+ return;
1602
+ }
1603
+ if (json) {
1604
+ outputJson({ ok: true, results });
1605
+ return;
1606
+ }
1607
+ console.log();
1608
+ for (const result of results) {
1609
+ if (result.success) {
1610
+ console.log(
1611
+ chalk9.green(" \u2713"),
1612
+ `Removed from ${result.toolName}:`,
1613
+ chalk9.dim(result.path)
1614
+ );
1615
+ }
1616
+ }
1617
+ } catch (error) {
1618
+ spinner.fail(`Failed: ${stringifyError(error, verbose)}`);
1619
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
1620
+ setExitCode(mapApiErrorToExitCode(error));
1621
+ }
1622
+ });
1623
+ }
1624
+
1625
+ // src/commands/package/list.ts
1626
+ import chalk10 from "chalk";
1627
+ function registerListCommand(program2) {
1628
+ program2.command("list").alias("ls").description("List installed skills and rules").option("-g, --global", "List global installations").action(async (opts) => {
1629
+ const { json, verbose } = program2.opts();
1630
+ const projectRoot = process.cwd();
1631
+ const spinner = new SpinnerManager("Detecting tools...", json ?? false);
1632
+ try {
1633
+ const detectedTools = await detectTools(projectRoot);
1634
+ if (detectedTools.length === 0) {
1635
+ spinner.info("No AI coding tools detected");
1636
+ if (json) outputJson({ ok: true, tools: {} });
1637
+ return;
1638
+ }
1639
+ const installed = await listInstalled(detectedTools, projectRoot, {
1640
+ global: opts.global ?? false
1641
+ });
1642
+ spinner.stop();
1643
+ const registry = await readRegistry(projectRoot, opts.global ?? false);
1644
+ const registryPackages = listInstalledPackages(registry);
1645
+ if (installed.size === 0) {
1646
+ if (json) {
1647
+ outputJson({ ok: true, tools: {}, registry: registryPackages });
1648
+ } else {
1649
+ console.log(chalk10.yellow("No packages installed"));
1650
+ }
1651
+ return;
1652
+ }
1653
+ if (json) {
1654
+ const tools = {};
1655
+ for (const [toolId, packages] of installed) {
1656
+ tools[toolId] = packages;
1657
+ }
1658
+ outputJson({ ok: true, tools, registry: registryPackages });
1659
+ return;
1660
+ }
1661
+ console.log();
1662
+ for (const [toolId, packages] of installed) {
1663
+ const config = TOOL_CONFIGS[toolId];
1664
+ console.log(chalk10.bold(config.name));
1665
+ if (packages.rules.length > 0) {
1666
+ console.log(chalk10.dim(" Rules:"));
1667
+ for (const rule of packages.rules) {
1668
+ console.log(` - ${rule}`);
1669
+ }
1670
+ }
1671
+ if (packages.skills.length > 0) {
1672
+ console.log(chalk10.dim(" Skills:"));
1673
+ for (const skill of packages.skills) {
1674
+ console.log(` - ${skill}`);
1675
+ }
1676
+ }
1677
+ console.log();
1678
+ }
1679
+ } catch (error) {
1680
+ spinner.fail(`Failed: ${stringifyError(error, verbose)}`);
1681
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
1682
+ setExitCode(mapApiErrorToExitCode(error));
1683
+ }
1684
+ });
1685
+ }
1686
+
1687
+ // src/commands/package/update.ts
1688
+ import chalk11 from "chalk";
1689
+ import semver from "semver";
1690
+ function registerUpdateCommand(program2) {
1691
+ program2.command("update [package]").description("Update installed packages to the latest version").option("-g, --global", "Update global installations").option("-t, --tools <tools>", "Comma-separated list of tools to install to").option("--as-skill", "Update as a skill (where supported)").option("-f, --force", "Overwrite existing installations").option("--dry-run", "Show what would be updated without making changes").action(async (packageInput, opts) => {
1692
+ const { json, verbose } = program2.opts();
1693
+ const projectRoot = process.cwd();
1694
+ const spinner = new SpinnerManager("Resolving updates...", json ?? false);
1695
+ try {
1696
+ const registry = await readRegistry(projectRoot, opts.global ?? false);
1697
+ let installed = listInstalledPackages(registry).filter((p) => p.source === "registry");
1698
+ if (packageInput) {
1699
+ const parsed = parsePackageSpec(packageInput);
1700
+ installed = installed.filter((p) => p.name === parsed.name);
1701
+ }
1702
+ if (installed.length === 0) {
1703
+ spinner.stop();
1704
+ if (json) {
1705
+ outputJson({ ok: true, updated: [] });
1706
+ } else {
1707
+ console.log(chalk11.yellow("No matching installed packages to update"));
1708
+ }
1709
+ return;
1710
+ }
1711
+ const api = new FasterAPI(getConfig());
1712
+ const updated = [];
1713
+ for (const pkg of installed) {
1714
+ const info = await api.getPackageInfo(pkg.name);
1715
+ const latest = info.latestVersion;
1716
+ const targetVersion = latest;
1717
+ if (!semver.valid(pkg.version) || !semver.valid(targetVersion)) {
1718
+ continue;
1719
+ }
1720
+ if (!semver.lt(pkg.version, targetVersion)) {
1721
+ continue;
1722
+ }
1723
+ const options = {
1724
+ global: opts.global ?? false,
1725
+ tools: opts.tools ? opts.tools.split(",") : pkg.tools,
1726
+ asSkill: opts.asSkill ?? pkg.installType === "skill",
1727
+ force: opts.force ?? false,
1728
+ dryRun: opts.dryRun ?? false
1729
+ };
1730
+ const detectedTools = await resolveDetectedTools(projectRoot, options);
1731
+ const downloaded = await api.downloadPackage(pkg.name, targetVersion);
1732
+ const results = await installPackage(downloaded, detectedTools, projectRoot, options);
1733
+ const successTools = results.filter((r) => r.success && !r.skipped).map((r) => r.tool);
1734
+ if (!options.dryRun && successTools.length > 0) {
1735
+ upsertInstalledPackage(registry, {
1736
+ name: downloaded.manifest.name,
1737
+ version: downloaded.manifest.version,
1738
+ installType: resolveInstallType(options.asSkill),
1739
+ tools: successTools,
1740
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1741
+ source: "registry"
1742
+ });
1743
+ }
1744
+ updated.push({
1745
+ name: pkg.name,
1746
+ from: pkg.version,
1747
+ to: targetVersion,
1748
+ installType: pkg.installType
1749
+ });
1750
+ }
1751
+ if (!opts.dryRun) {
1752
+ await writeRegistry(projectRoot, opts.global ?? false, registry);
1753
+ }
1754
+ spinner.stop();
1755
+ if (json) {
1756
+ outputJson({ ok: true, updated });
1757
+ return;
1758
+ }
1759
+ if (updated.length === 0) {
1760
+ console.log(chalk11.green("All packages are up to date"));
1761
+ return;
1762
+ }
1763
+ console.log();
1764
+ for (const update of updated) {
1765
+ console.log(
1766
+ ` \u2713 ${chalk11.bold(update.name)} (${update.installType}) ${chalk11.dim(update.from)} \u2192 ${chalk11.green(update.to)}`
1767
+ );
1768
+ }
1769
+ } catch (error) {
1770
+ spinner.fail(`Update failed: ${stringifyError(error, verbose)}`);
1771
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
1772
+ setExitCode(mapApiErrorToExitCode(error));
1773
+ }
1774
+ });
1775
+ }
1776
+
1777
+ // src/commands/package/outdated.ts
1778
+ import chalk12 from "chalk";
1779
+ import semver2 from "semver";
1780
+ function registerOutdatedCommand(program2) {
1781
+ program2.command("outdated").description("List installed packages that have updates available").option("-g, --global", "Check global installations").action(async (opts) => {
1782
+ const { json, verbose } = program2.opts();
1783
+ const projectRoot = process.cwd();
1784
+ const spinner = new SpinnerManager("Checking for updates...", json ?? false);
1785
+ try {
1786
+ const registry = await readRegistry(projectRoot, opts.global ?? false);
1787
+ const installed = listInstalledPackages(registry).filter((p) => p.source === "registry");
1788
+ if (installed.length === 0) {
1789
+ spinner.stop();
1790
+ if (json) {
1791
+ outputJson({ ok: true, updates: [] });
1792
+ } else {
1793
+ console.log(chalk12.green("All packages are up to date"));
1794
+ }
1795
+ return;
1796
+ }
1797
+ const api = new FasterAPI(getConfig());
1798
+ const updates = [];
1799
+ for (const pkg of installed) {
1800
+ const info = await api.getPackageInfo(pkg.name);
1801
+ const latest = info.latestVersion;
1802
+ if (semver2.valid(pkg.version) && semver2.valid(latest) && semver2.lt(pkg.version, latest)) {
1803
+ updates.push({
1804
+ name: pkg.name,
1805
+ current: pkg.version,
1806
+ latest,
1807
+ installType: pkg.installType
1808
+ });
1809
+ }
1810
+ }
1811
+ spinner.stop();
1812
+ if (json) {
1813
+ outputJson({ ok: true, updates });
1814
+ return;
1815
+ }
1816
+ if (updates.length === 0) {
1817
+ console.log(chalk12.green("All packages are up to date"));
1818
+ return;
1819
+ }
1820
+ console.log();
1821
+ for (const update of updates) {
1822
+ console.log(
1823
+ ` - ${chalk12.bold(update.name)} (${update.installType}) ${chalk12.dim(update.current)} \u2192 ${chalk12.green(update.latest)}`
1824
+ );
1825
+ }
1826
+ } catch (error) {
1827
+ spinner.fail(`Outdated check failed: ${stringifyError(error, verbose)}`);
1828
+ if (json) outputJson({ ok: false, error: stringifyError(error, verbose) });
1829
+ setExitCode(mapApiErrorToExitCode(error));
1830
+ }
1831
+ });
1832
+ }
1833
+
1834
+ // src/commands/package/index.ts
1835
+ function registerPackageCommands(program2) {
1836
+ registerInstallCommand(program2);
1837
+ registerRemoveCommand(program2);
1838
+ registerListCommand(program2);
1839
+ registerUpdateCommand(program2);
1840
+ registerOutdatedCommand(program2);
1841
+ }
1842
+
1843
+ // src/commands/config.ts
1844
+ import chalk13 from "chalk";
1845
+ function registerConfigCommand(program2) {
1846
+ program2.command("config").description("Manage CLI configuration").option("--set-tools <tools>", "Set default tools (comma-separated)").option("--clear-tools", "Clear default tools").option("--set-api-url <url>", "Set API base URL").option("--path", "Show config file path").action((opts) => {
1847
+ const { json } = program2.opts();
1848
+ if (opts.path) {
1849
+ if (json) outputJson({ path: getConfigPath() });
1850
+ else console.log(getConfigPath());
1851
+ return;
1852
+ }
1853
+ if (opts.setApiUrl) {
1854
+ setApiUrl(opts.setApiUrl);
1855
+ if (json) outputJson({ ok: true, apiUrl: opts.setApiUrl });
1856
+ else console.log(chalk13.green(`API URL set: ${opts.setApiUrl}`));
1857
+ return;
1858
+ }
1859
+ if (opts.setTools) {
1860
+ const tools = opts.setTools.split(",");
1861
+ for (const tool of tools) {
1862
+ if (!TOOL_CONFIGS[tool]) {
1863
+ if (json) {
1864
+ outputJson({ ok: false, error: `Unknown tool: ${tool}` });
1865
+ } else {
1866
+ console.log(chalk13.red(`Unknown tool: ${tool}`));
1867
+ console.log(chalk13.dim(`Available: ${Object.keys(TOOL_CONFIGS).join(", ")}`));
1868
+ }
1869
+ setExitCode(EXIT_CODES.INVALID_ARGS);
1870
+ return;
1871
+ }
1872
+ }
1873
+ setDefaultTools(tools);
1874
+ if (json) outputJson({ ok: true, defaultTools: tools });
1875
+ else console.log(chalk13.green(`Default tools set: ${tools.join(", ")}`));
1876
+ return;
1877
+ }
1878
+ if (opts.clearTools) {
1879
+ setDefaultTools([]);
1880
+ if (json) outputJson({ ok: true, defaultTools: [] });
1881
+ else console.log(chalk13.green("Default tools cleared"));
1882
+ return;
1883
+ }
1884
+ const config = getConfig();
1885
+ if (json) {
1886
+ outputJson({
1887
+ apiUrl: config.apiUrl,
1888
+ authenticated: Boolean(config.authToken),
1889
+ defaultTools: config.defaultTools ?? null,
1890
+ configPath: getConfigPath()
1891
+ });
1892
+ return;
1893
+ }
1894
+ console.log();
1895
+ console.log(chalk13.bold("Configuration:"));
1896
+ console.log(` API URL: ${config.apiUrl}`);
1897
+ console.log(` Authenticated: ${config.authToken ? chalk13.green("Yes") : chalk13.yellow("No")}`);
1898
+ console.log(` Default tools: ${config.defaultTools?.join(", ") || chalk13.dim("(all detected)")}`);
1899
+ console.log(` Config path: ${chalk13.dim(getConfigPath())}`);
1900
+ });
1901
+ }
1902
+
1903
+ // src/commands/index.ts
1904
+ function registerAllCommands(program2) {
1905
+ registerAuthCommands(program2);
1906
+ registerDiscoveryCommands(program2);
1907
+ registerPackageCommands(program2);
1908
+ registerConfigCommand(program2);
1909
+ }
1910
+
1911
+ // src/cli.ts
1912
+ var program = new Command();
1913
+ program.name("fasterdev").description("Install AI coding assistant skills and rules from faster.dev").version("0.1.0").option("--json", "Output JSON").option("--verbose", "Verbose output");
1914
+ registerAllCommands(program);
1915
+ program.parse();