@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.
- package/README.md +149 -13
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/{Dgh4EhoJ.js → BRCY8pHC.js} +1 -1
- package/dist/client/_nuxt/BVHVIm9H.js +12 -0
- package/dist/client/_nuxt/BZrcCMrf.js +1 -0
- package/dist/client/_nuxt/BbEuL4Z6.js +1 -0
- package/dist/client/_nuxt/BmjlsnUc.js +1 -0
- package/dist/client/_nuxt/D2NL8Xro.js +7 -0
- package/dist/client/_nuxt/D9qGFoJm.js +4 -0
- package/dist/client/_nuxt/DImlDIT-.js +59 -0
- package/dist/client/_nuxt/{B6Pm7LNk.js → DV075BoS.js} +1 -1
- package/dist/client/_nuxt/{BngXb2T5.js → DYNukx3V.js} +1 -1
- package/dist/client/_nuxt/Dbw96V2H.js +7 -0
- package/dist/client/_nuxt/FllXIyfS.js +8 -0
- package/dist/client/_nuxt/MarkdownContent.WwTYmYZK.css +1 -0
- package/dist/client/_nuxt/TvBJGid1.js +1 -0
- package/dist/client/_nuxt/XJ4dJUK2.js +1 -0
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/e8ae4dbb-462d-47a2-9aa2-50bed9498ab2.json +1 -0
- package/dist/client/_nuxt/e7kgpy_n.js +4 -0
- package/dist/client/_nuxt/entry.DwDQaFYc.css +1 -0
- package/dist/client/_nuxt/error-404.2GhCpCfF.css +1 -0
- package/dist/client/_nuxt/error-500.DqdIhFrl.css +1 -0
- package/dist/client/_nuxt/index.Bomb3OYy.css +1 -0
- package/dist/client/agents/index.html +1 -0
- package/dist/client/commands/index.html +1 -0
- package/dist/client/docs/index.html +1 -0
- package/dist/client/index.html +1 -1
- package/dist/client/mcp/index.html +1 -1
- package/dist/client/skills/index.html +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +22 -13
- package/dist/runtime/logger.d.ts +5 -0
- package/dist/runtime/logger.js +12 -0
- package/dist/runtime/server/agents-manager.d.ts +31 -0
- package/dist/runtime/server/agents-manager.js +193 -0
- package/dist/runtime/server/claude-session.d.ts +21 -4
- package/dist/runtime/server/claude-session.js +467 -25
- package/dist/runtime/server/commands-manager.d.ts +24 -0
- package/dist/runtime/server/commands-manager.js +132 -0
- package/dist/runtime/server/docs-manager.d.ts +48 -0
- package/dist/runtime/server/docs-manager.js +189 -0
- package/dist/runtime/server/history-manager.d.ts +24 -0
- package/dist/runtime/server/history-manager.js +184 -0
- package/dist/runtime/server/plugins/socket.io.d.ts +2 -0
- package/dist/runtime/server/plugins/socket.io.js +48 -0
- package/dist/runtime/server/skills-manager.d.ts +36 -0
- package/dist/runtime/server/skills-manager.js +210 -0
- package/dist/runtime/types.d.ts +156 -0
- package/dist/runtime/types.js +0 -0
- package/package.json +17 -1
- package/dist/client/_nuxt/BWmwj9se.js +0 -1
- package/dist/client/_nuxt/CRwOrvc3.js +0 -1
- package/dist/client/_nuxt/DMBGVttU.js +0 -1
- package/dist/client/_nuxt/DYOOVyPh.js +0 -1
- package/dist/client/_nuxt/JxSLzYFz.js +0 -4
- package/dist/client/_nuxt/builds/meta/f2b44466-6d7e-4b5c-bf6b-f6c7cf9e0d59.json +0 -1
- package/dist/client/_nuxt/entry.Ci1n7Rlt.css +0 -1
- package/dist/client/_nuxt/error-404.BLrjNXsr.css +0 -1
- 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,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
|
+
}
|