sundial-hub 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1593 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk9 from "chalk";
6
+ import { createRequire } from "module";
7
+
8
+ // src/constants.ts
9
+ var COMMANDS = ["add", "remove", "list", "show", "config"];
10
+ var AGENTS = [
11
+ { name: "Claude Code", flag: "claude", folderName: ".claude" },
12
+ { name: "Codex", flag: "codex", folderName: ".codex" },
13
+ { name: "Gemini", flag: "gemini", folderName: ".gemini" }
14
+ ];
15
+
16
+ // src/core/agents.ts
17
+ var SUPPORTED_AGENTS = AGENTS;
18
+ function getAgentByFlag(flag) {
19
+ return AGENTS.find((agent) => agent.flag === flag);
20
+ }
21
+ function getSupportedAgentsMessage() {
22
+ return `Currently supported agents: ${AGENTS.map((a) => a.name).join(", ")}`;
23
+ }
24
+
25
+ // src/commands/add.ts
26
+ import chalk2 from "chalk";
27
+ import ora from "ora";
28
+
29
+ // src/core/config-manager.ts
30
+ import fs from "fs-extra";
31
+ import path from "path";
32
+ import os from "os";
33
+ var CONFIG_DIR = path.join(os.homedir(), ".sun");
34
+ var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
35
+ var DEFAULT_CONFIG = {
36
+ defaultAgents: [],
37
+ firstRunComplete: false
38
+ };
39
+ async function ensureConfigDir() {
40
+ await fs.ensureDir(CONFIG_DIR);
41
+ }
42
+ async function loadConfig() {
43
+ try {
44
+ await ensureConfigDir();
45
+ if (await fs.pathExists(CONFIG_FILE)) {
46
+ const content = await fs.readFile(CONFIG_FILE, "utf-8");
47
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
48
+ }
49
+ } catch {
50
+ }
51
+ return { ...DEFAULT_CONFIG };
52
+ }
53
+ async function saveConfig(config) {
54
+ await ensureConfigDir();
55
+ await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
56
+ }
57
+ async function isFirstRun() {
58
+ const config = await loadConfig();
59
+ return !config.firstRunComplete;
60
+ }
61
+ async function getDefaultAgents() {
62
+ const config = await loadConfig();
63
+ return config.defaultAgents;
64
+ }
65
+ async function setDefaultAgents(agents) {
66
+ const config = await loadConfig();
67
+ config.defaultAgents = agents;
68
+ config.firstRunComplete = true;
69
+ await saveConfig(config);
70
+ }
71
+ function getConfigPath() {
72
+ return CONFIG_FILE;
73
+ }
74
+
75
+ // src/core/agent-detect.ts
76
+ import fs2 from "fs-extra";
77
+ import path2 from "path";
78
+ import os2 from "os";
79
+ import { exec } from "child_process";
80
+ import { promisify } from "util";
81
+ var execAsync = promisify(exec);
82
+ var AGENT_FOLDER_NAMES = new Set(SUPPORTED_AGENTS.map((a) => a.folderName));
83
+ async function detectAgentsInDirectory(directory, isGlobal) {
84
+ const detected = [];
85
+ for (const agent of SUPPORTED_AGENTS) {
86
+ const agentPath = path2.join(directory, agent.folderName);
87
+ if (await fs2.pathExists(agentPath)) {
88
+ detected.push({
89
+ agent,
90
+ path: agentPath,
91
+ isGlobal
92
+ });
93
+ }
94
+ }
95
+ return detected;
96
+ }
97
+ async function detectLocalAgents() {
98
+ return detectAgentsInDirectory(process.cwd(), false);
99
+ }
100
+ async function detectGlobalAgents() {
101
+ return detectAgentsInDirectory(os2.homedir(), true);
102
+ }
103
+ async function detectAllAgents() {
104
+ const [local, global] = await Promise.all([
105
+ detectLocalAgents(),
106
+ detectGlobalAgents()
107
+ ]);
108
+ return [...local, ...global];
109
+ }
110
+
111
+ // src/core/skill-install.ts
112
+ import fs6 from "fs-extra";
113
+ import path6 from "path";
114
+ import os4 from "os";
115
+ import { execFileSync } from "child_process";
116
+ import AdmZip from "adm-zip";
117
+
118
+ // src/core/skill-source.ts
119
+ import fs3 from "fs-extra";
120
+ import path3 from "path";
121
+
122
+ // src/lib/supabase.ts
123
+ var API_URL = "https://vfbndmrgggrhnlrileqv.supabase.co/functions/v1/skills";
124
+ async function fetchSkills() {
125
+ const res = await fetch(`${API_URL}/list`);
126
+ if (!res.ok) throw new Error(`Failed to fetch skills: ${res.statusText}`);
127
+ return res.json();
128
+ }
129
+ async function trackDownload(skillName) {
130
+ try {
131
+ await fetch(`${API_URL}/track`, {
132
+ method: "POST",
133
+ headers: { "Content-Type": "application/json" },
134
+ body: JSON.stringify({ skill_name: skillName })
135
+ });
136
+ } catch {
137
+ }
138
+ }
139
+
140
+ // src/utils/registry.ts
141
+ var STORAGE_URL = "https://vfbndmrgggrhnlrileqv.supabase.co/storage/v1/object/public/skill-zips";
142
+ var skillsCache = null;
143
+ async function getSkillsFromRegistry() {
144
+ if (!skillsCache) {
145
+ skillsCache = await fetchSkills();
146
+ }
147
+ return skillsCache;
148
+ }
149
+ async function getSkillByName(name) {
150
+ const skills = await getSkillsFromRegistry();
151
+ return skills.find((skill) => skill.name === name);
152
+ }
153
+ async function isShortcut(skill) {
154
+ return await getSkillByName(skill) !== void 0;
155
+ }
156
+ async function getShortcutUrl(skill) {
157
+ return (await getSkillByName(skill))?.degit_path;
158
+ }
159
+ async function getShortcutZipUrl(skill) {
160
+ const zipPath = (await getSkillByName(skill))?.zip_path;
161
+ return zipPath ? `${STORAGE_URL}/${zipPath}` : void 0;
162
+ }
163
+
164
+ // src/core/skill-source.ts
165
+ function isGithubUrl(input) {
166
+ return input.includes("github.com");
167
+ }
168
+ function normalizeGithubUrl(url) {
169
+ let location = url.trim();
170
+ location = location.replace(/^git\+/, "");
171
+ const urlString = /^[a-z]+:\/\//i.test(location) ? location : `https://${location}`;
172
+ let pathname = "";
173
+ try {
174
+ const parsed = new URL(urlString);
175
+ pathname = parsed.pathname;
176
+ } catch {
177
+ pathname = urlString.replace(/^https?:\/\//, "");
178
+ }
179
+ pathname = pathname.replace(/^\/+/, "");
180
+ const parts = pathname.split("/").filter(Boolean);
181
+ if (parts[0] === "github.com") {
182
+ parts.shift();
183
+ }
184
+ if (parts.length < 2) {
185
+ return pathname;
186
+ }
187
+ const user = parts[0];
188
+ const repo = parts[1].replace(/\.git$/, "");
189
+ const kind = parts[2];
190
+ if (kind === "tree" || kind === "blob" || kind === "raw") {
191
+ const branch = parts[3] || "";
192
+ let subpath = parts.slice(4).join("/");
193
+ if (kind !== "tree" && subpath) {
194
+ const subparts = subpath.split("/");
195
+ subparts.pop();
196
+ subpath = subparts.join("/");
197
+ }
198
+ if (branch) {
199
+ return subpath ? `${user}/${repo}/${subpath}#${branch}` : `${user}/${repo}#${branch}`;
200
+ }
201
+ }
202
+ const extraPath = parts.slice(2).join("/");
203
+ return extraPath ? `${user}/${repo}/${extraPath}` : `${user}/${repo}`;
204
+ }
205
+ function isLocalPath(input) {
206
+ if (input.startsWith("./") || input.startsWith("../") || input.startsWith("~/") || input.startsWith("/")) {
207
+ return true;
208
+ }
209
+ const resolved = path3.resolve(input);
210
+ return fs3.pathExistsSync(resolved);
211
+ }
212
+ async function resolveSkillSource(input) {
213
+ if (await isShortcut(input)) {
214
+ const degitPath = await getShortcutUrl(input);
215
+ if (!degitPath) {
216
+ throw new Error(`Skill "${input}" found in registry but has no degit_path`);
217
+ }
218
+ return {
219
+ type: "shortcut",
220
+ location: degitPath,
221
+ originalInput: input
222
+ };
223
+ }
224
+ if (isGithubUrl(input)) {
225
+ return {
226
+ type: "github",
227
+ location: normalizeGithubUrl(input),
228
+ originalInput: input
229
+ };
230
+ }
231
+ if (isLocalPath(input)) {
232
+ let location = input;
233
+ if (location.startsWith("~/")) {
234
+ location = path3.join(process.env.HOME || "", location.slice(2));
235
+ }
236
+ location = path3.resolve(location);
237
+ return {
238
+ type: "local",
239
+ location,
240
+ originalInput: input
241
+ };
242
+ }
243
+ throw new Error(`Skill not found: "${input}". Expected a shortcut name, GitHub URL, or local path.`);
244
+ }
245
+
246
+ // src/core/skill-info.ts
247
+ import fs5 from "fs-extra";
248
+ import path5 from "path";
249
+ import os3 from "os";
250
+
251
+ // src/core/skill-hash.ts
252
+ import crypto from "crypto";
253
+ import fs4 from "fs-extra";
254
+ import path4 from "path";
255
+ async function getAllFiles(dir) {
256
+ const files = [];
257
+ async function walk(currentDir) {
258
+ const entries = await fs4.readdir(currentDir, { withFileTypes: true });
259
+ for (const entry of entries) {
260
+ const fullPath = path4.join(currentDir, entry.name);
261
+ if (entry.isDirectory()) {
262
+ await walk(fullPath);
263
+ } else if (entry.isFile()) {
264
+ files.push(fullPath);
265
+ }
266
+ }
267
+ }
268
+ await walk(dir);
269
+ return files.sort();
270
+ }
271
+ async function computeContentHash(skillPath) {
272
+ const files = await getAllFiles(skillPath);
273
+ if (files.length === 0) {
274
+ return crypto.createHash("sha256").update("").digest("hex").slice(0, 12);
275
+ }
276
+ const hash = crypto.createHash("sha256");
277
+ for (const file of files) {
278
+ const relativePath = path4.relative(skillPath, file);
279
+ hash.update(relativePath);
280
+ const content = await fs4.readFile(file);
281
+ hash.update(content);
282
+ }
283
+ return hash.digest("hex").slice(0, 12);
284
+ }
285
+
286
+ // src/core/skill-info.ts
287
+ function parseFrontmatter(content) {
288
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
289
+ if (!frontmatterMatch) {
290
+ return null;
291
+ }
292
+ const lines = frontmatterMatch[1].split("\n");
293
+ let name = "";
294
+ let description = "";
295
+ let license;
296
+ let compatibility;
297
+ let allowedTools;
298
+ const metadata = {};
299
+ let inMetadata = false;
300
+ for (const line of lines) {
301
+ if (line.match(/^metadata:\s*$/)) {
302
+ inMetadata = true;
303
+ continue;
304
+ }
305
+ if (inMetadata && line.match(/^\S/)) {
306
+ inMetadata = false;
307
+ }
308
+ const colonIndex = line.indexOf(":");
309
+ if (colonIndex > 0) {
310
+ const key = line.slice(0, colonIndex).trim();
311
+ let value = line.slice(colonIndex + 1).trim();
312
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
313
+ value = value.slice(1, -1);
314
+ }
315
+ if (inMetadata) {
316
+ metadata[key] = value;
317
+ } else {
318
+ switch (key) {
319
+ case "name":
320
+ name = value;
321
+ break;
322
+ case "description":
323
+ description = value;
324
+ break;
325
+ case "license":
326
+ license = value;
327
+ break;
328
+ case "compatibility":
329
+ compatibility = value;
330
+ break;
331
+ case "allowed-tools":
332
+ allowedTools = value;
333
+ break;
334
+ }
335
+ }
336
+ }
337
+ }
338
+ if (!name || !description) {
339
+ return null;
340
+ }
341
+ return {
342
+ name,
343
+ description,
344
+ license,
345
+ compatibility,
346
+ metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
347
+ allowedTools
348
+ };
349
+ }
350
+ async function isValidSkillDirectory(dirPath) {
351
+ const skillMdPath = path5.join(dirPath, "SKILL.md");
352
+ if (!await fs5.pathExists(skillMdPath)) {
353
+ return false;
354
+ }
355
+ try {
356
+ const content = await fs5.readFile(skillMdPath, "utf-8");
357
+ const metadata = parseFrontmatter(content);
358
+ return metadata !== null;
359
+ } catch {
360
+ return false;
361
+ }
362
+ }
363
+ async function findSkillDirectories(searchPath) {
364
+ const skills = [];
365
+ async function searchRecursively(dir) {
366
+ try {
367
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
368
+ for (const entry of entries) {
369
+ const fullPath = path5.join(dir, entry.name);
370
+ if (entry.isFile() && entry.name === "SKILL.md") {
371
+ if (await isValidSkillDirectory(dir)) {
372
+ skills.push(dir);
373
+ }
374
+ } else if (entry.isDirectory() && !entry.name.startsWith(".git")) {
375
+ await searchRecursively(fullPath);
376
+ }
377
+ }
378
+ } catch {
379
+ }
380
+ }
381
+ await searchRecursively(searchPath);
382
+ return skills;
383
+ }
384
+ async function readSkillMetadata(skillPath) {
385
+ const skillMdPath = path5.join(skillPath, "SKILL.md");
386
+ try {
387
+ const content = await fs5.readFile(skillMdPath, "utf-8");
388
+ return parseFrontmatter(content);
389
+ } catch {
390
+ return null;
391
+ }
392
+ }
393
+ async function getSkillName(skillPath) {
394
+ const metadata = await readSkillMetadata(skillPath);
395
+ return metadata?.name || null;
396
+ }
397
+ async function findSkillInstallations(skillName) {
398
+ const installations = [];
399
+ const locations = [
400
+ { base: process.cwd(), isGlobal: false },
401
+ { base: os3.homedir(), isGlobal: true }
402
+ ];
403
+ for (const { base, isGlobal } of locations) {
404
+ for (const agent of SUPPORTED_AGENTS) {
405
+ const skillPath = path5.join(base, agent.folderName, "skills", skillName);
406
+ if (await fs5.pathExists(skillPath)) {
407
+ const metadata = await readSkillMetadata(skillPath);
408
+ if (metadata) {
409
+ const contentHash = await computeContentHash(skillPath);
410
+ installations.push({
411
+ agent: agent.flag,
412
+ path: skillPath,
413
+ isGlobal,
414
+ metadata,
415
+ contentHash
416
+ });
417
+ }
418
+ }
419
+ }
420
+ }
421
+ return installations;
422
+ }
423
+ async function listSkillsForAgent(agentFolderName, isGlobal) {
424
+ const base = isGlobal ? os3.homedir() : process.cwd();
425
+ const skillsDir = path5.join(base, agentFolderName, "skills");
426
+ if (!await fs5.pathExists(skillsDir)) {
427
+ return [];
428
+ }
429
+ const entries = await fs5.readdir(skillsDir, { withFileTypes: true });
430
+ const skills = [];
431
+ for (const entry of entries) {
432
+ if (entry.isDirectory()) {
433
+ const skillPath = path5.join(skillsDir, entry.name);
434
+ const skillName = await getSkillName(skillPath);
435
+ if (skillName) {
436
+ skills.push(skillName);
437
+ }
438
+ }
439
+ }
440
+ return skills;
441
+ }
442
+
443
+ // src/core/skill-install.ts
444
+ function getSkillInstallPath(skillName, agentFlag, isGlobal) {
445
+ const agent = getAgentByFlag(agentFlag);
446
+ if (!agent) {
447
+ throw new Error(`Unknown agent: ${agentFlag}`);
448
+ }
449
+ const base = isGlobal ? os4.homedir() : process.cwd();
450
+ return path6.join(base, agent.folderName, "skills", skillName);
451
+ }
452
+ async function installSkillDirectory(skillDir, agentFlag, isGlobal, confirmOverride) {
453
+ const metadata = await readSkillMetadata(skillDir);
454
+ if (!metadata) {
455
+ throw new Error(`Invalid skill at "${skillDir}": SKILL.md must have name and description in frontmatter`);
456
+ }
457
+ const dest = getSkillInstallPath(metadata.name, agentFlag, isGlobal);
458
+ if (await fs6.pathExists(dest)) {
459
+ if (confirmOverride) {
460
+ const shouldOverwrite = await confirmOverride({
461
+ skillName: metadata.name,
462
+ agentFlag,
463
+ isGlobal,
464
+ destPath: dest
465
+ });
466
+ if (!shouldOverwrite) {
467
+ return null;
468
+ }
469
+ }
470
+ }
471
+ await fs6.ensureDir(path6.dirname(dest));
472
+ await fs6.copy(skillDir, dest, { overwrite: true });
473
+ return metadata.name;
474
+ }
475
+ async function installFromLocal(source, agentFlag, isGlobal, confirmOverride) {
476
+ const skillDirs = await findSkillDirectories(source.location);
477
+ if (skillDirs.length === 0) {
478
+ throw new Error(`No skills found in "${source.location}". A skill must contain a SKILL.md file.`);
479
+ }
480
+ const installedSkills = [];
481
+ for (const skillDir of skillDirs) {
482
+ const skillName = await installSkillDirectory(skillDir, agentFlag, isGlobal, confirmOverride);
483
+ if (skillName) {
484
+ installedSkills.push(skillName);
485
+ }
486
+ }
487
+ return installedSkills;
488
+ }
489
+ async function installFromZip(zipUrl, source, agentFlag, isGlobal, confirmOverride) {
490
+ const tempDir = path6.join(os4.tmpdir(), `sun-install-${Date.now()}`);
491
+ try {
492
+ const response = await fetch(zipUrl);
493
+ if (!response.ok) {
494
+ throw new Error(`Failed to download: ${response.statusText}`);
495
+ }
496
+ const buffer = Buffer.from(await response.arrayBuffer());
497
+ const zip = new AdmZip(buffer);
498
+ await fs6.ensureDir(tempDir);
499
+ zip.extractAllTo(tempDir, true);
500
+ const entries = await fs6.readdir(tempDir);
501
+ const extractedDir = entries.length === 1 && (await fs6.stat(path6.join(tempDir, entries[0]))).isDirectory() ? path6.join(tempDir, entries[0]) : tempDir;
502
+ const skillDirs = await findSkillDirectories(extractedDir);
503
+ if (skillDirs.length === 0) {
504
+ throw new Error(`No skills found in "${source.originalInput}". A skill must contain a SKILL.md file.`);
505
+ }
506
+ const installedSkills = [];
507
+ for (const skillDir of skillDirs) {
508
+ const skillName = await installSkillDirectory(skillDir, agentFlag, isGlobal, confirmOverride);
509
+ if (skillName) {
510
+ installedSkills.push(skillName);
511
+ }
512
+ }
513
+ return installedSkills;
514
+ } finally {
515
+ await fs6.remove(tempDir).catch(() => {
516
+ });
517
+ }
518
+ }
519
+ async function installFromGithub(source, agentFlag, isGlobal, confirmOverride) {
520
+ const tempDir = path6.join(os4.tmpdir(), `sun-install-${Date.now()}`);
521
+ try {
522
+ await fs6.ensureDir(tempDir);
523
+ try {
524
+ execFileSync("npx", ["degit", source.location, tempDir], {
525
+ stdio: "pipe"
526
+ });
527
+ } catch (error) {
528
+ const err = error;
529
+ const stderr = err.stderr?.toString() || err.message;
530
+ throw new Error(`Failed to download from GitHub: ${stderr}`);
531
+ }
532
+ const skillDirs = await findSkillDirectories(tempDir);
533
+ if (skillDirs.length === 0) {
534
+ throw new Error(`No skills found in "${source.originalInput}". A skill must contain a SKILL.md file.`);
535
+ }
536
+ const installedSkills = [];
537
+ for (const skillDir of skillDirs) {
538
+ const skillName = await installSkillDirectory(skillDir, agentFlag, isGlobal, confirmOverride);
539
+ if (skillName) {
540
+ installedSkills.push(skillName);
541
+ }
542
+ }
543
+ return installedSkills;
544
+ } finally {
545
+ await fs6.remove(tempDir).catch(() => {
546
+ });
547
+ }
548
+ }
549
+ async function installSkill(skillInput, agentFlag, isGlobal, confirmOverride) {
550
+ const source = await resolveSkillSource(skillInput);
551
+ let skillNames;
552
+ switch (source.type) {
553
+ case "local":
554
+ skillNames = await installFromLocal(source, agentFlag, isGlobal, confirmOverride);
555
+ break;
556
+ case "shortcut": {
557
+ const zipUrl = await getShortcutZipUrl(source.originalInput);
558
+ if (zipUrl) {
559
+ skillNames = await installFromZip(zipUrl, source, agentFlag, isGlobal, confirmOverride);
560
+ } else {
561
+ skillNames = await installFromGithub(source, agentFlag, isGlobal, confirmOverride);
562
+ }
563
+ break;
564
+ }
565
+ case "github":
566
+ skillNames = await installFromGithub(source, agentFlag, isGlobal, confirmOverride);
567
+ break;
568
+ default:
569
+ throw new Error(`Unknown source type: ${source.type}`);
570
+ }
571
+ if (source.type === "shortcut") {
572
+ await trackDownload(source.originalInput);
573
+ }
574
+ return { skillNames, source };
575
+ }
576
+ async function removeSkill(skillName, agentFlag, isGlobal) {
577
+ const dest = getSkillInstallPath(skillName, agentFlag, isGlobal);
578
+ if (await fs6.pathExists(dest)) {
579
+ await fs6.remove(dest);
580
+ return true;
581
+ }
582
+ return false;
583
+ }
584
+
585
+ // src/utils/prompts.ts
586
+ import chalk from "chalk";
587
+ import { confirm } from "@inquirer/prompts";
588
+
589
+ // src/utils/checkbox-extended.ts
590
+ import {
591
+ Separator,
592
+ ValidationError,
593
+ createPrompt,
594
+ isDownKey,
595
+ isEnterKey,
596
+ isNumberKey,
597
+ isSpaceKey,
598
+ isTabKey,
599
+ isUpKey,
600
+ makeTheme,
601
+ useKeypress,
602
+ useMemo,
603
+ usePagination,
604
+ usePrefix,
605
+ useState
606
+ } from "@inquirer/core";
607
+ import { cursorHide } from "@inquirer/ansi";
608
+ import figures from "@inquirer/figures";
609
+ import { styleText } from "util";
610
+ function isSelectable(item) {
611
+ return !Separator.isSeparator(item) && !item.disabled;
612
+ }
613
+ function isChecked(item) {
614
+ return isSelectable(item) && item.checked;
615
+ }
616
+ function toggle(item) {
617
+ return isSelectable(item) ? { ...item, checked: !item.checked } : item;
618
+ }
619
+ function check(checked) {
620
+ return function(item) {
621
+ return isSelectable(item) ? { ...item, checked } : item;
622
+ };
623
+ }
624
+ function normalizeChoices(choices) {
625
+ return choices.map((choice) => {
626
+ if (Separator.isSeparator(choice)) return choice;
627
+ if (typeof choice === "string") {
628
+ return {
629
+ value: choice,
630
+ name: choice,
631
+ short: choice,
632
+ checkedName: choice,
633
+ disabled: false,
634
+ checked: false
635
+ };
636
+ }
637
+ const name = choice.name ?? String(choice.value);
638
+ const normalizedChoice = {
639
+ value: choice.value,
640
+ name,
641
+ short: choice.short ?? name,
642
+ checkedName: choice.checkedName ?? name,
643
+ disabled: choice.disabled ?? false,
644
+ checked: choice.checked ?? false
645
+ };
646
+ if (choice.description) normalizedChoice.description = choice.description;
647
+ return normalizedChoice;
648
+ });
649
+ }
650
+ function nextSelectableIndex(items, from, direction) {
651
+ let next = from;
652
+ do {
653
+ next = (next + direction + items.length) % items.length;
654
+ } while (!isSelectable(items[next]));
655
+ return next;
656
+ }
657
+ var checkboxTheme = {
658
+ icon: {
659
+ // Slightly larger/more visible rings by default vs the upstream checkbox prompt.
660
+ checked: styleText("green", figures.circleFilled),
661
+ unchecked: figures.circleDouble,
662
+ cursor: figures.pointer
663
+ },
664
+ style: {
665
+ disabledChoice: (text) => styleText("dim", `- ${text}`),
666
+ renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(", "),
667
+ description: (text) => styleText("cyan", text),
668
+ keysHelpTip: (keys) => keys.map(([key, action]) => `${styleText("bold", key)} ${styleText("dim", action)}`).join(styleText("dim", " \u2022 "))
669
+ },
670
+ keybindings: []
671
+ };
672
+ var checkboxExtended = createPrompt((config, done) => {
673
+ const { pageSize = 7, loop = true, required, validate = () => true } = config;
674
+ const shortcuts = { all: "a", invert: "i", ...config.shortcuts };
675
+ const theme = makeTheme(checkboxTheme, config.theme);
676
+ const { keybindings } = theme;
677
+ const [status, setStatus] = useState("idle");
678
+ const prefix = usePrefix({ status, theme });
679
+ const [items, setItems] = useState(normalizeChoices(config.choices));
680
+ const bounds = useMemo(() => {
681
+ const first = items.findIndex(isSelectable);
682
+ const last = items.findLastIndex(isSelectable);
683
+ if (first === -1) {
684
+ throw new ValidationError("[checkbox prompt] No selectable choices. All choices are disabled.");
685
+ }
686
+ return { first, last };
687
+ }, [items]);
688
+ const [active, setActive] = useState(bounds.first);
689
+ const [errorMsg, setError] = useState();
690
+ useKeypress(async (key) => {
691
+ if (isEnterKey(key)) {
692
+ const selection = items.filter(isChecked);
693
+ const isValid = await validate([...selection]);
694
+ if (required && !items.some(isChecked)) {
695
+ setError("At least one choice must be selected");
696
+ } else if (isValid === true) {
697
+ setStatus("done");
698
+ done(selection.map((choice) => choice.value));
699
+ } else {
700
+ setError(isValid || "You must select a valid value");
701
+ }
702
+ return;
703
+ }
704
+ if (isUpKey(key, keybindings) || isDownKey(key, keybindings)) {
705
+ if (loop || isUpKey(key, keybindings) && active !== bounds.first || isDownKey(key, keybindings) && active !== bounds.last) {
706
+ const direction = isUpKey(key, keybindings) ? -1 : 1;
707
+ setActive(nextSelectableIndex(items, active, direction));
708
+ }
709
+ return;
710
+ }
711
+ const isLeftRight = key.name === "left" || key.name === "right";
712
+ if (isSpaceKey(key) || isLeftRight) {
713
+ setError(void 0);
714
+ setItems(items.map((choice, i) => i === active ? toggle(choice) : choice));
715
+ return;
716
+ }
717
+ if (isTabKey(key)) {
718
+ setError(void 0);
719
+ setItems(items.map((choice, i) => i === active ? toggle(choice) : choice));
720
+ return;
721
+ }
722
+ if (key.name === shortcuts.all) {
723
+ const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked);
724
+ setItems(items.map(check(selectAll)));
725
+ return;
726
+ }
727
+ if (key.name === shortcuts.invert) {
728
+ setItems(items.map(toggle));
729
+ return;
730
+ }
731
+ if (isNumberKey(key)) {
732
+ const selectedIndex = Number(key.name) - 1;
733
+ let selectableIndex = -1;
734
+ const position = items.findIndex((item) => {
735
+ if (Separator.isSeparator(item)) return false;
736
+ selectableIndex++;
737
+ return selectableIndex === selectedIndex;
738
+ });
739
+ const selectedItem = items[position];
740
+ if (selectedItem && isSelectable(selectedItem)) {
741
+ setActive(position);
742
+ setItems(items.map((choice, i) => i === position ? toggle(choice) : choice));
743
+ }
744
+ }
745
+ });
746
+ const message = theme.style.message(config.message, status);
747
+ let description;
748
+ const page = usePagination({
749
+ items,
750
+ active,
751
+ renderItem({ item, isActive }) {
752
+ if (Separator.isSeparator(item)) return ` ${item.separator}`;
753
+ if (item.disabled) {
754
+ const disabledLabel = typeof item.disabled === "string" ? item.disabled : "(disabled)";
755
+ return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
756
+ }
757
+ if (isActive) description = item.description;
758
+ const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
759
+ const name = item.checked ? item.checkedName : item.name;
760
+ const color = isActive ? theme.style.highlight : (x) => x;
761
+ const cursor = isActive ? theme.icon.cursor : " ";
762
+ return color(`${cursor}${checkbox} ${name}`);
763
+ },
764
+ pageSize,
765
+ loop
766
+ });
767
+ if (status === "done") {
768
+ const selection = items.filter(isChecked);
769
+ const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
770
+ return [prefix, message, answer].filter(Boolean).join(" ");
771
+ }
772
+ const keys = [
773
+ ["\u2191\u2193", "navigate"],
774
+ ["space/tab/\u2190\u2192", "toggle"]
775
+ ];
776
+ if (shortcuts.all) keys.push([shortcuts.all, "all"]);
777
+ if (shortcuts.invert) keys.push([shortcuts.invert, "invert"]);
778
+ keys.push(["\u23CE", "submit"]);
779
+ const helpLine = theme.style.keysHelpTip(keys);
780
+ const lines = [
781
+ [prefix, message].filter(Boolean).join(" "),
782
+ page,
783
+ " ",
784
+ description ? theme.style.description(description) : "",
785
+ errorMsg ? theme.style.error(errorMsg) : "",
786
+ helpLine
787
+ ].filter(Boolean).join("\n").trimEnd();
788
+ return `${lines}${cursorHide}`;
789
+ });
790
+
791
+ // src/utils/prompts.ts
792
+ async function promptAgentSelection(currentDefaults = []) {
793
+ const isFirstRun2 = currentDefaults.length === 0;
794
+ const choices = SUPPORTED_AGENTS.map((agent) => ({
795
+ name: `${agent.name} (~/${agent.folderName}/ and ./${agent.folderName}/)`,
796
+ value: agent.flag,
797
+ // First run: select ALL agents
798
+ // Otherwise: only select if it was in previous defaults
799
+ checked: isFirstRun2 ? true : currentDefaults.includes(agent.flag)
800
+ }));
801
+ const selectedAgents = await checkboxExtended({
802
+ message: "Select default agents:",
803
+ choices,
804
+ required: true,
805
+ theme: {
806
+ icon: {
807
+ // Make the selection marker stand out a bit more.
808
+ checked: chalk.green("\u25C9"),
809
+ unchecked: chalk.white("\u25CE"),
810
+ cursor: chalk.white("\u276F")
811
+ },
812
+ style: {
813
+ // Keep active line readable (esp. on dark terminals) and avoid “cyan highlight”.
814
+ highlight: (text) => chalk.bold.white(text)
815
+ }
816
+ }
817
+ });
818
+ return selectedAgents;
819
+ }
820
+ async function promptSkillOverride(params) {
821
+ const agent = getAgentByFlag(params.agentFlag);
822
+ const agentLabel = agent ? agent.name : params.agentFlag;
823
+ const locationLabel = params.isGlobal ? "global" : "local";
824
+ return confirm({
825
+ message: `Skill "${params.skillName}" already exists in ${agentLabel} (${locationLabel}) at ${params.destPath}. Override?`,
826
+ default: false
827
+ });
828
+ }
829
+
830
+ // src/utils/tree.ts
831
+ import fs7 from "fs-extra";
832
+ import path7 from "path";
833
+ var DEFAULT_IGNORES = /* @__PURE__ */ new Set([".git", ".DS_Store"]);
834
+ function sortEntries(entries) {
835
+ return entries.slice().sort((a, b) => {
836
+ const aIsDir = a.isDirectory();
837
+ const bIsDir = b.isDirectory();
838
+ if (aIsDir !== bIsDir) {
839
+ return aIsDir ? -1 : 1;
840
+ }
841
+ return a.name.localeCompare(b.name);
842
+ });
843
+ }
844
+ async function walkTree(dir, prefix, depth, maxDepth) {
845
+ if (maxDepth !== void 0 && depth > maxDepth) {
846
+ return [];
847
+ }
848
+ let entries;
849
+ try {
850
+ entries = await fs7.readdir(dir, { withFileTypes: true });
851
+ } catch {
852
+ return [];
853
+ }
854
+ const visibleEntries = sortEntries(entries).filter((entry) => !DEFAULT_IGNORES.has(entry.name));
855
+ const lines = [];
856
+ for (let i = 0; i < visibleEntries.length; i++) {
857
+ const entry = visibleEntries[i];
858
+ const isLast = i === visibleEntries.length - 1;
859
+ const connector = isLast ? "\\-- " : "|-- ";
860
+ const line = `${prefix}${connector}${entry.name}`;
861
+ lines.push(line);
862
+ if (entry.isDirectory()) {
863
+ const nextPrefix = `${prefix}${isLast ? " " : "| "}`;
864
+ const childLines = await walkTree(path7.join(dir, entry.name), nextPrefix, depth + 1, maxDepth);
865
+ lines.push(...childLines);
866
+ }
867
+ }
868
+ return lines;
869
+ }
870
+ async function formatDirectoryTree(rootPath, maxDepth) {
871
+ const rootName = path7.basename(rootPath);
872
+ const lines = [rootName];
873
+ const childLines = await walkTree(rootPath, "", 1, maxDepth);
874
+ lines.push(...childLines);
875
+ return lines.join("\n");
876
+ }
877
+ function indentLines(text, indent) {
878
+ return text.split("\n").map((line) => `${indent}${line}`).join("\n");
879
+ }
880
+
881
+ // src/commands/add.ts
882
+ async function resolveTargetAgents(flags) {
883
+ const forceGlobal = flags.global ?? false;
884
+ const explicitAgents = [];
885
+ for (const agent of SUPPORTED_AGENTS) {
886
+ if (flags[agent.flag]) {
887
+ explicitAgents.push(agent.flag);
888
+ }
889
+ }
890
+ let targetAgents;
891
+ if (explicitAgents.length > 0) {
892
+ targetAgents = explicitAgents;
893
+ } else if (await isFirstRun()) {
894
+ const selectedAgents = await promptAgentSelection();
895
+ await setDefaultAgents(selectedAgents);
896
+ targetAgents = selectedAgents;
897
+ } else {
898
+ const defaultAgents = await getDefaultAgents();
899
+ if (defaultAgents.length === 0) {
900
+ throw new Error('No default agents configured. Run "sun config" to set up your agents.');
901
+ }
902
+ targetAgents = defaultAgents;
903
+ }
904
+ if (forceGlobal) {
905
+ return { agents: targetAgents, isGlobal: true };
906
+ }
907
+ const localAgents = await detectLocalAgents();
908
+ const localAgentFlags = new Set(localAgents.map((a) => a.agent.flag));
909
+ const hasLocalFolders = targetAgents.some((agentFlag) => localAgentFlags.has(agentFlag));
910
+ const isGlobal = !hasLocalFolders;
911
+ return { agents: targetAgents, isGlobal };
912
+ }
913
+ function formatList(items) {
914
+ if (items.length === 0) {
915
+ return "";
916
+ }
917
+ if (items.length === 1) {
918
+ return items[0];
919
+ }
920
+ if (items.length === 2) {
921
+ return `${items[0]} or ${items[1]}`;
922
+ }
923
+ return `${items.slice(0, -1).join(", ")}, or ${items[items.length - 1]}`;
924
+ }
925
+ async function addCommand(skills, flags) {
926
+ const { agents, isGlobal } = await resolveTargetAgents(flags);
927
+ const hasExplicitAgentFlags = SUPPORTED_AGENTS.some((agent) => flags[agent.flag]);
928
+ const results = [];
929
+ for (const skill of skills) {
930
+ const spinner = ora(`Adding ${skill}...`).start();
931
+ const confirmOverride = async (params) => {
932
+ if (spinner.isSpinning) {
933
+ spinner.stop();
934
+ }
935
+ return promptSkillOverride(params);
936
+ };
937
+ const result = {
938
+ skill,
939
+ installedNames: [],
940
+ agents: [],
941
+ isGlobal
942
+ };
943
+ try {
944
+ for (const agentFlag of agents) {
945
+ const { skillNames } = await installSkill(skill, agentFlag, isGlobal, confirmOverride);
946
+ if (skillNames.length > 0) {
947
+ result.installedNames = [.../* @__PURE__ */ new Set([...result.installedNames, ...skillNames])];
948
+ result.agents.push(getAgentByFlag(agentFlag).name);
949
+ }
950
+ }
951
+ if (result.installedNames.length === 0) {
952
+ spinner.info(`Skipped ${skill}`);
953
+ } else {
954
+ spinner.succeed(`Added ${result.installedNames.join(", ")}`);
955
+ }
956
+ } catch (error) {
957
+ const message = error instanceof Error ? error.message : String(error);
958
+ result.error = message;
959
+ spinner.fail(`Failed to add ${skill}: ${message}`);
960
+ }
961
+ results.push(result);
962
+ }
963
+ const successful = results.filter((r) => !r.error && r.installedNames.length > 0);
964
+ if (successful.length > 0) {
965
+ const allSkills = [...new Set(successful.flatMap((r) => r.installedNames))];
966
+ const agentFolders = [...new Set(successful.flatMap((r) => r.agents))];
967
+ const primaryAgent = agents[0];
968
+ if (primaryAgent) {
969
+ for (const result of successful) {
970
+ const skillNames = [...new Set(result.installedNames)];
971
+ for (const skillName of skillNames) {
972
+ const treePath = getSkillInstallPath(skillName, primaryAgent, result.isGlobal);
973
+ const tree = await formatDirectoryTree(treePath);
974
+ console.log();
975
+ console.log(chalk2.cyan(`Skill folder for ${skillName}:`));
976
+ console.log(indentLines(tree, " "));
977
+ }
978
+ }
979
+ }
980
+ console.log();
981
+ const location = isGlobal ? "(global)" : "(local)";
982
+ console.log(chalk2.green(`Added ${allSkills.join(", ")} to ${agentFolders.join(" and ")} ${chalk2.gray(location)}`));
983
+ const promptAgents = hasExplicitAgentFlags ? agents : await getDefaultAgents();
984
+ const commandHints = promptAgents.map((command) => `\`${command}\``);
985
+ const plural = allSkills.length > 1 ? "skills" : "skill";
986
+ console.log(chalk2.cyan(`Next: run ${formatList(commandHints)} and ask it to use the downloaded ${plural}`));
987
+ }
988
+ }
989
+
990
+ // src/commands/remove.ts
991
+ import chalk3 from "chalk";
992
+ import ora2 from "ora";
993
+ async function resolveTargetAgents2(flags) {
994
+ const forceGlobal = flags.global ?? false;
995
+ const explicitAgents = [];
996
+ for (const agent of SUPPORTED_AGENTS) {
997
+ if (flags[agent.flag]) {
998
+ explicitAgents.push(agent.flag);
999
+ }
1000
+ }
1001
+ let targetAgents;
1002
+ if (explicitAgents.length > 0) {
1003
+ targetAgents = explicitAgents;
1004
+ } else {
1005
+ const defaultAgents = await getDefaultAgents();
1006
+ if (defaultAgents.length === 0) {
1007
+ throw new Error('No default agents configured. Run "sun add" first or use --claude/--codex/--gemini flags.');
1008
+ }
1009
+ targetAgents = defaultAgents;
1010
+ }
1011
+ if (forceGlobal) {
1012
+ return { agents: targetAgents, isGlobal: true };
1013
+ }
1014
+ const localAgents = await detectLocalAgents();
1015
+ const localAgentFlags = new Set(localAgents.map((a) => a.agent.flag));
1016
+ const hasLocalFolders = targetAgents.some((agentFlag) => localAgentFlags.has(agentFlag));
1017
+ const isGlobal = !hasLocalFolders;
1018
+ return { agents: targetAgents, isGlobal };
1019
+ }
1020
+ async function removeCommand(skills, flags) {
1021
+ let agents;
1022
+ let isGlobal;
1023
+ try {
1024
+ const resolved = await resolveTargetAgents2(flags);
1025
+ agents = resolved.agents;
1026
+ isGlobal = resolved.isGlobal;
1027
+ } catch (error) {
1028
+ console.error(chalk3.red(error instanceof Error ? error.message : String(error)));
1029
+ process.exit(1);
1030
+ }
1031
+ const results = [];
1032
+ for (const skill of skills) {
1033
+ const spinner = ora2(`Removing ${skill}...`).start();
1034
+ const result = {
1035
+ skill,
1036
+ removedFrom: [],
1037
+ notFoundIn: [],
1038
+ isGlobal
1039
+ };
1040
+ try {
1041
+ for (const agentFlag of agents) {
1042
+ const removed = await removeSkill(skill, agentFlag, isGlobal);
1043
+ const agentName = getAgentByFlag(agentFlag).name;
1044
+ if (removed) {
1045
+ result.removedFrom.push(agentName);
1046
+ } else {
1047
+ result.notFoundIn.push(agentName);
1048
+ }
1049
+ }
1050
+ if (result.removedFrom.length > 0) {
1051
+ spinner.succeed(`Removed ${skill} from ${result.removedFrom.join(" and ")}`);
1052
+ } else {
1053
+ const pathPrefix = isGlobal ? "~/" : "./";
1054
+ const checkedPaths = agents.map((a) => `${pathPrefix}${getAgentByFlag(a).folderName}/`);
1055
+ spinner.warn(`${skill} not found in configured agents (${checkedPaths.join(", ")})`);
1056
+ }
1057
+ if (result.notFoundIn.length > 0 && result.removedFrom.length > 0) {
1058
+ console.log(chalk3.gray(` (not found in: ${result.notFoundIn.join(", ")})`));
1059
+ }
1060
+ } catch (error) {
1061
+ const message = error instanceof Error ? error.message : String(error);
1062
+ result.error = message;
1063
+ spinner.fail(`Failed to remove ${skill}: ${message}`);
1064
+ }
1065
+ results.push(result);
1066
+ }
1067
+ if (skills.length > 1) {
1068
+ const totalRemoved = results.filter((r) => r.removedFrom.length > 0).length;
1069
+ const totalFailed = results.filter((r) => r.error || r.removedFrom.length === 0).length;
1070
+ console.log();
1071
+ if (totalRemoved > 0) {
1072
+ console.log(chalk3.green(`Removed ${totalRemoved} skill(s)`));
1073
+ }
1074
+ if (totalFailed > 0) {
1075
+ console.log(chalk3.yellow(`${totalFailed} skill(s) not found or failed`));
1076
+ }
1077
+ }
1078
+ }
1079
+
1080
+ // src/commands/list.ts
1081
+ import chalk4 from "chalk";
1082
+ async function listCommand() {
1083
+ const allAgentSkills = [];
1084
+ for (const agent of SUPPORTED_AGENTS) {
1085
+ const localSkills = await listSkillsForAgent(agent.folderName, false);
1086
+ if (localSkills.length > 0) {
1087
+ allAgentSkills.push({
1088
+ agentName: agent.name,
1089
+ folderName: agent.folderName,
1090
+ isGlobal: false,
1091
+ skills: localSkills
1092
+ });
1093
+ }
1094
+ const globalSkills = await listSkillsForAgent(agent.folderName, true);
1095
+ if (globalSkills.length > 0) {
1096
+ allAgentSkills.push({
1097
+ agentName: agent.name,
1098
+ folderName: agent.folderName,
1099
+ isGlobal: true,
1100
+ skills: globalSkills
1101
+ });
1102
+ }
1103
+ }
1104
+ if (allAgentSkills.length === 0) {
1105
+ console.log(chalk4.yellow("No skills installed."));
1106
+ console.log();
1107
+ console.log(chalk4.gray(getSupportedAgentsMessage()));
1108
+ console.log(chalk4.gray('Browse curated skills with "sun list"'));
1109
+ console.log(chalk4.gray('You can add from the library, GitHub URLs, or local paths with "sun add <skill>"'));
1110
+ return;
1111
+ }
1112
+ for (const agentSkills of allAgentSkills) {
1113
+ const location = agentSkills.isGlobal ? `global ~/${agentSkills.folderName}/` : `${agentSkills.folderName}/`;
1114
+ console.log(chalk4.cyan(`${agentSkills.agentName} (${location}):`));
1115
+ for (const skill of agentSkills.skills) {
1116
+ console.log(` - ${skill}`);
1117
+ }
1118
+ console.log();
1119
+ }
1120
+ console.log(chalk4.gray(getSupportedAgentsMessage()));
1121
+ console.log(chalk4.gray('Browse curated skills with "sun list"'));
1122
+ console.log(chalk4.gray('Add from the library, GitHub URLs, or local paths with "sun add <skill>"'));
1123
+ console.log(chalk4.gray('Remove with "sun remove <skill>"'));
1124
+ }
1125
+
1126
+ // src/commands/list-registry.ts
1127
+ import chalk5 from "chalk";
1128
+ async function listRegistryCommand() {
1129
+ const skills = await getSkillsFromRegistry();
1130
+ if (skills.length === 0) {
1131
+ console.log(chalk5.yellow("No skills available in the Sundial library."));
1132
+ console.log(chalk5.gray("You can still add from GitHub URLs or local paths."));
1133
+ console.log(chalk5.gray("Example: sun add github.com/user/skill or sun add ./my-skill"));
1134
+ return;
1135
+ }
1136
+ const sorted = [...skills].sort((a, b) => a.name.localeCompare(b.name));
1137
+ console.log(chalk5.cyan(`Available skills from the Sundial library (${sorted.length}):`));
1138
+ for (const skill of sorted) {
1139
+ const description = skill.description?.trim();
1140
+ const author = skill.author?.trim();
1141
+ const descriptionText = description ? ` - ${description}` : "";
1142
+ const authorText = author ? ` (by ${author})` : "";
1143
+ console.log(` - ${skill.name}${chalk5.gray(descriptionText + authorText)}`);
1144
+ }
1145
+ console.log();
1146
+ console.log(chalk5.white('Install from the library with "sun add <skill>".'));
1147
+ console.log(chalk5.white("You can also add from GitHub URLs or local paths."));
1148
+ }
1149
+
1150
+ // src/commands/show.ts
1151
+ import chalk7 from "chalk";
1152
+ import path9 from "path";
1153
+
1154
+ // src/commands/show-dev.ts
1155
+ import chalk6 from "chalk";
1156
+ import path8 from "path";
1157
+ import os5 from "os";
1158
+ import fs8 from "fs-extra";
1159
+ import { exec as exec2 } from "child_process";
1160
+ import { promisify as promisify2 } from "util";
1161
+ var execAsync2 = promisify2(exec2);
1162
+ var FIND_TIMEOUT = 6e4;
1163
+ async function listSkillsInDir(skillsDir) {
1164
+ const skills = [];
1165
+ if (!await fs8.pathExists(skillsDir)) {
1166
+ return skills;
1167
+ }
1168
+ const entries = await fs8.readdir(skillsDir, { withFileTypes: true });
1169
+ for (const entry of entries) {
1170
+ if (entry.isDirectory()) {
1171
+ const skillPath = path8.join(skillsDir, entry.name);
1172
+ const metadata = await readSkillMetadata(skillPath);
1173
+ if (metadata) {
1174
+ skills.push({
1175
+ name: metadata.name,
1176
+ description: metadata.description
1177
+ });
1178
+ }
1179
+ }
1180
+ }
1181
+ return skills;
1182
+ }
1183
+ async function showAllAgentFolders() {
1184
+ const homeDir = os5.homedir();
1185
+ const namePatterns = SUPPORTED_AGENTS.map((a) => `-name "${a.folderName}"`).join(" -o ");
1186
+ const findCmd = `find "${homeDir}" -type d \\( ${namePatterns} \\)`;
1187
+ console.log(chalk6.cyan("All Agent Folders"));
1188
+ console.log(chalk6.gray("\u2500".repeat(40)));
1189
+ console.log();
1190
+ let stdout = "";
1191
+ try {
1192
+ const result = await execAsync2(findCmd, { maxBuffer: 10 * 1024 * 1024, timeout: FIND_TIMEOUT });
1193
+ stdout = result.stdout;
1194
+ } catch (err) {
1195
+ if (err.stdout) {
1196
+ stdout = err.stdout;
1197
+ } else {
1198
+ console.log(chalk6.red("Failed to search for agent folders."));
1199
+ console.log(chalk6.gray(String(err)));
1200
+ return;
1201
+ }
1202
+ }
1203
+ const agentPaths = stdout.trim().split("\n").filter(Boolean);
1204
+ if (agentPaths.length === 0) {
1205
+ console.log(chalk6.yellow("No agent folders found."));
1206
+ return;
1207
+ }
1208
+ for (const agentPath of agentPaths) {
1209
+ const folderName = path8.basename(agentPath);
1210
+ const agent = SUPPORTED_AGENTS.find((a) => a.folderName === folderName);
1211
+ if (!agent) continue;
1212
+ console.log(chalk6.white.bold(agent.name));
1213
+ console.log(chalk6.gray(` ${agentPath}`));
1214
+ const skillsDir = path8.join(agentPath, "skills");
1215
+ const skills = await listSkillsInDir(skillsDir);
1216
+ if (skills.length === 0) {
1217
+ console.log(chalk6.gray(" No skills installed"));
1218
+ } else {
1219
+ console.log(` Skills (${skills.length}):`);
1220
+ for (const skill of skills) {
1221
+ console.log(` - ${skill.name} ${chalk6.gray(`- ${skill.description}`)}`);
1222
+ }
1223
+ }
1224
+ console.log();
1225
+ }
1226
+ }
1227
+ async function showAllAgentSkillsFolders() {
1228
+ const homeDir = os5.homedir();
1229
+ const pathPatterns = SUPPORTED_AGENTS.map((a) => `-path "*/${a.folderName}/skills"`).join(" -o ");
1230
+ const findCmd = `find "${homeDir}" -type d \\( ${pathPatterns} \\)`;
1231
+ console.log(chalk6.cyan("All Agent Skills Folders"));
1232
+ console.log(chalk6.gray("\u2500".repeat(40)));
1233
+ console.log();
1234
+ let stdout = "";
1235
+ try {
1236
+ const result = await execAsync2(findCmd, { maxBuffer: 10 * 1024 * 1024, timeout: FIND_TIMEOUT });
1237
+ stdout = result.stdout;
1238
+ } catch (err) {
1239
+ if (err.stdout) {
1240
+ stdout = err.stdout;
1241
+ } else {
1242
+ console.log(chalk6.red("Failed to search for skills folders."));
1243
+ console.log(chalk6.gray(String(err)));
1244
+ return;
1245
+ }
1246
+ }
1247
+ const skillsDirs = stdout.trim().split("\n").filter(Boolean);
1248
+ if (skillsDirs.length === 0) {
1249
+ console.log(chalk6.yellow("No agent skills folders found."));
1250
+ return;
1251
+ }
1252
+ for (const skillsDir of skillsDirs) {
1253
+ const agentFolder = path8.basename(path8.dirname(skillsDir));
1254
+ const agent = SUPPORTED_AGENTS.find((a) => a.folderName === agentFolder);
1255
+ console.log(chalk6.white.bold(agent?.name ?? agentFolder));
1256
+ console.log(chalk6.gray(` ${skillsDir}`));
1257
+ const skills = await listSkillsInDir(skillsDir);
1258
+ if (skills.length === 0) {
1259
+ console.log(chalk6.gray(" No skills installed"));
1260
+ } else {
1261
+ console.log(` Skills (${skills.length}):`);
1262
+ for (const skill of skills) {
1263
+ console.log(` - ${skill.name} ${chalk6.gray(`- ${skill.description}`)}`);
1264
+ }
1265
+ }
1266
+ console.log();
1267
+ }
1268
+ }
1269
+ async function showAllSkillFolders() {
1270
+ const homeDir = os5.homedir();
1271
+ const findCmd = `find "${homeDir}" -name "SKILL.md" -type f`;
1272
+ console.log(chalk6.cyan("All Skill Folders"));
1273
+ console.log(chalk6.gray("\u2500".repeat(40)));
1274
+ console.log();
1275
+ let stdout = "";
1276
+ try {
1277
+ const result = await execAsync2(findCmd, { maxBuffer: 10 * 1024 * 1024, timeout: FIND_TIMEOUT });
1278
+ stdout = result.stdout;
1279
+ } catch (err) {
1280
+ if (err.stdout) {
1281
+ stdout = err.stdout;
1282
+ } else {
1283
+ console.log(chalk6.red("Failed to search for SKILL.md files."));
1284
+ console.log(chalk6.gray(String(err)));
1285
+ return;
1286
+ }
1287
+ }
1288
+ const skillMdPaths = stdout.trim().split("\n").filter(Boolean);
1289
+ if (skillMdPaths.length === 0) {
1290
+ console.log(chalk6.yellow("No SKILL.md files found."));
1291
+ return;
1292
+ }
1293
+ let validCount = 0;
1294
+ for (const skillMdPath of skillMdPaths) {
1295
+ const skillDir = path8.dirname(skillMdPath);
1296
+ const metadata = await readSkillMetadata(skillDir);
1297
+ if (metadata && metadata.name && metadata.description) {
1298
+ validCount++;
1299
+ console.log(chalk6.white.bold(metadata.name));
1300
+ console.log(chalk6.gray(` ${skillDir}`));
1301
+ console.log(` ${metadata.description}`);
1302
+ console.log();
1303
+ }
1304
+ }
1305
+ if (validCount === 0) {
1306
+ console.log(chalk6.yellow("No valid SKILL.md files found (missing name/description frontmatter)."));
1307
+ }
1308
+ }
1309
+
1310
+ // src/commands/show.ts
1311
+ import fs9 from "fs-extra";
1312
+ async function listSkillsInPath(skillsDir) {
1313
+ const skills = [];
1314
+ if (!await fs9.pathExists(skillsDir)) {
1315
+ return skills;
1316
+ }
1317
+ const entries = await fs9.readdir(skillsDir, { withFileTypes: true });
1318
+ for (const entry of entries) {
1319
+ if (entry.isDirectory()) {
1320
+ const skillPath = path9.join(skillsDir, entry.name);
1321
+ const metadata = await readSkillMetadata(skillPath);
1322
+ if (metadata) {
1323
+ skills.push({
1324
+ name: metadata.name,
1325
+ description: metadata.description
1326
+ });
1327
+ }
1328
+ }
1329
+ }
1330
+ return skills;
1331
+ }
1332
+ async function showAllAgents() {
1333
+ const allAgents = await detectAllAgents();
1334
+ const defaultAgents = await getDefaultAgents();
1335
+ const defaultSet = new Set(defaultAgents);
1336
+ const localAgents = await detectLocalAgents();
1337
+ const localAgentFlags = new Set(localAgents.map((a) => a.agent.flag));
1338
+ if (allAgents.length === 0) {
1339
+ console.log(chalk7.yellow("No agent folders found."));
1340
+ console.log(chalk7.gray("Agent folders are directories like .claude/, .codex/, .gemini/"));
1341
+ return;
1342
+ }
1343
+ console.log(chalk7.cyan("Agent Folders & Installed Skills"));
1344
+ console.log(chalk7.gray("\u2500".repeat(40)));
1345
+ console.log();
1346
+ for (const detected of allAgents) {
1347
+ const { agent, path: agentPath, isGlobal } = detected;
1348
+ const locationLabel = isGlobal ? chalk7.gray("(global)") : chalk7.gray("(local)");
1349
+ const isDefault = defaultSet.has(agent.flag);
1350
+ const hasLocalFolder = localAgentFlags.has(agent.flag);
1351
+ const wouldBeAffected = isDefault && (isGlobal ? !hasLocalFolder : true);
1352
+ const marker = wouldBeAffected ? chalk7.green("\u2713") : chalk7.gray("\u25CB");
1353
+ console.log(`${marker} ${chalk7.white.bold(agent.name)} ${locationLabel}`);
1354
+ console.log(chalk7.gray(` ${agentPath}`));
1355
+ const skillsDir = path9.join(agentPath, "skills");
1356
+ const skills = await listSkillsInPath(skillsDir);
1357
+ if (skills.length === 0) {
1358
+ console.log(chalk7.gray(" No skills installed"));
1359
+ } else {
1360
+ console.log(` Skills (${skills.length}):`);
1361
+ for (const skill of skills) {
1362
+ console.log(` - ${skill.name} ${chalk7.gray(`- ${skill.description}`)}`);
1363
+ }
1364
+ }
1365
+ console.log();
1366
+ }
1367
+ console.log(`${chalk7.green("\u2713")} = affected by sun add/remove in this directory`);
1368
+ console.log('Run "sun config" to change selected agents.');
1369
+ }
1370
+ async function showCommand(skillName) {
1371
+ if (skillName === "all-agent-folders") {
1372
+ await showAllAgentFolders();
1373
+ return;
1374
+ }
1375
+ if (skillName === "all-agent-skills-folders") {
1376
+ await showAllAgentSkillsFolders();
1377
+ return;
1378
+ }
1379
+ if (skillName === "all-skill-folders") {
1380
+ await showAllSkillFolders();
1381
+ return;
1382
+ }
1383
+ if (!skillName) {
1384
+ await showAllAgents();
1385
+ return;
1386
+ }
1387
+ const installations = await findSkillInstallations(skillName);
1388
+ if (installations.length === 0) {
1389
+ console.error(chalk7.yellow(`Skill "${skillName}" is not installed.`));
1390
+ process.exit(1);
1391
+ }
1392
+ const uniqueHashes = new Set(installations.map((i) => i.contentHash));
1393
+ const hasMultipleVersions = uniqueHashes.size > 1;
1394
+ if (hasMultipleVersions) {
1395
+ console.log(chalk7.cyan(`Skill: ${skillName}`));
1396
+ console.log(chalk7.yellow("Warning: Multiple versions detected"));
1397
+ console.log();
1398
+ const byHash = /* @__PURE__ */ new Map();
1399
+ for (const inst of installations) {
1400
+ const existing = byHash.get(inst.contentHash) || [];
1401
+ existing.push(inst);
1402
+ byHash.set(inst.contentHash, existing);
1403
+ }
1404
+ let versionNum = 1;
1405
+ for (const [hash, insts] of byHash) {
1406
+ const firstInst = insts[0];
1407
+ const locations = insts.map((i) => {
1408
+ const agent = getAgentByFlag(i.agent);
1409
+ return i.isGlobal ? `~/${agent.folderName}/` : `${agent.folderName}/`;
1410
+ });
1411
+ console.log(chalk7.white(`Version ${versionNum} (${locations.join(", ")}):`));
1412
+ console.log(` Description: ${firstInst.metadata.description}`);
1413
+ if (firstInst.metadata.metadata?.author) {
1414
+ console.log(` Author: ${firstInst.metadata.metadata.author}`);
1415
+ }
1416
+ if (firstInst.metadata.metadata?.version) {
1417
+ console.log(` Version: ${firstInst.metadata.metadata.version}`);
1418
+ }
1419
+ if (firstInst.metadata.license) {
1420
+ console.log(` License: ${firstInst.metadata.license}`);
1421
+ }
1422
+ console.log(` Content hash: ${hash}`);
1423
+ const tree = await formatDirectoryTree(firstInst.path);
1424
+ console.log(" Tree:");
1425
+ console.log(indentLines(tree, " "));
1426
+ console.log();
1427
+ versionNum++;
1428
+ }
1429
+ } else {
1430
+ const firstInst = installations[0];
1431
+ console.log(chalk7.cyan(`Skill: ${skillName}`));
1432
+ console.log(`Description: ${firstInst.metadata.description}`);
1433
+ if (firstInst.metadata.metadata?.author) {
1434
+ console.log(`Author: ${firstInst.metadata.metadata.author}`);
1435
+ }
1436
+ if (firstInst.metadata.metadata?.version) {
1437
+ console.log(`Version: ${firstInst.metadata.metadata.version}`);
1438
+ }
1439
+ if (firstInst.metadata.license) {
1440
+ console.log(`License: ${firstInst.metadata.license}`);
1441
+ }
1442
+ if (firstInst.metadata.compatibility) {
1443
+ console.log(`Compatibility: ${firstInst.metadata.compatibility}`);
1444
+ }
1445
+ console.log();
1446
+ console.log("Installed in:");
1447
+ for (const inst of installations) {
1448
+ const agent = getAgentByFlag(inst.agent);
1449
+ const base = inst.isGlobal ? `~/${agent.folderName}/` : `${agent.folderName}/`;
1450
+ const location = inst.isGlobal ? "(global)" : "(local)";
1451
+ console.log(` - ${base}skills/${skillName} ${chalk7.gray(location)}`);
1452
+ }
1453
+ const tree = await formatDirectoryTree(firstInst.path);
1454
+ console.log();
1455
+ console.log("Skill folder:");
1456
+ console.log(indentLines(tree, " "));
1457
+ }
1458
+ }
1459
+
1460
+ // src/commands/config.ts
1461
+ import chalk8 from "chalk";
1462
+ async function configCommand() {
1463
+ const config = await loadConfig();
1464
+ console.log(chalk8.cyan("Sundial CLI Configuration"));
1465
+ console.log(chalk8.gray(`Config file: ${getConfigPath()}`));
1466
+ console.log();
1467
+ if (config.defaultAgents.length > 0) {
1468
+ console.log("Current default agents:");
1469
+ for (const agentFlag of config.defaultAgents) {
1470
+ const agent = SUPPORTED_AGENTS.find((a) => a.flag === agentFlag);
1471
+ if (agent) {
1472
+ console.log(` - ${agent.name} ${chalk8.gray(`(~/${agent.folderName}/ and ./${agent.folderName}/)`)}`);
1473
+ }
1474
+ }
1475
+ console.log();
1476
+ }
1477
+ const selectedAgents = await promptAgentSelection(config.defaultAgents);
1478
+ await setDefaultAgents(selectedAgents);
1479
+ console.log();
1480
+ console.log(chalk8.green("Configuration saved!"));
1481
+ console.log("New default agents:");
1482
+ for (const agentFlag of selectedAgents) {
1483
+ const agent = SUPPORTED_AGENTS.find((a) => a.flag === agentFlag);
1484
+ if (agent) {
1485
+ console.log(` - ${agent.name} ${chalk8.gray(`(~/${agent.folderName}/ and ./${agent.folderName}/)`)}`);
1486
+ }
1487
+ }
1488
+ }
1489
+
1490
+ // src/utils/fuzzy-match.ts
1491
+ import fuzzysort from "fuzzysort";
1492
+ function findClosestCommand(input) {
1493
+ const results = fuzzysort.go(input, COMMANDS, {
1494
+ threshold: -Infinity
1495
+ });
1496
+ if (results.length === 0) {
1497
+ return null;
1498
+ }
1499
+ const best = results[0];
1500
+ return {
1501
+ command: best.target,
1502
+ score: best.score
1503
+ };
1504
+ }
1505
+ function suggestCommand(input) {
1506
+ const match = findClosestCommand(input);
1507
+ if (match && match.score > -100) {
1508
+ return match.command;
1509
+ }
1510
+ return null;
1511
+ }
1512
+ function getValidCommands() {
1513
+ return [...COMMANDS];
1514
+ }
1515
+
1516
+ // src/index.ts
1517
+ var require2 = createRequire(import.meta.url);
1518
+ var { version } = require2("../package.json");
1519
+ var program = new Command();
1520
+ function isPromptExit(error) {
1521
+ return error instanceof Error && error.name === "ExitPromptError";
1522
+ }
1523
+ function handleCommandError(error) {
1524
+ if (isPromptExit(error)) {
1525
+ process.exit(130);
1526
+ }
1527
+ console.error(chalk9.red(error instanceof Error ? error.message : String(error)));
1528
+ process.exit(1);
1529
+ }
1530
+ program.name("sun").description("Sundial CLI - Manage skills for your AI agents").version(version);
1531
+ var add = program.command("add <skills...>").description("Add skill(s) to agent configuration(s)").option("--global", "Install to global agent config (~/.claude/, ~/.codex/, etc.)");
1532
+ for (const agent of SUPPORTED_AGENTS) {
1533
+ add.option(`--${agent.flag}`, `Install to ${agent.name}`);
1534
+ }
1535
+ add.action(async (skills, options) => {
1536
+ try {
1537
+ await addCommand(skills, options);
1538
+ } catch (error) {
1539
+ handleCommandError(error);
1540
+ }
1541
+ });
1542
+ var remove = program.command("remove <skills...>").description("Remove skill(s) from agent configuration(s)").option("--global", "Remove from global config");
1543
+ for (const agent of SUPPORTED_AGENTS) {
1544
+ remove.option(`--${agent.flag}`, `Remove from ${agent.name}`);
1545
+ }
1546
+ remove.action(async (skills, options) => {
1547
+ try {
1548
+ await removeCommand(skills, options);
1549
+ } catch (error) {
1550
+ handleCommandError(error);
1551
+ }
1552
+ });
1553
+ program.command("list").description("List available skills from the registry").action(async () => {
1554
+ try {
1555
+ await listRegistryCommand();
1556
+ } catch (error) {
1557
+ handleCommandError(error);
1558
+ }
1559
+ });
1560
+ program.command("installed").alias("list-installed").description("List installed skills for each agent").action(async () => {
1561
+ try {
1562
+ await listCommand();
1563
+ } catch (error) {
1564
+ handleCommandError(error);
1565
+ }
1566
+ });
1567
+ program.command("show [skill]").description("Show all agent folders and packages, or details for a specific skill").action(async (skill) => {
1568
+ try {
1569
+ await showCommand(skill);
1570
+ } catch (error) {
1571
+ handleCommandError(error);
1572
+ }
1573
+ });
1574
+ program.command("config").description("Configure default agents").action(async () => {
1575
+ try {
1576
+ await configCommand();
1577
+ } catch (error) {
1578
+ handleCommandError(error);
1579
+ }
1580
+ });
1581
+ program.on("command:*", (operands) => {
1582
+ const unknownCommand = operands[0];
1583
+ const suggestion = suggestCommand(unknownCommand);
1584
+ console.error(chalk9.red(`Error: Unknown command '${unknownCommand}'`));
1585
+ if (suggestion) {
1586
+ console.error(chalk9.yellow(`Did you mean '${suggestion}'?`));
1587
+ }
1588
+ console.error();
1589
+ console.error(`Valid commands: ${getValidCommands().join(", ")}`);
1590
+ process.exit(1);
1591
+ });
1592
+ program.parse();
1593
+ //# sourceMappingURL=index.js.map