ccpanel 0.2.0 → 0.3.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.
@@ -1,5 +1,5 @@
1
1
  // src/server/app.ts
2
- import { Hono as Hono12 } from "hono";
2
+ import { Hono as Hono13 } from "hono";
3
3
  import { serveStatic } from "@hono/node-server/serve-static";
4
4
  import { ZodError } from "zod";
5
5
 
@@ -87,8 +87,8 @@ function resolvePaths(opts) {
87
87
  const panelDir = join(home, ".ccpanel");
88
88
  const codexDir = join(home, ".codex");
89
89
  const agentsSkillsDir = join(home, ".agents", "skills");
90
- const userSkillsDir = provider === "codex" ? join(codexDir, "skills") : provider === "agents" ? agentsSkillsDir : join(home, ".claude", "skills");
91
- const userDisabledSkillsDir = provider === "codex" ? join(panelDir, "codex-disabled-skills") : provider === "agents" ? join(panelDir, "agents-disabled-skills") : join(panelDir, "disabled-skills");
90
+ const userSkillsDir = provider === "codex" ? join(codexDir, "skills") : provider === "agents" ? agentsSkillsDir : provider === "custom" ? opts.customSkillsDir ?? "" : join(home, ".claude", "skills");
91
+ const userDisabledSkillsDir = provider === "codex" ? join(panelDir, "codex-disabled-skills") : provider === "agents" ? join(panelDir, "agents-disabled-skills") : provider === "custom" ? join(panelDir, "custom-disabled-skills") : join(panelDir, "disabled-skills");
92
92
  const paths = {
93
93
  provider,
94
94
  userClaudeJson: join(home, ".claude.json"),
@@ -130,7 +130,7 @@ function resolvePaths(opts) {
130
130
  // src/shared/schemas.ts
131
131
  import { z } from "zod";
132
132
  var ScopeSchema = z.enum(["user", "project"]);
133
- var ProviderSchema = z.enum(["claude", "codex", "agents"]);
133
+ var ProviderSchema = z.enum(["claude", "codex", "agents", "custom"]);
134
134
  var McpProviderSchema = z.enum(["claude", "codex"]);
135
135
  var ProviderScopeQuerySchema = z.object({
136
136
  provider: ProviderSchema.default("claude"),
@@ -259,37 +259,43 @@ var SkillBodySchema = z2.object({
259
259
  frontmatter: SkillFrontmatterSchema,
260
260
  body: z2.string()
261
261
  });
262
+ var MoveToDirBodySchema = z2.object({ targetDir: z2.string().min(1) });
263
+ function isVirtualProvider(p) {
264
+ return p === "agents" || p === "custom";
265
+ }
262
266
  function skillRoutes(deps) {
263
267
  const app = new Hono2();
264
- const ctx = (c) => {
268
+ const ctx = async (c) => {
265
269
  const q = ProviderScopeQuerySchema.parse({
266
270
  provider: c.req.query("provider"),
267
271
  scope: c.req.query("scope"),
268
272
  projectPath: c.req.query("projectPath")
269
273
  });
274
+ const customSkillsDir = q.provider === "custom" ? await deps.customSkillsDir.get() ?? void 0 : void 0;
270
275
  return {
271
276
  q,
272
277
  paths: resolvePaths({
273
278
  home: deps.home,
274
279
  projectPath: q.projectPath,
275
- provider: q.provider
280
+ provider: q.provider,
281
+ customSkillsDir
276
282
  })
277
283
  };
278
284
  };
279
285
  app.get("/", async (c) => {
280
- const { q, paths } = ctx(c);
286
+ const { q, paths } = await ctx(c);
281
287
  return c.json(await deps.skills.list(paths, q.scope));
282
288
  });
283
289
  app.get("/:name", async (c) => {
284
- const { q, paths } = ctx(c);
290
+ const { q, paths } = await ctx(c);
285
291
  return c.json(await deps.skills.read(paths, q.scope, c.req.param("name")));
286
292
  });
287
293
  app.get("/:name/files", async (c) => {
288
- const { q, paths } = ctx(c);
294
+ const { q, paths } = await ctx(c);
289
295
  return c.json(await deps.skills.listFiles(paths, q.scope, c.req.param("name")));
290
296
  });
291
297
  app.get("/:name/files/:filePath{.+}", async (c) => {
292
- const { q, paths } = ctx(c);
298
+ const { q, paths } = await ctx(c);
293
299
  return c.json(
294
300
  await deps.skills.readFileRaw(
295
301
  paths,
@@ -300,7 +306,7 @@ function skillRoutes(deps) {
300
306
  );
301
307
  });
302
308
  app.put("/:name/files/:filePath{.+}", async (c) => {
303
- const { q, paths } = ctx(c);
309
+ const { q, paths } = await ctx(c);
304
310
  const { content, baseMtime } = SkillFileWriteSchema.parse(await c.req.json());
305
311
  await deps.skills.writeFileRaw(
306
312
  paths,
@@ -313,25 +319,25 @@ function skillRoutes(deps) {
313
319
  return c.json({ ok: true });
314
320
  });
315
321
  app.post("/", async (c) => {
316
- const { q, paths } = ctx(c);
322
+ const { q, paths } = await ctx(c);
317
323
  const data = SkillBodySchema.parse(await c.req.json());
318
324
  await deps.skills.upsert(paths, q.scope, data.name, data.frontmatter, data.body);
319
325
  return c.json(await deps.skills.list(paths, q.scope));
320
326
  });
321
327
  app.put("/:name", async (c) => {
322
- const { q, paths } = ctx(c);
328
+ const { q, paths } = await ctx(c);
323
329
  const data = SkillBodySchema.parse({ ...await c.req.json(), name: c.req.param("name") });
324
330
  await deps.skills.upsert(paths, q.scope, data.name, data.frontmatter, data.body);
325
331
  return c.json(await deps.skills.list(paths, q.scope));
326
332
  });
327
333
  app.delete("/:name", async (c) => {
328
- const { q, paths } = ctx(c);
334
+ const { q, paths } = await ctx(c);
329
335
  await deps.skills.remove(paths, q.scope, c.req.param("name"));
330
336
  return c.json(await deps.skills.list(paths, q.scope));
331
337
  });
332
338
  app.post("/:name/toggle", async (c) => {
333
- const { q, paths } = ctx(c);
334
- if (q.provider === "agents") {
339
+ const { q, paths } = await ctx(c);
340
+ if (isVirtualProvider(q.provider)) {
335
341
  return c.json({ error: "\u516C\u5171 skills \u4E0D\u652F\u6301\u542F\u7528/\u7981\u7528" }, 400);
336
342
  }
337
343
  const { enabled } = await c.req.json();
@@ -339,13 +345,22 @@ function skillRoutes(deps) {
339
345
  return c.json(await deps.skills.list(paths, q.scope));
340
346
  });
341
347
  app.post("/:name/make-shared", async (c) => {
342
- const { q } = ctx(c);
343
- if (q.provider === "agents") {
348
+ const { q } = await ctx(c);
349
+ if (isVirtualProvider(q.provider)) {
344
350
  return c.json({ error: "\u8F6C\u516C\u5171\u4EC5\u652F\u6301 claude/codex \u4E0B\u7684\u6280\u80FD" }, 400);
345
351
  }
346
352
  await deps.sync.makeShared(q.provider, c.req.param("name"));
347
353
  return c.json({ ok: true });
348
354
  });
355
+ app.post("/:name/move-to-dir", async (c) => {
356
+ const { q } = await ctx(c);
357
+ if (isVirtualProvider(q.provider)) {
358
+ return c.json({ error: "\u540C\u6B65\u5230\u76EE\u5F55\u4EC5\u652F\u6301 claude/codex \u4E0B\u7684\u6280\u80FD" }, 400);
359
+ }
360
+ const { targetDir } = MoveToDirBodySchema.parse(await c.req.json());
361
+ await deps.sync.linkToDirectory(q.provider, c.req.param("name"), targetDir);
362
+ return c.json({ ok: true });
363
+ });
349
364
  return app;
350
365
  }
351
366
 
@@ -376,31 +391,73 @@ function sharedSkillRoutes(deps) {
376
391
  return app;
377
392
  }
378
393
 
379
- // src/server/routes/projects.ts
394
+ // src/server/routes/custom-skills.ts
380
395
  import { Hono as Hono4 } from "hono";
381
396
  import { z as z4 } from "zod";
382
- function projectRoutes(deps) {
397
+ var SyncBodySchema2 = z4.object({ target: z4.enum(["claude", "codex"]) });
398
+ var DirBodySchema = z4.object({ path: z4.string().min(1) });
399
+ function customSkillRoutes(deps) {
383
400
  const app = new Hono4();
401
+ app.get("/dir", async (c) => {
402
+ return c.json({ path: await deps.customSkillsDir.get() });
403
+ });
404
+ app.put("/dir", async (c) => {
405
+ const { path } = DirBodySchema.parse(await c.req.json());
406
+ await deps.customSkillsDir.set(path);
407
+ return c.json({ ok: true });
408
+ });
409
+ app.get("/", async (c) => {
410
+ const dir = await deps.customSkillsDir.get();
411
+ if (!dir) return c.json([]);
412
+ const paths = resolvePaths({ home: deps.home, provider: "custom", customSkillsDir: dir });
413
+ const base = await deps.skills.list(paths, "user");
414
+ const withSync = await Promise.all(
415
+ base.map(async (s) => ({ ...s, sync: await deps.sync.status(s.name, dir) }))
416
+ );
417
+ return c.json(withSync);
418
+ });
419
+ app.post("/:name/sync", async (c) => {
420
+ const dir = await deps.customSkillsDir.get();
421
+ if (!dir) return c.json({ error: "\u5C1A\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49 skills \u76EE\u5F55" }, 400);
422
+ const { target } = SyncBodySchema2.parse(await c.req.json());
423
+ await deps.sync.syncTo(c.req.param("name"), target, dir);
424
+ return c.json({ ok: true });
425
+ });
426
+ app.delete("/:name/sync", async (c) => {
427
+ const dir = await deps.customSkillsDir.get();
428
+ if (!dir) return c.json({ error: "\u5C1A\u672A\u914D\u7F6E\u81EA\u5B9A\u4E49 skills \u76EE\u5F55" }, 400);
429
+ const { target } = SyncBodySchema2.parse(await c.req.json());
430
+ await deps.sync.unsync(c.req.param("name"), target, dir);
431
+ return c.json({ ok: true });
432
+ });
433
+ return app;
434
+ }
435
+
436
+ // src/server/routes/projects.ts
437
+ import { Hono as Hono5 } from "hono";
438
+ import { z as z5 } from "zod";
439
+ function projectRoutes(deps) {
440
+ const app = new Hono5();
384
441
  app.get("/", async (c) => c.json({ projects: await deps.projects.list() }));
385
442
  app.get(
386
443
  "/known",
387
444
  async (c) => c.json({ projects: await deps.projects.known() })
388
445
  );
389
446
  app.post("/", async (c) => {
390
- const { path } = z4.object({ path: z4.string().min(1) }).parse(await c.req.json());
447
+ const { path } = z5.object({ path: z5.string().min(1) }).parse(await c.req.json());
391
448
  return c.json({ projects: await deps.projects.add(path) });
392
449
  });
393
450
  app.delete("/", async (c) => {
394
- const { path } = z4.object({ path: z4.string().min(1) }).parse(await c.req.json());
451
+ const { path } = z5.object({ path: z5.string().min(1) }).parse(await c.req.json());
395
452
  return c.json({ projects: await deps.projects.remove(path) });
396
453
  });
397
454
  return app;
398
455
  }
399
456
 
400
457
  // src/server/routes/memory.ts
401
- import { Hono as Hono5 } from "hono";
458
+ import { Hono as Hono6 } from "hono";
402
459
  function memoryRoutes(deps) {
403
- const app = new Hono5();
460
+ const app = new Hono6();
404
461
  const ctx = (c) => {
405
462
  const q = ScopeQuerySchema.parse({
406
463
  scope: c.req.query("scope"),
@@ -422,14 +479,14 @@ function memoryRoutes(deps) {
422
479
  }
423
480
 
424
481
  // src/server/routes/memory-files.ts
425
- import { Hono as Hono6 } from "hono";
426
- import { z as z5 } from "zod";
427
- var ProviderQuerySchema = z5.object({
428
- provider: z5.enum(["claude", "codex"]).default("claude")
482
+ import { Hono as Hono7 } from "hono";
483
+ import { z as z6 } from "zod";
484
+ var ProviderQuerySchema = z6.object({
485
+ provider: z6.enum(["claude", "codex"]).default("claude")
429
486
  });
430
- var ClaudeQuerySchema = z5.object({ projectPath: z5.string().min(1) });
487
+ var ClaudeQuerySchema = z6.object({ projectPath: z6.string().min(1) });
431
488
  function memoryFilesRoutes(deps) {
432
- const app = new Hono6();
489
+ const app = new Hono7();
433
490
  const ctx = (c) => {
434
491
  const { provider } = ProviderQuerySchema.parse({
435
492
  provider: c.req.query("provider") ?? "claude"
@@ -466,14 +523,14 @@ function memoryFilesRoutes(deps) {
466
523
  }
467
524
 
468
525
  // src/server/routes/sessions.ts
469
- import { Hono as Hono7 } from "hono";
470
- import { z as z6 } from "zod";
471
- var QuerySchema = z6.object({
472
- provider: z6.enum(["claude", "codex"]).default("claude"),
473
- projectPath: z6.string().min(1)
526
+ import { Hono as Hono8 } from "hono";
527
+ import { z as z7 } from "zod";
528
+ var QuerySchema = z7.object({
529
+ provider: z7.enum(["claude", "codex"]).default("claude"),
530
+ projectPath: z7.string().min(1)
474
531
  });
475
532
  function sessionRoutes(deps) {
476
- const app = new Hono7();
533
+ const app = new Hono8();
477
534
  const ctx = (c) => {
478
535
  const { provider, projectPath } = QuerySchema.parse({
479
536
  provider: c.req.query("provider") ?? "claude",
@@ -501,21 +558,21 @@ function sessionRoutes(deps) {
501
558
  }
502
559
 
503
560
  // src/server/routes/plugins.ts
504
- import { Hono as Hono8 } from "hono";
505
- import { z as z7 } from "zod";
506
- var ProviderQuerySchema2 = z7.object({
507
- provider: z7.enum(["claude", "codex"]).default("claude")
561
+ import { Hono as Hono9 } from "hono";
562
+ import { z as z8 } from "zod";
563
+ var ProviderQuerySchema2 = z8.object({
564
+ provider: z8.enum(["claude", "codex"]).default("claude")
508
565
  });
509
- var EnabledBodySchema = z7.object({
510
- enabled: z7.boolean(),
511
- baseMtime: z7.number().optional()
566
+ var EnabledBodySchema = z8.object({
567
+ enabled: z8.boolean(),
568
+ baseMtime: z8.number().optional()
512
569
  });
513
- var ComponentQuerySchema = z7.object({
514
- type: z7.enum(["skill", "command", "agent", "hook", "mcp"]),
515
- name: z7.string().min(1)
570
+ var ComponentQuerySchema = z8.object({
571
+ type: z8.enum(["skill", "command", "agent", "hook", "mcp"]),
572
+ name: z8.string().min(1)
516
573
  });
517
574
  function pluginRoutes(deps) {
518
- const app = new Hono8();
575
+ const app = new Hono9();
519
576
  const ctx = (c) => {
520
577
  const { provider } = ProviderQuerySchema2.parse({
521
578
  provider: c.req.query("provider") ?? "claude"
@@ -564,9 +621,9 @@ function pluginRoutes(deps) {
564
621
  }
565
622
 
566
623
  // src/server/routes/config-files.ts
567
- import { Hono as Hono9 } from "hono";
624
+ import { Hono as Hono10 } from "hono";
568
625
  function configFileRoutes(deps) {
569
- const app = new Hono9();
626
+ const app = new Hono10();
570
627
  const ctx = (c) => {
571
628
  const fileId = ConfigFileIdSchema.parse(c.req.param("file"));
572
629
  const q = ProviderScopeQuerySchema.parse({
@@ -595,15 +652,15 @@ function configFileRoutes(deps) {
595
652
  }
596
653
 
597
654
  // src/server/routes/agent-commands.ts
598
- import { Hono as Hono10 } from "hono";
599
- import { z as z8 } from "zod";
600
- var CreateSchema = z8.object({ name: z8.string().min(1), content: z8.string() });
601
- var WriteSchema = z8.object({
602
- content: z8.string(),
603
- baseMtime: z8.number().nullable().optional()
655
+ import { Hono as Hono11 } from "hono";
656
+ import { z as z9 } from "zod";
657
+ var CreateSchema = z9.object({ name: z9.string().min(1), content: z9.string() });
658
+ var WriteSchema = z9.object({
659
+ content: z9.string(),
660
+ baseMtime: z9.number().nullable().optional()
604
661
  });
605
662
  function agentCommandRoutes(deps, kind) {
606
- const app = new Hono10();
663
+ const app = new Hono11();
607
664
  const ctx = (c) => {
608
665
  const q = ScopeQuerySchema.parse({
609
666
  scope: c.req.query("scope"),
@@ -640,9 +697,9 @@ function agentCommandRoutes(deps, kind) {
640
697
  }
641
698
 
642
699
  // src/server/routes/shell-files.ts
643
- import { Hono as Hono11 } from "hono";
700
+ import { Hono as Hono12 } from "hono";
644
701
  function shellFileRoutes(deps) {
645
- const app = new Hono11();
702
+ const app = new Hono12();
646
703
  app.get("/:file", async (c) => {
647
704
  const fileId = ShellFileIdSchema.parse(c.req.param("file"));
648
705
  return c.json(await deps.shellFiles.read(fileId));
@@ -658,7 +715,7 @@ function shellFileRoutes(deps) {
658
715
 
659
716
  // src/server/app.ts
660
717
  function createApp(deps) {
661
- const app = new Hono12();
718
+ const app = new Hono13();
662
719
  app.onError((err, c) => {
663
720
  if (err instanceof ZodError) {
664
721
  return c.json({ error: "\u6821\u9A8C\u5931\u8D25", issues: err.issues }, 400);
@@ -677,6 +734,7 @@ function createApp(deps) {
677
734
  app.route("/api/mcp", mcpRoutes(deps));
678
735
  app.route("/api/skills", skillRoutes(deps));
679
736
  app.route("/api/shared-skills", sharedSkillRoutes(deps));
737
+ app.route("/api/custom-skills", customSkillRoutes(deps));
680
738
  app.route("/api/projects", projectRoutes(deps));
681
739
  app.route("/api/memory", memoryRoutes(deps));
682
740
  app.route("/api/memory-files", memoryFilesRoutes(deps));
package/dist/cli.js CHANGED
@@ -8,11 +8,11 @@ import {
8
8
  assertNoConflict,
9
9
  createApp,
10
10
  currentMtime
11
- } from "./chunk-ENKQJGKM.js";
11
+ } from "./chunk-BRKCF5YR.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { homedir, networkInterfaces } from "os";
15
- import { join as join13 } from "path";
15
+ import { join as join14 } from "path";
16
16
  import { createServer } from "net";
17
17
  import { fileURLToPath } from "url";
18
18
  import { realpathSync } from "fs";
@@ -1914,7 +1914,7 @@ var AgentCommandService = class {
1914
1914
 
1915
1915
  // src/core/sync-service.ts
1916
1916
  import { cp as cp2, lstat, mkdir as mkdir5, readlink as readlink2, rm as rm3, rmdir, stat as stat9, symlink, unlink } from "fs/promises";
1917
- import { dirname as dirname5, join as join11, resolve as resolve7 } from "path";
1917
+ import { dirname as dirname5, isAbsolute as isAbsolute5, join as join11, resolve as resolve7 } from "path";
1918
1918
  var SyncService = class {
1919
1919
  agentsSkillsDir;
1920
1920
  targets;
@@ -1943,7 +1943,7 @@ var SyncService = class {
1943
1943
  const raw = await readlink2(p).catch(() => void 0);
1944
1944
  return raw === void 0 ? void 0 : resolve7(dirname5(p), raw);
1945
1945
  }
1946
- async stateFor(targetDir, name) {
1946
+ async stateFor(targetDir, name, sourceDir) {
1947
1947
  const p = join11(targetDir, name);
1948
1948
  try {
1949
1949
  await lstat(p);
@@ -1951,21 +1951,22 @@ var SyncService = class {
1951
1951
  return "none";
1952
1952
  }
1953
1953
  const target = await this.linkTargetOf(p);
1954
- if (target !== void 0 && isSameLink(target, join11(this.agentsSkillsDir, name))) {
1954
+ if (target !== void 0 && isSameLink(target, join11(sourceDir, name))) {
1955
1955
  return "linked";
1956
1956
  }
1957
1957
  return "conflict";
1958
1958
  }
1959
- async status(name) {
1959
+ // sourceDir 缺省为 ~/.agents/skills(原「公共」目录);传入其他目录可复用同一套逻辑管理自定义公共目录。
1960
+ async status(name, sourceDir = this.agentsSkillsDir) {
1960
1961
  assertSafeName(name);
1961
1962
  return {
1962
- claude: await this.stateFor(this.targets.claude, name),
1963
- codex: await this.stateFor(this.targets.codex, name)
1963
+ claude: await this.stateFor(this.targets.claude, name, sourceDir),
1964
+ codex: await this.stateFor(this.targets.codex, name, sourceDir)
1964
1965
  };
1965
1966
  }
1966
- async syncTo(name, target) {
1967
+ async syncTo(name, target, sourceDir = this.agentsSkillsDir) {
1967
1968
  assertSafeName(name);
1968
- const src = join11(this.agentsSkillsDir, name);
1969
+ const src = join11(sourceDir, name);
1969
1970
  try {
1970
1971
  if (!(await stat9(src)).isDirectory()) throw new Error();
1971
1972
  } catch {
@@ -1981,11 +1982,11 @@ var SyncService = class {
1981
1982
  await mkdir5(this.targets[target], { recursive: true });
1982
1983
  await symlink(src, dest, "junction");
1983
1984
  }
1984
- async unsync(name, target) {
1985
+ async unsync(name, target, sourceDir = this.agentsSkillsDir) {
1985
1986
  assertSafeName(name);
1986
1987
  const dest = join11(this.targets[target], name);
1987
1988
  const linkTarget = await this.linkTargetOf(dest);
1988
- if (linkTarget === void 0 || !isSameLink(linkTarget, join11(this.agentsSkillsDir, name))) {
1989
+ if (linkTarget === void 0 || !isSameLink(linkTarget, join11(sourceDir, name))) {
1989
1990
  throw new Error(`\u300C${name}\u300D\u4E0D\u662F\u6307\u5411\u516C\u5171\u76EE\u5F55\u7684\u94FE\u63A5\uFF0C\u62D2\u7EDD\u5220\u9664`);
1990
1991
  }
1991
1992
  await this.removeLink(dest);
@@ -2024,6 +2025,41 @@ var SyncService = class {
2024
2025
  throw err;
2025
2026
  }
2026
2027
  }
2028
+ // 把 provider 下的 skill 移动到用户指定的任意目录,并在原路径创建指回它的链接。
2029
+ async linkToDirectory(provider, name, targetDir) {
2030
+ assertSafeName(name);
2031
+ if (!isAbsolute5(targetDir)) throw new Error("\u76EE\u6807\u76EE\u5F55\u987B\u4E3A\u7EDD\u5BF9\u8DEF\u5F84");
2032
+ const src = join11(this.targets[provider], name);
2033
+ let st;
2034
+ try {
2035
+ st = await lstat(src);
2036
+ } catch {
2037
+ throw new Error(`\u672A\u627E\u5230 skill\u300C${name}\u300D\uFF08\u987B\u4E3A\u542F\u7528\u72B6\u6001\uFF09`);
2038
+ }
2039
+ if (st.isSymbolicLink()) {
2040
+ throw new Error(`\u300C${name}\u300D\u5DF2\u7ECF\u662F\u94FE\u63A5\uFF0C\u65E0\u6CD5\u518D\u6B21\u540C\u6B65`);
2041
+ }
2042
+ const dest = join11(resolve7(targetDir), name);
2043
+ if (resolve7(dest) === resolve7(src)) {
2044
+ throw new Error("\u76EE\u6807\u76EE\u5F55\u4E0D\u80FD\u4E0E\u6280\u80FD\u5F53\u524D\u6240\u5728\u76EE\u5F55\u76F8\u540C");
2045
+ }
2046
+ try {
2047
+ await lstat(dest);
2048
+ throw new ConflictError(`\u76EE\u6807\u76EE\u5F55\u5DF2\u5B58\u5728\u540C\u540D\u6761\u76EE\u300C${name}\u300D`);
2049
+ } catch (err) {
2050
+ if (err instanceof ConflictError) throw err;
2051
+ }
2052
+ await mkdir5(resolve7(targetDir), { recursive: true });
2053
+ await cp2(src, dest, { recursive: true });
2054
+ await rm3(src, { recursive: true, force: true });
2055
+ try {
2056
+ await symlink(dest, src, "junction");
2057
+ } catch (err) {
2058
+ await cp2(dest, src, { recursive: true });
2059
+ await rm3(dest, { recursive: true, force: true });
2060
+ throw err;
2061
+ }
2062
+ }
2027
2063
  };
2028
2064
 
2029
2065
  // src/core/shell-file-service.ts
@@ -2063,6 +2099,25 @@ var ShellFileService = class {
2063
2099
  }
2064
2100
  };
2065
2101
 
2102
+ // src/core/custom-skills-dir-service.ts
2103
+ import { isAbsolute as isAbsolute6, join as join13 } from "path";
2104
+ var CustomSkillsDirService = class {
2105
+ constructor(store, home) {
2106
+ this.store = store;
2107
+ this.file = join13(home, ".ccpanel", "custom-skills-dir.json");
2108
+ }
2109
+ store;
2110
+ file;
2111
+ async get() {
2112
+ const data = await this.store.readJson(this.file);
2113
+ return data?.path ?? null;
2114
+ }
2115
+ async set(path) {
2116
+ if (!isAbsolute6(path)) throw new Error("\u76EE\u5F55\u987B\u4E3A\u7EDD\u5BF9\u8DEF\u5F84");
2117
+ await this.store.writeJson(this.file, { path });
2118
+ }
2119
+ };
2120
+
2066
2121
  // src/cli.ts
2067
2122
  function findFreePort(start) {
2068
2123
  return new Promise((resolve8, reject) => {
@@ -2079,14 +2134,14 @@ function findFreePort(start) {
2079
2134
  async function main() {
2080
2135
  const home = homedir();
2081
2136
  const store = new ConfigStore();
2082
- const registryFile = join13(home, ".ccpanel", "ccpanel-projects.json");
2137
+ const registryFile = join14(home, ".ccpanel", "ccpanel-projects.json");
2083
2138
  const webRoot = fileURLToPath(new URL("../web/dist", import.meta.url));
2084
2139
  const app = createApp({
2085
2140
  home,
2086
2141
  store,
2087
2142
  mcp: new McpService(store),
2088
2143
  skills: new SkillService(),
2089
- projects: new ProjectService(store, registryFile, join13(home, ".claude.json")),
2144
+ projects: new ProjectService(store, registryFile, join14(home, ".claude.json")),
2090
2145
  memory: new MemoryService(),
2091
2146
  memoryFiles: new MemoryFilesService(),
2092
2147
  sessions: new SessionService(),
@@ -2098,6 +2153,7 @@ async function main() {
2098
2153
  agentCommands: new AgentCommandService(),
2099
2154
  sync: new SyncService(home),
2100
2155
  shellFiles: new ShellFileService(store, home),
2156
+ customSkillsDir: new CustomSkillsDirService(store, home),
2101
2157
  registryFile,
2102
2158
  webRoot
2103
2159
  });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-ENKQJGKM.js";
3
+ } from "../chunk-BRKCF5YR.js";
4
4
  export {
5
5
  createApp
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccpanel",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "本地图形化管理 Claude Code 配置:MCP、Skills、子 Agent、Commands、配置文件、Memory、Sessions、Plugins",
5
5
  "type": "module",
6
6
  "repository": {