@agenshield/daemon 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -5422,6 +5422,20 @@ function mapSearchResult(result) {
5422
5422
  function isTextMime(mime) {
5423
5423
  return mime.startsWith("text/") || mime === "application/json" || mime === "text/yaml" || mime === "text/toml";
5424
5424
  }
5425
+ var IMAGE_EXT_MAP = {
5426
+ ".png": "image/png",
5427
+ ".jpg": "image/jpeg",
5428
+ ".jpeg": "image/jpeg",
5429
+ ".gif": "image/gif",
5430
+ ".svg": "image/svg+xml",
5431
+ ".webp": "image/webp",
5432
+ ".ico": "image/x-icon"
5433
+ };
5434
+ function isImageExt(filePath) {
5435
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
5436
+ return IMAGE_EXT_MAP[ext] ?? null;
5437
+ }
5438
+ var MAX_IMAGE_SIZE = 5e5;
5425
5439
  function getMarketplaceDir() {
5426
5440
  return path11.join(os4.homedir(), CONFIG_DIR2, MARKETPLACE_DIR);
5427
5441
  }
@@ -5444,9 +5458,19 @@ async function downloadAndExtractZip(slug) {
5444
5458
  const filename = zipPath.split("/").pop() || "";
5445
5459
  if (filename.startsWith(".")) continue;
5446
5460
  const mimeType = guessContentType(zipPath);
5447
- if (!isTextMime(mimeType)) continue;
5448
- const content = await zipEntry.async("text");
5449
- files.push({ name: zipPath, type: mimeType, content });
5461
+ if (isTextMime(mimeType)) {
5462
+ const content = await zipEntry.async("text");
5463
+ files.push({ name: zipPath, type: mimeType, content });
5464
+ continue;
5465
+ }
5466
+ const imageMime = isImageExt(zipPath);
5467
+ if (imageMime) {
5468
+ const buf = await zipEntry.async("nodebuffer");
5469
+ if (buf.length <= MAX_IMAGE_SIZE) {
5470
+ const dataUri = `data:${imageMime};base64,${buf.toString("base64")}`;
5471
+ files.push({ name: zipPath, type: imageMime, content: dataUri });
5472
+ }
5473
+ }
5450
5474
  }
5451
5475
  return files;
5452
5476
  }
@@ -5528,6 +5552,34 @@ function getDownloadedSkillMeta(slug) {
5528
5552
  }
5529
5553
  return null;
5530
5554
  }
5555
+ function inlineImagesInMarkdown(markdown, files) {
5556
+ const imageMap = /* @__PURE__ */ new Map();
5557
+ for (const file of files) {
5558
+ const mime = isImageExt(file.name);
5559
+ if (mime && file.content.startsWith("data:")) {
5560
+ imageMap.set(file.name, file.content);
5561
+ const basename2 = file.name.split("/").pop() ?? "";
5562
+ if (basename2 && !imageMap.has(basename2)) {
5563
+ imageMap.set(basename2, file.content);
5564
+ }
5565
+ }
5566
+ }
5567
+ if (imageMap.size === 0) return markdown;
5568
+ return markdown.replace(
5569
+ /!\[([^\]]*)\]\(([^)]+)\)/g,
5570
+ (_match, alt, src) => {
5571
+ if (src.startsWith("http://") || src.startsWith("https://") || src.startsWith("data:")) {
5572
+ return _match;
5573
+ }
5574
+ const normalized = src.replace(/^\.\//, "");
5575
+ const dataUri = imageMap.get(src) ?? imageMap.get(normalized) ?? imageMap.get(normalized.split("/").pop() ?? "");
5576
+ if (dataUri) {
5577
+ return `![${alt}](${dataUri})`;
5578
+ }
5579
+ return _match;
5580
+ }
5581
+ );
5582
+ }
5531
5583
  async function searchMarketplace(query) {
5532
5584
  const cacheKey = `search:${query}`;
5533
5585
  const cached = getCached(cacheKey);
@@ -5612,6 +5664,9 @@ async function getMarketplaceSkill(slug) {
5612
5664
  }
5613
5665
  }
5614
5666
  const tags = tagsFromRecord(skill.tags);
5667
+ if (readme && files.length > 0) {
5668
+ readme = inlineImagesInMarkdown(readme, files);
5669
+ }
5615
5670
  const mapped = {
5616
5671
  name: skill.displayName,
5617
5672
  slug: skill.slug,
@@ -5749,127 +5804,484 @@ async function getCachedAnalysis2(skillName, publisher) {
5749
5804
  };
5750
5805
  }
5751
5806
 
