ccpanel 0.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 +78 -0
- package/dist/chunk-7ROXP6AO.js +466 -0
- package/dist/cli.js +1056 -0
- package/dist/server/app.js +6 -0
- package/package.json +35 -0
- package/web/dist/assets/index-CscUfGaI.css +1 -0
- package/web/dist/assets/index-DoKBz_TC.js +88 -0
- package/web/dist/index.html +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# ccpanel
|
|
2
|
+
|
|
3
|
+
本地图形化管理 Claude Code 配置:MCP 服务器、Skills、子 Agent、Commands、配置文件、Memory、Sessions、Plugins(全局级 + 项目级)。
|
|
4
|
+
|
|
5
|
+
## 使用
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx ccpanel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
启动后自动打开浏览器(默认 http://localhost:7788)。
|
|
12
|
+
|
|
13
|
+
## 开发
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install && npm --prefix web install
|
|
17
|
+
# 终端 A:后端
|
|
18
|
+
npm run dev
|
|
19
|
+
# 终端 B:前端
|
|
20
|
+
npm --prefix web run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 构建
|
|
24
|
+
|
|
25
|
+
修改代码后需重新编译,构建产物供 `npx claude-config-manager` 生产运行:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run build # 全量:前端 web/dist + 后端 dist
|
|
29
|
+
npm run build:web # 仅前端,产物 web/dist(改动 web/ 下代码时)
|
|
30
|
+
npm run build:server # 仅后端,产物 dist/(改动 src/ 下代码时)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- 改完**前端**:`npm run build:web` 后刷新浏览器即可生效(后端静态托管 `web/dist`,无需重启)。
|
|
34
|
+
- 改完**后端**:`npm run build:server`;若用 `npm run dev`(tsx)运行后端,改动后需重启该进程才生效(tsx 不热重载)。
|
|
35
|
+
- 开发模式下前端 `npm --prefix web run dev` 自带热更新(HMR),免手动构建。
|
|
36
|
+
|
|
37
|
+
## 测试
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm test
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 发布 / 分发
|
|
44
|
+
|
|
45
|
+
让别人也能使用,有两种方式。两者都会自动包含前端产物 `web/dist`,发布前请确保已执行过 `npm run build`(`npm publish` 会通过 `prepublishOnly` 自动构建)。
|
|
46
|
+
|
|
47
|
+
### 方式一:发布到 npm(推荐,别人 `npx` 即用)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm login # 首次需要 npm 账号
|
|
51
|
+
npm publish # prepublishOnly 会自动先 npm run build
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
发布后任何人可直接运行:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx ccpanel
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> 包名 `ccpanel` 目前在 npm 上可用;如后续被占用,可改用作用域名(如 `@你的用户名/ccpanel`),并以 `npm publish --access public` 发布。
|
|
61
|
+
|
|
62
|
+
### 方式二:分发 tarball(内网 / 小范围,无需发布)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run build # 先构建
|
|
66
|
+
npm pack # 生成 ccpanel-<version>.tgz
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
把生成的 `.tgz` 发给对方,对方安装运行:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install -g ./ccpanel-0.1.0.tgz
|
|
73
|
+
ccpanel
|
|
74
|
+
# 或免安装直接运行:
|
|
75
|
+
npx ./ccpanel-0.1.0.tgz
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> 使用者需 Node.js ≥ 20。工具为本地服务,启动后在 `localhost:7788` 读写本机 `~/.claude` 下的配置。
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
// src/server/app.ts
|
|
2
|
+
import { Hono as Hono10 } from "hono";
|
|
3
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4
|
+
import { ZodError } from "zod";
|
|
5
|
+
|
|
6
|
+
// src/core/config-store.ts
|
|
7
|
+
import { mkdir, readFile, rename, writeFile, copyFile } from "fs/promises";
|
|
8
|
+
import { dirname } from "path";
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
var ConfigCorruptError = class extends Error {
|
|
11
|
+
constructor(file) {
|
|
12
|
+
super(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u65E0\u6CD5\u89E3\u6790\uFF1A${file}`);
|
|
13
|
+
this.file = file;
|
|
14
|
+
this.name = "ConfigCorruptError";
|
|
15
|
+
}
|
|
16
|
+
file;
|
|
17
|
+
};
|
|
18
|
+
var InvalidJsonError = class extends Error {
|
|
19
|
+
constructor(message = "\u5185\u5BB9\u4E0D\u662F\u5408\u6CD5\u7684 JSON") {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "InvalidJsonError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var ConfigStore = class {
|
|
25
|
+
async readJson(file) {
|
|
26
|
+
let raw;
|
|
27
|
+
try {
|
|
28
|
+
raw = await readFile(file, "utf8");
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err.code === "ENOENT") return void 0;
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
if (raw.trim() === "") return void 0;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
} catch {
|
|
37
|
+
throw new ConfigCorruptError(file);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async writeJson(file, data) {
|
|
41
|
+
await mkdir(dirname(file), { recursive: true });
|
|
42
|
+
try {
|
|
43
|
+
await copyFile(file, file + ".bak");
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (err.code !== "ENOENT") throw err;
|
|
46
|
+
}
|
|
47
|
+
const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
48
|
+
await writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
49
|
+
await rename(tmp, file);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/server/routes/mcp.ts
|
|
54
|
+
import { Hono } from "hono";
|
|
55
|
+
|
|
56
|
+
// src/core/paths.ts
|
|
57
|
+
import { createHash } from "crypto";
|
|
58
|
+
import { join, resolve } from "path";
|
|
59
|
+
function resolvePaths(opts) {
|
|
60
|
+
const { home, projectPath } = opts;
|
|
61
|
+
const panelDir = join(home, ".ccpanel");
|
|
62
|
+
const paths = {
|
|
63
|
+
userClaudeJson: join(home, ".claude.json"),
|
|
64
|
+
userSkillsDir: join(home, ".claude", "skills"),
|
|
65
|
+
userAgentsDir: join(home, ".claude", "agents"),
|
|
66
|
+
userCommandsDir: join(home, ".claude", "commands"),
|
|
67
|
+
userDisabledMcp: join(panelDir, "disabled-mcp.json"),
|
|
68
|
+
userDisabledSkillsDir: join(panelDir, "disabled-skills"),
|
|
69
|
+
userSettings: join(home, ".claude", "settings.json"),
|
|
70
|
+
userClaudeMd: join(home, ".claude", "CLAUDE.md"),
|
|
71
|
+
userProjectsDir: join(home, ".claude", "projects"),
|
|
72
|
+
pluginsDir: join(home, ".claude", "plugins"),
|
|
73
|
+
installedPluginsJson: join(home, ".claude", "plugins", "installed_plugins.json"),
|
|
74
|
+
knownMarketplacesJson: join(home, ".claude", "plugins", "known_marketplaces.json"),
|
|
75
|
+
panelDir
|
|
76
|
+
};
|
|
77
|
+
if (projectPath) {
|
|
78
|
+
paths.projectMcpJson = join(projectPath, ".mcp.json");
|
|
79
|
+
paths.projectSkillsDir = join(projectPath, ".claude", "skills");
|
|
80
|
+
paths.projectAgentsDir = join(projectPath, ".claude", "agents");
|
|
81
|
+
paths.projectCommandsDir = join(projectPath, ".claude", "commands");
|
|
82
|
+
paths.projectSettings = join(projectPath, ".claude", "settings.json");
|
|
83
|
+
paths.projectClaudeMd = join(projectPath, "CLAUDE.md");
|
|
84
|
+
const ns = createHash("sha256").update(resolve(projectPath)).digest("hex");
|
|
85
|
+
paths.projectDisabledSkillsDir = join(panelDir, "projects", ns, "disabled-skills");
|
|
86
|
+
}
|
|
87
|
+
return paths;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/shared/schemas.ts
|
|
91
|
+
import { z } from "zod";
|
|
92
|
+
var ScopeSchema = z.enum(["user", "project"]);
|
|
93
|
+
var StdioMcpServerSchema = z.object({
|
|
94
|
+
type: z.literal("stdio").optional(),
|
|
95
|
+
command: z.string().min(1),
|
|
96
|
+
args: z.array(z.string()).optional(),
|
|
97
|
+
env: z.record(z.string()).optional()
|
|
98
|
+
});
|
|
99
|
+
var RemoteMcpServerSchema = z.object({
|
|
100
|
+
type: z.enum(["sse", "http"]),
|
|
101
|
+
url: z.string().url(),
|
|
102
|
+
headers: z.record(z.string()).optional()
|
|
103
|
+
});
|
|
104
|
+
var McpServerSchema = z.union([
|
|
105
|
+
RemoteMcpServerSchema,
|
|
106
|
+
StdioMcpServerSchema
|
|
107
|
+
]);
|
|
108
|
+
var McpServerInputSchema = z.intersection(
|
|
109
|
+
z.object({ name: z.string().min(1) }),
|
|
110
|
+
McpServerSchema
|
|
111
|
+
);
|
|
112
|
+
var SkillFrontmatterSchema = z.object({ name: z.string().min(1), description: z.string().min(1) }).passthrough();
|
|
113
|
+
var SkillFileWriteSchema = z.object({ content: z.string() });
|
|
114
|
+
var MemoryWriteSchema = z.object({ content: z.string() });
|
|
115
|
+
var ConfigFileIdSchema = z.enum(["settings", "claude-json"]);
|
|
116
|
+
var ConfigFileWriteSchema = z.object({ content: z.string() });
|
|
117
|
+
var ScopeQuerySchema = z.object({
|
|
118
|
+
scope: ScopeSchema,
|
|
119
|
+
projectPath: z.string().optional()
|
|
120
|
+
}).refine((v) => v.scope === "user" || !!v.projectPath, {
|
|
121
|
+
message: "project scope \u9700\u8981 projectPath",
|
|
122
|
+
path: ["projectPath"]
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// src/server/routes/mcp.ts
|
|
126
|
+
function mcpRoutes(deps) {
|
|
127
|
+
const app = new Hono();
|
|
128
|
+
const ctx = (c) => {
|
|
129
|
+
const q = ScopeQuerySchema.parse({
|
|
130
|
+
scope: c.req.query("scope"),
|
|
131
|
+
projectPath: c.req.query("projectPath")
|
|
132
|
+
});
|
|
133
|
+
return { q, paths: resolvePaths({ home: deps.home, projectPath: q.projectPath }) };
|
|
134
|
+
};
|
|
135
|
+
app.get("/", async (c) => {
|
|
136
|
+
const { q, paths } = ctx(c);
|
|
137
|
+
return c.json(await deps.mcp.list(paths, q.scope));
|
|
138
|
+
});
|
|
139
|
+
app.post("/", async (c) => {
|
|
140
|
+
const { q, paths } = ctx(c);
|
|
141
|
+
const input = McpServerInputSchema.parse(await c.req.json());
|
|
142
|
+
await deps.mcp.upsert(paths, q.scope, input);
|
|
143
|
+
return c.json(await deps.mcp.list(paths, q.scope));
|
|
144
|
+
});
|
|
145
|
+
app.put("/:name", async (c) => {
|
|
146
|
+
const { q, paths } = ctx(c);
|
|
147
|
+
const body = await c.req.json();
|
|
148
|
+
const input = McpServerInputSchema.parse({ ...body, name: c.req.param("name") });
|
|
149
|
+
await deps.mcp.upsert(paths, q.scope, input);
|
|
150
|
+
return c.json(await deps.mcp.list(paths, q.scope));
|
|
151
|
+
});
|
|
152
|
+
app.delete("/:name", async (c) => {
|
|
153
|
+
const { q, paths } = ctx(c);
|
|
154
|
+
await deps.mcp.remove(paths, q.scope, c.req.param("name"));
|
|
155
|
+
return c.json(await deps.mcp.list(paths, q.scope));
|
|
156
|
+
});
|
|
157
|
+
app.post("/:name/toggle", async (c) => {
|
|
158
|
+
const { q, paths } = ctx(c);
|
|
159
|
+
const { enabled } = await c.req.json();
|
|
160
|
+
await deps.mcp.toggle(paths, q.scope, c.req.param("name"), enabled);
|
|
161
|
+
return c.json(await deps.mcp.list(paths, q.scope));
|
|
162
|
+
});
|
|
163
|
+
return app;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/server/routes/skills.ts
|
|
167
|
+
import { Hono as Hono2 } from "hono";
|
|
168
|
+
import { z as z2 } from "zod";
|
|
169
|
+
var SkillBodySchema = z2.object({
|
|
170
|
+
name: z2.string().min(1),
|
|
171
|
+
frontmatter: SkillFrontmatterSchema,
|
|
172
|
+
body: z2.string()
|
|
173
|
+
});
|
|
174
|
+
function skillRoutes(deps) {
|
|
175
|
+
const app = new Hono2();
|
|
176
|
+
const ctx = (c) => {
|
|
177
|
+
const q = ScopeQuerySchema.parse({
|
|
178
|
+
scope: c.req.query("scope"),
|
|
179
|
+
projectPath: c.req.query("projectPath")
|
|
180
|
+
});
|
|
181
|
+
return { q, paths: resolvePaths({ home: deps.home, projectPath: q.projectPath }) };
|
|
182
|
+
};
|
|
183
|
+
app.get("/", async (c) => {
|
|
184
|
+
const { q, paths } = ctx(c);
|
|
185
|
+
return c.json(await deps.skills.list(paths, q.scope));
|
|
186
|
+
});
|
|
187
|
+
app.get("/:name", async (c) => {
|
|
188
|
+
const { q, paths } = ctx(c);
|
|
189
|
+
return c.json(await deps.skills.read(paths, q.scope, c.req.param("name")));
|
|
190
|
+
});
|
|
191
|
+
app.get("/:name/files", async (c) => {
|
|
192
|
+
const { q, paths } = ctx(c);
|
|
193
|
+
return c.json(await deps.skills.listFiles(paths, q.scope, c.req.param("name")));
|
|
194
|
+
});
|
|
195
|
+
app.get("/:name/files/:filePath{.+}", async (c) => {
|
|
196
|
+
const { q, paths } = ctx(c);
|
|
197
|
+
return c.json(
|
|
198
|
+
await deps.skills.readFileRaw(
|
|
199
|
+
paths,
|
|
200
|
+
q.scope,
|
|
201
|
+
c.req.param("name"),
|
|
202
|
+
c.req.param("filePath")
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
app.put("/:name/files/:filePath{.+}", async (c) => {
|
|
207
|
+
const { q, paths } = ctx(c);
|
|
208
|
+
const { content } = SkillFileWriteSchema.parse(await c.req.json());
|
|
209
|
+
await deps.skills.writeFileRaw(
|
|
210
|
+
paths,
|
|
211
|
+
q.scope,
|
|
212
|
+
c.req.param("name"),
|
|
213
|
+
c.req.param("filePath"),
|
|
214
|
+
content
|
|
215
|
+
);
|
|
216
|
+
return c.json({ ok: true });
|
|
217
|
+
});
|
|
218
|
+
app.post("/", async (c) => {
|
|
219
|
+
const { q, paths } = ctx(c);
|
|
220
|
+
const data = SkillBodySchema.parse(await c.req.json());
|
|
221
|
+
await deps.skills.upsert(paths, q.scope, data.name, data.frontmatter, data.body);
|
|
222
|
+
return c.json(await deps.skills.list(paths, q.scope));
|
|
223
|
+
});
|
|
224
|
+
app.put("/:name", async (c) => {
|
|
225
|
+
const { q, paths } = ctx(c);
|
|
226
|
+
const data = SkillBodySchema.parse({ ...await c.req.json(), name: c.req.param("name") });
|
|
227
|
+
await deps.skills.upsert(paths, q.scope, data.name, data.frontmatter, data.body);
|
|
228
|
+
return c.json(await deps.skills.list(paths, q.scope));
|
|
229
|
+
});
|
|
230
|
+
app.delete("/:name", async (c) => {
|
|
231
|
+
const { q, paths } = ctx(c);
|
|
232
|
+
await deps.skills.remove(paths, q.scope, c.req.param("name"));
|
|
233
|
+
return c.json(await deps.skills.list(paths, q.scope));
|
|
234
|
+
});
|
|
235
|
+
app.post("/:name/toggle", async (c) => {
|
|
236
|
+
const { q, paths } = ctx(c);
|
|
237
|
+
const { enabled } = await c.req.json();
|
|
238
|
+
await deps.skills.toggle(paths, q.scope, c.req.param("name"), enabled);
|
|
239
|
+
return c.json(await deps.skills.list(paths, q.scope));
|
|
240
|
+
});
|
|
241
|
+
return app;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/server/routes/projects.ts
|
|
245
|
+
import { Hono as Hono3 } from "hono";
|
|
246
|
+
import { z as z3 } from "zod";
|
|
247
|
+
function projectRoutes(deps) {
|
|
248
|
+
const app = new Hono3();
|
|
249
|
+
app.get("/", async (c) => c.json({ projects: await deps.projects.list() }));
|
|
250
|
+
app.post("/", async (c) => {
|
|
251
|
+
const { path } = z3.object({ path: z3.string().min(1) }).parse(await c.req.json());
|
|
252
|
+
return c.json({ projects: await deps.projects.add(path) });
|
|
253
|
+
});
|
|
254
|
+
app.delete("/", async (c) => {
|
|
255
|
+
const { path } = z3.object({ path: z3.string().min(1) }).parse(await c.req.json());
|
|
256
|
+
return c.json({ projects: await deps.projects.remove(path) });
|
|
257
|
+
});
|
|
258
|
+
return app;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/server/routes/memory.ts
|
|
262
|
+
import { Hono as Hono4 } from "hono";
|
|
263
|
+
function memoryRoutes(deps) {
|
|
264
|
+
const app = new Hono4();
|
|
265
|
+
const ctx = (c) => {
|
|
266
|
+
const q = ScopeQuerySchema.parse({
|
|
267
|
+
scope: c.req.query("scope"),
|
|
268
|
+
projectPath: c.req.query("projectPath")
|
|
269
|
+
});
|
|
270
|
+
return { q, paths: resolvePaths({ home: deps.home, projectPath: q.projectPath }) };
|
|
271
|
+
};
|
|
272
|
+
app.get("/", async (c) => {
|
|
273
|
+
const { q, paths } = ctx(c);
|
|
274
|
+
return c.json(await deps.memory.read(paths, q.scope));
|
|
275
|
+
});
|
|
276
|
+
app.put("/", async (c) => {
|
|
277
|
+
const { q, paths } = ctx(c);
|
|
278
|
+
const { content } = MemoryWriteSchema.parse(await c.req.json());
|
|
279
|
+
await deps.memory.write(paths, q.scope, content);
|
|
280
|
+
return c.json({ ok: true });
|
|
281
|
+
});
|
|
282
|
+
return app;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/server/routes/memory-files.ts
|
|
286
|
+
import { Hono as Hono5 } from "hono";
|
|
287
|
+
import { z as z4 } from "zod";
|
|
288
|
+
var QuerySchema = z4.object({ projectPath: z4.string().min(1) });
|
|
289
|
+
function memoryFilesRoutes(deps) {
|
|
290
|
+
const app = new Hono5();
|
|
291
|
+
const ctx = (c) => {
|
|
292
|
+
const { projectPath } = QuerySchema.parse({ projectPath: c.req.query("projectPath") });
|
|
293
|
+
return { projectPath, paths: resolvePaths({ home: deps.home, projectPath }) };
|
|
294
|
+
};
|
|
295
|
+
app.get("/", async (c) => {
|
|
296
|
+
const { projectPath, paths } = ctx(c);
|
|
297
|
+
return c.json(await deps.memoryFiles.overview(paths, projectPath));
|
|
298
|
+
});
|
|
299
|
+
app.get("/:file", async (c) => {
|
|
300
|
+
const { projectPath, paths } = ctx(c);
|
|
301
|
+
return c.json(await deps.memoryFiles.read(paths, projectPath, c.req.param("file")));
|
|
302
|
+
});
|
|
303
|
+
return app;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/server/routes/sessions.ts
|
|
307
|
+
import { Hono as Hono6 } from "hono";
|
|
308
|
+
import { z as z5 } from "zod";
|
|
309
|
+
var QuerySchema2 = z5.object({ projectPath: z5.string().min(1) });
|
|
310
|
+
function sessionRoutes(deps) {
|
|
311
|
+
const app = new Hono6();
|
|
312
|
+
const ctx = (c) => {
|
|
313
|
+
const { projectPath } = QuerySchema2.parse({ projectPath: c.req.query("projectPath") });
|
|
314
|
+
return { projectPath, paths: resolvePaths({ home: deps.home, projectPath }) };
|
|
315
|
+
};
|
|
316
|
+
app.get("/", async (c) => {
|
|
317
|
+
const { projectPath, paths } = ctx(c);
|
|
318
|
+
return c.json(await deps.sessions.list(paths, projectPath));
|
|
319
|
+
});
|
|
320
|
+
app.get("/:id", async (c) => {
|
|
321
|
+
const { projectPath, paths } = ctx(c);
|
|
322
|
+
return c.json(await deps.sessions.read(paths, projectPath, c.req.param("id")));
|
|
323
|
+
});
|
|
324
|
+
return app;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/server/routes/plugins.ts
|
|
328
|
+
import { Hono as Hono7 } from "hono";
|
|
329
|
+
import { z as z6 } from "zod";
|
|
330
|
+
var EnabledBodySchema = z6.object({ enabled: z6.boolean() });
|
|
331
|
+
var ComponentQuerySchema = z6.object({
|
|
332
|
+
type: z6.enum(["skill", "command", "agent", "hook", "mcp"]),
|
|
333
|
+
name: z6.string().min(1)
|
|
334
|
+
});
|
|
335
|
+
function pluginRoutes(deps) {
|
|
336
|
+
const app = new Hono7();
|
|
337
|
+
const paths = () => resolvePaths({ home: deps.home });
|
|
338
|
+
app.get("/", async (c) => c.json(await deps.plugins.list(paths())));
|
|
339
|
+
app.get(
|
|
340
|
+
"/:id",
|
|
341
|
+
async (c) => c.json(await deps.plugins.detail(paths(), c.req.param("id")))
|
|
342
|
+
);
|
|
343
|
+
app.get("/:id/component", async (c) => {
|
|
344
|
+
const q = ComponentQuerySchema.parse({
|
|
345
|
+
type: c.req.query("type"),
|
|
346
|
+
name: c.req.query("name")
|
|
347
|
+
});
|
|
348
|
+
return c.json(
|
|
349
|
+
await deps.plugins.readComponent(
|
|
350
|
+
paths(),
|
|
351
|
+
c.req.param("id"),
|
|
352
|
+
q.type,
|
|
353
|
+
q.name
|
|
354
|
+
)
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
app.patch("/:id", async (c) => {
|
|
358
|
+
const { enabled } = EnabledBodySchema.parse(await c.req.json());
|
|
359
|
+
await deps.plugins.setEnabled(paths(), c.req.param("id"), enabled);
|
|
360
|
+
return c.json(await deps.plugins.list(paths()));
|
|
361
|
+
});
|
|
362
|
+
return app;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/server/routes/config-files.ts
|
|
366
|
+
import { Hono as Hono8 } from "hono";
|
|
367
|
+
function configFileRoutes(deps) {
|
|
368
|
+
const app = new Hono8();
|
|
369
|
+
const ctx = (c) => {
|
|
370
|
+
const fileId = ConfigFileIdSchema.parse(c.req.param("file"));
|
|
371
|
+
const q = ScopeQuerySchema.parse({
|
|
372
|
+
scope: c.req.query("scope"),
|
|
373
|
+
projectPath: c.req.query("projectPath")
|
|
374
|
+
});
|
|
375
|
+
return { fileId, q, paths: resolvePaths({ home: deps.home, projectPath: q.projectPath }) };
|
|
376
|
+
};
|
|
377
|
+
app.get("/:file", async (c) => {
|
|
378
|
+
const { fileId, q, paths } = ctx(c);
|
|
379
|
+
return c.json(await deps.configFiles.read(paths, fileId, q.scope));
|
|
380
|
+
});
|
|
381
|
+
app.put("/:file", async (c) => {
|
|
382
|
+
const { fileId, q, paths } = ctx(c);
|
|
383
|
+
const { content } = ConfigFileWriteSchema.parse(await c.req.json());
|
|
384
|
+
await deps.configFiles.write(paths, fileId, q.scope, content);
|
|
385
|
+
return c.json({ ok: true });
|
|
386
|
+
});
|
|
387
|
+
return app;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/server/routes/agent-commands.ts
|
|
391
|
+
import { Hono as Hono9 } from "hono";
|
|
392
|
+
import { z as z7 } from "zod";
|
|
393
|
+
var CreateSchema = z7.object({ name: z7.string().min(1), content: z7.string() });
|
|
394
|
+
var WriteSchema = z7.object({ content: z7.string() });
|
|
395
|
+
function agentCommandRoutes(deps, kind) {
|
|
396
|
+
const app = new Hono9();
|
|
397
|
+
const ctx = (c) => {
|
|
398
|
+
const q = ScopeQuerySchema.parse({
|
|
399
|
+
scope: c.req.query("scope"),
|
|
400
|
+
projectPath: c.req.query("projectPath")
|
|
401
|
+
});
|
|
402
|
+
return { q, paths: resolvePaths({ home: deps.home, projectPath: q.projectPath }) };
|
|
403
|
+
};
|
|
404
|
+
app.get("/", async (c) => {
|
|
405
|
+
const { q, paths } = ctx(c);
|
|
406
|
+
return c.json(await deps.agentCommands.list(paths, kind, q.scope));
|
|
407
|
+
});
|
|
408
|
+
app.get("/:name", async (c) => {
|
|
409
|
+
const { q, paths } = ctx(c);
|
|
410
|
+
return c.json(await deps.agentCommands.read(paths, kind, q.scope, c.req.param("name")));
|
|
411
|
+
});
|
|
412
|
+
app.post("/", async (c) => {
|
|
413
|
+
const { q, paths } = ctx(c);
|
|
414
|
+
const data = CreateSchema.parse(await c.req.json());
|
|
415
|
+
await deps.agentCommands.write(paths, kind, q.scope, data.name, data.content);
|
|
416
|
+
return c.json(await deps.agentCommands.list(paths, kind, q.scope));
|
|
417
|
+
});
|
|
418
|
+
app.put("/:name", async (c) => {
|
|
419
|
+
const { q, paths } = ctx(c);
|
|
420
|
+
const { content } = WriteSchema.parse(await c.req.json());
|
|
421
|
+
await deps.agentCommands.write(paths, kind, q.scope, c.req.param("name"), content);
|
|
422
|
+
return c.json({ ok: true });
|
|
423
|
+
});
|
|
424
|
+
app.delete("/:name", async (c) => {
|
|
425
|
+
const { q, paths } = ctx(c);
|
|
426
|
+
await deps.agentCommands.remove(paths, kind, q.scope, c.req.param("name"));
|
|
427
|
+
return c.json(await deps.agentCommands.list(paths, kind, q.scope));
|
|
428
|
+
});
|
|
429
|
+
return app;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/server/app.ts
|
|
433
|
+
function createApp(deps) {
|
|
434
|
+
const app = new Hono10();
|
|
435
|
+
app.onError((err, c) => {
|
|
436
|
+
if (err instanceof ZodError) {
|
|
437
|
+
return c.json({ error: "\u6821\u9A8C\u5931\u8D25", issues: err.issues }, 400);
|
|
438
|
+
}
|
|
439
|
+
if (err instanceof ConfigCorruptError) {
|
|
440
|
+
return c.json({ error: err.message, file: err.file }, 409);
|
|
441
|
+
}
|
|
442
|
+
if (err instanceof InvalidJsonError) {
|
|
443
|
+
return c.json({ error: err.message }, 400);
|
|
444
|
+
}
|
|
445
|
+
return c.json({ error: err.message }, 500);
|
|
446
|
+
});
|
|
447
|
+
app.route("/api/mcp", mcpRoutes(deps));
|
|
448
|
+
app.route("/api/skills", skillRoutes(deps));
|
|
449
|
+
app.route("/api/projects", projectRoutes(deps));
|
|
450
|
+
app.route("/api/memory", memoryRoutes(deps));
|
|
451
|
+
app.route("/api/memory-files", memoryFilesRoutes(deps));
|
|
452
|
+
app.route("/api/sessions", sessionRoutes(deps));
|
|
453
|
+
app.route("/api/plugins", pluginRoutes(deps));
|
|
454
|
+
app.route("/api/config-files", configFileRoutes(deps));
|
|
455
|
+
app.route("/api/agents", agentCommandRoutes(deps, "agents"));
|
|
456
|
+
app.route("/api/commands", agentCommandRoutes(deps, "commands"));
|
|
457
|
+
app.use("/*", serveStatic({ root: deps.webRoot ?? "./web/dist" }));
|
|
458
|
+
return app;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export {
|
|
462
|
+
InvalidJsonError,
|
|
463
|
+
ConfigStore,
|
|
464
|
+
SkillFrontmatterSchema,
|
|
465
|
+
createApp
|
|
466
|
+
};
|