@agenshield/daemon 0.4.3 → 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
@@ -5750,23 +5750,45 @@ async function getCachedAnalysis2(skillName, publisher) {
5750
5750
  }
5751
5751
 
5752
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
+ }
5753
5764
  async function skillsRoutes(app) {
5754
5765
  app.get("/skills", async (_request, reply) => {
5755
5766
  const approved = listApproved();
5756
5767
  const quarantined = listQuarantined();
5757
5768
  const downloaded = listDownloadedSkills();
5769
+ const skillsDir2 = getSkillsDir();
5758
5770
  const approvedNames = new Set(approved.map((a) => a.name));
5759
5771
  const availableDownloads = downloaded.filter((d) => !approvedNames.has(d.slug));
5760
- 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
+ );
5761
5783
  const data = [
5762
- // Approved → active
5784
+ // Approved → active (with descriptions from SKILL.md)
5763
5785
  ...approved.map((a) => ({
5764
5786
  name: a.name,
5765
5787
  source: "user",
5766
5788
  status: "active",
5767
5789
  path: path12.join(skillsDir2 ?? "", a.name),
5768
5790
  publisher: a.publisher,
5769
- description: void 0
5791
+ description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, a.name)) : void 0
5770
5792
  })),
5771
5793
  // Quarantined
5772
5794
  ...quarantined.map((q) => ({
@@ -5776,6 +5798,14 @@ async function skillsRoutes(app) {
5776
5798
  path: q.originalPath,
5777
5799
  description: void 0
5778
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
+ })),
5779
5809
  // Downloaded (not installed) → available
5780
5810
  ...availableDownloads.map((d) => ({
5781
5811
  name: d.slug,
@@ -7099,6 +7129,120 @@ async function fsRoutes(app) {
7099
7129
  });
7100
7130
  }
7101
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
+
7102
7246
  // libs/shield-daemon/src/routes/rpc.ts
7103
7247
  function globToRegex(pattern) {
7104
7248
  const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
@@ -7297,7 +7441,7 @@ async function registerRoutes(app) {
7297
7441
  });
7298
7442
  app.addHook("onResponse", (request, reply, done) => {
7299
7443
  if (!request.url.startsWith("/sse") && !request.url.startsWith("/rpc") && !request.url.includes(".") && !request.url.endsWith("/health")) {
7300
- 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) {
7301
7445
  done();
7302
7446
  return;
7303
7447
  }
@@ -7350,28 +7494,29 @@ async function registerRoutes(app) {
7350
7494
  await api.register(secretsRoutes);
7351
7495
  await api.register(marketplaceRoutes);
7352
7496
  await api.register(fsRoutes);
7497
+ await api.register(activityRoutes);
7353
7498
  },
7354
7499
  { prefix: API_PREFIX }
7355
7500
  );
7356
7501
  }
7357
7502
 
7358
7503
  // libs/shield-daemon/src/static.ts
7359
- import * as fs17 from "node:fs";
7360
- import * as path16 from "node:path";
7504
+ import * as fs18 from "node:fs";
7505
+ import * as path17 from "node:path";
7361
7506
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7362
7507
  var __filename = fileURLToPath2(import.meta.url);
7363
- var __dirname = path16.dirname(__filename);
7508
+ var __dirname = path17.dirname(__filename);
7364
7509
  function getUiAssetsPath() {
7365
- const pkgRootPath = path16.join(__dirname, "..", "ui-assets");
7366
- if (fs17.existsSync(pkgRootPath)) {
7510
+ const pkgRootPath = path17.join(__dirname, "..", "ui-assets");
7511
+ if (fs18.existsSync(pkgRootPath)) {
7367
7512
  return pkgRootPath;
7368
7513
  }
7369
- const bundledPath = path16.join(__dirname, "ui-assets");
7370
- if (fs17.existsSync(bundledPath)) {
7514
+ const bundledPath = path17.join(__dirname, "ui-assets");
7515
+ if (fs18.existsSync(bundledPath)) {
7371
7516
  return bundledPath;
7372
7517
  }
7373
- const devPath = path16.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
7374
- if (fs17.existsSync(devPath)) {
7518
+ const devPath = path17.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
7519
+ if (fs18.existsSync(devPath)) {
7375
7520
  return devPath;
7376
7521
  }
7377
7522
  return null;
@@ -7430,74 +7575,6 @@ function stopSecurityWatcher() {
7430
7575
  }
7431
7576
  }
7432
7577
 
7433
- // libs/shield-daemon/src/services/activity-log.ts
7434
- import * as fs18 from "node:fs";
7435
- import * as path17 from "node:path";
7436
- var ACTIVITY_FILE = "activity.jsonl";
7437
- var MAX_SIZE_BYTES = 100 * 1024 * 1024;
7438
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7439
- var PRUNE_INTERVAL = 1e3;
7440
- var ActivityLog = class {
7441
- filePath;
7442
- writeCount = 0;
7443
- unsubscribe;
7444
- constructor() {
7445
- this.filePath = path17.join(getConfigDir(), ACTIVITY_FILE);
7446
- }
7447
- start() {
7448
- this.pruneOldEntries();
7449
- this.unsubscribe = daemonEvents.subscribe((event) => {
7450
- this.append(event);
7451
- });
7452
- }
7453
- stop() {
7454
- this.unsubscribe?.();
7455
- }
7456
- append(event) {
7457
- const line = JSON.stringify(event) + "\n";
7458
- fs18.appendFileSync(this.filePath, line, "utf-8");
7459
- this.writeCount++;
7460
- if (this.writeCount % PRUNE_INTERVAL === 0) {
7461
- this.rotate();
7462
- }
7463
- }
7464
- rotate() {
7465
- try {
7466
- const stat = fs18.statSync(this.filePath);
7467
- if (stat.size > MAX_SIZE_BYTES) {
7468
- this.truncateBySize();
7469
- }
7470
- } catch {
7471
- }
7472
- this.pruneOldEntries();
7473
- }
7474
- /** Keep newest half of lines when file exceeds size limit */
7475
- truncateBySize() {
7476
- const content = fs18.readFileSync(this.filePath, "utf-8");
7477
- const lines = content.split("\n").filter(Boolean);
7478
- const keep = lines.slice(Math.floor(lines.length / 2));
7479
- fs18.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
7480
- }
7481
- /** Remove entries older than 24 hours */
7482
- pruneOldEntries() {
7483
- if (!fs18.existsSync(this.filePath)) return;
7484
- const content = fs18.readFileSync(this.filePath, "utf-8");
7485
- const lines = content.split("\n").filter(Boolean);
7486
- const cutoff = Date.now() - MAX_AGE_MS;
7487
- const kept = lines.filter((line) => {
7488
- try {
7489
- const evt = JSON.parse(line);
7490
- return new Date(evt.timestamp).getTime() >= cutoff;
7491
- } catch {
7492
- return false;
7493
- }
7494
- });
7495
- if (kept.length < lines.length) {
7496
- fs18.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
7497
- }
7498
- }
7499
- };
7500
-
7501
7578
  // libs/shield-daemon/src/server.ts
7502
7579
  async function createServer(config) {
7503
7580
  const app = Fastify({
@@ -7536,7 +7613,7 @@ async function startServer(config) {
7536
7613
  onQuarantined: (info) => emitSkillQuarantined(info.name, info.reason),
7537
7614
  onApproved: (name) => emitSkillApproved(name)
7538
7615
  }, 3e4);
7539
- const activityLog = new ActivityLog();
7616
+ const activityLog = getActivityLog();
7540
7617
  activityLog.start();
7541
7618
  try {
7542
7619
  const vault = getVault();
package/main.js CHANGED
@@ -5714,23 +5714,45 @@ async function getCachedAnalysis2(skillName, publisher) {
5714
5714
  }
5715
5715
 
5716
5716
  // libs/shield-daemon/src/routes/skills.ts
5717
+ function readSkillDescription(skillDir) {
5718
+ try {
5719
+ const skillMdPath = path12.join(skillDir, "SKILL.md");
5720
+ if (!fs13.existsSync(skillMdPath)) return void 0;
5721
+ const content = fs13.readFileSync(skillMdPath, "utf-8");
5722
+ const parsed = parseSkillMd(content);
5723
+ return parsed?.metadata?.description ?? void 0;
5724
+ } catch {
5725
+ return void 0;
5726
+ }
5727
+ }
5717
5728
  async function skillsRoutes(app) {
5718
5729
  app.get("/skills", async (_request, reply) => {
5719
5730
  const approved = listApproved();
5720
5731
  const quarantined = listQuarantined();
5721
5732
  const downloaded = listDownloadedSkills();
5733
+ const skillsDir2 = getSkillsDir();
5722
5734
  const approvedNames = new Set(approved.map((a) => a.name));
5723
5735
  const availableDownloads = downloaded.filter((d) => !approvedNames.has(d.slug));
5724
- const skillsDir2 = getSkillsDir();
5736
+ const quarantinedNames = new Set(quarantined.map((q) => q.name));
5737
+ let onDiskNames = [];
5738
+ if (skillsDir2) {
5739
+ try {
5740
+ onDiskNames = fs13.readdirSync(skillsDir2, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
5741
+ } catch {
5742
+ }
5743
+ }
5744
+ const workspaceNames = onDiskNames.filter(
5745
+ (n) => !approvedNames.has(n) && !quarantinedNames.has(n)
5746
+ );
5725
5747
  const data = [
5726
- // Approved → active
5748
+ // Approved → active (with descriptions from SKILL.md)
5727
5749
  ...approved.map((a) => ({
5728
5750
  name: a.name,
5729
5751
  source: "user",
5730
5752
  status: "active",
5731
5753
  path: path12.join(skillsDir2 ?? "", a.name),
5732
5754
  publisher: a.publisher,
5733
- description: void 0
5755
+ description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, a.name)) : void 0
5734
5756
  })),
5735
5757
  // Quarantined
5736
5758
  ...quarantined.map((q) => ({
@@ -5740,6 +5762,14 @@ async function skillsRoutes(app) {
5740
5762
  path: q.originalPath,
5741
5763
  description: void 0
5742
5764
  })),
5765
+ // Workspace: on disk but not approved or quarantined
5766
+ ...workspaceNames.map((name) => ({
5767
+ name,
5768
+ source: "workspace",
5769
+ status: "workspace",
5770
+ path: path12.join(skillsDir2 ?? "", name),
5771
+ description: skillsDir2 ? readSkillDescription(path12.join(skillsDir2, name)) : void 0
5772
+ })),
5743
5773
  // Downloaded (not installed) → available
5744
5774
  ...availableDownloads.map((d) => ({
5745
5775
  name: d.slug,
@@ -7063,6 +7093,120 @@ async function fsRoutes(app) {
7063
7093
  });
7064
7094
  }
7065
7095
 
7096
+ // libs/shield-daemon/src/services/activity-log.ts
7097
+ import * as fs17 from "node:fs";
7098
+ import * as path16 from "node:path";
7099
+ var ACTIVITY_FILE = "activity.jsonl";
7100
+ var MAX_SIZE_BYTES = 100 * 1024 * 1024;
7101
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7102
+ var PRUNE_INTERVAL = 1e3;
7103
+ var instance = null;
7104
+ function getActivityLog() {
7105
+ if (!instance) {
7106
+ instance = new ActivityLog();
7107
+ }
7108
+ return instance;
7109
+ }
7110
+ var ActivityLog = class {
7111
+ filePath;
7112
+ writeCount = 0;
7113
+ unsubscribe;
7114
+ constructor() {
7115
+ this.filePath = path16.join(getConfigDir(), ACTIVITY_FILE);
7116
+ }
7117
+ /** Read historical events from the JSONL file, newest first */
7118
+ getHistory(limit = 500) {
7119
+ if (!fs17.existsSync(this.filePath)) return [];
7120
+ const content = fs17.readFileSync(this.filePath, "utf-8");
7121
+ const lines = content.split("\n").filter(Boolean);
7122
+ const events = [];
7123
+ for (const line of lines) {
7124
+ try {
7125
+ const evt = JSON.parse(line);
7126
+ if (evt.type === "heartbeat") continue;
7127
+ events.push(evt);
7128
+ } catch {
7129
+ }
7130
+ }
7131
+ events.reverse();
7132
+ return events.slice(0, limit);
7133
+ }
7134
+ start() {
7135
+ this.pruneOldEntries();
7136
+ this.unsubscribe = daemonEvents.subscribe((event) => {
7137
+ this.append(event);
7138
+ });
7139
+ }
7140
+ stop() {
7141
+ this.unsubscribe?.();
7142
+ }
7143
+ append(event) {
7144
+ const line = JSON.stringify(event) + "\n";
7145
+ fs17.appendFileSync(this.filePath, line, "utf-8");
7146
+ this.writeCount++;
7147
+ if (this.writeCount % PRUNE_INTERVAL === 0) {
7148
+ this.rotate();
7149
+ }
7150
+ }
7151
+ rotate() {
7152
+ try {
7153
+ const stat = fs17.statSync(this.filePath);
7154
+ if (stat.size > MAX_SIZE_BYTES) {
7155
+ this.truncateBySize();
7156
+ }
7157
+ } catch {
7158
+ }
7159
+ this.pruneOldEntries();
7160
+ }
7161
+ /** Keep newest half of lines when file exceeds size limit */
7162
+ truncateBySize() {
7163
+ const content = fs17.readFileSync(this.filePath, "utf-8");
7164
+ const lines = content.split("\n").filter(Boolean);
7165
+ const keep = lines.slice(Math.floor(lines.length / 2));
7166
+ fs17.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
7167
+ }
7168
+ /** Remove entries older than 24 hours */
7169
+ pruneOldEntries() {
7170
+ if (!fs17.existsSync(this.filePath)) return;
7171
+ const content = fs17.readFileSync(this.filePath, "utf-8");
7172
+ const lines = content.split("\n").filter(Boolean);
7173
+ const cutoff = Date.now() - MAX_AGE_MS;
7174
+ const kept = lines.filter((line) => {
7175
+ try {
7176
+ const evt = JSON.parse(line);
7177
+ return new Date(evt.timestamp).getTime() >= cutoff;
7178
+ } catch {
7179
+ return false;
7180
+ }
7181
+ });
7182
+ if (kept.length < lines.length) {
7183
+ fs17.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
7184
+ }
7185
+ }
7186
+ };
7187
+
7188
+ // libs/shield-daemon/src/routes/activity.ts
7189
+ async function activityRoutes(app) {
7190
+ app.get(
7191
+ "/activity",
7192
+ async (request) => {
7193
+ const authenticated = isAuthenticated(request);
7194
+ const raw = Number(request.query.limit) || 500;
7195
+ const limit = Math.min(Math.max(raw, 1), 1e4);
7196
+ const events = getActivityLog().getHistory(limit);
7197
+ if (authenticated) {
7198
+ return { data: events };
7199
+ }
7200
+ const stripped = events.map((e) => ({
7201
+ type: e.type,
7202
+ timestamp: e.timestamp,
7203
+ data: {}
7204
+ }));
7205
+ return { data: stripped };
7206
+ }
7207
+ );
7208
+ }
7209
+
7066
7210
  // libs/shield-daemon/src/routes/rpc.ts
7067
7211
  function globToRegex(pattern) {
7068
7212
  const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
@@ -7261,7 +7405,7 @@ async function registerRoutes(app) {
7261
7405
  });
7262
7406
  app.addHook("onResponse", (request, reply, done) => {
7263
7407
  if (!request.url.startsWith("/sse") && !request.url.startsWith("/rpc") && !request.url.includes(".") && !request.url.endsWith("/health")) {
7264
- if (request.method === "GET" && request.url.startsWith("/api/status") && reply.statusCode === 200) {
7408
+ if (request.method === "GET" && (request.url.startsWith("/api/status") || request.url.startsWith("/api/activity")) && reply.statusCode === 200) {
7265
7409
  done();
7266
7410
  return;
7267
7411
  }
@@ -7314,28 +7458,29 @@ async function registerRoutes(app) {
7314
7458
  await api.register(secretsRoutes);
7315
7459
  await api.register(marketplaceRoutes);
7316
7460
  await api.register(fsRoutes);
7461
+ await api.register(activityRoutes);
7317
7462
  },
7318
7463
  { prefix: API_PREFIX }
7319
7464
  );
7320
7465
  }
7321
7466
 
7322
7467
  // libs/shield-daemon/src/static.ts
7323
- import * as fs17 from "node:fs";
7324
- import * as path16 from "node:path";
7468
+ import * as fs18 from "node:fs";
7469
+ import * as path17 from "node:path";
7325
7470
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7326
7471
  var __filename = fileURLToPath2(import.meta.url);
7327
- var __dirname = path16.dirname(__filename);
7472
+ var __dirname = path17.dirname(__filename);
7328
7473
  function getUiAssetsPath() {
7329
- const pkgRootPath = path16.join(__dirname, "..", "ui-assets");
7330
- if (fs17.existsSync(pkgRootPath)) {
7474
+ const pkgRootPath = path17.join(__dirname, "..", "ui-assets");
7475
+ if (fs18.existsSync(pkgRootPath)) {
7331
7476
  return pkgRootPath;
7332
7477
  }
7333
- const bundledPath = path16.join(__dirname, "ui-assets");
7334
- if (fs17.existsSync(bundledPath)) {
7478
+ const bundledPath = path17.join(__dirname, "ui-assets");
7479
+ if (fs18.existsSync(bundledPath)) {
7335
7480
  return bundledPath;
7336
7481
  }
7337
- const devPath = path16.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
7338
- if (fs17.existsSync(devPath)) {
7482
+ const devPath = path17.join(__dirname, "..", "..", "..", "dist", "apps", "shield-ui");
7483
+ if (fs18.existsSync(devPath)) {
7339
7484
  return devPath;
7340
7485
  }
7341
7486
  return null;
@@ -7394,74 +7539,6 @@ function stopSecurityWatcher() {
7394
7539
  }
7395
7540
  }
7396
7541
 
7397
- // libs/shield-daemon/src/services/activity-log.ts
7398
- import * as fs18 from "node:fs";
7399
- import * as path17 from "node:path";
7400
- var ACTIVITY_FILE = "activity.jsonl";
7401
- var MAX_SIZE_BYTES = 100 * 1024 * 1024;
7402
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7403
- var PRUNE_INTERVAL = 1e3;
7404
- var ActivityLog = class {
7405
- filePath;
7406
- writeCount = 0;
7407
- unsubscribe;
7408
- constructor() {
7409
- this.filePath = path17.join(getConfigDir(), ACTIVITY_FILE);
7410
- }
7411
- start() {
7412
- this.pruneOldEntries();
7413
- this.unsubscribe = daemonEvents.subscribe((event) => {
7414
- this.append(event);
7415
- });
7416
- }
7417
- stop() {
7418
- this.unsubscribe?.();
7419
- }
7420
- append(event) {
7421
- const line = JSON.stringify(event) + "\n";
7422
- fs18.appendFileSync(this.filePath, line, "utf-8");
7423
- this.writeCount++;
7424
- if (this.writeCount % PRUNE_INTERVAL === 0) {
7425
- this.rotate();
7426
- }
7427
- }
7428
- rotate() {
7429
- try {
7430
- const stat = fs18.statSync(this.filePath);
7431
- if (stat.size > MAX_SIZE_BYTES) {
7432
- this.truncateBySize();
7433
- }
7434
- } catch {
7435
- }
7436
- this.pruneOldEntries();
7437
- }
7438
- /** Keep newest half of lines when file exceeds size limit */
7439
- truncateBySize() {
7440
- const content = fs18.readFileSync(this.filePath, "utf-8");
7441
- const lines = content.split("\n").filter(Boolean);
7442
- const keep = lines.slice(Math.floor(lines.length / 2));
7443
- fs18.writeFileSync(this.filePath, keep.join("\n") + "\n", "utf-8");
7444
- }
7445
- /** Remove entries older than 24 hours */
7446
- pruneOldEntries() {
7447
- if (!fs18.existsSync(this.filePath)) return;
7448
- const content = fs18.readFileSync(this.filePath, "utf-8");
7449
- const lines = content.split("\n").filter(Boolean);
7450
- const cutoff = Date.now() - MAX_AGE_MS;
7451
- const kept = lines.filter((line) => {
7452
- try {
7453
- const evt = JSON.parse(line);
7454
- return new Date(evt.timestamp).getTime() >= cutoff;
7455
- } catch {
7456
- return false;
7457
- }
7458
- });
7459
- if (kept.length < lines.length) {
7460
- fs18.writeFileSync(this.filePath, kept.join("\n") + "\n", "utf-8");
7461
- }
7462
- }
7463
- };
7464
-
7465
7542
  // libs/shield-daemon/src/server.ts
7466
7543
  async function createServer(config) {
7467
7544
  const app = Fastify({
@@ -7500,7 +7577,7 @@ async function startServer(config) {
7500
7577
  onQuarantined: (info) => emitSkillQuarantined(info.name, info.reason),
7501
7578
  onApproved: (name) => emitSkillApproved(name)
7502
7579
  }, 3e4);
7503
- const activityLog = new ActivityLog();
7580
+ const activityLog = getActivityLog();
7504
7581
  activityLog.start();
7505
7582
  try {
7506
7583
  const vault = getVault();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenshield/daemon",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "type": "module",
5
5
  "description": "AgenShield HTTP daemon server with embedded UI",
6
6
  "main": "./index.js",
@@ -24,9 +24,9 @@
24
24
  ],
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
- "@agenshield/ipc": "0.4.3",
28
- "@agenshield/broker": "0.4.3",
29
- "@agenshield/sandbox": "0.4.3",
27
+ "@agenshield/ipc": "0.4.4",
28
+ "@agenshield/broker": "0.4.4",
29
+ "@agenshield/sandbox": "0.4.4",
30
30
  "@modelcontextprotocol/sdk": "^1.26.0",
31
31
  "fastify": "^5.7.0",
32
32
  "zod": "^4.3.6",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Activity history route
3
+ */
4
+ import type { FastifyInstance } from 'fastify';
5
+ export declare function activityRoutes(app: FastifyInstance): Promise<void>;
6
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../src/routes/activity.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAI/D,wBAAsB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBxE"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAoB/C;;GAEG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFxE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAqB/C;;GAEG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFxE"}
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/routes/skills.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AA0C7E;;GAEG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuYtE"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/routes/skills.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AA0D7E;;GAEG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA+ZtE"}
@@ -4,11 +4,15 @@
4
4
  * Appends every daemon event as JSONL to ~/.agenshield/activity.jsonl.
5
5
  * Rotation: max 100 MB file size (keep newest half), max 24 h retention.
6
6
  */
7
+ import { type DaemonEvent } from '../events/emitter';
8
+ export declare function getActivityLog(): ActivityLog;
7
9
  export declare class ActivityLog {
8
10
  private filePath;
9
11
  private writeCount;
10
12
  private unsubscribe?;
11
13
  constructor();
14
+ /** Read historical events from the JSONL file, newest first */
15
+ getHistory(limit?: number): DaemonEvent[];
12
16
  start(): void;
13
17
  stop(): void;
14
18
  private append;