tenfold 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.
@@ -0,0 +1,2814 @@
1
+ // src/index.ts
2
+ import { Command as Command7 } from "commander";
3
+
4
+ // src/ui/banner.ts
5
+ import figlet from "figlet";
6
+
7
+ // src/ui/theme.ts
8
+ import chalk from "chalk";
9
+ import gradientString from "gradient-string";
10
+ var colors = {
11
+ primary: chalk.hex("#3b82f6"),
12
+ // Blue
13
+ secondary: chalk.hex("#60a5fa"),
14
+ // Light Blue
15
+ success: chalk.hex("#22c55e"),
16
+ // Green
17
+ warning: chalk.hex("#f59e0b"),
18
+ // Amber
19
+ error: chalk.hex("#ef4444"),
20
+ // Red
21
+ info: chalk.hex("#0ea5e9"),
22
+ // Sky
23
+ muted: chalk.dim,
24
+ highlight: chalk.cyan,
25
+ bold: chalk.bold,
26
+ underline: chalk.underline
27
+ };
28
+ var brandGradient = gradientString([
29
+ "#2563eb",
30
+ // Blue 600
31
+ "#3b82f6",
32
+ // Blue 500
33
+ "#60a5fa"
34
+ // Blue 400
35
+ ]);
36
+ var typeColors = {
37
+ INSTRUCTION: chalk.hex("#3b82f6"),
38
+ // Blue
39
+ HOOK: chalk.hex("#f59e0b"),
40
+ // Amber
41
+ COMMAND: chalk.hex("#22c55e"),
42
+ // Green
43
+ MCP_SERVER: chalk.hex("#8b5cf6"),
44
+ // Violet
45
+ SKILL: chalk.hex("#ec4899"),
46
+ // Pink
47
+ AGENT: chalk.hex("#14b8a6")
48
+ // Teal
49
+ };
50
+ var statusColors = {
51
+ installed: colors.success,
52
+ available: colors.info,
53
+ outdated: colors.warning,
54
+ conflict: colors.error
55
+ };
56
+ var typeIcons = {
57
+ INSTRUCTION: "\u{1F4DD}",
58
+ // 📝
59
+ HOOK: "\u{1F527}",
60
+ // 🔧
61
+ COMMAND: "\u2318",
62
+ // ⌘
63
+ MCP_SERVER: "\u{1F5A5}",
64
+ // 🖥️
65
+ SKILL: "\u26A1",
66
+ // ⚡
67
+ AGENT: "\u{1F916}"
68
+ // 🤖
69
+ };
70
+ function success(text) {
71
+ return colors.success(text);
72
+ }
73
+ function muted(text) {
74
+ return colors.muted(text);
75
+ }
76
+ function highlight(text) {
77
+ return colors.highlight(text);
78
+ }
79
+ function bold(text) {
80
+ return colors.bold(text);
81
+ }
82
+ function formatInstalls(count) {
83
+ if (count < 1e3) return muted(`${count} installs`);
84
+ if (count < 1e6) return muted(`${(count / 1e3).toFixed(1)}k installs`);
85
+ return muted(`${(count / 1e6).toFixed(1)}M installs`);
86
+ }
87
+
88
+ // src/ui/banner.ts
89
+ var BANNER_TEXT = "TENFOLD";
90
+ function renderBanner() {
91
+ const ascii = figlet.textSync(BANNER_TEXT, {
92
+ font: "ANSI Shadow",
93
+ horizontalLayout: "default"
94
+ });
95
+ const gradientBanner = brandGradient(ascii);
96
+ const tagline = muted(" The package manager for Claude Code components\n");
97
+ return `
98
+ ${gradientBanner}
99
+ ${tagline}`;
100
+ }
101
+ function renderWelcome() {
102
+ return `
103
+ ${renderBanner()}
104
+ ${muted("Get started:")}
105
+ tenfold search "typescript" Search for components
106
+ tenfold browse --type=hook Browse by type
107
+ tenfold install <author/name> Install a component
108
+
109
+ ${muted("Learn more:")}
110
+ tenfold --help Show all commands
111
+ https://tenfold.dev/docs Documentation
112
+ `;
113
+ }
114
+
115
+ // src/commands/search.ts
116
+ import { Command } from "commander";
117
+
118
+ // src/utils/errors.ts
119
+ var TenfoldError = class extends Error {
120
+ constructor(message, code) {
121
+ super(message);
122
+ this.code = code;
123
+ this.name = "TenfoldError";
124
+ }
125
+ };
126
+ var ApiError = class _ApiError extends TenfoldError {
127
+ constructor(status, message) {
128
+ super(`API Error (${status}): ${message}`, "API_ERROR");
129
+ this.status = status;
130
+ this.name = "ApiError";
131
+ }
132
+ static fromStatus(status, body) {
133
+ let message;
134
+ switch (status) {
135
+ case 400:
136
+ message = "Bad request. Check your input.";
137
+ break;
138
+ case 401:
139
+ message = "Authentication required. Run 'tenfold login' first.";
140
+ break;
141
+ case 403:
142
+ message = "Permission denied. You may not have access to this resource.";
143
+ break;
144
+ case 404:
145
+ message = "Not found. Check the author/slug spelling.";
146
+ break;
147
+ case 429:
148
+ message = "Rate limited. Please wait a moment and try again.";
149
+ break;
150
+ case 500:
151
+ case 502:
152
+ case 503:
153
+ message = "Server error. Please try again later.";
154
+ break;
155
+ default:
156
+ message = body ?? "An unexpected error occurred.";
157
+ }
158
+ return new _ApiError(status, message);
159
+ }
160
+ };
161
+ var NotFoundError = class extends TenfoldError {
162
+ constructor(resourceType, identifier) {
163
+ super(`${resourceType} not found: ${identifier}`, "NOT_FOUND");
164
+ this.resourceType = resourceType;
165
+ this.identifier = identifier;
166
+ this.name = "NotFoundError";
167
+ }
168
+ };
169
+ var ConflictError = class extends TenfoldError {
170
+ constructor(conflicts) {
171
+ super(`${conflicts.length} conflict(s) detected`, "CONFLICT");
172
+ this.conflicts = conflicts;
173
+ this.name = "ConflictError";
174
+ }
175
+ };
176
+ var AuthRequiredError = class extends TenfoldError {
177
+ constructor(operation) {
178
+ super(
179
+ `Authentication required for: ${operation}. Run 'tenfold login' first.`,
180
+ "AUTH_REQUIRED"
181
+ );
182
+ this.operation = operation;
183
+ this.name = "AuthRequiredError";
184
+ }
185
+ };
186
+ var ConfigError = class extends TenfoldError {
187
+ constructor(filePath, message) {
188
+ super(`Config error in ${filePath}: ${message}`, "CONFIG_ERROR");
189
+ this.filePath = filePath;
190
+ this.name = "ConfigError";
191
+ }
192
+ };
193
+ var ValidationError = class extends TenfoldError {
194
+ constructor(message) {
195
+ super(message, "VALIDATION_ERROR");
196
+ this.name = "ValidationError";
197
+ }
198
+ };
199
+ var NetworkError = class extends TenfoldError {
200
+ constructor(message) {
201
+ super(message, "NETWORK_ERROR");
202
+ this.name = "NetworkError";
203
+ }
204
+ };
205
+ function formatError(error) {
206
+ if (error instanceof ApiError) {
207
+ return error.message;
208
+ }
209
+ if (error instanceof NotFoundError) {
210
+ return error.message;
211
+ }
212
+ if (error instanceof ConflictError) {
213
+ const conflicts = error.conflicts.map(
214
+ (c) => ` - ${c.type}: ${c.existing.source} vs ${c.incoming.source}`
215
+ ).join("\n");
216
+ return `Conflicts detected:
217
+ ${conflicts}
218
+ Use --force to override.`;
219
+ }
220
+ if (error instanceof AuthRequiredError) {
221
+ return error.message;
222
+ }
223
+ if (error instanceof ConfigError) {
224
+ return error.message;
225
+ }
226
+ if (error instanceof ValidationError) {
227
+ return error.message;
228
+ }
229
+ if (error instanceof NetworkError) {
230
+ return `Network error: ${error.message}. Check your internet connection.`;
231
+ }
232
+ if (error instanceof TenfoldError) {
233
+ return error.message;
234
+ }
235
+ if (error instanceof Error) {
236
+ return error.message;
237
+ }
238
+ return "An unexpected error occurred.";
239
+ }
240
+ function isRetryable(error) {
241
+ if (error instanceof ApiError) {
242
+ return [429, 500, 502, 503, 504].includes(error.status);
243
+ }
244
+ if (error instanceof NetworkError) {
245
+ return true;
246
+ }
247
+ return false;
248
+ }
249
+
250
+ // src/api/client.ts
251
+ var DEFAULT_BASE_URL = process.env.TENFOLD_API_URL ?? "http://localhost:3000";
252
+ var DEFAULT_TIMEOUT = 3e4;
253
+ var MAX_RETRIES = 3;
254
+ var RETRY_DELAY = 1e3;
255
+ var TenfoldApiClient = class {
256
+ baseUrl;
257
+ apiKey;
258
+ timeout;
259
+ constructor(options = {}) {
260
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
261
+ this.apiKey = options.apiKey;
262
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
263
+ }
264
+ // Set API key (for authenticated operations)
265
+ setApiKey(key) {
266
+ this.apiKey = key;
267
+ }
268
+ // Core request method
269
+ async request(router, procedure, input2, options = {}) {
270
+ const isQuery = options.method !== "POST";
271
+ const url = new URL(`/api/trpc/${router}.${procedure}`, this.baseUrl);
272
+ if (isQuery && input2 !== void 0) {
273
+ url.searchParams.set("input", JSON.stringify({ json: input2 }));
274
+ }
275
+ const headers = {
276
+ "Content-Type": "application/json",
277
+ "User-Agent": "tenfold-cli/0.1.0"
278
+ };
279
+ if (this.apiKey) {
280
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
281
+ }
282
+ const controller = new AbortController();
283
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
284
+ try {
285
+ const response = await fetch(url.toString(), {
286
+ method: isQuery ? "GET" : "POST",
287
+ headers,
288
+ signal: controller.signal,
289
+ ...input2 !== void 0 && !isQuery && { body: JSON.stringify({ json: input2 }) }
290
+ });
291
+ clearTimeout(timeoutId);
292
+ if (!response.ok) {
293
+ const body = await response.text().catch(() => "");
294
+ if (options.retry !== false && isRetryable(ApiError.fromStatus(response.status))) {
295
+ return this.retryRequest(router, procedure, input2, options);
296
+ }
297
+ throw ApiError.fromStatus(response.status, body);
298
+ }
299
+ const data = await response.json();
300
+ if (data.result?.data?.json !== void 0) {
301
+ return data.result.data.json;
302
+ }
303
+ return data;
304
+ } catch (error) {
305
+ clearTimeout(timeoutId);
306
+ if (error instanceof ApiError) {
307
+ throw error;
308
+ }
309
+ if (error instanceof Error) {
310
+ if (error.name === "AbortError") {
311
+ throw new NetworkError("Request timed out");
312
+ }
313
+ throw new NetworkError(error.message);
314
+ }
315
+ throw new NetworkError("Unknown network error");
316
+ }
317
+ }
318
+ // Retry with exponential backoff
319
+ async retryRequest(router, procedure, input2, options = {}, attempt = 1) {
320
+ if (attempt > MAX_RETRIES) {
321
+ throw new NetworkError(`Failed after ${MAX_RETRIES} retries`);
322
+ }
323
+ await new Promise(
324
+ (resolve) => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1))
325
+ );
326
+ return this.request(router, procedure, input2, {
327
+ ...options,
328
+ retry: attempt < MAX_RETRIES
329
+ });
330
+ }
331
+ // ============================================
332
+ // Component Operations
333
+ // ============================================
334
+ async searchComponents(query, limit = 10) {
335
+ const result = await this.request(
336
+ "component",
337
+ "search",
338
+ { query, limit }
339
+ );
340
+ return result ?? [];
341
+ }
342
+ async getComponent(slug) {
343
+ const result = await this.request(
344
+ "component",
345
+ "get",
346
+ { slug }
347
+ );
348
+ if (!result) {
349
+ throw new NotFoundError("Component", slug);
350
+ }
351
+ return result;
352
+ }
353
+ async getComponentVersion(componentId, version) {
354
+ const result = await this.request(
355
+ "component",
356
+ "getVersion",
357
+ { componentId, version }
358
+ );
359
+ if (!result) {
360
+ throw new NotFoundError("Component version", `${componentId}@${version}`);
361
+ }
362
+ return result;
363
+ }
364
+ async getAttribution(versionId) {
365
+ return this.request("component", "getAttribution", {
366
+ versionId
367
+ });
368
+ }
369
+ async listComponents(input2 = {}) {
370
+ return this.request("component", "list", input2);
371
+ }
372
+ async getFeaturedComponents(limit = 10) {
373
+ const result = await this.request(
374
+ "component",
375
+ "featured",
376
+ { limit }
377
+ );
378
+ return result ?? [];
379
+ }
380
+ async getTrendingComponents(limit = 10) {
381
+ const result = await this.request(
382
+ "component",
383
+ "trending",
384
+ { limit }
385
+ );
386
+ return result ?? [];
387
+ }
388
+ // ============================================
389
+ // Bundle Operations
390
+ // ============================================
391
+ async searchBundles(query, limit = 10) {
392
+ const result = await this.request(
393
+ "bundle",
394
+ "search",
395
+ { query, limit }
396
+ );
397
+ return result ?? [];
398
+ }
399
+ async getBundle(slug) {
400
+ const result = await this.request("bundle", "get", {
401
+ slug
402
+ });
403
+ if (!result) {
404
+ throw new NotFoundError("Bundle", slug);
405
+ }
406
+ return result;
407
+ }
408
+ async listBundles(input2 = {}) {
409
+ return this.request("bundle", "list", input2);
410
+ }
411
+ // ============================================
412
+ // Category Operations
413
+ // ============================================
414
+ async listCategories() {
415
+ const result = await this.request(
416
+ "category",
417
+ "list",
418
+ {}
419
+ );
420
+ return result ?? [];
421
+ }
422
+ // ============================================
423
+ // Installation Tracking
424
+ // ============================================
425
+ async trackComponentInstall(componentSlug, versionId) {
426
+ await this.request(
427
+ "componentInstall",
428
+ "trackComponent",
429
+ { componentSlug, versionId },
430
+ { method: "POST" }
431
+ );
432
+ }
433
+ async trackBundleInstall(bundleSlug) {
434
+ await this.request(
435
+ "componentInstall",
436
+ "trackBundle",
437
+ { bundleSlug },
438
+ { method: "POST" }
439
+ );
440
+ }
441
+ // ============================================
442
+ // Authenticated Operations
443
+ // ============================================
444
+ async forkComponent(input2) {
445
+ if (!this.apiKey) {
446
+ throw new ApiError(401, "Authentication required for fork operation");
447
+ }
448
+ return this.request("component", "fork", input2, {
449
+ method: "POST"
450
+ });
451
+ }
452
+ };
453
+ var _client = null;
454
+ function getApiClient(options) {
455
+ if (!_client || options) {
456
+ _client = new TenfoldApiClient(options);
457
+ }
458
+ return _client;
459
+ }
460
+
461
+ // src/ui/spinner.ts
462
+ import ora from "ora";
463
+ function createSpinner(text, options) {
464
+ return ora({
465
+ text,
466
+ color: options?.color ?? "cyan",
467
+ spinner: "dots"
468
+ });
469
+ }
470
+ function startSpinner(text, options) {
471
+ const spinner = createSpinner(text, options);
472
+ return spinner.start();
473
+ }
474
+
475
+ // src/ui/table.ts
476
+ import Table from "cli-table3";
477
+ var tableStyle = {
478
+ head: ["cyan"],
479
+ border: ["dim"]
480
+ };
481
+ var tableChars = {
482
+ top: "\u2500",
483
+ "top-mid": "\u252C",
484
+ "top-left": "\u250C",
485
+ "top-right": "\u2510",
486
+ bottom: "\u2500",
487
+ "bottom-mid": "\u2534",
488
+ "bottom-left": "\u2514",
489
+ "bottom-right": "\u2518",
490
+ left: "\u2502",
491
+ "left-mid": "\u251C",
492
+ mid: "\u2500",
493
+ "mid-mid": "\u253C",
494
+ right: "\u2502",
495
+ "right-mid": "\u2524",
496
+ middle: "\u2502"
497
+ };
498
+ function searchResultsTable(components) {
499
+ const table = new Table({
500
+ head: ["", "Component", "Type", "Description", "Installs"],
501
+ style: tableStyle,
502
+ chars: tableChars,
503
+ colWidths: [3, 26, 13, 35, 10],
504
+ wordWrap: true
505
+ });
506
+ for (const component of components) {
507
+ const icon = typeIcons[component.type] || "\u{1F4E6}";
508
+ const typeColor = typeColors[component.type] || colors.muted;
509
+ const [author, name] = component.slug.split("/");
510
+ table.push([
511
+ icon,
512
+ `${muted(author + "/")}${highlight(name || "")}`,
513
+ typeColor(component.type),
514
+ component.description.slice(0, 50) + (component.description.length > 50 ? "..." : ""),
515
+ formatInstalls(component.installCount)
516
+ ]);
517
+ }
518
+ return table.toString();
519
+ }
520
+ function browseResultsTable(components) {
521
+ const table = new Table({
522
+ head: ["Component", "Author", "Description", "Installs"],
523
+ style: tableStyle,
524
+ chars: tableChars,
525
+ colWidths: [25, 15, 40, 10],
526
+ wordWrap: true
527
+ });
528
+ for (const component of components) {
529
+ table.push([
530
+ highlight(component.slug.split("/")[1] || component.slug),
531
+ muted(`@${component.author.name}`),
532
+ component.description.slice(0, 55) + (component.description.length > 55 ? "..." : ""),
533
+ formatInstalls(component.installCount)
534
+ ]);
535
+ }
536
+ return table.toString();
537
+ }
538
+ function installedTable(components) {
539
+ const table = new Table({
540
+ head: ["Component", "Type", "Version", "Installed"],
541
+ style: tableStyle,
542
+ chars: tableChars,
543
+ colWidths: [30, 13, 12, 15]
544
+ });
545
+ for (const component of components) {
546
+ const icon = typeIcons[component.type] || "\u{1F4E6}";
547
+ const typeColor = typeColors[component.type] || colors.muted;
548
+ table.push([
549
+ `${icon} ${highlight(component.slug)}`,
550
+ typeColor(component.type),
551
+ muted(`v${component.version}`),
552
+ muted(formatRelativeTime(component.installedAt))
553
+ ]);
554
+ }
555
+ return table.toString();
556
+ }
557
+ function bundleItemsTable(bundle) {
558
+ const table = new Table({
559
+ head: ["", "Component", "Type", "Author"],
560
+ style: tableStyle,
561
+ chars: tableChars,
562
+ colWidths: [3, 30, 13, 15]
563
+ });
564
+ for (const item of bundle.items) {
565
+ const component = item.componentVersion.component;
566
+ const icon = typeIcons[component.type] || "\u{1F4E6}";
567
+ const typeColor = typeColors[component.type] || colors.muted;
568
+ table.push([
569
+ icon,
570
+ highlight(component.slug),
571
+ typeColor(component.type),
572
+ muted(`@${component.author.name}`)
573
+ ]);
574
+ }
575
+ return table.toString();
576
+ }
577
+ function installResultTable(results) {
578
+ const table = new Table({
579
+ head: ["Component", "Type", "Status"],
580
+ style: tableStyle,
581
+ chars: tableChars,
582
+ colWidths: [30, 13, 12]
583
+ });
584
+ for (const result of results) {
585
+ const icon = typeIcons[result.type] || "\u{1F4E6}";
586
+ const typeColor = typeColors[result.type] || colors.muted;
587
+ const statusIcon = result.status === "Added" ? colors.success("\u2714") : result.status === "Skipped" ? colors.warning("\u25CB") : colors.error("\u2718");
588
+ table.push([
589
+ `${icon} ${highlight(result.component)}`,
590
+ typeColor(result.type),
591
+ `${statusIcon} ${result.status}`
592
+ ]);
593
+ }
594
+ return table.toString();
595
+ }
596
+ function attributionTable(attributions) {
597
+ const table = new Table({
598
+ style: tableStyle,
599
+ chars: tableChars,
600
+ colWidths: [20, 10]
601
+ });
602
+ for (const attr of attributions) {
603
+ table.push([muted(`@${attr.authorName}`), `${attr.percentage.toFixed(0)}%`]);
604
+ }
605
+ return table.toString();
606
+ }
607
+ function formatRelativeTime(date) {
608
+ const d = new Date(date);
609
+ const now = /* @__PURE__ */ new Date();
610
+ const diffMs = now.getTime() - d.getTime();
611
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
612
+ if (diffDays === 0) return "today";
613
+ if (diffDays === 1) return "yesterday";
614
+ if (diffDays < 7) return `${diffDays}d ago`;
615
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`;
616
+ if (diffDays < 365) return `${Math.floor(diffDays / 30)}mo ago`;
617
+ return `${Math.floor(diffDays / 365)}y ago`;
618
+ }
619
+
620
+ // src/commands/search.ts
621
+ function createSearchCommand() {
622
+ return new Command("search").description("Search for components").argument("<query>", "Search query").option("-t, --type <type>", "Filter by type (hook, skill, mcp_server, etc.)").option("-l, --limit <n>", "Number of results", "10").action(async (query, options) => {
623
+ const api = getApiClient();
624
+ const spinner = startSpinner(`Searching for "${query}"...`);
625
+ try {
626
+ const limit = parseInt(options.limit, 10);
627
+ const results = await api.searchComponents(query, limit);
628
+ spinner.stop();
629
+ if (results.length === 0) {
630
+ console.log(muted("\n No results found.\n"));
631
+ console.log(muted(" Try a different search term or browse by type:"));
632
+ console.log(muted(" tenfold browse --type=hook\n"));
633
+ return;
634
+ }
635
+ console.log(bold(`
636
+ \u{1F50D} Results for "${query}"
637
+ `));
638
+ console.log(searchResultsTable(results));
639
+ console.log();
640
+ if (results.length >= limit) {
641
+ console.log(
642
+ muted(` Showing first ${limit} results. Use --limit to see more.
643
+ `)
644
+ );
645
+ }
646
+ } catch (error) {
647
+ spinner.fail("Search failed");
648
+ console.error(colors.error(`
649
+ ${formatError(error)}
650
+ `));
651
+ process.exit(1);
652
+ }
653
+ });
654
+ }
655
+
656
+ // src/commands/browse.ts
657
+ import { Command as Command2 } from "commander";
658
+ var VALID_TYPES = [
659
+ "instruction",
660
+ "hook",
661
+ "command",
662
+ "mcp_server",
663
+ "skill",
664
+ "agent"
665
+ ];
666
+ function createBrowseCommand() {
667
+ return new Command2("browse").description("Browse components by type or category").option("-t, --type <type>", "Filter by type (hook, skill, mcp_server, etc.)").option("-c, --category <category>", "Filter by category").option("-l, --limit <n>", "Number of results", "20").option("--featured", "Show only featured components").action(
668
+ async (options) => {
669
+ const api = getApiClient();
670
+ if (options.type) {
671
+ const normalizedType = options.type.toLowerCase().replace("-", "_");
672
+ if (!VALID_TYPES.includes(normalizedType)) {
673
+ console.error(
674
+ colors.error(`
675
+ Invalid type: ${options.type}
676
+ `)
677
+ );
678
+ console.log(muted(" Valid types:"));
679
+ for (const t of VALID_TYPES) {
680
+ const icon = typeIcons[t.toUpperCase()] || "\u{1F4E6}";
681
+ console.log(muted(` ${icon} ${t}`));
682
+ }
683
+ console.log();
684
+ process.exit(1);
685
+ }
686
+ }
687
+ const spinner = startSpinner("Loading components...");
688
+ try {
689
+ const limit = parseInt(options.limit, 10);
690
+ const typeFilter = options.type?.toUpperCase().replace("-", "_");
691
+ const result = await api.listComponents({
692
+ limit,
693
+ type: typeFilter,
694
+ categorySlug: options.category,
695
+ featured: options.featured
696
+ });
697
+ spinner.stop();
698
+ if (result.components.length === 0) {
699
+ console.log(muted("\n No components found.\n"));
700
+ return;
701
+ }
702
+ let header = "\n ";
703
+ if (options.type) {
704
+ const icon = typeIcons[typeFilter || ""] || "\u{1F4E6}";
705
+ header += `${icon} ${bold(options.type.toUpperCase())} Components`;
706
+ } else if (options.category) {
707
+ header += `\u{1F4C1} ${bold(options.category)} Category`;
708
+ } else if (options.featured) {
709
+ header += `\u2B50 ${bold("Featured")} Components`;
710
+ } else {
711
+ header += bold("All Components");
712
+ }
713
+ console.log(header + "\n");
714
+ console.log(browseResultsTable(result.components));
715
+ console.log();
716
+ if (result.hasMore) {
717
+ console.log(
718
+ muted(
719
+ ` Showing ${result.components.length} of ${result.total}. Use --limit to see more.
720
+ `
721
+ )
722
+ );
723
+ }
724
+ } catch (error) {
725
+ spinner.fail("Failed to load components");
726
+ console.error(colors.error(`
727
+ ${formatError(error)}
728
+ `));
729
+ process.exit(1);
730
+ }
731
+ }
732
+ );
733
+ }
734
+
735
+ // src/commands/install.ts
736
+ import { Command as Command3 } from "commander";
737
+ import { Listr } from "listr2";
738
+
739
+ // src/config/installed.ts
740
+ import { readFile, writeFile, mkdir } from "fs/promises";
741
+ import { existsSync } from "fs";
742
+ import { dirname } from "path";
743
+
744
+ // src/types/config.ts
745
+ import { z } from "zod";
746
+ var TenfoldMetadataSchema = z.object({
747
+ componentId: z.string(),
748
+ versionId: z.string(),
749
+ version: z.string(),
750
+ author: z.string(),
751
+ slug: z.string(),
752
+ installedAt: z.string()
753
+ });
754
+ var TargetFileSchema = z.object({
755
+ type: z.enum([
756
+ "claude_md",
757
+ "settings_json",
758
+ "mcp_json",
759
+ "skill_dir",
760
+ "command_file"
761
+ ]),
762
+ path: z.string(),
763
+ section: z.string().optional(),
764
+ key: z.string().optional()
765
+ });
766
+ var InstalledComponentSchema = z.object({
767
+ componentId: z.string(),
768
+ versionId: z.string(),
769
+ author: z.string(),
770
+ slug: z.string(),
771
+ type: z.enum([
772
+ "INSTRUCTION",
773
+ "HOOK",
774
+ "COMMAND",
775
+ "MCP_SERVER",
776
+ "SKILL",
777
+ "AGENT"
778
+ ]),
779
+ version: z.string(),
780
+ installedAt: z.string(),
781
+ updatedAt: z.string().optional(),
782
+ targetFiles: z.array(TargetFileSchema),
783
+ attribution: z.array(
784
+ z.object({
785
+ authorId: z.string(),
786
+ authorName: z.string(),
787
+ percentage: z.number()
788
+ })
789
+ ).optional()
790
+ });
791
+ var InstalledManifestSchema = z.object({
792
+ version: z.literal("1.0.0"),
793
+ components: z.array(InstalledComponentSchema),
794
+ lastUpdatedAt: z.string()
795
+ });
796
+ var HookDefinitionSchema = z.object({
797
+ type: z.literal("command"),
798
+ command: z.string()
799
+ });
800
+ var HookEntrySchema = z.object({
801
+ matcher: z.string(),
802
+ hooks: z.array(HookDefinitionSchema),
803
+ _tenfold: TenfoldMetadataSchema.optional()
804
+ });
805
+ var ClaudeSettingsSchema = z.object({
806
+ hooks: z.object({
807
+ PreToolUse: z.array(HookEntrySchema).optional(),
808
+ PostToolUse: z.array(HookEntrySchema).optional(),
809
+ Stop: z.array(HookEntrySchema).optional(),
810
+ Start: z.array(HookEntrySchema).optional(),
811
+ Subagent: z.array(HookEntrySchema).optional()
812
+ }).optional()
813
+ });
814
+ var McpServerEntrySchema = z.object({
815
+ command: z.string(),
816
+ args: z.array(z.string()).optional(),
817
+ env: z.record(z.string()).optional(),
818
+ _tenfold: TenfoldMetadataSchema.optional()
819
+ });
820
+ var McpConfigSchema = z.object({
821
+ mcpServers: z.record(McpServerEntrySchema).optional()
822
+ });
823
+ var CredentialsSchema = z.object({
824
+ apiKey: z.string().optional(),
825
+ userId: z.string().optional(),
826
+ createdAt: z.string().optional()
827
+ });
828
+ function createEmptyManifest() {
829
+ return {
830
+ version: "1.0.0",
831
+ components: [],
832
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
833
+ };
834
+ }
835
+
836
+ // src/utils/paths.ts
837
+ import { homedir } from "os";
838
+ import { join } from "path";
839
+ function getHomeDir() {
840
+ return homedir();
841
+ }
842
+ function getClaudeDir() {
843
+ return join(getHomeDir(), ".claude");
844
+ }
845
+ function getTenfoldDir() {
846
+ return join(getHomeDir(), ".tenfold");
847
+ }
848
+ function getClaudeMdPath() {
849
+ return join(getClaudeDir(), "CLAUDE.md");
850
+ }
851
+ function getSettingsPath() {
852
+ return join(getClaudeDir(), "settings.json");
853
+ }
854
+ function getCommandsDir() {
855
+ return join(getClaudeDir(), "commands");
856
+ }
857
+ function getPluginsDir() {
858
+ return join(getClaudeDir(), "plugins");
859
+ }
860
+ function getMcpConfigPath() {
861
+ return join(getHomeDir(), ".mcp.json");
862
+ }
863
+ function getInstalledManifestPath() {
864
+ return join(getTenfoldDir(), "installed.json");
865
+ }
866
+ function isValidSlug(slug) {
867
+ const slugRegex = /^[a-z0-9_-]+\/[a-z0-9_-]+$/i;
868
+ return slugRegex.test(slug);
869
+ }
870
+
871
+ // src/config/installed.ts
872
+ var InstalledManager = class {
873
+ manifestPath;
874
+ manifest = null;
875
+ constructor(manifestPath) {
876
+ this.manifestPath = manifestPath ?? getInstalledManifestPath();
877
+ }
878
+ // Load manifest from disk
879
+ async load() {
880
+ if (this.manifest) {
881
+ return this.manifest;
882
+ }
883
+ if (!existsSync(this.manifestPath)) {
884
+ this.manifest = createEmptyManifest();
885
+ return this.manifest;
886
+ }
887
+ try {
888
+ const content = await readFile(this.manifestPath, "utf-8");
889
+ const parsed = JSON.parse(content);
890
+ const validated = InstalledManifestSchema.parse(parsed);
891
+ this.manifest = validated;
892
+ return validated;
893
+ } catch (error) {
894
+ if (error instanceof SyntaxError) {
895
+ throw new ConfigError(this.manifestPath, "Invalid JSON");
896
+ }
897
+ throw new ConfigError(
898
+ this.manifestPath,
899
+ error instanceof Error ? error.message : "Unknown error"
900
+ );
901
+ }
902
+ }
903
+ // Save manifest to disk
904
+ async save() {
905
+ if (!this.manifest) {
906
+ return;
907
+ }
908
+ this.manifest.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
909
+ const dir = dirname(this.manifestPath);
910
+ if (!existsSync(dir)) {
911
+ await mkdir(dir, { recursive: true });
912
+ }
913
+ await writeFile(
914
+ this.manifestPath,
915
+ JSON.stringify(this.manifest, null, 2),
916
+ "utf-8"
917
+ );
918
+ }
919
+ // Get all installed components
920
+ async getAll() {
921
+ const manifest = await this.load();
922
+ return manifest.components;
923
+ }
924
+ // Get installed component by slug
925
+ async get(slug) {
926
+ const manifest = await this.load();
927
+ return manifest.components.find((c) => c.slug === slug) ?? null;
928
+ }
929
+ // Check if component is installed
930
+ async isInstalled(slug) {
931
+ const component = await this.get(slug);
932
+ return component !== null;
933
+ }
934
+ // Get installed version
935
+ async getInstalledVersion(slug) {
936
+ const component = await this.get(slug);
937
+ return component?.version ?? null;
938
+ }
939
+ // Track a new installation
940
+ async track(component, targetFiles, attribution) {
941
+ const manifest = await this.load();
942
+ manifest.components = manifest.components.filter(
943
+ (c) => c.slug !== component.slug
944
+ );
945
+ manifest.components.push({
946
+ componentId: component.componentId,
947
+ versionId: component.versionId,
948
+ author: component.author,
949
+ slug: component.slug,
950
+ type: component.type,
951
+ version: component.version,
952
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
953
+ targetFiles,
954
+ attribution: attribution?.map((a) => ({
955
+ authorId: a.authorId,
956
+ authorName: a.authorName,
957
+ percentage: a.percentage
958
+ }))
959
+ });
960
+ await this.save();
961
+ }
962
+ // Update an existing installation
963
+ async update(slug, newVersion, targetFiles) {
964
+ const manifest = await this.load();
965
+ const index = manifest.components.findIndex((c) => c.slug === slug);
966
+ if (index === -1) {
967
+ throw new ConfigError(this.manifestPath, `Component not installed: ${slug}`);
968
+ }
969
+ manifest.components[index] = {
970
+ ...manifest.components[index],
971
+ versionId: newVersion.versionId,
972
+ version: newVersion.version,
973
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
974
+ targetFiles
975
+ };
976
+ await this.save();
977
+ }
978
+ // Remove an installation
979
+ async remove(slug) {
980
+ const manifest = await this.load();
981
+ const index = manifest.components.findIndex((c) => c.slug === slug);
982
+ if (index === -1) {
983
+ return null;
984
+ }
985
+ const [removed] = manifest.components.splice(index, 1);
986
+ await this.save();
987
+ return removed ?? null;
988
+ }
989
+ // Get components by type
990
+ async getByType(type) {
991
+ const manifest = await this.load();
992
+ return manifest.components.filter((c) => c.type === type);
993
+ }
994
+ // Get components that need updates (compare with latest versions)
995
+ async getOutdated(latestVersions) {
996
+ const manifest = await this.load();
997
+ return manifest.components.filter((c) => {
998
+ const latest = latestVersions.get(c.slug);
999
+ return latest && latest !== c.version;
1000
+ });
1001
+ }
1002
+ // Clear all installed components (for testing)
1003
+ async clear() {
1004
+ this.manifest = createEmptyManifest();
1005
+ await this.save();
1006
+ }
1007
+ };
1008
+ var _manager = null;
1009
+ function getInstalledManager() {
1010
+ if (!_manager) {
1011
+ _manager = new InstalledManager();
1012
+ }
1013
+ return _manager;
1014
+ }
1015
+
1016
+ // src/config/settings.ts
1017
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1018
+ import { existsSync as existsSync2 } from "fs";
1019
+ import { dirname as dirname2 } from "path";
1020
+ var SettingsManager = class {
1021
+ settingsPath;
1022
+ settings = null;
1023
+ constructor(settingsPath) {
1024
+ this.settingsPath = settingsPath ?? getSettingsPath();
1025
+ }
1026
+ // Load settings from disk
1027
+ async load() {
1028
+ if (this.settings) {
1029
+ return this.settings;
1030
+ }
1031
+ if (!existsSync2(this.settingsPath)) {
1032
+ this.settings = { hooks: {} };
1033
+ return this.settings;
1034
+ }
1035
+ try {
1036
+ const content = await readFile2(this.settingsPath, "utf-8");
1037
+ if (!content.trim()) {
1038
+ this.settings = { hooks: {} };
1039
+ return this.settings;
1040
+ }
1041
+ this.settings = JSON.parse(content);
1042
+ return this.settings;
1043
+ } catch (error) {
1044
+ if (error instanceof SyntaxError) {
1045
+ throw new ConfigError(this.settingsPath, "Invalid JSON");
1046
+ }
1047
+ throw new ConfigError(
1048
+ this.settingsPath,
1049
+ error instanceof Error ? error.message : "Unknown error"
1050
+ );
1051
+ }
1052
+ }
1053
+ // Save settings to disk
1054
+ async save() {
1055
+ if (!this.settings) {
1056
+ return;
1057
+ }
1058
+ const dir = dirname2(this.settingsPath);
1059
+ if (!existsSync2(dir)) {
1060
+ await mkdir2(dir, { recursive: true });
1061
+ }
1062
+ await writeFile2(
1063
+ this.settingsPath,
1064
+ JSON.stringify(this.settings, null, 2),
1065
+ "utf-8"
1066
+ );
1067
+ }
1068
+ // Install a hook
1069
+ async installHook(hookContent, metadata) {
1070
+ const settings = await this.load();
1071
+ if (!settings.hooks) {
1072
+ settings.hooks = {};
1073
+ }
1074
+ const event = hookContent.event;
1075
+ if (!settings.hooks[event]) {
1076
+ settings.hooks[event] = [];
1077
+ }
1078
+ const hookEntry = {
1079
+ matcher: hookContent.matcher || "*",
1080
+ hooks: [
1081
+ {
1082
+ type: "command",
1083
+ command: `tenfold run-hook ${metadata.slug}`
1084
+ }
1085
+ ],
1086
+ _tenfold: metadata
1087
+ };
1088
+ settings.hooks[event].push(hookEntry);
1089
+ await this.save();
1090
+ return {
1091
+ type: "settings_json",
1092
+ path: this.settingsPath,
1093
+ key: `hooks.${event}`
1094
+ };
1095
+ }
1096
+ // Uninstall a hook by slug
1097
+ async uninstallHook(slug) {
1098
+ const settings = await this.load();
1099
+ if (!settings.hooks) {
1100
+ return false;
1101
+ }
1102
+ let removed = false;
1103
+ for (const event of Object.keys(settings.hooks)) {
1104
+ const hooks = settings.hooks[event];
1105
+ if (!hooks) continue;
1106
+ const initialLength = hooks.length;
1107
+ settings.hooks[event] = hooks.filter(
1108
+ (h) => h._tenfold?.slug !== slug
1109
+ );
1110
+ if (settings.hooks[event].length < initialLength) {
1111
+ removed = true;
1112
+ }
1113
+ if (settings.hooks[event].length === 0) {
1114
+ delete settings.hooks[event];
1115
+ }
1116
+ }
1117
+ if (removed) {
1118
+ await this.save();
1119
+ }
1120
+ return removed;
1121
+ }
1122
+ // Check if a hook is installed
1123
+ async isHookInstalled(slug) {
1124
+ const settings = await this.load();
1125
+ if (!settings.hooks) {
1126
+ return false;
1127
+ }
1128
+ for (const event of Object.keys(settings.hooks)) {
1129
+ const hooks = settings.hooks[event];
1130
+ if (!hooks) continue;
1131
+ if (hooks.some((h) => h._tenfold?.slug === slug)) {
1132
+ return true;
1133
+ }
1134
+ }
1135
+ return false;
1136
+ }
1137
+ // Get all installed hooks with tenfold metadata
1138
+ async getInstalledHooks() {
1139
+ const settings = await this.load();
1140
+ const result = [];
1141
+ if (!settings.hooks) {
1142
+ return result;
1143
+ }
1144
+ for (const event of Object.keys(settings.hooks)) {
1145
+ const hooks = settings.hooks[event];
1146
+ if (!hooks) continue;
1147
+ for (const entry of hooks) {
1148
+ if (entry._tenfold) {
1149
+ result.push({ event, entry });
1150
+ }
1151
+ }
1152
+ }
1153
+ return result;
1154
+ }
1155
+ // Check for hook conflicts (same event + matcher)
1156
+ async detectConflict(hookContent) {
1157
+ const settings = await this.load();
1158
+ if (!settings.hooks) {
1159
+ return null;
1160
+ }
1161
+ const event = hookContent.event;
1162
+ const hooks = settings.hooks[event];
1163
+ if (!hooks) {
1164
+ return null;
1165
+ }
1166
+ const matcher = hookContent.matcher || "*";
1167
+ for (const entry of hooks) {
1168
+ if (entry.matcher === matcher && entry._tenfold) {
1169
+ return entry;
1170
+ }
1171
+ }
1172
+ return null;
1173
+ }
1174
+ // Replace an existing hook
1175
+ async replaceHook(oldSlug, hookContent, metadata) {
1176
+ await this.uninstallHook(oldSlug);
1177
+ return this.installHook(hookContent, metadata);
1178
+ }
1179
+ // Get hook definition for run-hook command
1180
+ async getHookDefinition(slug) {
1181
+ const settings = await this.load();
1182
+ if (!settings.hooks) {
1183
+ return null;
1184
+ }
1185
+ for (const event of Object.keys(settings.hooks)) {
1186
+ const hooks = settings.hooks[event];
1187
+ if (!hooks) continue;
1188
+ const entry = hooks.find((h) => h._tenfold?.slug === slug);
1189
+ if (entry && entry._tenfold) {
1190
+ return {
1191
+ name: slug.split("/")[1] || slug,
1192
+ event,
1193
+ matcher: entry.matcher,
1194
+ command: entry.hooks[0]?.command || ""
1195
+ };
1196
+ }
1197
+ }
1198
+ return null;
1199
+ }
1200
+ };
1201
+ var _manager2 = null;
1202
+ function getSettingsManager() {
1203
+ if (!_manager2) {
1204
+ _manager2 = new SettingsManager();
1205
+ }
1206
+ return _manager2;
1207
+ }
1208
+
1209
+ // src/config/mcp.ts
1210
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1211
+ import { existsSync as existsSync3 } from "fs";
1212
+ var McpManager = class {
1213
+ configPath;
1214
+ config = null;
1215
+ constructor(configPath) {
1216
+ this.configPath = configPath ?? getMcpConfigPath();
1217
+ }
1218
+ // Load config from disk
1219
+ async load() {
1220
+ if (this.config) {
1221
+ return this.config;
1222
+ }
1223
+ if (!existsSync3(this.configPath)) {
1224
+ this.config = { mcpServers: {} };
1225
+ return this.config;
1226
+ }
1227
+ try {
1228
+ const content = await readFile3(this.configPath, "utf-8");
1229
+ if (!content.trim()) {
1230
+ this.config = { mcpServers: {} };
1231
+ return this.config;
1232
+ }
1233
+ this.config = JSON.parse(content);
1234
+ return this.config;
1235
+ } catch (error) {
1236
+ if (error instanceof SyntaxError) {
1237
+ throw new ConfigError(this.configPath, "Invalid JSON");
1238
+ }
1239
+ throw new ConfigError(
1240
+ this.configPath,
1241
+ error instanceof Error ? error.message : "Unknown error"
1242
+ );
1243
+ }
1244
+ }
1245
+ // Save config to disk
1246
+ async save() {
1247
+ if (!this.config) {
1248
+ return;
1249
+ }
1250
+ await writeFile3(
1251
+ this.configPath,
1252
+ JSON.stringify(this.config, null, 2),
1253
+ "utf-8"
1254
+ );
1255
+ }
1256
+ // Install an MCP server
1257
+ async installServer(serverContent, metadata) {
1258
+ const config = await this.load();
1259
+ if (!config.mcpServers) {
1260
+ config.mcpServers = {};
1261
+ }
1262
+ const serverEntry = {
1263
+ command: serverContent.command,
1264
+ args: serverContent.args,
1265
+ env: serverContent.env,
1266
+ _tenfold: metadata
1267
+ };
1268
+ config.mcpServers[serverContent.name] = serverEntry;
1269
+ await this.save();
1270
+ return {
1271
+ type: "mcp_json",
1272
+ path: this.configPath,
1273
+ key: `mcpServers.${serverContent.name}`
1274
+ };
1275
+ }
1276
+ // Uninstall an MCP server by name
1277
+ async uninstallServer(name) {
1278
+ const config = await this.load();
1279
+ if (!config.mcpServers || !config.mcpServers[name]) {
1280
+ return false;
1281
+ }
1282
+ delete config.mcpServers[name];
1283
+ await this.save();
1284
+ return true;
1285
+ }
1286
+ // Uninstall by slug
1287
+ async uninstallBySlug(slug) {
1288
+ const config = await this.load();
1289
+ if (!config.mcpServers) {
1290
+ return false;
1291
+ }
1292
+ let removed = false;
1293
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
1294
+ if (entry._tenfold?.slug === slug) {
1295
+ delete config.mcpServers[name];
1296
+ removed = true;
1297
+ }
1298
+ }
1299
+ if (removed) {
1300
+ await this.save();
1301
+ }
1302
+ return removed;
1303
+ }
1304
+ // Check if a server name exists
1305
+ async hasServer(name) {
1306
+ const config = await this.load();
1307
+ return !!(config.mcpServers && config.mcpServers[name]);
1308
+ }
1309
+ // Check if a slug is installed
1310
+ async isInstalled(slug) {
1311
+ const config = await this.load();
1312
+ if (!config.mcpServers) {
1313
+ return false;
1314
+ }
1315
+ return Object.values(config.mcpServers).some(
1316
+ (entry) => entry._tenfold?.slug === slug
1317
+ );
1318
+ }
1319
+ // Get all installed MCP servers with tenfold metadata
1320
+ async getInstalledServers() {
1321
+ const config = await this.load();
1322
+ const result = [];
1323
+ if (!config.mcpServers) {
1324
+ return result;
1325
+ }
1326
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
1327
+ if (entry._tenfold) {
1328
+ result.push({ name, entry });
1329
+ }
1330
+ }
1331
+ return result;
1332
+ }
1333
+ // Detect name conflict
1334
+ async detectConflict(serverContent) {
1335
+ const config = await this.load();
1336
+ if (!config.mcpServers) {
1337
+ return null;
1338
+ }
1339
+ const existing = config.mcpServers[serverContent.name];
1340
+ if (existing) {
1341
+ return existing;
1342
+ }
1343
+ return null;
1344
+ }
1345
+ // Replace an existing server
1346
+ async replaceServer(name, serverContent, metadata) {
1347
+ await this.uninstallServer(name);
1348
+ return this.installServer(serverContent, metadata);
1349
+ }
1350
+ // Rename a server (for conflict resolution)
1351
+ async renameServer(oldName, newName) {
1352
+ const config = await this.load();
1353
+ if (!config.mcpServers || !config.mcpServers[oldName]) {
1354
+ return false;
1355
+ }
1356
+ const entry = config.mcpServers[oldName];
1357
+ delete config.mcpServers[oldName];
1358
+ config.mcpServers[newName] = entry;
1359
+ await this.save();
1360
+ return true;
1361
+ }
1362
+ // Get server entry by slug
1363
+ async getBySlug(slug) {
1364
+ const config = await this.load();
1365
+ if (!config.mcpServers) {
1366
+ return null;
1367
+ }
1368
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
1369
+ if (entry._tenfold?.slug === slug) {
1370
+ return { name, entry };
1371
+ }
1372
+ }
1373
+ return null;
1374
+ }
1375
+ };
1376
+ var _manager3 = null;
1377
+ function getMcpManager() {
1378
+ if (!_manager3) {
1379
+ _manager3 = new McpManager();
1380
+ }
1381
+ return _manager3;
1382
+ }
1383
+
1384
+ // src/config/claude-md.ts
1385
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
1386
+ import { existsSync as existsSync4 } from "fs";
1387
+ import { dirname as dirname3 } from "path";
1388
+ var SECTION_START = (slug) => `<!-- tenfold:start ${slug} -->`;
1389
+ var SECTION_END = (slug) => `<!-- tenfold:end ${slug} -->`;
1390
+ var ClaudeMdManager = class {
1391
+ mdPath;
1392
+ content = null;
1393
+ constructor(mdPath) {
1394
+ this.mdPath = mdPath ?? getClaudeMdPath();
1395
+ }
1396
+ // Load content from disk
1397
+ async load() {
1398
+ if (this.content !== null) {
1399
+ return this.content;
1400
+ }
1401
+ if (!existsSync4(this.mdPath)) {
1402
+ this.content = "";
1403
+ return this.content;
1404
+ }
1405
+ try {
1406
+ this.content = await readFile4(this.mdPath, "utf-8");
1407
+ return this.content;
1408
+ } catch (error) {
1409
+ throw new ConfigError(
1410
+ this.mdPath,
1411
+ error instanceof Error ? error.message : "Unknown error"
1412
+ );
1413
+ }
1414
+ }
1415
+ // Save content to disk
1416
+ async save() {
1417
+ if (this.content === null) {
1418
+ return;
1419
+ }
1420
+ const dir = dirname3(this.mdPath);
1421
+ if (!existsSync4(dir)) {
1422
+ await mkdir3(dir, { recursive: true });
1423
+ }
1424
+ await writeFile4(this.mdPath, this.content, "utf-8");
1425
+ }
1426
+ // Install instruction content
1427
+ async install(instructionContent, metadata, strategy = "section") {
1428
+ let content = await this.load();
1429
+ const paragraphs = instructionContent.paragraphs.join("\n\n");
1430
+ const authorName = metadata.author;
1431
+ switch (strategy) {
1432
+ case "replace":
1433
+ this.content = this.wrapWithSection(paragraphs, metadata.slug, authorName);
1434
+ break;
1435
+ case "append":
1436
+ if (content && !content.endsWith("\n\n")) {
1437
+ content += content.endsWith("\n") ? "\n" : "\n\n";
1438
+ }
1439
+ this.content = content + this.wrapWithSection(paragraphs, metadata.slug, authorName);
1440
+ break;
1441
+ case "section":
1442
+ default:
1443
+ content = this.removeSection(content, metadata.slug);
1444
+ if (content && !content.endsWith("\n\n")) {
1445
+ content += content.endsWith("\n") ? "\n" : "\n\n";
1446
+ }
1447
+ this.content = content + this.wrapWithSection(paragraphs, metadata.slug, authorName);
1448
+ break;
1449
+ }
1450
+ await this.save();
1451
+ return {
1452
+ type: "claude_md",
1453
+ path: this.mdPath,
1454
+ section: metadata.slug
1455
+ };
1456
+ }
1457
+ // Uninstall by removing section
1458
+ async uninstall(slug) {
1459
+ const content = await this.load();
1460
+ if (!this.hasSection(content, slug)) {
1461
+ return false;
1462
+ }
1463
+ this.content = this.removeSection(content, slug);
1464
+ await this.save();
1465
+ return true;
1466
+ }
1467
+ // Check if a section exists
1468
+ hasSection(content, slug) {
1469
+ return content.includes(SECTION_START(slug)) && content.includes(SECTION_END(slug));
1470
+ }
1471
+ // Remove a section
1472
+ removeSection(content, slug) {
1473
+ const startMarker = SECTION_START(slug);
1474
+ const endMarker = SECTION_END(slug);
1475
+ const startIdx = content.indexOf(startMarker);
1476
+ if (startIdx === -1) {
1477
+ return content;
1478
+ }
1479
+ const endIdx = content.indexOf(endMarker);
1480
+ if (endIdx === -1) {
1481
+ return content;
1482
+ }
1483
+ const before = content.slice(0, startIdx);
1484
+ let after = content.slice(endIdx + endMarker.length);
1485
+ while (after.startsWith("\n")) {
1486
+ after = after.slice(1);
1487
+ }
1488
+ return before.trimEnd() + (after ? "\n\n" + after : "");
1489
+ }
1490
+ // Wrap content with section markers
1491
+ wrapWithSection(content, slug, authorName) {
1492
+ const displayName = slug.split("/")[1] || slug;
1493
+ return `${SECTION_START(slug)}
1494
+ ## ${this.titleCase(displayName)} (by @${authorName})
1495
+
1496
+ ${content}
1497
+
1498
+ ${SECTION_END(slug)}`;
1499
+ }
1500
+ // Get all installed sections
1501
+ async getInstalledSections() {
1502
+ const content = await this.load();
1503
+ const sections = [];
1504
+ const regex = /<!-- tenfold:start ([^\s]+) -->/g;
1505
+ let match;
1506
+ while ((match = regex.exec(content)) !== null) {
1507
+ sections.push(match[1]);
1508
+ }
1509
+ return sections;
1510
+ }
1511
+ // Check if section exists (for conflict detection)
1512
+ async detectConflict(slug) {
1513
+ const content = await this.load();
1514
+ return this.hasSection(content, slug);
1515
+ }
1516
+ // Get section content
1517
+ async getSectionContent(slug) {
1518
+ const content = await this.load();
1519
+ const startMarker = SECTION_START(slug);
1520
+ const endMarker = SECTION_END(slug);
1521
+ const startIdx = content.indexOf(startMarker);
1522
+ if (startIdx === -1) {
1523
+ return null;
1524
+ }
1525
+ const endIdx = content.indexOf(endMarker);
1526
+ if (endIdx === -1) {
1527
+ return null;
1528
+ }
1529
+ return content.slice(startIdx + startMarker.length, endIdx).trim();
1530
+ }
1531
+ // Helper: title case
1532
+ titleCase(str) {
1533
+ return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1534
+ }
1535
+ };
1536
+ var _manager4 = null;
1537
+ function getClaudeMdManager() {
1538
+ if (!_manager4) {
1539
+ _manager4 = new ClaudeMdManager();
1540
+ }
1541
+ return _manager4;
1542
+ }
1543
+
1544
+ // src/config/commands.ts
1545
+ import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir4, unlink, readdir } from "fs/promises";
1546
+ import { existsSync as existsSync5 } from "fs";
1547
+ import { join as join2, basename } from "path";
1548
+ var FRONTMATTER_START = "---";
1549
+ var TENFOLD_MARKER = "_tenfold:";
1550
+ var CommandsManager = class {
1551
+ commandsDir;
1552
+ constructor(commandsDir) {
1553
+ this.commandsDir = commandsDir ?? getCommandsDir();
1554
+ }
1555
+ // Ensure directory exists
1556
+ async ensureDir() {
1557
+ if (!existsSync5(this.commandsDir)) {
1558
+ await mkdir4(this.commandsDir, { recursive: true });
1559
+ }
1560
+ }
1561
+ // Install a command
1562
+ async install(commandContent, metadata) {
1563
+ await this.ensureDir();
1564
+ const commandPath = join2(this.commandsDir, `${commandContent.name}.md`);
1565
+ const frontmatter = [
1566
+ FRONTMATTER_START,
1567
+ `description: ${commandContent.description}`,
1568
+ `${TENFOLD_MARKER} ${JSON.stringify(metadata)}`
1569
+ ];
1570
+ if (commandContent.arguments && commandContent.arguments.length > 0) {
1571
+ frontmatter.push("arguments:");
1572
+ for (const arg of commandContent.arguments) {
1573
+ frontmatter.push(` - name: ${arg.name}`);
1574
+ frontmatter.push(` type: ${arg.type}`);
1575
+ frontmatter.push(` required: ${arg.required}`);
1576
+ if (arg.description) {
1577
+ frontmatter.push(` description: ${arg.description}`);
1578
+ }
1579
+ }
1580
+ }
1581
+ frontmatter.push(FRONTMATTER_START);
1582
+ const content = frontmatter.join("\n") + "\n\n" + commandContent.content;
1583
+ await writeFile5(commandPath, content, "utf-8");
1584
+ return {
1585
+ type: "command_file",
1586
+ path: commandPath
1587
+ };
1588
+ }
1589
+ // Uninstall a command by slug
1590
+ async uninstall(slug) {
1591
+ const installed = await this.getInstalledCommands();
1592
+ const command = installed.find((c) => c.metadata?.slug === slug);
1593
+ if (!command) {
1594
+ return false;
1595
+ }
1596
+ const commandPath = join2(this.commandsDir, `${command.name}.md`);
1597
+ if (existsSync5(commandPath)) {
1598
+ await unlink(commandPath);
1599
+ return true;
1600
+ }
1601
+ return false;
1602
+ }
1603
+ // Uninstall by command name
1604
+ async uninstallByName(name) {
1605
+ const commandPath = join2(this.commandsDir, `${name}.md`);
1606
+ if (existsSync5(commandPath)) {
1607
+ await unlink(commandPath);
1608
+ return true;
1609
+ }
1610
+ return false;
1611
+ }
1612
+ // Check if a command name exists
1613
+ async hasCommand(name) {
1614
+ const commandPath = join2(this.commandsDir, `${name}.md`);
1615
+ return existsSync5(commandPath);
1616
+ }
1617
+ // Check if a slug is installed
1618
+ async isInstalled(slug) {
1619
+ const installed = await this.getInstalledCommands();
1620
+ return installed.some((c) => c.metadata?.slug === slug);
1621
+ }
1622
+ // Get all installed commands with tenfold metadata
1623
+ async getInstalledCommands() {
1624
+ if (!existsSync5(this.commandsDir)) {
1625
+ return [];
1626
+ }
1627
+ const files = await readdir(this.commandsDir);
1628
+ const result = [];
1629
+ for (const file of files) {
1630
+ if (!file.endsWith(".md")) continue;
1631
+ const commandPath = join2(this.commandsDir, file);
1632
+ const content = await readFile5(commandPath, "utf-8");
1633
+ const metadata = this.extractMetadata(content);
1634
+ result.push({
1635
+ name: basename(file, ".md"),
1636
+ path: commandPath,
1637
+ metadata
1638
+ });
1639
+ }
1640
+ return result;
1641
+ }
1642
+ // Extract tenfold metadata from file content
1643
+ extractMetadata(content) {
1644
+ const lines = content.split("\n");
1645
+ let inFrontmatter = false;
1646
+ for (const line of lines) {
1647
+ if (line.trim() === FRONTMATTER_START) {
1648
+ if (inFrontmatter) break;
1649
+ inFrontmatter = true;
1650
+ continue;
1651
+ }
1652
+ if (inFrontmatter && line.startsWith(TENFOLD_MARKER)) {
1653
+ try {
1654
+ const jsonStr = line.slice(TENFOLD_MARKER.length).trim();
1655
+ return JSON.parse(jsonStr);
1656
+ } catch {
1657
+ return null;
1658
+ }
1659
+ }
1660
+ }
1661
+ return null;
1662
+ }
1663
+ // Detect name conflict
1664
+ async detectConflict(commandContent) {
1665
+ const commandPath = join2(this.commandsDir, `${commandContent.name}.md`);
1666
+ if (!existsSync5(commandPath)) {
1667
+ return null;
1668
+ }
1669
+ const content = await readFile5(commandPath, "utf-8");
1670
+ return this.extractMetadata(content);
1671
+ }
1672
+ // Replace an existing command
1673
+ async replace(commandContent, metadata) {
1674
+ await this.uninstallByName(commandContent.name);
1675
+ return this.install(commandContent, metadata);
1676
+ }
1677
+ // Rename a command (for conflict resolution)
1678
+ async rename(oldName, newName) {
1679
+ const oldPath = join2(this.commandsDir, `${oldName}.md`);
1680
+ const newPath = join2(this.commandsDir, `${newName}.md`);
1681
+ if (!existsSync5(oldPath)) {
1682
+ return false;
1683
+ }
1684
+ const content = await readFile5(oldPath, "utf-8");
1685
+ await writeFile5(newPath, content, "utf-8");
1686
+ await unlink(oldPath);
1687
+ return true;
1688
+ }
1689
+ };
1690
+ var _manager5 = null;
1691
+ function getCommandsManager() {
1692
+ if (!_manager5) {
1693
+ _manager5 = new CommandsManager();
1694
+ }
1695
+ return _manager5;
1696
+ }
1697
+
1698
+ // src/config/skills.ts
1699
+ import { writeFile as writeFile6, mkdir as mkdir5, rm, readdir as readdir2, readFile as readFile6 } from "fs/promises";
1700
+ import { existsSync as existsSync6 } from "fs";
1701
+ import { join as join3 } from "path";
1702
+ var METADATA_FILE = ".tenfold.json";
1703
+ var SkillsManager = class {
1704
+ pluginsDir;
1705
+ constructor(pluginsDir) {
1706
+ this.pluginsDir = pluginsDir ?? getPluginsDir();
1707
+ }
1708
+ // Get skill directory for a slug
1709
+ getSkillDirForSlug(slug) {
1710
+ const dirName = slug.replace("/", "-");
1711
+ return join3(this.pluginsDir, dirName, "skills");
1712
+ }
1713
+ // Install a skill
1714
+ async installSkill(skillContent, metadata) {
1715
+ const skillDir = this.getSkillDirForSlug(metadata.slug);
1716
+ const skillPath = join3(skillDir, skillContent.name);
1717
+ if (!existsSync6(skillPath)) {
1718
+ await mkdir5(skillPath, { recursive: true });
1719
+ }
1720
+ const promptPath = join3(skillPath, "prompt.md");
1721
+ await writeFile6(promptPath, skillContent.prompt, "utf-8");
1722
+ if (skillContent.description) {
1723
+ const descPath = join3(skillPath, "description.txt");
1724
+ await writeFile6(descPath, skillContent.description, "utf-8");
1725
+ }
1726
+ const metadataPath = join3(skillPath, METADATA_FILE);
1727
+ await writeFile6(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
1728
+ return {
1729
+ type: "skill_dir",
1730
+ path: skillPath
1731
+ };
1732
+ }
1733
+ // Install an agent (similar to skill but with system prompt)
1734
+ async installAgent(agentContent, metadata) {
1735
+ const agentDir = this.getSkillDirForSlug(metadata.slug);
1736
+ const agentPath = join3(agentDir, agentContent.name);
1737
+ if (!existsSync6(agentPath)) {
1738
+ await mkdir5(agentPath, { recursive: true });
1739
+ }
1740
+ const promptPath = join3(agentPath, "system-prompt.md");
1741
+ await writeFile6(promptPath, agentContent.systemPrompt, "utf-8");
1742
+ if (agentContent.description) {
1743
+ const descPath = join3(agentPath, "description.txt");
1744
+ await writeFile6(descPath, agentContent.description, "utf-8");
1745
+ }
1746
+ if (agentContent.tools && agentContent.tools.length > 0) {
1747
+ const toolsPath = join3(agentPath, "tools.json");
1748
+ await writeFile6(
1749
+ toolsPath,
1750
+ JSON.stringify(agentContent.tools, null, 2),
1751
+ "utf-8"
1752
+ );
1753
+ }
1754
+ const metadataPath = join3(agentPath, METADATA_FILE);
1755
+ await writeFile6(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
1756
+ return {
1757
+ type: "skill_dir",
1758
+ path: agentPath
1759
+ };
1760
+ }
1761
+ // Uninstall by slug (removes entire plugin directory)
1762
+ async uninstall(slug) {
1763
+ const dirName = slug.replace("/", "-");
1764
+ const pluginDir = join3(this.pluginsDir, dirName);
1765
+ if (!existsSync6(pluginDir)) {
1766
+ return false;
1767
+ }
1768
+ await rm(pluginDir, { recursive: true, force: true });
1769
+ return true;
1770
+ }
1771
+ // Check if a slug is installed
1772
+ async isInstalled(slug) {
1773
+ const dirName = slug.replace("/", "-");
1774
+ const pluginDir = join3(this.pluginsDir, dirName);
1775
+ return existsSync6(pluginDir);
1776
+ }
1777
+ // Get all installed skills/agents with metadata
1778
+ async getInstalledSkills() {
1779
+ if (!existsSync6(this.pluginsDir)) {
1780
+ return [];
1781
+ }
1782
+ const result = [];
1783
+ const pluginDirs = await readdir2(this.pluginsDir);
1784
+ for (const dir of pluginDirs) {
1785
+ const pluginPath = join3(this.pluginsDir, dir);
1786
+ const skillsPath = join3(pluginPath, "skills");
1787
+ if (!existsSync6(skillsPath)) continue;
1788
+ const skillDirs = await readdir2(skillsPath);
1789
+ for (const skillDir of skillDirs) {
1790
+ const skillPath = join3(skillsPath, skillDir);
1791
+ const metadataPath = join3(skillPath, METADATA_FILE);
1792
+ let metadata = null;
1793
+ if (existsSync6(metadataPath)) {
1794
+ try {
1795
+ const content = await readFile6(metadataPath, "utf-8");
1796
+ metadata = JSON.parse(content);
1797
+ } catch {
1798
+ }
1799
+ }
1800
+ const hasSystemPrompt = existsSync6(join3(skillPath, "system-prompt.md"));
1801
+ const type = hasSystemPrompt ? "agent" : "skill";
1802
+ result.push({
1803
+ slug: dir.replace("-", "/"),
1804
+ path: skillPath,
1805
+ metadata,
1806
+ type
1807
+ });
1808
+ }
1809
+ }
1810
+ return result;
1811
+ }
1812
+ // Detect conflict (check if skill/agent name already exists)
1813
+ async detectConflict(content, slug) {
1814
+ const skillDir = this.getSkillDirForSlug(slug);
1815
+ const skillPath = join3(skillDir, content.name);
1816
+ const metadataPath = join3(skillPath, METADATA_FILE);
1817
+ if (!existsSync6(metadataPath)) {
1818
+ return null;
1819
+ }
1820
+ try {
1821
+ const metadataContent = await readFile6(metadataPath, "utf-8");
1822
+ return JSON.parse(metadataContent);
1823
+ } catch {
1824
+ return null;
1825
+ }
1826
+ }
1827
+ // Replace existing skill/agent
1828
+ async replace(content, metadata, type) {
1829
+ const skillDir = this.getSkillDirForSlug(metadata.slug);
1830
+ const skillPath = join3(skillDir, content.name);
1831
+ if (existsSync6(skillPath)) {
1832
+ await rm(skillPath, { recursive: true, force: true });
1833
+ }
1834
+ if (type === "agent") {
1835
+ return this.installAgent(content, metadata);
1836
+ }
1837
+ return this.installSkill(content, metadata);
1838
+ }
1839
+ };
1840
+ var _manager6 = null;
1841
+ function getSkillsManager() {
1842
+ if (!_manager6) {
1843
+ _manager6 = new SkillsManager();
1844
+ }
1845
+ return _manager6;
1846
+ }
1847
+
1848
+ // src/config/manager.ts
1849
+ var ConfigManager = class {
1850
+ installed;
1851
+ settings;
1852
+ mcp;
1853
+ claudeMd;
1854
+ commands;
1855
+ skills;
1856
+ constructor() {
1857
+ this.installed = getInstalledManager();
1858
+ this.settings = getSettingsManager();
1859
+ this.mcp = getMcpManager();
1860
+ this.claudeMd = getClaudeMdManager();
1861
+ this.commands = getCommandsManager();
1862
+ this.skills = getSkillsManager();
1863
+ }
1864
+ // Create metadata from component and version
1865
+ createMetadata(component, version) {
1866
+ return {
1867
+ componentId: component.id,
1868
+ versionId: version.id,
1869
+ version: version.version,
1870
+ author: component.author.name,
1871
+ slug: `${component.author.githubUsername}/${component.slug}`,
1872
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1873
+ };
1874
+ }
1875
+ // Detect conflicts for a component version
1876
+ async detectConflicts(component, version) {
1877
+ const conflicts = [];
1878
+ const content = version.content;
1879
+ const slug = `${component.author.githubUsername}/${component.slug}`;
1880
+ switch (component.type) {
1881
+ case "HOOK": {
1882
+ const hookContent = content;
1883
+ const existing = await this.settings.detectConflict(hookContent);
1884
+ if (existing && existing._tenfold?.slug !== slug) {
1885
+ conflicts.push({
1886
+ type: "hook_duplicate",
1887
+ severity: "blocking",
1888
+ existing: {
1889
+ source: existing._tenfold?.slug || "unknown",
1890
+ version: existing._tenfold?.version || "unknown"
1891
+ },
1892
+ incoming: {
1893
+ source: slug,
1894
+ version: version.version
1895
+ }
1896
+ });
1897
+ }
1898
+ break;
1899
+ }
1900
+ case "MCP_SERVER": {
1901
+ const mcpContent = content;
1902
+ const existing = await this.mcp.detectConflict(mcpContent);
1903
+ if (existing && existing._tenfold?.slug !== slug) {
1904
+ conflicts.push({
1905
+ type: "mcp_server_name",
1906
+ severity: "blocking",
1907
+ existing: {
1908
+ source: existing._tenfold?.slug || "user-defined",
1909
+ version: existing._tenfold?.version || "unknown"
1910
+ },
1911
+ incoming: {
1912
+ source: slug,
1913
+ version: version.version
1914
+ }
1915
+ });
1916
+ }
1917
+ break;
1918
+ }
1919
+ case "INSTRUCTION": {
1920
+ const hasConflict = await this.claudeMd.detectConflict(slug);
1921
+ if (hasConflict) {
1922
+ conflicts.push({
1923
+ type: "claude_md_section",
1924
+ severity: "warning",
1925
+ existing: {
1926
+ source: slug,
1927
+ version: "current"
1928
+ },
1929
+ incoming: {
1930
+ source: slug,
1931
+ version: version.version
1932
+ }
1933
+ });
1934
+ }
1935
+ break;
1936
+ }
1937
+ case "COMMAND": {
1938
+ const cmdContent = content;
1939
+ const existing = await this.commands.detectConflict(cmdContent);
1940
+ if (existing && existing.slug !== slug) {
1941
+ conflicts.push({
1942
+ type: "command_slug",
1943
+ severity: "blocking",
1944
+ existing: {
1945
+ source: existing.slug,
1946
+ version: existing.version
1947
+ },
1948
+ incoming: {
1949
+ source: slug,
1950
+ version: version.version
1951
+ }
1952
+ });
1953
+ }
1954
+ break;
1955
+ }
1956
+ case "SKILL":
1957
+ case "AGENT": {
1958
+ const skillContent = content;
1959
+ const existing = await this.skills.detectConflict(skillContent, slug);
1960
+ if (existing && existing.slug !== slug) {
1961
+ conflicts.push({
1962
+ type: "command_slug",
1963
+ // Reuse type for skills
1964
+ severity: "blocking",
1965
+ existing: {
1966
+ source: existing.slug,
1967
+ version: existing.version
1968
+ },
1969
+ incoming: {
1970
+ source: slug,
1971
+ version: version.version
1972
+ }
1973
+ });
1974
+ }
1975
+ break;
1976
+ }
1977
+ }
1978
+ return conflicts;
1979
+ }
1980
+ // Install a component
1981
+ async installComponent(component, version, options = {}) {
1982
+ const metadata = this.createMetadata(component, version);
1983
+ const slug = metadata.slug;
1984
+ const content = version.content;
1985
+ const targetFiles = [];
1986
+ if (options.dryRun) {
1987
+ return {
1988
+ success: true,
1989
+ component: {
1990
+ slug,
1991
+ type: component.type,
1992
+ version: version.version,
1993
+ author: component.author.name
1994
+ },
1995
+ targetFiles: []
1996
+ };
1997
+ }
1998
+ switch (component.type) {
1999
+ case "HOOK": {
2000
+ const hookContent = content;
2001
+ const target = await this.settings.installHook(hookContent, metadata);
2002
+ targetFiles.push(target);
2003
+ break;
2004
+ }
2005
+ case "MCP_SERVER": {
2006
+ const mcpContent = content;
2007
+ const target = await this.mcp.installServer(mcpContent, metadata);
2008
+ targetFiles.push(target);
2009
+ break;
2010
+ }
2011
+ case "INSTRUCTION": {
2012
+ const instructionContent = content;
2013
+ const target = await this.claudeMd.install(
2014
+ instructionContent,
2015
+ metadata,
2016
+ options.claudeMdStrategy || "section"
2017
+ );
2018
+ targetFiles.push(target);
2019
+ break;
2020
+ }
2021
+ case "COMMAND": {
2022
+ const cmdContent = content;
2023
+ const target = await this.commands.install(cmdContent, metadata);
2024
+ targetFiles.push(target);
2025
+ break;
2026
+ }
2027
+ case "SKILL": {
2028
+ const skillContent = content;
2029
+ const target = await this.skills.installSkill(skillContent, metadata);
2030
+ targetFiles.push(target);
2031
+ break;
2032
+ }
2033
+ case "AGENT": {
2034
+ const agentContent = content;
2035
+ const target = await this.skills.installAgent(agentContent, metadata);
2036
+ targetFiles.push(target);
2037
+ break;
2038
+ }
2039
+ }
2040
+ await this.installed.track(
2041
+ {
2042
+ componentId: component.id,
2043
+ versionId: version.id,
2044
+ author: component.author.name,
2045
+ slug,
2046
+ type: component.type,
2047
+ version: version.version
2048
+ },
2049
+ targetFiles,
2050
+ version.attributionSnapshot?.attributions
2051
+ );
2052
+ return {
2053
+ success: true,
2054
+ component: {
2055
+ slug,
2056
+ type: component.type,
2057
+ version: version.version,
2058
+ author: component.author.name
2059
+ },
2060
+ targetFiles
2061
+ };
2062
+ }
2063
+ // Uninstall a component
2064
+ async uninstallComponent(slug) {
2065
+ const installed = await this.installed.get(slug);
2066
+ if (!installed) {
2067
+ return false;
2068
+ }
2069
+ switch (installed.type) {
2070
+ case "HOOK":
2071
+ await this.settings.uninstallHook(slug);
2072
+ break;
2073
+ case "MCP_SERVER":
2074
+ await this.mcp.uninstallBySlug(slug);
2075
+ break;
2076
+ case "INSTRUCTION":
2077
+ await this.claudeMd.uninstall(slug);
2078
+ break;
2079
+ case "COMMAND":
2080
+ await this.commands.uninstall(slug);
2081
+ break;
2082
+ case "SKILL":
2083
+ case "AGENT":
2084
+ await this.skills.uninstall(slug);
2085
+ break;
2086
+ }
2087
+ await this.installed.remove(slug);
2088
+ return true;
2089
+ }
2090
+ // Get all installed components
2091
+ async getInstalledComponents() {
2092
+ return this.installed.getAll();
2093
+ }
2094
+ // Check if component is installed
2095
+ async isInstalled(slug) {
2096
+ return this.installed.isInstalled(slug);
2097
+ }
2098
+ // Get installed version
2099
+ async getInstalledVersion(slug) {
2100
+ return this.installed.getInstalledVersion(slug);
2101
+ }
2102
+ // Get hook definition for run-hook command
2103
+ async getHookDefinition(slug) {
2104
+ return this.settings.getHookDefinition(slug);
2105
+ }
2106
+ };
2107
+ var _manager7 = null;
2108
+ function getConfigManager() {
2109
+ if (!_manager7) {
2110
+ _manager7 = new ConfigManager();
2111
+ }
2112
+ return _manager7;
2113
+ }
2114
+
2115
+ // src/conflict/detector.ts
2116
+ var ConflictDetector = class {
2117
+ // Detect all conflicts for a component version
2118
+ async detect(component, version) {
2119
+ const configManager = getConfigManager();
2120
+ const conflicts = await configManager.detectConflicts(component, version);
2121
+ const blocking = conflicts.filter((c) => c.severity === "blocking");
2122
+ const warnings = conflicts.filter((c) => c.severity === "warning");
2123
+ return {
2124
+ hasConflicts: conflicts.length > 0,
2125
+ blocking,
2126
+ warnings
2127
+ };
2128
+ }
2129
+ // Check if any conflicts require user intervention
2130
+ hasBlockingConflicts(result) {
2131
+ return result.blocking.length > 0;
2132
+ }
2133
+ // Format conflict for display
2134
+ formatConflict(conflict) {
2135
+ const typeLabels = {
2136
+ mcp_server_name: "MCP Server name collision",
2137
+ hook_duplicate: "Hook event+matcher conflict",
2138
+ command_slug: "Command name collision",
2139
+ claude_md_section: "CLAUDE.md section exists"
2140
+ };
2141
+ return `${typeLabels[conflict.type] || conflict.type}: ${conflict.existing.source} vs ${conflict.incoming.source}`;
2142
+ }
2143
+ };
2144
+ var _detector = null;
2145
+ function getConflictDetector() {
2146
+ if (!_detector) {
2147
+ _detector = new ConflictDetector();
2148
+ }
2149
+ return _detector;
2150
+ }
2151
+
2152
+ // src/ui/prompt.ts
2153
+ import { select, confirm, input } from "@inquirer/prompts";
2154
+ async function confirmPrompt(message, defaultValue = false) {
2155
+ return confirm({
2156
+ message,
2157
+ default: defaultValue
2158
+ });
2159
+ }
2160
+ async function selectPrompt(message, choices) {
2161
+ return select({
2162
+ message,
2163
+ choices: choices.map((c) => ({
2164
+ name: c.name,
2165
+ value: c.value,
2166
+ description: c.description
2167
+ }))
2168
+ });
2169
+ }
2170
+ async function inputPrompt(message, options) {
2171
+ return input({
2172
+ message,
2173
+ default: options?.default,
2174
+ validate: options?.validate
2175
+ });
2176
+ }
2177
+ async function conflictResolutionPrompt(existing, incoming) {
2178
+ console.log(colors.warning("\n \u26A0 Conflict detected!"));
2179
+ console.log(muted(` Current: ${existing}`));
2180
+ console.log(muted(` Incoming: ${incoming}
2181
+ `));
2182
+ return selectPrompt(
2183
+ "How would you like to resolve this?",
2184
+ [
2185
+ { name: `Replace with ${incoming}`, value: "replace" },
2186
+ { name: `Keep ${existing} (skip)`, value: "keep" },
2187
+ { name: "Rename incoming", value: "rename" },
2188
+ { name: "View diff", value: "diff" }
2189
+ ]
2190
+ );
2191
+ }
2192
+ async function claudeMdStrategyPrompt() {
2193
+ return selectPrompt(
2194
+ "How should CLAUDE.md content be added?",
2195
+ [
2196
+ {
2197
+ name: "Append to existing CLAUDE.md",
2198
+ value: "append",
2199
+ description: "Add content at the end of the file"
2200
+ },
2201
+ {
2202
+ name: "Replace existing CLAUDE.md",
2203
+ value: "replace",
2204
+ description: "Overwrite the entire file"
2205
+ },
2206
+ {
2207
+ name: "Add as separate section with source marker",
2208
+ value: "section",
2209
+ description: "Add with markers for easy identification"
2210
+ }
2211
+ ]
2212
+ );
2213
+ }
2214
+ async function installBundlePrompt(bundleName, itemCount) {
2215
+ return confirmPrompt(
2216
+ `Install all ${itemCount} components from ${bundleName}?`,
2217
+ true
2218
+ );
2219
+ }
2220
+ async function renamePrompt(currentName, suggestedName) {
2221
+ return inputPrompt(`Enter new name (current: ${currentName}):`, {
2222
+ default: suggestedName,
2223
+ validate: (value) => {
2224
+ if (!value || value.length < 1) {
2225
+ return "Name cannot be empty";
2226
+ }
2227
+ if (!/^[a-z0-9_-]+$/i.test(value)) {
2228
+ return "Name can only contain letters, numbers, hyphens, and underscores";
2229
+ }
2230
+ return true;
2231
+ }
2232
+ });
2233
+ }
2234
+
2235
+ // src/ui/box.ts
2236
+ import boxen from "boxen";
2237
+ var defaultBoxOptions = {
2238
+ padding: 1,
2239
+ margin: { top: 1, bottom: 1, left: 2, right: 2 },
2240
+ borderStyle: "round"
2241
+ };
2242
+ function installHeaderBox(component, version) {
2243
+ const icon = typeIcons[component.type] || "\u{1F4E6}";
2244
+ const content = [
2245
+ `${icon} Installing: ${brandGradient(component.slug)} v${version.version}`,
2246
+ colors.muted(` Author: ${component.author.name} (@${component.author.githubUsername})`)
2247
+ ].join("\n");
2248
+ return boxen(content, {
2249
+ ...defaultBoxOptions,
2250
+ borderColor: "magenta"
2251
+ });
2252
+ }
2253
+ function bundleInstallHeaderBox(bundle) {
2254
+ const content = [
2255
+ `\u{1F4E6} Installing bundle: ${brandGradient(bundle.slug)}`,
2256
+ colors.muted(` Author: ${bundle.author.name} (@${bundle.author.githubUsername})`)
2257
+ ].join("\n");
2258
+ return boxen(content, {
2259
+ ...defaultBoxOptions,
2260
+ borderColor: "magenta"
2261
+ });
2262
+ }
2263
+ function conflictBox(existing, incoming) {
2264
+ const content = [
2265
+ colors.warning("\u26A0 Conflict detected!"),
2266
+ "",
2267
+ `Current: ${existing.source} v${existing.version}`,
2268
+ `Incoming: ${incoming.source} v${incoming.version}`
2269
+ ].join("\n");
2270
+ return boxen(content, {
2271
+ ...defaultBoxOptions,
2272
+ borderColor: "yellow"
2273
+ });
2274
+ }
2275
+
2276
+ // src/conflict/resolver.ts
2277
+ var ConflictResolver = class {
2278
+ // Resolve a single conflict interactively
2279
+ async resolve(conflict) {
2280
+ console.log(
2281
+ conflictBox(conflict.existing, conflict.incoming)
2282
+ );
2283
+ const resolution = await conflictResolutionPrompt(
2284
+ `${conflict.existing.source} v${conflict.existing.version}`,
2285
+ `${conflict.incoming.source} v${conflict.incoming.version}`
2286
+ );
2287
+ if (resolution === "diff") {
2288
+ await this.showDiff(conflict);
2289
+ return this.resolve(conflict);
2290
+ }
2291
+ if (resolution === "rename") {
2292
+ const suggestedName = this.suggestRename(conflict);
2293
+ const newName = await renamePrompt(
2294
+ conflict.incoming.source.split("/")[1] || conflict.incoming.source,
2295
+ suggestedName
2296
+ );
2297
+ return { conflict, resolution, newName };
2298
+ }
2299
+ return { conflict, resolution };
2300
+ }
2301
+ // Resolve all conflicts
2302
+ async resolveAll(conflicts) {
2303
+ const resolved = [];
2304
+ for (const conflict of conflicts) {
2305
+ const result = await this.resolve(conflict);
2306
+ resolved.push(result);
2307
+ }
2308
+ return resolved;
2309
+ }
2310
+ // Show diff between existing and incoming
2311
+ async showDiff(conflict) {
2312
+ console.log(muted("\n Diff view:"));
2313
+ console.log(muted(` - Current: ${conflict.existing.source} v${conflict.existing.version}`));
2314
+ console.log(muted(` + Incoming: ${conflict.incoming.source} v${conflict.incoming.version}`));
2315
+ console.log(muted("\n (Detailed diff not yet implemented)\n"));
2316
+ }
2317
+ // Suggest a new name for rename resolution
2318
+ suggestRename(conflict) {
2319
+ const incomingName = conflict.incoming.source.split("/")[1] || conflict.incoming.source;
2320
+ const author = conflict.incoming.source.split("/")[0] || "custom";
2321
+ return `${incomingName}-${author}`;
2322
+ }
2323
+ // Check if resolution is to skip installation
2324
+ isSkip(resolution) {
2325
+ return resolution === "keep";
2326
+ }
2327
+ // Check if resolution is to replace
2328
+ isReplace(resolution) {
2329
+ return resolution === "replace";
2330
+ }
2331
+ // Check if resolution is to rename
2332
+ isRename(resolution) {
2333
+ return resolution === "rename";
2334
+ }
2335
+ };
2336
+ var _resolver = null;
2337
+ function getConflictResolver() {
2338
+ if (!_resolver) {
2339
+ _resolver = new ConflictResolver();
2340
+ }
2341
+ return _resolver;
2342
+ }
2343
+
2344
+ // src/ui/links.ts
2345
+ import terminalLink from "terminal-link";
2346
+ var BASE_URL = process.env.TENFOLD_WEB_URL ?? "https://tenfold.dev";
2347
+ function link(text, url) {
2348
+ return terminalLink(text, url, {
2349
+ fallback: (text2, url2) => `${text2} (${muted(url2)})`
2350
+ });
2351
+ }
2352
+ function componentLink(slug) {
2353
+ const url = `${BASE_URL}/components/${slug}`;
2354
+ return link(colors.info("View online \u2192"), url);
2355
+ }
2356
+ function bundleLink(slug) {
2357
+ const url = `${BASE_URL}/bundles/${slug}`;
2358
+ return link(colors.info("View online \u2192"), url);
2359
+ }
2360
+
2361
+ // src/commands/install.ts
2362
+ function createInstallCommand() {
2363
+ return new Command3("install").description("Install a component or bundle").argument("<slug>", "Component or bundle to install (author/name)").option("-f, --force", "Override conflicts without prompting").option("--dry-run", "Show what would be installed without making changes").option("-v, --version <version>", "Install specific version").action(
2364
+ async (slug, options) => {
2365
+ if (!isValidSlug(slug)) {
2366
+ console.error(
2367
+ colors.error(
2368
+ `
2369
+ Invalid slug format: "${slug}". Expected "author/name".
2370
+ `
2371
+ )
2372
+ );
2373
+ process.exit(1);
2374
+ }
2375
+ const api = getApiClient();
2376
+ const configManager = getConfigManager();
2377
+ const detector = getConflictDetector();
2378
+ const resolver = getConflictResolver();
2379
+ let isBundle = false;
2380
+ let component = null;
2381
+ let bundle = null;
2382
+ try {
2383
+ component = await api.getComponent(slug);
2384
+ } catch (error) {
2385
+ if (error instanceof NotFoundError) {
2386
+ try {
2387
+ bundle = await api.getBundle(slug);
2388
+ isBundle = true;
2389
+ } catch {
2390
+ console.error(colors.error(`
2391
+ Not found: ${slug}
2392
+ `));
2393
+ const similar = await api.searchComponents(
2394
+ slug.split("/")[1] || slug,
2395
+ 3
2396
+ );
2397
+ if (similar.length > 0) {
2398
+ console.log(muted(" Did you mean:"));
2399
+ for (const s of similar) {
2400
+ console.log(muted(` - ${s.author.githubUsername}/${s.slug}`));
2401
+ }
2402
+ console.log();
2403
+ }
2404
+ process.exit(1);
2405
+ }
2406
+ } else {
2407
+ throw error;
2408
+ }
2409
+ }
2410
+ if (isBundle && bundle) {
2411
+ await installBundle(bundle, api, configManager, options);
2412
+ } else if (component) {
2413
+ await installComponent(
2414
+ component,
2415
+ api,
2416
+ configManager,
2417
+ detector,
2418
+ resolver,
2419
+ options
2420
+ );
2421
+ }
2422
+ }
2423
+ );
2424
+ }
2425
+ async function installComponent(component, api, configManager, detector, resolver, options) {
2426
+ const version = component.versions[0];
2427
+ if (!version) {
2428
+ console.error(colors.error(`
2429
+ No versions available for ${component.slug}
2430
+ `));
2431
+ process.exit(1);
2432
+ }
2433
+ console.log(installHeaderBox(component, version));
2434
+ let strategy = "section";
2435
+ if (component.type === "INSTRUCTION" && !options.force) {
2436
+ strategy = await claudeMdStrategyPrompt();
2437
+ }
2438
+ const tasks = new Listr(
2439
+ [
2440
+ {
2441
+ title: "Checking for conflicts",
2442
+ task: async (ctx) => {
2443
+ const result = await detector.detect(component, version);
2444
+ ctx.conflicts = result;
2445
+ if (result.blocking.length > 0 && !options.force) {
2446
+ throw new Error("CONFLICTS_DETECTED");
2447
+ }
2448
+ }
2449
+ },
2450
+ {
2451
+ title: options.dryRun ? "Dry run - no changes made" : "Installing component",
2452
+ skip: (ctx) => {
2453
+ if (ctx.conflicts?.blocking.length && !options.force) {
2454
+ return "Blocked by conflicts";
2455
+ }
2456
+ return false;
2457
+ },
2458
+ task: async (ctx) => {
2459
+ if (options.dryRun) {
2460
+ ctx.installed = false;
2461
+ return;
2462
+ }
2463
+ const result = await configManager.installComponent(
2464
+ component,
2465
+ version,
2466
+ { claudeMdStrategy: strategy }
2467
+ );
2468
+ ctx.installed = result.success;
2469
+ ctx.results = [
2470
+ {
2471
+ component: component.slug,
2472
+ type: component.type,
2473
+ status: "Added"
2474
+ }
2475
+ ];
2476
+ }
2477
+ },
2478
+ {
2479
+ title: "Tracking installation",
2480
+ skip: () => !!options.dryRun,
2481
+ task: async () => {
2482
+ try {
2483
+ await api.trackComponentInstall(
2484
+ `${component.author.githubUsername}/${component.slug}`,
2485
+ version.id
2486
+ );
2487
+ } catch {
2488
+ }
2489
+ }
2490
+ }
2491
+ ],
2492
+ {
2493
+ exitOnError: false,
2494
+ rendererOptions: {
2495
+ collapseErrors: false
2496
+ }
2497
+ }
2498
+ );
2499
+ try {
2500
+ const ctx = await tasks.run();
2501
+ if (ctx.conflicts?.blocking.length && !options.force) {
2502
+ console.log();
2503
+ for (const conflict of ctx.conflicts.blocking) {
2504
+ const resolved = await resolver.resolve(conflict);
2505
+ if (resolver.isSkip(resolved.resolution)) {
2506
+ console.log(muted(` Skipped: ${component.slug}
2507
+ `));
2508
+ return;
2509
+ }
2510
+ if (resolver.isReplace(resolved.resolution)) {
2511
+ await configManager.uninstallComponent(conflict.existing.source);
2512
+ const result = await configManager.installComponent(
2513
+ component,
2514
+ version,
2515
+ { claudeMdStrategy: strategy }
2516
+ );
2517
+ ctx.installed = result.success;
2518
+ ctx.results = [
2519
+ {
2520
+ component: component.slug,
2521
+ type: component.type,
2522
+ status: "Replaced"
2523
+ }
2524
+ ];
2525
+ }
2526
+ }
2527
+ }
2528
+ if (ctx.installed && ctx.results) {
2529
+ console.log(installResultTable(ctx.results));
2530
+ console.log();
2531
+ const attribution = version.attributionSnapshot?.attributions;
2532
+ if (attribution && attribution.length > 1) {
2533
+ console.log(muted(" Attribution:"));
2534
+ console.log(attributionTable(attribution));
2535
+ console.log();
2536
+ }
2537
+ console.log(
2538
+ ` ${componentLink(`${component.author.githubUsername}/${component.slug}`)}
2539
+ `
2540
+ );
2541
+ }
2542
+ } catch (error) {
2543
+ if (error.message !== "CONFLICTS_DETECTED") {
2544
+ console.error(colors.error(`
2545
+ ${formatError(error)}
2546
+ `));
2547
+ process.exit(1);
2548
+ }
2549
+ }
2550
+ }
2551
+ async function installBundle(bundle, api, configManager, options) {
2552
+ console.log(bundleInstallHeaderBox(bundle));
2553
+ console.log(muted(" Components included:\n"));
2554
+ console.log(bundleItemsTable(bundle));
2555
+ console.log();
2556
+ if (!options.force) {
2557
+ const confirm2 = await installBundlePrompt(
2558
+ bundle.displayName,
2559
+ bundle.items.length
2560
+ );
2561
+ if (!confirm2) {
2562
+ console.log(muted("\n Installation cancelled.\n"));
2563
+ return;
2564
+ }
2565
+ }
2566
+ const results = [];
2567
+ for (const item of bundle.items) {
2568
+ const component = item.componentVersion.component;
2569
+ const version = item.componentVersion;
2570
+ try {
2571
+ if (!options.dryRun) {
2572
+ await configManager.installComponent(component, version);
2573
+ }
2574
+ results.push({
2575
+ component: `${component.author.githubUsername}/${component.slug}`,
2576
+ type: component.type,
2577
+ status: options.dryRun ? "Would add" : "Added"
2578
+ });
2579
+ } catch {
2580
+ results.push({
2581
+ component: `${component.author.githubUsername}/${component.slug}`,
2582
+ type: component.type,
2583
+ status: "Failed"
2584
+ });
2585
+ }
2586
+ }
2587
+ if (!options.dryRun) {
2588
+ try {
2589
+ await api.trackBundleInstall(
2590
+ `${bundle.author.githubUsername}/${bundle.slug}`
2591
+ );
2592
+ } catch {
2593
+ }
2594
+ }
2595
+ console.log(success(`
2596
+ ${options.dryRun ? "Would install" : "Installed"} bundle: ${bundle.slug}
2597
+ `));
2598
+ console.log(installResultTable(results));
2599
+ console.log();
2600
+ console.log(
2601
+ ` ${bundleLink(`${bundle.author.githubUsername}/${bundle.slug}`)}
2602
+ `
2603
+ );
2604
+ }
2605
+
2606
+ // src/commands/list.ts
2607
+ import { Command as Command4 } from "commander";
2608
+ function createListCommand() {
2609
+ return new Command4("list").alias("ls").description("List installed components").option("-t, --type <type>", "Filter by type (hook, skill, mcp_server, etc.)").action(async (options) => {
2610
+ const manager = getInstalledManager();
2611
+ try {
2612
+ let components = await manager.getAll();
2613
+ if (options.type) {
2614
+ const typeFilter = options.type.toUpperCase().replace("-", "_");
2615
+ components = components.filter((c) => c.type === typeFilter);
2616
+ }
2617
+ if (components.length === 0) {
2618
+ if (options.type) {
2619
+ console.log(
2620
+ muted(`
2621
+ No ${options.type} components installed.
2622
+ `)
2623
+ );
2624
+ } else {
2625
+ console.log(muted("\n No components installed.\n"));
2626
+ console.log(muted(" Get started:"));
2627
+ console.log(muted(" tenfold search <query>"));
2628
+ console.log(muted(" tenfold install <author>/<component>\n"));
2629
+ }
2630
+ return;
2631
+ }
2632
+ console.log(bold("\n Installed Components\n"));
2633
+ console.log(installedTable(components));
2634
+ console.log();
2635
+ console.log(muted(` ${components.length} component(s) installed
2636
+ `));
2637
+ } catch (error) {
2638
+ console.error(colors.error(`
2639
+ ${formatError(error)}
2640
+ `));
2641
+ process.exit(1);
2642
+ }
2643
+ });
2644
+ }
2645
+
2646
+ // src/commands/uninstall.ts
2647
+ import { Command as Command5 } from "commander";
2648
+ function createUninstallCommand() {
2649
+ return new Command5("uninstall").alias("rm").description("Uninstall a component").argument("<slug>", "Component to uninstall (author/name)").option("-f, --force", "Skip confirmation prompt").action(async (slug, options) => {
2650
+ if (!isValidSlug(slug)) {
2651
+ console.error(
2652
+ colors.error(
2653
+ `
2654
+ Invalid slug format: "${slug}". Expected "author/name".
2655
+ `
2656
+ )
2657
+ );
2658
+ process.exit(1);
2659
+ }
2660
+ const configManager = getConfigManager();
2661
+ const isInstalled = await configManager.isInstalled(slug);
2662
+ if (!isInstalled) {
2663
+ console.log(muted(`
2664
+ Component not installed: ${slug}
2665
+ `));
2666
+ process.exit(1);
2667
+ }
2668
+ if (!options.force) {
2669
+ const confirmed = await confirmPrompt(
2670
+ `Uninstall ${slug}?`,
2671
+ false
2672
+ );
2673
+ if (!confirmed) {
2674
+ console.log(muted("\n Cancelled.\n"));
2675
+ return;
2676
+ }
2677
+ }
2678
+ const spinner = startSpinner(`Uninstalling ${slug}...`);
2679
+ try {
2680
+ const removed = await configManager.uninstallComponent(slug);
2681
+ if (removed) {
2682
+ spinner.succeed(`Uninstalled ${slug}`);
2683
+ console.log();
2684
+ } else {
2685
+ spinner.fail(`Failed to uninstall ${slug}`);
2686
+ process.exit(1);
2687
+ }
2688
+ } catch (error) {
2689
+ spinner.fail("Uninstall failed");
2690
+ console.error(colors.error(`
2691
+ ${formatError(error)}
2692
+ `));
2693
+ process.exit(1);
2694
+ }
2695
+ });
2696
+ }
2697
+
2698
+ // src/commands/run-hook.ts
2699
+ import { Command as Command6 } from "commander";
2700
+ import { spawn } from "child_process";
2701
+ function createRunHookCommand() {
2702
+ return new Command6("run-hook").description("Execute an installed hook").argument("<slug>", "Hook to execute (author/name)").option("--timeout <ms>", "Execution timeout in milliseconds", "30000").action(async (slug, options) => {
2703
+ if (!isValidSlug(slug)) {
2704
+ console.error(
2705
+ colors.error(
2706
+ `Invalid slug format: "${slug}". Expected "author/name".`
2707
+ )
2708
+ );
2709
+ process.exit(1);
2710
+ }
2711
+ const configManager = getConfigManager();
2712
+ const hookDef = await configManager.getHookDefinition(slug);
2713
+ if (!hookDef) {
2714
+ console.error(colors.error(`Hook not installed: ${slug}`));
2715
+ console.error(muted(`Install it first: tenfold install ${slug}`));
2716
+ process.exit(1);
2717
+ }
2718
+ const timeout = parseInt(options.timeout, 10);
2719
+ const child = spawn(hookDef.command, [], {
2720
+ stdio: "inherit",
2721
+ shell: true,
2722
+ timeout
2723
+ });
2724
+ child.on("exit", (code) => {
2725
+ process.exit(code ?? 0);
2726
+ });
2727
+ child.on("error", (error) => {
2728
+ console.error(colors.error(`Hook execution failed: ${error.message}`));
2729
+ process.exit(1);
2730
+ });
2731
+ });
2732
+ }
2733
+
2734
+ // src/types/component.ts
2735
+ function isHookContent(content) {
2736
+ return "event" in content && "command" in content;
2737
+ }
2738
+ function isMcpServerContent(content) {
2739
+ return "command" in content && !("event" in content) && !("prompt" in content);
2740
+ }
2741
+ function isInstructionContent(content) {
2742
+ return "paragraphs" in content;
2743
+ }
2744
+ function isCommandContent(content) {
2745
+ return "content" in content && "name" in content && !("prompt" in content);
2746
+ }
2747
+ function isSkillContent(content) {
2748
+ return "prompt" in content && !("systemPrompt" in content);
2749
+ }
2750
+ function isAgentContent(content) {
2751
+ return "systemPrompt" in content;
2752
+ }
2753
+ var COMPONENT_ICONS = {
2754
+ INSTRUCTION: "\u{1F4DD}",
2755
+ HOOK: "\u{1F527}",
2756
+ COMMAND: "\u2318",
2757
+ MCP_SERVER: "\u{1F5A5}",
2758
+ SKILL: "\u26A1",
2759
+ AGENT: "\u{1F916}"
2760
+ };
2761
+ function getComponentIcon(type) {
2762
+ return COMPONENT_ICONS[type] || "\u{1F4E6}";
2763
+ }
2764
+
2765
+ // src/types/api.ts
2766
+ var DEFAULT_API_CONFIG = {
2767
+ baseUrl: process.env.TENFOLD_API_URL ?? "https://api.tenfold.dev",
2768
+ timeout: 3e4
2769
+ };
2770
+
2771
+ // src/index.ts
2772
+ var VERSION = "0.1.0";
2773
+ function createProgram() {
2774
+ const program = new Command7().name("tenfold").description("Package manager for Claude Code components").version(VERSION, "-V, --version", "Display version number");
2775
+ program.addHelpText("beforeAll", renderBanner());
2776
+ program.addCommand(createSearchCommand());
2777
+ program.addCommand(createBrowseCommand());
2778
+ program.addCommand(createInstallCommand());
2779
+ program.addCommand(createListCommand());
2780
+ program.addCommand(createUninstallCommand());
2781
+ program.addCommand(createRunHookCommand());
2782
+ program.action(() => {
2783
+ console.log(renderWelcome());
2784
+ });
2785
+ return program;
2786
+ }
2787
+
2788
+ export {
2789
+ TenfoldApiClient,
2790
+ getApiClient,
2791
+ TenfoldMetadataSchema,
2792
+ TargetFileSchema,
2793
+ InstalledComponentSchema,
2794
+ InstalledManifestSchema,
2795
+ HookDefinitionSchema,
2796
+ HookEntrySchema,
2797
+ ClaudeSettingsSchema,
2798
+ McpServerEntrySchema,
2799
+ McpConfigSchema,
2800
+ CredentialsSchema,
2801
+ createEmptyManifest,
2802
+ ConfigManager,
2803
+ getConfigManager,
2804
+ isHookContent,
2805
+ isMcpServerContent,
2806
+ isInstructionContent,
2807
+ isCommandContent,
2808
+ isSkillContent,
2809
+ isAgentContent,
2810
+ COMPONENT_ICONS,
2811
+ getComponentIcon,
2812
+ DEFAULT_API_CONFIG,
2813
+ createProgram
2814
+ };