@agenshield/daemon 0.7.0 → 0.7.1

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/index.js CHANGED
@@ -1046,10 +1046,7 @@ PLISTEOF`);
1046
1046
  }
1047
1047
  async function startOpenClawServices() {
1048
1048
  try {
1049
- try {
1050
- await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
1051
- } catch {
1052
- }
1049
+ await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
1053
1050
  return {
1054
1051
  success: true,
1055
1052
  message: "OpenClaw gateway started"
@@ -1064,10 +1061,7 @@ async function startOpenClawServices() {
1064
1061
  }
1065
1062
  async function stopOpenClawServices() {
1066
1063
  try {
1067
- try {
1068
- await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
1069
- } catch {
1070
- }
1064
+ await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
1071
1065
  return {
1072
1066
  success: true,
1073
1067
  message: "OpenClaw gateway stopped"
@@ -1082,10 +1076,7 @@ async function stopOpenClawServices() {
1082
1076
  }
1083
1077
  async function restartOpenClawServices() {
1084
1078
  try {
1085
- try {
1086
- await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
1087
- } catch {
1088
- }
1079
+ await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
1089
1080
  return {
1090
1081
  success: true,
1091
1082
  message: "OpenClaw gateway restarted"
@@ -1168,15 +1159,29 @@ function getOpenClawStatusSync() {
1168
1159
  async function getOpenClawDashboardUrl() {
1169
1160
  try {
1170
1161
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
1171
- const agentUsername = path22.basename(agentHome);
1172
- const { stdout, stderr } = await execAsync3(
1173
- `sudo -H -u ${agentUsername} ${OPENCLAW_LAUNCHER_PATH} dashboard`,
1174
- { timeout: 15e3, maxBuffer: 10 * 1024 * 1024 }
1175
- );
1176
- const url = stdout.trim();
1177
- if (!url) {
1178
- return { success: false, error: stderr.trim() || "No URL returned from openclaw dashboard" };
1162
+ const configPath = path22.join(agentHome, ".openclaw", "openclaw.json");
1163
+ let raw;
1164
+ try {
1165
+ raw = await fs32.readFile(configPath, "utf-8");
1166
+ } catch (err) {
1167
+ if (err.code === "EACCES") {
1168
+ const agentUsername = path22.basename(agentHome);
1169
+ const { stdout } = await execAsync3(
1170
+ `sudo -H -u ${agentUsername} cat "${configPath}"`,
1171
+ { cwd: "/" }
1172
+ );
1173
+ raw = stdout;
1174
+ } else {
1175
+ return { success: false, error: `Cannot read openclaw.json: ${err.message}` };
1176
+ }
1177
+ }
1178
+ const config = JSON.parse(raw);
1179
+ const port = config.gateway?.port;
1180
+ const token = config.gateway?.auth?.token;
1181
+ if (!port || !token) {
1182
+ return { success: false, error: "Gateway port or auth token not found in openclaw.json" };
1179
1183
  }
1184
+ const url = `http://127.0.0.1:${port}/?token=${token}`;
1180
1185
  return { success: true, url };
