@sundial-ai/cli 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.
Files changed (108) hide show
  1. package/DEV.md +58 -0
  2. package/README.md +30 -0
  3. package/dist/commands/add.d.ts +13 -0
  4. package/dist/commands/add.d.ts.map +1 -0
  5. package/dist/commands/add.js +111 -0
  6. package/dist/commands/add.js.map +1 -0
  7. package/dist/commands/config.d.ts +5 -0
  8. package/dist/commands/config.d.ts.map +1 -0
  9. package/dist/commands/config.js +40 -0
  10. package/dist/commands/config.js.map +1 -0
  11. package/dist/commands/list.d.ts +5 -0
  12. package/dist/commands/list.d.ts.map +1 -0
  13. package/dist/commands/list.js +53 -0
  14. package/dist/commands/list.js.map +1 -0
  15. package/dist/commands/remove.d.ts +13 -0
  16. package/dist/commands/remove.d.ts.map +1 -0
  17. package/dist/commands/remove.js +129 -0
  18. package/dist/commands/remove.js.map +1 -0
  19. package/dist/commands/show-dev.d.ts +20 -0
  20. package/dist/commands/show-dev.d.ts.map +1 -0
  21. package/dist/commands/show-dev.js +195 -0
  22. package/dist/commands/show-dev.js.map +1 -0
  23. package/dist/commands/show.d.ts +11 -0
  24. package/dist/commands/show.d.ts.map +1 -0
  25. package/dist/commands/show.js +175 -0
  26. package/dist/commands/show.js.map +1 -0
  27. package/dist/core/agent-detect.d.ts +22 -0
  28. package/dist/core/agent-detect.d.ts.map +1 -0
  29. package/dist/core/agent-detect.js +107 -0
  30. package/dist/core/agent-detect.js.map +1 -0
  31. package/dist/core/agents.d.ts +8 -0
  32. package/dist/core/agents.d.ts.map +1 -0
  33. package/dist/core/agents.js +34 -0
  34. package/dist/core/agents.js.map +1 -0
  35. package/dist/core/config-manager.d.ts +9 -0
  36. package/dist/core/config-manager.d.ts.map +1 -0
  37. package/dist/core/config-manager.js +47 -0
  38. package/dist/core/config-manager.js.map +1 -0
  39. package/dist/core/skill-hash.d.ts +12 -0
  40. package/dist/core/skill-hash.d.ts.map +1 -0
  41. package/dist/core/skill-hash.js +53 -0
  42. package/dist/core/skill-hash.js.map +1 -0
  43. package/dist/core/skill-info.d.ts +34 -0
  44. package/dist/core/skill-info.d.ts.map +1 -0
  45. package/dist/core/skill-info.js +213 -0
  46. package/dist/core/skill-info.js.map +1 -0
  47. package/dist/core/skill-install.d.ts +24 -0
  48. package/dist/core/skill-install.d.ts.map +1 -0
  49. package/dist/core/skill-install.js +123 -0
  50. package/dist/core/skill-install.js.map +1 -0
  51. package/dist/core/skill-source.d.ts +29 -0
  52. package/dist/core/skill-source.d.ts.map +1 -0
  53. package/dist/core/skill-source.js +111 -0
  54. package/dist/core/skill-source.js.map +1 -0
  55. package/dist/index.d.ts +3 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +104 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/types/index.d.ts +57 -0
  60. package/dist/types/index.d.ts.map +1 -0
  61. package/dist/types/index.js +2 -0
  62. package/dist/types/index.js.map +1 -0
  63. package/dist/utils/fuzzy-match.d.ts +16 -0
  64. package/dist/utils/fuzzy-match.d.ts.map +1 -0
  65. package/dist/utils/fuzzy-match.js +37 -0
  66. package/dist/utils/fuzzy-match.js.map +1 -0
  67. package/dist/utils/prompts.d.ts +16 -0
  68. package/dist/utils/prompts.d.ts.map +1 -0
  69. package/dist/utils/prompts.js +46 -0
  70. package/dist/utils/prompts.js.map +1 -0
  71. package/dist/utils/registry.d.ts +6 -0
  72. package/dist/utils/registry.d.ts.map +1 -0
  73. package/dist/utils/registry.js +14 -0
  74. package/dist/utils/registry.js.map +1 -0
  75. package/package.json +42 -0
  76. package/src/commands/add.ts +136 -0
  77. package/src/commands/config.ts +47 -0
  78. package/src/commands/list.ts +68 -0
  79. package/src/commands/remove.ts +154 -0
  80. package/src/commands/show-dev.ts +223 -0
  81. package/src/commands/show.ts +203 -0
  82. package/src/core/agent-detect.ts +125 -0
  83. package/src/core/agents.ts +40 -0
  84. package/src/core/config-manager.ts +55 -0
  85. package/src/core/skill-hash.ts +61 -0
  86. package/src/core/skill-info.ts +248 -0
  87. package/src/core/skill-install.ts +165 -0
  88. package/src/core/skill-source.ts +125 -0
  89. package/src/index.ts +116 -0
  90. package/src/types/index.ts +64 -0
  91. package/src/utils/fuzzy-match.ts +48 -0
  92. package/src/utils/prompts.ts +54 -0
  93. package/src/utils/registry.ts +16 -0
  94. package/test/README.md +123 -0
  95. package/test/fixtures/multi-skills/skill-one/SKILL.md +8 -0
  96. package/test/fixtures/multi-skills/skill-two/SKILL.md +8 -0
  97. package/test/fixtures/sample-skill/SKILL.md +8 -0
  98. package/test/logs/add-remove.log +108 -0
  99. package/test/logs/config.log +72 -0
  100. package/test/logs/fuzzy-match.log +64 -0
  101. package/test/logs/show.log +110 -0
  102. package/test/run-all.sh +83 -0
  103. package/test/test-add-remove.sh +245 -0
  104. package/test/test-config.sh +208 -0
  105. package/test/test-fuzzy-match.sh +166 -0
  106. package/test/test-show.sh +179 -0
  107. package/tsconfig.json +20 -0
  108. package/vitest.config.ts +15 -0
