@tuent/sentinel 0.1.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/LICENSE +201 -0
- package/README.md +96 -0
- package/dist/Sentinel-B_sv8Kiy.d.ts +1785 -0
- package/dist/Sentinel-JLQL3YRD.js +10 -0
- package/dist/auditTrailKeys-GKCW5KUD.js +23 -0
- package/dist/chunk-2FFMYSVC.js +428 -0
- package/dist/chunk-3U3PKD4N.js +539 -0
- package/dist/chunk-6MHWJATS.js +1221 -0
- package/dist/chunk-CUJKNIKT.js +62 -0
- package/dist/chunk-FMZWHT4M.js +20 -0
- package/dist/chunk-NUXSUSYY.js +95 -0
- package/dist/chunk-PDWWRZXF.js +238 -0
- package/dist/chunk-QFRDEISP.js +7429 -0
- package/dist/chunk-Z3PWIJKT.js +2268 -0
- package/dist/cli.js +80 -0
- package/dist/gateway/index.d.ts +241 -0
- package/dist/gateway/index.js +10 -0
- package/dist/gatewayDaemon.js +25 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +28 -0
- package/dist/logAdapter-IB6ZDEV2.js +7 -0
- package/dist/mcpAdapter-R47GX2P3.js +178 -0
- package/dist/pidManager-ZYC7SICM.js +15 -0
- package/dist/policyLoader-6KR5VFVV.js +15 -0
- package/dist/webhookReceiver-NAVMQ6N5.js +203 -0
- package/package.json +61 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LogAdapter
|
|
3
|
+
} from "./chunk-PDWWRZXF.js";
|
|
4
|
+
|
|
5
|
+
// src/adapters/mcpAdapter.ts
|
|
6
|
+
var ACTION_MAP = {
|
|
7
|
+
bash: "command_exec",
|
|
8
|
+
shell: "command_exec",
|
|
9
|
+
terminal: "command_exec",
|
|
10
|
+
read_file: "file_read",
|
|
11
|
+
file_read: "file_read",
|
|
12
|
+
view: "file_read",
|
|
13
|
+
write_file: "file_write",
|
|
14
|
+
file_write: "file_write",
|
|
15
|
+
create_file: "file_write",
|
|
16
|
+
str_replace: "file_write",
|
|
17
|
+
web_search: "network_request",
|
|
18
|
+
fetch: "network_request",
|
|
19
|
+
http: "network_request",
|
|
20
|
+
sql: "database_query",
|
|
21
|
+
query: "database_query",
|
|
22
|
+
database: "database_query"
|
|
23
|
+
};
|
|
24
|
+
function mapToolToAction(tool) {
|
|
25
|
+
return ACTION_MAP[tool] ?? "tool_invocation";
|
|
26
|
+
}
|
|
27
|
+
var McpAdapter = class {
|
|
28
|
+
agentId;
|
|
29
|
+
agentName;
|
|
30
|
+
logDir;
|
|
31
|
+
pollIntervalMs;
|
|
32
|
+
logAdapter = null;
|
|
33
|
+
currentLogFile = null;
|
|
34
|
+
onEvent = null;
|
|
35
|
+
dirPollTimer = null;
|
|
36
|
+
running = false;
|
|
37
|
+
scanning = false;
|
|
38
|
+
constructor(agentId, agentName, options) {
|
|
39
|
+
this.agentId = agentId;
|
|
40
|
+
this.agentName = agentName;
|
|
41
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
42
|
+
this.logDir = options?.logDir ?? `${home}/.mcp/logs`;
|
|
43
|
+
this.pollIntervalMs = options?.pollIntervalMs ?? 3e3;
|
|
44
|
+
}
|
|
45
|
+
async start(onEvent) {
|
|
46
|
+
if (this.running) return;
|
|
47
|
+
this.onEvent = onEvent;
|
|
48
|
+
this.running = true;
|
|
49
|
+
const latestLog = await this.findLatestLogFile();
|
|
50
|
+
if (latestLog) {
|
|
51
|
+
await this.attachToLogFile(latestLog);
|
|
52
|
+
} else {
|
|
53
|
+
console.warn(`No MCP logs found in ${this.logDir}. Waiting for activity...`);
|
|
54
|
+
}
|
|
55
|
+
this.dirPollTimer = setInterval(() => this.scanForNewLogs(), this.pollIntervalMs);
|
|
56
|
+
console.log(`McpAdapter monitoring MCP activity for agent ${this.agentId}`);
|
|
57
|
+
}
|
|
58
|
+
async stop() {
|
|
59
|
+
if (this.dirPollTimer) {
|
|
60
|
+
clearInterval(this.dirPollTimer);
|
|
61
|
+
this.dirPollTimer = null;
|
|
62
|
+
}
|
|
63
|
+
await this.logAdapter?.stop();
|
|
64
|
+
this.logAdapter = null;
|
|
65
|
+
this.currentLogFile = null;
|
|
66
|
+
this.running = false;
|
|
67
|
+
this.onEvent = null;
|
|
68
|
+
console.log(`McpAdapter stopped for agent ${this.agentId}`);
|
|
69
|
+
}
|
|
70
|
+
async findLatestLogFile() {
|
|
71
|
+
const { readdir, stat } = await import("fs/promises");
|
|
72
|
+
const { join } = await import("path");
|
|
73
|
+
let entries;
|
|
74
|
+
try {
|
|
75
|
+
entries = await readdir(this.logDir);
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
let latestFile = null;
|
|
80
|
+
let latestMtime = 0;
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const fullPath = join(this.logDir, entry);
|
|
83
|
+
try {
|
|
84
|
+
const info = await stat(fullPath);
|
|
85
|
+
if (info.isFile() && info.mtimeMs > latestMtime) {
|
|
86
|
+
latestMtime = info.mtimeMs;
|
|
87
|
+
latestFile = fullPath;
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return latestFile;
|
|
93
|
+
}
|
|
94
|
+
async scanForNewLogs() {
|
|
95
|
+
if (!this.running || this.scanning) return;
|
|
96
|
+
this.scanning = true;
|
|
97
|
+
try {
|
|
98
|
+
const latestLog = await this.findLatestLogFile();
|
|
99
|
+
if (latestLog && latestLog !== this.currentLogFile) {
|
|
100
|
+
await this.attachToLogFile(latestLog);
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
this.scanning = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async attachToLogFile(logFile) {
|
|
107
|
+
await this.logAdapter?.stop();
|
|
108
|
+
this.currentLogFile = logFile;
|
|
109
|
+
this.logAdapter = new LogAdapter(this.agentId, logFile, {
|
|
110
|
+
pollIntervalMs: this.pollIntervalMs,
|
|
111
|
+
fieldMapping: { action: "timestamp", target: "timestamp" }
|
|
112
|
+
});
|
|
113
|
+
await this.logAdapter.start((rawEvent) => {
|
|
114
|
+
const event = this.parseMcpEntry(rawEvent);
|
|
115
|
+
if (event) {
|
|
116
|
+
this.onEvent?.(event);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
parseMcpEntry(raw) {
|
|
121
|
+
const timestamp = raw.timestamp;
|
|
122
|
+
if (!timestamp) return null;
|
|
123
|
+
if (raw.method && typeof raw.method === "string") {
|
|
124
|
+
const params = raw.params;
|
|
125
|
+
const toolName = params?.name ?? raw.method;
|
|
126
|
+
const args = params?.arguments;
|
|
127
|
+
const target = args ? `${toolName}: ${JSON.stringify(args)}` : toolName;
|
|
128
|
+
const t = target.slice(0, 200);
|
|
129
|
+
return {
|
|
130
|
+
agentId: this.agentId,
|
|
131
|
+
agentName: this.agentName,
|
|
132
|
+
agentRole: "mcp-agent",
|
|
133
|
+
action: "tool_invocation",
|
|
134
|
+
targets: [t],
|
|
135
|
+
primaryTarget: t,
|
|
136
|
+
schemaVersion: 2,
|
|
137
|
+
timestamp,
|
|
138
|
+
metadata: { tool: raw.method }
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (raw.tool && typeof raw.tool === "string") {
|
|
142
|
+
const input = typeof raw.input === "string" ? raw.input : "";
|
|
143
|
+
const t = input.slice(0, 200);
|
|
144
|
+
return {
|
|
145
|
+
agentId: this.agentId,
|
|
146
|
+
agentName: this.agentName,
|
|
147
|
+
agentRole: "mcp-agent",
|
|
148
|
+
action: mapToolToAction(raw.tool),
|
|
149
|
+
targets: [t],
|
|
150
|
+
primaryTarget: t,
|
|
151
|
+
schemaVersion: 2,
|
|
152
|
+
timestamp,
|
|
153
|
+
metadata: { tool: raw.tool }
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (raw.type === "tool_use" && raw.name && typeof raw.name === "string") {
|
|
157
|
+
const content = typeof raw.content === "string" ? raw.content : "";
|
|
158
|
+
const t = content.slice(0, 200);
|
|
159
|
+
return {
|
|
160
|
+
agentId: this.agentId,
|
|
161
|
+
agentName: this.agentName,
|
|
162
|
+
agentRole: "mcp-agent",
|
|
163
|
+
action: mapToolToAction(raw.name),
|
|
164
|
+
targets: [t],
|
|
165
|
+
primaryTarget: t,
|
|
166
|
+
schemaVersion: 2,
|
|
167
|
+
timestamp,
|
|
168
|
+
metadata: { tool: raw.name }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
export {
|
|
175
|
+
McpAdapter,
|
|
176
|
+
mapToolToAction
|
|
177
|
+
};
|
|
178
|
+
//# sourceMappingURL=mcpAdapter-R47GX2P3.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acquireGatewayLock,
|
|
3
|
+
readPidFile,
|
|
4
|
+
removePidFile,
|
|
5
|
+
verifyPidIsGateway,
|
|
6
|
+
writePidFile
|
|
7
|
+
} from "./chunk-CUJKNIKT.js";
|
|
8
|
+
export {
|
|
9
|
+
acquireGatewayLock,
|
|
10
|
+
readPidFile,
|
|
11
|
+
removePidFile,
|
|
12
|
+
verifyPidIsGateway,
|
|
13
|
+
writePidFile
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=pidManager-ZYC7SICM.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOCKED_ACTIONABLE_TYPES,
|
|
3
|
+
loadPolicy,
|
|
4
|
+
loadPolicyFromString,
|
|
5
|
+
policyToConfig,
|
|
6
|
+
policyToRole
|
|
7
|
+
} from "./chunk-2FFMYSVC.js";
|
|
8
|
+
export {
|
|
9
|
+
LOCKED_ACTIONABLE_TYPES,
|
|
10
|
+
loadPolicy,
|
|
11
|
+
loadPolicyFromString,
|
|
12
|
+
policyToConfig,
|
|
13
|
+
policyToRole
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=policyLoader-6KR5VFVV.js.map
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// src/adapters/webhookReceiver.ts
|
|
2
|
+
var DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
|
|
3
|
+
var REQUIRED_FIELDS = ["agentId", "action", "targets", "timestamp"];
|
|
4
|
+
var KNOWN_ACTIONS = /* @__PURE__ */ new Set([
|
|
5
|
+
"file_read",
|
|
6
|
+
"file_write",
|
|
7
|
+
"api_call",
|
|
8
|
+
"database_query",
|
|
9
|
+
"command_exec",
|
|
10
|
+
"tool_invocation",
|
|
11
|
+
"network_request"
|
|
12
|
+
]);
|
|
13
|
+
var WebhookReceiver = class {
|
|
14
|
+
configuredPort;
|
|
15
|
+
apiKey;
|
|
16
|
+
maxBodySize;
|
|
17
|
+
server = null;
|
|
18
|
+
onEvent = null;
|
|
19
|
+
running = false;
|
|
20
|
+
startedAt = 0;
|
|
21
|
+
constructor(port = 5151, options) {
|
|
22
|
+
this.configuredPort = port;
|
|
23
|
+
this.apiKey = options?.apiKey;
|
|
24
|
+
this.maxBodySize = options?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
|
|
25
|
+
}
|
|
26
|
+
get port() {
|
|
27
|
+
const addr = this.server?.address();
|
|
28
|
+
if (addr && typeof addr === "object") {
|
|
29
|
+
return addr.port;
|
|
30
|
+
}
|
|
31
|
+
return this.configuredPort;
|
|
32
|
+
}
|
|
33
|
+
async start(onEvent) {
|
|
34
|
+
if (this.running) return;
|
|
35
|
+
this.onEvent = onEvent;
|
|
36
|
+
this.running = true;
|
|
37
|
+
this.startedAt = Date.now();
|
|
38
|
+
const http = await import("http");
|
|
39
|
+
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
this.server.once("error", reject);
|
|
42
|
+
this.server.listen(this.configuredPort, () => {
|
|
43
|
+
this.server.removeListener("error", reject);
|
|
44
|
+
resolve();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
console.log(`Sentinel webhook receiver listening on port ${this.port}`);
|
|
48
|
+
}
|
|
49
|
+
async stop() {
|
|
50
|
+
if (!this.server) return;
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
this.server.close((err) => err ? reject(err) : resolve());
|
|
53
|
+
});
|
|
54
|
+
this.server = null;
|
|
55
|
+
this.running = false;
|
|
56
|
+
this.onEvent = null;
|
|
57
|
+
console.log("Sentinel webhook receiver stopped");
|
|
58
|
+
}
|
|
59
|
+
isRunning() {
|
|
60
|
+
return this.running;
|
|
61
|
+
}
|
|
62
|
+
handleRequest(req, res) {
|
|
63
|
+
const url = req.url ?? "";
|
|
64
|
+
const method = req.method ?? "";
|
|
65
|
+
if (method === "GET" && url === "/api/sentinel/health") {
|
|
66
|
+
this.sendJson(res, 200, {
|
|
67
|
+
status: "running",
|
|
68
|
+
uptime: Math.floor((Date.now() - this.startedAt) / 1e3)
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (method === "POST" && url === "/api/sentinel/events") {
|
|
73
|
+
this.readBody(req, res, (body) => this.handleSingleEvent(body, req, res));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (method === "POST" && url === "/api/sentinel/events/batch") {
|
|
77
|
+
this.readBody(req, res, (body) => this.handleBatchEvents(body, req, res));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.sendJson(res, 404, { error: "not found" });
|
|
81
|
+
}
|
|
82
|
+
readBody(req, res, onBody) {
|
|
83
|
+
let size = 0;
|
|
84
|
+
let exceeded = false;
|
|
85
|
+
const chunks = [];
|
|
86
|
+
req.on("data", (chunk) => {
|
|
87
|
+
size += chunk.length;
|
|
88
|
+
if (size > this.maxBodySize) {
|
|
89
|
+
if (!exceeded) {
|
|
90
|
+
exceeded = true;
|
|
91
|
+
this.sendJson(res, 413, { error: "body too large" });
|
|
92
|
+
req.destroy();
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
chunks.push(chunk);
|
|
97
|
+
});
|
|
98
|
+
req.on("end", () => {
|
|
99
|
+
if (exceeded) return;
|
|
100
|
+
onBody(Buffer.concat(chunks).toString("utf-8"));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
handleSingleEvent(body, req, res) {
|
|
104
|
+
if (!this.checkAuth(req, res)) return;
|
|
105
|
+
let event;
|
|
106
|
+
try {
|
|
107
|
+
event = JSON.parse(body);
|
|
108
|
+
} catch {
|
|
109
|
+
this.sendJson(res, 400, { error: "invalid JSON" });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.normalizeEvent(event);
|
|
113
|
+
const missing = this.validateEvent(event);
|
|
114
|
+
if (missing) {
|
|
115
|
+
this.sendJson(res, 400, { error: `missing required field: ${missing}` });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!event.agentName) event.agentName = event.agentId;
|
|
119
|
+
if (!event.agentRole) event.agentRole = "unknown";
|
|
120
|
+
this.flagUnknownAction(event);
|
|
121
|
+
this.onEvent?.(event.agentId, event);
|
|
122
|
+
this.sendJson(res, 200, { status: "accepted" });
|
|
123
|
+
}
|
|
124
|
+
handleBatchEvents(body, req, res) {
|
|
125
|
+
if (!this.checkAuth(req, res)) return;
|
|
126
|
+
let payload;
|
|
127
|
+
try {
|
|
128
|
+
payload = JSON.parse(body);
|
|
129
|
+
} catch {
|
|
130
|
+
this.sendJson(res, 400, { error: "invalid JSON" });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!Array.isArray(payload.events)) {
|
|
134
|
+
this.sendJson(res, 400, { error: "missing required field: events" });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
let processed = 0;
|
|
138
|
+
let skipped = 0;
|
|
139
|
+
for (const event of payload.events) {
|
|
140
|
+
this.normalizeEvent(event);
|
|
141
|
+
const missing = this.validateEvent(event);
|
|
142
|
+
if (missing) {
|
|
143
|
+
skipped++;
|
|
144
|
+
} else {
|
|
145
|
+
if (!event.agentName) event.agentName = event.agentId;
|
|
146
|
+
if (!event.agentRole) event.agentRole = "unknown";
|
|
147
|
+
this.flagUnknownAction(event);
|
|
148
|
+
this.onEvent?.(event.agentId, event);
|
|
149
|
+
processed++;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
this.sendJson(res, 200, { status: "accepted", processed, skipped });
|
|
153
|
+
}
|
|
154
|
+
checkAuth(req, res) {
|
|
155
|
+
if (!this.apiKey) return true;
|
|
156
|
+
const auth = req.headers.authorization;
|
|
157
|
+
if (!auth || auth !== `Bearer ${this.apiKey}`) {
|
|
158
|
+
this.sendJson(res, 401, { error: "unauthorized" });
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
flagUnknownAction(event) {
|
|
164
|
+
if (!KNOWN_ACTIONS.has(event.action)) {
|
|
165
|
+
console.warn(`Sentinel: unknown action type "${event.action}" from agent ${event.agentId}`);
|
|
166
|
+
if (!event.metadata) {
|
|
167
|
+
event.metadata = {};
|
|
168
|
+
}
|
|
169
|
+
event.metadata._unknownAction = "true";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* v1→v2 normalization (Decision 4): accept legacy `target: string` payloads
|
|
174
|
+
* and normalize to `targets: string[]` + `primaryTarget` + `schemaVersion: 2`.
|
|
175
|
+
*/
|
|
176
|
+
normalizeEvent(event) {
|
|
177
|
+
if (!event.targets && typeof event.target === "string") {
|
|
178
|
+
event.targets = [event.target];
|
|
179
|
+
event.primaryTarget = event.target;
|
|
180
|
+
event.schemaVersion = 2;
|
|
181
|
+
}
|
|
182
|
+
if (Array.isArray(event.targets) && !event.primaryTarget) {
|
|
183
|
+
event.primaryTarget = event.targets[0];
|
|
184
|
+
}
|
|
185
|
+
if (!event.schemaVersion) {
|
|
186
|
+
event.schemaVersion = 2;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
validateEvent(event) {
|
|
190
|
+
for (const field of REQUIRED_FIELDS) {
|
|
191
|
+
if (!event[field]) return field;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
sendJson(res, status, data) {
|
|
196
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
197
|
+
res.end(JSON.stringify(data));
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
export {
|
|
201
|
+
WebhookReceiver
|
|
202
|
+
};
|
|
203
|
+
//# sourceMappingURL=webhookReceiver-NAVMQ6N5.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tuent/sentinel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI agent behavioral security monitoring SDK",
|
|
5
|
+
"author": "Tuent LLC",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"ai-agent",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"runtime-security",
|
|
10
|
+
"behavioral-monitoring",
|
|
11
|
+
"anomaly-detection",
|
|
12
|
+
"audit-trail",
|
|
13
|
+
"security",
|
|
14
|
+
"sentinel"
|
|
15
|
+
],
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./gateway": {
|
|
26
|
+
"types": "./dist/gateway/index.d.ts",
|
|
27
|
+
"import": "./dist/gateway/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"!dist/**/*.map",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"sentinel": "dist/cli.js"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup && node scripts/scrub-dts.mjs && node scripts/minify-cli.mjs",
|
|
47
|
+
"prepack": "npm run build",
|
|
48
|
+
"test": "vitest run"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"js-yaml": "^4.1.0",
|
|
52
|
+
"shell-quote": "^1.8.3"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/js-yaml": "^4.0.9",
|
|
56
|
+
"@types/node": "^25.3.2",
|
|
57
|
+
"@types/shell-quote": "^1.7.5",
|
|
58
|
+
"tsup": "^8.5.1",
|
|
59
|
+
"typescript": "^5.7.0"
|
|
60
|
+
}
|
|
61
|
+
}
|