1181
1186
  } catch (error) {
1182
1187
  return { success: false, error: `Failed to get dashboard URL: ${error.message}` };
@@ -1766,7 +1771,7 @@ var init_secret_sync = __esm({
1766
1771
  });
1767
1772
 
1768
1773
  // libs/shield-daemon/src/server.ts
1769
- import * as fs21 from "node:fs";
1774
+ import * as fs23 from "node:fs";
1770
1775
  import Fastify from "fastify";
1771
1776
  import cors from "@fastify/cors";
1772
1777
  import fastifyStatic from "@fastify/static";
@@ -2150,11 +2155,24 @@ function getOpenClawConfigPath() {
2150
2155
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
2151
2156
  return path8.join(agentHome, ".openclaw", "openclaw.json");
2152
2157
  }
2153
- function readConfig() {
2158
+ function readOpenClawConfig() {
2154
2159
  const configPath = getOpenClawConfigPath();
2155
2160
  try {
2156
2161
  if (fs8.existsSync(configPath)) {
2157
- return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
2162
+ try {
2163
+ return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
2164
+ } catch (err) {
2165
+ if (err.code === "EACCES") {
2166
+ const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
2167
+ const agentUsername = path8.basename(agentHome);
2168
+ const raw = execSync6(
2169
+ `sudo -H -u ${agentUsername} cat "${configPath}"`,
2170
+ { encoding: "utf-8", cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
2171
+ );
2172
+ return JSON.parse(raw);
2173
+ }
2174
+ throw err;
2175
+ }
2158
2176
  }
2159
2177
  } catch {
2160
2178
  console.warn("[OpenClawConfig] Failed to read openclaw.json, starting fresh");
@@ -2172,7 +2190,7 @@ function writeConfig(config) {
2172
2190
  const agentUsername = path8.basename(agentHome);
2173
2191
  execSync6(
2174
2192
  `sudo -H -u ${agentUsername} tee "${configPath}" > /dev/null`,
2175
- { input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"] }
2193
+ { input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"], cwd: "/" }
2176
2194
  );
2177
2195
  } else {
2178
2196
  throw err;
@@ -2180,27 +2198,30 @@ function writeConfig(config) {
2180
2198
  }
2181
2199
  }
2182
2200
  function addSkillEntry(slug) {
2183
- const config = readConfig();
2201
+ const config = readOpenClawConfig();
2184
2202
  if (!config.skills) {
2185
2203
  config.skills = {};
2186
2204
  }
2187
2205
  if (!config.skills.entries) {
2188
2206
  config.skills.entries = {};
2189
2207
  }
2190
- config.skills.entries[slug] = { enabled: true };
2208
+ const existing = config.skills.entries[slug] ?? {};
2209
+ config.skills.entries[slug] = { ...existing, enabled: true };
2191
2210
  writeConfig(config);
2192
2211
  console.log(`[OpenClawConfig] Added skill entry: ${slug}`);
2193
2212
  }
2194
2213
  function removeSkillEntry(slug) {
2195
- const config = readConfig();
2214
+ const config = readOpenClawConfig();
2196
2215
  if (config.skills?.entries?.[slug]) {
2197
- delete config.skills.entries[slug];
2216
+ const existing = config.skills.entries[slug];
2217
+ delete existing.env;
2218
+ config.skills.entries[slug] = { ...existing, enabled: false };
2198
2219
  writeConfig(config);
2199
- console.log(`[OpenClawConfig] Removed skill entry: ${slug}`);
2220
+ console.log(`[OpenClawConfig] Disabled skill entry: ${slug}`);
2200
2221
  }
2201
2222
  }
2202
2223
  function syncOpenClawFromPolicies(policies) {
2203
- const config = readConfig();
2224
+ const config = readOpenClawConfig();
2204
2225
  if (!config.skills) config.skills = {};
2205
2226
  const allowBundled = [];
2206
2227
  for (const p of policies) {
@@ -2448,35 +2469,35 @@ var ANALYSIS_TIMEOUT = 4 * 6e4;
2448
2469
  var SEARCH_CACHE_TTL = 6e4;
2449
2470
  var DETAIL_CACHE_TTL = 5 * 6e4;
2450
2471
  var SHORT_TIMEOUT = 1e4;
2451
- async function convexAction(path21, args, timeout) {
2472
+ async function convexAction(path24, args, timeout) {
2452
2473
  const res = await fetch(`${CONVEX_BASE}/api/action`, {
2453
2474
  method: "POST",
2454
2475
  signal: AbortSignal.timeout(timeout),
2455
2476
  headers: { "Content-Type": "application/json" },
2456
- body: JSON.stringify({ path: path21, args, format: "json" })
2477
+ body: JSON.stringify({ path: path24, args, format: "json" })
2457
2478
  });
2458
2479
  if (!res.ok) {
2459
- throw new Error(`Convex action ${path21} returned ${res.status}`);
2480
+ throw new Error(`Convex action ${path24} returned ${res.status}`);
2460
2481
  }
2461
2482
  const body = await res.json();
2462
2483
  if (body.status === "error") {
2463
- throw new Error(`Convex action ${path21}: ${body.errorMessage ?? "unknown error"}`);
2484
+ throw new Error(`Convex action ${path24}: ${body.errorMessage ?? "unknown error"}`);
2464
2485
  }
2465
2486
  return body.value;
2466
2487
  }
2467
- async function convexQuery(path21, args, timeout) {
2488
+ async function convexQuery(path24, args, timeout) {
2468
2489
  const res = await fetch(`${CONVEX_BASE}/api/query`, {
2469
2490
  method: "POST",
2470
2491
  signal: AbortSignal.timeout(timeout),
2471
2492
  headers: { "Content-Type": "application/json" },
2472
- body: JSON.stringify({ path: path21, args, format: "json" })
2493
+ body: JSON.stringify({ path: path24, args, format: "json" })
2473
2494
  });
2474
2495
  if (!res.ok) {
2475
- throw new Error(`Convex query ${path21} returned ${res.status}`);
2496
+ throw new Error(`Convex query ${path24} returned ${res.status}`);
2476
2497
  }
2477
2498
  const body = await res.json();
2478
2499
  if (body.status === "error") {
2479
- throw new Error(`Convex query ${path21}: ${body.errorMessage ?? "unknown error"}`);
2500
+ throw new Error(`Convex query ${path24}: ${body.errorMessage ?? "unknown error"}`);
2480
2501
  }
2481
2502
  return body.value;
2482
2503
  }
@@ -2678,9 +2699,9 @@ function inlineImagesInMarkdown(markdown, files) {
2678
2699
  const mime = isImageExt(file.name);
2679
2700
  if (mime && file.content.startsWith("data:")) {
2680
2701
  imageMap.set(file.name, file.content);
2681
- const basename3 = file.name.split("/").pop() ?? "";
2682
- if (basename3 && !imageMap.has(basename3)) {
2683
- imageMap.set(basename3, file.content);
2702
+ const basename6 = file.name.split("/").pop() ?? "";
2703
+ if (basename6 && !imageMap.has(basename6)) {
2704
+ imageMap.set(basename6, file.content);
2684
2705
  }
2685
2706
  }
2686
2707
  }
@@ -3206,10 +3227,10 @@ function emitSecurityWarning(warning) {
3206
3227
  function emitSecurityCritical(issue) {
3207
3228
  daemonEvents.broadcast("security:critical", { message: issue });
3208
3229
  }
3209
- function emitApiRequest(method, path21, statusCode, duration, requestBody, responseBody) {
3230
+ function emitApiRequest(method, path24, statusCode, duration, requestBody, responseBody) {
3210
3231
  daemonEvents.broadcast("api:request", {
3211
3232
  method,
3212
- path: path21,
3233
+ path: path24,
3213
3234
  statusCode,
3214
3235
  duration,
3215
3236
  ...requestBody !== void 0 && { requestBody },
@@ -3464,26 +3485,16 @@ function scanSkills() {
3464
3485
  }
3465
3486
  function detectOpenClawMismatches() {
3466
3487
  try {
3467
- const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
3468
- const configPath = path11.join(agentHome, ".openclaw", "openclaw.json");
3469
- if (!fs11.existsSync(configPath)) return;
3470
- const raw = fs11.readFileSync(configPath, "utf-8");
3471
- const config = JSON.parse(raw);
3472
- if (!config.skills?.entries) return;
3473
3488
  const approved = loadApprovedSkills();
3474
3489
  const approvedNames = new Set(approved.map((a) => a.name));
3475
- const entries = Object.keys(config.skills.entries);
3476
- let changed = false;
3477
- for (const name of entries) {
3490
+ const config = readOpenClawConfig();
3491
+ if (!config.skills?.entries) return;
3492
+ for (const name of Object.keys(config.skills.entries)) {
3478
3493
  if (!approvedNames.has(name)) {
3479
- delete config.skills.entries[name];
3480
- changed = true;
3481
- console.log(`[SkillsWatcher] Removed stale openclaw.json entry: ${name}`);
3494
+ removeSkillEntry(name);
3495
+ console.log(`[SkillsWatcher] Disabled stale openclaw.json entry: ${name}`);
3482
3496
  }
3483
3497
  }
3484
- if (changed) {
3485
- fs11.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
3486
- }
3487
3498
  } catch {
3488
3499
  }
3489
3500
  }
@@ -4042,16 +4053,16 @@ var PROTECTED_ROUTES = [
4042
4053
  { method: "POST", path: "/api/skills/install" },
4043
4054
  { method: "GET", path: "/api/openclaw/dashboard-url" }
4044
4055
  ];
4045
- function isProtectedRoute(method, path21) {
4056
+ function isProtectedRoute(method, path24) {
4046
4057
  return PROTECTED_ROUTES.some(
4047
- (route) => route.method === method && path21.startsWith(route.path)
4058
+ (route) => route.method === method && path24.startsWith(route.path)
4048
4059
  );
4049
4060
  }
4050
4061
  function createAuthHook() {
4051
4062
  return async (request2, reply) => {
4052
4063
  const method = request2.method;
4053
- const path21 = request2.url.split("?")[0];
4054
- if (!isProtectedRoute(method, path21)) {
4064
+ const path24 = request2.url.split("?")[0];
4065
+ if (!isProtectedRoute(method, path24)) {
4055
4066
  return;
4056
4067
  }
4057
4068
  if (!isAuthenticated(request2)) {
@@ -7550,19 +7561,50 @@ async function agencoRoutes(app) {
7550
7561
  }
7551
7562
 
7552
7563
  // libs/shield-daemon/src/routes/skills.ts
7553
- import * as fs16 from "node:fs";
7554
- import * as path16 from "node:path";
7564
+ import * as fs17 from "node:fs";
7565
+ import * as path17 from "node:path";
7555
7566
  import { execSync as execSync9 } from "node:child_process";
7556
- import { parseSkillMd as parseSkillMd2 } from "@agenshield/sandbox";
7567
+ import { parseSkillMd as parseSkillMd3, stripEnvFromSkillMd as stripEnvFromSkillMd2 } from "@agenshield/sandbox";
7557
7568
 
7558
7569
  // libs/shield-daemon/src/services/skill-lifecycle.ts
7559
7570
  import * as fs14 from "node:fs";
7560
7571
  import * as path14 from "node:path";
7561
7572
  import { execSync as execSync8 } from "node:child_process";
7562
- function createSkillWrapper(name, binDir) {
7563
- if (!fs14.existsSync(binDir)) {
7564
- fs14.mkdirSync(binDir, { recursive: true });
7573
+ function sudoMkdir(dir, agentUsername) {
7574
+ try {
7575
+ fs14.mkdirSync(dir, { recursive: true });
7576
+ } catch (err) {
7577
+ if (err.code === "EACCES") {
7578
+ execSync8(`sudo -H -u ${agentUsername} /bin/mkdir -p "${dir}"`, { cwd: "/", stdio: "pipe" });
7579
+ } else {
7580
+ throw err;
7581
+ }
7582
+ }
7583
+ }
7584
+ function sudoWriteFile(filePath, content, agentUsername, mode) {
7585
+ try {
7586
+ fs14.writeFileSync(filePath, content, { mode });
7587
+ } catch (err) {
7588
+ if (err.code === "EACCES") {
7589
+ execSync8(
7590
+ `sudo -H -u ${agentUsername} tee "${filePath}" > /dev/null`,
7591
+ { input: content, cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
7592
+ );
7593
+ if (mode) {
7594
+ try {
7595
+ execSync8(`sudo -H -u ${agentUsername} chmod ${mode.toString(8)} "${filePath}"`, { cwd: "/", stdio: "pipe" });
7596
+ } catch {
7597
+ }
7598
+ }
7599
+ } else {
7600
+ throw err;
7601
+ }
7565
7602
  }
7603
+ }
7604
+ function createSkillWrapper(name, binDir) {
7605
+ const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
7606
+ const agentUsername = path14.basename(agentHome);
7607
+ sudoMkdir(binDir, agentUsername);
7566
7608
  const wrapperPath = path14.join(binDir, name);
7567
7609
  const wrapperContent = `#!/bin/bash
7568
7610
  # ${name} skill wrapper - policy-enforced execution
@@ -7570,7 +7612,7 @@ function createSkillWrapper(name, binDir) {
7570
7612
  if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
7571
7613
  exec /opt/agenshield/bin/shield-client skill run "${name}" "$@"
7572
7614
  `;
7573
- fs14.writeFileSync(wrapperPath, wrapperContent, { mode: 493 });
7615
+ sudoWriteFile(wrapperPath, wrapperContent, agentUsername, 493);
7574
7616
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
7575
7617
  try {
7576
7618
  execSync8(`chown root:${socketGroup} "${wrapperPath}"`, { stdio: "pipe" });
@@ -7615,8 +7657,8 @@ function removeSkillPolicy(name) {
7615
7657
  }
7616
7658
 
7617
7659
  // libs/shield-daemon/src/routes/marketplace.ts
7618
- import * as fs15 from "node:fs";
7619
- import * as path15 from "node:path";
7660
+ import * as fs16 from "node:fs";
7661
+ import * as path16 from "node:path";
7620
7662
 
7621
7663
  // libs/shield-broker/dist/index.js
7622
7664
  import { exec as exec4 } from "node:child_process";
@@ -7892,6 +7934,175 @@ async function uninstallSkillViaBroker(slug, options = {}) {
7892
7934
  return result;
7893
7935
  }
7894
7936
 
7937
+ // libs/shield-daemon/src/routes/marketplace.ts
7938
+ import { stripEnvFromSkillMd } from "@agenshield/sandbox";
7939
+
7940
+ // libs/shield-daemon/src/services/skill-deps.ts
7941
+ import * as fs15 from "node:fs";
7942
+ import * as path15 from "node:path";
7943
+ import { parseSkillMd as parseSkillMd2, extractSkillInfo } from "@agenshield/sandbox";
7944
+ import { execWithProgress as execWithProgress3 } from "@agenshield/sandbox";
7945
+ var SUPPORTED_KINDS = /* @__PURE__ */ new Set(["brew", "npm", "pip"]);
7946
+ var SAFE_PACKAGE_RE = /^[a-zA-Z0-9@/_.\-]+$/;
7947
+ function findSkillMdRecursive(dir, depth = 0) {
7948
+ if (depth > 3) return null;
7949
+ try {
7950
+ for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
7951
+ const candidate = path15.join(dir, name);
7952
+ if (fs15.existsSync(candidate)) return candidate;
7953
+ }
7954
+ const entries = fs15.readdirSync(dir, { withFileTypes: true });
7955
+ for (const entry of entries) {
7956
+ if (!entry.isDirectory()) continue;
7957
+ const found = findSkillMdRecursive(path15.join(dir, entry.name), depth + 1);
7958
+ if (found) return found;
7959
+ }
7960
+ } catch {
7961
+ }
7962
+ return null;
7963
+ }
7964
+ async function executeSkillInstallSteps(options) {
7965
+ const { slug, skillDir, agentHome, agentUsername, onLog } = options;
7966
+ const installed = [];
7967
+ const errors = [];
7968
+ const skillMdPath = findSkillMdRecursive(skillDir);
7969
+ if (!skillMdPath) {
7970
+ return { success: true, installed, errors };
7971
+ }
7972
+ let content;
7973
+ try {
7974
+ content = fs15.readFileSync(skillMdPath, "utf-8");
7975
+ } catch {
7976
+ return { success: true, installed, errors };
7977
+ }
7978
+ const parsed = parseSkillMd2(content);
7979
+ if (!parsed) {
7980
+ return { success: true, installed, errors };
7981
+ }
7982
+ const info = extractSkillInfo(parsed.metadata);
7983
+ const installSteps = info.installSteps;
7984
+ if (!Array.isArray(installSteps) || installSteps.length === 0) {
7985
+ return { success: true, installed, errors };
7986
+ }
7987
+ onLog(`Found ${installSteps.length} dependency install step(s) for ${slug}`);
7988
+ for (const step of installSteps) {
7989
+ const kind = step.kind;
7990
+ const stepId = step.id || kind;
7991
+ if (!SUPPORTED_KINDS.has(kind)) {
7992
+ errors.push(`Unsupported install kind "${kind}" (step: ${stepId})`);
7993
+ continue;
7994
+ }
7995
+ try {
7996
+ switch (kind) {
7997
+ case "brew": {
7998
+ const formula = step.formula;
7999
+ if (!formula) {
8000
+ errors.push(`Brew step "${stepId}" missing formula`);
8001
+ break;
8002
+ }
8003
+ if (!SAFE_PACKAGE_RE.test(formula)) {
8004
+ errors.push(`Unsafe brew formula name: ${formula}`);
8005
+ break;
8006
+ }
8007
+ onLog(`Installing brew formula: ${formula}`);
8008
+ const brewCmd = [
8009
+ `export HOME="${agentHome}"`,
8010
+ `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:$PATH"`,
8011
+ `brew install ${formula}`
8012
+ ].join(" && ");
8013
+ await execWithProgress3(
8014
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${brewCmd}'`,
8015
+ onLog,
8016
+ { timeout: 12e4, cwd: "/" }
8017
+ );
8018
+ installed.push(formula);
8019
+ break;
8020
+ }
8021
+ case "npm": {
8022
+ const pkg = step["package"];
8023
+ if (!pkg) {
8024
+ errors.push(`npm step "${stepId}" missing package`);
8025
+ break;
8026
+ }
8027
+ if (!SAFE_PACKAGE_RE.test(pkg)) {
8028
+ errors.push(`Unsafe npm package name: ${pkg}`);
8029
+ break;
8030
+ }
8031
+ onLog(`Installing npm package: ${pkg}`);
8032
+ const npmCmd = [
8033
+ `export HOME="${agentHome}"`,
8034
+ `export PATH="${agentHome}/bin:$PATH"`,
8035
+ `source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
8036
+ `npm install -g ${pkg}`
8037
+ ].join(" && ");
8038
+ await execWithProgress3(
8039
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${npmCmd}'`,
8040
+ onLog,
8041
+ { timeout: 6e4, cwd: "/" }
8042
+ );
8043
+ installed.push(pkg);
8044
+ break;
8045
+ }
8046
+ case "pip": {
8047
+ const pkg = step["package"];
8048
+ if (!pkg) {
8049
+ errors.push(`pip step "${stepId}" missing package`);
8050
+ break;
8051
+ }
8052
+ if (!SAFE_PACKAGE_RE.test(pkg)) {
8053
+ errors.push(`Unsafe pip package name: ${pkg}`);
8054
+ break;
8055
+ }
8056
+ onLog(`Installing pip package: ${pkg}`);
8057
+ const pipCmd = [
8058
+ `export HOME="${agentHome}"`,
8059
+ `export PATH="${agentHome}/bin:$PATH"`,
8060
+ `pip install ${pkg}`
8061
+ ].join(" && ");
8062
+ await execWithProgress3(
8063
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${pipCmd}'`,
8064
+ onLog,
8065
+ { timeout: 6e4, cwd: "/" }
8066
+ );
8067
+ installed.push(pkg);
8068
+ break;
8069
+ }
8070
+ }
8071
+ } catch (err) {
8072
+ const msg = `Failed to install ${kind} dep (step: ${stepId}): ${err.message}`;
8073
+ onLog(msg);
8074
+ errors.push(msg);
8075
+ }
8076
+ }
8077
+ const requiredBins = info.bins;
8078
+ if (requiredBins.length > 0) {
8079
+ onLog(`Verifying required binaries: ${requiredBins.join(", ")}`);
8080
+ for (const bin of requiredBins) {
8081
+ try {
8082
+ const checkCmd = [
8083
+ `export HOME="${agentHome}"`,
8084
+ `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:$PATH"`,
8085
+ `source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
8086
+ `which ${bin}`
8087
+ ].join(" && ");
8088
+ await execWithProgress3(
8089
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${checkCmd}'`,
8090
+ () => {
8091
+ },
8092
+ { timeout: 5e3, cwd: "/" }
8093
+ );
8094
+ } catch {
8095
+ errors.push(`Required binary "${bin}" not found in agent PATH after install`);
8096
+ }
8097
+ }
8098
+ }
8099
+ return {
8100
+ success: errors.length === 0,
8101
+ installed,
8102
+ errors
8103
+ };
8104
+ }
8105
+
7895
8106
  // libs/shield-daemon/src/routes/marketplace.ts
7896
8107
  var installInProgress = /* @__PURE__ */ new Set();
7897
8108
  function isInstallInProgress(slug) {
@@ -8146,7 +8357,7 @@ async function marketplaceRoutes(app) {
8146
8357
  error: "Critical vulnerability detected",
8147
8358
  analysis: analysisResult
8148
8359
  });
8149
- return { success: false, name: slug, analysis: analysisResult, logs };
8360
+ return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
8150
8361
  }
8151
8362
  emitSkillInstallProgress(slug, "download", "Downloading skill files");
8152
8363
  const skill = await getMarketplaceSkill(slug);
@@ -8156,7 +8367,7 @@ async function marketplaceRoutes(app) {
8156
8367
  name: slug,
8157
8368
  error: "No files available for installation"
8158
8369
  });
8159
- return { success: false, name: slug, analysis: analysisResult, logs };
8370
+ return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
8160
8371
  }
8161
8372
  const publisher = skill.author;
8162
8373
  logs.push("Downloaded skill files");
@@ -8165,18 +8376,23 @@ async function marketplaceRoutes(app) {
8165
8376
  throw new Error("Skills directory not configured");
8166
8377
  }
8167
8378
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8168
- const binDir = path15.join(agentHome, "bin");
8379
+ const agentUsername = path16.basename(agentHome);
8380
+ const binDir = path16.join(agentHome, "bin");
8169
8381
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8170
- skillDir = path15.join(skillsDir2, slug);
8382
+ skillDir = path16.join(skillsDir2, slug);
8171
8383
  emitSkillInstallProgress(slug, "approve", "Pre-approving skill");
8172
8384
  addToApprovedList(slug, publisher);
8173
8385
  logs.push("Skill pre-approved");
8174
8386
  emitSkillInstallProgress(slug, "copy", "Writing skill files");
8175
8387
  const brokerAvailable = await isBrokerAvailable();
8176
8388
  if (brokerAvailable) {
8389
+ const sanitizedFiles = files.map((f) => ({
8390
+ name: f.name,
8391
+ content: /SKILL\.md$/i.test(f.name) ? stripEnvFromSkillMd(f.content) : f.content
8392
+ }));
8177
8393
  const brokerResult = await installSkillViaBroker(
8178
8394
  slug,
8179
- files.map((f) => ({ name: f.name, content: f.content })),
8395
+ sanitizedFiles,
8180
8396
  { createWrapper: true, agentHome, socketGroup }
8181
8397
  );
8182
8398
  if (!brokerResult.installed) {
@@ -8195,18 +8411,45 @@ async function marketplaceRoutes(app) {
8195
8411
  }
8196
8412
  } else {
8197
8413
  console.log(`[Marketplace] Broker unavailable, installing ${slug} directly`);
8198
- fs15.mkdirSync(skillDir, { recursive: true });
8414
+ sudoMkdir(skillDir, agentUsername);
8199
8415
  for (const file of files) {
8200
- const filePath = path15.join(skillDir, file.name);
8201
- const fileDir = path15.dirname(filePath);
8416
+ const filePath = path16.join(skillDir, file.name);
8417
+ const fileDir = path16.dirname(filePath);
8202
8418
  if (fileDir !== skillDir) {
8203
- fs15.mkdirSync(fileDir, { recursive: true });
8419
+ sudoMkdir(fileDir, agentUsername);
8204
8420
  }
8205
- fs15.writeFileSync(filePath, file.content, "utf-8");
8421
+ const content = /SKILL\.md$/i.test(file.name) ? stripEnvFromSkillMd(file.content) : file.content;
8422
+ sudoWriteFile(filePath, content, agentUsername);
8206
8423
  }
8207
8424
  createSkillWrapper(slug, binDir);
8208
8425
  logs.push(`Files written directly: ${files.length} files`);
8209
- logs.push(`Wrapper created: ${path15.join(binDir, slug)}`);
8426
+ logs.push(`Wrapper created: ${path16.join(binDir, slug)}`);
8427
+ }
8428
+ let depsSuccess = true;
8429
+ emitSkillInstallProgress(slug, "deps", "Installing skill dependencies");
8430
+ try {
8431
+ const depsResult = await executeSkillInstallSteps({
8432
+ slug,
8433
+ skillDir,
8434
+ agentHome,
8435
+ agentUsername,
8436
+ onLog: (msg) => emitSkillInstallProgress(slug, "deps", msg)
8437
+ });
8438
+ if (depsResult.installed.length > 0) {
8439
+ logs.push(`Dependencies installed: ${depsResult.installed.join(", ")}`);
8440
+ }
8441
+ if (depsResult.errors.length > 0) {
8442
+ depsSuccess = false;
8443
+ for (const err of depsResult.errors) {
8444
+ emitSkillInstallProgress(slug, "warning", `Dependency warning: ${err}`);
8445
+ logs.push(`Dependency warning: ${err}`);
8446
+ }
8447
+ }
8448
+ } catch (err) {
8449
+ depsSuccess = false;
8450
+ const msg = `Dependency installation failed: ${err.message}`;
8451
+ emitSkillInstallProgress(slug, "warning", msg);
8452
+ logs.push(msg);
8210
8453
  }
8211
8454
  addSkillEntry(slug);
8212
8455
  addSkillPolicy(slug);
@@ -8218,13 +8461,14 @@ async function marketplaceRoutes(app) {
8218
8461
  logs.push("Integrity hash recorded");
8219
8462
  }
8220
8463
  installInProgress.delete(slug);
8221
- daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult });
8464
+ const depsWarnings = depsSuccess ? void 0 : logs.filter((l) => l.startsWith("Dependency"));
8465
+ daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult, depsWarnings });
8222
8466
  logs.push("Installation complete");
8223
- return { success: true, name: slug, analysis: analysisResult, logs };
8467
+ return { success: true, name: slug, analysis: analysisResult, logs, depsSuccess };
8224
8468
  } catch (err) {
8225
8469
  try {
8226
- if (skillDir && fs15.existsSync(skillDir)) {
8227
- fs15.rmSync(skillDir, { recursive: true, force: true });
8470
+ if (skillDir && fs16.existsSync(skillDir)) {
8471
+ fs16.rmSync(skillDir, { recursive: true, force: true });
8228
8472
  }
8229
8473
  removeFromApprovedList(slug);
8230
8474
  } catch {
@@ -8233,7 +8477,7 @@ async function marketplaceRoutes(app) {
8233
8477
  installInProgress.delete(slug);
8234
8478
  daemonEvents.broadcast("skills:install_failed", { name: slug, error: errorMsg });
8235
8479
  console.error("[Marketplace] Install failed:", errorMsg);
8236
- return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`] };
8480
+ return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`], depsSuccess: void 0 };
8237
8481
  } finally {
8238
8482
  installInProgress.delete(slug);
8239
8483
  }
@@ -8257,17 +8501,17 @@ async function marketplaceRoutes(app) {
8257
8501
  }
8258
8502
 
8259
8503
  // libs/shield-daemon/src/routes/skills.ts
8260
- function findSkillMdRecursive(dir, depth = 0) {
8504
+ function findSkillMdRecursive2(dir, depth = 0) {
8261
8505
  if (depth > 3) return null;
8262
8506
  try {
8263
8507
  for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
8264
- const candidate = path16.join(dir, name);
8265
- if (fs16.existsSync(candidate)) return candidate;
8508
+ const candidate = path17.join(dir, name);
8509
+ if (fs17.existsSync(candidate)) return candidate;
8266
8510
  }
8267
- const entries = fs16.readdirSync(dir, { withFileTypes: true });
8511
+ const entries = fs17.readdirSync(dir, { withFileTypes: true });
8268
8512
  for (const entry of entries) {
8269
8513
  if (!entry.isDirectory()) continue;
8270
- const found = findSkillMdRecursive(path16.join(dir, entry.name), depth + 1);
8514
+ const found = findSkillMdRecursive2(path17.join(dir, entry.name), depth + 1);
8271
8515
  if (found) return found;
8272
8516
  }
8273
8517
  } catch {
@@ -8276,10 +8520,10 @@ function findSkillMdRecursive(dir, depth = 0) {
8276
8520
  }
8277
8521
  function readSkillMetadata(skillDir) {
8278
8522
  try {
8279
- const mdPath = findSkillMdRecursive(skillDir);
8523
+ const mdPath = findSkillMdRecursive2(skillDir);
8280
8524
  if (!mdPath) return {};
8281
- const content = fs16.readFileSync(mdPath, "utf-8");
8282
- const parsed = parseSkillMd2(content);
8525
+ const content = fs17.readFileSync(mdPath, "utf-8");
8526
+ const parsed = parseSkillMd3(content);
8283
8527
  const meta = parsed?.metadata;
8284
8528
  return {
8285
8529
  description: meta?.description,
@@ -8337,7 +8581,7 @@ async function skillsRoutes(app) {
8337
8581
  let onDiskNames = [];
8338
8582
  if (skillsDir2) {
8339
8583
  try {
8340
- onDiskNames = fs16.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8584
+ onDiskNames = fs17.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8341
8585
  } catch {
8342
8586
  }
8343
8587
  }
@@ -8347,14 +8591,14 @@ async function skillsRoutes(app) {
8347
8591
  const data = [
8348
8592
  // Approved → active (with metadata from SKILL.md + cached analysis)
8349
8593
  ...approved.map((a) => {
8350
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, a.name)) : {};
8594
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, a.name)) : {};
8351
8595
  const cached = getCachedAnalysis2(a.name);
8352
8596
  const dlMeta = getDownloadedSkillMeta(a.name);
8353
8597
  return {
8354
8598
  name: a.name,
8355
8599
  source: "user",
8356
8600
  status: "active",
8357
- path: path16.join(skillsDir2 ?? "", a.name),
8601
+ path: path17.join(skillsDir2 ?? "", a.name),
8358
8602
  publisher: a.publisher,
8359
8603
  description: meta.description,
8360
8604
  version: meta.version,
@@ -8381,12 +8625,12 @@ async function skillsRoutes(app) {
8381
8625
  }),
8382
8626
  // Workspace: on disk but not approved or untrusted
8383
8627
  ...workspaceNames.map((name) => {
8384
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8628
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8385
8629
  return {
8386
8630
  name,
8387
8631
  source: "workspace",
8388
8632
  status: "workspace",
8389
- path: path16.join(skillsDir2 ?? "", name),
8633
+ path: path17.join(skillsDir2 ?? "", name),
8390
8634
  description: meta.description,
8391
8635
  version: meta.version,
8392
8636
  author: meta.author,
@@ -8417,7 +8661,7 @@ async function skillsRoutes(app) {
8417
8661
  let isWorkspace = false;
8418
8662
  if (!entry && !uEntry && skillsDir2) {
8419
8663
  try {
8420
- isWorkspace = fs16.existsSync(path16.join(skillsDir2, name));
8664
+ isWorkspace = fs17.existsSync(path17.join(skillsDir2, name));
8421
8665
  } catch {
8422
8666
  }
8423
8667
  }
@@ -8435,13 +8679,13 @@ async function skillsRoutes(app) {
8435
8679
  analysis: buildFullAnalysis(name, dlMeta?.analysis || getCachedAnalysis2(name))
8436
8680
  };
8437
8681
  } else if (entry) {
8438
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8682
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8439
8683
  const cached = getCachedAnalysis2(name);
8440
8684
  summary = {
8441
8685
  name,
8442
8686
  source: "user",
8443
8687
  status: "active",
8444
- path: skillsDir2 ? path16.join(skillsDir2, name) : "",
8688
+ path: skillsDir2 ? path17.join(skillsDir2, name) : "",
8445
8689
  publisher: entry.publisher,
8446
8690
  description: meta.description,
8447
8691
  version: meta.version,
@@ -8450,12 +8694,12 @@ async function skillsRoutes(app) {
8450
8694
  analysis: buildFullAnalysis(name, dlMeta?.analysis || cached)
8451
8695
  };
8452
8696
  } else if (isWorkspace) {
8453
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8697
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8454
8698
  summary = {
8455
8699
  name,
8456
8700
  source: "workspace",
8457
8701
  status: "workspace",
8458
- path: skillsDir2 ? path16.join(skillsDir2, name) : "",
8702
+ path: skillsDir2 ? path17.join(skillsDir2, name) : "",
8459
8703
  description: meta.description,
8460
8704
  version: meta.version,
8461
8705
  author: meta.author,
@@ -8480,11 +8724,11 @@ async function skillsRoutes(app) {
8480
8724
  return reply.code(404).send({ error: `Skill "${name}" not found` });
8481
8725
  }
8482
8726
  let content = "";
8483
- const dirToRead = summary.path || (skillsDir2 ? path16.join(skillsDir2, name) : "");
8727
+ const dirToRead = summary.path || (skillsDir2 ? path17.join(skillsDir2, name) : "");
8484
8728
  if (dirToRead) {
8485
8729
  try {
8486
- const mdPath = findSkillMdRecursive(dirToRead);
8487
- if (mdPath) content = fs16.readFileSync(mdPath, "utf-8");
8730
+ const mdPath = findSkillMdRecursive2(dirToRead);
8731
+ if (mdPath) content = fs17.readFileSync(mdPath, "utf-8");
8488
8732
  } catch {
8489
8733
  }
8490
8734
  }
@@ -8522,14 +8766,14 @@ async function skillsRoutes(app) {
8522
8766
  if (!content) {
8523
8767
  const skillsDir2 = getSkillsDir();
8524
8768
  const possibleDirs = [
8525
- skillsDir2 ? path16.join(skillsDir2, name) : null
8769
+ skillsDir2 ? path17.join(skillsDir2, name) : null
8526
8770
  ].filter(Boolean);
8527
8771
  for (const dir of possibleDirs) {
8528
8772
  try {
8529
- const mdPath = findSkillMdRecursive(dir);
8773
+ const mdPath = findSkillMdRecursive2(dir);
8530
8774
  if (mdPath) {
8531
- content = fs16.readFileSync(mdPath, "utf-8");
8532
- const parsed = parseSkillMd2(content);
8775
+ content = fs17.readFileSync(mdPath, "utf-8");
8776
+ const parsed = parseSkillMd3(content);
8533
8777
  if (parsed?.metadata && !metadata) {
8534
8778
  metadata = parsed.metadata;
8535
8779
  }
@@ -8544,7 +8788,7 @@ async function skillsRoutes(app) {
8544
8788
  const skillFile = localFiles.find((f) => /skill\.md/i.test(f.name));
8545
8789
  if (skillFile?.content) {
8546
8790
  content = skillFile.content;
8547
- const parsed = parseSkillMd2(content);
8791
+ const parsed = parseSkillMd3(content);
8548
8792
  if (parsed?.metadata && !metadata) {
8549
8793
  metadata = parsed.metadata;
8550
8794
  }
@@ -8559,7 +8803,7 @@ async function skillsRoutes(app) {
8559
8803
  const skillFile = freshFiles.find((f) => /skill\.md/i.test(f.name));
8560
8804
  if (skillFile?.content) {
8561
8805
  content = skillFile.content;
8562
- const parsed = parseSkillMd2(content);
8806
+ const parsed = parseSkillMd3(content);
8563
8807
  if (parsed?.metadata && !metadata) {
8564
8808
  metadata = parsed.metadata;
8565
8809
  }
@@ -8713,10 +8957,10 @@ async function skillsRoutes(app) {
8713
8957
  return reply.code(500).send({ error: "Skills directory not configured" });
8714
8958
  }
8715
8959
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8716
- const binDir = path16.join(agentHome, "bin");
8960
+ const binDir = path17.join(agentHome, "bin");
8717
8961
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8718
- const skillDir = path16.join(skillsDir2, name);
8719
- const isInstalled = fs16.existsSync(skillDir);
8962
+ const skillDir = path17.join(skillsDir2, name);
8963
+ const isInstalled = fs17.existsSync(skillDir);
8720
8964
  if (isInstalled) {
8721
8965
  try {
8722
8966
  const brokerAvailable = await isBrokerAvailable();
@@ -8726,7 +8970,7 @@ async function skillsRoutes(app) {
8726
8970
  agentHome
8727
8971
  });
8728
8972
  } else {
8729
- fs16.rmSync(skillDir, { recursive: true, force: true });
8973
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8730
8974
  removeSkillWrapper(name, binDir);
8731
8975
  }
8732
8976
  removeSkillEntry(name);
@@ -8771,11 +9015,11 @@ async function skillsRoutes(app) {
8771
9015
  }
8772
9016
  }
8773
9017
  } else {
8774
- fs16.mkdirSync(skillDir, { recursive: true });
9018
+ fs17.mkdirSync(skillDir, { recursive: true });
8775
9019
  for (const file of files) {
8776
- const filePath = path16.join(skillDir, file.name);
8777
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8778
- fs16.writeFileSync(filePath, file.content, "utf-8");
9020
+ const filePath = path17.join(skillDir, file.name);
9021
+ fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
9022
+ fs17.writeFileSync(filePath, file.content, "utf-8");
8779
9023
  }
8780
9024
  try {
8781
9025
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8793,8 +9037,8 @@ async function skillsRoutes(app) {
8793
9037
  return reply.send({ success: true, action: "enabled", name });
8794
9038
  } catch (err) {
8795
9039
  try {
8796
- if (fs16.existsSync(skillDir)) {
8797
- fs16.rmSync(skillDir, { recursive: true, force: true });
9040
+ if (fs17.existsSync(skillDir)) {
9041
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8798
9042
  }
8799
9043
  removeFromApprovedList(name);
8800
9044
  } catch {
@@ -8820,7 +9064,7 @@ async function skillsRoutes(app) {
8820
9064
  const skillMdFile = files.find((f) => f.name === "SKILL.md");
8821
9065
  let metadata;
8822
9066
  if (skillMdFile) {
8823
- const parsed = parseSkillMd2(skillMdFile.content);
9067
+ const parsed = parseSkillMd3(skillMdFile.content);
8824
9068
  metadata = parsed?.metadata;
8825
9069
  }
8826
9070
  const analysis = analyzeSkill(name, combinedContent, metadata);
@@ -8832,16 +9076,18 @@ async function skillsRoutes(app) {
8832
9076
  return reply.code(500).send({ error: "Skills directory not configured" });
8833
9077
  }
8834
9078
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8835
- const binDir = path16.join(agentHome, "bin");
9079
+ const agentUsername = path17.basename(agentHome);
9080
+ const binDir = path17.join(agentHome, "bin");
8836
9081
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8837
- const skillDir = path16.join(skillsDir2, name);
9082
+ const skillDir = path17.join(skillsDir2, name);
8838
9083
  try {
8839
9084
  addToApprovedList(name, publisher);
8840
- fs16.mkdirSync(skillDir, { recursive: true });
9085
+ sudoMkdir(skillDir, agentUsername);
8841
9086
  for (const file of files) {
8842
- const filePath = path16.join(skillDir, file.name);
8843
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8844
- fs16.writeFileSync(filePath, file.content, "utf-8");
9087
+ const filePath = path17.join(skillDir, file.name);
9088
+ sudoMkdir(path17.dirname(filePath), agentUsername);
9089
+ const content = /SKILL\.md$/i.test(file.name) ? stripEnvFromSkillMd2(file.content) : file.content;
9090
+ sudoWriteFile(filePath, content, agentUsername);
8845
9091
  }
8846
9092
  try {
8847
9093
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8855,8 +9101,8 @@ async function skillsRoutes(app) {
8855
9101
  return reply.send({ success: true, name, analysis });
8856
9102
  } catch (err) {
8857
9103
  try {
8858
- if (fs16.existsSync(skillDir)) {
8859
- fs16.rmSync(skillDir, { recursive: true, force: true });
9104
+ if (fs17.existsSync(skillDir)) {
9105
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8860
9106
  }
8861
9107
  removeFromApprovedList(name);
8862
9108
  } catch {
@@ -8890,9 +9136,9 @@ async function skillsRoutes(app) {
8890
9136
  }
8891
9137
  try {
8892
9138
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8893
- const binDir = path16.join(agentHome, "bin");
9139
+ const binDir = path17.join(agentHome, "bin");
8894
9140
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8895
- const skillDir = path16.join(getSkillsDir(), name);
9141
+ const skillDir = path17.join(getSkillsDir(), name);
8896
9142
  addToApprovedList(name, meta.author);
8897
9143
  const brokerAvailable = await isBrokerAvailable();
8898
9144
  if (brokerAvailable) {
@@ -8910,11 +9156,11 @@ async function skillsRoutes(app) {
8910
9156
  }
8911
9157
  }
8912
9158
  } else {
8913
- fs16.mkdirSync(skillDir, { recursive: true });
9159
+ fs17.mkdirSync(skillDir, { recursive: true });
8914
9160
  for (const file of files) {
8915
- const filePath = path16.join(skillDir, file.name);
8916
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8917
- fs16.writeFileSync(filePath, file.content, "utf-8");
9161
+ const filePath = path17.join(skillDir, file.name);
9162
+ fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
9163
+ fs17.writeFileSync(filePath, file.content, "utf-8");
8918
9164
  }
8919
9165
  try {
8920
9166
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8926,7 +9172,7 @@ async function skillsRoutes(app) {
8926
9172
  addSkillEntry(name);
8927
9173
  addSkillPolicy(name);
8928
9174
  syncOpenClawFromPolicies(loadConfig().policies);
8929
- const unblockHash = computeSkillHash(path16.join(getSkillsDir(), name));
9175
+ const unblockHash = computeSkillHash(path17.join(getSkillsDir(), name));
8930
9176
  if (unblockHash) updateApprovedHash(name, unblockHash);
8931
9177
  console.log(`[Skills] Unblocked and installed skill: ${name}`);
8932
9178
  return reply.send({ success: true, message: `Skill "${name}" approved and installed` });
@@ -8957,7 +9203,7 @@ async function skillsRoutes(app) {
8957
9203
  const skillMdFile = files.find((f) => /skill\.md/i.test(f.name));
8958
9204
  if (skillMdFile) {
8959
9205
  try {
8960
- const parsed = parseSkillMd2(skillMdFile.content);
9206
+ const parsed = parseSkillMd3(skillMdFile.content);
8961
9207
  if (parsed?.metadata) {
8962
9208
  parsedDescription = parsedDescription || parsed.metadata.description;
8963
9209
  parsedVersion = parsedVersion || parsed.metadata.version;
@@ -8986,10 +9232,10 @@ async function skillsRoutes(app) {
8986
9232
 
8987
9233
  // libs/shield-daemon/src/routes/exec.ts
8988
9234
  init_paths();
8989
- import * as fs17 from "node:fs";
8990
- import * as path17 from "node:path";
9235
+ import * as fs18 from "node:fs";
9236
+ import * as path18 from "node:path";
8991
9237
  function getAllowedCommandsPath2() {
8992
- return path17.join(getSystemConfigDir(), "allowed-commands.json");
9238
+ return path18.join(getSystemConfigDir(), "allowed-commands.json");
8993
9239
  }
8994
9240
  var BIN_DIRS = [
8995
9241
  "/usr/bin",
@@ -9002,22 +9248,22 @@ var binCache = null;
9002
9248
  var BIN_CACHE_TTL = 6e4;
9003
9249
  var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
9004
9250
  function loadConfig2() {
9005
- if (!fs17.existsSync(getAllowedCommandsPath2())) {
9251
+ if (!fs18.existsSync(getAllowedCommandsPath2())) {
9006
9252
  return { version: "1.0.0", commands: [] };
9007
9253
  }
9008
9254
  try {
9009
- const content = fs17.readFileSync(getAllowedCommandsPath2(), "utf-8");
9255
+ const content = fs18.readFileSync(getAllowedCommandsPath2(), "utf-8");
9010
9256
  return JSON.parse(content);
9011
9257
  } catch {
9012
9258
  return { version: "1.0.0", commands: [] };
9013
9259
  }
9014
9260
  }
9015
9261
  function saveConfig2(config) {
9016
- const dir = path17.dirname(getAllowedCommandsPath2());
9017
- if (!fs17.existsSync(dir)) {
9018
- fs17.mkdirSync(dir, { recursive: true });
9262
+ const dir = path18.dirname(getAllowedCommandsPath2());
9263
+ if (!fs18.existsSync(dir)) {
9264
+ fs18.mkdirSync(dir, { recursive: true });
9019
9265
  }
9020
- fs17.writeFileSync(getAllowedCommandsPath2(), JSON.stringify(config, null, 2) + "\n", "utf-8");
9266
+ fs18.writeFileSync(getAllowedCommandsPath2(), JSON.stringify(config, null, 2) + "\n", "utf-8");
9021
9267
  }
9022
9268
  function scanSystemBins() {
9023
9269
  const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
@@ -9026,13 +9272,13 @@ function scanSystemBins() {
9026
9272
  const results = [];
9027
9273
  for (const dir of allDirs) {
9028
9274
  try {
9029
- if (!fs17.existsSync(dir)) continue;
9030
- const entries = fs17.readdirSync(dir);
9275
+ if (!fs18.existsSync(dir)) continue;
9276
+ const entries = fs18.readdirSync(dir);
9031
9277
  for (const entry of entries) {
9032
9278
  if (seen.has(entry)) continue;
9033
- const fullPath = path17.join(dir, entry);
9279
+ const fullPath = path18.join(dir, entry);
9034
9280
  try {
9035
- const stat = fs17.statSync(fullPath);
9281
+ const stat = fs18.statSync(fullPath);
9036
9282
  if (stat.isFile() && (stat.mode & 73) !== 0) {
9037
9283
  seen.add(entry);
9038
9284
  results.push({ name: entry, path: fullPath });
@@ -9085,7 +9331,7 @@ async function execRoutes(app) {
9085
9331
  };
9086
9332
  }
9087
9333
  for (const p of paths) {
9088
- if (!path17.isAbsolute(p)) {
9334
+ if (!path18.isAbsolute(p)) {
9089
9335
  return {
9090
9336
  success: false,
9091
9337
  error: {
@@ -9581,18 +9827,18 @@ async function secretsRoutes(app) {
9581
9827
  }
9582
9828
 
9583
9829
  // libs/shield-daemon/src/routes/fs.ts
9584
- import * as fs18 from "node:fs";
9585
- import * as path18 from "node:path";
9830
+ import * as fs19 from "node:fs";
9831
+ import * as path19 from "node:path";
9586
9832
  import * as os6 from "node:os";
9587
9833
  var MAX_ENTRIES = 200;
9588
9834
  async function fsRoutes(app) {
9589
9835
  app.get("/fs/browse", async (request2) => {
9590
9836
  const dirPath = request2.query.path || os6.homedir();
9591
9837
  const showHidden = request2.query.showHidden === "true";
9592
- const resolvedPath = path18.resolve(dirPath);
9838
+ const resolvedPath = path19.resolve(dirPath);
9593
9839
  let dirents;
9594
9840
  try {
9595
- dirents = fs18.readdirSync(resolvedPath, { withFileTypes: true });
9841
+ dirents = fs19.readdirSync(resolvedPath, { withFileTypes: true });
9596
9842
  } catch {
9597
9843
  return { success: true, data: { entries: [] } };
9598
9844
  }
@@ -9601,7 +9847,7 @@ async function fsRoutes(app) {
9601
9847
  if (!showHidden && dirent.name.startsWith(".")) continue;
9602
9848
  entries.push({
9603
9849
  name: dirent.name,
9604
- path: path18.join(resolvedPath, dirent.name),
9850
+ path: path19.join(resolvedPath, dirent.name),
9605
9851
  type: dirent.isDirectory() ? "directory" : "file"
9606
9852
  });
9607
9853
  if (entries.length >= MAX_ENTRIES) break;
@@ -9615,8 +9861,8 @@ async function fsRoutes(app) {
9615
9861
  }
9616
9862
 
9617
9863
  // libs/shield-daemon/src/services/activity-log.ts
9618
- import * as fs19 from "node:fs";
9619
- import * as path19 from "node:path";
9864
+ import * as fs20 from "node:fs";
9865
+ import * as path20 from "node:path";
9620
9866
  init_paths();
9621
9867
  var ACTIVITY_FILE = "activity.jsonl";
9622
9868
  var MAX_SIZE_BYTES = 100 * 1024 * 1024;
@@ -9634,12 +9880,12 @@ var ActivityLog = class {
9634
9880
  writeCount = 0;
9635
9881
  unsubscribe;
9636
9882
  constructor() {
9637
- this.filePath = path19.join(getConfigDir(), ACTIVITY_FILE);
9883
+ this.filePath = path20.join(getConfigDir(), ACTIVITY_FILE);
9638
9884
  }
9639
9885
  /** Read historical events from the JSONL file, newest first */
9640
9886
  getHistory(limit = 500) {
9641
- if (!fs19.existsSync(this.filePath)) return [];
9642
- const content = fs19.readFileSync(this.filePath, "utf-8");
9887
+ if (!fs20.existsSync(this.filePath)) return [];
9888
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9643
9889
  const lines = content.split("\n").filter(Boolean);
9644
9890
  const events = [];
9645
9891
  for (const line of lines) {
@@ -9664,7 +9910,7 @@ var ActivityLog = class {
9664
9910
  }
9665
9911
  append(event) {
9666
9912
  const line = JSON.stringify(event) + "\n";
9667
- fs19.appendFileSync(this.filePath, line, "utf-8");
9913
+ fs20.appendFileSync(this.filePath, line, "utf-8");
9668
9914
  this.writeCount++;
9669
9915
  if (this.writeCount % PRUNE_INTERVAL === 0) {
9670
9916
  this.rotate();
@@ -9672,7 +9918,7 @@ var ActivityLog = class {
9672
9918
  }
9673
9919
  rotate() {
9674
9920
  try {
9675
- const stat = fs19.statSync(this.filePath);
9921
+ const stat = fs20.statSync(this.filePath);
9676
9922
  if (stat.size > MAX_SIZE_BYTES) {
9677
9923
  this.truncateBySize();
9678
9924
  }
@@ -9682,15 +9928,15 @@ var ActivityLog = class {
9682
9928
  }
9683
9929
  /** Keep newest half of lines when file exceeds size limit */
9684
9930
  truncateBySize() {
9685
- const content = fs19.readFileSync(this.filePath, "utf-8");
9931
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9686
9932
  const lines = content.split("\n").filter(Boolean);
9687
9933
  const keep = lines.slice(Math.floor(lines.length / 2));
9688
- fs19.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
9934
+ fs20.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
9689
9935
  }
9690
9936
  /** Remove entries older than 24 hours */
9691
9937
  pruneOldEntries() {
9692
- if (!fs19.existsSync(this.filePath)) return;
9693
- const content = fs19.readFileSync(this.filePath, "utf-8");
9938
+ if (!fs20.existsSync(this.filePath)) return;
9939
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9694
9940
  const lines = content.split("\n").filter(Boolean);
9695
9941
  const cutoff = Date.now() - MAX_AGE_MS;
9696
9942
  const kept = lines.filter((line) => {
@@ -9702,7 +9948,7 @@ var ActivityLog = class {
9702
9948
  }
9703
9949
  });
9704
9950
  if (kept.length < lines.length) {
9705
- fs19.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
9951
+ fs20.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
9706
9952
  }
9707
9953
  }
9708
9954
  };
@@ -9843,11 +10089,11 @@ function normalizeUrlTarget(url) {
9843
10089
  const trimmed = url.trim();
9844
10090
  try {
9845
10091
  const parsed = new URL(trimmed);
9846
- let path21 = parsed.pathname;
9847
- if (path21.length > 1) {
9848
- path21 = path21.replace(/\/+$/, "");
10092
+ let path24 = parsed.pathname;
10093
+ if (path24.length > 1) {
10094
+ path24 = path24.replace(/\/+$/, "");
9849
10095
  }
9850
- return `${parsed.protocol}//${parsed.host}${path21}${parsed.search}`;
10096
+ return `${parsed.protocol}//${parsed.host}${path24}${parsed.search}`;
9851
10097
  } catch {
9852
10098
  return trimmed.replace(/\/+$/, "");
9853
10099
  }
@@ -10133,8 +10379,8 @@ function matchCommandPattern(pattern, target) {
10133
10379
  const firstSpace = target.indexOf(" ");
10134
10380
  const cmd = firstSpace >= 0 ? target.slice(0, firstSpace) : target;
10135
10381
  if (cmd.startsWith("/")) {
10136
- const basename3 = cmd.split("/").pop() || cmd;
10137
- normalizedTarget = firstSpace >= 0 ? basename3 + target.slice(firstSpace) : basename3;
10382
+ const basename6 = cmd.split("/").pop() || cmd;
10383
+ normalizedTarget = firstSpace >= 0 ? basename6 + target.slice(firstSpace) : basename6;
10138
10384
  }
10139
10385
  if (trimmed.endsWith(":*")) {
10140
10386
  const prefix = trimmed.slice(0, -2);
@@ -10185,8 +10431,8 @@ function determineNetworkAccess(_config, matchedPolicy, target) {
10185
10431
  if (matchedPolicy?.networkAccess) return matchedPolicy.networkAccess;
10186
10432
  const cleanTarget = target.startsWith("fork:") ? target.slice(5) : target;
10187
10433
  const cmdPart = cleanTarget.split(" ")[0] || "";
10188
- const basename3 = cmdPart.includes("/") ? cmdPart.split("/").pop() : cmdPart;
10189
- if (!NETWORK_COMMANDS.has(basename3.toLowerCase())) return "none";
10434
+ const basename6 = cmdPart.includes("/") ? cmdPart.split("/").pop() : cmdPart;
10435
+ if (!NETWORK_COMMANDS.has(basename6.toLowerCase())) return "none";
10190
10436
  return "proxy";
10191
10437
  }
10192
10438
  async function buildSandboxConfig(config, matchedPolicy, _context, target) {
@@ -10487,22 +10733,22 @@ async function registerRoutes(app) {
10487
10733
  }
10488
10734
 
10489
10735
  // libs/shield-daemon/src/static.ts
10490
- import * as fs20 from "node:fs";
10491
- import * as path20 from "node:path";
10736
+ import * as fs21 from "node:fs";
10737
+ import * as path21 from "node:path";
10492
10738
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10493
10739
  var __filename = fileURLToPath2(import.meta.url);
10494
- var __dirname = path20.dirname(__filename);
10740
+ var __dirname = path21.dirname(__filename);
10495
10741
  function getUiAssetsPath() {
10496
- const pkgRootPath = path20.join(__dirname, "..", "ui-assets");
10497
- if (fs20.existsSync(pkgRootPath)) {
10742
+ const pkgRootPath = path21.join(__dirname, "..", "ui-assets");
10743
+ if (fs21.existsSync(pkgRootPath)) {
10498
10744
  return pkgRootPath;
10499
10745
  }
10500
- const bundledPath = path20.join(__dirname, "ui-assets");
10501
- if (fs20.existsSync(bundledPath)) {
10746
+ const bundledPath = path21.join(__dirname, "ui-assets");
10747
+ if (fs21.existsSync(bundledPath)) {
10502
10748
  return bundledPath;
10503
10749
  }
10504
- const devPath = path20.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
10505
- if (fs20.existsSync(devPath)) {
10750
+ const devPath = path21.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
10751
+ if (fs21.existsSync(devPath)) {
10506
10752
  return devPath;
10507
10753
  }
10508
10754
  return null;
@@ -10592,8 +10838,8 @@ async function startServer(config) {
10592
10838
  startSecurityWatcher(1e4);
10593
10839
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
10594
10840
  const skillsDir2 = `${agentHome}/.openclaw/skills`;
10595
- if (!fs21.existsSync(skillsDir2)) {
10596
- fs21.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
10841
+ if (!fs23.existsSync(skillsDir2)) {
10842
+ fs23.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
10597
10843
  console.log(`[Daemon] Created skills directory: ${skillsDir2}`);
10598
10844
  }
10599
10845
  startSkillsWatcher(skillsDir2, {