ccpanel 0.1.7 → 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 Hono11 } 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"),
@@ -182,6 +182,17 @@ var ConfigFileWriteSchema = z.object({
182
182
  content: z.string(),
183
183
  baseMtime: BaseMtimeSchema
184
184
  });
185
+ var ShellFileIdSchema = z.enum([
186
+ "bashrc",
187
+ "bash-profile",
188
+ "zshrc",
189
+ "zprofile",
190
+ "profile"
191
+ ]);
192
+ var ShellFileWriteSchema = z.object({
193
+ content: z.string(),
194
+ baseMtime: BaseMtimeSchema
195
+ });
185
196
  var ScopeQuerySchema = z.object({
186
197
  scope: ScopeSchema,
187
198
  projectPath: z.string().optional()
@@ -248,37 +259,43 @@ var SkillBodySchema = z2.object({
248
259
  frontmatter: SkillFrontmatterSchema,
249
260
  body: z2.string()
250
261
  });
262
+ var MoveToDirBodySchema = z2.object({ targetDir: z2.string().min(1) });
263
+ function isVirtualProvider(p) {
264
+ return p === "agents" || p === "custom";
265
+ }
251
266
  function skillRoutes(deps) {
252
267
  const app = new Hono2();
253
- const ctx = (c) => {
268
+ const ctx = async (c) => {
254
269
  const q = ProviderScopeQuerySchema.parse({
255
270
  provider: c.req.query("provider"),
256
271
  scope: c.req.query("scope"),
257
272
  projectPath: c.req.query("projectPath")
258
273
  });
274
+ const customSkillsDir = q.provider === "custom" ? await deps.customSkillsDir.get() ?? void 0 : void 0;
259
275
  return {
260
276
  q,
261
277
  paths: resolvePaths({
262
278
  home: deps.home,
263
279
  projectPath: q.projectPath,
264
- provider: q.provider
280
+ provider: q.provider,
281
+ customSkillsDir
265
282
  })
266
283
  };
267
284
  };
268
285
  app.get("/", async (c) => {
269
- const { q, paths } = ctx(c);
286
+ const { q, paths } = await ctx(c);
270
287
  return c.json(await deps.skills.list(paths, q.scope));
271
288
  });
272
289
  app.get("/:name", async (c) => {
273
- const { q, paths } = ctx(c);
290
+ const { q, paths } = await ctx(c);
274
291
  return c.json(await deps.skills.read(paths, q.scope, c.req.param("name")));
275
292
  });
276
293
  app.get("/:name/files", async (c) => {
277
- const { q, paths } = ctx(c);
294
+ const { q, paths } = await ctx(c);
278
295
  return c.json(await deps.skills.listFiles(paths, q.scope, c.req.param("name")));
279
296
  });
280
297
  app.get("/:name/files/:filePath{.+}", async (c) => {
281
- const { q, paths } = ctx(c);
298
+ const { q, paths } = await ctx(c);
282
299
  return c.json(
283
300
  await deps.skills.readFileRaw(
284
301
  paths,
@@ -289,7 +306,7 @@ function skillRoutes(deps) {
289
306
  );
290
307
  });
291
308
  app.put("/:name/files/:filePath{.+}", async (c) => {
292
- const { q, paths } = ctx(c);
309
+ const { q, paths } = await ctx(c);
293
310
  const { content, baseMtime } = SkillFileWriteSchema.parse(await c.req.json());
294
311
  await deps.skills.writeFileRaw(
295
312
  paths,
@@ -302,25 +319,25 @@ function skillRoutes(deps) {
302
319
  return c.json({ ok: true });
303
320
  });
304
321
  app.post("/", async (c) => {
305
- const { q, paths } = ctx(c);
322
+ const { q, paths } = await ctx(c);
306
323
  const data = SkillBodySchema.parse(await c.req.json());
307
324
  await deps.skills.upsert(paths, q.scope, data.name, data.frontmatter, data.body);
308
325
  return c.json(await deps.skills.list(paths, q.scope));
309
326
  });
310
327
  app.put("/:name", async (c) => {
311
- const { q, paths } = ctx(c);
328
+ const { q, paths } = await ctx(c);
312
329
  const data = SkillBodySchema.parse({ ...await c.req.json(), name: c.req.param("name") });
313
330
  await deps.skills.upsert(paths, q.scope, data.name, data.frontmatter, data.body);
314
331
  return c.json(await deps.skills.list(paths, q.scope));
315
332
  });
316
333
  app.delete("/:name", async (c) => {
317
- const { q, paths } = ctx(c);
334
+ const { q, paths } = await ctx(c);
318
335
  await deps.skills.remove(paths, q.scope, c.req.param("name"));
319
336
  return c.json(await deps.skills.list(paths, q.scope));
320
337
  });
321
338
  app.post("/:name/toggle", async (c) => {
322
- const { q, paths } = ctx(c);
323
- if (q.provider === "agents") {
339
+ const { q, paths } = await ctx(c);
340
+ if (isVirtualProvider(q.provider)) {
324
341
  return c.json({ error: "\u516C\u5171 skills \u4E0D\u652F\u6301\u542F\u7528/\u7981\u7528" }, 400);
325
342
  }
326
343
  const { enabled } = await c.req.json();
@@ -328,13 +345,22 @@ function skillRoutes(deps) {
328
345
  return c.json(await deps.skills.list(paths, q.scope));
329
346
  });
330
347
  app.post("/:name/make-shared", async (c) => {
331
- const { q } = ctx(c);
332
- if (q.provider === "agents") {
348
+ const { q } = await ctx(c);
349
+ if (isVirtualProvider(q.provider)) {
333
350
  return c.json({ error: "\u8F6C\u516C\u5171\u4EC5\u652F\u6301 claude/codex \u4E0B\u7684\u6280\u80FD" }, 400);
334
351
  }
335
352
  await deps.sync.makeShared(q.provider, c.req.param("name"));
336
353
  return c.json({ ok: true });
337
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
+ });
338
364
  return app;
339
365
  }
340
366
 
@@ -365,31 +391,73 @@ function sharedSkillRoutes(deps) {
365
391
  return app;
366
392
  }
367
393
 
368
- // src/server/routes/projects.ts
394
+ // src/server/routes/custom-skills.ts
369
395
  import { Hono as Hono4 } from "hono";
370
396
  import { z as z4 } from "zod";
371
- 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) {
372
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();
373
441
  app.get("/", async (c) => c.json({ projects: await deps.projects.list() }));
374
442
  app.get(
375
443
  "/known",
376
444
  async (c) => c.json({ projects: await deps.projects.known() })
377
445
  );
378
446
  app.post("/", async (c) => {
379
- 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());
380
448
  return c.json({ projects: await deps.projects.add(path) });
381
449
  });
382
450
  app.delete("/", async (c) => {
383
- 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());
384
452
  return c.json({ projects: await deps.projects.remove(path) });
385
453
  });
386
454
  return app;
387
455
  }
388
456
 
389
457
  // src/server/routes/memory.ts
390
- import { Hono as Hono5 } from "hono";
458
+ import { Hono as Hono6 } from "hono";
391
459
  function memoryRoutes(deps) {
392
- const app = new Hono5();
460
+ const app = new Hono6();
393
461
  const ctx = (c) => {
394
462
  const q = ScopeQuerySchema.parse({
395
463
  scope: c.req.query("scope"),
@@ -411,14 +479,14 @@ function memoryRoutes(deps) {
411
479
  }
412
480
 
413
481
  // src/server/routes/memory-files.ts
414
- import { Hono as Hono6 } from "hono";
415
- import { z as z5 } from "zod";
416
- var ProviderQuerySchema = z5.object({
417
- 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")
418
486
  });
419
- var ClaudeQuerySchema = z5.object({ projectPath: z5.string().min(1) });
487
+ var ClaudeQuerySchema = z6.object({ projectPath: z6.string().min(1) });
420
488
  function memoryFilesRoutes(deps) {
421
- const app = new Hono6();
489
+ const app = new Hono7();
422
490
  const ctx = (c) => {
423
491
  const { provider } = ProviderQuerySchema.parse({
424
492
  provider: c.req.query("provider") ?? "claude"
@@ -455,14 +523,14 @@ function memoryFilesRoutes(deps) {
455
523
  }
456
524
 
457
525
  // src/server/routes/sessions.ts
458
- import { Hono as Hono7 } from "hono";
459
- import { z as z6 } from "zod";
460
- var QuerySchema = z6.object({
461
- provider: z6.enum(["claude", "codex"]).default("claude"),
462
- 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)
463
531
  });
464
532
  function sessionRoutes(deps) {
465
- const app = new Hono7();
533
+ const app = new Hono8();
466
534
  const ctx = (c) => {
467
535
  const { provider, projectPath } = QuerySchema.parse({
468
536
  provider: c.req.query("provider") ?? "claude",
@@ -490,21 +558,21 @@ function sessionRoutes(deps) {
490
558
  }
491
559
 
492
560
  // src/server/routes/plugins.ts
493
- import { Hono as Hono8 } from "hono";
494
- import { z as z7 } from "zod";
495
- var ProviderQuerySchema2 = z7.object({
496
- 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")
497
565
  });
498
- var EnabledBodySchema = z7.object({
499
- enabled: z7.boolean(),
500
- baseMtime: z7.number().optional()
566
+ var EnabledBodySchema = z8.object({
567
+ enabled: z8.boolean(),
568
+ baseMtime: z8.number().optional()
501
569
  });
502
- var ComponentQuerySchema = z7.object({
503
- type: z7.enum(["skill", "command", "agent", "hook", "mcp"]),
504
- 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)
505
573
  });
506
574
  function pluginRoutes(deps) {
507
- const app = new Hono8();
575
+ const app = new Hono9();
508
576
  const ctx = (c) => {
509
577
  const { provider } = ProviderQuerySchema2.parse({
510
578
  provider: c.req.query("provider") ?? "claude"
@@ -553,9 +621,9 @@ function pluginRoutes(deps) {
553
621
  }
554
622
 
555
623
  // src/server/routes/config-files.ts
556
- import { Hono as Hono9 } from "hono";
624
+ import { Hono as Hono10 } from "hono";
557
625
  function configFileRoutes(deps) {
558
- const app = new Hono9();
626
+ const app = new Hono10();
559
627
  const ctx = (c) => {
560
628
  const fileId = ConfigFileIdSchema.parse(c.req.param("file"));
561
629
  const q = ProviderScopeQuerySchema.parse({
@@ -584,15 +652,15 @@ function configFileRoutes(deps) {
584
652
  }
585
653
 
586
654
  // src/server/routes/agent-commands.ts
587
- import { Hono as Hono10 } from "hono";
588
- import { z as z8 } from "zod";
589
- var CreateSchema = z8.object({ name: z8.string().min(1), content: z8.string() });
590
- var WriteSchema = z8.object({
591
- content: z8.string(),
592
- 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()
593
661
  });
594
662
  function agentCommandRoutes(deps, kind) {
595
- const app = new Hono10();
663
+ const app = new Hono11();
596
664
  const ctx = (c) => {
597
665
  const q = ScopeQuerySchema.parse({
598
666
  scope: c.req.query("scope"),
@@ -628,9 +696,26 @@ function agentCommandRoutes(deps, kind) {
628
696
  return app;
629
697
  }
630
698
 
699
+ // src/server/routes/shell-files.ts
700
+ import { Hono as Hono12 } from "hono";
701
+ function shellFileRoutes(deps) {
702
+ const app = new Hono12();
703
+ app.get("/:file", async (c) => {
704
+ const fileId = ShellFileIdSchema.parse(c.req.param("file"));
705
+ return c.json(await deps.shellFiles.read(fileId));
706
+ });
707
+ app.put("/:file", async (c) => {
708
+ const fileId = ShellFileIdSchema.parse(c.req.param("file"));
709
+ const { content, baseMtime } = ShellFileWriteSchema.parse(await c.req.json());
710
+ await deps.shellFiles.write(fileId, content, baseMtime);
711
+ return c.json({ ok: true });
712
+ });
713
+ return app;
714
+ }
715
+
631
716
  // src/server/app.ts
632
717
  function createApp(deps) {
633
- const app = new Hono11();
718
+ const app = new Hono13();
634
719
  app.onError((err, c) => {
635
720
  if (err instanceof ZodError) {
636
721
  return c.json({ error: "\u6821\u9A8C\u5931\u8D25", issues: err.issues }, 400);
@@ -649,12 +734,14 @@ function createApp(deps) {
649
734
  app.route("/api/mcp", mcpRoutes(deps));
650
735
  app.route("/api/skills", skillRoutes(deps));
651
736
  app.route("/api/shared-skills", sharedSkillRoutes(deps));
737
+ app.route("/api/custom-skills", customSkillRoutes(deps));
652
738
  app.route("/api/projects", projectRoutes(deps));
653
739
  app.route("/api/memory", memoryRoutes(deps));
654
740
  app.route("/api/memory-files", memoryFilesRoutes(deps));
655
741
  app.route("/api/sessions", sessionRoutes(deps));
656
742
  app.route("/api/plugins", pluginRoutes(deps));
657
743
  app.route("/api/config-files", configFileRoutes(deps));
744
+ app.route("/api/shell-files", shellFileRoutes(deps));
658
745
  app.route("/api/agents", agentCommandRoutes(deps, "agents"));
659
746
  app.route("/api/commands", agentCommandRoutes(deps, "commands"));
660
747
  app.use("/*", serveStatic({ root: deps.webRoot ?? "./web/dist" }));
package/dist/cli.js CHANGED
@@ -8,11 +8,11 @@ import {
8
8
  assertNoConflict,
9
9
  createApp,
10
10
  currentMtime
11
- } from "./chunk-LZFMCC5M.js";
11
+ } from "./chunk-BRKCF5YR.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { homedir, networkInterfaces } from "os";
15
- import { join as join12 } 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,97 @@ 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
+ }
2063
+ };
2064
+
2065
+ // src/core/shell-file-service.ts
2066
+ import { readFile as readFile12 } from "fs/promises";
2067
+ import { join as join12 } from "path";
2068
+ var FILE_NAMES = {
2069
+ bashrc: ".bashrc",
2070
+ "bash-profile": ".bash_profile",
2071
+ zshrc: ".zshrc",
2072
+ zprofile: ".zprofile",
2073
+ profile: ".profile"
2074
+ };
2075
+ var ShellFileService = class {
2076
+ constructor(store, home) {
2077
+ this.store = store;
2078
+ this.home = home;
2079
+ }
2080
+ store;
2081
+ home;
2082
+ path(fileId) {
2083
+ return join12(this.home, FILE_NAMES[fileId]);
2084
+ }
2085
+ // 文件不存在视为「尚未创建」,返回空串与 null mtime(与 ConfigFileService 一致)。
2086
+ async read(fileId) {
2087
+ const file = this.path(fileId);
2088
+ try {
2089
+ return { content: await readFile12(file, "utf8"), mtime: await currentMtime(file) };
2090
+ } catch (err) {
2091
+ if (err.code === "ENOENT") return { content: "", mtime: null };
2092
+ throw err;
2093
+ }
2094
+ }
2095
+ async write(fileId, content, baseMtime) {
2096
+ const file = this.path(fileId);
2097
+ await assertNoConflict(file, baseMtime);
2098
+ await this.store.writeText(file, content);
2099
+ }
2100
+ };
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
+ }
2027
2119
  };
2028
2120
 
2029
2121
  // src/cli.ts
@@ -2042,14 +2134,14 @@ function findFreePort(start) {
2042
2134
  async function main() {
2043
2135
  const home = homedir();
2044
2136
  const store = new ConfigStore();
2045
- const registryFile = join12(home, ".ccpanel", "ccpanel-projects.json");
2137
+ const registryFile = join14(home, ".ccpanel", "ccpanel-projects.json");
2046
2138
  const webRoot = fileURLToPath(new URL("../web/dist", import.meta.url));
2047
2139
  const app = createApp({
2048
2140
  home,
2049
2141
  store,
2050
2142
  mcp: new McpService(store),
2051
2143
  skills: new SkillService(),
2052
- projects: new ProjectService(store, registryFile, join12(home, ".claude.json")),
2144
+ projects: new ProjectService(store, registryFile, join14(home, ".claude.json")),
2053
2145
  memory: new MemoryService(),
2054
2146
  memoryFiles: new MemoryFilesService(),
2055
2147
  sessions: new SessionService(),
@@ -2060,6 +2152,8 @@ async function main() {
2060
2152
  configFiles: new ConfigFileService(store),
2061
2153
  agentCommands: new AgentCommandService(),
2062
2154
  sync: new SyncService(home),
2155
+ shellFiles: new ShellFileService(store, home),
2156
+ customSkillsDir: new CustomSkillsDirService(store, home),
2063
2157
  registryFile,
2064
2158
  webRoot
2065
2159
  });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-LZFMCC5M.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.1.7",
3
+ "version": "0.3.0",
4
4
  "description": "本地图形化管理 Claude Code 配置:MCP、Skills、子 Agent、Commands、配置文件、Memory、Sessions、Plugins",
5
5
  "type": "module",
6
6
  "repository": {