@clawos-dev/clawd 0.2.440 → 0.2.442

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/dist/cli.cjs CHANGED
@@ -7089,10 +7089,9 @@ var init_persona_schemas = __esm({
7089
7089
  writableRoots: external_exports.array(external_exports.string()).optional(),
7090
7090
  // 禁读 glob(相对 workspace root 或绝对),如 ".secrets/**"、"**/*.env"。
7091
7091
  denyRead: external_exports.array(external_exports.string()).optional(),
7092
- // true → 网络全放(*);false → 断网。**缺省等于联网**:daemon 写入的默认值是 network:true
7093
- // (对齐 claude guest 一贯的域名全放,见 spec 2026-09-01-codex-guest-network §1.3),
7094
- // 且 manager / UI 读到 undefined 时都回落 true —— 两侧必须同向,否则出现
7095
- // 「卡片显示开、实际断网」的幽灵态。
7092
+ // true → 网络全放(*);false / 缺省 → 断网(fail-closed)。**「新建默认联网」不靠这里的缺省**,
7093
+ // 靠 persona 创建时就把 CODEX_SANDBOX_DEFAULT 物化进文件(见 PersonaManager.create)——
7094
+ // 缺省只在文件损坏这种异常路径上出现,那时断网是更安全的一侧。
7096
7095
  network: external_exports.boolean().optional()
7097
7096
  }).strict();
