dev3000 0.0.144 → 0.0.146

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 (126) hide show
  1. package/dist/cli.js +82 -8
  2. package/dist/cli.js.map +1 -1
  3. package/dist/components/SkillSelector.d.ts +2 -1
  4. package/dist/components/SkillSelector.d.ts.map +1 -1
  5. package/dist/components/SkillSelector.js +16 -5
  6. package/dist/components/SkillSelector.js.map +1 -1
  7. package/dist/dev-environment.d.ts.map +1 -1
  8. package/dist/dev-environment.js +94 -0
  9. package/dist/dev-environment.js.map +1 -1
  10. package/dist/skills/index.d.ts +49 -0
  11. package/dist/skills/index.d.ts.map +1 -0
  12. package/dist/skills/index.js +185 -0
  13. package/dist/skills/index.js.map +1 -0
  14. package/dist/skills/index.test.ts +218 -0
  15. package/dist/skills/index.ts +219 -0
  16. package/dist/skills/react-performance/SKILL.md +1034 -0
  17. package/dist/utils/agent-browser.d.ts +120 -0
  18. package/dist/utils/agent-browser.d.ts.map +1 -0
  19. package/dist/utils/agent-browser.js +387 -0
  20. package/dist/utils/agent-browser.js.map +1 -0
  21. package/dist/utils/skill-installer.d.ts +10 -0
  22. package/dist/utils/skill-installer.d.ts.map +1 -1
  23. package/dist/utils/skill-installer.js +77 -4
  24. package/dist/utils/skill-installer.js.map +1 -1
  25. package/dist/utils/user-config.d.ts +1 -0
  26. package/dist/utils/user-config.d.ts.map +1 -1
  27. package/dist/utils/user-config.js +10 -8
  28. package/dist/utils/user-config.js.map +1 -1
  29. package/mcp-server/.next/BUILD_ID +1 -1
  30. package/mcp-server/.next/build-manifest.json +2 -2
  31. package/mcp-server/.next/fallback-build-manifest.json +2 -2
  32. package/mcp-server/.next/prerender-manifest.json +3 -3
  33. package/mcp-server/.next/server/app/_global-error/page.js +1 -1
  34. package/mcp-server/.next/server/app/_global-error/page.js.nft.json +1 -1
  35. package/mcp-server/.next/server/app/_global-error.html +2 -2
  36. package/mcp-server/.next/server/app/_global-error.rsc +1 -1
  37. package/mcp-server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  38. package/mcp-server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  39. package/mcp-server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  40. package/mcp-server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  41. package/mcp-server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  42. package/mcp-server/.next/server/app/_not-found/page.js +1 -1
  43. package/mcp-server/.next/server/app/_not-found/page.js.nft.json +1 -1
  44. package/mcp-server/.next/server/app/_not-found.html +1 -1
  45. package/mcp-server/.next/server/app/_not-found.rsc +1 -1
  46. package/mcp-server/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  47. package/mcp-server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  48. package/mcp-server/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  49. package/mcp-server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  50. package/mcp-server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  51. package/mcp-server/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  52. package/mcp-server/.next/server/app/api/jank/[session]/route.js +1 -1
  53. package/mcp-server/.next/server/app/api/jank/[session]/route.js.nft.json +1 -1
  54. package/mcp-server/.next/server/app/api/logs/rotate/route.js +1 -1
  55. package/mcp-server/.next/server/app/api/logs/rotate/route.js.nft.json +1 -1
  56. package/mcp-server/.next/server/app/api/orchestrator/route.js +1 -1
  57. package/mcp-server/.next/server/app/api/orchestrator/route.js.nft.json +1 -1
  58. package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js +1 -1
  59. package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js.nft.json +1 -1
  60. package/mcp-server/.next/server/app/api/screenshots/capture/route.js +2 -2
  61. package/mcp-server/.next/server/app/api/screenshots/capture/route.js.nft.json +1 -1
  62. package/mcp-server/.next/server/app/api/screenshots/clear/route.js +1 -1
  63. package/mcp-server/.next/server/app/api/screenshots/clear/route.js.nft.json +1 -1
  64. package/mcp-server/.next/server/app/api/screenshots/list/route.js +1 -1
  65. package/mcp-server/.next/server/app/api/screenshots/list/route.js.nft.json +1 -1
  66. package/mcp-server/.next/server/app/index.html +1 -1
  67. package/mcp-server/.next/server/app/index.rsc +1 -1
  68. package/mcp-server/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  69. package/mcp-server/.next/server/app/index.segments/_full.segment.rsc +1 -1
  70. package/mcp-server/.next/server/app/index.segments/_head.segment.rsc +1 -1
  71. package/mcp-server/.next/server/app/index.segments/_index.segment.rsc +1 -1
  72. package/mcp-server/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  73. package/mcp-server/.next/server/app/logs/page.js +1 -1
  74. package/mcp-server/.next/server/app/logs/page.js.nft.json +1 -1
  75. package/mcp-server/.next/server/app/mcp/route.js +4 -4
  76. package/mcp-server/.next/server/app/mcp/route.js.nft.json +1 -1
  77. package/mcp-server/.next/server/app/page.js +1 -1
  78. package/mcp-server/.next/server/app/page.js.nft.json +1 -1
  79. package/mcp-server/.next/server/app/video/[session]/page.js +1 -1
  80. package/mcp-server/.next/server/app/video/[session]/page.js.nft.json +1 -1
  81. package/mcp-server/.next/server/chunks/[externals]__0e1fd2ca._.js +3 -0
  82. package/mcp-server/.next/server/chunks/[root-of-the-server]__130a5f58._.js +4 -0
  83. package/mcp-server/.next/server/chunks/{[root-of-the-server]__ee39655c._.js.map → [root-of-the-server]__130a5f58._.js.map} +1 -1
  84. package/mcp-server/.next/server/chunks/{[root-of-the-server]__af8a6500._.js → [root-of-the-server]__1dca9894._.js} +2 -2
  85. package/mcp-server/.next/server/chunks/[root-of-the-server]__2f95edf0._.js +3 -3
  86. package/mcp-server/.next/server/chunks/[root-of-the-server]__2f95edf0._.js.map +1 -1
  87. package/mcp-server/.next/server/chunks/[root-of-the-server]__444592aa._.js +3 -0
  88. package/mcp-server/.next/server/chunks/{[root-of-the-server]__69498adb._.js.map → [root-of-the-server]__444592aa._.js.map} +1 -1
  89. package/mcp-server/.next/server/chunks/{[root-of-the-server]__7ed5139a._.js → [root-of-the-server]__8f84b4cc._.js} +2 -2
  90. package/mcp-server/.next/server/chunks/{[root-of-the-server]__6c914ee2._.js → [root-of-the-server]__94946101._.js} +2 -2
  91. package/mcp-server/.next/server/chunks/{[root-of-the-server]__6728fd4b._.js → [root-of-the-server]__9c4c7095._.js} +2 -2
  92. package/mcp-server/.next/server/chunks/[root-of-the-server]__b71c83ed._.js +4 -0
  93. package/mcp-server/.next/server/chunks/{[root-of-the-server]__f4213a2f._.js.map → [root-of-the-server]__b71c83ed._.js.map} +1 -1
  94. package/mcp-server/.next/server/chunks/{[root-of-the-server]__daca64a4._.js → [root-of-the-server]__b86e20b6._.js} +2 -2
  95. package/mcp-server/.next/server/chunks/{[root-of-the-server]__95098840._.js → [root-of-the-server]__ee139f8a._.js} +2 -2
  96. package/mcp-server/.next/server/chunks/mcp-server_app_mcp_tools_ts_faf6d7df._.js +26 -31
  97. package/mcp-server/.next/server/chunks/mcp-server_app_mcp_tools_ts_faf6d7df._.js.map +1 -1
  98. package/mcp-server/.next/server/chunks/src_utils_agent-browser_ts_cc00e0d8._.js +3 -0
  99. package/mcp-server/.next/server/chunks/src_utils_agent-browser_ts_cc00e0d8._.js.map +1 -0
  100. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__50eb2eba._.js +3 -0
  101. package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__4b74b898._.js → [root-of-the-server]__f66148e5._.js} +2 -2
  102. package/mcp-server/.next/server/pages/404.html +1 -1
  103. package/mcp-server/.next/server/pages/500.html +2 -2
  104. package/mcp-server/.next/server/server-reference-manifest.js +1 -1
  105. package/mcp-server/.next/server/server-reference-manifest.json +1 -1
  106. package/mcp-server/app/mcp/route.ts +30 -0
  107. package/mcp-server/app/mcp/tools.ts +204 -61
  108. package/mcp-server/start-production.mjs +37 -3
  109. package/package.json +4 -3
  110. package/mcp-server/.next/server/chunks/[externals]__eb460e97._.js +0 -3
  111. package/mcp-server/.next/server/chunks/[root-of-the-server]__69498adb._.js +0 -3
  112. package/mcp-server/.next/server/chunks/[root-of-the-server]__ee39655c._.js +0 -4
  113. package/mcp-server/.next/server/chunks/[root-of-the-server]__f4213a2f._.js +0 -4
  114. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__163e0fe2._.js +0 -3
  115. /package/mcp-server/.next/server/chunks/{[externals]__eb460e97._.js.map → [externals]__0e1fd2ca._.js.map} +0 -0
  116. /package/mcp-server/.next/server/chunks/{[root-of-the-server]__af8a6500._.js.map → [root-of-the-server]__1dca9894._.js.map} +0 -0
  117. /package/mcp-server/.next/server/chunks/{[root-of-the-server]__7ed5139a._.js.map → [root-of-the-server]__8f84b4cc._.js.map} +0 -0
  118. /package/mcp-server/.next/server/chunks/{[root-of-the-server]__6c914ee2._.js.map → [root-of-the-server]__94946101._.js.map} +0 -0
  119. /package/mcp-server/.next/server/chunks/{[root-of-the-server]__6728fd4b._.js.map → [root-of-the-server]__9c4c7095._.js.map} +0 -0
  120. /package/mcp-server/.next/server/chunks/{[root-of-the-server]__daca64a4._.js.map → [root-of-the-server]__b86e20b6._.js.map} +0 -0
  121. /package/mcp-server/.next/server/chunks/{[root-of-the-server]__95098840._.js.map → [root-of-the-server]__ee139f8a._.js.map} +0 -0
  122. /package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__163e0fe2._.js.map → [root-of-the-server]__50eb2eba._.js.map} +0 -0
  123. /package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__4b74b898._.js.map → [root-of-the-server]__f66148e5._.js.map} +0 -0
  124. /package/mcp-server/.next/static/{2Xvryb3dyQkf-Of6R9qdS → f8-qsTYpMHe5da6lkWH6m}/_buildManifest.js +0 -0
  125. /package/mcp-server/.next/static/{2Xvryb3dyQkf-Of6R9qdS → f8-qsTYpMHe5da6lkWH6m}/_clientMiddlewareManifest.json +0 -0
  126. /package/mcp-server/.next/static/{2Xvryb3dyQkf-Of6R9qdS → f8-qsTYpMHe5da6lkWH6m}/_ssgManifest.js +0 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Shared skill module for d3k
