@oro.ad/nuxt-claude-devtools 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +149 -13
  2. package/dist/client/200.html +1 -1
  3. package/dist/client/404.html +1 -1
  4. package/dist/client/_nuxt/{BpkYThsl.js → BRCY8pHC.js} +1 -1
  5. package/dist/client/_nuxt/BVHVIm9H.js +12 -0
  6. package/dist/client/_nuxt/BZrcCMrf.js +1 -0
  7. package/dist/client/_nuxt/BbEuL4Z6.js +1 -0
  8. package/dist/client/_nuxt/BmjlsnUc.js +1 -0
  9. package/dist/client/_nuxt/D2NL8Xro.js +7 -0
  10. package/dist/client/_nuxt/D9qGFoJm.js +4 -0
  11. package/dist/client/_nuxt/DImlDIT-.js +59 -0
  12. package/dist/client/_nuxt/{B1H6wO_D.js → DV075BoS.js} +1 -1
  13. package/dist/client/_nuxt/{D-z88P1l.js → DYNukx3V.js} +1 -1
  14. package/dist/client/_nuxt/Dbw96V2H.js +7 -0
  15. package/dist/client/_nuxt/FllXIyfS.js +8 -0
  16. package/dist/client/_nuxt/MarkdownContent.WwTYmYZK.css +1 -0
  17. package/dist/client/_nuxt/TvBJGid1.js +1 -0
  18. package/dist/client/_nuxt/XJ4dJUK2.js +1 -0
  19. package/dist/client/_nuxt/builds/latest.json +1 -1
  20. package/dist/client/_nuxt/builds/meta/e8ae4dbb-462d-47a2-9aa2-50bed9498ab2.json +1 -0
  21. package/dist/client/_nuxt/e7kgpy_n.js +4 -0
  22. package/dist/client/_nuxt/entry.DwDQaFYc.css +1 -0
  23. package/dist/client/_nuxt/index.Bomb3OYy.css +1 -0
  24. package/dist/client/agents/index.html +1 -0
  25. package/dist/client/commands/index.html +1 -0
  26. package/dist/client/docs/index.html +1 -0
  27. package/dist/client/index.html +1 -1
  28. package/dist/client/mcp/index.html +1 -1
  29. package/dist/client/skills/index.html +1 -0
  30. package/dist/module.json +1 -1
  31. package/dist/runtime/server/agents-manager.d.ts +31 -0
  32. package/dist/runtime/server/agents-manager.js +193 -0
  33. package/dist/runtime/server/claude-session.d.ts +15 -0
  34. package/dist/runtime/server/claude-session.js +456 -5
  35. package/dist/runtime/server/commands-manager.d.ts +24 -0
  36. package/dist/runtime/server/commands-manager.js +132 -0
  37. package/dist/runtime/server/docs-manager.d.ts +48 -0
  38. package/dist/runtime/server/docs-manager.js +189 -0
  39. package/dist/runtime/server/history-manager.d.ts +24 -0
  40. package/dist/runtime/server/history-manager.js +184 -0
  41. package/dist/runtime/server/skills-manager.d.ts +36 -0
  42. package/dist/runtime/server/skills-manager.js +210 -0
  43. package/dist/runtime/types.d.ts +156 -0
  44. package/dist/runtime/types.js +0 -0
  45. package/package.json +16 -1
  46. package/dist/client/_nuxt/C2ORx7Gd.js +0 -1
  47. package/dist/client/_nuxt/CfGtRVGd.js +0 -4
  48. package/dist/client/_nuxt/DJn_CTvm.js +0 -1
  49. package/dist/client/_nuxt/EMyRkg8p.js +0 -1
  50. package/dist/client/_nuxt/PGt8fA_Y.js +0 -1
  51. package/dist/client/_nuxt/builds/meta/88c99fa0-10e0-4015-a61a-e0c6d7a01859.json +0 -1
  52. package/dist/client/_nuxt/entry.Ci1n7Rlt.css +0 -1
