@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 +159 -82
- package/main.js +159 -82
- package/package.json +4 -4
- package/routes/activity.d.ts +6 -0
- package/routes/activity.d.ts.map +1 -0
- package/routes/index.d.ts.map +1 -1
- package/routes/skills.d.ts.map +1 -1
- package/services/activity-log.d.ts +4 -0
- package/services/activity-log.d.ts.map +1 -1
- package/ui-assets/assets/{index-Chp3YFDr.js → index-gZFEJ5lI.js} +105 -105
- package/ui-assets/index.html +1 -1
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
|
|
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
|
|
7360
|
-
import * as
|
|
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 =
|
|
7508
|
+
var __dirname = path17.dirname(__filename);
|
|
7364
7509
|
function getUiAssetsPath() {
|
|
7365
|
-
const pkgRootPath =
|
|
7366
|
-
if (
|
|
7510
|
+
const pkgRootPath = path17.join(__dirname, "..", "ui-assets");
|
|
7511
|
+
if (fs18.existsSync(pkgRootPath)) {
|
|
7367
7512
|
return pkgRootPath;
|
|
7368
7513
|
}
|
|
7369
|
-
const bundledPath =
|
|
7370
|
-
if (
|
|
7514
|
+
const bundledPath = path17.join(__dirname, "ui-assets");
|
|
7515
|
+
if (fs18.existsSync(bundledPath)) {
|
|
7371
7516
|
return bundledPath;
|
|
7372
7517
|
}
|
|
7373
|
-
const devPath =
|
|
7374
|
-
if (
|
|
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 =
|
|
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
|
|
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
|
|
7324
|
-
import * as
|
|
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 =
|
|
7472
|
+
var __dirname = path17.dirname(__filename);
|
|
7328
7473
|
function getUiAssetsPath() {
|
|
7329
|
-
const pkgRootPath =
|
|
7330
|
-
if (
|
|
7474
|
+
const pkgRootPath = path17.join(__dirname, "..", "ui-assets");
|
|
7475
|
+
if (fs18.existsSync(pkgRootPath)) {
|
|
7331
7476
|
return pkgRootPath;
|
|
7332
7477
|
}
|
|
7333
|
-
const bundledPath =
|
|
7334
|
-
if (
|
|
7478
|
+
const bundledPath = path17.join(__dirname, "ui-assets");
|
|
7479
|
+
if (fs18.existsSync(bundledPath)) {
|
|
7335
7480
|
return bundledPath;
|
|
7336
7481
|
}
|
|
7337
|
-
const devPath =
|
|
7338
|
-
if (
|
|
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 =
|
|
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
|
+
"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.
|
|
28
|
-
"@agenshield/broker": "0.4.
|
|
29
|
-
"@agenshield/sandbox": "0.4.
|
|
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 @@
|
|
|
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"}
|
package/routes/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/routes/skills.d.ts.map
CHANGED
|
@@ -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;
|
|
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;
|