3
+ *
4
+ * Skills are prompt templates stored as SKILL.md files that provide
5
+ * specialized instructions for specific tasks.
6
+ *
7
+ * This module is used by both:
8
+ * - CLI: `d3k skill <name>`
9
+ * - MCP: `get_skill` tool
10
+ */
11
+ import { existsSync, readdirSync, readFileSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ import { fileURLToPath } from "url";
14
+ /**
15
+ * Get the base directories where skills can be found.
16
+ * Searches in order of priority:
17
+ * 1. Project-local skills (.claude/skills/)
18
+ * 2. Source skills (src/skills/) - for development
19
+ * 3. Dist skills (dist/skills/) - for installed packages
20
+ */
21
+ export function getSkillDirectories(cwd) {
22
+ const dirs = [];
23
+ // 1. Project-local skills (highest priority)
24
+ const projectDir = cwd || process.cwd();
25
+ dirs.push(join(projectDir, ".claude", "skills"));
26
+ // 2. Check if we're running from a compiled binary (bun compile)
27
+ // In compiled binaries, import.meta.url returns /$bunfs/... virtual path
28
+ // We detect this by checking if the URL starts with /$bunfs
29
+ const moduleUrl = import.meta.url;
30
+ const isCompiledBinary = moduleUrl.startsWith("file:///$bunfs") || moduleUrl.startsWith("/$bunfs");
31
+ if (isCompiledBinary) {
32
+ // For compiled binaries, process.execPath contains the actual binary path
33
+ const binaryPath = process.execPath;
34
+ if (binaryPath && existsSync(binaryPath)) {
35
+ const binDir = dirname(binaryPath); // bin/
36
+ const packageDir = dirname(binDir); // platform package root (e.g., @d3k/darwin-arm64)
37
+ dirs.push(join(packageDir, "skills"));
38
+ }
39
+ }
40
+ // 3. Source and dist skills from the d3k package
41
+ // Handle both ESM (__dirname equivalent) and different execution contexts
42
+ try {
43
+ const currentFile = fileURLToPath(import.meta.url);
44
+ const srcDir = dirname(currentFile); // src/skills
45
+ const packageRoot = dirname(dirname(srcDir)); // package root
46
+ // Source skills (for development)
47
+ dirs.push(join(packageRoot, "src", "skills"));
48
+ // Dist skills (for installed package)
49
+ dirs.push(join(packageRoot, "dist", "skills"));
50
+ // Also check if we're in a binary distribution
51
+ dirs.push(join(packageRoot, "skills"));
52
+ }
53
+ catch {
54
+ // Fallback for CommonJS or other contexts
55
+ }
56
+ return dirs.filter((dir) => existsSync(dir));
57
+ }
58
+ /**
59
+ * Find a skill by name.
60
+ * Returns the path to the SKILL.md file if found.
61
+ */
62
+ export function findSkill(name, cwd) {
63
+ const skillDirs = getSkillDirectories(cwd);
64
+ for (const dir of skillDirs) {
65
+ const skillPath = join(dir, name, "SKILL.md");
66
+ if (existsSync(skillPath)) {
67
+ return skillPath;
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ /**
73
+ * Get the content of a skill by name.
74
+ */
75
+ export function getSkill(name, cwd) {
76
+ const skillPath = findSkill(name, cwd);
77
+ if (skillPath) {
78
+ try {
79
+ const content = readFileSync(skillPath, "utf-8");
80
+ return {
81
+ found: true,
82
+ name,
83
+ content,
84
+ path: skillPath
85
+ };
86
+ }
87
+ catch (error) {
88
+ return {
89
+ found: false,
90
+ name,
91
+ error: `Failed to read skill: ${error instanceof Error ? error.message : String(error)}`,
92
+ availableSkills: listAvailableSkills(cwd)
93
+ };
94
+ }
95
+ }
96
+ return {
97
+ found: false,
98
+ name,
99
+ error: `Skill "${name}" not found`,
100
+ availableSkills: listAvailableSkills(cwd)
101
+ };
102
+ }
103
+ /**
104
+ * List all available skills.
105
+ */
106
+ export function listAvailableSkills(cwd) {
107
+ const skills = new Set();
108
+ const skillDirs = getSkillDirectories(cwd);
109
+ for (const dir of skillDirs) {
110
+ try {
111
+ const entries = readdirSync(dir, { withFileTypes: true });
112
+ for (const entry of entries) {
113
+ if (entry.isDirectory()) {
114
+ const skillFile = join(dir, entry.name, "SKILL.md");
115
+ if (existsSync(skillFile)) {
116
+ skills.add(entry.name);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ catch {
122
+ // Directory might not exist or be readable
123
+ }
124
+ }
125
+ return Array.from(skills).sort();
126
+ }
127
+ /**
128
+ * Get detailed info about all available skills.
129
+ */
130
+ export function getSkillsInfo(cwd) {
131
+ const skillsMap = new Map();
132
+ const skillDirs = getSkillDirectories(cwd);
133
+ for (const dir of skillDirs) {
134
+ try {
135
+ const entries = readdirSync(dir, { withFileTypes: true });
136
+ for (const entry of entries) {
137
+ if (entry.isDirectory() && !skillsMap.has(entry.name)) {
138
+ const skillPath = join(dir, entry.name, "SKILL.md");
139
+ if (existsSync(skillPath)) {
140
+ try {
141
+ const content = readFileSync(skillPath, "utf-8");
142
+ const description = extractDescription(content);
143
+ skillsMap.set(entry.name, {
144
+ name: entry.name,
145
+ description,
146
+ path: skillPath
147
+ });
148
+ }
149
+ catch {
150
+ // Skip unreadable skills
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ catch {
157
+ // Directory might not exist or be readable
158
+ }
159
+ }
160
+ return Array.from(skillsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
161
+ }
162
+ /**
163
+ * Extract description from SKILL.md frontmatter or first paragraph.
164
+ */
165
+ function extractDescription(content) {
166
+ // Try to extract from YAML frontmatter
167
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
168
+ if (frontmatterMatch) {
169
+ const frontmatter = frontmatterMatch[1];
170
+ const descMatch = frontmatter.match(/description:\s*(.+)/);
171
+ if (descMatch) {
172
+ return descMatch[1].trim();
173
+ }
174
+ }
175
+ // Fallback: use first non-heading, non-empty line
176
+ const lines = content.split("\n");
177
+ for (const line of lines) {
178
+ const trimmed = line.trim();
179
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("---")) {
180
+ return trimmed.slice(0, 100) + (trimmed.length > 100 ? "..." : "");
181
+ }
182
+ }
183
+ return "No description available";
184
+ }
185
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/skills/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,218 @@
1
+ import { existsSync, readdirSync, readFileSync } from "fs"
2
+ import { beforeEach, describe, expect, it, vi } from "vitest"
3
+ import { findSkill, getSkill, getSkillDirectories, getSkillsInfo, listAvailableSkills, type SkillResult } from "./index"
4
+
5
+ // Mock fs module
6
+ vi.mock("fs", () => ({
7
+ existsSync: vi.fn(),
8
+ readdirSync: vi.fn(),
9
+ readFileSync: vi.fn()
10
+ }))
11
+
12
+ describe("skills module", () => {
13
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof vi.fn>
14
+ const mockReaddirSync = readdirSync as unknown as ReturnType<typeof vi.fn>
15
+ const mockReadFileSync = readFileSync as unknown as ReturnType<typeof vi.fn>
16
+
17
+ beforeEach(() => {
18
+ vi.clearAllMocks()
19
+ })
20
+
21
+ describe("getSkillDirectories", () => {
22
+ it("should return directories that exist", () => {
23
+ // At minimum, should return project-local .claude/skills if it exists
24
+ mockExistsSync.mockImplementation((path: string) => {
25
+ return path.includes(".claude/skills") || path.includes("src/skills")
26
+ })
27
+
28
+ const dirs = getSkillDirectories("/test/project")
29
+ expect(dirs.length).toBeGreaterThan(0)
30
+ })
31
+
32
+ it("should include project-local skills directory", () => {
33
+ mockExistsSync.mockReturnValue(true)
34
+
35
+ const dirs = getSkillDirectories("/test/project")
36
+ const hasProjectLocal = dirs.some((d) => d.includes(".claude/skills"))
37
+ expect(hasProjectLocal).toBe(true)
38
+ })
39
+ })
40
+
41
+ describe("findSkill", () => {
42
+ it("should find a skill in the first matching directory", () => {
43
+ // Mock: directory exists AND skill file exists
44
+ mockExistsSync.mockImplementation((path: string) => {
45
+ // Return true for both the directory check and the SKILL.md file check
46
+ return path.includes(".claude/skills") || path.includes("src/skills") || path.includes("test-skill/SKILL.md")
47
+ })
48
+
49
+ const result = findSkill("test-skill", "/test/project")
50
+ expect(result).toBeTruthy()
51
+ expect(result).toContain("test-skill/SKILL.md")
52
+ })
53
+
54
+ it("should return null if skill not found", () => {
55
+ mockExistsSync.mockReturnValue(false)
56
+
57
+ const result = findSkill("nonexistent-skill", "/test/project")
58
+ expect(result).toBeNull()
59
+ })
60
+ })
61
+
62
+ describe("getSkill", () => {
63
+ it("should return skill content when found", () => {
64
+ const skillContent = `---
65
+ description: Test skill description
66
+ ---
67
+
68
+ # Test Skill
69
+
70
+ This is test content.`
71
+
72
+ // Mock: directory exists AND skill file exists
73
+ mockExistsSync.mockImplementation((path: string) => {
74
+ return path.includes(".claude/skills") || path.includes("src/skills") || path.includes("test-skill/SKILL.md")
75
+ })
76
+ mockReadFileSync.mockReturnValue(skillContent)
77
+
78
+ const result: SkillResult = getSkill("test-skill", "/test/project")
79
+ expect(result.found).toBe(true)
80
+ expect(result.name).toBe("test-skill")
81
+ expect(result.content).toBe(skillContent)
82
+ expect(result.path).toBeTruthy()
83
+ })
84
+
85
+ it("should return error when skill not found", () => {
86
+ mockExistsSync.mockReturnValue(false)
87
+ mockReaddirSync.mockReturnValue([])
88
+
89
+ const result: SkillResult = getSkill("nonexistent", "/test/project")
90
+ expect(result.found).toBe(false)
91
+ expect(result.error).toContain("not found")
92
+ expect(result.availableSkills).toBeDefined()
93
+ })
94
+
95
+ it("should handle read errors gracefully", () => {
96
+ // Mock: directory and file exist but read fails
97
+ mockExistsSync.mockImplementation((path: string) => {
98
+ return path.includes(".claude/skills") || path.includes("src/skills") || path.includes("error-skill/SKILL.md")
99
+ })
100
+ mockReadFileSync.mockImplementation(() => {
101
+ throw new Error("Permission denied")
102
+ })
103
+ mockReaddirSync.mockReturnValue([])
104
+
105
+ const result: SkillResult = getSkill("error-skill", "/test/project")
106
+ expect(result.found).toBe(false)
107
+ expect(result.error).toContain("Failed to read skill")
108
+ })
109
+ })
110
+
111
+ describe("listAvailableSkills", () => {
112
+ it("should list skills from all directories", () => {
113
+ mockExistsSync.mockReturnValue(true)
114
+ mockReaddirSync.mockImplementation((dir: string) => {
115
+ if (dir.includes(".claude/skills")) {
116
+ return [
117
+ { name: "skill-a", isDirectory: () => true },
118
+ { name: "skill-b", isDirectory: () => true }
119
+ ]
120
+ }
121
+ if (dir.includes("src/skills")) {
122
+ return [
123
+ { name: "skill-c", isDirectory: () => true },
124
+ { name: "not-a-skill.txt", isDirectory: () => false }
125
+ ]
126
+ }
127
+ return []
128
+ })
129
+
130
+ const skills = listAvailableSkills("/test/project")
131
+ expect(skills).toContain("skill-a")
132
+ expect(skills).toContain("skill-b")
133
+ expect(skills).toContain("skill-c")
134
+ expect(skills).not.toContain("not-a-skill.txt")
135
+ })
136
+
137
+ it("should deduplicate skills across directories", () => {
138
+ mockExistsSync.mockReturnValue(true)
139
+ mockReaddirSync.mockImplementation(() => [{ name: "same-skill", isDirectory: () => true }])
140
+
141
+ const skills = listAvailableSkills("/test/project")
142
+ const sameSkillCount = skills.filter((s) => s === "same-skill").length
143
+ expect(sameSkillCount).toBe(1)
144
+ })
145
+
146
+ it("should return sorted list", () => {
147
+ mockExistsSync.mockReturnValue(true)
148
+ mockReaddirSync.mockImplementation(() => [
149
+ { name: "zebra", isDirectory: () => true },
150
+ { name: "alpha", isDirectory: () => true },
151
+ { name: "beta", isDirectory: () => true }
152
+ ])
153
+
154
+ const skills = listAvailableSkills("/test/project")
155
+ expect(skills).toEqual(["alpha", "beta", "zebra"])
156
+ })
157
+
158
+ it("should handle directory read errors gracefully", () => {
159
+ mockExistsSync.mockReturnValue(true)
160
+ mockReaddirSync.mockImplementation(() => {
161
+ throw new Error("Permission denied")
162
+ })
163
+
164
+ const skills = listAvailableSkills("/test/project")
165
+ expect(skills).toEqual([])
166
+ })
167
+ })
168
+
169
+ describe("getSkillsInfo", () => {
170
+ it("should return detailed skill info with descriptions", () => {
171
+ const skillContent = `---
172
+ description: A helpful skill for testing
173
+ ---
174
+
175
+ # Test Skill`
176
+
177
+ mockExistsSync.mockReturnValue(true)
178
+ mockReaddirSync.mockImplementation(() => [{ name: "test-skill", isDirectory: () => true }])
179
+ mockReadFileSync.mockReturnValue(skillContent)
180
+
181
+ const skills = getSkillsInfo("/test/project")
182
+ expect(skills.length).toBeGreaterThan(0)
183
+ expect(skills[0].name).toBe("test-skill")
184
+ expect(skills[0].description).toBe("A helpful skill for testing")
185
+ expect(skills[0].path).toBeTruthy()
186
+ })
187
+
188
+ it("should extract description from first paragraph if no frontmatter", () => {
189
+ const skillContent = `# Test Skill
190
+
191
+ This is the first paragraph that should be used as description.
192
+
193
+ More content here.`
194
+
195
+ mockExistsSync.mockReturnValue(true)
196
+ mockReaddirSync.mockImplementation(() => [{ name: "test-skill", isDirectory: () => true }])
197
+ mockReadFileSync.mockReturnValue(skillContent)
198
+
199
+ const skills = getSkillsInfo("/test/project")
200
+ expect(skills[0].description).toContain("This is the first paragraph")
201
+ })
202
+
203
+ it("should truncate long descriptions", () => {
204
+ const longDescription = "A".repeat(150)
205
+ const skillContent = `---
206
+ description: ${longDescription}
207
+ ---`
208
+
209
+ mockExistsSync.mockReturnValue(true)
210
+ mockReaddirSync.mockImplementation(() => [{ name: "test-skill", isDirectory: () => true }])
211
+ mockReadFileSync.mockReturnValue(skillContent)
212
+
213
+ const skills = getSkillsInfo("/test/project")
214
+ // The frontmatter description isn't truncated, only first-paragraph fallback is
215
+ expect(skills[0].description).toBe(longDescription)
216
+ })
217
+ })
218
+ })
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Shared skill module for d3k
3
+ *
4
+ * Skills are prompt templates stored as SKILL.md files that provide
5
+ * specialized instructions for specific tasks.
6
+ *
7
+ * This module is used by both:
8
+ * - CLI: `d3k skill <name>`
9
+ * - MCP: `get_skill` tool
10
+ */
11
+
12
+ import { existsSync, readdirSync, readFileSync } from "fs"
13
+ import { dirname, join } from "path"
14
+ import { fileURLToPath } from "url"
15
+
16
+ export interface SkillInfo {
17
+ name: string
18
+ description: string
19
+ path: string
20
+ }
21
+
22
+ export interface SkillResult {
23
+ found: boolean
24
+ name: string
25
+ content?: string
26
+ path?: string
27
+ error?: string
28
+ availableSkills?: string[]
29
+ }
30
+
31
+ /**
32
+ * Get the base directories where skills can be found.
33
+ * Searches in order of priority:
34
+ * 1. Project-local skills (.claude/skills/)
35
+ * 2. Source skills (src/skills/) - for development
36
+ * 3. Dist skills (dist/skills/) - for installed packages
37
+ */
38
+ export function getSkillDirectories(cwd?: string): string[] {
39
+ const dirs: string[] = []
40
+
41
+ // 1. Project-local skills (highest priority)
42
+ const projectDir = cwd || process.cwd()
43
+ dirs.push(join(projectDir, ".claude", "skills"))
44
+
45
+ // 2. Check if we're running from a compiled binary (bun compile)
46
+ // In compiled binaries, import.meta.url returns /$bunfs/... virtual path
47
+ // We detect this by checking if the URL starts with /$bunfs
48
+ const moduleUrl = import.meta.url
49
+ const isCompiledBinary = moduleUrl.startsWith("file:///$bunfs") || moduleUrl.startsWith("/$bunfs")
50
+
51
+ if (isCompiledBinary) {
52
+ // For compiled binaries, process.execPath contains the actual binary path
53
+ const binaryPath = process.execPath
54
+ if (binaryPath && existsSync(binaryPath)) {
55
+ const binDir = dirname(binaryPath) // bin/
56
+ const packageDir = dirname(binDir) // platform package root (e.g., @d3k/darwin-arm64)
57
+ dirs.push(join(packageDir, "skills"))
58
+ }
59
+ }
60
+
61
+ // 3. Source and dist skills from the d3k package
62
+ // Handle both ESM (__dirname equivalent) and different execution contexts
63
+ try {
64
+ const currentFile = fileURLToPath(import.meta.url)
65
+ const srcDir = dirname(currentFile) // src/skills
66
+ const packageRoot = dirname(dirname(srcDir)) // package root
67
+
68
+ // Source skills (for development)
69
+ dirs.push(join(packageRoot, "src", "skills"))
70
+
71
+ // Dist skills (for installed package)
72
+ dirs.push(join(packageRoot, "dist", "skills"))
73
+
74
+ // Also check if we're in a binary distribution
75
+ dirs.push(join(packageRoot, "skills"))
76
+ } catch {
77
+ // Fallback for CommonJS or other contexts
78
+ }
79
+
80
+ return dirs.filter((dir) => existsSync(dir))
81
+ }
82
+
83
+ /**
84
+ * Find a skill by name.
85
+ * Returns the path to the SKILL.md file if found.
86
+ */
87
+ export function findSkill(name: string, cwd?: string): string | null {
88
+ const skillDirs = getSkillDirectories(cwd)
89
+
90
+ for (const dir of skillDirs) {
91
+ const skillPath = join(dir, name, "SKILL.md")
92
+ if (existsSync(skillPath)) {
93
+ return skillPath
94
+ }
95
+ }
96
+
97
+ return null
98
+ }
99
+
100
+ /**
101
+ * Get the content of a skill by name.
102
+ */
103
+ export function getSkill(name: string, cwd?: string): SkillResult {
104
+ const skillPath = findSkill(name, cwd)
105
+
106
+ if (skillPath) {
107
+ try {
108
+ const content = readFileSync(skillPath, "utf-8")
109
+ return {
110
+ found: true,
111
+ name,
112
+ content,
113
+ path: skillPath
114
+ }
115
+ } catch (error) {
116
+ return {
117
+ found: false,
118
+ name,
119
+ error: `Failed to read skill: ${error instanceof Error ? error.message : String(error)}`,
120
+ availableSkills: listAvailableSkills(cwd)
121
+ }
122
+ }
123
+ }
124
+
125
+ return {
126
+ found: false,
127
+ name,
128
+ error: `Skill "${name}" not found`,
129
+ availableSkills: listAvailableSkills(cwd)
130
+ }
131
+ }
132
+
133
+ /**
134
+ * List all available skills.
135
+ */
136
+ export function listAvailableSkills(cwd?: string): string[] {
137
+ const skills = new Set<string>()
138
+ const skillDirs = getSkillDirectories(cwd)
139
+
140
+ for (const dir of skillDirs) {
141
+ try {
142
+ const entries = readdirSync(dir, { withFileTypes: true })
143
+ for (const entry of entries) {
144
+ if (entry.isDirectory()) {
145
+ const skillFile = join(dir, entry.name, "SKILL.md")
146
+ if (existsSync(skillFile)) {
147
+ skills.add(entry.name)
148
+ }
149
+ }
150
+ }
151
+ } catch {
152
+ // Directory might not exist or be readable
153
+ }
154
+ }
155
+
156
+ return Array.from(skills).sort()
157
+ }
158
+
159
+ /**
160
+ * Get detailed info about all available skills.
161
+ */
162
+ export function getSkillsInfo(cwd?: string): SkillInfo[] {
163
+ const skillsMap = new Map<string, SkillInfo>()
164
+ const skillDirs = getSkillDirectories(cwd)
165
+
166
+ for (const dir of skillDirs) {
167
+ try {
168
+ const entries = readdirSync(dir, { withFileTypes: true })
169
+ for (const entry of entries) {
170
+ if (entry.isDirectory() && !skillsMap.has(entry.name)) {
171
+ const skillPath = join(dir, entry.name, "SKILL.md")
172
+ if (existsSync(skillPath)) {
173
+ try {
174
+ const content = readFileSync(skillPath, "utf-8")
175
+ const description = extractDescription(content)
176
+ skillsMap.set(entry.name, {
177
+ name: entry.name,
178
+ description,
179
+ path: skillPath
180
+ })
181
+ } catch {
182
+ // Skip unreadable skills
183
+ }
184
+ }
185
+ }
186
+ }
187
+ } catch {
188
+ // Directory might not exist or be readable
189
+ }
190
+ }
191
+
192
+ return Array.from(skillsMap.values()).sort((a, b) => a.name.localeCompare(b.name))
193
+ }
194
+
195
+ /**
196
+ * Extract description from SKILL.md frontmatter or first paragraph.
197
+ */
198
+ function extractDescription(content: string): string {
199
+ // Try to extract from YAML frontmatter
200
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
201
+ if (frontmatterMatch) {
202
+ const frontmatter = frontmatterMatch[1]
203
+ const descMatch = frontmatter.match(/description:\s*(.+)/)
204
+ if (descMatch) {
205
+ return descMatch[1].trim()
206
+ }
207
+ }
208
+
209
+ // Fallback: use first non-heading, non-empty line
210
+ const lines = content.split("\n")
211
+ for (const line of lines) {
212
+ const trimmed = line.trim()
213
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("---")) {
214
+ return trimmed.slice(0, 100) + (trimmed.length > 100 ? "..." : "")
215
+ }
216
+ }
217
+
218
+ return "No description available"
219
+ }