5752
- // libs/shield-daemon/src/routes/skills.ts
5753
- function readSkillDescription(skillDir) {
5754
- try {
5755
- const skillMdPath = path12.join(skillDir, "SKILL.md");
5756
- if (!fs13.existsSync(skillMdPath)) return void 0;
5757
- const content = fs13.readFileSync(skillMdPath, "utf-8");
5758
- const parsed = parseSkillMd(content);
5759
- return parsed?.metadata?.description ?? void 0;
5760
- } catch {
5761
- return void 0;
5807
+ // libs/shield-broker/dist/index.js
5808
+ import { exec } from "node:child_process";
5809
+ import { promisify } from "node:util";
5810
+ import * as net2 from "node:net";
5811
+ import { randomUUID as randomUUID3 } from "node:crypto";
5812
+ var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
5813
+ var execAsync = promisify(exec);
5814
+ var BrokerClient = class {
5815
+ socketPath;
5816
+ httpHost;
5817
+ httpPort;
5818
+ timeout;
5819
+ preferSocket;
5820
+ constructor(options = {}) {
5821
+ this.socketPath = options.socketPath || "/var/run/agenshield/agenshield.sock";
5822
+ this.httpHost = options.httpHost || "localhost";
5823
+ this.httpPort = options.httpPort || 5201;
5824
+ this.timeout = options.timeout || 3e4;
5825
+ this.preferSocket = options.preferSocket ?? true;
5762
5826
  }
5763
- }
5764
- async function skillsRoutes(app) {
5765
- app.get("/skills", async (_request, reply) => {
5766
- const approved = listApproved();
5767
- const quarantined = listQuarantined();
5768
- const downloaded = listDownloadedSkills();
5769
- const skillsDir2 = getSkillsDir();
5770
- const approvedNames = new Set(approved.map((a) => a.name));
5771
- const availableDownloads = downloaded.filter((d) => !approvedNames.has(d.slug));
5772
- const quarantinedNames = new Set(quarantined.map((q) => q.name));
5773
- let onDiskNames = [];
5774
- if (skillsDir2) {
5775
- try {
5776
- onDiskNames = fs13.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
5777
- } catch {
5778
- }
5827
+ /**
5828
+ * Make an HTTP request through the broker
5829
+ */
5830
+ async httpRequest(params, options) {
5831
+ return this.request("http_request", params, options);
5832
+ }
5833
+ /**
5834
+ * Read a file through the broker
5835
+ */
5836
+ async fileRead(params, options) {
5837
+ return this.request("file_read", params, options);
5838
+ }
5839
+ /**
5840
+ * Write a file through the broker
5841
+ */
5842
+ async fileWrite(params, options) {
5843
+ return this.request("file_write", params, {
5844
+ ...options,
5845
+ channel: "socket"
5846
+ // file_write only allowed via socket
5847
+ });
5848
+ }
5849
+ /**
5850
+ * List files through the broker
5851
+ */
5852
+ async fileList(params, options) {
5853
+ return this.request("file_list", params, options);
5854
+ }
5855
+ /**
5856
+ * Execute a command through the broker
5857
+ */
5858
+ async exec(params, options) {
5859
+ return this.request("exec", params, {
5860
+ ...options,
5861
+ channel: "socket"
5862
+ // exec only allowed via socket
5863
+ });
5864
+ }
5865
+ /**
5866
+ * Open a URL through the broker
5867
+ */
5868
+ async openUrl(params, options) {
5869
+ return this.request("open_url", params, options);
5870
+ }
5871
+ /**
5872
+ * Inject a secret through the broker
5873
+ */
5874
+ async secretInject(params, options) {
5875
+ return this.request("secret_inject", params, {
5876
+ ...options,
5877
+ channel: "socket"
5878
+ // secret_inject only allowed via socket
5879
+ });
5880
+ }
5881
+ /**
5882
+ * Ping the broker
5883
+ */
5884
+ async ping(echo, options) {
5885
+ return this.request("ping", { echo }, options);
5886
+ }
5887
+ /**
5888
+ * Install a skill through the broker
5889
+ * Socket-only operation due to privileged file operations
5890
+ */
5891
+ async skillInstall(params, options) {
5892
+ return this.request("skill_install", params, {
5893
+ ...options,
5894
+ channel: "socket"
5895
+ // skill_install only allowed via socket
5896
+ });
5897
+ }
5898
+ /**
5899
+ * Uninstall a skill through the broker
5900
+ * Socket-only operation due to privileged file operations
5901
+ */
5902
+ async skillUninstall(params, options) {
5903
+ return this.request("skill_uninstall", params, {
5904
+ ...options,
5905
+ channel: "socket"
5906
+ // skill_uninstall only allowed via socket
5907
+ });
5908
+ }
5909
+ /**
5910
+ * Check if the broker is available
5911
+ */
5912
+ async isAvailable() {
5913
+ try {
5914
+ await this.ping();
5915
+ return true;
5916
+ } catch {
5917
+ return false;
5779
5918
  }
5780
- const workspaceNames = onDiskNames.filter(
5781
- (n) => !approvedNames.has(n) && !quarantinedNames.has(n)
5782
- );
5783
- const data = [
5784
- // Approved → active (with descriptions from SKILL.md)
5785
- ...approved.map((a) => ({
5786
- name: a.name,
5787
- source: "user",
5788
- status: "active",
5789
- path: path12.join(skillsDir2 ?? "", a.name),
5790
- publisher: a.publisher,
5791
- description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, a.name)) : void 0
5792
- })),
5793
- // Quarantined
5794
- ...quarantined.map((q) => ({
5795
- name: q.name,
5796
- source: "quarantine",
5797
- status: "quarantined",
5798
- path: q.originalPath,
5799
- description: void 0
5800
- })),
5801
- // Workspace: on disk but not approved or quarantined
5802
- ...workspaceNames.map((name) => ({
5803
- name,
5804
- source: "workspace",
5805
- status: "workspace",
5806
- path: path12.join(skillsDir2 ?? "", name),
5807
- description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, name)) : void 0
5808
- })),
5809
- // Downloaded (not installed) → available
5810
- ...availableDownloads.map((d) => ({
5811
- name: d.slug,
5812
- source: "marketplace",
5813
- status: "downloaded",
5814
- description: d.description,
5815
- path: "",
5816
- publisher: d.author
5817
- }))
5818
- ];
5819
- return reply.send({ data });
5820
- });
5821
- app.get("/skills/quarantined", async (_request, reply) => {
5822
- const quarantined = listQuarantined();
5823
- return reply.send({ quarantined });
5824
- });
5825
- app.get(
5826
- "/skills/:name",
5827
- async (request, reply) => {
5828
- const { name } = request.params;
5829
- if (!name || typeof name !== "string") {
5830
- return reply.code(400).send({ error: "Skill name is required" });
5831
- }
5832
- const analysis = getCachedAnalysis(name);
5833
- const approved = listApproved();
5834
- const entry = approved.find((s) => s.name === name);
5835
- return reply.send({
5836
- success: true,
5837
- data: {
5838
- name,
5839
- analysis: analysis ?? null,
5840
- publisher: entry?.publisher ?? null
5919
+ }
5920
+ /**
5921
+ * Make a request to the broker
5922
+ */
5923
+ async request(method, params, options) {
5924
+ const channel = options?.channel || (this.preferSocket ? "socket" : "http");
5925
+ const timeout = options?.timeout || this.timeout;
5926
+ if (channel === "socket") {
5927
+ try {
5928
+ return await this.socketRequest(method, params, timeout);
5929
+ } catch (error) {
5930
+ if (!options?.channel) {
5931
+ return await this.httpRequest_internal(method, params, timeout);
5841
5932
  }
5842
- });
5843
- }
5844
- );
5845
- app.post(
5846
- "/skills/:name/analyze",
5847
- async (request, reply) => {
5848
- const { name } = request.params;
5849
- const { content, metadata } = request.body ?? {};
5850
- if (!name || typeof name !== "string") {
5851
- return reply.code(400).send({ error: "Skill name is required" });
5852
- }
5853
- clearCachedAnalysis(name);
5854
- if (content) {
5855
- const analysis = analyzeSkill(name, content, metadata);
5856
- return reply.send({ success: true, data: { analysis } });
5933
+ throw error;
5857
5934
  }
5858
- return reply.send({
5859
- success: true,
5860
- data: {
5861
- analysis: {
5862
- status: "pending",
5863
- analyzerId: "agenshield",
5864
- commands: []
5865
- }
5866
- }
5867
- });
5935
+ } else {
5936
+ return await this.httpRequest_internal(method, params, timeout);
5868
5937
  }
5869
- );
5870
- app.post(
5871
- "/skills/:name/approve",
5872
- async (request, reply) => {
5938
+ }
5939
+ /**
5940
+ * Make a request via Unix socket
5941
+ */
5942
+ async socketRequest(method, params, timeout) {
5943
+ return new Promise((resolve3, reject) => {
5944
+ const socket = net2.createConnection(this.socketPath);
5945
+ const id = randomUUID3();
5946
+ let responseData = "";
5947
+ let timeoutId;
5948
+ socket.on("connect", () => {
5949
+ const request = {
5950
+ jsonrpc: "2.0",
5951
+ id,
5952
+ method,
5953
+ params
5954
+ };
5955
+ socket.write(JSON.stringify(request) + "\n");
5956
+ timeoutId = setTimeout(() => {
5957
+ socket.destroy();
5958
+ reject(new Error("Request timeout"));
5959
+ }, timeout);
5960
+ });
5961
+ socket.on("data", (data) => {
5962
+ responseData += data.toString();
5963
+ const newlineIndex = responseData.indexOf("\n");
5964
+ if (newlineIndex !== -1) {
5965
+ clearTimeout(timeoutId);
5966
+ socket.end();
5967
+ try {
5968
+ const response = JSON.parse(
5969
+ responseData.slice(0, newlineIndex)
5970
+ );
5971
+ if (response.error) {
5972
+ const error = new Error(response.error.message);
5973
+ error.code = response.error.code;
5974
+ reject(error);
5975
+ } else {
5976
+ resolve3(response.result);
5977
+ }
5978
+ } catch (error) {
5979
+ reject(new Error("Invalid response from broker"));
5980
+ }
5981
+ }
5982
+ });
5983
+ socket.on("error", (error) => {
5984
+ clearTimeout(timeoutId);
5985
+ reject(error);
5986
+ });
5987
+ });
5988
+ }
5989
+ /**
5990
+ * Make a request via HTTP
5991
+ */
5992
+ async httpRequest_internal(method, params, timeout) {
5993
+ const url = `http://${this.httpHost}:${this.httpPort}/rpc`;
5994
+ const id = randomUUID3();
5995
+ const request = {
5996
+ jsonrpc: "2.0",
5997
+ id,
5998
+ method,
5999
+ params
6000
+ };
6001
+ const controller = new AbortController();
6002
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
6003
+ try {
6004
+ const response = await fetch(url, {
6005
+ method: "POST",
6006
+ headers: { "Content-Type": "application/json" },
6007
+ body: JSON.stringify(request),
6008
+ signal: controller.signal
6009
+ });
6010
+ clearTimeout(timeoutId);
6011
+ if (!response.ok) {
6012
+ throw new Error(`HTTP error: ${response.status}`);
6013
+ }
6014
+ const jsonResponse = await response.json();
6015
+ if (jsonResponse.error) {
6016
+ const error = new Error(jsonResponse.error.message);
6017
+ error.code = jsonResponse.error.code;
6018
+ throw error;
6019
+ }
6020
+ return jsonResponse.result;
6021
+ } catch (error) {
6022
+ clearTimeout(timeoutId);
6023
+ if (error.name === "AbortError") {
6024
+ throw new Error("Request timeout");
6025
+ }
6026
+ throw error;
6027
+ }
6028
+ }
6029
+ };
6030
+
6031
+ // libs/shield-daemon/src/services/broker-bridge.ts
6032
+ var brokerClient = null;
6033
+ function getBrokerClient() {
6034
+ if (!brokerClient) {
6035
+ brokerClient = new BrokerClient({
6036
+ socketPath: process.env["AGENSHIELD_SOCKET"] || "/var/run/agenshield/agenshield.sock",
6037
+ httpHost: "localhost",
6038
+ httpPort: 5201,
6039
+ // Broker uses 5201, daemon uses 5200
6040
+ timeout: 6e4,
6041
+ // 60s timeout for file operations
6042
+ preferSocket: true
6043
+ });
6044
+ }
6045
+ return brokerClient;
6046
+ }
6047
+ async function isBrokerAvailable() {
6048
+ try {
6049
+ const client = getBrokerClient();
6050
+ return await client.isAvailable();
6051
+ } catch {
6052
+ return false;
6053
+ }
6054
+ }
6055
+ async function installSkillViaBroker(slug, files, options = {}) {
6056
+ const client = getBrokerClient();
6057
+ const brokerFiles = files.map((file) => ({
6058
+ name: file.name,
6059
+ content: file.content,
6060
+ base64: false
6061
+ }));
6062
+ const result = await client.skillInstall({
6063
+ slug,
6064
+ files: brokerFiles,
6065
+ createWrapper: options.createWrapper ?? true,
6066
+ agentHome: options.agentHome,
6067
+ socketGroup: options.socketGroup
6068
+ });
6069
+ return result;
6070
+ }
6071
+ async function uninstallSkillViaBroker(slug, options = {}) {
6072
+ const client = getBrokerClient();
6073
+ const result = await client.skillUninstall({
6074
+ slug,
6075
+ removeWrapper: options.removeWrapper ?? true,
6076
+ agentHome: options.agentHome
6077
+ });
6078
+ return result;
6079
+ }
6080
+
6081
+ // libs/shield-daemon/src/routes/skills.ts
6082
+ function findSkillMdRecursive(dir, depth = 0) {
6083
+ if (depth > 3) return null;
6084
+ try {
6085
+ for (const name of ["SKILL.md", "skill.md", "README.md", "readme.md"]) {
6086
+ const candidate = path12.join(dir, name);
6087
+ if (fs13.existsSync(candidate)) return candidate;
6088
+ }
6089
+ const entries = fs13.readdirSync(dir, { withFileTypes: true });
6090
+ for (const entry of entries) {
6091
+ if (!entry.isDirectory()) continue;
6092
+ const found = findSkillMdRecursive(path12.join(dir, entry.name), depth + 1);
6093
+ if (found) return found;
6094
+ }
6095
+ } catch {
6096
+ }
6097
+ return null;
6098
+ }
6099
+ function readSkillDescription(skillDir) {
6100
+ try {
6101
+ const mdPath = findSkillMdRecursive(skillDir);
6102
+ if (!mdPath) return void 0;
6103
+ const content = fs13.readFileSync(mdPath, "utf-8");
6104
+ const parsed = parseSkillMd(content);
6105
+ return parsed?.metadata?.description ?? void 0;
6106
+ } catch {
6107
+ return void 0;
6108
+ }
6109
+ }
6110
+ async function skillsRoutes(app) {
6111
+ app.get("/skills", async (_request, reply) => {
6112
+ const approved = listApproved();
6113
+ const quarantined = listQuarantined();
6114
+ const downloaded = listDownloadedSkills();
6115
+ const skillsDir2 = getSkillsDir();
6116
+ const approvedNames = new Set(approved.map((a) => a.name));
6117
+ const availableDownloads = downloaded.filter((d) => !approvedNames.has(d.slug));
6118
+ const quarantinedNames = new Set(quarantined.map((q) => q.name));
6119
+ let onDiskNames = [];
6120
+ if (skillsDir2) {
6121
+ try {
6122
+ onDiskNames = fs13.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
6123
+ } catch {
6124
+ }
6125
+ }
6126
+ const workspaceNames = onDiskNames.filter(
6127
+ (n) => !approvedNames.has(n) && !quarantinedNames.has(n)
6128
+ );
6129
+ const data = [
6130
+ // Approved → active (with descriptions from SKILL.md)
6131
+ ...approved.map((a) => ({
6132
+ name: a.name,
6133
+ source: "user",
6134
+ status: "active",
6135
+ path: path12.join(skillsDir2 ?? "", a.name),
6136
+ publisher: a.publisher,
6137
+ description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, a.name)) : void 0
6138
+ })),
6139
+ // Quarantined
6140
+ ...quarantined.map((q) => ({
6141
+ name: q.name,
6142
+ source: "quarantine",
6143
+ status: "quarantined",
6144
+ path: q.originalPath,
6145
+ description: void 0
6146
+ })),
6147
+ // Workspace: on disk but not approved or quarantined
6148
+ ...workspaceNames.map((name) => ({
6149
+ name,
6150
+ source: "workspace",
6151
+ status: "workspace",
6152
+ path: path12.join(skillsDir2 ?? "", name),
6153
+ description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, name)) : void 0
6154
+ })),
6155
+ // Downloaded (not installed) → available
6156
+ ...availableDownloads.map((d) => ({
6157
+ name: d.slug,
6158
+ source: "marketplace",
6159
+ status: "downloaded",
6160
+ description: d.description,
6161
+ path: "",
6162
+ publisher: d.author
6163
+ }))
6164
+ ];
6165
+ return reply.send({ data });
6166
+ });
6167
+ app.get("/skills/quarantined", async (_request, reply) => {
6168
+ const quarantined = listQuarantined();
6169
+ return reply.send({ quarantined });
6170
+ });
6171
+ app.get(
6172
+ "/skills/:name",
6173
+ async (request, reply) => {
6174
+ const { name } = request.params;
6175
+ if (!name || typeof name !== "string") {
6176
+ return reply.code(400).send({ error: "Skill name is required" });
6177
+ }
6178
+ const analysis = getCachedAnalysis(name);
6179
+ const approved = listApproved();
6180
+ const quarantined = listQuarantined();
6181
+ const entry = approved.find((s) => s.name === name);
6182
+ const qEntry = quarantined.find((q) => q.name === name);
6183
+ const skillsDir2 = getSkillsDir();
6184
+ let source = "user";
6185
+ let status = "active";
6186
+ let skillPath = "";
6187
+ if (qEntry) {
6188
+ source = "quarantine";
6189
+ status = "quarantined";
6190
+ skillPath = qEntry.originalPath;
6191
+ } else if (entry) {
6192
+ source = "user";
6193
+ status = "active";
6194
+ skillPath = skillsDir2 ? path12.join(skillsDir2, name) : "";
6195
+ } else if (skillsDir2) {
6196
+ source = "workspace";
6197
+ status = "workspace";
6198
+ skillPath = path12.join(skillsDir2, name);
6199
+ }
6200
+ let content = "";
6201
+ let metadata;
6202
+ const dirToRead = skillPath || (skillsDir2 ? path12.join(skillsDir2, name) : "");
6203
+ if (dirToRead) {
6204
+ try {
6205
+ const mdPath = findSkillMdRecursive(dirToRead);
6206
+ if (mdPath) {
6207
+ content = fs13.readFileSync(mdPath, "utf-8");
6208
+ const parsed = parseSkillMd(content);
6209
+ metadata = parsed?.metadata;
6210
+ }
6211
+ } catch {
6212
+ }
6213
+ }
6214
+ if (!content) {
6215
+ try {
6216
+ const localMeta = getDownloadedSkillMeta(name);
6217
+ if (localMeta) {
6218
+ const localFiles = getDownloadedSkillFiles(name);
6219
+ const readmeFile = localFiles.find((f) => /readme|skill\.md/i.test(f.name));
6220
+ if (readmeFile?.content) {
6221
+ content = readmeFile.content;
6222
+ if (!metadata) {
6223
+ const parsed = parseSkillMd(content);
6224
+ metadata = parsed?.metadata;
6225
+ }
6226
+ }
6227
+ }
6228
+ } catch {
6229
+ }
6230
+ }
6231
+ const description = dirToRead ? readSkillDescription(dirToRead) : void 0;
6232
+ if (content) {
6233
+ try {
6234
+ const cachedFiles = getDownloadedSkillFiles(name);
6235
+ if (cachedFiles.length > 0) {
6236
+ content = inlineImagesInMarkdown(content, cachedFiles);
6237
+ }
6238
+ } catch {
6239
+ }
6240
+ }
6241
+ return reply.send({
6242
+ success: true,
6243
+ data: {
6244
+ name,
6245
+ source,
6246
+ status,
6247
+ path: skillPath,
6248
+ description,
6249
+ content,
6250
+ metadata: metadata ?? null,
6251
+ analysis: analysis ?? null,
6252
+ publisher: entry?.publisher ?? null
6253
+ }
6254
+ });
6255
+ }
6256
+ );
6257
+ app.post(
6258
+ "/skills/:name/analyze",
6259
+ async (request, reply) => {
6260
+ const { name } = request.params;
6261
+ const { content, metadata } = request.body ?? {};
6262
+ if (!name || typeof name !== "string") {
6263
+ return reply.code(400).send({ error: "Skill name is required" });
6264
+ }
6265
+ clearCachedAnalysis(name);
6266
+ if (content) {
6267
+ const analysis = analyzeSkill(name, content, metadata);
6268
+ return reply.send({ success: true, data: { analysis } });
6269
+ }
6270
+ return reply.send({
6271
+ success: true,
6272
+ data: {
6273
+ analysis: {
6274
+ status: "pending",
6275
+ analyzerId: "agenshield",
6276
+ commands: []
6277
+ }
6278
+ }
6279
+ });
6280
+ }
6281
+ );
6282
+ app.post(
6283
+ "/skills/:name/approve",
6284
+ async (request, reply) => {
5873
6285
  const { name } = request.params;
5874
6286
  if (!name || typeof name !== "string") {
5875
6287
  return reply.code(400).send({ error: "Skill name is required" });
@@ -5927,8 +6339,16 @@ async function skillsRoutes(app) {
5927
6339
  const isInstalled = fs13.existsSync(skillDir);
5928
6340
  if (isInstalled) {
5929
6341
  try {
5930
- fs13.rmSync(skillDir, { recursive: true, force: true });
5931
- removeSkillWrapper(name, binDir);
6342
+ const brokerAvailable = await isBrokerAvailable();
6343
+ if (brokerAvailable) {
6344
+ await uninstallSkillViaBroker(name, {
6345
+ removeWrapper: true,
6346
+ agentHome
6347
+ });
6348
+ } else {
6349
+ fs13.rmSync(skillDir, { recursive: true, force: true });
6350
+ removeSkillWrapper(name, binDir);
6351
+ }
5932
6352
  removeSkillEntry(name);
5933
6353
  removeSkillPolicy(name);
5934
6354
  removeFromApprovedList(name);
@@ -5949,18 +6369,30 @@ async function skillsRoutes(app) {
5949
6369
  }
5950
6370
  try {
5951
6371
  addToApprovedList(name, meta.author);
5952
- fs13.mkdirSync(skillDir, { recursive: true });
5953
- for (const file of files) {
5954
- const filePath = path12.join(skillDir, file.name);
5955
- fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
5956
- fs13.writeFileSync(filePath, file.content, "utf-8");
5957
- }
5958
- try {
5959
- execSync8(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
5960
- execSync8(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
5961
- } catch {
6372
+ const brokerAvailable = await isBrokerAvailable();
6373
+ if (brokerAvailable) {
6374
+ const brokerResult = await installSkillViaBroker(
6375
+ name,
6376
+ files.map((f) => ({ name: f.name, content: f.content })),
6377
+ { createWrapper: true, agentHome, socketGroup }
6378
+ );
6379
+ if (!brokerResult.installed) {
6380
+ throw new Error("Broker failed to install skill files");
6381
+ }
6382
+ } else {
6383
+ fs13.mkdirSync(skillDir, { recursive: true });
6384
+ for (const file of files) {
6385
+ const filePath = path12.join(skillDir, file.name);
6386
+ fs13.mkdirSync(path12.dirname(filePath), { recursive: true });
6387
+ fs13.writeFileSync(filePath, file.content, "utf-8");
6388
+ }
6389
+ try {
6390
+ execSync8(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
6391
+ execSync8(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
6392
+ } catch {
6393
+ }
6394
+ createSkillWrapper(name, binDir);
5962
6395
  }
5963
- createSkillWrapper(name, binDir);
5964
6396
  addSkillEntry(name);
5965
6397
  addSkillPolicy(name);
5966
6398
  console.log(`[Skills] Enabled marketplace skill: ${name}`);
@@ -6403,47 +6835,21 @@ async function authRoutes(app) {
6403
6835
  );
6404
6836
  app.post(
6405
6837
  "/auth/enable",
6406
- async (request, reply) => {
6407
- const passcodeSet = await isPasscodeSet();
6408
- if (!passcodeSet) {
6409
- reply.code(400);
6410
- return {
6411
- success: false,
6412
- error: "Passcode not configured. Use /auth/setup first."
6413
- };
6414
- }
6415
- setProtectionEnabled(true);
6416
- return { success: true };
6417
- }
6418
- );
6419
- app.post(
6420
- "/auth/disable",
6421
- async (request, reply) => {
6422
- if (!isRunningAsRoot()) {
6423
- const token = extractToken(request);
6424
- if (!token) {
6425
- reply.code(401);
6426
- return {
6427
- success: false,
6428
- error: "Authentication required to disable protection"
6429
- };
6430
- }
6431
- const sessionManager = getSessionManager();
6432
- const session = sessionManager.validateSession(token);
6433
- if (!session) {
6434
- reply.code(401);
6435
- return {
6436
- success: false,
6437
- error: "Invalid or expired token"
6438
- };
6439
- }
6838
+ async (request, reply) => {
6839
+ const passcodeSet = await isPasscodeSet();
6840
+ if (!passcodeSet) {
6841
+ reply.code(400);
6842
+ return {
6843
+ success: false,
6844
+ error: "Passcode not configured. Use /auth/setup first."
6845
+ };
6440
6846
  }
6441
- setProtectionEnabled(false);
6847
+ setProtectionEnabled(true);
6442
6848
  return { success: true };
6443
6849
  }
6444
6850
  );
6445
6851
  app.post(
6446
- "/auth/anonymous-readonly",
6852
+ "/auth/disable",
6447
6853
  async (request, reply) => {
6448
6854
  if (!isRunningAsRoot()) {
6449
6855
  const token = extractToken(request);
@@ -6451,391 +6857,150 @@ async function authRoutes(app) {
6451
6857
  reply.code(401);
6452
6858
  return {
6453
6859
  success: false,
6454
- error: "Authentication required to change anonymous access"
6455
- };
6456
- }
6457
- const sessionManager = getSessionManager();
6458
- const session = sessionManager.validateSession(token);
6459
- if (!session) {
6460
- reply.code(401);
6461
- return {
6462
- success: false,
6463
- error: "Invalid or expired token"
6860
+ error: "Authentication required to disable protection"
6861
+ };
6862
+ }
6863
+ const sessionManager = getSessionManager();
6864
+ const session = sessionManager.validateSession(token);
6865
+ if (!session) {
6866
+ reply.code(401);
6867
+ return {
6868
+ success: false,
6869
+ error: "Invalid or expired token"
6464
6870
  };
6465
6871
  }
6466
6872
  }
6467
- const { allowed } = request.body;
6468
- if (typeof allowed !== "boolean") {
6469
- reply.code(400);
6470
- return {
6471
- success: false,
6472
- error: 'Invalid request: "allowed" must be a boolean'
6473
- };
6474
- }
6475
- setAnonymousReadOnly(allowed);
6476
- return { success: true, allowAnonymousReadOnly: allowed };
6477
- }
6478
- );
6479
- }
6480
-
6481
- // libs/shield-daemon/src/routes/secrets.ts
6482
- import { isSecretEnvVar } from "@agenshield/sandbox";
6483
- import crypto4 from "node:crypto";
6484
- function maskValue(value) {
6485
- if (value.length <= 4) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
6486
- return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + value.slice(-4);
6487
- }
6488
- function toMasked(secret) {
6489
- return {
6490
- id: secret.id,
6491
- name: secret.name,
6492
- policyIds: secret.policyIds,
6493
- maskedValue: maskValue(secret.value),
6494
- createdAt: secret.createdAt
6495
- };
6496
- }
6497
- async function secretsRoutes(app) {
6498
- app.get("/secrets", async () => {
6499
- const vault = getVault();
6500
- const secrets = await vault.get("secrets") ?? [];
6501
- return { data: secrets.map(toMasked) };
6502
- });
6503
- app.get("/secrets/env", async () => {
6504
- const names = /* @__PURE__ */ new Set();
6505
- for (const key of Object.keys(process.env)) {
6506
- if (process.env[key] && isSecretEnvVar(key)) {
6507
- names.add(key);
6508
- }
6509
- }
6510
- const userSecrets = process.env["AGENSHIELD_USER_SECRETS"];
6511
- if (userSecrets) {
6512
- for (const name of userSecrets.split(",").filter(Boolean)) {
6513
- names.add(name);
6514
- }
6515
- }
6516
- return { data: Array.from(names).sort((a, b) => a.localeCompare(b)) };
6517
- });
6518
- app.post(
6519
- "/secrets",
6520
- async (request) => {
6521
- const { name, value, policyIds } = request.body;
6522
- if (!name?.trim() || !value?.trim()) {
6523
- return { success: false, error: "Name and value are required" };
6524
- }
6525
- const vault = getVault();
6526
- const secrets = await vault.get("secrets") ?? [];
6527
- const newSecret = {
6528
- id: crypto4.randomUUID(),
6529
- name: name.trim(),
6530
- value,
6531
- policyIds: policyIds ?? [],
6532
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
6533
- };
6534
- secrets.push(newSecret);
6535
- await vault.set("secrets", secrets);
6536
- return { data: toMasked(newSecret) };
6537
- }
6538
- );
6539
- app.patch(
6540
- "/secrets/:id",
6541
- async (request) => {
6542
- const { id } = request.params;
6543
- const { policyIds } = request.body;
6544
- const vault = getVault();
6545
- const secrets = await vault.get("secrets") ?? [];
6546
- const idx = secrets.findIndex((s) => s.id === id);
6547
- if (idx === -1) return { success: false, error: "Secret not found" };
6548
- secrets[idx].policyIds = policyIds ?? [];
6549
- await vault.set("secrets", secrets);
6550
- return { data: toMasked(secrets[idx]) };
6551
- }
6552
- );
6553
- app.delete(
6554
- "/secrets/:id",
6555
- async (request) => {
6556
- const { id } = request.params;
6557
- const vault = getVault();
6558
- const secrets = await vault.get("secrets") ?? [];
6559
- const filtered = secrets.filter((s) => s.id !== id);
6560
- if (filtered.length === secrets.length) {
6561
- return { success: false, error: "Secret not found" };
6562
- }
6563
- await vault.set("secrets", filtered);
6564
- return { deleted: true };
6565
- }
6566
- );
6567
- }
6568
-
6569
- // libs/shield-daemon/src/routes/marketplace.ts
6570
- import * as fs15 from "node:fs";
6571
- import * as path14 from "node:path";
6572
-
6573
- // libs/shield-broker/dist/index.js
6574
- import { exec } from "node:child_process";
6575
- import { promisify } from "node:util";
6576
- import * as net2 from "node:net";
6577
- import { randomUUID as randomUUID3 } from "node:crypto";
6578
- var MAX_OUTPUT_SIZE = 10 * 1024 * 1024;
6579
- var execAsync = promisify(exec);
6580
- var BrokerClient = class {
6581
- socketPath;
6582
- httpHost;
6583
- httpPort;
6584
- timeout;
6585
- preferSocket;
6586
- constructor(options = {}) {
6587
- this.socketPath = options.socketPath || "/var/run/agenshield/agenshield.sock";
6588
- this.httpHost = options.httpHost || "localhost";
6589
- this.httpPort = options.httpPort || 5201;
6590
- this.timeout = options.timeout || 3e4;
6591
- this.preferSocket = options.preferSocket ?? true;
6592
- }
6593
- /**
6594
- * Make an HTTP request through the broker
6595
- */
6596
- async httpRequest(params, options) {
6597
- return this.request("http_request", params, options);
6598
- }
6599
- /**
6600
- * Read a file through the broker
6601
- */
6602
- async fileRead(params, options) {
6603
- return this.request("file_read", params, options);
6604
- }
6605
- /**
6606
- * Write a file through the broker
6607
- */
6608
- async fileWrite(params, options) {
6609
- return this.request("file_write", params, {
6610
- ...options,
6611
- channel: "socket"
6612
- // file_write only allowed via socket
6613
- });
6614
- }
6615
- /**
6616
- * List files through the broker
6617
- */
6618
- async fileList(params, options) {
6619
- return this.request("file_list", params, options);
6620
- }
6621
- /**
6622
- * Execute a command through the broker
6623
- */
6624
- async exec(params, options) {
6625
- return this.request("exec", params, {
6626
- ...options,
6627
- channel: "socket"
6628
- // exec only allowed via socket
6629
- });
6630
- }
6631
- /**
6632
- * Open a URL through the broker
6633
- */
6634
- async openUrl(params, options) {
6635
- return this.request("open_url", params, options);
6636
- }
6637
- /**
6638
- * Inject a secret through the broker
6639
- */
6640
- async secretInject(params, options) {
6641
- return this.request("secret_inject", params, {
6642
- ...options,
6643
- channel: "socket"
6644
- // secret_inject only allowed via socket
6645
- });
6646
- }
6647
- /**
6648
- * Ping the broker
6649
- */
6650
- async ping(echo, options) {
6651
- return this.request("ping", { echo }, options);
6652
- }
6653
- /**
6654
- * Install a skill through the broker
6655
- * Socket-only operation due to privileged file operations
6656
- */
6657
- async skillInstall(params, options) {
6658
- return this.request("skill_install", params, {
6659
- ...options,
6660
- channel: "socket"
6661
- // skill_install only allowed via socket
6662
- });
6663
- }
6664
- /**
6665
- * Uninstall a skill through the broker
6666
- * Socket-only operation due to privileged file operations
6667
- */
6668
- async skillUninstall(params, options) {
6669
- return this.request("skill_uninstall", params, {
6670
- ...options,
6671
- channel: "socket"
6672
- // skill_uninstall only allowed via socket
6673
- });
6674
- }
6675
- /**
6676
- * Check if the broker is available
6677
- */
6678
- async isAvailable() {
6679
- try {
6680
- await this.ping();
6681
- return true;
6682
- } catch {
6683
- return false;
6684
- }
6685
- }
6686
- /**
6687
- * Make a request to the broker
6688
- */
6689
- async request(method, params, options) {
6690
- const channel = options?.channel || (this.preferSocket ? "socket" : "http");
6691
- const timeout = options?.timeout || this.timeout;
6692
- if (channel === "socket") {
6693
- try {
6694
- return await this.socketRequest(method, params, timeout);
6695
- } catch (error) {
6696
- if (!options?.channel) {
6697
- return await this.httpRequest_internal(method, params, timeout);
6698
- }
6699
- throw error;
6700
- }
6701
- } else {
6702
- return await this.httpRequest_internal(method, params, timeout);
6873
+ setProtectionEnabled(false);
6874
+ return { success: true };
6703
6875
  }
6704
- }
6705
- /**
6706
- * Make a request via Unix socket
6707
- */
6708
- async socketRequest(method, params, timeout) {
6709
- return new Promise((resolve3, reject) => {
6710
- const socket = net2.createConnection(this.socketPath);
6711
- const id = randomUUID3();
6712
- let responseData = "";
6713
- let timeoutId;
6714
- socket.on("connect", () => {
6715
- const request = {
6716
- jsonrpc: "2.0",
6717
- id,
6718
- method,
6719
- params
6720
- };
6721
- socket.write(JSON.stringify(request) + "\n");
6722
- timeoutId = setTimeout(() => {
6723
- socket.destroy();
6724
- reject(new Error("Request timeout"));
6725
- }, timeout);
6726
- });
6727
- socket.on("data", (data) => {
6728
- responseData += data.toString();
6729
- const newlineIndex = responseData.indexOf("\n");
6730
- if (newlineIndex !== -1) {
6731
- clearTimeout(timeoutId);
6732
- socket.end();
6733
- try {
6734
- const response = JSON.parse(
6735
- responseData.slice(0, newlineIndex)
6736
- );
6737
- if (response.error) {
6738
- const error = new Error(response.error.message);
6739
- error.code = response.error.code;
6740
- reject(error);
6741
- } else {
6742
- resolve3(response.result);
6743
- }
6744
- } catch (error) {
6745
- reject(new Error("Invalid response from broker"));
6746
- }
6876
+ );
6877
+ app.post(
6878
+ "/auth/anonymous-readonly",
6879
+ async (request, reply) => {
6880
+ if (!isRunningAsRoot()) {
6881
+ const token = extractToken(request);
6882
+ if (!token) {
6883
+ reply.code(401);
6884
+ return {
6885
+ success: false,
6886
+ error: "Authentication required to change anonymous access"
6887
+ };
6888
+ }
6889
+ const sessionManager = getSessionManager();
6890
+ const session = sessionManager.validateSession(token);
6891
+ if (!session) {
6892
+ reply.code(401);
6893
+ return {
6894
+ success: false,
6895
+ error: "Invalid or expired token"
6896
+ };
6747
6897
  }
6748
- });
6749
- socket.on("error", (error) => {
6750
- clearTimeout(timeoutId);
6751
- reject(error);
6752
- });
6753
- });
6754
- }
6755
- /**
6756
- * Make a request via HTTP
6757
- */
6758
- async httpRequest_internal(method, params, timeout) {
6759
- const url = `http://${this.httpHost}:${this.httpPort}/rpc`;
6760
- const id = randomUUID3();
6761
- const request = {
6762
- jsonrpc: "2.0",
6763
- id,
6764
- method,
6765
- params
6766
- };
6767
- const controller = new AbortController();
6768
- const timeoutId = setTimeout(() => controller.abort(), timeout);
6769
- try {
6770
- const response = await fetch(url, {
6771
- method: "POST",
6772
- headers: { "Content-Type": "application/json" },
6773
- body: JSON.stringify(request),
6774
- signal: controller.signal
6775
- });
6776
- clearTimeout(timeoutId);
6777
- if (!response.ok) {
6778
- throw new Error(`HTTP error: ${response.status}`);
6779
- }
6780
- const jsonResponse = await response.json();
6781
- if (jsonResponse.error) {
6782
- const error = new Error(jsonResponse.error.message);
6783
- error.code = jsonResponse.error.code;
6784
- throw error;
6785
6898
  }
6786
- return jsonResponse.result;
6787
- } catch (error) {
6788
- clearTimeout(timeoutId);
6789
- if (error.name === "AbortError") {
6790
- throw new Error("Request timeout");
6899
+ const { allowed } = request.body;
6900
+ if (typeof allowed !== "boolean") {
6901
+ reply.code(400);
6902
+ return {
6903
+ success: false,
6904
+ error: 'Invalid request: "allowed" must be a boolean'
6905
+ };
6791
6906
  }
6792
- throw error;
6907
+ setAnonymousReadOnly(allowed);
6908
+ return { success: true, allowAnonymousReadOnly: allowed };
6793
6909
  }
6794
- }
6795
- };
6910
+ );
6911
+ }
6796
6912
 
6797
- // libs/shield-daemon/src/services/broker-bridge.ts
6798
- var brokerClient = null;
6799
- function getBrokerClient() {
6800
- if (!brokerClient) {
6801
- brokerClient = new BrokerClient({
6802
- socketPath: process.env["AGENSHIELD_SOCKET"] || "/var/run/agenshield/agenshield.sock",
6803
- httpHost: "localhost",
6804
- httpPort: 5201,
6805
- // Broker uses 5201, daemon uses 5200
6806
- timeout: 6e4,
6807
- // 60s timeout for file operations
6808
- preferSocket: true
6809
- });
6810
- }
6811
- return brokerClient;
6913
+ // libs/shield-daemon/src/routes/secrets.ts
6914
+ import { isSecretEnvVar } from "@agenshield/sandbox";
6915
+ import crypto4 from "node:crypto";
6916
+ function maskValue(value) {
6917
+ if (value.length <= 4) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
6918
+ return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + value.slice(-4);
6812
6919
  }
6813
- async function isBrokerAvailable() {
6814
- try {
6815
- const client = getBrokerClient();
6816
- return await client.isAvailable();
6817
- } catch {
6818
- return false;
6819
- }
6920
+ function toMasked(secret) {
6921
+ return {
6922
+ id: secret.id,
6923
+ name: secret.name,
6924
+ policyIds: secret.policyIds,
6925
+ maskedValue: maskValue(secret.value),
6926
+ createdAt: secret.createdAt
6927
+ };
6820
6928
  }
6821
- async function installSkillViaBroker(slug, files, options = {}) {
6822
- const client = getBrokerClient();
6823
- const brokerFiles = files.map((file) => ({
6824
- name: file.name,
6825
- content: file.content,
6826
- base64: false
6827
- }));
6828
- const result = await client.skillInstall({
6829
- slug,
6830
- files: brokerFiles,
6831
- createWrapper: options.createWrapper ?? true,
6832
- agentHome: options.agentHome,
6833
- socketGroup: options.socketGroup
6929
+ async function secretsRoutes(app) {
6930
+ app.get("/secrets", async () => {
6931
+ const vault = getVault();
6932
+ const secrets = await vault.get("secrets") ?? [];
6933
+ return { data: secrets.map(toMasked) };
6834
6934
  });
6835
- return result;
6935
+ app.get("/secrets/env", async () => {
6936
+ const names = /* @__PURE__ */ new Set();
6937
+ for (const key of Object.keys(process.env)) {
6938
+ if (process.env[key] && isSecretEnvVar(key)) {
6939
+ names.add(key);
6940
+ }
6941
+ }
6942
+ const userSecrets = process.env["AGENSHIELD_USER_SECRETS"];
6943
+ if (userSecrets) {
6944
+ for (const name of userSecrets.split(",").filter(Boolean)) {
6945
+ names.add(name);
6946
+ }
6947
+ }
6948
+ return { data: Array.from(names).sort((a, b) => a.localeCompare(b)) };
6949
+ });
6950
+ app.post(
6951
+ "/secrets",
6952
+ async (request) => {
6953
+ const { name, value, policyIds } = request.body;
6954
+ if (!name?.trim() || !value?.trim()) {
6955
+ return { success: false, error: "Name and value are required" };
6956
+ }
6957
+ const vault = getVault();
6958
+ const secrets = await vault.get("secrets") ?? [];
6959
+ const newSecret = {
6960
+ id: crypto4.randomUUID(),
6961
+ name: name.trim(),
6962
+ value,
6963
+ policyIds: policyIds ?? [],
6964
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
6965
+ };
6966
+ secrets.push(newSecret);
6967
+ await vault.set("secrets", secrets);
6968
+ return { data: toMasked(newSecret) };
6969
+ }
6970
+ );
6971
+ app.patch(
6972
+ "/secrets/:id",
6973
+ async (request) => {
6974
+ const { id } = request.params;
6975
+ const { policyIds } = request.body;
6976
+ const vault = getVault();
6977
+ const secrets = await vault.get("secrets") ?? [];
6978
+ const idx = secrets.findIndex((s) => s.id === id);
6979
+ if (idx === -1) return { success: false, error: "Secret not found" };
6980
+ secrets[idx].policyIds = policyIds ?? [];
6981
+ await vault.set("secrets", secrets);
6982
+ return { data: toMasked(secrets[idx]) };
6983
+ }
6984
+ );
6985
+ app.delete(
6986
+ "/secrets/:id",
6987
+ async (request) => {
6988
+ const { id } = request.params;
6989
+ const vault = getVault();
6990
+ const secrets = await vault.get("secrets") ?? [];
6991
+ const filtered = secrets.filter((s) => s.id !== id);
6992
+ if (filtered.length === secrets.length) {
6993
+ return { success: false, error: "Secret not found" };
6994
+ }
6995
+ await vault.set("secrets", filtered);
6996
+ return { deleted: true };
6997
+ }
6998
+ );
6836
6999
  }
6837
7000
 
6838
7001
  // libs/shield-daemon/src/routes/marketplace.ts
7002
+ import * as fs15 from "node:fs";
7003
+ import * as path14 from "node:path";
6839
7004
  async function marketplaceRoutes(app) {
6840
7005
  app.get(
6841
7006
  "/marketplace/search",
@@ -6871,6 +7036,10 @@ async function marketplaceRoutes(app) {
6871
7036
  if (localMeta) {
6872
7037
  const localFiles = getDownloadedSkillFiles(slug);
6873
7038
  const readmeFile = localFiles.find((f) => /readme|skill\.md/i.test(f.name));
7039
+ let readme = readmeFile?.content;
7040
+ if (readme) {
7041
+ readme = inlineImagesInMarkdown(readme, localFiles);
7042
+ }
6874
7043
  const skill2 = {
6875
7044
  name: localMeta.name,
6876
7045
  slug: localMeta.slug,
@@ -6880,7 +7049,7 @@ async function marketplaceRoutes(app) {
6880
7049
  installs: 0,
6881
7050
  // Not stored locally
6882
7051
  tags: localMeta.tags,
6883
- readme: readmeFile?.content,
7052
+ readme,
6884
7053
  files: localFiles
6885
7054
  };
6886
7055
  if (localMeta.analysis) {