@@ -0,0 +1,47 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ const CONFIG_DIR = path.join(os.homedir(), '.sun');
5
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
6
+ const DEFAULT_CONFIG = {
7
+ defaultAgents: [],
8
+ firstRunComplete: false
9
+ };
10
+ export async function ensureConfigDir() {
11
+ await fs.ensureDir(CONFIG_DIR);
12
+ }
13
+ export async function loadConfig() {
14
+ try {
15
+ await ensureConfigDir();
16
+ if (await fs.pathExists(CONFIG_FILE)) {
17
+ const content = await fs.readFile(CONFIG_FILE, 'utf-8');
18
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
19
+ }
20
+ }
21
+ catch {
22
+ // Config doesn't exist or is invalid, return default
23
+ }
24
+ return { ...DEFAULT_CONFIG };
25
+ }
26
+ export async function saveConfig(config) {
27
+ await ensureConfigDir();
28
+ await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
29
+ }
30
+ export async function isFirstRun() {
31
+ const config = await loadConfig();
32
+ return !config.firstRunComplete;
33
+ }
34
+ export async function getDefaultAgents() {
35
+ const config = await loadConfig();
36
+ return config.defaultAgents;
37
+ }
38
+ export async function setDefaultAgents(agents) {
39
+ const config = await loadConfig();
40
+ config.defaultAgents = agents;
41
+ config.firstRunComplete = true;
42
+ await saveConfig(config);
43
+ }
44
+ export function getConfigPath() {
45
+ return CONFIG_FILE;
46
+ }
47
+ //# sourceMappingURL=config-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-manager.js","sourceRoot":"","sources":["../../src/core/config-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AACnD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,cAAc,GAAc;IAChC,aAAa,EAAE,EAAE;IACjB,gBAAgB,EAAE,KAAK;CACxB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,eAAe,EAAE,CAAC;QACxB,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YACxD,OAAO,EAAE,GAAG,cAAc,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;IACvD,CAAC;IACD,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAiB;IAChD,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,OAAO,MAAM,CAAC,aAAa,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAmB;IACxD,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC;IAC9B,MAAM,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAC/B,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Compute a SHA-256 hash of all files in a skill folder.
3
+ * This allows detecting if two skills with the same name have different content.
4
+ *
5
+ * The hash is computed by:
6
+ * 1. Getting all files recursively
7
+ * 2. Sorting them by path for consistency
8
+ * 3. Hashing each file's relative path + content
9
+ * 4. Combining into a final hash
10
+ */
11
+ export declare function computeContentHash(skillPath: string): Promise<string>;
12
+ //# sourceMappingURL=skill-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-hash.d.ts","sourceRoot":"","sources":["../../src/core/skill-hash.ts"],"names":[],"mappings":"AA4BA;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB3E"}
@@ -0,0 +1,53 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ /**
5
+ * Get all files in a directory recursively, sorted for consistency.
6
+ */
7
+ async function getAllFiles(dir) {
8
+ const files = [];
9
+ async function walk(currentDir) {
10
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
11
+ for (const entry of entries) {
12
+ const fullPath = path.join(currentDir, entry.name);
13
+ if (entry.isDirectory()) {
14
+ await walk(fullPath);
15
+ }
16
+ else if (entry.isFile()) {
17
+ files.push(fullPath);
18
+ }
19
+ }
20
+ }
21
+ await walk(dir);
22
+ // Sort for consistent ordering across platforms
23
+ return files.sort();
24
+ }
25
+ /**
26
+ * Compute a SHA-256 hash of all files in a skill folder.
27
+ * This allows detecting if two skills with the same name have different content.
28
+ *
29
+ * The hash is computed by:
30
+ * 1. Getting all files recursively
31
+ * 2. Sorting them by path for consistency
32
+ * 3. Hashing each file's relative path + content
33
+ * 4. Combining into a final hash
34
+ */
35
+ export async function computeContentHash(skillPath) {
36
+ const files = await getAllFiles(skillPath);
37
+ if (files.length === 0) {
38
+ // Empty folder - return hash of empty string
39
+ return crypto.createHash('sha256').update('').digest('hex').slice(0, 12);
40
+ }
41
+ const hash = crypto.createHash('sha256');
42
+ for (const file of files) {
43
+ // Include relative path in hash (so moving files changes the hash)
44
+ const relativePath = path.relative(skillPath, file);
45
+ hash.update(relativePath);
46
+ // Include file content
47
+ const content = await fs.readFile(file);
48
+ hash.update(content);
49
+ }
50
+ // Return first 12 chars for readability
51
+ return hash.digest('hex').slice(0, 12);
52
+ }
53
+ //# sourceMappingURL=skill-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-hash.js","sourceRoot":"","sources":["../../src/core/skill-hash.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;GAEG;AACH,KAAK,UAAU,WAAW,CAAC,GAAW;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,UAAU,IAAI,CAAC,UAAkB;QACpC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAEtE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACnD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,gDAAgD;IAChD,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,SAAiB;IACxD,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,SAAS,CAAC,CAAC;IAE3C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,6CAA6C;QAC7C,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,mEAAmE;QACnE,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAE1B,uBAAuB;QACvB,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,wCAAwC;IACxC,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { SkillMetadata, SkillInstallation } from '../types/index.js';
2
+ /**
3
+ * Check if a directory is a valid skill.
4
+ * A valid skill must contain a SKILL.md with required frontmatter (name, description).
5
+ */
6
+ export declare function isValidSkillDirectory(dirPath: string): Promise<boolean>;
7
+ /**
8
+ * Find skill directories within a given path by searching recursively for SKILL.md files.
9
+ * A skill is any directory containing a valid SKILL.md file (with name + description).
10
+ */
11
+ export declare function findSkillDirectories(searchPath: string): Promise<string[]>;
12
+ /**
13
+ * Read skill metadata from SKILL.md frontmatter.
14
+ * The canonical skill name comes from the frontmatter, not the folder name.
15
+ */
16
+ export declare function readSkillMetadata(skillPath: string): Promise<SkillMetadata | null>;
17
+ /**
18
+ * Get the canonical skill name from SKILL.md frontmatter.
19
+ * Returns null if not a valid skill.
20
+ */
21
+ export declare function getSkillName(skillPath: string): Promise<string | null>;
22
+ /**
23
+ * Find all installations of a skill by name across all agents.
24
+ */
25
+ export declare function findSkillInstallations(skillName: string): Promise<SkillInstallation[]>;
26
+ /**
27
+ * List all installed skills for a specific agent.
28
+ */
29
+ export declare function listSkillsForAgent(agentFolderName: string, isGlobal: boolean): Promise<string[]>;
30
+ /**
31
+ * Check if a skill exists in an agent's folder.
32
+ */
33
+ export declare function skillExists(skillName: string, agentFolderName: string, isGlobal: boolean): Promise<boolean>;
34
+ //# sourceMappingURL=skill-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-info.d.ts","sourceRoot":"","sources":["../../src/core/skill-info.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAa,MAAM,mBAAmB,CAAC;AAuFrF;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc7E;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA4BhF;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CASxF;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG5E;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA+B5F;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAsBnB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,OAAO,CAAC,CAIlB"}
@@ -0,0 +1,213 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { SUPPORTED_AGENTS } from './agents.js';
5
+ import { computeContentHash } from './skill-hash.js';
6
+ /**
7
+ * Parse YAML-like frontmatter from SKILL.md content.
8
+ * Frontmatter is delimited by --- at start and end.
9
+ * Handles nested metadata field as key-value pairs.
10
+ */
11
+ function parseFrontmatter(content) {
12
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
13
+ if (!frontmatterMatch) {
14
+ return null;
15
+ }
16
+ const lines = frontmatterMatch[1].split('\n');
17
+ let name = '';
18
+ let description = '';
19
+ let license;
20
+ let compatibility;
21
+ let allowedTools;
22
+ const metadata = {};
23
+ let inMetadata = false;
24
+ for (const line of lines) {
25
+ // Check if we're entering metadata block (indented or explicit)
26
+ if (line.match(/^metadata:\s*$/)) {
27
+ inMetadata = true;
28
+ continue;
29
+ }
30
+ // If line starts with non-whitespace, we're out of metadata block
31
+ if (inMetadata && line.match(/^\S/)) {
32
+ inMetadata = false;
33
+ }
34
+ const colonIndex = line.indexOf(':');
35
+ if (colonIndex > 0) {
36
+ const key = line.slice(0, colonIndex).trim();
37
+ let value = line.slice(colonIndex + 1).trim();
38
+ // Remove surrounding quotes if present
39
+ if ((value.startsWith('"') && value.endsWith('"')) ||
40
+ (value.startsWith("'") && value.endsWith("'"))) {
41
+ value = value.slice(1, -1);
42
+ }
43
+ if (inMetadata) {
44
+ // Nested under metadata
45
+ metadata[key] = value;
46
+ }
47
+ else {
48
+ // Top-level fields
49
+ switch (key) {
50
+ case 'name':
51
+ name = value;
52
+ break;
53
+ case 'description':
54
+ description = value;
55
+ break;
56
+ case 'license':
57
+ license = value;
58
+ break;
59
+ case 'compatibility':
60
+ compatibility = value;
61
+ break;
62
+ case 'allowed-tools':
63
+ allowedTools = value;
64
+ break;
65
+ }
66
+ }
67
+ }
68
+ }
69
+ // name and description are required
70
+ if (!name || !description) {
71
+ return null;
72
+ }
73
+ return {
74
+ name,
75
+ description,
76
+ license,
77
+ compatibility,
78
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
79
+ allowedTools
80
+ };
81
+ }
82
+ /**
83
+ * Check if a directory is a valid skill.
84
+ * A valid skill must contain a SKILL.md with required frontmatter (name, description).
85
+ */
86
+ export async function isValidSkillDirectory(dirPath) {
87
+ const skillMdPath = path.join(dirPath, 'SKILL.md');
88
+ if (!await fs.pathExists(skillMdPath)) {
89
+ return false;
90
+ }
91
+ try {
92
+ const content = await fs.readFile(skillMdPath, 'utf-8');
93
+ const metadata = parseFrontmatter(content);
94
+ return metadata !== null;
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
100
+ /**
101
+ * Find skill directories within a given path by searching recursively for SKILL.md files.
102
+ * A skill is any directory containing a valid SKILL.md file (with name + description).
103
+ */
104
+ export async function findSkillDirectories(searchPath) {
105
+ const skills = [];
106
+ // Recursively find all SKILL.md files
107
+ async function searchRecursively(dir) {
108
+ try {
109
+ const entries = await fs.readdir(dir, { withFileTypes: true });
110
+ for (const entry of entries) {
111
+ const fullPath = path.join(dir, entry.name);
112
+ if (entry.isFile() && entry.name === 'SKILL.md') {
113
+ // Found a SKILL.md - check if it's valid
114
+ if (await isValidSkillDirectory(dir)) {
115
+ skills.push(dir);
116
+ }
117
+ }
118
+ else if (entry.isDirectory() && !entry.name.startsWith('.git')) {
119
+ // Skip .git directories but recurse into others
120
+ await searchRecursively(fullPath);
121
+ }
122
+ }
123
+ }
124
+ catch {
125
+ // Permission denied or other error, skip this directory
126
+ }
127
+ }
128
+ await searchRecursively(searchPath);
129
+ return skills;
130
+ }
131
+ /**
132
+ * Read skill metadata from SKILL.md frontmatter.
133
+ * The canonical skill name comes from the frontmatter, not the folder name.
134
+ */
135
+ export async function readSkillMetadata(skillPath) {
136
+ const skillMdPath = path.join(skillPath, 'SKILL.md');
137
+ try {
138
+ const content = await fs.readFile(skillMdPath, 'utf-8');
139
+ return parseFrontmatter(content);
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ }
145
+ /**
146
+ * Get the canonical skill name from SKILL.md frontmatter.
147
+ * Returns null if not a valid skill.
148
+ */
149
+ export async function getSkillName(skillPath) {
150
+ const metadata = await readSkillMetadata(skillPath);
151
+ return metadata?.name || null;
152
+ }
153
+ /**
154
+ * Find all installations of a skill by name across all agents.
155
+ */
156
+ export async function findSkillInstallations(skillName) {
157
+ const installations = [];
158
+ // Check both local and global for each agent
159
+ const locations = [
160
+ { base: process.cwd(), isGlobal: false },
161
+ { base: os.homedir(), isGlobal: true }
162
+ ];
163
+ for (const { base, isGlobal } of locations) {
164
+ for (const agent of SUPPORTED_AGENTS) {
165
+ const skillPath = path.join(base, agent.folderName, 'skills', skillName);
166
+ if (await fs.pathExists(skillPath)) {
167
+ const metadata = await readSkillMetadata(skillPath);
168
+ if (metadata) {
169
+ const contentHash = await computeContentHash(skillPath);
170
+ installations.push({
171
+ agent: agent.flag,
172
+ path: skillPath,
173
+ isGlobal,
174
+ metadata,
175
+ contentHash
176
+ });
177
+ }
178
+ }
179
+ }
180
+ }
181
+ return installations;
182
+ }
183
+ /**
184
+ * List all installed skills for a specific agent.
185
+ */
186
+ export async function listSkillsForAgent(agentFolderName, isGlobal) {
187
+ const base = isGlobal ? os.homedir() : process.cwd();
188
+ const skillsDir = path.join(base, agentFolderName, 'skills');
189
+ if (!await fs.pathExists(skillsDir)) {
190
+ return [];
191
+ }
192
+ const entries = await fs.readdir(skillsDir, { withFileTypes: true });
193
+ const skills = [];
194
+ for (const entry of entries) {
195
+ if (entry.isDirectory()) {
196
+ const skillPath = path.join(skillsDir, entry.name);
197
+ const skillName = await getSkillName(skillPath);
198
+ if (skillName) {
199
+ skills.push(skillName);
200
+ }
201
+ }
202
+ }
203
+ return skills;
204
+ }
205
+ /**
206
+ * Check if a skill exists in an agent's folder.
207
+ */
208
+ export async function skillExists(skillName, agentFolderName, isGlobal) {
209
+ const base = isGlobal ? os.homedir() : process.cwd();
210
+ const skillPath = path.join(base, agentFolderName, 'skills', skillName);
211
+ return (await fs.pathExists(skillPath)) && (await isValidSkillDirectory(skillPath));
212
+ }
213
+ //# sourceMappingURL=skill-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-info.js","sourceRoot":"","sources":["../../src/core/skill-info.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { AgentType, SkillSource } from '../types/index.js';
2
+ /**
3
+ * Install skill(s) from a local path.
4
+ * Checks if path is a skill, otherwise checks direct children for SKILL.md.
5
+ */
6
+ export declare function installFromLocal(source: SkillSource, agentFlag: AgentType, isGlobal: boolean): Promise<string[]>;
7
+ /**
8
+ * Install skill(s) from GitHub using degit.
9
+ * After downloading, checks if it's a skill or searches direct children for SKILL.md.
10
+ */
11
+ export declare function installFromGithub(source: SkillSource, agentFlag: AgentType, isGlobal: boolean): Promise<string[]>;
12
+ /**
13
+ * Install skill(s) from any source to an agent.
14
+ * Returns list of installed skill names.
15
+ */
16
+ export declare function installSkill(skillInput: string, agentFlag: AgentType, isGlobal: boolean): Promise<{
17
+ skillNames: string[];
18
+ source: SkillSource;
19
+ }>;
20
+ /**
21
+ * Remove a skill from an agent.
22
+ */
23
+ export declare function removeSkill(skillName: string, agentFlag: AgentType, isGlobal: boolean): Promise<boolean>;
24
+ //# sourceMappingURL=skill-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-install.d.ts","sourceRoot":"","sources":["../../src/core/skill-install.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA2ChE;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAcnB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoCnB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC,CAkBxD;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,OAAO,CAAC,CASlB"}
@@ -0,0 +1,123 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { execSync } from 'child_process';
5
+ import { getAgentByFlag } from './agents.js';
6
+ import { resolveSkillSource } from './skill-source.js';
7
+ import { findSkillDirectories, readSkillMetadata } from './skill-info.js';
8
+ /**
9
+ * Get the destination path for installing a skill.
10
+ * Skill name comes from SKILL.md frontmatter.
11
+ */
12
+ function getSkillDestination(skillName, agentFlag, isGlobal) {
13
+ const agent = getAgentByFlag(agentFlag);
14
+ if (!agent) {
15
+ throw new Error(`Unknown agent: ${agentFlag}`);
16
+ }
17
+ const base = isGlobal ? os.homedir() : process.cwd();
18
+ return path.join(base, agent.folderName, 'skills', skillName);
19
+ }
20
+ /**
21
+ * Install a single skill directory to an agent.
22
+ * The skill name is taken from SKILL.md frontmatter (not the folder name).
23
+ * The destination folder will be named after the frontmatter name.
24
+ */
25
+ async function installSkillDirectory(skillDir, agentFlag, isGlobal) {
26
+ // Get metadata from SKILL.md frontmatter (name and description are required)
27
+ const metadata = await readSkillMetadata(skillDir);
28
+ if (!metadata) {
29
+ throw new Error(`Invalid skill at "${skillDir}": SKILL.md must have name and description in frontmatter`);
30
+ }
31
+ const dest = getSkillDestination(metadata.name, agentFlag, isGlobal);
32
+ // Ensure parent directory exists
33
+ await fs.ensureDir(path.dirname(dest));
34
+ // Copy the skill folder (folder will be renamed to match frontmatter name)
35
+ await fs.copy(skillDir, dest, { overwrite: true });
36
+ return metadata.name;
37
+ }
38
+ /**
39
+ * Install skill(s) from a local path.
40
+ * Checks if path is a skill, otherwise checks direct children for SKILL.md.
41
+ */
42
+ export async function installFromLocal(source, agentFlag, isGlobal) {
43
+ const skillDirs = await findSkillDirectories(source.location);
44
+ if (skillDirs.length === 0) {
45
+ throw new Error(`No skills found in "${source.location}". A skill must contain a SKILL.md file.`);
46
+ }
47
+ const installedSkills = [];
48
+ for (const skillDir of skillDirs) {
49
+ const skillName = await installSkillDirectory(skillDir, agentFlag, isGlobal);
50
+ installedSkills.push(skillName);
51
+ }
52
+ return installedSkills;
53
+ }
54
+ /**
55
+ * Install skill(s) from GitHub using degit.
56
+ * After downloading, checks if it's a skill or searches direct children for SKILL.md.
57
+ */
58
+ export async function installFromGithub(source, agentFlag, isGlobal) {
59
+ // Create a temp directory to download to
60
+ const tempDir = path.join(os.tmpdir(), `sun-install-${Date.now()}`);
61
+ try {
62
+ // Download using degit
63
+ await fs.ensureDir(tempDir);
64
+ try {
65
+ execSync(`npx degit ${source.location} "${tempDir}"`, {
66
+ stdio: 'pipe'
67
+ });
68
+ }
69
+ catch (error) {
70
+ const err = error;
71
+ const stderr = err.stderr?.toString() || err.message;
72
+ throw new Error(`Failed to download from GitHub: ${stderr}`);
73
+ }
74
+ // Find skills in downloaded content (checks itself and direct children)
75
+ const skillDirs = await findSkillDirectories(tempDir);
76
+ if (skillDirs.length === 0) {
77
+ throw new Error(`No skills found in "${source.originalInput}". A skill must contain a SKILL.md file.`);
78
+ }
79
+ // Install each skill found (name comes from SKILL.md frontmatter)
80
+ const installedSkills = [];
81
+ for (const skillDir of skillDirs) {
82
+ const skillName = await installSkillDirectory(skillDir, agentFlag, isGlobal);
83
+ installedSkills.push(skillName);
84
+ }
85
+ return installedSkills;
86
+ }
87
+ finally {
88
+ // Clean up temp directory
89
+ await fs.remove(tempDir).catch(() => { });
90
+ }
91
+ }
92
+ /**
93
+ * Install skill(s) from any source to an agent.
94
+ * Returns list of installed skill names.
95
+ */
96
+ export async function installSkill(skillInput, agentFlag, isGlobal) {
97
+ const source = resolveSkillSource(skillInput);
98
+ let skillNames;
99
+ switch (source.type) {
100
+ case 'local':
101
+ skillNames = await installFromLocal(source, agentFlag, isGlobal);
102
+ break;
103
+ case 'github':
104
+ case 'shortcut':
105
+ skillNames = await installFromGithub(source, agentFlag, isGlobal);
106
+ break;
107
+ default:
108
+ throw new Error(`Unknown source type: ${source.type}`);
109
+ }
110
+ return { skillNames, source };
111
+ }
112
+ /**
113
+ * Remove a skill from an agent.
114
+ */
115
+ export async function removeSkill(skillName, agentFlag, isGlobal) {
116
+ const dest = getSkillDestination(skillName, agentFlag, isGlobal);
117
+ if (await fs.pathExists(dest)) {
118
+ await fs.remove(dest);
119
+ return true;
120
+ }
121
+ return false;
122
+ }
123
+ //# sourceMappingURL=skill-install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-install.js","sourceRoot":"","sources":["../../src/core/skill-install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { SkillSource } from '../types/index.js';
2
+ /**
3
+ * Check if input looks like a GitHub URL or reference.
4
+ * Matches: github.com/user/repo, https://github.com/..., etc.
5
+ */
6
+ export declare function isGithubUrl(input: string): boolean;
7
+ /**
8
+ * Check if input looks like a local file path.
9
+ * Matches: ./path, ../path, ~/path, /absolute/path
10
+ */
11
+ export declare function isLocalPath(input: string): boolean;
12
+ /**
13
+ * Resolve a skill input to its source type and location.
14
+ *
15
+ * Resolution order:
16
+ * 1. Check if it's a registered shortcut (e.g., "tinker")
17
+ * 2. Check if it contains "github.com" (treat as GitHub URL)
18
+ * 3. Check if it's a valid local path
19
+ * 4. Otherwise, throw error
20
+ */
21
+ export declare function resolveSkillSource(input: string): SkillSource;
22
+ /**
23
+ * Extract skill name from a source.
24
+ * For GitHub: last path segment
25
+ * For local: folder name
26
+ * For shortcut: the shortcut name itself
27
+ */
28
+ export declare function getSkillNameFromSource(source: SkillSource): string;
29
+ //# sourceMappingURL=skill-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-source.d.ts","sourceRoot":"","sources":["../../src/core/skill-source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAElD;AA0BD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAYlD;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAsC7D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAclE"}