@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/main.js CHANGED
@@ -1044,10 +1044,7 @@ PLISTEOF`);
1044
1044
  }
1045
1045
  async function startOpenClawServices() {
1046
1046
  try {
1047
- try {
1048
- await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
1049
- } catch {
1050
- }
1047
+ await execAsync3(`sudo launchctl kickstart system/${OPENCLAW_GATEWAY_LABEL}`);
1051
1048
  return {
1052
1049
  success: true,
1053
1050
  message: "OpenClaw gateway started"
@@ -1062,10 +1059,7 @@ async function startOpenClawServices() {
1062
1059
  }
1063
1060
  async function stopOpenClawServices() {
1064
1061
  try {
1065
- try {
1066
- await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
1067
- } catch {
1068
- }
1062
+ await execAsync3(`sudo launchctl kill SIGTERM system/${OPENCLAW_GATEWAY_LABEL}`);
1069
1063
  return {
1070
1064
  success: true,
1071
1065
  message: "OpenClaw gateway stopped"
@@ -1080,10 +1074,7 @@ async function stopOpenClawServices() {
1080
1074
  }
1081
1075
  async function restartOpenClawServices() {
1082
1076
  try {
1083
- try {
1084
- await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
1085
- } catch {
1086
- }
1077
+ await execAsync3(`sudo launchctl kickstart -k system/${OPENCLAW_GATEWAY_LABEL}`);
1087
1078
  return {
1088
1079
  success: true,
1089
1080
  message: "OpenClaw gateway restarted"
@@ -1166,15 +1157,29 @@ function getOpenClawStatusSync() {
1166
1157
  async function getOpenClawDashboardUrl() {
1167
1158
  try {
1168
1159
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
1169
- const agentUsername = path22.basename(agentHome);
1170
- const { stdout, stderr } = await execAsync3(
1171
- `sudo -H -u ${agentUsername} ${OPENCLAW_LAUNCHER_PATH} dashboard`,
1172
- { timeout: 15e3, maxBuffer: 10 * 1024 * 1024 }
1173
- );
1174
- const url = stdout.trim();
1175
- if (!url) {
1176
- return { success: false, error: stderr.trim() || "No URL returned from openclaw dashboard" };
1160
+ const configPath = path22.join(agentHome, ".openclaw", "openclaw.json");
1161
+ let raw;
1162
+ try {
1163
+ raw = await fs32.readFile(configPath, "utf-8");
1164
+ } catch (err) {
1165
+ if (err.code === "EACCES") {
1166
+ const agentUsername = path22.basename(agentHome);
1167
+ const { stdout } = await execAsync3(
1168
+ `sudo -H -u ${agentUsername} cat "${configPath}"`,
1169
+ { cwd: "/" }
1170
+ );
1171
+ raw = stdout;
1172
+ } else {
1173
+ return { success: false, error: `Cannot read openclaw.json: ${err.message}` };
1174
+ }
1175
+ }
1176
+ const config = JSON.parse(raw);
1177
+ const port = config.gateway?.port;
1178
+ const token = config.gateway?.auth?.token;
1179
+ if (!port || !token) {
1180
+ return { success: false, error: "Gateway port or auth token not found in openclaw.json" };
1177
1181
  }
1182
+ const url = `http://127.0.0.1:${port}/?token=${token}`;
1178
1183
  return { success: true, url };
