@agenshield/daemon 0.4.2 → 0.4.4

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
@@ -4986,7 +4986,7 @@ async function agencoRoutes(app) {
4986
4986
  // libs/shield-daemon/src/routes/skills.ts
4987
4987
  import * as fs13 from "node:fs";
4988
4988
  import * as path12 from "node:path";
4989
- import { execSync as execSync7 } from "node:child_process";
4989
+ import { execSync as execSync8 } from "node:child_process";
4990
4990
  import { parseSkillMd } from "@agenshield/sandbox";
4991
4991
 
4992
4992
  // libs/shield-daemon/src/services/skill-analyzer.ts
@@ -5269,6 +5269,7 @@ function removeSkillPolicy(name) {
5269
5269
  // libs/shield-daemon/src/services/openclaw-config.ts
5270
5270
  import * as fs11 from "node:fs";
5271
5271
  import * as path10 from "node:path";
5272
+ import { execSync as execSync7 } from "node:child_process";
5272
5273
  function getOpenClawConfigPath() {
5273
5274
  const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
5274
5275
  return path10.join(agentHome, ".openclaw", "openclaw.json");
@@ -5286,7 +5287,16 @@ function readConfig() {
5286
5287
  }
5287
5288
  function writeConfig(config) {
5288
5289
  const configPath = getOpenClawConfigPath();
5290
+ fs11.mkdirSync(path10.dirname(configPath), { recursive: true });
5289
5291
  fs11.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
5292
+ const agentHome = process.env["AGENSHIELD_AGENT_HOME"] || "/Users/ash_default_agent";
5293
+ const brokerUser = path10.basename(agentHome) + "_broker";
5294
+ const socketGroup = process.env["AGENSHIELD_SOCKET_GROUP"] || "clawshield";
5295
+ try {
5296
+ execSync7(`chown ${brokerUser}:${socketGroup} "${configPath}"`, { stdio: "pipe" });
5297
+ execSync7(`chmod 664 "${configPath}"`, { stdio: "pipe" });
5298
+ } catch {
5299
+ }
5290
5300
  }
5291
5301
  function addSkillEntry(slug, env) {
5292
5302
  const config = readConfig();
@@ -5740,23 +5750,45 @@ async function getCachedAnalysis2(skillName, publisher) {
5740
5750
  }
5741
5751
 
5742
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;
5762
+ }
5763
+ }
5743
5764
  async function skillsRoutes(app) {
5744
5765
  app.get("/skills", async (_request, reply) => {
5745
5766
  const approved = listApproved();
5746
5767
  const quarantined = listQuarantined();
5747
5768
  const downloaded = listDownloadedSkills();
5769
+ const skillsDir2 = getSkillsDir();
5748
5770
  const approvedNames = new Set(approved.map((a) => a.name));
5749
5771
  const availableDownloads = downloaded.filter((d) => !approvedNames.has(d.slug));
5750
- const skillsDir2 = getSkillsDir();
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
+ }
5779
+ }
5780
+ const workspaceNames = onDiskNames.filter(
5781
+ (n) => !approvedNames.has(n) && !quarantinedNames.has(n)
5782
+ );
5751
5783
  const data = [
5752
- // Approved → active
5784
+ // Approved → active (with descriptions from SKILL.md)
5753
5785
  ...approved.map((a) => ({
5754
5786
  name: a.name,
5755
5787
  source: "user",
5756
5788
  status: "active",
5757
5789
  path: path12.join(skillsDir2 ?? "", a.name),
5758
5790
  publisher: a.publisher,
5759
- description: void 0
5791
+ description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, a.name)) : void 0
5760
5792
  })),
5761
5793
  // Quarantined
5762
5794
  ...quarantined.map((q) => ({
@@ -5766,6 +5798,14 @@ async function skillsRoutes(app) {
5766
5798
  path: q.originalPath,
5767
5799
  description: void 0
5768
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
+ })),
5769
5809
  // Downloaded (not installed) → available
5770
5810
  ...availableDownloads.map((d) => ({
5771
5811
  name: d.slug,
@@ -5916,8 +5956,8 @@ async function skillsRoutes(app) {
5916
5956
  fs13.writeFileSync(filePath, file.content, "utf-8");
5917
5957
  }
5918
5958
  try {
5919
- execSync7(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
5920
- execSync7(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
5959
+ execSync8(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
5960
+ execSync8(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
5921
5961
  } catch {
5922
5962
  }
5923
5963
  createSkillWrapper(name, binDir);
@@ -5978,8 +6018,8 @@ async function skillsRoutes(app) {
5978
6018
  fs13.writeFileSync(filePath, file.content, "utf-8");
5979
6019
  }
5980
6020
  try {
5981
- execSync7(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
5982
- execSync7(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
6021
+ execSync8(`chown -R root:${socketGroup} "${skillDir}"`, { stdio: "pipe" });
6022
+ execSync8(`chmod -R a+rX,go-w "${skillDir}"`, { stdio: "pipe" });
5983
6023
  } catch {
5984
6024
  }
5985
6025
  createSkillWrapper(name, binDir);
@@ -7015,8 +7055,6 @@ async function marketplaceRoutes(app) {
7015
7055
  if (brokerResult.wrapperPath) {
7016
7056
  logs.push(`Wrapper created: ${brokerResult.wrapperPath}`);
7017
7057
  }
7018
- addSkillEntry(slug);
7019
- logs.push("Config entry added");
7020
7058
  addSkillPolicy(slug);
7021
7059
  logs.push("Policy rule added");
7022
7060
  emitSkillInstallProgress(slug, "local_analysis", "Running local analysis");
@@ -7091,6 +7129,120 @@ async function fsRoutes(app) {
7091
7129
  });
7092
7130
  }
7093
7131
 
7132
+ // libs/shield-daemon/src/services/activity-log.ts
7133
+ import * as fs17 from "node:fs";
7134
+ import * as path16 from "node:path";
7135
+ var ACTIVITY_FILE = "activity.jsonl";
7136
+ var MAX_SIZE_BYTES = 100 * 1024 * 1024;
7137
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7138
+ var PRUNE_INTERVAL = 1e3;
7139
+ var instance = null;
7140
+ function getActivityLog() {
7141
+ if (!instance) {
7142
+ instance = new ActivityLog();
7143
+ }
7144
+ return instance;
7145
+ }
7146
+ var ActivityLog = class {
7147
+ filePath;
7148
+ writeCount = 0;
7149
+ unsubscribe;
7150
+ constructor() {
7151
+ this.filePath = path16.join(getConfigDir(), ACTIVITY_FILE);
7152
+ }
7153
+ /** Read historical events from the JSONL file, newest first */
7154
+ getHistory(limit = 500) {
7155
+ if (!fs17.existsSync(this.filePath)) return [];
7156
+ const content = fs17.readFileSync(this.filePath, "utf-8");
7157
+ const lines = content.split("\n").filter(Boolean);
7158
+ const events = [];
7159
+ for (const line of lines) {
7160
+ try {
7161
+ const evt = JSON.parse(line);
7162
+ if (evt.type === "heartbeat") continue;
7163
+ events.push(evt);
7164
+ } catch {
7165
+ }
7166
+ }
7167
+ events.reverse();
7168
+ return events.slice(0, limit);
7169
+ }
7170
+ start() {
7171
+ this.pruneOldEntries();
7172
+ this.unsubscribe = daemonEvents.subscribe((event) => {
7173
+ this.append(event);
7174
+ });
7175
+ }
7176
+ stop() {
7177
+ this.unsubscribe?.();
7178
+ }
7179
+ append(event) {
7180
+ const line = JSON.stringify(event) + "\n";
7181
+ fs17.appendFileSync(this.filePath, line, "utf-8");
7182
+ this.writeCount++;
7183
+ if (this.writeCount % PRUNE_INTERVAL === 0) {
7184
+ this.rotate();
7185
+ }
7186
+ }
7187
+ rotate() {
7188
+ try {
7189
+ const stat = fs17.statSync(this.filePath);
7190
+ if (stat.size > MAX_SIZE_BYTES) {
7191
+ this.truncateBySize();
7192
+ }
7193
+ } catch {
7194
+ }
7195
+ this.pruneOldEntries();
7196
+ }
7197
+ /** Keep newest half of lines when file exceeds size limit */
7198
+ truncateBySize() {
7199
+ const content = fs17.readFileSync(this.filePath, "utf-8");
7200
+ const lines = content.split("\n").filter(Boolean);
7201
+ const keep = lines.slice(Math.floor(lines.length / 2));
7202
+ fs17.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
7203
+ }
7204
+ /** Remove entries older than 24 hours */
7205
+ pruneOldEntries() {
7206
+ if (!fs17.existsSync(this.filePath)) return;
7207
+ const content = fs17.readFileSync(this.filePath, "utf-8");
7208
+ const lines = content.split("\n").filter(Boolean);
7209
+ const cutoff = Date.now() - MAX_AGE_MS;
7210
+ const kept = lines.filter((line) => {
7211
+ try {
7212
+ const evt = JSON.parse(line);
7213
+ return new Date(evt.timestamp).getTime() >= cutoff;
7214
+ } catch {
7215
+ return false;
7216
+ }
7217
+ });
7218
+ if (kept.length < lines.length) {
7219
+ fs17.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
7220
+ }
7221
+ }
7222
+ };
7223
+
7224
+ // libs/shield-daemon/src/routes/activity.ts
7225
+ async function activityRoutes(app) {
7226
+ app.get(
7227
+ "/activity",
7228
+ async (request) => {
7229
+ const authenticated = isAuthenticated(request);
7230
+ const raw = Number(request.query.limit) || 500;
7231
+ const limit = Math.min(Math.max(raw, 1), 1e4);
7232
+ const events = getActivityLog().getHistory(limit);
7233
+ if (authenticated) {
7234
+ return { data: events };
7235
+ }
7236
+ const stripped = events.map((e) => ({
7237
+ type: e.type,
7238
+ timestamp: e.timestamp,
7239
+ data: {}
7240
+ }));
7241
+ return { data: stripped };
7242
+ }
7243
+ );
7244
+ }
7245
+
7094
7246
  // libs/shield-daemon/src/routes/rpc.ts
7095
7247
  function globToRegex(pattern) {
7096
7248
  const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
@@ -7289,7 +7441,7 @@ async function registerRoutes(app) {
7289
7441
  });
7290
7442
  app.addHook("onResponse", (request, reply, done) => {
7291
7443
  if (!request.url.startsWith("/sse") && !request.url.startsWith("/rpc") && !request.url.includes(".") && !request.url.endsWith("/health")) {
7292
- if (request.method === "GET" && request.url.startsWith("/api/status") && reply.statusCode === 200) {
7444
+ if (request.method === "GET" && (request.url.startsWith("/api/status") || request.url.startsWith("/api/activity")) && reply.statusCode === 200) {
7293
7445
  done();
7294
7446
  return;
7295
7447
  }
@@ -7342,28 +7494,29 @@ async function registerRoutes(app) {
7342
7494
  await api.register(secretsRoutes);
7343
7495
  await api.register(marketplaceRoutes);
7344
7496
  await api.register(fsRoutes);
7497
+ await api.register(activityRoutes);
7345
7498
  },
7346
7499
  { prefix: API_PREFIX }
7347
7500
  );
7348
7501
  }
7349
7502
 
7350
7503
  // libs/shield-daemon/src/static.ts
7351
- import * as fs17 from "node:fs";
7352
- import * as path16 from "node:path";
7504
+ import * as fs18 from "node:fs";
7505
+ import * as path17 from "node:path";
7353
7506
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7354
7507
  var __filename = fileURLToPath2(import.meta.url);
7355
- var __dirname = path16.dirname(__filename);
7508
+ var __dirname = path17.dirname(__filename);
7356
7509
  function getUiAssetsPath() {
7357
- const pkgRootPath = path16.join(__dirname, "..", "ui-assets");
7358
- if (fs17.existsSync(pkgRootPath)) {
7510
+ const pkgRootPath = path17.join(__dirname, "..", "ui-assets");
7511
+ if (fs18.existsSync(pkgRootPath)) {
7359
7512
  return pkgRootPath;
7360
7513
  }
7361
- const bundledPath = path16.join(__dirname, "ui-assets");
7362
- if (fs17.existsSync(bundledPath)) {
7514
+ const bundledPath = path17.join(__dirname, "ui-assets");
7515
+ if (fs18.existsSync(bundledPath)) {
7363
7516
  return bundledPath;
7364
7517
  }
7365
- const devPath = path16.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
7366
- if (fs17.existsSync(devPath)) {
7518
+ const devPath = path17.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
7519
+ if (fs18.existsSync(devPath)) {
7367
7520
  return devPath;
7368
7521
  }
7369
7522
  return null;
@@ -7422,74 +7575,6 @@ function stopSecurityWatcher() {
7422
7575
  }
7423
7576
  }
7424
7577
 
7425
- // libs/shield-daemon/src/services/activity-log.ts
7426
- import * as fs18 from "node:fs";
7427
- import * as path17 from "node:path";
7428
- var ACTIVITY_FILE = "activity.jsonl";
7429
- var MAX_SIZE_BYTES = 100 * 1024 * 1024;
7430
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7431
- var PRUNE_INTERVAL = 1e3;
7432
- var ActivityLog = class {
7433
- filePath;
7434
- writeCount = 0;
7435
- unsubscribe;
7436
- constructor() {
7437
- this.filePath = path17.join(getConfigDir(), ACTIVITY_FILE);
7438
- }
7439
- start() {
7440
- this.pruneOldEntries();
7441
- this.unsubscribe = daemonEvents.subscribe((event) => {
7442
- this.append(event);
7443
- });
7444
- }
7445
- stop() {
7446
- this.unsubscribe?.();
7447
- }
7448
- append(event) {
7449
- const line = JSON.stringify(event) + "\n";
7450
- fs18.appendFileSync(this.filePath, line, "utf-8");
7451
- this.writeCount++;
7452
- if (this.writeCount % PRUNE_INTERVAL === 0) {
7453
- this.rotate();
7454
- }
7455
- }
7456
- rotate() {
7457
- try {
7458
- const stat = fs18.statSync(this.filePath);
7459
- if (stat.size > MAX_SIZE_BYTES) {
7460
- this.truncateBySize();
7461
- }
7462
- } catch {
7463
- }
7464
- this.pruneOldEntries();
7465
- }
7466
- /** Keep newest half of lines when file exceeds size limit */
7467
- truncateBySize() {
7468
- const content = fs18.readFileSync(this.filePath, "utf-8");
7469
- const lines = content.split("\n").filter(Boolean);
7470
- const keep = lines.slice(Math.floor(lines.length / 2));
7471
- fs18.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
7472
- }
7473
- /** Remove entries older than 24 hours */
7474
- pruneOldEntries() {
7475
- if (!fs18.existsSync(this.filePath)) return;
7476
- const content = fs18.readFileSync(this.filePath, "utf-8");
7477
- const lines = content.split("\n").filter(Boolean);
7478
- const cutoff = Date.now() - MAX_AGE_MS;
7479
- const kept = lines.filter((line) => {
7480
- try {
7481
- const evt = JSON.parse(line);
7482
- return new Date(evt.timestamp).getTime() >= cutoff;
7483
- } catch {
7484
- return false;
7485
- }
7486
- });
7487
- if (kept.length < lines.length) {
7488
- fs18.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
7489
- }
7490
- }
7491
- };
7492
-
7493
7578
  // libs/shield-daemon/src/server.ts
7494
7579
  async function createServer(config) {
7495
7580
  const app = Fastify({
@@ -7528,7 +7613,7 @@ async function startServer(config) {
7528
7613
  onQuarantined: (info) => emitSkillQuarantined(info.name, info.reason),
7529
7614
  onApproved: (name) => emitSkillApproved(name)
7530
7615
  }, 3e4);
7531
- const activityLog = new ActivityLog();
7616
+ const activityLog = getActivityLog();
7532
7617
  activityLog.start();
7533
7618
  try {
7534
7619
  const vault = getVault();