@@ -0,0 +1,48 @@
1
+ export interface DocFile {
2
+ path: string;
3
+ name: string;
4
+ content: string;
5
+ updatedAt: string;
6
+ }
7
+ export interface LlmsSource {
8
+ url: string;
9
+ domain: string;
10
+ title?: string;
11
+ description?: string;
12
+ addedAt: string;
13
+ }
14
+ export interface LlmsStore {
15
+ version: 1;
16
+ sources: LlmsSource[];
17
+ }
18
+ export declare class DocsManager {
19
+ private docsDir;
20
+ private llmsPath;
21
+ private projectPath;
22
+ constructor(projectPath: string);
23
+ getDocFiles(): DocFile[];
24
+ private scanDirectory;
25
+ getDocFile(relativePath: string): DocFile | null;
26
+ saveDocFile(relativePath: string, content: string): DocFile;
27
+ deleteDocFile(relativePath: string): boolean;
28
+ private get claudeMdPath();
29
+ getClaudeMd(): {
30
+ content: string;
31
+ exists: boolean;
32
+ updatedAt: string | null;
33
+ };
34
+ saveClaudeMd(content: string): {
35
+ content: string;
36
+ exists: boolean;
37
+ updatedAt: string;
38
+ };
39
+ private loadLlmsStore;
40
+ private saveLlmsStore;
41
+ getLlmsSources(): LlmsSource[];
42
+ addLlmsSource(url: string, title?: string, description?: string): LlmsSource;
43
+ removeLlmsSource(url: string): boolean;
44
+ updateLlmsSource(url: string, updates: {
45
+ title?: string;
46
+ description?: string;
47
+ }): LlmsSource | null;
48
+ }
@@ -0,0 +1,189 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { basename, dirname, join, relative } from "node:path";
3
+ import { createLogger } from "../logger.js";
4
+ const log = createLogger("docs", { timestamp: true });
5
+ export class DocsManager {
6
+ docsDir;
7
+ llmsPath;
8
+ projectPath;
9
+ constructor(projectPath) {
10
+ this.projectPath = projectPath;
11
+ this.docsDir = join(projectPath, ".claude", "docs");
12
+ this.llmsPath = join(projectPath, ".claude-devtools", "llms.json");
13
+ if (!existsSync(this.docsDir)) {
14
+ mkdirSync(this.docsDir, { recursive: true });
15
+ log("Created docs directory", { path: this.docsDir });
16
+ }
17
+ }
18
+ // ============ Doc Files ============
19
+ // Get all doc files recursively
20
+ getDocFiles() {
21
+ const files = [];
22
+ this.scanDirectory(this.docsDir, files);
23
+ return files.sort((a, b) => a.path.localeCompare(b.path));
24
+ }
25
+ scanDirectory(dir, files) {
26
+ if (!existsSync(dir)) return;
27
+ const entries = readdirSync(dir);
28
+ for (const entry of entries) {
29
+ const fullPath = join(dir, entry);
30
+ const stat = statSync(fullPath);
31
+ if (stat.isDirectory()) {
32
+ this.scanDirectory(fullPath, files);
33
+ } else if (entry.endsWith(".md")) {
34
+ const relativePath = relative(this.docsDir, fullPath);
35
+ const content = readFileSync(fullPath, "utf-8");
36
+ files.push({
37
+ path: relativePath,
38
+ name: basename(entry, ".md"),
39
+ content,
40
+ updatedAt: stat.mtime.toISOString()
41
+ });
42
+ }
43
+ }
44
+ }
45
+ // Get single doc file
46
+ getDocFile(relativePath) {
47
+ const fullPath = join(this.docsDir, relativePath);
48
+ if (!existsSync(fullPath)) return null;
49
+ const stat = statSync(fullPath);
50
+ const content = readFileSync(fullPath, "utf-8");
51
+ return {
52
+ path: relativePath,
53
+ name: basename(relativePath, ".md"),
54
+ content,
55
+ updatedAt: stat.mtime.toISOString()
56
+ };
57
+ }
58
+ // Create or update doc file
59
+ saveDocFile(relativePath, content) {
60
+ if (!relativePath.endsWith(".md")) {
61
+ relativePath += ".md";
62
+ }
63
+ const fullPath = join(this.docsDir, relativePath);
64
+ const dir = dirname(fullPath);
65
+ if (!existsSync(dir)) {
66
+ mkdirSync(dir, { recursive: true });
67
+ }
68
+ writeFileSync(fullPath, content, "utf-8");
69
+ log("Saved doc file", { path: relativePath });
70
+ return {
71
+ path: relativePath,
72
+ name: basename(relativePath, ".md"),
73
+ content,
74
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
75
+ };
76
+ }
77
+ // Delete doc file
78
+ deleteDocFile(relativePath) {
79
+ const fullPath = join(this.docsDir, relativePath);
80
+ if (!existsSync(fullPath)) return false;
81
+ unlinkSync(fullPath);
82
+ log("Deleted doc file", { path: relativePath });
83
+ return true;
84
+ }
85
+ // ============ CLAUDE.md ============
86
+ get claudeMdPath() {
87
+ return join(this.projectPath, "CLAUDE.md");
88
+ }
89
+ // Get CLAUDE.md content
90
+ getClaudeMd() {
91
+ const path = this.claudeMdPath;
92
+ if (!existsSync(path)) {
93
+ return { content: "", exists: false, updatedAt: null };
94
+ }
95
+ const stat = statSync(path);
96
+ const content = readFileSync(path, "utf-8");
97
+ return {
98
+ content,
99
+ exists: true,
100
+ updatedAt: stat.mtime.toISOString()
101
+ };
102
+ }
103
+ // Save CLAUDE.md content
104
+ saveClaudeMd(content) {
105
+ writeFileSync(this.claudeMdPath, content, "utf-8");
106
+ log("Saved CLAUDE.md");
107
+ return {
108
+ content,
109
+ exists: true,
110
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
111
+ };
112
+ }
113
+ // ============ LLMS Sources ============
114
+ loadLlmsStore() {
115
+ try {
116
+ if (existsSync(this.llmsPath)) {
117
+ const data = readFileSync(this.llmsPath, "utf-8");
118
+ return JSON.parse(data);
119
+ }
120
+ } catch (error) {
121
+ log("Failed to load llms store", { error });
122
+ }
123
+ return {
124
+ version: 1,
125
+ sources: []
126
+ };
127
+ }
128
+ saveLlmsStore(store) {
129
+ const dir = dirname(this.llmsPath);
130
+ if (!existsSync(dir)) {
131
+ mkdirSync(dir, { recursive: true });
132
+ }
133
+ writeFileSync(this.llmsPath, JSON.stringify(store, null, 2));
134
+ log("Saved llms store");
135
+ }
136
+ // Get all LLMS sources
137
+ getLlmsSources() {
138
+ return this.loadLlmsStore().sources;
139
+ }
140
+ // Add LLMS source
141
+ addLlmsSource(url, title, description) {
142
+ const store = this.loadLlmsStore();
143
+ let domain;
144
+ try {
145
+ const urlObj = new URL(url);
146
+ domain = urlObj.hostname;
147
+ } catch {
148
+ domain = url.replace(/^https?:\/\//, "").split("/")[0];
149
+ }
150
+ const existing = store.sources.find((s) => s.url === url);
151
+ if (existing) {
152
+ existing.title = title || existing.title;
153
+ existing.description = description || existing.description;
154
+ this.saveLlmsStore(store);
155
+ return existing;
156
+ }
157
+ const source = {
158
+ url,
159
+ domain,
160
+ title,
161
+ description,
162
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
163
+ };
164
+ store.sources.push(source);
165
+ this.saveLlmsStore(store);
166
+ log("Added LLMS source", { url, domain });
167
+ return source;
168
+ }
169
+ // Remove LLMS source
170
+ removeLlmsSource(url) {
171
+ const store = this.loadLlmsStore();
172
+ const index = store.sources.findIndex((s) => s.url === url);
173
+ if (index === -1) return false;
174
+ store.sources.splice(index, 1);
175
+ this.saveLlmsStore(store);
176
+ log("Removed LLMS source", { url });
177
+ return true;
178
+ }
179
+ // Update LLMS source metadata
180
+ updateLlmsSource(url, updates) {
181
+ const store = this.loadLlmsStore();
182
+ const source = store.sources.find((s) => s.url === url);
183
+ if (!source) return null;
184
+ if (updates.title !== void 0) source.title = updates.title;
185
+ if (updates.description !== void 0) source.description = updates.description;
186
+ this.saveLlmsStore(store);
187
+ return source;
188
+ }
189
+ }
@@ -0,0 +1,24 @@
1
+ import type { Conversation, Message } from '../types.js';
2
+ export declare class HistoryManager {
3
+ private storePath;
4
+ private store;
5
+ private projectPath;
6
+ constructor(projectPath: string);
7
+ private loadStore;
8
+ private saveStore;
9
+ private generateId;
10
+ createConversation(): Conversation;
11
+ getActiveConversation(): Conversation;
12
+ addMessage(message: Message): void;
13
+ updateLastAssistantMessage(updates: Partial<Message>): void;
14
+ getConversations(): Conversation[];
15
+ getConversation(id: string): Conversation | undefined;
16
+ setActiveConversation(id: string): Conversation | undefined;
17
+ deleteConversation(id: string): boolean;
18
+ resetSession(): Conversation;
19
+ getActiveConversationId(): string | null;
20
+ setClaudeSessionId(sessionId: string): void;
21
+ getClaudeSessionId(): string | null;
22
+ formatHistoryForSystemPrompt(): string | null;
23
+ hasHistoryForRecovery(): boolean;
24
+ }
@@ -0,0 +1,184 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createLogger } from "../logger.js";
4
+ const log = createLogger("history", { timestamp: true });
5
+ export class HistoryManager {
6
+ storePath;
7
+ store;
8
+ projectPath;
9
+ constructor(projectPath) {
10
+ this.projectPath = projectPath;
11
+ const historyDir = join(projectPath, ".claude-devtools");
12
+ this.storePath = join(historyDir, "history.json");
13
+ if (!existsSync(historyDir)) {
14
+ mkdirSync(historyDir, { recursive: true });
15
+ log("Created history directory", { path: historyDir });
16
+ }
17
+ this.store = this.loadStore();
18
+ }
19
+ loadStore() {
20
+ try {
21
+ if (existsSync(this.storePath)) {
22
+ const data = readFileSync(this.storePath, "utf-8");
23
+ const parsed = JSON.parse(data);
24
+ log("Loaded history store", { conversations: parsed.conversations.length });
25
+ return parsed;
26
+ }
27
+ } catch (error) {
28
+ log("Failed to load history store", { error });
29
+ }
30
+ return {
31
+ version: 1,
32
+ conversations: [],
33
+ activeConversationId: null
34
+ };
35
+ }
36
+ saveStore() {
37
+ try {
38
+ writeFileSync(this.storePath, JSON.stringify(this.store, null, 2));
39
+ log("Saved history store");
40
+ } catch (error) {
41
+ log("Failed to save history store", { error });
42
+ }
43
+ }
44
+ generateId() {
45
+ return `conv_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
46
+ }
47
+ // Create new conversation
48
+ createConversation() {
49
+ const conversation = {
50
+ id: this.generateId(),
51
+ messages: [],
52
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
53
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
54
+ projectPath: this.projectPath
55
+ };
56
+ this.store.conversations.unshift(conversation);
57
+ this.store.activeConversationId = conversation.id;
58
+ this.saveStore();
59
+ log("Created conversation", { id: conversation.id });
60
+ return conversation;
61
+ }
62
+ // Get active conversation or create new one
63
+ getActiveConversation() {
64
+ if (this.store.activeConversationId) {
65
+ const conv = this.store.conversations.find(
66
+ (c) => c.id === this.store.activeConversationId
67
+ );
68
+ if (conv) return conv;
69
+ }
70
+ return this.createConversation();
71
+ }
72
+ // Add message to active conversation
73
+ addMessage(message) {
74
+ const conversation = this.getActiveConversation();
75
+ conversation.messages.push(message);
76
+ conversation.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
77
+ if (!conversation.title && message.role === "user") {
78
+ conversation.title = message.content.substring(0, 50) + (message.content.length > 50 ? "..." : "");
79
+ }
80
+ this.saveStore();
81
+ }
82
+ // Update last assistant message (for streaming completion)
83
+ updateLastAssistantMessage(updates) {
84
+ const conversation = this.getActiveConversation();
85
+ const lastAssistant = [...conversation.messages].reverse().find((m) => m.role === "assistant");
86
+ if (lastAssistant) {
87
+ Object.assign(lastAssistant, updates);
88
+ conversation.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
89
+ this.saveStore();
90
+ }
91
+ }
92
+ // Get all conversations (for history list)
93
+ getConversations() {
94
+ return this.store.conversations;
95
+ }
96
+ // Get specific conversation by ID
97
+ getConversation(id) {
98
+ return this.store.conversations.find((c) => c.id === id);
99
+ }
100
+ // Set active conversation
101
+ setActiveConversation(id) {
102
+ const conversation = this.getConversation(id);
103
+ if (conversation) {
104
+ this.store.activeConversationId = id;
105
+ this.saveStore();
106
+ }
107
+ return conversation;
108
+ }
109
+ // Delete conversation
110
+ deleteConversation(id) {
111
+ const index = this.store.conversations.findIndex((c) => c.id === id);
112
+ if (index !== -1) {
113
+ this.store.conversations.splice(index, 1);
114
+ if (this.store.activeConversationId === id) {
115
+ this.store.activeConversationId = this.store.conversations[0]?.id || null;
116
+ }
117
+ this.saveStore();
118
+ return true;
119
+ }
120
+ return false;
121
+ }
122
+ // Reset (new conversation)
123
+ resetSession() {
124
+ this.store.activeConversationId = null;
125
+ return this.createConversation();
126
+ }
127
+ // Get active conversation ID
128
+ getActiveConversationId() {
129
+ return this.store.activeConversationId;
130
+ }
131
+ // Set Claude session ID for active conversation (empty string clears it)
132
+ setClaudeSessionId(sessionId) {
133
+ const conversation = this.getActiveConversation();
134
+ if (sessionId) {
135
+ conversation.claudeSessionId = sessionId;
136
+ } else {
137
+ delete conversation.claudeSessionId;
138
+ }
139
+ this.saveStore();
140
+ log("Set Claude session ID", { conversationId: conversation.id, sessionId: sessionId || "(cleared)" });
141
+ }
142
+ // Get Claude session ID for active conversation
143
+ getClaudeSessionId() {
144
+ const conversation = this.store.activeConversationId ? this.store.conversations.find((c) => c.id === this.store.activeConversationId) : null;
145
+ return conversation?.claudeSessionId || null;
146
+ }
147
+ // Format conversation history for system prompt (fallback when --resume fails)
148
+ formatHistoryForSystemPrompt() {
149
+ const conversation = this.store.activeConversationId ? this.store.conversations.find((c) => c.id === this.store.activeConversationId) : null;
150
+ if (!conversation || conversation.messages.length === 0) {
151
+ return null;
152
+ }
153
+ const lines = [
154
+ "=== CONVERSATION HISTORY (context recovery) ===",
155
+ "The following is the history of our previous conversation. Please continue from where we left off.",
156
+ ""
157
+ ];
158
+ for (const msg of conversation.messages) {
159
+ const role = msg.role === "user" ? "User" : "Assistant";
160
+ const timestamp = typeof msg.timestamp === "string" ? msg.timestamp : msg.timestamp.toISOString();
161
+ lines.push(`[${role}] (${timestamp})`);
162
+ if (msg.role === "assistant") {
163
+ if (msg.content) {
164
+ lines.push(msg.content);
165
+ }
166
+ } else {
167
+ lines.push(msg.content);
168
+ }
169
+ lines.push("");
170
+ }
171
+ lines.push("=== END OF HISTORY ===");
172
+ lines.push("");
173
+ log("Formatted history for system prompt", {
174
+ conversationId: conversation.id,
175
+ messageCount: conversation.messages.length
176
+ });
177
+ return lines.join("\n");
178
+ }
179
+ // Check if conversation has history that can be used for context recovery
180
+ hasHistoryForRecovery() {
181
+ const conversation = this.store.activeConversationId ? this.store.conversations.find((c) => c.id === this.store.activeConversationId) : null;
182
+ return !!conversation && conversation.messages.length > 0;
183
+ }
184
+ }
@@ -0,0 +1,36 @@
1
+ export interface Skill {
2
+ name: string;
3
+ description: string;
4
+ content: string;
5
+ rawContent: string;
6
+ argumentHint?: string;
7
+ disableModelInvocation?: boolean;
8
+ userInvocable?: boolean;
9
+ allowedTools?: string[];
10
+ model?: string;
11
+ context?: 'fork';
12
+ agent?: string;
13
+ updatedAt: string;
14
+ }
15
+ export declare class SkillsManager {
16
+ private skillsDir;
17
+ constructor(projectPath: string);
18
+ private parseFrontmatter;
19
+ private buildFrontmatter;
20
+ getSkills(): Skill[];
21
+ getSkill(name: string): Skill | null;
22
+ getSkillNames(): string[];
23
+ saveSkill(skill: {
24
+ name: string;
25
+ description: string;
26
+ content: string;
27
+ argumentHint?: string;
28
+ disableModelInvocation?: boolean;
29
+ userInvocable?: boolean;
30
+ allowedTools?: string[];
31
+ model?: string;
32
+ context?: 'fork';
33
+ agent?: string;
34
+ }): Skill;
35
+ deleteSkill(name: string): boolean;
36
+ }
@@ -0,0 +1,210 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createLogger } from "../logger.js";
4
+ const log = createLogger("skills", { timestamp: true });
5
+ export class SkillsManager {
6
+ skillsDir;
7
+ constructor(projectPath) {
8
+ this.skillsDir = join(projectPath, ".claude", "skills");
9
+ if (!existsSync(this.skillsDir)) {
10
+ mkdirSync(this.skillsDir, { recursive: true });
11
+ log("Created skills directory", { path: this.skillsDir });
12
+ }
13
+ }
14
+ // Parse YAML frontmatter from markdown content
15
+ parseFrontmatter(content) {
16
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
17
+ const match = content.match(frontmatterRegex);
18
+ if (!match) {
19
+ return { frontmatter: {}, body: content.trim() };
20
+ }
21
+ const [, yaml, body] = match;
22
+ const frontmatter = {};
23
+ for (const line of yaml.split("\n")) {
24
+ const colonIndex = line.indexOf(":");
25
+ if (colonIndex === -1) continue;
26
+ const key = line.slice(0, colonIndex).trim();
27
+ const value = line.slice(colonIndex + 1).trim();
28
+ switch (key) {
29
+ case "name":
30
+ frontmatter.name = value;
31
+ break;
32
+ case "description":
33
+ frontmatter.description = value;
34
+ break;
35
+ case "argument-hint":
36
+ frontmatter["argument-hint"] = value;
37
+ break;
38
+ case "disable-model-invocation":
39
+ frontmatter["disable-model-invocation"] = value === "true";
40
+ break;
41
+ case "user-invocable":
42
+ frontmatter["user-invocable"] = value === "true";
43
+ break;
44
+ case "allowed-tools":
45
+ frontmatter["allowed-tools"] = value;
46
+ break;
47
+ case "model":
48
+ frontmatter.model = value;
49
+ break;
50
+ case "context":
51
+ frontmatter.context = value;
52
+ break;
53
+ case "agent":
54
+ frontmatter.agent = value;
55
+ break;
56
+ }
57
+ }
58
+ return { frontmatter, body: body.trim() };
59
+ }
60
+ // Build frontmatter string
61
+ buildFrontmatter(skill) {
62
+ const lines = ["---"];
63
+ if (skill.name) {
64
+ lines.push(`name: ${skill.name}`);
65
+ }
66
+ if (skill.description) {
67
+ lines.push(`description: ${skill.description}`);
68
+ }
69
+ if (skill.argumentHint) {
70
+ lines.push(`argument-hint: ${skill.argumentHint}`);
71
+ }
72
+ if (skill.disableModelInvocation !== void 0) {
73
+ lines.push(`disable-model-invocation: ${skill.disableModelInvocation}`);
74
+ }
75
+ if (skill.userInvocable !== void 0) {
76
+ lines.push(`user-invocable: ${skill.userInvocable}`);
77
+ }
78
+ if (skill.allowedTools && skill.allowedTools.length > 0) {
79
+ lines.push(`allowed-tools: ${skill.allowedTools.join(", ")}`);
80
+ }
81
+ if (skill.model) {
82
+ lines.push(`model: ${skill.model}`);
83
+ }
84
+ if (skill.context) {
85
+ lines.push(`context: ${skill.context}`);
86
+ }
87
+ if (skill.agent) {
88
+ lines.push(`agent: ${skill.agent}`);
89
+ }
90
+ lines.push("---");
91
+ return lines.join("\n");
92
+ }
93
+ // Get all skills
94
+ getSkills() {
95
+ const skills = [];
96
+ if (!existsSync(this.skillsDir)) return skills;
97
+ const entries = readdirSync(this.skillsDir);
98
+ for (const entry of entries) {
99
+ const skillDir = join(this.skillsDir, entry);
100
+ const stat = statSync(skillDir);
101
+ if (!stat.isDirectory()) continue;
102
+ const skillFile = join(skillDir, "SKILL.md");
103
+ if (!existsSync(skillFile)) continue;
104
+ const fileStat = statSync(skillFile);
105
+ const rawContent = readFileSync(skillFile, "utf-8");
106
+ const { frontmatter, body } = this.parseFrontmatter(rawContent);
107
+ skills.push({
108
+ name: frontmatter.name || entry,
109
+ description: frontmatter.description || "",
110
+ content: body,
111
+ rawContent,
112
+ argumentHint: frontmatter["argument-hint"],
113
+ disableModelInvocation: frontmatter["disable-model-invocation"],
114
+ userInvocable: frontmatter["user-invocable"],
115
+ allowedTools: frontmatter["allowed-tools"] ? frontmatter["allowed-tools"].split(",").map((s) => s.trim()) : void 0,
116
+ model: frontmatter.model,
117
+ context: frontmatter.context === "fork" ? "fork" : void 0,
118
+ agent: frontmatter.agent,
119
+ updatedAt: fileStat.mtime.toISOString()
120
+ });
121
+ }
122
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
123
+ }
124
+ // Get single skill
125
+ getSkill(name) {
126
+ const skillDir = join(this.skillsDir, name);
127
+ const skillFile = join(skillDir, "SKILL.md");
128
+ if (!existsSync(skillFile)) return null;
129
+ const stat = statSync(skillFile);
130
+ const rawContent = readFileSync(skillFile, "utf-8");
131
+ const { frontmatter, body } = this.parseFrontmatter(rawContent);
132
+ return {
133
+ name: frontmatter.name || name,
134
+ description: frontmatter.description || "",
135
+ content: body,
136
+ rawContent,
137
+ argumentHint: frontmatter["argument-hint"],
138
+ disableModelInvocation: frontmatter["disable-model-invocation"],
139
+ userInvocable: frontmatter["user-invocable"],
140
+ allowedTools: frontmatter["allowed-tools"] ? frontmatter["allowed-tools"].split(",").map((s) => s.trim()) : void 0,
141
+ model: frontmatter.model,
142
+ context: frontmatter.context === "fork" ? "fork" : void 0,
143
+ agent: frontmatter.agent,
144
+ updatedAt: stat.mtime.toISOString()
145
+ };
146
+ }
147
+ // Get skill names only (for agent skills selector)
148
+ getSkillNames() {
149
+ if (!existsSync(this.skillsDir)) return [];
150
+ const names = [];
151
+ const entries = readdirSync(this.skillsDir);
152
+ for (const entry of entries) {
153
+ const skillDir = join(this.skillsDir, entry);
154
+ const stat = statSync(skillDir);
155
+ if (!stat.isDirectory()) continue;
156
+ const skillFile = join(skillDir, "SKILL.md");
157
+ if (existsSync(skillFile)) {
158
+ names.push(entry);
159
+ }
160
+ }
161
+ return names.sort();
162
+ }
163
+ // Create or update skill
164
+ saveSkill(skill) {
165
+ const safeName = skill.name.replace(/[^\w-]/g, "-").toLowerCase();
166
+ const skillDir = join(this.skillsDir, safeName);
167
+ const skillFile = join(skillDir, "SKILL.md");
168
+ if (!existsSync(skillDir)) {
169
+ mkdirSync(skillDir, { recursive: true });
170
+ }
171
+ const frontmatter = this.buildFrontmatter({
172
+ name: safeName,
173
+ description: skill.description,
174
+ argumentHint: skill.argumentHint,
175
+ disableModelInvocation: skill.disableModelInvocation,
176
+ userInvocable: skill.userInvocable,
177
+ allowedTools: skill.allowedTools,
178
+ model: skill.model,
179
+ context: skill.context,
180
+ agent: skill.agent
181
+ });
182
+ const rawContent = `${frontmatter}
183
+
184
+ ${skill.content}`;
185
+ writeFileSync(skillFile, rawContent, "utf-8");
186
+ log("Saved skill", { name: safeName, path: skillFile });
187
+ return {
188
+ name: safeName,
189
+ description: skill.description,
190
+ content: skill.content,
191
+ rawContent,
192
+ argumentHint: skill.argumentHint,
193
+ disableModelInvocation: skill.disableModelInvocation,
194
+ userInvocable: skill.userInvocable,
195
+ allowedTools: skill.allowedTools,
196
+ model: skill.model,
197
+ context: skill.context,
198
+ agent: skill.agent,
199
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
200
+ };
201
+ }
202
+ // Delete skill
203
+ deleteSkill(name) {
204
+ const skillDir = join(this.skillsDir, name);
205
+ if (!existsSync(skillDir)) return false;
206
+ rmSync(skillDir, { recursive: true });
207
+ log("Deleted skill", { name });
208
+ return true;
209
+ }
210
+ }