@oro.ad/nuxt-claude-devtools 1.0.6 → 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 (61) 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/{Dgh4EhoJ.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/{B6Pm7LNk.js → DV075BoS.js} +1 -1
  13. package/dist/client/_nuxt/{BngXb2T5.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/error-404.2GhCpCfF.css +1 -0
  24. package/dist/client/_nuxt/error-500.DqdIhFrl.css +1 -0
  25. package/dist/client/_nuxt/index.Bomb3OYy.css +1 -0
  26. package/dist/client/agents/index.html +1 -0
  27. package/dist/client/commands/index.html +1 -0
  28. package/dist/client/docs/index.html +1 -0
  29. package/dist/client/index.html +1 -1
  30. package/dist/client/mcp/index.html +1 -1
  31. package/dist/client/skills/index.html +1 -0
  32. package/dist/module.json +1 -1
  33. package/dist/module.mjs +22 -13
  34. package/dist/runtime/logger.d.ts +5 -0
  35. package/dist/runtime/logger.js +12 -0
  36. package/dist/runtime/server/agents-manager.d.ts +31 -0
  37. package/dist/runtime/server/agents-manager.js +193 -0
  38. package/dist/runtime/server/claude-session.d.ts +21 -4
  39. package/dist/runtime/server/claude-session.js +467 -25
  40. package/dist/runtime/server/commands-manager.d.ts +24 -0
  41. package/dist/runtime/server/commands-manager.js +132 -0
  42. package/dist/runtime/server/docs-manager.d.ts +48 -0
  43. package/dist/runtime/server/docs-manager.js +189 -0
  44. package/dist/runtime/server/history-manager.d.ts +24 -0
  45. package/dist/runtime/server/history-manager.js +184 -0
  46. package/dist/runtime/server/plugins/socket.io.d.ts +2 -0
  47. package/dist/runtime/server/plugins/socket.io.js +48 -0
  48. package/dist/runtime/server/skills-manager.d.ts +36 -0
  49. package/dist/runtime/server/skills-manager.js +210 -0
  50. package/dist/runtime/types.d.ts +156 -0
  51. package/dist/runtime/types.js +0 -0
  52. package/package.json +17 -1
  53. package/dist/client/_nuxt/BWmwj9se.js +0 -1
  54. package/dist/client/_nuxt/CRwOrvc3.js +0 -1
  55. package/dist/client/_nuxt/DMBGVttU.js +0 -1
  56. package/dist/client/_nuxt/DYOOVyPh.js +0 -1
  57. package/dist/client/_nuxt/JxSLzYFz.js +0 -4
  58. package/dist/client/_nuxt/builds/meta/f2b44466-6d7e-4b5c-bf6b-f6c7cf9e0d59.json +0 -1
  59. package/dist/client/_nuxt/entry.Ci1n7Rlt.css +0 -1
  60. package/dist/client/_nuxt/error-404.BLrjNXsr.css +0 -1
  61. package/dist/client/_nuxt/error-500.DLkAwcfL.css +0 -1
@@ -0,0 +1,132 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ import { createLogger } from "../logger.js";
4
+ const log = createLogger("commands", { timestamp: true });
5
+ export class CommandsManager {
6
+ commandsDir;
7
+ constructor(projectPath) {
8
+ this.commandsDir = join(projectPath, ".claude", "commands");
9
+ if (!existsSync(this.commandsDir)) {
10
+ mkdirSync(this.commandsDir, { recursive: true });
11
+ log("Created commands directory", { path: this.commandsDir });
12
+ }
13
+ }
14
+ // Parse frontmatter from markdown content
15
+ parseFrontmatter(content) {
16
+ const frontmatterRegex = /^---\n((?:[^\n]*\n)*?)---\n([\s\S]*)$/;
17
+ const match = content.match(frontmatterRegex);
18
+ if (!match) {
19
+ return { frontmatter: {}, body: content };
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
+ if (key === "description") {
29
+ frontmatter.description = value;
30
+ } else if (key === "allowed-tools") {
31
+ frontmatter["allowed-tools"] = value;
32
+ } else if (key === "disable-model-invocation") {
33
+ frontmatter["disable-model-invocation"] = value === "true";
34
+ }
35
+ }
36
+ return { frontmatter, body: body.trim() };
37
+ }
38
+ // Build frontmatter string
39
+ buildFrontmatter(command) {
40
+ const lines = ["---"];
41
+ if (command.description) {
42
+ lines.push(`description: ${command.description}`);
43
+ }
44
+ if (command.allowedTools && command.allowedTools.length > 0) {
45
+ lines.push(`allowed-tools: ${command.allowedTools.join(", ")}`);
46
+ }
47
+ if (command.disableModelInvocation !== void 0) {
48
+ lines.push(`disable-model-invocation: ${command.disableModelInvocation}`);
49
+ }
50
+ lines.push("---");
51
+ return lines.join("\n");
52
+ }
53
+ // Get all slash commands
54
+ getCommands() {
55
+ const commands = [];
56
+ if (!existsSync(this.commandsDir)) return commands;
57
+ const entries = readdirSync(this.commandsDir);
58
+ for (const entry of entries) {
59
+ if (!entry.endsWith(".md")) continue;
60
+ const fullPath = join(this.commandsDir, entry);
61
+ const stat = statSync(fullPath);
62
+ if (stat.isDirectory()) continue;
63
+ const rawContent = readFileSync(fullPath, "utf-8");
64
+ const { frontmatter, body } = this.parseFrontmatter(rawContent);
65
+ commands.push({
66
+ name: basename(entry, ".md"),
67
+ path: entry,
68
+ description: frontmatter.description,
69
+ allowedTools: frontmatter["allowed-tools"] ? frontmatter["allowed-tools"].split(",").map((s) => s.trim()) : void 0,
70
+ disableModelInvocation: frontmatter["disable-model-invocation"],
71
+ content: body,
72
+ rawContent,
73
+ updatedAt: stat.mtime.toISOString()
74
+ });
75
+ }
76
+ return commands.sort((a, b) => a.name.localeCompare(b.name));
77
+ }
78
+ // Get single command
79
+ getCommand(name) {
80
+ const fileName = name.endsWith(".md") ? name : `${name}.md`;
81
+ const fullPath = join(this.commandsDir, fileName);
82
+ if (!existsSync(fullPath)) return null;
83
+ const stat = statSync(fullPath);
84
+ const rawContent = readFileSync(fullPath, "utf-8");
85
+ const { frontmatter, body } = this.parseFrontmatter(rawContent);
86
+ return {
87
+ name: basename(fileName, ".md"),
88
+ path: fileName,
89
+ description: frontmatter.description,
90
+ allowedTools: frontmatter["allowed-tools"] ? frontmatter["allowed-tools"].split(",").map((s) => s.trim()) : void 0,
91
+ disableModelInvocation: frontmatter["disable-model-invocation"],
92
+ content: body,
93
+ rawContent,
94
+ updatedAt: stat.mtime.toISOString()
95
+ };
96
+ }
97
+ // Create or update command
98
+ saveCommand(name, content, options) {
99
+ const safeName = name.replace(/[^\w-]/g, "-").toLowerCase();
100
+ const fileName = `${safeName}.md`;
101
+ const fullPath = join(this.commandsDir, fileName);
102
+ const frontmatter = this.buildFrontmatter({
103
+ description: options?.description,
104
+ allowedTools: options?.allowedTools,
105
+ disableModelInvocation: options?.disableModelInvocation
106
+ });
107
+ const rawContent = `${frontmatter}
108
+
109
+ ${content}`;
110
+ writeFileSync(fullPath, rawContent, "utf-8");
111
+ log("Saved command", { name: safeName });
112
+ return {
113
+ name: safeName,
114
+ path: fileName,
115
+ description: options?.description,
116
+ allowedTools: options?.allowedTools,
117
+ disableModelInvocation: options?.disableModelInvocation,
118
+ content,
119
+ rawContent,
120
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
121
+ };
122
+ }
123
+ // Delete command
124
+ deleteCommand(name) {
125
+ const fileName = name.endsWith(".md") ? name : `${name}.md`;
126
+ const fullPath = join(this.commandsDir, fileName);
127
+ if (!existsSync(fullPath)) return false;
128
+ unlinkSync(fullPath);
129
+ log("Deleted command", { name });
130
+ return true;
131
+ }
132
+ }
@@ -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,2 @@
1
+ declare const _default: import("nitropack").NitroAppPlugin;
2
+ export default _default;
@@ -0,0 +1,48 @@
1
+ import { defineNitroPlugin, useRuntimeConfig } from "nitropack/runtime";
2
+ import { Server as Engine } from "engine.io";
3
+ import { Server } from "socket.io";
4
+ import { defineEventHandler } from "h3";
5
+ import { createLogger } from "../../logger.js";
6
+ import { getClaudeSessionInstance, initClaudeSession } from "../claude-session.js";
7
+ const log = createLogger("plugin");
8
+ export default defineNitroPlugin((nitroApp) => {
9
+ const config = useRuntimeConfig();
10
+ const claudeConfig = config.claudeDevtools;
11
+ if (!claudeConfig) {
12
+ log("No config found, skipping Socket.IO setup");
13
+ return;
14
+ }
15
+ log("Initializing Socket.IO server");
16
+ initClaudeSession({
17
+ command: claudeConfig.claude.command,
18
+ args: claudeConfig.claude.args,
19
+ rootDir: claudeConfig.rootDir,
20
+ tunnelOrigin: claudeConfig.tunnelOrigin || null
21
+ });
22
+ const engine = new Engine();
23
+ const io = new Server();
24
+ io.bind(engine);
25
+ const session = getClaudeSessionInstance();
26
+ if (session) {
27
+ session.attachSocketIO(io);
28
+ }
29
+ nitroApp.router.use("/__claude_devtools_socket/", defineEventHandler({
30
+ handler(event) {
31
+ engine.handleRequest(event.node.req, event.node.res);
32
+ event._handled = true;
33
+ },
34
+ websocket: {
35
+ open(peer) {
36
+ engine.prepare(peer._internal.nodeReq);
37
+ engine.onWebSocket(
38
+ // @ts-expect-error - accessing internal
39
+ peer._internal.nodeReq,
40
+ // @ts-expect-error - accessing internal
41
+ peer._internal.nodeReq.socket,
42
+ peer.websocket
43
+ );
44
+ }
45
+ }
46
+ }));
47
+ log("Socket.IO server ready on /__claude_devtools_socket/");
48
+ });
@@ -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
+ }