7098
7097
  PersonaSandboxSettingsSchema = external_exports.object({
@@ -47089,7 +47088,7 @@ var SessionManager = class {
47089
47088
  subSessionMeta.codexSandbox = {
47090
47089
  writableRoots: [subSessionMeta.userWorkDir, ...(codexCfg?.writableRoots ?? []).map(expand)],
47091
47090
  denyRead: codexCfg?.denyRead ?? [],
47092
- network: codexCfg?.network ?? true
47091
+ network: codexCfg?.network ?? false
47093
47092
  };
47094
47093
  }
47095
47094
  if (subSessionMeta?.personaMode === "owner" && file.projectPath) {
@@ -49466,408 +49465,20 @@ var PersonaRegistry = class {
49466
49465
  }
49467
49466
  };
49468
49467
 
49469
- // src/skills/scanner.ts
49470
- var import_node_fs9 = __toESM(require("fs"), 1);
49471
- var import_node_os6 = __toESM(require("os"), 1);
49472
- var import_node_path9 = __toESM(require("path"), 1);
49473
-
49474
- // src/skills/frontmatter.ts
49475
- var STRIP_QUOTES = /^["']|["']$/g;
49476
- function strip(s) {
49477
- return s.trim().replace(STRIP_QUOTES, "");
49478
- }
49479
- function parseFrontmatter(content, keys) {
49480
- const out = {};
49481
- if (!content.startsWith("---")) return out;
49482
- const end = content.indexOf("---", 3);
49483
- if (end === -1) return out;
49484
- const lines = content.slice(3, end).split("\n");
49485
- for (let i = 0; i < lines.length; i++) {
49486
- const trimmed = lines[i].trim();
49487
- for (const key of keys) {
49488
- const prefix = `${key}:`;
49489
- if (!trimmed.startsWith(prefix)) continue;
49490
- const rest = trimmed.slice(prefix.length).trim();
49491
- if (rest === "|" || rest === ">" || rest === "|-" || rest === ">-") {
49492
- const parts = [];
49493
- for (let j = i + 1; j < lines.length; j++) {
49494
- const next = lines[j];
49495
- if (next.length === 0) {
49496
- parts.push("");
49497
- continue;
49498
- }
49499
- if (!/^\s/.test(next)) break;
49500
- parts.push(next.replace(/^\s+/, ""));
49501
- }
49502
- const value = parts.filter(Boolean).join(" ").trim();
49503
- if (value) out[key] = value;
49504
- } else {
49505
- const value = strip(rest);
49506
- if (value) out[key] = value;
49507
- }
49508
- break;
49509
- }
49510
- }
49511
- return out;
49512
- }
49513
-
49514
- // src/skills/builtin-cc-resources.ts
49515
- var BUILTIN_SKILLS = [
49516
- { name: "update-config", source: "builtin", description: "Update Claude Code configuration." },
49517
- { name: "keybindings-help", source: "builtin", description: "Show available keybindings." },
49518
- { name: "verify", source: "builtin", description: "Verify the implementation." },
49519
- { name: "debug", source: "builtin", description: "Debug current issue." },
49520
- { name: "lorem-ipsum", source: "builtin", description: "Generate lorem ipsum placeholder text." },
49521
- { name: "skillify", source: "builtin", description: "Convert a process into a reusable skill." },
49522
- { name: "remember", source: "builtin", description: "Remember context for later use." },
49523
- { name: "simplify", source: "builtin", description: "Simplify the code or message." },
49524
- { name: "batch", source: "builtin", description: "Run a batch of similar operations." },
49525
- { name: "stuck", source: "builtin", description: "Help unstick a stalled task." },
49526
- { name: "loop", source: "builtin", description: "Loop a task until a condition is met." },
49527
- { name: "cron-list", source: "builtin", description: "List scheduled cron tasks." },
49528
- { name: "cron-delete", source: "builtin", description: "Delete a scheduled cron task." },
49529
- { name: "dream", source: "builtin", description: "Brainstorm freely without constraints." },
49530
- { name: "hunter", source: "builtin", description: "Hunt down a bug or root cause." },
49531
- { name: "schedule", source: "builtin", description: "Schedule a task to run later." },
49532
- { name: "claude-api", source: "builtin", description: "Use the Claude API directly." },
49533
- { name: "claude-in-chrome", source: "builtin", description: "Drive Claude inside Chrome via the Chrome extension." }
49534
- ];
49535
- var BUILTIN_AGENTS = [
49536
- {
49537
- name: "general-purpose",
49538
- source: "builtin",
49539
- description: "General-purpose agent for researching complex questions and executing multi-step tasks.",
49540
- whenToUse: "When you are searching for a keyword or file and are not confident you will find the right match in the first few tries."
49541
- },
49542
- {
49543
- name: "statusline-setup",
49544
- source: "builtin",
49545
- description: "Configure the user's Claude Code status line setting.",
49546
- whenToUse: "When the user is setting up or changing their status line configuration."
49547
- },
49548
- {
49549
- name: "Explore",
49550
- source: "builtin",
49551
- description: "Explore the codebase to gather context.",
49552
- whenToUse: "Before making changes that require understanding the surrounding code."
49553
- },
49554
- {
49555
- name: "Plan",
49556
- source: "builtin",
49557
- description: "Produce an implementation plan before writing code.",
49558
- whenToUse: "When the task is non-trivial and would benefit from an explicit plan."
49559
- },
49560
- {
49561
- name: "claude-code-guide",
49562
- source: "builtin",
49563
- description: "Guide the user through Claude Code features.",
49564
- whenToUse: "When the user needs help understanding what Claude Code can do."
49565
- },
49566
- {
49567
- name: "verification",
49568
- source: "builtin",
49569
- description: "Verify that a change works as intended.",
49570
- whenToUse: "After implementing a change, before declaring it done."
49571
- }
49572
- ];
49573
-
49574
- // src/skills/scanner.ts
49575
- function parseDescription(content) {
49576
- const fields = parseFrontmatter(content, ["description"]);
49577
- return { description: fields.description ?? "" };
49578
- }
49579
- function isDirLikeSync(p2) {
49580
- try {
49581
- return import_node_fs9.default.statSync(p2).isDirectory();
49582
- } catch {
49583
- return false;
49584
- }
49585
- }
49586
- function scanSkillDir(dir, source, seen, out, pluginName) {
49587
- let entries;
49588
- try {
49589
- entries = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
49590
- } catch {
49591
- return;
49592
- }
49593
- for (const ent of entries) {
49594
- const entryPath = import_node_path9.default.join(dir, ent.name);
49595
- if (!ent.isDirectory() && !(ent.isSymbolicLink() && isDirLikeSync(entryPath))) continue;
49596
- let content;
49597
- try {
49598
- content = import_node_fs9.default.readFileSync(import_node_path9.default.join(entryPath, "SKILL.md"), "utf8");
49599
- } catch {
49600
- try {
49601
- content = import_node_fs9.default.readFileSync(import_node_path9.default.join(entryPath, "skill.md"), "utf8");
49602
- } catch {
49603
- continue;
49604
- }
49605
- }
49606
- const { description } = parseDescription(content);
49607
- const baseName = ent.name;
49608
- const name = pluginName ? `${pluginName}:${baseName}` : baseName;
49609
- if (seen.has(name)) continue;
49610
- seen.add(name);
49611
- const info = { name, source, path: entryPath, description: description || void 0 };
49612
- if (pluginName) info.plugin = pluginName;
49613
- out.push(info);
49614
- }
49615
- }
49616
- function listSkillsForDir(dir, source) {
49617
- const seen = /* @__PURE__ */ new Set();
49618
- const out = [];
49619
- scanSkillDir(dir, source, seen, out);
49620
- return out;
49621
- }
49622
- function scanCommandDir(dir, source, seen, out, pluginName) {
49623
- let entries;
49624
- try {
49625
- entries = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
49626
- } catch {
49627
- return;
49628
- }
49629
- for (const ent of entries) {
49630
- const entryPath = import_node_path9.default.join(dir, ent.name);
49631
- if (ent.isDirectory() || ent.isSymbolicLink() && isDirLikeSync(entryPath)) {
49632
- const ns = ent.name;
49633
- let subEntries;
49634
- try {
49635
- subEntries = import_node_fs9.default.readdirSync(entryPath, { withFileTypes: true });
49636
- } catch {
49637
- continue;
49638
- }
49639
- for (const se of subEntries) {
49640
- if (!se.name.endsWith(".md")) continue;
49641
- const sePath = import_node_path9.default.join(entryPath, se.name);
49642
- let content;
49643
- try {
49644
- content = import_node_fs9.default.readFileSync(sePath, "utf8");
49645
- } catch {
49646
- continue;
49647
- }
49648
- const cmd = se.name.replace(/\.md$/, "");
49649
- const { description } = parseDescription(content);
49650
- const qualified = `${ns}:${cmd}`;
49651
- const name = pluginName ? `${pluginName}:${qualified}` : qualified;
49652
- if (seen.has(name)) continue;
49653
- seen.add(name);
49654
- const info = { name, source, path: sePath, description: description || void 0 };
49655
- if (pluginName) info.plugin = pluginName;
49656
- out.push(info);
49657
- }
49658
- } else if (ent.name.endsWith(".md")) {
49659
- let content;
49660
- try {
49661
- content = import_node_fs9.default.readFileSync(entryPath, "utf8");
49662
- } catch {
49663
- continue;
49664
- }
49665
- const cmd = ent.name.replace(/\.md$/, "");
49666
- const { description } = parseDescription(content);
49667
- const name = pluginName ? `${pluginName}:${cmd}` : cmd;
49668
- if (seen.has(name)) continue;
49669
- seen.add(name);
49670
- const info = { name, source, path: entryPath, description: description || void 0 };
49671
- if (pluginName) info.plugin = pluginName;
49672
- out.push(info);
49673
- }
49674
- }
49675
- }
49676
- function readInstalledPlugins(home) {
49677
- const file = import_node_path9.default.join(home, ".claude", "plugins", "installed_plugins.json");
49678
- let raw;
49679
- try {
49680
- raw = import_node_fs9.default.readFileSync(file, "utf8");
49681
- } catch {
49682
- return [];
49683
- }
49684
- let parsed;
49685
- try {
49686
- parsed = JSON.parse(raw);
49687
- } catch {
49688
- return [];
49689
- }
49690
- const out = [];
49691
- for (const [key, entries] of Object.entries(parsed.plugins ?? {})) {
49692
- if (!Array.isArray(entries) || entries.length === 0) continue;
49693
- const entry = entries[0];
49694
- if (!entry?.installPath) continue;
49695
- const pluginName = key.includes("@") ? key.slice(0, key.indexOf("@")) : key;
49696
- if (!pluginName) continue;
49697
- out.push({ name: pluginName, root: entry.installPath });
49698
- }
49699
- return out;
49700
- }
49701
- var SkillsScanner = class {
49702
- home;
49703
- extraPluginRoots;
49704
- constructor(opts = {}) {
49705
- this.home = opts.home ?? import_node_os6.default.homedir();
49706
- this.extraPluginRoots = opts.extraPluginRoots ?? [];
49707
- }
49708
- /**
49709
- * 对齐 cc-direct(openclaw-plugin-clawos/src/claude-code/skills-scanner.ts):
49710
- * 1. global: ~/.claude/skills + ~/.claude/commands
49711
- * 2. project: <cwd>/.claude/skills + <cwd>/.claude/commands
49712
- * 3. plugin: installed_plugins.json 里每个 plugin 的 skills + commands
49713
- * 顺序 global → project → plugin,同名去重(先到先得)
49714
- */
49715
- list(args) {
49716
- const seen = /* @__PURE__ */ new Set();
49717
- const builtinBlock = [];
49718
- for (const b2 of BUILTIN_SKILLS) {
49719
- if (seen.has(b2.name)) continue;
49720
- seen.add(b2.name);
49721
- builtinBlock.push({
49722
- name: b2.name,
49723
- source: "builtin",
49724
- description: b2.description
49725
- });
49726
- }
49727
- const fsBlock = [];
49728
- scanSkillDir(import_node_path9.default.join(this.home, ".claude", "skills"), "global", seen, fsBlock);
49729
- scanCommandDir(import_node_path9.default.join(this.home, ".claude", "commands"), "global", seen, fsBlock);
49730
- scanSkillDir(import_node_path9.default.join(args.cwd, ".claude", "skills"), "project", seen, fsBlock);
49731
- scanCommandDir(import_node_path9.default.join(args.cwd, ".claude", "commands"), "project", seen, fsBlock);
49732
- const plugins = [...readInstalledPlugins(this.home), ...this.extraPluginRoots];
49733
- for (const { name, root } of plugins) {
49734
- scanSkillDir(import_node_path9.default.join(root, "skills"), "plugin", seen, fsBlock, name);
49735
- scanCommandDir(import_node_path9.default.join(root, "commands"), "plugin", seen, fsBlock, name);
49736
- }
49737
- fsBlock.sort((a, b2) => a.name < b2.name ? -1 : a.name > b2.name ? 1 : 0);
49738
- return [...builtinBlock, ...fsBlock];
49739
- }
49740
- };
49741
-
49742
- // src/persona/manager.ts
49743
- var PersonaManager = class {
49744
- constructor(deps) {
49745
- this.deps = deps;
49746
- }
49747
- deps;
49748
- /**
49749
- * slug 是 persona 的身份,label 只是显示名——两者刻意分开:id 落进文件路径 /
49750
- * 分享链接且创建后不可改,label 可中文、可随时改。此前 id 从 label 推导,
49751
- * 非 ascii 被剥空后一律 fallback 成 `persona-persona`。
49752
- *
49753
- * 撞名直接抛错而非改名:id 由调用方显式指定,静默换成带后缀的 id 等于把
49754
- * 用户的选择偷偷改掉。字符集校验在 PersonaCreateArgsSchema(路径穿越边界)。
49755
- */
49756
- create(args) {
49757
- const personaId2 = `persona-${args.slug}`;
49758
- if (this.deps.store.has(personaId2)) {
49759
- throw new Error(`personaId already exists: ${personaId2}`);
49760
- }
49761
- const now = Date.now();
49762
- const persona = {
49763
- personaId: personaId2,
49764
- label: args.label,
49765
- model: args.model,
49766
- // effort 兜底 high:persona 一律持有具体档位(spec 2026-08-12),UI 会带界面上高亮的
49767
- // 那一档,但 persona:create 也可能来自 butler 的 persona-authoring skill 这类不带该
49768
- // 字段的调用方——不兜的话要等下次 daemon 启动 backfill 才补上,中间建的 session 就漏了。
49769
- // 取值理由与 backfillPersonaSessionDefaults 一致(见那里的注释)。
49770
- effort: args.effort ?? "high",
49771
- tool: args.tool,
49772
- public: args.public ?? false,
49773
- iconKey: args.iconKey,
49774
- createdAt: now,
49775
- updatedAt: now
49776
- };
49777
- this.deps.store.write(persona, args.personality);
49778
- this.deps.registry.set(persona);
49779
- return persona;
49780
- }
49781
- update(personaId2, patch, texts) {
49782
- const existing = this.deps.registry.get(personaId2);
49783
- if (!existing) throw new Error(`persona not found: ${personaId2}`);
49784
- const { iconKey: iconKeyPatch, ...restPatch } = patch;
49785
- const updated = {
49786
- ...existing,
49787
- ...restPatch,
49788
- updatedAt: Date.now()
49789
- };
49790
- const toolChanged = patch.tool !== void 0 && (patch.tool ?? "claude") !== (existing.tool ?? "claude");
49791
- if (toolChanged) {
49792
- if (patch.model === void 0) delete updated.model;
49793
- if (patch.effort === void 0) delete updated.effort;
49794
- }
49795
- if (iconKeyPatch === null) {
49796
- delete updated.iconKey;
49797
- } else if (iconKeyPatch !== void 0) {
49798
- updated.iconKey = iconKeyPatch;
49799
- }
49800
- if (texts?.personality !== void 0) {
49801
- this.deps.store.writePersonality(personaId2, texts.personality);
49802
- }
49803
- if (texts?.personalityLocal !== void 0) {
49804
- this.deps.store.writePersonalityLocal(personaId2, texts.personalityLocal);
49805
- }
49806
- this.deps.store.writeMeta(updated);
49807
- this.deps.registry.set(updated);
49808
- return updated;
49809
- }
49810
- /** 读取 CLAUDE.md 内容;persona 目录或文件不存在时返回 null。透传给 handler 拼响应。 */
49811
- readPersonality(personaId2) {
49812
- return this.deps.store.readPersonality(personaId2);
49813
- }
49814
- /** 读人格补充层(`.clawd/personality.local.md`);没写过返回 null。供 persona:get 拼响应。 */
49815
- readPersonalityLocal(personaId2) {
49816
- return this.deps.store.readPersonalityLocal(personaId2);
49817
- }
49818
- /** 扫 persona 私有 skills 目录;目录不存在返回空数组。供 persona:get handler 拼响应。 */
49819
- listSkills(personaId2) {
49820
- return listSkillsForDir(this.deps.store.skillsDir(personaId2), "project");
49821
- }
49822
- /**
49823
- * 读取 persona `.claude/settings.json` 中已启用的插件列表(value === true 的 key)。
49824
- * 文件不存在 / 解析失败 / 字段缺失 → 空数组。供 persona:get handler 拼响应。
49825
- */
49826
- listEnabledPlugins(personaId2) {
49827
- return this.deps.store.readEnabledPlugins(personaId2);
49828
- }
49829
- /** 读 sandbox-settings.json;不存在 / 损坏返回 null。供 persona:get handler 拼响应。 */
49830
- /** 读 codex guest 沙箱配置(`.clawd/codex-sandbox.json`);缺失 / 坏 → null。透传给 handler 拼响应。 */
49831
- readCodexSandboxSettings(personaId2) {
49832
- return this.deps.store.readCodexSandboxSettings(personaId2);
49833
- }
49834
- /**
49835
- * 局部合并写 codex guest 沙箱配置,返回合并后的全量。
49836
- *
49837
- * **必须是合并不是覆盖**:UI 的「访客联网」开关只发 `{network}`,整体覆盖会静默抹掉 owner
49838
- * 手配的 writableRoots / denyRead(丢的是安全边界,且用户不会立刻发现)。
49839
- * 不走 PersonaManager.update 是因为它管的是 PersonaFile meta + 人格两层文本,
49840
- * 而这份配置是另一个文件、另一套 schema。
49841
- */
49842
- mergeCodexSandbox(personaId2, patch) {
49843
- if (!this.deps.registry.get(personaId2)) throw new Error(`persona not found: ${personaId2}`);
49844
- const merged = { ...this.deps.store.readCodexSandboxSettings(personaId2) ?? {}, ...patch };
49845
- this.deps.store.writeCodexSandboxSettings(personaId2, merged);
49846
- return merged;
49847
- }
49848
- readSandboxSettings(personaId2) {
49849
- return this.deps.store.readSandboxSettings(personaId2);
49850
- }
49851
- /**
49852
- * 删除 persona。
49853
- * PersonaStore.remove(personaId) 级联删整个 <personaRoot>/<personaId>/ 目录
49854
- * (含 .clawd/sessions/owner/ 和 .clawd/sessions/guests/<capId>/),rmSync
49855
- * recursive force 一锅清。
49856
- */
49857
- delete(personaId2) {
49858
- this.deps.store.remove(personaId2);
49859
- this.deps.registry.remove(personaId2);
49860
- }
49861
- };
49862
-
49863
49468
  // src/persona/seed.ts
49864
- var fs13 = __toESM(require("fs"), 1);
49865
- var path16 = __toESM(require("path"), 1);
49469
+ var fs12 = __toESM(require("fs"), 1);
49470
+ var path15 = __toESM(require("path"), 1);
49866
49471
  var import_node_url = require("url");
49867
49472
  var import_meta = {};
49868
49473
  var DEFAULT_BYPASS_PROFILE = {
49869
49474
  permissions: { defaultMode: "bypassPermissions" }
49870
49475
  };
49476
+ var PKG_CACHE_ROOTS = ["~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"];
49477
+ var CODEX_SANDBOX_DEFAULT = {
49478
+ writableRoots: [...PKG_CACHE_ROOTS],
49479
+ denyRead: [".secrets/**"],
49480
+ network: true
49481
+ };
49871
49482
  var DEFAULT_PERSONAS = [
49872
49483
  {
49873
49484
  personaId: "persona-researcher",
@@ -49952,7 +49563,7 @@ var DEFAULT_PERSONAS = [
49952
49563
  }
49953
49564
  },
49954
49565
  codexSandbox: {
49955
- writableRoots: ["~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"],
49566
+ writableRoots: [...PKG_CACHE_ROOTS],
49956
49567
  denyRead: [".secrets/**"],
49957
49568
  network: true
49958
49569
  }
@@ -49986,7 +49597,7 @@ var DEFAULT_PERSONAS = [
49986
49597
  }
49987
49598
  },
49988
49599
  codexSandbox: {
49989
- writableRoots: ["~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"],
49600
+ writableRoots: [...PKG_CACHE_ROOTS],
49990
49601
  denyRead: [".secrets/**"],
49991
49602
  network: true
49992
49603
  }
@@ -50028,7 +49639,7 @@ var DEFAULT_PERSONAS = [
50028
49639
  }
50029
49640
  },
50030
49641
  codexSandbox: {
50031
- writableRoots: ["~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"],
49642
+ writableRoots: [...PKG_CACHE_ROOTS],
50032
49643
  denyRead: [".secrets/**"],
50033
49644
  network: true
50034
49645
  }
@@ -50053,24 +49664,24 @@ function bundleSiblingFromArgv(argv1, sibling) {
50053
49664
  if (!argv1) return null;
50054
49665
  let real = argv1;
50055
49666
  try {
50056
- real = fs13.realpathSync(argv1);
49667
+ real = fs12.realpathSync(argv1);
50057
49668
  } catch {
50058
49669
  }
50059
- return path16.resolve(path16.dirname(real), sibling);
49670
+ return path15.resolve(path15.dirname(real), sibling);
50060
49671
  }
50061
49672
  function findDefaultsRoot(logger) {
50062
49673
  const candidates = [];
50063
49674
  try {
50064
- const here = path16.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
50065
- candidates.push(path16.resolve(here, "defaults"));
50066
- candidates.push(path16.resolve(here, "persona-defaults"));
49675
+ const here = path15.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
49676
+ candidates.push(path15.resolve(here, "defaults"));
49677
+ candidates.push(path15.resolve(here, "persona-defaults"));
50067
49678
  } catch {
50068
49679
  }
50069
49680
  const fromArgv = bundleSiblingFromArgv(process.argv[1], "persona-defaults");
50070
49681
  if (fromArgv) candidates.push(fromArgv);
50071
49682
  for (const c of candidates) {
50072
49683
  try {
50073
- if (fs13.statSync(c).isDirectory()) {
49684
+ if (fs12.statSync(c).isDirectory()) {
50074
49685
  logger?.info("persona.defaults-root.resolved", { root: c });
50075
49686
  return c;
50076
49687
  }
@@ -50087,8 +49698,8 @@ function seedDefaultPersonas(args) {
50087
49698
  args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
50088
49699
  continue;
50089
49700
  }
50090
- const bundleDir = path16.join(args.defaultsRoot, entry.personaId);
50091
- if (!fs13.existsSync(bundleDir)) {
49701
+ const bundleDir = path15.join(args.defaultsRoot, entry.personaId);
49702
+ if (!fs12.existsSync(bundleDir)) {
50092
49703
  args.logger.warn("persona.seed.skip", {
50093
49704
  personaId: entry.personaId,
50094
49705
  reason: "bundle-missing",
@@ -50096,8 +49707,8 @@ function seedDefaultPersonas(args) {
50096
49707
  });
50097
49708
  continue;
50098
49709
  }
50099
- const claudeMdPath = path16.join(bundleDir, "CLAUDE.md");
50100
- if (!fs13.existsSync(claudeMdPath)) {
49710
+ const claudeMdPath = path15.join(bundleDir, "CLAUDE.md");
49711
+ if (!fs12.existsSync(claudeMdPath)) {
50101
49712
  args.logger.warn("persona.seed.skip", {
50102
49713
  personaId: entry.personaId,
50103
49714
  reason: "no-CLAUDE.md",
@@ -50105,7 +49716,7 @@ function seedDefaultPersonas(args) {
50105
49716
  });
50106
49717
  continue;
50107
49718
  }
50108
- const personality = fs13.readFileSync(claudeMdPath, "utf8");
49719
+ const personality = fs12.readFileSync(claudeMdPath, "utf8");
50109
49720
  const now = Date.now();
50110
49721
  const persona = {
50111
49722
  personaId: entry.personaId,
@@ -50132,51 +49743,51 @@ function seedDefaultPersonas(args) {
50132
49743
  }
50133
49744
  }
50134
49745
  function skipNodeModulesUnder(srcRoot) {
50135
- return (src) => !path16.relative(srcRoot, src).split(path16.sep).includes("node_modules");
49746
+ return (src) => !path15.relative(srcRoot, src).split(path15.sep).includes("node_modules");
50136
49747
  }
50137
49748
  function copyBundleExtras(srcDir, dstDir) {
50138
- for (const entry of fs13.readdirSync(srcDir, { withFileTypes: true })) {
49749
+ for (const entry of fs12.readdirSync(srcDir, { withFileTypes: true })) {
50139
49750
  if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
50140
- const srcPath = path16.join(srcDir, entry.name);
50141
- const dstPath = path16.join(dstDir, entry.name);
49751
+ const srcPath = path15.join(srcDir, entry.name);
49752
+ const dstPath = path15.join(dstDir, entry.name);
50142
49753
  if (entry.isDirectory()) {
50143
- fs13.cpSync(srcPath, dstPath, { recursive: true, dereference: true, filter: skipNodeModulesUnder(srcPath) });
49754
+ fs12.cpSync(srcPath, dstPath, { recursive: true, dereference: true, filter: skipNodeModulesUnder(srcPath) });
50144
49755
  } else if (entry.isFile()) {
50145
- fs13.copyFileSync(srcPath, dstPath);
49756
+ fs12.copyFileSync(srcPath, dstPath);
50146
49757
  }
50147
49758
  }
50148
49759
  }
50149
49760
  var DAEMON_MANAGED_PATHS = ["extension-kit", "CLAUDE.md", ".mcp.json", ".claude/skills", ".claude/agents"];
50150
49761
  function replaceManagedPath(src, dst) {
50151
- if (!fs13.statSync(src).isDirectory()) {
50152
- fs13.mkdirSync(path16.dirname(dst), { recursive: true });
50153
- fs13.rmSync(dst, { recursive: true, force: true });
50154
- fs13.cpSync(src, dst, { dereference: true });
49762
+ if (!fs12.statSync(src).isDirectory()) {
49763
+ fs12.mkdirSync(path15.dirname(dst), { recursive: true });
49764
+ fs12.rmSync(dst, { recursive: true, force: true });
49765
+ fs12.cpSync(src, dst, { dereference: true });
50155
49766
  return;
50156
49767
  }
50157
- fs13.mkdirSync(dst, { recursive: true });
50158
- for (const e of fs13.readdirSync(src, { withFileTypes: true })) {
49768
+ fs12.mkdirSync(dst, { recursive: true });
49769
+ for (const e of fs12.readdirSync(src, { withFileTypes: true })) {
50159
49770
  if (e.name === "node_modules") continue;
50160
- const child = path16.join(dst, e.name);
50161
- fs13.rmSync(child, { recursive: true, force: true });
50162
- fs13.cpSync(path16.join(src, e.name), child, { recursive: true, dereference: true });
49771
+ const child = path15.join(dst, e.name);
49772
+ fs12.rmSync(child, { recursive: true, force: true });
49773
+ fs12.cpSync(path15.join(src, e.name), child, { recursive: true, dereference: true });
50163
49774
  }
50164
49775
  }
50165
49776
  function refreshDaemonManagedDirs(args) {
50166
49777
  const entries = args.entries ?? DEFAULT_PERSONAS;
50167
49778
  for (const entry of entries) {
50168
- const bundleDir = path16.join(args.defaultsRoot, entry.personaId);
50169
- if (!fs13.existsSync(bundleDir)) continue;
49779
+ const bundleDir = path15.join(args.defaultsRoot, entry.personaId);
49780
+ if (!fs12.existsSync(bundleDir)) continue;
50170
49781
  const personaDir = args.store.personaDirPath(entry.personaId);
50171
- if (!fs13.existsSync(personaDir)) continue;
49782
+ if (!fs12.existsSync(personaDir)) continue;
50172
49783
  for (const relPath of DAEMON_MANAGED_PATHS) {
50173
- const srcPath = path16.join(bundleDir, relPath);
50174
- if (!fs13.existsSync(srcPath)) continue;
50175
- const dstPath = path16.join(personaDir, relPath);
49784
+ const srcPath = path15.join(bundleDir, relPath);
49785
+ if (!fs12.existsSync(srcPath)) continue;
49786
+ const dstPath = path15.join(personaDir, relPath);
50176
49787
  try {
50177
49788
  replaceManagedPath(srcPath, dstPath);
50178
49789
  if (relPath === "CLAUDE.md") {
50179
- replaceManagedPath(srcPath, path16.join(personaDir, "AGENTS.md"));
49790
+ replaceManagedPath(srcPath, path15.join(personaDir, "AGENTS.md"));
50180
49791
  }
50181
49792
  args.logger.info("persona.refresh.synced", {
50182
49793
  personaId: entry.personaId,
@@ -50200,12 +49811,12 @@ function migratePersonalityOverlay(args) {
50200
49811
  for (const entry of entries) {
50201
49812
  const personaId2 = entry.personaId;
50202
49813
  try {
50203
- if (fs13.existsSync(args.store.personalityLocalPath(personaId2))) continue;
50204
- const bundleClaudeMd = path16.join(args.defaultsRoot, personaId2, "CLAUDE.md");
50205
- if (!fs13.existsSync(bundleClaudeMd)) continue;
49814
+ if (fs12.existsSync(args.store.personalityLocalPath(personaId2))) continue;
49815
+ const bundleClaudeMd = path15.join(args.defaultsRoot, personaId2, "CLAUDE.md");
49816
+ if (!fs12.existsSync(bundleClaudeMd)) continue;
50206
49817
  const current = args.store.readPersonality(personaId2);
50207
49818
  if (current === null) continue;
50208
- const bundle = fs13.readFileSync(bundleClaudeMd, "utf8");
49819
+ const bundle = fs12.readFileSync(bundleClaudeMd, "utf8");
50209
49820
  if (current === bundle) continue;
50210
49821
  if (!current.startsWith(bundle)) continue;
50211
49822
  const tail = current.slice(bundle.length).trim();
@@ -50246,8 +49857,6 @@ function migrateAgentsMirror(args) {
50246
49857
  }
50247
49858
  if (mirrored > 0) args.logger.info("persona.agents-mirror.done", { mirrored });
50248
49859
  }
50249
- var PKG_CACHE_ROOTS = ["~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"];
50250
- var CODEX_SANDBOX_DEFAULT = { writableRoots: [...PKG_CACHE_ROOTS], denyRead: [], network: true };
50251
49860
  function migrateCodexSandbox(args) {
50252
49861
  const entries = args.entries ?? DEFAULT_PERSONAS;
50253
49862
  let n = 0;
@@ -50306,7 +49915,7 @@ function migrateSandboxAllowRead(args) {
50306
49915
  for (const personaId2 of args.store.list()) {
50307
49916
  try {
50308
49917
  const readPath = tildePath(args.store.personaDirPath(personaId2));
50309
- if (!fs13.existsSync(args.store.sandboxSettingsPath(personaId2))) {
49918
+ if (!fs12.existsSync(args.store.sandboxSettingsPath(personaId2))) {
50310
49919
  args.store.writeSandboxSettings(personaId2, buildGuestSettingsV1());
50311
49920
  n++;
50312
49921
  continue;
@@ -50349,13 +49958,13 @@ var PERSONA_BUILDER_TOMBSTONE = `${PERSONA_BUILDER_TOMBSTONE_MARKER}
50349
49958
  function containsAnyFile(dir) {
50350
49959
  let entries;
50351
49960
  try {
50352
- entries = fs13.readdirSync(dir, { withFileTypes: true });
49961
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
50353
49962
  } catch {
50354
49963
  return false;
50355
49964
  }
50356
49965
  for (const e of entries) {
50357
49966
  if (e.isDirectory()) {
50358
- if (containsAnyFile(path16.join(dir, e.name))) return true;
49967
+ if (containsAnyFile(path15.join(dir, e.name))) return true;
50359
49968
  } else {
50360
49969
  return true;
50361
49970
  }
@@ -50364,9 +49973,9 @@ function containsAnyFile(dir) {
50364
49973
  }
50365
49974
  function retirePersonaBuilder(args) {
50366
49975
  const dir = args.store.personaDirPath(PERSONA_BUILDER_ID);
50367
- if (!fs13.existsSync(dir)) return;
49976
+ if (!fs12.existsSync(dir)) return;
50368
49977
  try {
50369
- const used = containsAnyFile(path16.join(dir, ".clawd", "sessions")) || containsAnyFile(path16.join(dir, ".claude", "skills")) || containsAnyFile(path16.join(dir, "projects"));
49978
+ const used = containsAnyFile(path15.join(dir, ".clawd", "sessions")) || containsAnyFile(path15.join(dir, ".claude", "skills")) || containsAnyFile(path15.join(dir, "projects"));
50370
49979
  if (!used) {
50371
49980
  args.store.remove(PERSONA_BUILDER_ID);
50372
49981
  args.logger.info("persona.retire-builder.removed", { personaId: PERSONA_BUILDER_ID });
@@ -50374,10 +49983,10 @@ function retirePersonaBuilder(args) {
50374
49983
  }
50375
49984
  let wrote = false;
50376
49985
  for (const name of ["CLAUDE.md", "AGENTS.md"]) {
50377
- const p2 = path16.join(dir, name);
50378
- const cur = fs13.existsSync(p2) ? fs13.readFileSync(p2, "utf8") : null;
49986
+ const p2 = path15.join(dir, name);
49987
+ const cur = fs12.existsSync(p2) ? fs12.readFileSync(p2, "utf8") : null;
50379
49988
  if (cur?.startsWith(PERSONA_BUILDER_TOMBSTONE_MARKER)) continue;
50380
- fs13.writeFileSync(p2, PERSONA_BUILDER_TOMBSTONE);
49989
+ fs12.writeFileSync(p2, PERSONA_BUILDER_TOMBSTONE);
50381
49990
  wrote = true;
50382
49991
  }
50383
49992
  if (wrote) args.logger.info("persona.retire-builder.tombstoned", { personaId: PERSONA_BUILDER_ID });
@@ -50391,15 +50000,15 @@ function retirePersonaBuilder(args) {
50391
50000
  function findDeployKitRoot(logger) {
50392
50001
  const candidates = [];
50393
50002
  try {
50394
- const here = path16.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
50395
- candidates.push(path16.resolve(here, "..", "deploy-kit"));
50003
+ const here = path15.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
50004
+ candidates.push(path15.resolve(here, "..", "deploy-kit"));
50396
50005
  } catch {
50397
50006
  }
50398
50007
  const fromArgv = bundleSiblingFromArgv(process.argv[1], "deploy-kit");
50399
50008
  if (fromArgv) candidates.push(fromArgv);
50400
50009
  for (const c of candidates) {
50401
50010
  try {
50402
- if (fs13.statSync(c).isDirectory()) {
50011
+ if (fs12.statSync(c).isDirectory()) {
50403
50012
  logger?.info("persona.deploy-kit-root.resolved", { root: c });
50404
50013
  return c;
50405
50014
  }
@@ -50410,8 +50019,8 @@ function findDeployKitRoot(logger) {
50410
50019
  return null;
50411
50020
  }
50412
50021
  function seedDeployKit(args) {
50413
- const dst = path16.join(args.dataDir, "deploy-kit");
50414
- fs13.cpSync(args.deployKitBundleRoot, dst, {
50022
+ const dst = path15.join(args.dataDir, "deploy-kit");
50023
+ fs12.cpSync(args.deployKitBundleRoot, dst, {
50415
50024
  recursive: true,
50416
50025
  dereference: true,
50417
50026
  force: false,
@@ -50420,28 +50029,434 @@ function seedDeployKit(args) {
50420
50029
  args.logger.info("deploy-kit.seed.done", { dst });
50421
50030
  }
50422
50031
  function refreshDeployKit(args) {
50423
- const dst = path16.join(args.dataDir, "deploy-kit");
50424
- if (!fs13.existsSync(dst)) {
50032
+ const dst = path15.join(args.dataDir, "deploy-kit");
50033
+ if (!fs12.existsSync(dst)) {
50425
50034
  seedDeployKit(args);
50426
50035
  return;
50427
50036
  }
50428
50037
  for (const sub of ["scripts", "contract"]) {
50429
- const s = path16.join(args.deployKitBundleRoot, sub);
50430
- if (fs13.existsSync(s)) {
50431
- fs13.cpSync(s, path16.join(dst, sub), { recursive: true, force: true, dereference: true });
50038
+ const s = path15.join(args.deployKitBundleRoot, sub);
50039
+ if (fs12.existsSync(s)) {
50040
+ fs12.cpSync(s, path15.join(dst, sub), { recursive: true, force: true, dereference: true });
50432
50041
  }
50433
50042
  }
50434
- const secretsSrc = path16.join(args.deployKitBundleRoot, ".secrets");
50435
- if (fs13.existsSync(secretsSrc)) {
50436
- fs13.mkdirSync(path16.join(dst, ".secrets"), { recursive: true });
50437
- for (const f of fs13.readdirSync(secretsSrc)) {
50043
+ const secretsSrc = path15.join(args.deployKitBundleRoot, ".secrets");
50044
+ if (fs12.existsSync(secretsSrc)) {
50045
+ fs12.mkdirSync(path15.join(dst, ".secrets"), { recursive: true });
50046
+ for (const f of fs12.readdirSync(secretsSrc)) {
50438
50047
  if (f.endsWith(".local")) continue;
50439
- fs13.copyFileSync(path16.join(secretsSrc, f), path16.join(dst, ".secrets", f));
50048
+ fs12.copyFileSync(path15.join(secretsSrc, f), path15.join(dst, ".secrets", f));
50440
50049
  }
50441
50050
  }
50442
50051
  args.logger.info("deploy-kit.refresh.done", { dst });
50443
50052
  }
50444
50053
 
50054
+ // src/skills/scanner.ts
50055
+ var import_node_fs9 = __toESM(require("fs"), 1);
50056
+ var import_node_os6 = __toESM(require("os"), 1);
50057
+ var import_node_path9 = __toESM(require("path"), 1);
50058
+
50059
+ // src/skills/frontmatter.ts
50060
+ var STRIP_QUOTES = /^["']|["']$/g;
50061
+ function strip(s) {
50062
+ return s.trim().replace(STRIP_QUOTES, "");
50063
+ }
50064
+ function parseFrontmatter(content, keys) {
50065
+ const out = {};
50066
+ if (!content.startsWith("---")) return out;
50067
+ const end = content.indexOf("---", 3);
50068
+ if (end === -1) return out;
50069
+ const lines = content.slice(3, end).split("\n");
50070
+ for (let i = 0; i < lines.length; i++) {
50071
+ const trimmed = lines[i].trim();
50072
+ for (const key of keys) {
50073
+ const prefix = `${key}:`;
50074
+ if (!trimmed.startsWith(prefix)) continue;
50075
+ const rest = trimmed.slice(prefix.length).trim();
50076
+ if (rest === "|" || rest === ">" || rest === "|-" || rest === ">-") {
50077
+ const parts = [];
50078
+ for (let j = i + 1; j < lines.length; j++) {
50079
+ const next = lines[j];
50080
+ if (next.length === 0) {
50081
+ parts.push("");
50082
+ continue;
50083
+ }
50084
+ if (!/^\s/.test(next)) break;
50085
+ parts.push(next.replace(/^\s+/, ""));
50086
+ }
50087
+ const value = parts.filter(Boolean).join(" ").trim();
50088
+ if (value) out[key] = value;
50089
+ } else {
50090
+ const value = strip(rest);
50091
+ if (value) out[key] = value;
50092
+ }
50093
+ break;
50094
+ }
50095
+ }
50096
+ return out;
50097
+ }
50098
+
50099
+ // src/skills/builtin-cc-resources.ts
50100
+ var BUILTIN_SKILLS = [
50101
+ { name: "update-config", source: "builtin", description: "Update Claude Code configuration." },
50102
+ { name: "keybindings-help", source: "builtin", description: "Show available keybindings." },
50103
+ { name: "verify", source: "builtin", description: "Verify the implementation." },
50104
+ { name: "debug", source: "builtin", description: "Debug current issue." },
50105
+ { name: "lorem-ipsum", source: "builtin", description: "Generate lorem ipsum placeholder text." },
50106
+ { name: "skillify", source: "builtin", description: "Convert a process into a reusable skill." },
50107
+ { name: "remember", source: "builtin", description: "Remember context for later use." },
50108
+ { name: "simplify", source: "builtin", description: "Simplify the code or message." },
50109
+ { name: "batch", source: "builtin", description: "Run a batch of similar operations." },
50110
+ { name: "stuck", source: "builtin", description: "Help unstick a stalled task." },
50111
+ { name: "loop", source: "builtin", description: "Loop a task until a condition is met." },
50112
+ { name: "cron-list", source: "builtin", description: "List scheduled cron tasks." },
50113
+ { name: "cron-delete", source: "builtin", description: "Delete a scheduled cron task." },
50114
+ { name: "dream", source: "builtin", description: "Brainstorm freely without constraints." },
50115
+ { name: "hunter", source: "builtin", description: "Hunt down a bug or root cause." },
50116
+ { name: "schedule", source: "builtin", description: "Schedule a task to run later." },
50117
+ { name: "claude-api", source: "builtin", description: "Use the Claude API directly." },
50118
+ { name: "claude-in-chrome", source: "builtin", description: "Drive Claude inside Chrome via the Chrome extension." }
50119
+ ];
50120
+ var BUILTIN_AGENTS = [
50121
+ {
50122
+ name: "general-purpose",
50123
+ source: "builtin",
50124
+ description: "General-purpose agent for researching complex questions and executing multi-step tasks.",
50125
+ whenToUse: "When you are searching for a keyword or file and are not confident you will find the right match in the first few tries."
50126
+ },
50127
+ {
50128
+ name: "statusline-setup",
50129
+ source: "builtin",
50130
+ description: "Configure the user's Claude Code status line setting.",
50131
+ whenToUse: "When the user is setting up or changing their status line configuration."
50132
+ },
50133
+ {
50134
+ name: "Explore",
50135
+ source: "builtin",
50136
+ description: "Explore the codebase to gather context.",
50137
+ whenToUse: "Before making changes that require understanding the surrounding code."
50138
+ },
50139
+ {
50140
+ name: "Plan",
50141
+ source: "builtin",
50142
+ description: "Produce an implementation plan before writing code.",
50143
+ whenToUse: "When the task is non-trivial and would benefit from an explicit plan."
50144
+ },
50145
+ {
50146
+ name: "claude-code-guide",
50147
+ source: "builtin",
50148
+ description: "Guide the user through Claude Code features.",
50149
+ whenToUse: "When the user needs help understanding what Claude Code can do."
50150
+ },
50151
+ {
50152
+ name: "verification",
50153
+ source: "builtin",
50154
+ description: "Verify that a change works as intended.",
50155
+ whenToUse: "After implementing a change, before declaring it done."
50156
+ }
50157
+ ];
50158
+
50159
+ // src/skills/scanner.ts
50160
+ function parseDescription(content) {
50161
+ const fields = parseFrontmatter(content, ["description"]);
50162
+ return { description: fields.description ?? "" };
50163
+ }
50164
+ function isDirLikeSync(p2) {
50165
+ try {
50166
+ return import_node_fs9.default.statSync(p2).isDirectory();
50167
+ } catch {
50168
+ return false;
50169
+ }
50170
+ }
50171
+ function scanSkillDir(dir, source, seen, out, pluginName) {
50172
+ let entries;
50173
+ try {
50174
+ entries = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
50175
+ } catch {
50176
+ return;
50177
+ }
50178
+ for (const ent of entries) {
50179
+ const entryPath = import_node_path9.default.join(dir, ent.name);
50180
+ if (!ent.isDirectory() && !(ent.isSymbolicLink() && isDirLikeSync(entryPath))) continue;
50181
+ let content;
50182
+ try {
50183
+ content = import_node_fs9.default.readFileSync(import_node_path9.default.join(entryPath, "SKILL.md"), "utf8");
50184
+ } catch {
50185
+ try {
50186
+ content = import_node_fs9.default.readFileSync(import_node_path9.default.join(entryPath, "skill.md"), "utf8");
50187
+ } catch {
50188
+ continue;
50189
+ }
50190
+ }
50191
+ const { description } = parseDescription(content);
50192
+ const baseName = ent.name;
50193
+ const name = pluginName ? `${pluginName}:${baseName}` : baseName;
50194
+ if (seen.has(name)) continue;
50195
+ seen.add(name);
50196
+ const info = { name, source, path: entryPath, description: description || void 0 };
50197
+ if (pluginName) info.plugin = pluginName;
50198
+ out.push(info);
50199
+ }
50200
+ }
50201
+ function listSkillsForDir(dir, source) {
50202
+ const seen = /* @__PURE__ */ new Set();
50203
+ const out = [];
50204
+ scanSkillDir(dir, source, seen, out);
50205
+ return out;
50206
+ }
50207
+ function scanCommandDir(dir, source, seen, out, pluginName) {
50208
+ let entries;
50209
+ try {
50210
+ entries = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
50211
+ } catch {
50212
+ return;
50213
+ }
50214
+ for (const ent of entries) {
50215
+ const entryPath = import_node_path9.default.join(dir, ent.name);
50216
+ if (ent.isDirectory() || ent.isSymbolicLink() && isDirLikeSync(entryPath)) {
50217
+ const ns = ent.name;
50218
+ let subEntries;
50219
+ try {
50220
+ subEntries = import_node_fs9.default.readdirSync(entryPath, { withFileTypes: true });
50221
+ } catch {
50222
+ continue;
50223
+ }
50224
+ for (const se of subEntries) {
50225
+ if (!se.name.endsWith(".md")) continue;
50226
+ const sePath = import_node_path9.default.join(entryPath, se.name);
50227
+ let content;
50228
+ try {
50229
+ content = import_node_fs9.default.readFileSync(sePath, "utf8");
50230
+ } catch {
50231
+ continue;
50232
+ }
50233
+ const cmd = se.name.replace(/\.md$/, "");
50234
+ const { description } = parseDescription(content);
50235
+ const qualified = `${ns}:${cmd}`;
50236
+ const name = pluginName ? `${pluginName}:${qualified}` : qualified;
50237
+ if (seen.has(name)) continue;
50238
+ seen.add(name);
50239
+ const info = { name, source, path: sePath, description: description || void 0 };
50240
+ if (pluginName) info.plugin = pluginName;
50241
+ out.push(info);
50242
+ }
50243
+ } else if (ent.name.endsWith(".md")) {
50244
+ let content;
50245
+ try {
50246
+ content = import_node_fs9.default.readFileSync(entryPath, "utf8");
50247
+ } catch {
50248
+ continue;
50249
+ }
50250
+ const cmd = ent.name.replace(/\.md$/, "");
50251
+ const { description } = parseDescription(content);
50252
+ const name = pluginName ? `${pluginName}:${cmd}` : cmd;
50253
+ if (seen.has(name)) continue;
50254
+ seen.add(name);
50255
+ const info = { name, source, path: entryPath, description: description || void 0 };
50256
+ if (pluginName) info.plugin = pluginName;
50257
+ out.push(info);
50258
+ }
50259
+ }
50260
+ }
50261
+ function readInstalledPlugins(home) {
50262
+ const file = import_node_path9.default.join(home, ".claude", "plugins", "installed_plugins.json");
50263
+ let raw;
50264
+ try {
50265
+ raw = import_node_fs9.default.readFileSync(file, "utf8");
50266
+ } catch {
50267
+ return [];
50268
+ }
50269
+ let parsed;
50270
+ try {
50271
+ parsed = JSON.parse(raw);
50272
+ } catch {
50273
+ return [];
50274
+ }
50275
+ const out = [];
50276
+ for (const [key, entries] of Object.entries(parsed.plugins ?? {})) {
50277
+ if (!Array.isArray(entries) || entries.length === 0) continue;
50278
+ const entry = entries[0];
50279
+ if (!entry?.installPath) continue;
50280
+ const pluginName = key.includes("@") ? key.slice(0, key.indexOf("@")) : key;
50281
+ if (!pluginName) continue;
50282
+ out.push({ name: pluginName, root: entry.installPath });
50283
+ }
50284
+ return out;
50285
+ }
50286
+ var SkillsScanner = class {
50287
+ home;
50288
+ extraPluginRoots;
50289
+ constructor(opts = {}) {
50290
+ this.home = opts.home ?? import_node_os6.default.homedir();
50291
+ this.extraPluginRoots = opts.extraPluginRoots ?? [];
50292
+ }
50293
+ /**
50294
+ * 对齐 cc-direct(openclaw-plugin-clawos/src/claude-code/skills-scanner.ts):
50295
+ * 1. global: ~/.claude/skills + ~/.claude/commands
50296
+ * 2. project: <cwd>/.claude/skills + <cwd>/.claude/commands
50297
+ * 3. plugin: installed_plugins.json 里每个 plugin 的 skills + commands
50298
+ * 顺序 global → project → plugin,同名去重(先到先得)
50299
+ */
50300
+ list(args) {
50301
+ const seen = /* @__PURE__ */ new Set();
50302
+ const builtinBlock = [];
50303
+ for (const b2 of BUILTIN_SKILLS) {
50304
+ if (seen.has(b2.name)) continue;
50305
+ seen.add(b2.name);
50306
+ builtinBlock.push({
50307
+ name: b2.name,
50308
+ source: "builtin",
50309
+ description: b2.description
50310
+ });
50311
+ }
50312
+ const fsBlock = [];
50313
+ scanSkillDir(import_node_path9.default.join(this.home, ".claude", "skills"), "global", seen, fsBlock);
50314
+ scanCommandDir(import_node_path9.default.join(this.home, ".claude", "commands"), "global", seen, fsBlock);
50315
+ scanSkillDir(import_node_path9.default.join(args.cwd, ".claude", "skills"), "project", seen, fsBlock);
50316
+ scanCommandDir(import_node_path9.default.join(args.cwd, ".claude", "commands"), "project", seen, fsBlock);
50317
+ const plugins = [...readInstalledPlugins(this.home), ...this.extraPluginRoots];
50318
+ for (const { name, root } of plugins) {
50319
+ scanSkillDir(import_node_path9.default.join(root, "skills"), "plugin", seen, fsBlock, name);
50320
+ scanCommandDir(import_node_path9.default.join(root, "commands"), "plugin", seen, fsBlock, name);
50321
+ }
50322
+ fsBlock.sort((a, b2) => a.name < b2.name ? -1 : a.name > b2.name ? 1 : 0);
50323
+ return [...builtinBlock, ...fsBlock];
50324
+ }
50325
+ };
50326
+
50327
+ // src/persona/manager.ts
50328
+ var PersonaManager = class {
50329
+ constructor(deps) {
50330
+ this.deps = deps;
50331
+ }
50332
+ deps;
50333
+ /**
50334
+ * slug 是 persona 的身份,label 只是显示名——两者刻意分开:id 落进文件路径 /
50335
+ * 分享链接且创建后不可改,label 可中文、可随时改。此前 id 从 label 推导,
50336
+ * 非 ascii 被剥空后一律 fallback 成 `persona-persona`。
50337
+ *
50338
+ * 撞名直接抛错而非改名:id 由调用方显式指定,静默换成带后缀的 id 等于把
50339
+ * 用户的选择偷偷改掉。字符集校验在 PersonaCreateArgsSchema(路径穿越边界)。
50340
+ */
50341
+ create(args) {
50342
+ const personaId2 = `persona-${args.slug}`;
50343
+ if (this.deps.store.has(personaId2)) {
50344
+ throw new Error(`personaId already exists: ${personaId2}`);
50345
+ }
50346
+ const now = Date.now();
50347
+ const persona = {
50348
+ personaId: personaId2,
50349
+ label: args.label,
50350
+ model: args.model,
50351
+ // effort 兜底 high:persona 一律持有具体档位(spec 2026-08-12),UI 会带界面上高亮的
50352
+ // 那一档,但 persona:create 也可能来自 butler 的 persona-authoring skill 这类不带该
50353
+ // 字段的调用方——不兜的话要等下次 daemon 启动 backfill 才补上,中间建的 session 就漏了。
50354
+ // 取值理由与 backfillPersonaSessionDefaults 一致(见那里的注释)。
50355
+ effort: args.effort ?? "high",
50356
+ tool: args.tool,
50357
+ public: args.public ?? false,
50358
+ iconKey: args.iconKey,
50359
+ createdAt: now,
50360
+ updatedAt: now
50361
+ };
50362
+ this.deps.store.write(persona, args.personality);
50363
+ this.deps.store.writeCodexSandboxSettings(personaId2, CODEX_SANDBOX_DEFAULT);
50364
+ this.deps.registry.set(persona);
50365
+ return persona;
50366
+ }
50367
+ update(personaId2, patch, texts) {
50368
+ const existing = this.deps.registry.get(personaId2);
50369
+ if (!existing) throw new Error(`persona not found: ${personaId2}`);
50370
+ const { iconKey: iconKeyPatch, ...restPatch } = patch;
50371
+ const updated = {
50372
+ ...existing,
50373
+ ...restPatch,
50374
+ updatedAt: Date.now()
50375
+ };
50376
+ const toolChanged = patch.tool !== void 0 && (patch.tool ?? "claude") !== (existing.tool ?? "claude");
50377
+ if (toolChanged) {
50378
+ if (patch.model === void 0) delete updated.model;
50379
+ if (patch.effort === void 0) delete updated.effort;
50380
+ }
50381
+ if (iconKeyPatch === null) {
50382
+ delete updated.iconKey;
50383
+ } else if (iconKeyPatch !== void 0) {
50384
+ updated.iconKey = iconKeyPatch;
50385
+ }
50386
+ if (texts?.personality !== void 0) {
50387
+ this.deps.store.writePersonality(personaId2, texts.personality);
50388
+ }
50389
+ if (texts?.personalityLocal !== void 0) {
50390
+ this.deps.store.writePersonalityLocal(personaId2, texts.personalityLocal);
50391
+ }
50392
+ this.deps.store.writeMeta(updated);
50393
+ this.deps.registry.set(updated);
50394
+ return updated;
50395
+ }
50396
+ /** 读取 CLAUDE.md 内容;persona 目录或文件不存在时返回 null。透传给 handler 拼响应。 */
50397
+ readPersonality(personaId2) {
50398
+ return this.deps.store.readPersonality(personaId2);
50399
+ }
50400
+ /** 读人格补充层(`.clawd/personality.local.md`);没写过返回 null。供 persona:get 拼响应。 */
50401
+ readPersonalityLocal(personaId2) {
50402
+ return this.deps.store.readPersonalityLocal(personaId2);
50403
+ }
50404
+ /** 扫 persona 私有 skills 目录;目录不存在返回空数组。供 persona:get handler 拼响应。 */
50405
+ listSkills(personaId2) {
50406
+ return listSkillsForDir(this.deps.store.skillsDir(personaId2), "project");
50407
+ }
50408
+ /**
50409
+ * 读取 persona `.claude/settings.json` 中已启用的插件列表(value === true 的 key)。
50410
+ * 文件不存在 / 解析失败 / 字段缺失 → 空数组。供 persona:get handler 拼响应。
50411
+ */
50412
+ listEnabledPlugins(personaId2) {
50413
+ return this.deps.store.readEnabledPlugins(personaId2);
50414
+ }
50415
+ /** 读 codex guest 沙箱配置(`.clawd/codex-sandbox.json`);缺失 / 坏 → null。透传给 handler 拼响应。 */
50416
+ readCodexSandboxSettings(personaId2) {
50417
+ return this.deps.store.readCodexSandboxSettings(personaId2);
50418
+ }
50419
+ /**
50420
+ * 局部合并写 codex guest 沙箱配置,返回合并后的全量。
50421
+ *
50422
+ * **必须是合并不是覆盖**:UI 的「访客联网」开关只发 `{network}`,整体覆盖会静默抹掉 owner
50423
+ * 手配的 writableRoots / denyRead(丢的是安全边界,且用户不会立刻发现)。
50424
+ * 不走 PersonaManager.update 是因为它管的是 PersonaFile meta + 人格两层文本,
50425
+ * 而这份配置是另一个文件、另一套 schema。
50426
+ *
50427
+ * **读不到时 base 用 CODEX_SANDBOX_DEFAULT 而不是 `{}`**:`{}` 会让这次「合并」实际写出一份
50428
+ * 只有 patch 字段的残缺配置(例如只有 `{network:true}`,没有包管理器缓存目录 → `npm install`
50429
+ * EPERM),而且从此 migrateCodexSandbox 见文件已存在就跳过,UI 上再也补不回来。
50430
+ * 正常路径下文件在 create 时就已物化,这条兜底只在文件被删 / 被改坏时触发。
50431
+ *
50432
+ * ⚠️ 已知限制:`CodexSandboxSettingsSchema` 是 `.strict()` 的,owner 手编时多写一个键
50433
+ * (注释键 / 历史字段)会让整份文件读成 null,于是这次合并把它替换成默认值 ⊕ patch。
50434
+ * 已有测试锁定该 strict 语义(store.spec「schema 校验失败 → 返回 null」),本次不动它——
50435
+ * 至少残缺配置不会再被写出去。要放宽得单独评估。
50436
+ */
50437
+ mergeCodexSandbox(personaId2, patch) {
50438
+ if (!this.deps.registry.get(personaId2)) throw new Error(`persona not found: ${personaId2}`);
50439
+ const base = this.deps.store.readCodexSandboxSettings(personaId2) ?? CODEX_SANDBOX_DEFAULT;
50440
+ const merged = { ...base, ...patch };
50441
+ this.deps.store.writeCodexSandboxSettings(personaId2, merged);
50442
+ return merged;
50443
+ }
50444
+ /** 读 sandbox-settings.json;不存在 / 损坏返回 null。供 persona:get handler 拼响应。 */
50445
+ readSandboxSettings(personaId2) {
50446
+ return this.deps.store.readSandboxSettings(personaId2);
50447
+ }
50448
+ /**
50449
+ * 删除 persona。
50450
+ * PersonaStore.remove(personaId) 级联删整个 <personaRoot>/<personaId>/ 目录
50451
+ * (含 .clawd/sessions/owner/ 和 .clawd/sessions/guests/<capId>/),rmSync
50452
+ * recursive force 一锅清。
50453
+ */
50454
+ delete(personaId2) {
50455
+ this.deps.store.remove(personaId2);
50456
+ this.deps.registry.remove(personaId2);
50457
+ }
50458
+ };
50459
+
50445
50460
  // src/share-md-viewer/load.ts
50446
50461
  var import_node_fs11 = __toESM(require("fs"), 1);
50447
50462
  var import_node_path10 = __toESM(require("path"), 1);
@@ -63003,7 +63018,7 @@ function computeMethodAccess(args) {
63003
63018
  }
63004
63019
 
63005
63020
  // src/version.ts
63006
- var version = "0.2.440".length > 0 ? "0.2.440" : "dev";
63021
+ var version = "0.2.442".length > 0 ? "0.2.442" : "dev";
63007
63022
 
63008
63023
  // src/cli-probe/probe.ts
63009
63024
  var fs66 = __toESM(require("fs"), 1);
@@ -63187,16 +63202,8 @@ function buildPersonaHandlers(deps) {
63187
63202
  const args = parseArgs2(PersonaUpdateArgsSchema, frame, "persona:update");
63188
63203
  const { personality, personalityLocal, codexSandbox, ...metaPatch } = args.patch;
63189
63204
  const persona = personaManager.update(args.personaId, metaPatch, { personality, personalityLocal });
63190
- const mergedCodexSandbox = codexSandbox ? personaManager.mergeCodexSandbox(args.personaId, codexSandbox) : void 0;
63191
- return {
63192
- response: {
63193
- type: "persona:info",
63194
- ...persona,
63195
- // 只在本次确实动过时回带,让调用方拿到合并后的真值而不用再 get 一次;
63196
- // 没动就不带(与 personality 等分层字段同口径:persona:update 的响应是 meta 视图)。
63197
- ...mergedCodexSandbox ? { codexSandbox: mergedCodexSandbox } : {}
63198
- }
63199
- };
63205
+ if (codexSandbox) personaManager.mergeCodexSandbox(args.personaId, codexSandbox);
63206
+ return { response: { type: "persona:info", ...persona } };
63200
63207
  };
63201
63208
  const del = async (frame) => {
63202
63209
  const args = parseArgs2(PersonaIdArgsSchema, frame, "persona:delete");