1179
1184
  } catch (error) {
1180
1185
  return { success: false, error: `Failed to get dashboard URL: ${error.message}` };
@@ -1761,7 +1766,7 @@ var init_secret_sync = __esm({
1761
1766
  });
1762
1767
 
1763
1768
  // libs/shield-daemon/src/main.ts
1764
- import * as fs23 from "node:fs";
1769
+ import * as fs24 from "node:fs";
1765
1770
 
1766
1771
  // libs/shield-daemon/src/config/index.ts
1767
1772
  init_paths();
@@ -1769,7 +1774,7 @@ init_defaults();
1769
1774
  init_loader();
1770
1775
 
1771
1776
  // libs/shield-daemon/src/server.ts
1772
- import * as fs21 from "node:fs";
1777
+ import * as fs23 from "node:fs";
1773
1778
  import Fastify from "fastify";
1774
1779
  import cors from "@fastify/cors";
1775
1780
  import fastifyStatic from "@fastify/static";
@@ -2141,11 +2146,24 @@ function getOpenClawConfigPath() {
2141
2146
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
2142
2147
  return path8.join(agentHome, ".openclaw", "openclaw.json");
2143
2148
  }
2144
- function readConfig() {
2149
+ function readOpenClawConfig() {
2145
2150
  const configPath = getOpenClawConfigPath();
2146
2151
  try {
2147
2152
  if (fs8.existsSync(configPath)) {
2148
- return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
2153
+ try {
2154
+ return JSON.parse(fs8.readFileSync(configPath, "utf-8"));
2155
+ } catch (err) {
2156
+ if (err.code === "EACCES") {
2157
+ const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
2158
+ const agentUsername = path8.basename(agentHome);
2159
+ const raw = execSync6(
2160
+ `sudo -H -u ${agentUsername} cat "${configPath}"`,
2161
+ { encoding: "utf-8", cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
2162
+ );
2163
+ return JSON.parse(raw);
2164
+ }
2165
+ throw err;
2166
+ }
2149
2167
  }
2150
2168
  } catch {
2151
2169
  console.warn("[OpenClawConfig] Failed to read openclaw.json, starting fresh");
@@ -2163,7 +2181,7 @@ function writeConfig(config) {
2163
2181
  const agentUsername = path8.basename(agentHome);
2164
2182
  execSync6(
2165
2183
  `sudo -H -u ${agentUsername} tee "${configPath}" > /dev/null`,
2166
- { input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"] }
2184
+ { input: JSON.stringify(config, null, 2), stdio: ["pipe", "pipe", "pipe"], cwd: "/" }
2167
2185
  );
2168
2186
  } else {
2169
2187
  throw err;
@@ -2171,27 +2189,30 @@ function writeConfig(config) {
2171
2189
  }
2172
2190
  }
2173
2191
  function addSkillEntry(slug) {
2174
- const config = readConfig();
2192
+ const config = readOpenClawConfig();
2175
2193
  if (!config.skills) {
2176
2194
  config.skills = {};
2177
2195
  }
2178
2196
  if (!config.skills.entries) {
2179
2197
  config.skills.entries = {};
2180
2198
  }
2181
- config.skills.entries[slug] = { enabled: true };
2199
+ const existing = config.skills.entries[slug] ?? {};
2200
+ config.skills.entries[slug] = { ...existing, enabled: true };
2182
2201
  writeConfig(config);
2183
2202
  console.log(`[OpenClawConfig] Added skill entry: ${slug}`);
2184
2203
  }
2185
2204
  function removeSkillEntry(slug) {
2186
- const config = readConfig();
2205
+ const config = readOpenClawConfig();
2187
2206
  if (config.skills?.entries?.[slug]) {
2188
- delete config.skills.entries[slug];
2207
+ const existing = config.skills.entries[slug];
2208
+ delete existing.env;
2209
+ config.skills.entries[slug] = { ...existing, enabled: false };
2189
2210
  writeConfig(config);
2190
- console.log(`[OpenClawConfig] Removed skill entry: ${slug}`);
2211
+ console.log(`[OpenClawConfig] Disabled skill entry: ${slug}`);
2191
2212
  }
2192
2213
  }
2193
2214
  function syncOpenClawFromPolicies(policies) {
2194
- const config = readConfig();
2215
+ const config = readOpenClawConfig();
2195
2216
  if (!config.skills) config.skills = {};
2196
2217
  const allowBundled = [];
2197
2218
  for (const p of policies) {
@@ -2439,35 +2460,35 @@ var ANALYSIS_TIMEOUT = 4 * 6e4;
2439
2460
  var SEARCH_CACHE_TTL = 6e4;
2440
2461
  var DETAIL_CACHE_TTL = 5 * 6e4;
2441
2462
  var SHORT_TIMEOUT = 1e4;
2442
- async function convexAction(path21, args, timeout) {
2463
+ async function convexAction(path24, args, timeout) {
2443
2464
  const res = await fetch(`${CONVEX_BASE}/api/action`, {
2444
2465
  method: "POST",
2445
2466
  signal: AbortSignal.timeout(timeout),
2446
2467
  headers: { "Content-Type": "application/json" },
2447
- body: JSON.stringify({ path: path21, args, format: "json" })
2468
+ body: JSON.stringify({ path: path24, args, format: "json" })
2448
2469
  });
2449
2470
  if (!res.ok) {
2450
- throw new Error(`Convex action ${path21} returned ${res.status}`);
2471
+ throw new Error(`Convex action ${path24} returned ${res.status}`);
2451
2472
  }
2452
2473
  const body = await res.json();
2453
2474
  if (body.status === "error") {
2454
- throw new Error(`Convex action ${path21}: ${body.errorMessage ?? "unknown error"}`);
2475
+ throw new Error(`Convex action ${path24}: ${body.errorMessage ?? "unknown error"}`);
2455
2476
  }
2456
2477
  return body.value;
2457
2478
  }
2458
- async function convexQuery(path21, args, timeout) {
2479
+ async function convexQuery(path24, args, timeout) {
2459
2480
  const res = await fetch(`${CONVEX_BASE}/api/query`, {
2460
2481
  method: "POST",
2461
2482
  signal: AbortSignal.timeout(timeout),
2462
2483
  headers: { "Content-Type": "application/json" },
2463
- body: JSON.stringify({ path: path21, args, format: "json" })
2484
+ body: JSON.stringify({ path: path24, args, format: "json" })
2464
2485
  });
2465
2486
  if (!res.ok) {
2466
- throw new Error(`Convex query ${path21} returned ${res.status}`);
2487
+ throw new Error(`Convex query ${path24} returned ${res.status}`);
2467
2488
  }
2468
2489
  const body = await res.json();
2469
2490
  if (body.status === "error") {
2470
- throw new Error(`Convex query ${path21}: ${body.errorMessage ?? "unknown error"}`);
2491
+ throw new Error(`Convex query ${path24}: ${body.errorMessage ?? "unknown error"}`);
2471
2492
  }
2472
2493
  return body.value;
2473
2494
  }
@@ -2669,9 +2690,9 @@ function inlineImagesInMarkdown(markdown, files) {
2669
2690
  const mime = isImageExt(file.name);
2670
2691
  if (mime && file.content.startsWith("data:")) {
2671
2692
  imageMap.set(file.name, file.content);
2672
- const basename3 = file.name.split("/").pop() ?? "";
2673
- if (basename3 && !imageMap.has(basename3)) {
2674
- imageMap.set(basename3, file.content);
2693
+ const basename6 = file.name.split("/").pop() ?? "";
2694
+ if (basename6 && !imageMap.has(basename6)) {
2695
+ imageMap.set(basename6, file.content);
2675
2696
  }
2676
2697
  }
2677
2698
  }
@@ -3197,10 +3218,10 @@ function emitSecurityWarning(warning) {
3197
3218
  function emitSecurityCritical(issue) {
3198
3219
  daemonEvents.broadcast("security:critical", { message: issue });
3199
3220
  }
3200
- function emitApiRequest(method, path21, statusCode, duration, requestBody, responseBody) {
3221
+ function emitApiRequest(method, path24, statusCode, duration, requestBody, responseBody) {
3201
3222
  daemonEvents.broadcast("api:request", {
3202
3223
  method,
3203
- path: path21,
3224
+ path: path24,
3204
3225
  statusCode,
3205
3226
  duration,
3206
3227
  ...requestBody !== void 0 && { requestBody },
@@ -3455,26 +3476,16 @@ function scanSkills() {
3455
3476
  }
3456
3477
  function detectOpenClawMismatches() {
3457
3478
  try {
3458
- const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
3459
- const configPath = path11.join(agentHome, ".openclaw", "openclaw.json");
3460
- if (!fs11.existsSync(configPath)) return;
3461
- const raw = fs11.readFileSync(configPath, "utf-8");
3462
- const config = JSON.parse(raw);
3463
- if (!config.skills?.entries) return;
3464
3479
  const approved = loadApprovedSkills();
3465
3480
  const approvedNames = new Set(approved.map((a) => a.name));
3466
- const entries = Object.keys(config.skills.entries);
3467
- let changed = false;
3468
- for (const name of entries) {
3481
+ const config = readOpenClawConfig();
3482
+ if (!config.skills?.entries) return;
3483
+ for (const name of Object.keys(config.skills.entries)) {
3469
3484
  if (!approvedNames.has(name)) {
3470
- delete config.skills.entries[name];
3471
- changed = true;
3472
- console.log(`[SkillsWatcher] Removed stale openclaw.json entry: ${name}`);
3485
+ removeSkillEntry(name);
3486
+ console.log(`[SkillsWatcher] Disabled stale openclaw.json entry: ${name}`);
3473
3487
  }
3474
3488
  }
3475
- if (changed) {
3476
- fs11.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
3477
- }
3478
3489
  } catch {
3479
3490
  }
3480
3491
  }
@@ -4030,16 +4041,16 @@ var PROTECTED_ROUTES = [
4030
4041
  { method: "POST", path: "/api/skills/install" },
4031
4042
  { method: "GET", path: "/api/openclaw/dashboard-url" }
4032
4043
  ];
4033
- function isProtectedRoute(method, path21) {
4044
+ function isProtectedRoute(method, path24) {
4034
4045
  return PROTECTED_ROUTES.some(
4035
- (route) => route.method === method && path21.startsWith(route.path)
4046
+ (route) => route.method === method && path24.startsWith(route.path)
4036
4047
  );
4037
4048
  }
4038
4049
  function createAuthHook() {
4039
4050
  return async (request2, reply) => {
4040
4051
  const method = request2.method;
4041
- const path21 = request2.url.split("?")[0];
4042
- if (!isProtectedRoute(method, path21)) {
4052
+ const path24 = request2.url.split("?")[0];
4053
+ if (!isProtectedRoute(method, path24)) {
4043
4054
  return;
4044
4055
  }
4045
4056
  if (!isAuthenticated(request2)) {
@@ -7538,19 +7549,50 @@ async function agencoRoutes(app) {
7538
7549
  }
7539
7550
 
7540
7551
  // libs/shield-daemon/src/routes/skills.ts
7541
- import * as fs16 from "node:fs";
7542
- import * as path16 from "node:path";
7552
+ import * as fs17 from "node:fs";
7553
+ import * as path17 from "node:path";
7543
7554
  import { execSync as execSync9 } from "node:child_process";
7544
- import { parseSkillMd as parseSkillMd2 } from "@agenshield/sandbox";
7555
+ import { parseSkillMd as parseSkillMd3, stripEnvFromSkillMd as stripEnvFromSkillMd2 } from "@agenshield/sandbox";
7545
7556
 
7546
7557
  // libs/shield-daemon/src/services/skill-lifecycle.ts
7547
7558
  import * as fs14 from "node:fs";
7548
7559
  import * as path14 from "node:path";
7549
7560
  import { execSync as execSync8 } from "node:child_process";
7550
- function createSkillWrapper(name, binDir) {
7551
- if (!fs14.existsSync(binDir)) {
7552
- fs14.mkdirSync(binDir, { recursive: true });
7561
+ function sudoMkdir(dir, agentUsername) {
7562
+ try {
7563
+ fs14.mkdirSync(dir, { recursive: true });
7564
+ } catch (err) {
7565
+ if (err.code === "EACCES") {
7566
+ execSync8(`sudo -H -u ${agentUsername} /bin/mkdir -p "${dir}"`, { cwd: "/", stdio: "pipe" });
7567
+ } else {
7568
+ throw err;
7569
+ }
7570
+ }
7571
+ }
7572
+ function sudoWriteFile(filePath, content, agentUsername, mode) {
7573
+ try {
7574
+ fs14.writeFileSync(filePath, content, { mode });
7575
+ } catch (err) {
7576
+ if (err.code === "EACCES") {
7577
+ execSync8(
7578
+ `sudo -H -u ${agentUsername} tee "${filePath}" > /dev/null`,
7579
+ { input: content, cwd: "/", stdio: ["pipe", "pipe", "pipe"] }
7580
+ );
7581
+ if (mode) {
7582
+ try {
7583
+ execSync8(`sudo -H -u ${agentUsername} chmod ${mode.toString(8)} "${filePath}"`, { cwd: "/", stdio: "pipe" });
7584
+ } catch {
7585
+ }
7586
+ }
7587
+ } else {
7588
+ throw err;
7589
+ }
7553
7590
  }
7591
+ }
7592
+ function createSkillWrapper(name, binDir) {
7593
+ const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
7594
+ const agentUsername = path14.basename(agentHome);
7595
+ sudoMkdir(binDir, agentUsername);
7554
7596
  const wrapperPath = path14.join(binDir, name);
7555
7597
  const wrapperContent = `#!/bin/bash
7556
7598
  # ${name} skill wrapper - policy-enforced execution
@@ -7558,7 +7600,7 @@ function createSkillWrapper(name, binDir) {
7558
7600
  if ! /bin/pwd > /dev/null 2>&1; then cd ~ 2>/dev/null || cd /; fi
7559
7601
  exec /opt/agenshield/bin/shield-client skill run "${name}" "$@"
7560
7602
  `;
7561
- fs14.writeFileSync(wrapperPath, wrapperContent, { mode: 493 });
7603
+ sudoWriteFile(wrapperPath, wrapperContent, agentUsername, 493);
7562
7604
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
7563
7605
  try {
7564
7606
  execSync8(`chown root:${socketGroup} "${wrapperPath}"`, { stdio: "pipe" });
@@ -7603,8 +7645,8 @@ function removeSkillPolicy(name) {
7603
7645
  }
7604
7646
 
7605
7647
  // libs/shield-daemon/src/routes/marketplace.ts
7606
- import * as fs15 from "node:fs";
7607
- import * as path15 from "node:path";
7648
+ import * as fs16 from "node:fs";
7649
+ import * as path16 from "node:path";
7608
7650
 
7609
7651
  // libs/shield-broker/dist/index.js
7610
7652
  import { exec as exec4 } from "node:child_process";
@@ -7880,6 +7922,175 @@ async function uninstallSkillViaBroker(slug, options = {}) {
7880
7922
  return result;
7881
7923
  }
7882
7924
 
7925
+ // libs/shield-daemon/src/routes/marketplace.ts
7926
+ import { stripEnvFromSkillMd } from "@agenshield/sandbox";
7927
+
7928
+ // libs/shield-daemon/src/services/skill-deps.ts
7929
+ import * as fs15 from "node:fs";
7930
+ import * as path15 from "node:path";
7931
+ import { parseSkillMd as parseSkillMd2, extractSkillInfo } from "@agenshield/sandbox";
7932
+ import { execWithProgress as execWithProgress3 } from "@agenshield/sandbox";
7933
+ var SUPPORTED_KINDS = /* @__PURE__ */ new Set(["brew", "npm", "pip"]);
7934
+ var SAFE_PACKAGE_RE = /^[a-zA-Z0-9@/_.\-]+$/;
7935
+ function findSkillMdRecursive(dir, depth = 0) {
7936
+ if (depth > 3) return null;
7937
+ try {
7938
+ for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
7939
+ const candidate = path15.join(dir, name);
7940
+ if (fs15.existsSync(candidate)) return candidate;
7941
+ }
7942
+ const entries = fs15.readdirSync(dir, { withFileTypes: true });
7943
+ for (const entry of entries) {
7944
+ if (!entry.isDirectory()) continue;
7945
+ const found = findSkillMdRecursive(path15.join(dir, entry.name), depth + 1);
7946
+ if (found) return found;
7947
+ }
7948
+ } catch {
7949
+ }
7950
+ return null;
7951
+ }
7952
+ async function executeSkillInstallSteps(options) {
7953
+ const { slug, skillDir, agentHome, agentUsername, onLog } = options;
7954
+ const installed = [];
7955
+ const errors = [];
7956
+ const skillMdPath = findSkillMdRecursive(skillDir);
7957
+ if (!skillMdPath) {
7958
+ return { success: true, installed, errors };
7959
+ }
7960
+ let content;
7961
+ try {
7962
+ content = fs15.readFileSync(skillMdPath, "utf-8");
7963
+ } catch {
7964
+ return { success: true, installed, errors };
7965
+ }
7966
+ const parsed = parseSkillMd2(content);
7967
+ if (!parsed) {
7968
+ return { success: true, installed, errors };
7969
+ }
7970
+ const info = extractSkillInfo(parsed.metadata);
7971
+ const installSteps = info.installSteps;
7972
+ if (!Array.isArray(installSteps) || installSteps.length === 0) {
7973
+ return { success: true, installed, errors };
7974
+ }
7975
+ onLog(`Found ${installSteps.length} dependency install step(s) for ${slug}`);
7976
+ for (const step of installSteps) {
7977
+ const kind = step.kind;
7978
+ const stepId = step.id || kind;
7979
+ if (!SUPPORTED_KINDS.has(kind)) {
7980
+ errors.push(`Unsupported install kind "${kind}" (step: ${stepId})`);
7981
+ continue;
7982
+ }
7983
+ try {
7984
+ switch (kind) {
7985
+ case "brew": {
7986
+ const formula = step.formula;
7987
+ if (!formula) {
7988
+ errors.push(`Brew step "${stepId}" missing formula`);
7989
+ break;
7990
+ }
7991
+ if (!SAFE_PACKAGE_RE.test(formula)) {
7992
+ errors.push(`Unsafe brew formula name: ${formula}`);
7993
+ break;
7994
+ }
7995
+ onLog(`Installing brew formula: ${formula}`);
7996
+ const brewCmd = [
7997
+ `export HOME="${agentHome}"`,
7998
+ `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:$PATH"`,
7999
+ `brew install ${formula}`
8000
+ ].join(" && ");
8001
+ await execWithProgress3(
8002
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${brewCmd}'`,
8003
+ onLog,
8004
+ { timeout: 12e4, cwd: "/" }
8005
+ );
8006
+ installed.push(formula);
8007
+ break;
8008
+ }
8009
+ case "npm": {
8010
+ const pkg = step["package"];
8011
+ if (!pkg) {
8012
+ errors.push(`npm step "${stepId}" missing package`);
8013
+ break;
8014
+ }
8015
+ if (!SAFE_PACKAGE_RE.test(pkg)) {
8016
+ errors.push(`Unsafe npm package name: ${pkg}`);
8017
+ break;
8018
+ }
8019
+ onLog(`Installing npm package: ${pkg}`);
8020
+ const npmCmd = [
8021
+ `export HOME="${agentHome}"`,
8022
+ `export PATH="${agentHome}/bin:$PATH"`,
8023
+ `source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
8024
+ `npm install -g ${pkg}`
8025
+ ].join(" && ");
8026
+ await execWithProgress3(
8027
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${npmCmd}'`,
8028
+ onLog,
8029
+ { timeout: 6e4, cwd: "/" }
8030
+ );
8031
+ installed.push(pkg);
8032
+ break;
8033
+ }
8034
+ case "pip": {
8035
+ const pkg = step["package"];
8036
+ if (!pkg) {
8037
+ errors.push(`pip step "${stepId}" missing package`);
8038
+ break;
8039
+ }
8040
+ if (!SAFE_PACKAGE_RE.test(pkg)) {
8041
+ errors.push(`Unsafe pip package name: ${pkg}`);
8042
+ break;
8043
+ }
8044
+ onLog(`Installing pip package: ${pkg}`);
8045
+ const pipCmd = [
8046
+ `export HOME="${agentHome}"`,
8047
+ `export PATH="${agentHome}/bin:$PATH"`,
8048
+ `pip install ${pkg}`
8049
+ ].join(" && ");
8050
+ await execWithProgress3(
8051
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${pipCmd}'`,
8052
+ onLog,
8053
+ { timeout: 6e4, cwd: "/" }
8054
+ );
8055
+ installed.push(pkg);
8056
+ break;
8057
+ }
8058
+ }
8059
+ } catch (err) {
8060
+ const msg = `Failed to install ${kind} dep (step: ${stepId}): ${err.message}`;
8061
+ onLog(msg);
8062
+ errors.push(msg);
8063
+ }
8064
+ }
8065
+ const requiredBins = info.bins;
8066
+ if (requiredBins.length > 0) {
8067
+ onLog(`Verifying required binaries: ${requiredBins.join(", ")}`);
8068
+ for (const bin of requiredBins) {
8069
+ try {
8070
+ const checkCmd = [
8071
+ `export HOME="${agentHome}"`,
8072
+ `export PATH="${agentHome}/homebrew/bin:${agentHome}/bin:$PATH"`,
8073
+ `source "${agentHome}/.nvm/nvm.sh" 2>/dev/null || true`,
8074
+ `which ${bin}`
8075
+ ].join(" && ");
8076
+ await execWithProgress3(
8077
+ `sudo -H -u ${agentUsername} /bin/bash --norc --noprofile -c '${checkCmd}'`,
8078
+ () => {
8079
+ },
8080
+ { timeout: 5e3, cwd: "/" }
8081
+ );
8082
+ } catch {
8083
+ errors.push(`Required binary "${bin}" not found in agent PATH after install`);
8084
+ }
8085
+ }
8086
+ }
8087
+ return {
8088
+ success: errors.length === 0,
8089
+ installed,
8090
+ errors
8091
+ };
8092
+ }
8093
+
7883
8094
  // libs/shield-daemon/src/routes/marketplace.ts
7884
8095
  var installInProgress = /* @__PURE__ */ new Set();
7885
8096
  function isInstallInProgress(slug) {
@@ -8134,7 +8345,7 @@ async function marketplaceRoutes(app) {
8134
8345
  error: "Critical vulnerability detected",
8135
8346
  analysis: analysisResult
8136
8347
  });
8137
- return { success: false, name: slug, analysis: analysisResult, logs };
8348
+ return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
8138
8349
  }
8139
8350
  emitSkillInstallProgress(slug, "download", "Downloading skill files");
8140
8351
  const skill = await getMarketplaceSkill(slug);
@@ -8144,7 +8355,7 @@ async function marketplaceRoutes(app) {
8144
8355
  name: slug,
8145
8356
  error: "No files available for installation"
8146
8357
  });
8147
- return { success: false, name: slug, analysis: analysisResult, logs };
8358
+ return { success: false, name: slug, analysis: analysisResult, logs, depsSuccess: void 0 };
8148
8359
  }
8149
8360
  const publisher = skill.author;
8150
8361
  logs.push("Downloaded skill files");
@@ -8153,18 +8364,23 @@ async function marketplaceRoutes(app) {
8153
8364
  throw new Error("Skills directory not configured");
8154
8365
  }
8155
8366
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8156
- const binDir = path15.join(agentHome, "bin");
8367
+ const agentUsername = path16.basename(agentHome);
8368
+ const binDir = path16.join(agentHome, "bin");
8157
8369
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8158
- skillDir = path15.join(skillsDir2, slug);
8370
+ skillDir = path16.join(skillsDir2, slug);
8159
8371
  emitSkillInstallProgress(slug, "approve", "Pre-approving skill");
8160
8372
  addToApprovedList(slug, publisher);
8161
8373
  logs.push("Skill pre-approved");
8162
8374
  emitSkillInstallProgress(slug, "copy", "Writing skill files");
8163
8375
  const brokerAvailable = await isBrokerAvailable();
8164
8376
  if (brokerAvailable) {
8377
+ const sanitizedFiles = files.map((f) => ({
8378
+ name: f.name,
8379
+ content: /SKILL\.md$/i.test(f.name) ? stripEnvFromSkillMd(f.content) : f.content
8380
+ }));
8165
8381
  const brokerResult = await installSkillViaBroker(
8166
8382
  slug,
8167
- files.map((f) => ({ name: f.name, content: f.content })),
8383
+ sanitizedFiles,
8168
8384
  { createWrapper: true, agentHome, socketGroup }
8169
8385
  );
8170
8386
  if (!brokerResult.installed) {
@@ -8183,18 +8399,45 @@ async function marketplaceRoutes(app) {
8183
8399
  }
8184
8400
  } else {
8185
8401
  console.log(`[Marketplace] Broker unavailable, installing ${slug} directly`);
8186
- fs15.mkdirSync(skillDir, { recursive: true });
8402
+ sudoMkdir(skillDir, agentUsername);
8187
8403
  for (const file of files) {
8188
- const filePath = path15.join(skillDir, file.name);
8189
- const fileDir = path15.dirname(filePath);
8404
+ const filePath = path16.join(skillDir, file.name);
8405
+ const fileDir = path16.dirname(filePath);
8190
8406
  if (fileDir !== skillDir) {
8191
- fs15.mkdirSync(fileDir, { recursive: true });
8407
+ sudoMkdir(fileDir, agentUsername);
8192
8408
  }
8193
- fs15.writeFileSync(filePath, file.content, "utf-8");
8409
+ const content = /SKILL\.md$/i.test(file.name) ? stripEnvFromSkillMd(file.content) : file.content;
8410
+ sudoWriteFile(filePath, content, agentUsername);
8194
8411
  }
8195
8412
  createSkillWrapper(slug, binDir);
8196
8413
  logs.push(`Files written directly: ${files.length} files`);
8197
- logs.push(`Wrapper created: ${path15.join(binDir, slug)}`);
8414
+ logs.push(`Wrapper created: ${path16.join(binDir, slug)}`);
8415
+ }
8416
+ let depsSuccess = true;
8417
+ emitSkillInstallProgress(slug, "deps", "Installing skill dependencies");
8418
+ try {
8419
+ const depsResult = await executeSkillInstallSteps({
8420
+ slug,
8421
+ skillDir,
8422
+ agentHome,
8423
+ agentUsername,
8424
+ onLog: (msg) => emitSkillInstallProgress(slug, "deps", msg)
8425
+ });
8426
+ if (depsResult.installed.length > 0) {
8427
+ logs.push(`Dependencies installed: ${depsResult.installed.join(", ")}`);
8428
+ }
8429
+ if (depsResult.errors.length > 0) {
8430
+ depsSuccess = false;
8431
+ for (const err of depsResult.errors) {
8432
+ emitSkillInstallProgress(slug, "warning", `Dependency warning: ${err}`);
8433
+ logs.push(`Dependency warning: ${err}`);
8434
+ }
8435
+ }
8436
+ } catch (err) {
8437
+ depsSuccess = false;
8438
+ const msg = `Dependency installation failed: ${err.message}`;
8439
+ emitSkillInstallProgress(slug, "warning", msg);
8440
+ logs.push(msg);
8198
8441
  }
8199
8442
  addSkillEntry(slug);
8200
8443
  addSkillPolicy(slug);
@@ -8206,13 +8449,14 @@ async function marketplaceRoutes(app) {
8206
8449
  logs.push("Integrity hash recorded");
8207
8450
  }
8208
8451
  installInProgress.delete(slug);
8209
- daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult });
8452
+ const depsWarnings = depsSuccess ? void 0 : logs.filter((l) => l.startsWith("Dependency"));
8453
+ daemonEvents.broadcast("skills:installed", { name: slug, analysis: analysisResult, depsWarnings });
8210
8454
  logs.push("Installation complete");
8211
- return { success: true, name: slug, analysis: analysisResult, logs };
8455
+ return { success: true, name: slug, analysis: analysisResult, logs, depsSuccess };
8212
8456
  } catch (err) {
8213
8457
  try {
8214
- if (skillDir && fs15.existsSync(skillDir)) {
8215
- fs15.rmSync(skillDir, { recursive: true, force: true });
8458
+ if (skillDir && fs16.existsSync(skillDir)) {
8459
+ fs16.rmSync(skillDir, { recursive: true, force: true });
8216
8460
  }
8217
8461
  removeFromApprovedList(slug);
8218
8462
  } catch {
@@ -8221,7 +8465,7 @@ async function marketplaceRoutes(app) {
8221
8465
  installInProgress.delete(slug);
8222
8466
  daemonEvents.broadcast("skills:install_failed", { name: slug, error: errorMsg });
8223
8467
  console.error("[Marketplace] Install failed:", errorMsg);
8224
- return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`] };
8468
+ return { success: false, name: slug, analysis: analysisResult, logs: [...logs, `Error: ${errorMsg}`], depsSuccess: void 0 };
8225
8469
  } finally {
8226
8470
  installInProgress.delete(slug);
8227
8471
  }
@@ -8245,17 +8489,17 @@ async function marketplaceRoutes(app) {
8245
8489
  }
8246
8490
 
8247
8491
  // libs/shield-daemon/src/routes/skills.ts
8248
- function findSkillMdRecursive(dir, depth = 0) {
8492
+ function findSkillMdRecursive2(dir, depth = 0) {
8249
8493
  if (depth > 3) return null;
8250
8494
  try {
8251
8495
  for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
8252
- const candidate = path16.join(dir, name);
8253
- if (fs16.existsSync(candidate)) return candidate;
8496
+ const candidate = path17.join(dir, name);
8497
+ if (fs17.existsSync(candidate)) return candidate;
8254
8498
  }
8255
- const entries = fs16.readdirSync(dir, { withFileTypes: true });
8499
+ const entries = fs17.readdirSync(dir, { withFileTypes: true });
8256
8500
  for (const entry of entries) {
8257
8501
  if (!entry.isDirectory()) continue;
8258
- const found = findSkillMdRecursive(path16.join(dir, entry.name), depth + 1);
8502
+ const found = findSkillMdRecursive2(path17.join(dir, entry.name), depth + 1);
8259
8503
  if (found) return found;
8260
8504
  }
8261
8505
  } catch {
@@ -8264,10 +8508,10 @@ function findSkillMdRecursive(dir, depth = 0) {
8264
8508
  }
8265
8509
  function readSkillMetadata(skillDir) {
8266
8510
  try {
8267
- const mdPath = findSkillMdRecursive(skillDir);
8511
+ const mdPath = findSkillMdRecursive2(skillDir);
8268
8512
  if (!mdPath) return {};
8269
- const content = fs16.readFileSync(mdPath, "utf-8");
8270
- const parsed = parseSkillMd2(content);
8513
+ const content = fs17.readFileSync(mdPath, "utf-8");
8514
+ const parsed = parseSkillMd3(content);
8271
8515
  const meta = parsed?.metadata;
8272
8516
  return {
8273
8517
  description: meta?.description,
@@ -8325,7 +8569,7 @@ async function skillsRoutes(app) {
8325
8569
  let onDiskNames = [];
8326
8570
  if (skillsDir2) {
8327
8571
  try {
8328
- onDiskNames = fs16.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8572
+ onDiskNames = fs17.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
8329
8573
  } catch {
8330
8574
  }
8331
8575
  }
@@ -8335,14 +8579,14 @@ async function skillsRoutes(app) {
8335
8579
  const data = [
8336
8580
  // Approved → active (with metadata from SKILL.md + cached analysis)
8337
8581
  ...approved.map((a) => {
8338
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, a.name)) : {};
8582
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, a.name)) : {};
8339
8583
  const cached = getCachedAnalysis2(a.name);
8340
8584
  const dlMeta = getDownloadedSkillMeta(a.name);
8341
8585
  return {
8342
8586
  name: a.name,
8343
8587
  source: "user",
8344
8588
  status: "active",
8345
- path: path16.join(skillsDir2 ?? "", a.name),
8589
+ path: path17.join(skillsDir2 ?? "", a.name),
8346
8590
  publisher: a.publisher,
8347
8591
  description: meta.description,
8348
8592
  version: meta.version,
@@ -8369,12 +8613,12 @@ async function skillsRoutes(app) {
8369
8613
  }),
8370
8614
  // Workspace: on disk but not approved or untrusted
8371
8615
  ...workspaceNames.map((name) => {
8372
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8616
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8373
8617
  return {
8374
8618
  name,
8375
8619
  source: "workspace",
8376
8620
  status: "workspace",
8377
- path: path16.join(skillsDir2 ?? "", name),
8621
+ path: path17.join(skillsDir2 ?? "", name),
8378
8622
  description: meta.description,
8379
8623
  version: meta.version,
8380
8624
  author: meta.author,
@@ -8405,7 +8649,7 @@ async function skillsRoutes(app) {
8405
8649
  let isWorkspace = false;
8406
8650
  if (!entry && !uEntry && skillsDir2) {
8407
8651
  try {
8408
- isWorkspace = fs16.existsSync(path16.join(skillsDir2, name));
8652
+ isWorkspace = fs17.existsSync(path17.join(skillsDir2, name));
8409
8653
  } catch {
8410
8654
  }
8411
8655
  }
@@ -8423,13 +8667,13 @@ async function skillsRoutes(app) {
8423
8667
  analysis: buildFullAnalysis(name, dlMeta?.analysis || getCachedAnalysis2(name))
8424
8668
  };
8425
8669
  } else if (entry) {
8426
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8670
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8427
8671
  const cached = getCachedAnalysis2(name);
8428
8672
  summary = {
8429
8673
  name,
8430
8674
  source: "user",
8431
8675
  status: "active",
8432
- path: skillsDir2 ? path16.join(skillsDir2, name) : "",
8676
+ path: skillsDir2 ? path17.join(skillsDir2, name) : "",
8433
8677
  publisher: entry.publisher,
8434
8678
  description: meta.description,
8435
8679
  version: meta.version,
@@ -8438,12 +8682,12 @@ async function skillsRoutes(app) {
8438
8682
  analysis: buildFullAnalysis(name, dlMeta?.analysis || cached)
8439
8683
  };
8440
8684
  } else if (isWorkspace) {
8441
- const meta = skillsDir2 ? readSkillMetadata(path16.join(skillsDir2, name)) : {};
8685
+ const meta = skillsDir2 ? readSkillMetadata(path17.join(skillsDir2, name)) : {};
8442
8686
  summary = {
8443
8687
  name,
8444
8688
  source: "workspace",
8445
8689
  status: "workspace",
8446
- path: skillsDir2 ? path16.join(skillsDir2, name) : "",
8690
+ path: skillsDir2 ? path17.join(skillsDir2, name) : "",
8447
8691
  description: meta.description,
8448
8692
  version: meta.version,
8449
8693
  author: meta.author,
@@ -8468,11 +8712,11 @@ async function skillsRoutes(app) {
8468
8712
  return reply.code(404).send({ error: `Skill "${name}" not found` });
8469
8713
  }
8470
8714
  let content = "";
8471
- const dirToRead = summary.path || (skillsDir2 ? path16.join(skillsDir2, name) : "");
8715
+ const dirToRead = summary.path || (skillsDir2 ? path17.join(skillsDir2, name) : "");
8472
8716
  if (dirToRead) {
8473
8717
  try {
8474
- const mdPath = findSkillMdRecursive(dirToRead);
8475
- if (mdPath) content = fs16.readFileSync(mdPath, "utf-8");
8718
+ const mdPath = findSkillMdRecursive2(dirToRead);
8719
+ if (mdPath) content = fs17.readFileSync(mdPath, "utf-8");
8476
8720
  } catch {
8477
8721
  }
8478
8722
  }
@@ -8510,14 +8754,14 @@ async function skillsRoutes(app) {
8510
8754
  if (!content) {
8511
8755
  const skillsDir2 = getSkillsDir();
8512
8756
  const possibleDirs = [
8513
- skillsDir2 ? path16.join(skillsDir2, name) : null
8757
+ skillsDir2 ? path17.join(skillsDir2, name) : null
8514
8758
  ].filter(Boolean);
8515
8759
  for (const dir of possibleDirs) {
8516
8760
  try {
8517
- const mdPath = findSkillMdRecursive(dir);
8761
+ const mdPath = findSkillMdRecursive2(dir);
8518
8762
  if (mdPath) {
8519
- content = fs16.readFileSync(mdPath, "utf-8");
8520
- const parsed = parseSkillMd2(content);
8763
+ content = fs17.readFileSync(mdPath, "utf-8");
8764
+ const parsed = parseSkillMd3(content);
8521
8765
  if (parsed?.metadata && !metadata) {
8522
8766
  metadata = parsed.metadata;
8523
8767
  }
@@ -8532,7 +8776,7 @@ async function skillsRoutes(app) {
8532
8776
  const skillFile = localFiles.find((f) => /skill\.md/i.test(f.name));
8533
8777
  if (skillFile?.content) {
8534
8778
  content = skillFile.content;
8535
- const parsed = parseSkillMd2(content);
8779
+ const parsed = parseSkillMd3(content);
8536
8780
  if (parsed?.metadata && !metadata) {
8537
8781
  metadata = parsed.metadata;
8538
8782
  }
@@ -8547,7 +8791,7 @@ async function skillsRoutes(app) {
8547
8791
  const skillFile = freshFiles.find((f) => /skill\.md/i.test(f.name));
8548
8792
  if (skillFile?.content) {
8549
8793
  content = skillFile.content;
8550
- const parsed = parseSkillMd2(content);
8794
+ const parsed = parseSkillMd3(content);
8551
8795
  if (parsed?.metadata && !metadata) {
8552
8796
  metadata = parsed.metadata;
8553
8797
  }
@@ -8701,10 +8945,10 @@ async function skillsRoutes(app) {
8701
8945
  return reply.code(500).send({ error: "Skills directory not configured" });
8702
8946
  }
8703
8947
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8704
- const binDir = path16.join(agentHome, "bin");
8948
+ const binDir = path17.join(agentHome, "bin");
8705
8949
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8706
- const skillDir = path16.join(skillsDir2, name);
8707
- const isInstalled = fs16.existsSync(skillDir);
8950
+ const skillDir = path17.join(skillsDir2, name);
8951
+ const isInstalled = fs17.existsSync(skillDir);
8708
8952
  if (isInstalled) {
8709
8953
  try {
8710
8954
  const brokerAvailable = await isBrokerAvailable();
@@ -8714,7 +8958,7 @@ async function skillsRoutes(app) {
8714
8958
  agentHome
8715
8959
  });
8716
8960
  } else {
8717
- fs16.rmSync(skillDir, { recursive: true, force: true });
8961
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8718
8962
  removeSkillWrapper(name, binDir);
8719
8963
  }
8720
8964
  removeSkillEntry(name);
@@ -8759,11 +9003,11 @@ async function skillsRoutes(app) {
8759
9003
  }
8760
9004
  }
8761
9005
  } else {
8762
- fs16.mkdirSync(skillDir, { recursive: true });
9006
+ fs17.mkdirSync(skillDir, { recursive: true });
8763
9007
  for (const file of files) {
8764
- const filePath = path16.join(skillDir, file.name);
8765
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8766
- fs16.writeFileSync(filePath, file.content, "utf-8");
9008
+ const filePath = path17.join(skillDir, file.name);
9009
+ fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
9010
+ fs17.writeFileSync(filePath, file.content, "utf-8");
8767
9011
  }
8768
9012
  try {
8769
9013
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8781,8 +9025,8 @@ async function skillsRoutes(app) {
8781
9025
  return reply.send({ success: true, action: "enabled", name });
8782
9026
  } catch (err) {
8783
9027
  try {
8784
- if (fs16.existsSync(skillDir)) {
8785
- fs16.rmSync(skillDir, { recursive: true, force: true });
9028
+ if (fs17.existsSync(skillDir)) {
9029
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8786
9030
  }
8787
9031
  removeFromApprovedList(name);
8788
9032
  } catch {
@@ -8808,7 +9052,7 @@ async function skillsRoutes(app) {
8808
9052
  const skillMdFile = files.find((f) => f.name === "SKILL.md");
8809
9053
  let metadata;
8810
9054
  if (skillMdFile) {
8811
- const parsed = parseSkillMd2(skillMdFile.content);
9055
+ const parsed = parseSkillMd3(skillMdFile.content);
8812
9056
  metadata = parsed?.metadata;
8813
9057
  }
8814
9058
  const analysis = analyzeSkill(name, combinedContent, metadata);
@@ -8820,16 +9064,18 @@ async function skillsRoutes(app) {
8820
9064
  return reply.code(500).send({ error: "Skills directory not configured" });
8821
9065
  }
8822
9066
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8823
- const binDir = path16.join(agentHome, "bin");
9067
+ const agentUsername = path17.basename(agentHome);
9068
+ const binDir = path17.join(agentHome, "bin");
8824
9069
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8825
- const skillDir = path16.join(skillsDir2, name);
9070
+ const skillDir = path17.join(skillsDir2, name);
8826
9071
  try {
8827
9072
  addToApprovedList(name, publisher);
8828
- fs16.mkdirSync(skillDir, { recursive: true });
9073
+ sudoMkdir(skillDir, agentUsername);
8829
9074
  for (const file of files) {
8830
- const filePath = path16.join(skillDir, file.name);
8831
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8832
- fs16.writeFileSync(filePath, file.content, "utf-8");
9075
+ const filePath = path17.join(skillDir, file.name);
9076
+ sudoMkdir(path17.dirname(filePath), agentUsername);
9077
+ const content = /SKILL\.md$/i.test(file.name) ? stripEnvFromSkillMd2(file.content) : file.content;
9078
+ sudoWriteFile(filePath, content, agentUsername);
8833
9079
  }
8834
9080
  try {
8835
9081
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8843,8 +9089,8 @@ async function skillsRoutes(app) {
8843
9089
  return reply.send({ success: true, name, analysis });
8844
9090
  } catch (err) {
8845
9091
  try {
8846
- if (fs16.existsSync(skillDir)) {
8847
- fs16.rmSync(skillDir, { recursive: true, force: true });
9092
+ if (fs17.existsSync(skillDir)) {
9093
+ fs17.rmSync(skillDir, { recursive: true, force: true });
8848
9094
  }
8849
9095
  removeFromApprovedList(name);
8850
9096
  } catch {
@@ -8878,9 +9124,9 @@ async function skillsRoutes(app) {
8878
9124
  }
8879
9125
  try {
8880
9126
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
8881
- const binDir = path16.join(agentHome, "bin");
9127
+ const binDir = path17.join(agentHome, "bin");
8882
9128
  const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "ash_default";
8883
- const skillDir = path16.join(getSkillsDir(), name);
9129
+ const skillDir = path17.join(getSkillsDir(), name);
8884
9130
  addToApprovedList(name, meta.author);
8885
9131
  const brokerAvailable = await isBrokerAvailable();
8886
9132
  if (brokerAvailable) {
@@ -8898,11 +9144,11 @@ async function skillsRoutes(app) {
8898
9144
  }
8899
9145
  }
8900
9146
  } else {
8901
- fs16.mkdirSync(skillDir, { recursive: true });
9147
+ fs17.mkdirSync(skillDir, { recursive: true });
8902
9148
  for (const file of files) {
8903
- const filePath = path16.join(skillDir, file.name);
8904
- fs16.mkdirSync(path16.dirname(filePath), { recursive: true });
8905
- fs16.writeFileSync(filePath, file.content, "utf-8");
9149
+ const filePath = path17.join(skillDir, file.name);
9150
+ fs17.mkdirSync(path17.dirname(filePath), { recursive: true });
9151
+ fs17.writeFileSync(filePath, file.content, "utf-8");
8906
9152
  }
8907
9153
  try {
8908
9154
  execSync9(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
@@ -8914,7 +9160,7 @@ async function skillsRoutes(app) {
8914
9160
  addSkillEntry(name);
8915
9161
  addSkillPolicy(name);
8916
9162
  syncOpenClawFromPolicies(loadConfig().policies);
8917
- const unblockHash = computeSkillHash(path16.join(getSkillsDir(), name));
9163
+ const unblockHash = computeSkillHash(path17.join(getSkillsDir(), name));
8918
9164
  if (unblockHash) updateApprovedHash(name, unblockHash);
8919
9165
  console.log(`[Skills] Unblocked and installed skill: ${name}`);
8920
9166
  return reply.send({ success: true, message: `Skill "${name}" approved and installed` });
@@ -8945,7 +9191,7 @@ async function skillsRoutes(app) {
8945
9191
  const skillMdFile = files.find((f) => /skill\.md/i.test(f.name));
8946
9192
  if (skillMdFile) {
8947
9193
  try {
8948
- const parsed = parseSkillMd2(skillMdFile.content);
9194
+ const parsed = parseSkillMd3(skillMdFile.content);
8949
9195
  if (parsed?.metadata) {
8950
9196
  parsedDescription = parsedDescription || parsed.metadata.description;
8951
9197
  parsedVersion = parsedVersion || parsed.metadata.version;
@@ -8974,10 +9220,10 @@ async function skillsRoutes(app) {
8974
9220
 
8975
9221
  // libs/shield-daemon/src/routes/exec.ts
8976
9222
  init_paths();
8977
- import * as fs17 from "node:fs";
8978
- import * as path17 from "node:path";
9223
+ import * as fs18 from "node:fs";
9224
+ import * as path18 from "node:path";
8979
9225
  function getAllowedCommandsPath2() {
8980
- return path17.join(getSystemConfigDir(), "allowed-commands.json");
9226
+ return path18.join(getSystemConfigDir(), "allowed-commands.json");
8981
9227
  }
8982
9228
  var BIN_DIRS = [
8983
9229
  "/usr/bin",
@@ -8990,22 +9236,22 @@ var binCache = null;
8990
9236
  var BIN_CACHE_TTL = 6e4;
8991
9237
  var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
8992
9238
  function loadConfig2() {
8993
- if (!fs17.existsSync(getAllowedCommandsPath2())) {
9239
+ if (!fs18.existsSync(getAllowedCommandsPath2())) {
8994
9240
  return { version: "1.0.0", commands: [] };
8995
9241
  }
8996
9242
  try {
8997
- const content = fs17.readFileSync(getAllowedCommandsPath2(), "utf-8");
9243
+ const content = fs18.readFileSync(getAllowedCommandsPath2(), "utf-8");
8998
9244
  return JSON.parse(content);
8999
9245
  } catch {
9000
9246
  return { version: "1.0.0", commands: [] };
9001
9247
  }
9002
9248
  }
9003
9249
  function saveConfig2(config) {
9004
- const dir = path17.dirname(getAllowedCommandsPath2());
9005
- if (!fs17.existsSync(dir)) {
9006
- fs17.mkdirSync(dir, { recursive: true });
9250
+ const dir = path18.dirname(getAllowedCommandsPath2());
9251
+ if (!fs18.existsSync(dir)) {
9252
+ fs18.mkdirSync(dir, { recursive: true });
9007
9253
  }
9008
- fs17.writeFileSync(getAllowedCommandsPath2(), JSON.stringify(config, null, 2) + "\n", "utf-8");
9254
+ fs18.writeFileSync(getAllowedCommandsPath2(), JSON.stringify(config, null, 2) + "\n", "utf-8");
9009
9255
  }
9010
9256
  function scanSystemBins() {
9011
9257
  const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
@@ -9014,13 +9260,13 @@ function scanSystemBins() {
9014
9260
  const results = [];
9015
9261
  for (const dir of allDirs) {
9016
9262
  try {
9017
- if (!fs17.existsSync(dir)) continue;
9018
- const entries = fs17.readdirSync(dir);
9263
+ if (!fs18.existsSync(dir)) continue;
9264
+ const entries = fs18.readdirSync(dir);
9019
9265
  for (const entry of entries) {
9020
9266
  if (seen.has(entry)) continue;
9021
- const fullPath = path17.join(dir, entry);
9267
+ const fullPath = path18.join(dir, entry);
9022
9268
  try {
9023
- const stat = fs17.statSync(fullPath);
9269
+ const stat = fs18.statSync(fullPath);
9024
9270
  if (stat.isFile() && (stat.mode & 73) !== 0) {
9025
9271
  seen.add(entry);
9026
9272
  results.push({ name: entry, path: fullPath });
@@ -9073,7 +9319,7 @@ async function execRoutes(app) {
9073
9319
  };
9074
9320
  }
9075
9321
  for (const p of paths) {
9076
- if (!path17.isAbsolute(p)) {
9322
+ if (!path18.isAbsolute(p)) {
9077
9323
  return {
9078
9324
  success: false,
9079
9325
  error: {
@@ -9569,18 +9815,18 @@ async function secretsRoutes(app) {
9569
9815
  }
9570
9816
 
9571
9817
  // libs/shield-daemon/src/routes/fs.ts
9572
- import * as fs18 from "node:fs";
9573
- import * as path18 from "node:path";
9818
+ import * as fs19 from "node:fs";
9819
+ import * as path19 from "node:path";
9574
9820
  import * as os6 from "node:os";
9575
9821
  var MAX_ENTRIES = 200;
9576
9822
  async function fsRoutes(app) {
9577
9823
  app.get("/fs/browse", async (request2) => {
9578
9824
  const dirPath = request2.query.path || os6.homedir();
9579
9825
  const showHidden = request2.query.showHidden === "true";
9580
- const resolvedPath = path18.resolve(dirPath);
9826
+ const resolvedPath = path19.resolve(dirPath);
9581
9827
  let dirents;
9582
9828
  try {
9583
- dirents = fs18.readdirSync(resolvedPath, { withFileTypes: true });
9829
+ dirents = fs19.readdirSync(resolvedPath, { withFileTypes: true });
9584
9830
  } catch {
9585
9831
  return { success: true, data: { entries: [] } };
9586
9832
  }
@@ -9589,7 +9835,7 @@ async function fsRoutes(app) {
9589
9835
  if (!showHidden && dirent.name.startsWith(".")) continue;
9590
9836
  entries.push({
9591
9837
  name: dirent.name,
9592
- path: path18.join(resolvedPath, dirent.name),
9838
+ path: path19.join(resolvedPath, dirent.name),
9593
9839
  type: dirent.isDirectory() ? "directory" : "file"
9594
9840
  });
9595
9841
  if (entries.length >= MAX_ENTRIES) break;
@@ -9603,8 +9849,8 @@ async function fsRoutes(app) {
9603
9849
  }
9604
9850
 
9605
9851
  // libs/shield-daemon/src/services/activity-log.ts
9606
- import * as fs19 from "node:fs";
9607
- import * as path19 from "node:path";
9852
+ import * as fs20 from "node:fs";
9853
+ import * as path20 from "node:path";
9608
9854
  init_paths();
9609
9855
  var ACTIVITY_FILE = "activity.jsonl";
9610
9856
  var MAX_SIZE_BYTES = 100 * 1024 * 1024;
@@ -9622,12 +9868,12 @@ var ActivityLog = class {
9622
9868
  writeCount = 0;
9623
9869
  unsubscribe;
9624
9870
  constructor() {
9625
- this.filePath = path19.join(getConfigDir(), ACTIVITY_FILE);
9871
+ this.filePath = path20.join(getConfigDir(), ACTIVITY_FILE);
9626
9872
  }
9627
9873
  /** Read historical events from the JSONL file, newest first */
9628
9874
  getHistory(limit = 500) {
9629
- if (!fs19.existsSync(this.filePath)) return [];
9630
- const content = fs19.readFileSync(this.filePath, "utf-8");
9875
+ if (!fs20.existsSync(this.filePath)) return [];
9876
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9631
9877
  const lines = content.split("\n").filter(Boolean);
9632
9878
  const events = [];
9633
9879
  for (const line of lines) {
@@ -9652,7 +9898,7 @@ var ActivityLog = class {
9652
9898
  }
9653
9899
  append(event) {
9654
9900
  const line = JSON.stringify(event) + "\n";
9655
- fs19.appendFileSync(this.filePath, line, "utf-8");
9901
+ fs20.appendFileSync(this.filePath, line, "utf-8");
9656
9902
  this.writeCount++;
9657
9903
  if (this.writeCount % PRUNE_INTERVAL === 0) {
9658
9904
  this.rotate();
@@ -9660,7 +9906,7 @@ var ActivityLog = class {
9660
9906
  }
9661
9907
  rotate() {
9662
9908
  try {
9663
- const stat = fs19.statSync(this.filePath);
9909
+ const stat = fs20.statSync(this.filePath);
9664
9910
  if (stat.size > MAX_SIZE_BYTES) {
9665
9911
  this.truncateBySize();
9666
9912
  }
@@ -9670,15 +9916,15 @@ var ActivityLog = class {
9670
9916
  }
9671
9917
  /** Keep newest half of lines when file exceeds size limit */
9672
9918
  truncateBySize() {
9673
- const content = fs19.readFileSync(this.filePath, "utf-8");
9919
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9674
9920
  const lines = content.split("\n").filter(Boolean);
9675
9921
  const keep = lines.slice(Math.floor(lines.length / 2));
9676
- fs19.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
9922
+ fs20.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
9677
9923
  }
9678
9924
  /** Remove entries older than 24 hours */
9679
9925
  pruneOldEntries() {
9680
- if (!fs19.existsSync(this.filePath)) return;
9681
- const content = fs19.readFileSync(this.filePath, "utf-8");
9926
+ if (!fs20.existsSync(this.filePath)) return;
9927
+ const content = fs20.readFileSync(this.filePath, "utf-8");
9682
9928
  const lines = content.split("\n").filter(Boolean);
9683
9929
  const cutoff = Date.now() - MAX_AGE_MS;
9684
9930
  const kept = lines.filter((line) => {
@@ -9690,7 +9936,7 @@ var ActivityLog = class {
9690
9936
  }
9691
9937
  });
9692
9938
  if (kept.length < lines.length) {
9693
- fs19.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
9939
+ fs20.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
9694
9940
  }
9695
9941
  }
9696
9942
  };
@@ -9831,11 +10077,11 @@ function normalizeUrlTarget(url) {
9831
10077
  const trimmed = url.trim();
9832
10078
  try {
9833
10079
  const parsed = new URL(trimmed);
9834
- let path21 = parsed.pathname;
9835
- if (path21.length > 1) {
9836
- path21 = path21.replace(/\/+$/, "");
10080
+ let path24 = parsed.pathname;
10081
+ if (path24.length > 1) {
10082
+ path24 = path24.replace(/\/+$/, "");
9837
10083
  }
9838
- return `${parsed.protocol}//${parsed.host}${path21}${parsed.search}`;
10084
+ return `${parsed.protocol}//${parsed.host}${path24}${parsed.search}`;
9839
10085
  } catch {
9840
10086
  return trimmed.replace(/\/+$/, "");
9841
10087
  }
@@ -10121,8 +10367,8 @@ function matchCommandPattern(pattern, target) {
10121
10367
  const firstSpace = target.indexOf(" ");
10122
10368
  const cmd = firstSpace >= 0 ? target.slice(0, firstSpace) : target;
10123
10369
  if (cmd.startsWith("/")) {
10124
- const basename3 = cmd.split("/").pop() || cmd;
10125
- normalizedTarget = firstSpace >= 0 ? basename3 + target.slice(firstSpace) : basename3;
10370
+ const basename6 = cmd.split("/").pop() || cmd;
10371
+ normalizedTarget = firstSpace >= 0 ? basename6 + target.slice(firstSpace) : basename6;
10126
10372
  }
10127
10373
  if (trimmed.endsWith(":*")) {
10128
10374
  const prefix = trimmed.slice(0, -2);
@@ -10173,8 +10419,8 @@ function determineNetworkAccess(_config, matchedPolicy, target) {
10173
10419
  if (matchedPolicy?.networkAccess) return matchedPolicy.networkAccess;
10174
10420
  const cleanTarget = target.startsWith("fork:") ? target.slice(5) : target;
10175
10421
  const cmdPart = cleanTarget.split(" ")[0] || "";
10176
- const basename3 = cmdPart.includes("/") ? cmdPart.split("/").pop() : cmdPart;
10177
- if (!NETWORK_COMMANDS.has(basename3.toLowerCase())) return "none";
10422
+ const basename6 = cmdPart.includes("/") ? cmdPart.split("/").pop() : cmdPart;
10423
+ if (!NETWORK_COMMANDS.has(basename6.toLowerCase())) return "none";
10178
10424
  return "proxy";
10179
10425
  }
10180
10426
  async function buildSandboxConfig(config, matchedPolicy, _context, target) {
@@ -10475,22 +10721,22 @@ async function registerRoutes(app) {
10475
10721
  }
10476
10722
 
10477
10723
  // libs/shield-daemon/src/static.ts
10478
- import * as fs20 from "node:fs";
10479
- import * as path20 from "node:path";
10724
+ import * as fs21 from "node:fs";
10725
+ import * as path21 from "node:path";
10480
10726
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10481
10727
  var __filename = fileURLToPath2(import.meta.url);
10482
- var __dirname = path20.dirname(__filename);
10728
+ var __dirname = path21.dirname(__filename);
10483
10729
  function getUiAssetsPath() {
10484
- const pkgRootPath = path20.join(__dirname, "..", "ui-assets");
10485
- if (fs20.existsSync(pkgRootPath)) {
10730
+ const pkgRootPath = path21.join(__dirname, "..", "ui-assets");
10731
+ if (fs21.existsSync(pkgRootPath)) {
10486
10732
  return pkgRootPath;
10487
10733
  }
10488
- const bundledPath = path20.join(__dirname, "ui-assets");
10489
- if (fs20.existsSync(bundledPath)) {
10734
+ const bundledPath = path21.join(__dirname, "ui-assets");
10735
+ if (fs21.existsSync(bundledPath)) {
10490
10736
  return bundledPath;
10491
10737
  }
10492
- const devPath = path20.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
10493
- if (fs20.existsSync(devPath)) {
10738
+ const devPath = path21.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
10739
+ if (fs21.existsSync(devPath)) {
10494
10740
  return devPath;
10495
10741
  }
10496
10742
  return null;
@@ -10580,8 +10826,8 @@ async function startServer(config) {
10580
10826
  startSecurityWatcher(1e4);
10581
10827
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
10582
10828
  const skillsDir2 = `${agentHome}/.openclaw/skills`;
10583
- if (!fs21.existsSync(skillsDir2)) {
10584
- fs21.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
10829
+ if (!fs23.existsSync(skillsDir2)) {
10830
+ fs23.mkdirSync(skillsDir2, { recursive: true, mode: 493 });
10585
10831
  console.log(`[Daemon] Created skills directory: ${skillsDir2}`);
10586
10832
  }
10587
10833
  startSkillsWatcher(skillsDir2, {
@@ -10632,10 +10878,10 @@ async function main() {
10632
10878
  ensureConfigDir();
10633
10879
  const config = loadConfig();
10634
10880
  const pidPath = getPidPath();
10635
- fs23.writeFileSync(pidPath, process.pid.toString(), "utf-8");
10881
+ fs24.writeFileSync(pidPath, process.pid.toString(), "utf-8");
10636
10882
  const cleanup = () => {
10637
- if (fs23.existsSync(pidPath)) {
10638
- fs23.unlinkSync(pidPath);
10883
+ if (fs24.existsSync(pidPath)) {
10884
+ fs24.unlinkSync(pidPath);
10639
10885
  }
10640
10886
  process.exit(0);
10641
10887
  };