@goplus/agentguard 1.1.13 → 1.1.18
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/README.md +8 -2
- package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
- package/dist/adapters/openclaw-plugin.js +6 -18
- package/dist/adapters/openclaw-plugin.js.map +1 -1
- package/dist/cli.js +423 -55
- package/dist/cli.js.map +1 -1
- package/dist/cloud/client.d.ts +16 -2
- package/dist/cloud/client.d.ts.map +1 -1
- package/dist/cloud/client.js +56 -9
- package/dist/cloud/client.js.map +1 -1
- package/dist/cloud/openclaw-notify.d.ts +18 -0
- package/dist/cloud/openclaw-notify.d.ts.map +1 -0
- package/dist/cloud/openclaw-notify.js +69 -0
- package/dist/cloud/openclaw-notify.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -0
- package/dist/config.js.map +1 -1
- package/dist/feed/cron.d.ts +1 -0
- package/dist/feed/cron.d.ts.map +1 -1
- package/dist/feed/cron.js +309 -19
- package/dist/feed/cron.js.map +1 -1
- package/dist/feed/selfcheck.d.ts.map +1 -1
- package/dist/feed/selfcheck.js +470 -23
- package/dist/feed/selfcheck.js.map +1 -1
- package/dist/feed/state.d.ts +5 -7
- package/dist/feed/state.d.ts.map +1 -1
- package/dist/feed/state.js +53 -22
- package/dist/feed/state.js.map +1 -1
- package/dist/feed/types.d.ts +17 -14
- package/dist/feed/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/installers.js +81 -19
- package/dist/installers.js.map +1 -1
- package/dist/runtime/evaluator.d.ts.map +1 -1
- package/dist/runtime/evaluator.js +45 -3
- package/dist/runtime/evaluator.js.map +1 -1
- package/dist/runtime/protect.d.ts.map +1 -1
- package/dist/runtime/protect.js +9 -2
- package/dist/runtime/protect.js.map +1 -1
- package/dist/tests/cli-checkup.test.js +29 -1
- package/dist/tests/cli-checkup.test.js.map +1 -1
- package/dist/tests/cli-connect.test.d.ts +2 -0
- package/dist/tests/cli-connect.test.d.ts.map +1 -0
- package/dist/tests/cli-connect.test.js +209 -0
- package/dist/tests/cli-connect.test.js.map +1 -0
- package/dist/tests/cli-init.test.js +28 -0
- package/dist/tests/cli-init.test.js.map +1 -1
- package/dist/tests/cli-policy.test.js +72 -0
- package/dist/tests/cli-policy.test.js.map +1 -1
- package/dist/tests/cli-subscribe.test.js +237 -2
- package/dist/tests/cli-subscribe.test.js.map +1 -1
- package/dist/tests/feed-cloud.test.js +45 -1
- package/dist/tests/feed-cloud.test.js.map +1 -1
- package/dist/tests/feed-cron.test.js +274 -19
- package/dist/tests/feed-cron.test.js.map +1 -1
- package/dist/tests/feed-selfcheck.test.js +61 -13
- package/dist/tests/feed-selfcheck.test.js.map +1 -1
- package/dist/tests/feed-state.test.js +49 -10
- package/dist/tests/feed-state.test.js.map +1 -1
- package/dist/tests/installer.test.js +36 -0
- package/dist/tests/installer.test.js.map +1 -1
- package/dist/tests/integration.test.js +41 -9
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/runtime-cloud.test.js +65 -0
- package/dist/tests/runtime-cloud.test.js.map +1 -1
- package/openclaw.plugin.json +4 -0
- package/package.json +1 -1
- package/skills/agentguard/SKILL.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,8 @@ const installers_js_1 = require("./installers.js");
|
|
|
15
15
|
const version_js_1 = require("./version.js");
|
|
16
16
|
const selfcheck_js_1 = require("./feed/selfcheck.js");
|
|
17
17
|
const state_js_1 = require("./feed/state.js");
|
|
18
|
+
const client_js_2 = require("./cloud/client.js");
|
|
19
|
+
const openclaw_notify_js_1 = require("./cloud/openclaw-notify.js");
|
|
18
20
|
const cron_js_1 = require("./feed/cron.js");
|
|
19
21
|
const SUPPORTED_AGENT_INSTALLERS = ['claude-code', 'codex', 'openclaw', 'hermes', 'qclaw'];
|
|
20
22
|
const AUTO_AGENT_DETECTION = [
|
|
@@ -38,8 +40,10 @@ async function main() {
|
|
|
38
40
|
.option('--agent <agent>', 'Install hook/template for claude-code, codex, openclaw, hermes, or qclaw')
|
|
39
41
|
.option('--cloud <url>', 'AgentGuard Cloud URL to store in local config')
|
|
40
42
|
.option('--force', 'Overwrite existing hook/template files')
|
|
43
|
+
.option('--no-force', 'Do not overwrite existing hook/template files')
|
|
41
44
|
.action((options) => {
|
|
42
|
-
const
|
|
45
|
+
const forceTemplates = options.force !== false;
|
|
46
|
+
let config = (0, config_js_1.ensureConfig)();
|
|
43
47
|
if (options.level) {
|
|
44
48
|
if (!['strict', 'balanced', 'permissive'].includes(options.level)) {
|
|
45
49
|
throw new Error('Invalid level. Use strict, balanced, or permissive.');
|
|
@@ -57,7 +61,7 @@ async function main() {
|
|
|
57
61
|
if (options.agent) {
|
|
58
62
|
const normalizedAgent = String(options.agent).trim().toLowerCase();
|
|
59
63
|
if (normalizedAgent === 'auto') {
|
|
60
|
-
const results = initAutoAgents(config,
|
|
64
|
+
const results = initAutoAgents(config, forceTemplates);
|
|
61
65
|
if (results.detected.length === 0) {
|
|
62
66
|
console.log('No supported agent directories found. Looked for .claude, .openclaw, .hermes, .qclaw, and .codex.');
|
|
63
67
|
}
|
|
@@ -81,7 +85,7 @@ async function main() {
|
|
|
81
85
|
config.agentHost = agent;
|
|
82
86
|
config.agentHosts = appendAgentHost(config.agentHosts, agent);
|
|
83
87
|
(0, config_js_1.saveConfig)(config);
|
|
84
|
-
const result = (0, installers_js_1.installAgentTemplates)(agent, { force:
|
|
88
|
+
const result = (0, installers_js_1.installAgentTemplates)(agent, { force: forceTemplates });
|
|
85
89
|
console.log(`Installed ${result.agent} template:`);
|
|
86
90
|
for (const file of result.files)
|
|
87
91
|
console.log(`- ${file}`);
|
|
@@ -97,7 +101,52 @@ async function main() {
|
|
|
97
101
|
.action(async (options) => {
|
|
98
102
|
const apiKey = options.key || options.apiKey || process.env.AGENTGUARD_API_KEY;
|
|
99
103
|
if (!apiKey) {
|
|
100
|
-
|
|
104
|
+
const config = (0, config_js_1.ensureConfig)();
|
|
105
|
+
if (!isOpenClawAgentConfigured(config)) {
|
|
106
|
+
throw new Error('Missing API key. Pass --key, --api-key, set AGENTGUARD_API_KEY, or run `agentguard init --agent openclaw` before using Agent JWT registration.');
|
|
107
|
+
}
|
|
108
|
+
const cloudUrl = (0, config_js_1.normalizeCloudUrl)(options.cloud || options.url || config.cloudUrl || 'https://agentguard.gopluslabs.io');
|
|
109
|
+
if (config.agentId && config.agentJwt) {
|
|
110
|
+
const existingConfig = { ...config, cloudUrl };
|
|
111
|
+
const client = new client_js_1.AgentGuardCloudClient(existingConfig);
|
|
112
|
+
try {
|
|
113
|
+
const policy = await client.fetchEffectivePolicy();
|
|
114
|
+
const savedConfig = (0, config_js_1.connectAgentJwt)({
|
|
115
|
+
agentId: config.agentId,
|
|
116
|
+
agentJwt: config.agentJwt,
|
|
117
|
+
agentRegisterUrl: config.agentRegisterUrl,
|
|
118
|
+
cloudUrl,
|
|
119
|
+
});
|
|
120
|
+
(0, policy_js_1.saveCachedPolicy)(savedConfig.policyCachePath, policy);
|
|
121
|
+
console.log(`Connected to AgentGuard Cloud (${savedConfig.cloudUrl}).`);
|
|
122
|
+
console.log(`Agent JWT is active for local agent ${savedConfig.agentId}.`);
|
|
123
|
+
console.log(`Cached policy ${policy.policyVersion} at ${savedConfig.policyCachePath}.`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if (!(err instanceof client_js_2.CloudRequestError && err.status === 401)) {
|
|
128
|
+
console.log(`Agent JWT is configured for ${cloudUrl}.`);
|
|
129
|
+
console.log(`Could not verify it right now; local protection still works offline. ${err instanceof Error ? err.message : ''}`.trim());
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const registration = await registerAgentCredential({
|
|
135
|
+
cloudUrl,
|
|
136
|
+
reason: 'connect',
|
|
137
|
+
notifyOpenClaw: true,
|
|
138
|
+
resetExistingJwt: true,
|
|
139
|
+
});
|
|
140
|
+
console.log(`Registered local AgentGuard agent (${registration.config.agentId}).`);
|
|
141
|
+
console.log('Open this link to bind AgentGuard Cloud to your email:');
|
|
142
|
+
console.log(registration.registerUrl);
|
|
143
|
+
if (registration.openClawNotification.notified) {
|
|
144
|
+
console.log('Sent the activation link to the last OpenClaw channel.');
|
|
145
|
+
}
|
|
146
|
+
else if (registration.openClawNotification.reason) {
|
|
147
|
+
console.log(`OpenClaw notification skipped: ${registration.openClawNotification.reason}`);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
101
150
|
}
|
|
102
151
|
const config = (0, config_js_1.connectCloud)({ apiKey, cloudUrl: options.cloud || options.url });
|
|
103
152
|
const client = new client_js_1.AgentGuardCloudClient(config);
|
|
@@ -118,7 +167,7 @@ async function main() {
|
|
|
118
167
|
.action(() => {
|
|
119
168
|
const config = (0, config_js_1.disconnectCloud)();
|
|
120
169
|
console.log('Disconnected from AgentGuard Cloud.');
|
|
121
|
-
console.log('Removed local Cloud API key, connection timestamp, pending event spool, and cached Cloud policy.');
|
|
170
|
+
console.log('Removed local Cloud API key, Agent JWT, connection timestamp, pending event spool, and cached Cloud policy.');
|
|
122
171
|
console.log(`Local protection remains active using the built-in policy. Audit log: ${config.auditPath}`);
|
|
123
172
|
});
|
|
124
173
|
program
|
|
@@ -131,6 +180,9 @@ async function main() {
|
|
|
131
180
|
console.log(`Protection level: ${config.level}`);
|
|
132
181
|
console.log(`Cloud URL: ${config.cloudUrl || 'not configured'}`);
|
|
133
182
|
console.log(`API key: ${(0, config_js_1.maskApiKey)(config.apiKey)}`);
|
|
183
|
+
console.log(`Agent ID: ${config.agentId || 'not configured'}`);
|
|
184
|
+
console.log(`Agent JWT: ${config.agentJwt ? 'configured' : 'not configured'}`);
|
|
185
|
+
console.log(`Agent activation URL: ${config.agentRegisterUrl || 'not configured'}`);
|
|
134
186
|
console.log(`Agent host: ${config.agentHost || 'not configured'}`);
|
|
135
187
|
console.log(`Agent hosts: ${config.agentHosts?.join(', ') || 'not configured'}`);
|
|
136
188
|
console.log(`Policy cache: ${config.policyCachePath}`);
|
|
@@ -145,10 +197,10 @@ async function main() {
|
|
|
145
197
|
.description('Pull the latest effective runtime policy from AgentGuard Cloud into the local cache')
|
|
146
198
|
.option('--json', 'Print JSON output')
|
|
147
199
|
.action(async (options) => {
|
|
148
|
-
|
|
149
|
-
|
|
200
|
+
let config = (0, config_js_1.ensureConfig)();
|
|
201
|
+
let client = new client_js_1.AgentGuardCloudClient(config);
|
|
150
202
|
if (!client.connected) {
|
|
151
|
-
const message = 'AgentGuard Cloud is not connected. Run `agentguard connect
|
|
203
|
+
const message = 'AgentGuard Cloud is not connected. Run `agentguard connect` first.';
|
|
152
204
|
if (options.json) {
|
|
153
205
|
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
|
154
206
|
}
|
|
@@ -159,7 +211,16 @@ async function main() {
|
|
|
159
211
|
return;
|
|
160
212
|
}
|
|
161
213
|
try {
|
|
162
|
-
const
|
|
214
|
+
const result = await runCloudRequestWithAgentJwtReauth({
|
|
215
|
+
config,
|
|
216
|
+
client,
|
|
217
|
+
reason: 'reauth',
|
|
218
|
+
notifyOpenClaw: true,
|
|
219
|
+
operation: (activeClient) => activeClient.fetchEffectivePolicy(),
|
|
220
|
+
});
|
|
221
|
+
config = result.config;
|
|
222
|
+
client = result.client;
|
|
223
|
+
const pulledPolicy = result.value;
|
|
163
224
|
(0, policy_js_1.saveCachedPolicy)(config.policyCachePath, pulledPolicy);
|
|
164
225
|
if (options.json) {
|
|
165
226
|
console.log(JSON.stringify({
|
|
@@ -229,8 +290,8 @@ async function main() {
|
|
|
229
290
|
console.log(`✓ Home: ${paths.home}`);
|
|
230
291
|
console.log(`✓ Config: ${paths.configPath}`);
|
|
231
292
|
console.log(`✓ Node: ${process.version}`);
|
|
232
|
-
|
|
233
|
-
|
|
293
|
+
const client = new client_js_1.AgentGuardCloudClient(config);
|
|
294
|
+
if (client.connected) {
|
|
234
295
|
try {
|
|
235
296
|
const status = await client.status();
|
|
236
297
|
const label = status.status || (status.ok ? 'ok' : status.service || 'reachable');
|
|
@@ -302,29 +363,143 @@ async function main() {
|
|
|
302
363
|
.option('--cron-run', 'Internal: run from the OpenClaw cron prompt without trying to install cron again')
|
|
303
364
|
.option('--cron-notify-run', 'Internal: run from an OpenClaw cron prompt and print only the notification body or NO_REPLY')
|
|
304
365
|
.action(async (options) => {
|
|
305
|
-
|
|
306
|
-
|
|
366
|
+
let config = (0, config_js_1.ensureConfig)();
|
|
367
|
+
let client = new client_js_1.AgentGuardCloudClient(config);
|
|
368
|
+
const cronAgentHost = resolveCronAgentHost(config);
|
|
307
369
|
const state = (0, state_js_1.loadFeedState)();
|
|
308
|
-
const since = options.since
|
|
370
|
+
const since = options.since;
|
|
309
371
|
const quiet = Boolean(options.quiet);
|
|
310
372
|
const cronNotifyRun = Boolean(options.cronNotifyRun);
|
|
311
373
|
const cronTarget = validateCronTarget(options.cronTarget);
|
|
374
|
+
const cronRunSendsToOpenClaw = Boolean(options.cronRun) && cronAgentHost === 'openclaw';
|
|
312
375
|
const cronExpression = options.cron && !options.cronRun
|
|
313
376
|
? (0, cron_js_1.validateCronExpression)(options.cron)
|
|
314
377
|
: undefined;
|
|
378
|
+
let registration = null;
|
|
379
|
+
if (!client.connected) {
|
|
380
|
+
if (!isOpenClawAgentConfigured(config)) {
|
|
381
|
+
const message = 'AgentGuard Cloud is not connected. Run `agentguard connect --key <key>` first, or run `agentguard init --agent openclaw` to use Agent JWT registration.';
|
|
382
|
+
if (cronNotifyRun) {
|
|
383
|
+
console.log('NO_REPLY');
|
|
384
|
+
}
|
|
385
|
+
else if (options.json) {
|
|
386
|
+
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
console.error(message);
|
|
390
|
+
}
|
|
391
|
+
process.exitCode = 1;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
registration = await registerAgentCredential({
|
|
396
|
+
cloudUrl: config.cloudUrl,
|
|
397
|
+
reason: 'subscribe',
|
|
398
|
+
notifyOpenClaw: resolveCronAgentHost(config) === 'openclaw',
|
|
399
|
+
});
|
|
400
|
+
config = registration.config;
|
|
401
|
+
client = registration.client;
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
if (cronNotifyRun) {
|
|
405
|
+
console.log('NO_REPLY');
|
|
406
|
+
process.exitCode = 0;
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
console.error(`! Could not register AgentGuard agent: ${err.message}`);
|
|
410
|
+
process.exitCode = 1;
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
await client.subscribeFeed();
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
if (err instanceof client_js_2.CloudRequestError && err.status === 401) {
|
|
419
|
+
if (!isOpenClawAgentConfigured(config)) {
|
|
420
|
+
console.error('! AgentGuard Cloud credential was rejected. Run `agentguard connect --key <key>` again.');
|
|
421
|
+
process.exitCode = 1;
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
registration = await registerAgentCredential({
|
|
426
|
+
cloudUrl: config.cloudUrl,
|
|
427
|
+
reason: 'subscribe',
|
|
428
|
+
notifyOpenClaw: resolveCronAgentHost(config) === 'openclaw',
|
|
429
|
+
resetExistingJwt: true,
|
|
430
|
+
});
|
|
431
|
+
config = registration.config;
|
|
432
|
+
client = registration.client;
|
|
433
|
+
await client.subscribeFeed();
|
|
434
|
+
}
|
|
435
|
+
catch (retryErr) {
|
|
436
|
+
if (cronNotifyRun) {
|
|
437
|
+
console.log('NO_REPLY');
|
|
438
|
+
process.exitCode = 0;
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
printAgentActivationRequired(registration, retryErr);
|
|
442
|
+
process.exitCode = 1;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
if (cronNotifyRun) {
|
|
448
|
+
console.log('NO_REPLY');
|
|
449
|
+
process.exitCode = 0;
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
console.error(`! Could not subscribe to AgentGuard Cloud feed: ${err.message}`);
|
|
453
|
+
process.exitCode = 1;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (registration && !cronNotifyRun && !quiet && !options.json) {
|
|
458
|
+
printAgentRegistrationNotice(registration);
|
|
459
|
+
}
|
|
315
460
|
let advisories;
|
|
316
461
|
try {
|
|
317
462
|
advisories = await client.pullAdvisories(since);
|
|
318
463
|
}
|
|
319
464
|
catch (err) {
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
465
|
+
if (err instanceof client_js_2.CloudRequestError && err.status === 401) {
|
|
466
|
+
if (!isOpenClawAgentConfigured(config)) {
|
|
467
|
+
console.error('! AgentGuard Cloud credential was rejected. Run `agentguard connect --key <key>` again.');
|
|
468
|
+
process.exitCode = 1;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
registration = await registerAgentCredential({
|
|
473
|
+
cloudUrl: config.cloudUrl,
|
|
474
|
+
reason: 'subscribe',
|
|
475
|
+
notifyOpenClaw: resolveCronAgentHost(config) === 'openclaw',
|
|
476
|
+
resetExistingJwt: true,
|
|
477
|
+
});
|
|
478
|
+
config = registration.config;
|
|
479
|
+
client = registration.client;
|
|
480
|
+
advisories = await client.pullAdvisories(since);
|
|
481
|
+
}
|
|
482
|
+
catch (retryErr) {
|
|
483
|
+
if (cronNotifyRun) {
|
|
484
|
+
console.log('NO_REPLY');
|
|
485
|
+
process.exitCode = 0;
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
printAgentActivationRequired(registration, retryErr);
|
|
489
|
+
process.exitCode = 1;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
if (cronNotifyRun) {
|
|
495
|
+
console.log('NO_REPLY');
|
|
496
|
+
process.exitCode = 0;
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
console.error(`! Could not reach AgentGuard Cloud: ${err.message}`);
|
|
500
|
+
process.exitCode = 1;
|
|
323
501
|
return;
|
|
324
502
|
}
|
|
325
|
-
console.error(`! Could not reach AgentGuard Cloud: ${err.message}`);
|
|
326
|
-
process.exitCode = 1;
|
|
327
|
-
return;
|
|
328
503
|
}
|
|
329
504
|
if (advisories === null) {
|
|
330
505
|
// 404 — older Cloud build without the feed endpoint. Not an error.
|
|
@@ -339,16 +514,17 @@ async function main() {
|
|
|
339
514
|
}
|
|
340
515
|
return;
|
|
341
516
|
}
|
|
342
|
-
const seen = new Set(
|
|
343
|
-
// Process oldest-first so
|
|
344
|
-
//
|
|
517
|
+
const seen = new Set((0, state_js_1.getSeenAdvisoryIds)(state));
|
|
518
|
+
// Process oldest-first so output stays deterministic when Cloud returns
|
|
519
|
+
// multiple fresh advisories.
|
|
345
520
|
const fresh = advisories
|
|
346
521
|
.filter((a) => !seen.has(a.id))
|
|
347
522
|
.sort((a, b) => (a.publishedAt < b.publishedAt ? -1 : 1));
|
|
348
523
|
const results = [];
|
|
349
|
-
let cursorOk = true; // stops advancing on the first hard failure
|
|
350
|
-
let latestPublishedAt = state.lastPulledAt;
|
|
351
524
|
let hardFailures = 0;
|
|
525
|
+
const newSeenIds = [];
|
|
526
|
+
const foundIds = [];
|
|
527
|
+
const pulledAt = new Date().toISOString();
|
|
352
528
|
if (quiet) {
|
|
353
529
|
for (const advisory of fresh) {
|
|
354
530
|
let processed = true;
|
|
@@ -361,7 +537,6 @@ async function main() {
|
|
|
361
537
|
// not been evaluated — don't mark it seen and don't advance.
|
|
362
538
|
console.error(`! Self-check threw for ${advisory.id}: ${err.message}`);
|
|
363
539
|
hardFailures += 1;
|
|
364
|
-
cursorOk = false;
|
|
365
540
|
continue;
|
|
366
541
|
}
|
|
367
542
|
results.push(result);
|
|
@@ -370,10 +545,20 @@ async function main() {
|
|
|
370
545
|
// match, we must NOT mark the advisory seen, otherwise a
|
|
371
546
|
// transient network blip silently buries a real hit.
|
|
372
547
|
try {
|
|
373
|
-
await
|
|
374
|
-
|
|
375
|
-
|
|
548
|
+
const reportResult = await runCloudRequestWithAgentJwtReauth({
|
|
549
|
+
config,
|
|
550
|
+
client,
|
|
551
|
+
reason: 'reauth',
|
|
552
|
+
notifyOpenClaw: resolveCronAgentHost(config) === 'openclaw',
|
|
553
|
+
operation: (activeClient) => activeClient.reportSelfCheck(advisory.id, result.matchedArtifacts, {
|
|
554
|
+
elapsedMs: result.elapsedMs,
|
|
555
|
+
warnings: result.warnings,
|
|
556
|
+
}),
|
|
376
557
|
});
|
|
558
|
+
config = reportResult.config;
|
|
559
|
+
client = reportResult.client;
|
|
560
|
+
if (reportResult.registration)
|
|
561
|
+
registration = reportResult.registration;
|
|
377
562
|
}
|
|
378
563
|
catch (err) {
|
|
379
564
|
console.error(`! Failed to report self-check for ${advisory.id}: ${err.message}`);
|
|
@@ -382,28 +567,29 @@ async function main() {
|
|
|
382
567
|
}
|
|
383
568
|
}
|
|
384
569
|
if (processed) {
|
|
385
|
-
|
|
386
|
-
if (
|
|
387
|
-
|
|
570
|
+
newSeenIds.push(advisory.id);
|
|
571
|
+
if (result.matchedArtifacts.length > 0) {
|
|
572
|
+
foundIds.push(advisory.id);
|
|
388
573
|
}
|
|
389
574
|
}
|
|
390
575
|
else {
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
cursorOk = false;
|
|
576
|
+
// Failed advisories are left out of newSeenIds, so the ID-based
|
|
577
|
+
// state will re-process them on the next subscribe run.
|
|
394
578
|
}
|
|
395
579
|
}
|
|
396
580
|
}
|
|
397
581
|
else {
|
|
398
582
|
for (const advisory of fresh) {
|
|
399
|
-
|
|
400
|
-
if (cursorOk && (!latestPublishedAt || advisory.publishedAt > latestPublishedAt)) {
|
|
401
|
-
latestPublishedAt = advisory.publishedAt;
|
|
402
|
-
}
|
|
583
|
+
newSeenIds.push(advisory.id);
|
|
403
584
|
}
|
|
404
585
|
}
|
|
405
|
-
|
|
406
|
-
|
|
586
|
+
const pendingStateEntry = newSeenIds.length > 0 || foundIds.length > 0
|
|
587
|
+
? {
|
|
588
|
+
pulledAt,
|
|
589
|
+
newSeenIds,
|
|
590
|
+
foundIds,
|
|
591
|
+
}
|
|
592
|
+
: null;
|
|
407
593
|
const totalMatches = results.reduce((acc, r) => acc + r.matchedArtifacts.length, 0);
|
|
408
594
|
const summary = buildSubscribeSummary({
|
|
409
595
|
supported: true,
|
|
@@ -433,6 +619,33 @@ async function main() {
|
|
|
433
619
|
throw err;
|
|
434
620
|
}
|
|
435
621
|
}
|
|
622
|
+
if (cronRunSendsToOpenClaw) {
|
|
623
|
+
if (summary.shouldNotify && summary.hardFailures === 0) {
|
|
624
|
+
const body = summary.notification?.body;
|
|
625
|
+
if (!body) {
|
|
626
|
+
console.error('! OpenClaw cron notification was requested, but no notification body was generated.');
|
|
627
|
+
process.exitCode = 1;
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const notification = await (0, openclaw_notify_js_1.notifyOpenClawMessage)(body, resolveOpenClawGatewayOptionsFromEnv(), {
|
|
631
|
+
idempotencyKeyPrefix: 'agentguard-subscribe',
|
|
632
|
+
});
|
|
633
|
+
if (!notification.notified) {
|
|
634
|
+
console.error(`! Could not send OpenClaw cron notification: ${notification.reason ?? 'Unknown error'}`);
|
|
635
|
+
process.exitCode = 1;
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (pendingStateEntry) {
|
|
640
|
+
(0, state_js_1.saveFeedState)((0, state_js_1.prependFeedStateEntry)(state, pendingStateEntry));
|
|
641
|
+
}
|
|
642
|
+
console.log('NO_REPLY');
|
|
643
|
+
process.exitCode = hardFailures > 0 ? 1 : 0;
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (pendingStateEntry) {
|
|
647
|
+
(0, state_js_1.saveFeedState)((0, state_js_1.prependFeedStateEntry)(state, pendingStateEntry));
|
|
648
|
+
}
|
|
436
649
|
if (cronNotifyRun) {
|
|
437
650
|
console.log(summary.shouldNotify && summary.hardFailures === 0 ? summary.notification?.body ?? 'NO_REPLY' : 'NO_REPLY');
|
|
438
651
|
process.exitCode = 0;
|
|
@@ -448,10 +661,7 @@ async function main() {
|
|
|
448
661
|
}
|
|
449
662
|
console.log(`Pulled ${advisories.length} advisory record(s); ${fresh.length} new.`);
|
|
450
663
|
if (!quiet && fresh.length > 0) {
|
|
451
|
-
console.log(
|
|
452
|
-
for (const advisory of fresh) {
|
|
453
|
-
console.log(` - ${advisory.id} [${advisory.severity}] ${advisory.summary}`);
|
|
454
|
-
}
|
|
664
|
+
console.log(summary.notification?.body ?? formatNewAdvisoryNotification(fresh));
|
|
455
665
|
}
|
|
456
666
|
else if (quiet && fresh.length > 0) {
|
|
457
667
|
console.log(`Self-check found ${totalMatches} match(es) across the new advisories.`);
|
|
@@ -466,8 +676,11 @@ async function main() {
|
|
|
466
676
|
}
|
|
467
677
|
if (summary.cron.result) {
|
|
468
678
|
const label = summary.cron.result.backend ?? 'cron';
|
|
469
|
-
const action = summary.cron.result.created ? `Installed ${label} cron job` : `${label} cron job already exists`;
|
|
679
|
+
const action = summary.cron.result.created ? `Installed ${label} cron job` : `${label} cron job already exists and was left unchanged`;
|
|
470
680
|
console.log(`${action} "${summary.cron.result.name}" (${summary.cron.result.schedule}, ${summary.cron.result.timezone}).`);
|
|
681
|
+
if (!summary.cron.result.created) {
|
|
682
|
+
console.log('Existing cron jobs are not reconfigured unless --force is passed; rerun with --force to apply the requested quiet/manual mode and schedule.');
|
|
683
|
+
}
|
|
471
684
|
if (summary.cron.result.backend === 'system') {
|
|
472
685
|
console.log(`System cron output: ${(0, node_path_1.join)((0, config_js_1.getAgentGuardPaths)().home, 'feed-cron.log')}`);
|
|
473
686
|
}
|
|
@@ -476,7 +689,7 @@ async function main() {
|
|
|
476
689
|
}
|
|
477
690
|
}
|
|
478
691
|
// Exit codes: 2 = matches found, 1 = at least one advisory failed
|
|
479
|
-
// to evaluate or report
|
|
692
|
+
// to evaluate or report, 0 = clean.
|
|
480
693
|
if (hardFailures > 0) {
|
|
481
694
|
console.error(`! ${hardFailures} advisory record(s) failed to process and will be re-pulled next run.`);
|
|
482
695
|
process.exitCode = 1;
|
|
@@ -494,7 +707,7 @@ async function main() {
|
|
|
494
707
|
.option('--against-advisory <id>', 'Restrict the check to a single advisory id (fetches it from Cloud if needed)')
|
|
495
708
|
.option('--json', 'Emit machine-readable result')
|
|
496
709
|
.action(async (options) => {
|
|
497
|
-
|
|
710
|
+
let config = (0, config_js_1.ensureConfig)();
|
|
498
711
|
const advisoryId = options.againstAdvisory;
|
|
499
712
|
if (!advisoryId) {
|
|
500
713
|
const report = await runLocalHealthCheckup(config);
|
|
@@ -513,9 +726,9 @@ async function main() {
|
|
|
513
726
|
process.exitCode = 0;
|
|
514
727
|
return;
|
|
515
728
|
}
|
|
516
|
-
|
|
729
|
+
let client = new client_js_1.AgentGuardCloudClient(config);
|
|
517
730
|
if (!client.connected) {
|
|
518
|
-
const message = 'AgentGuard Cloud is not connected. Run `agentguard connect
|
|
731
|
+
const message = 'AgentGuard Cloud is not connected. Run `agentguard connect` first.';
|
|
519
732
|
if (options.json) {
|
|
520
733
|
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
|
521
734
|
}
|
|
@@ -527,10 +740,25 @@ async function main() {
|
|
|
527
740
|
}
|
|
528
741
|
let advisory = null;
|
|
529
742
|
try {
|
|
530
|
-
|
|
743
|
+
const result = await runCloudRequestWithAgentJwtReauth({
|
|
744
|
+
config,
|
|
745
|
+
client,
|
|
746
|
+
reason: 'reauth',
|
|
747
|
+
notifyOpenClaw: true,
|
|
748
|
+
operation: (activeClient) => activeClient.getAdvisory(advisoryId),
|
|
749
|
+
});
|
|
750
|
+
config = result.config;
|
|
751
|
+
client = result.client;
|
|
752
|
+
advisory = result.value;
|
|
531
753
|
}
|
|
532
754
|
catch (err) {
|
|
533
|
-
|
|
755
|
+
const message = `Could not reach AgentGuard Cloud: ${err.message}`;
|
|
756
|
+
if (options.json) {
|
|
757
|
+
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
console.error(`! ${message}`);
|
|
761
|
+
}
|
|
534
762
|
process.exitCode = 1;
|
|
535
763
|
return;
|
|
536
764
|
}
|
|
@@ -709,12 +937,39 @@ function discoverSkillDirs(roots) {
|
|
|
709
937
|
if (!entry.isDirectory())
|
|
710
938
|
continue;
|
|
711
939
|
const dir = (0, node_path_1.join)(root, entry.name);
|
|
712
|
-
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'SKILL.md')))
|
|
713
|
-
|
|
940
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'SKILL.md')))
|
|
941
|
+
continue;
|
|
942
|
+
if (isManagedAgentGuardSkillDir(dir))
|
|
943
|
+
continue;
|
|
944
|
+
dirs.push(dir);
|
|
714
945
|
}
|
|
715
946
|
}
|
|
716
947
|
return dirs;
|
|
717
948
|
}
|
|
949
|
+
function isManagedAgentGuardSkillDir(dir) {
|
|
950
|
+
if (!/[/\\]agentguard$/i.test(dir))
|
|
951
|
+
return false;
|
|
952
|
+
const manifest = (0, node_path_1.join)(dir, 'SKILL.md');
|
|
953
|
+
let body = '';
|
|
954
|
+
try {
|
|
955
|
+
body = (0, node_fs_1.readFileSync)(manifest, 'utf8').slice(0, 16 * 1024);
|
|
956
|
+
}
|
|
957
|
+
catch {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
const hasAgentGuardIdentity = /^name:\s*agentguard\s*$/im.test(body) &&
|
|
961
|
+
/GoPlus AgentGuard|GoPlusSecurity/i.test(body);
|
|
962
|
+
if (!hasAgentGuardIdentity)
|
|
963
|
+
return false;
|
|
964
|
+
const expectedScripts = [
|
|
965
|
+
(0, node_path_1.join)(dir, 'scripts', 'guard-hook.js'),
|
|
966
|
+
(0, node_path_1.join)(dir, 'scripts', 'hermes-hook.js'),
|
|
967
|
+
(0, node_path_1.join)(dir, 'scripts', 'checkup-report.js'),
|
|
968
|
+
];
|
|
969
|
+
if (!expectedScripts.every((path) => (0, node_fs_1.existsSync)(path)))
|
|
970
|
+
return false;
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
718
973
|
function checkCredentialSafety(skillDirs) {
|
|
719
974
|
let score = 100;
|
|
720
975
|
const findings = [];
|
|
@@ -870,6 +1125,102 @@ function runCommandText(command, args) {
|
|
|
870
1125
|
}
|
|
871
1126
|
});
|
|
872
1127
|
}
|
|
1128
|
+
async function runCloudRequestWithAgentJwtReauth(options) {
|
|
1129
|
+
try {
|
|
1130
|
+
return {
|
|
1131
|
+
value: await options.operation(options.client),
|
|
1132
|
+
config: options.config,
|
|
1133
|
+
client: options.client,
|
|
1134
|
+
registration: null,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
catch (err) {
|
|
1138
|
+
if (!(err instanceof client_js_2.CloudRequestError && err.status === 401) ||
|
|
1139
|
+
!options.config.agentJwt ||
|
|
1140
|
+
!isOpenClawAgentConfigured(options.config)) {
|
|
1141
|
+
throw err;
|
|
1142
|
+
}
|
|
1143
|
+
const registration = await registerAgentCredential({
|
|
1144
|
+
cloudUrl: options.config.cloudUrl,
|
|
1145
|
+
reason: options.reason,
|
|
1146
|
+
notifyOpenClaw: options.notifyOpenClaw,
|
|
1147
|
+
resetExistingJwt: true,
|
|
1148
|
+
});
|
|
1149
|
+
return {
|
|
1150
|
+
value: await options.operation(registration.client),
|
|
1151
|
+
config: registration.config,
|
|
1152
|
+
client: registration.client,
|
|
1153
|
+
registration,
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
async function registerAgentCredential(options) {
|
|
1158
|
+
if (options.resetExistingJwt) {
|
|
1159
|
+
(0, config_js_1.clearAgentJwt)();
|
|
1160
|
+
}
|
|
1161
|
+
const baseConfig = (0, config_js_1.ensureConfig)();
|
|
1162
|
+
const cloudUrl = (0, config_js_1.normalizeCloudUrl)(options.cloudUrl || baseConfig.cloudUrl || 'https://agentguard.gopluslabs.io');
|
|
1163
|
+
const client = new client_js_1.AgentGuardCloudClient({ ...baseConfig, cloudUrl });
|
|
1164
|
+
const registration = await client.registerAgent({
|
|
1165
|
+
metadata: {
|
|
1166
|
+
agentHost: baseConfig.agentHost,
|
|
1167
|
+
agentHosts: baseConfig.agentHosts,
|
|
1168
|
+
agentVersion: version_js_1.packageVersion,
|
|
1169
|
+
platform: process.platform,
|
|
1170
|
+
arch: process.arch,
|
|
1171
|
+
reason: options.reason,
|
|
1172
|
+
},
|
|
1173
|
+
});
|
|
1174
|
+
const config = (0, config_js_1.connectAgentJwt)({
|
|
1175
|
+
agentId: registration.agentId,
|
|
1176
|
+
agentJwt: registration.jwt,
|
|
1177
|
+
agentRegisterUrl: registration.registerUrl,
|
|
1178
|
+
cloudUrl,
|
|
1179
|
+
});
|
|
1180
|
+
const nextClient = new client_js_1.AgentGuardCloudClient(config);
|
|
1181
|
+
const openClawNotification = options.notifyOpenClaw
|
|
1182
|
+
? await (0, openclaw_notify_js_1.notifyOpenClawRegistrationLink)(registration.registerUrl, resolveOpenClawGatewayOptionsFromEnv())
|
|
1183
|
+
: { notified: false, reason: 'OpenClaw notification was not requested.' };
|
|
1184
|
+
return {
|
|
1185
|
+
config,
|
|
1186
|
+
client: nextClient,
|
|
1187
|
+
registerUrl: registration.registerUrl,
|
|
1188
|
+
openClawNotification,
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
function printAgentRegistrationNotice(registration) {
|
|
1192
|
+
console.log('AgentGuard Cloud activation is ready:');
|
|
1193
|
+
console.log(registration.registerUrl);
|
|
1194
|
+
if (registration.openClawNotification.notified) {
|
|
1195
|
+
console.log('Sent the activation link to the last OpenClaw channel.');
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
function printAgentActivationRequired(registration, err) {
|
|
1199
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1200
|
+
console.error(`! AgentGuard Cloud authorization is not active yet. ${message}`);
|
|
1201
|
+
const registerUrl = registration?.registerUrl || (0, config_js_1.ensureConfig)().agentRegisterUrl;
|
|
1202
|
+
if (registerUrl) {
|
|
1203
|
+
console.error('Open this link to bind this agent to your email, then rerun the command:');
|
|
1204
|
+
console.error(registerUrl);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function isOpenClawAgentConfigured(config) {
|
|
1208
|
+
return config.agentHost === 'openclaw' || config.agentHosts?.includes('openclaw') === true;
|
|
1209
|
+
}
|
|
1210
|
+
function resolveOpenClawGatewayOptionsFromEnv() {
|
|
1211
|
+
const url = process.env.AGENTGUARD_OPENCLAW_GATEWAY_URL?.trim();
|
|
1212
|
+
const host = process.env.AGENTGUARD_OPENCLAW_GATEWAY_HOST?.trim();
|
|
1213
|
+
const portRaw = process.env.AGENTGUARD_OPENCLAW_GATEWAY_PORT?.trim();
|
|
1214
|
+
const timeoutRaw = process.env.AGENTGUARD_OPENCLAW_GATEWAY_TIMEOUT_MS?.trim();
|
|
1215
|
+
const port = portRaw ? Number(portRaw) : undefined;
|
|
1216
|
+
const timeoutMs = timeoutRaw ? Number(timeoutRaw) : undefined;
|
|
1217
|
+
return {
|
|
1218
|
+
...(url ? { url } : {}),
|
|
1219
|
+
...(host ? { host } : {}),
|
|
1220
|
+
...(Number.isFinite(port) ? { port } : {}),
|
|
1221
|
+
...(Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
873
1224
|
function calculateCompositeScore(dimensions) {
|
|
874
1225
|
const web3Score = dimensions.web3_safety.score;
|
|
875
1226
|
if (web3Score === null || dimensions.web3_safety.na) {
|
|
@@ -1003,13 +1354,30 @@ function formatNewAdvisoryNotification(advisories) {
|
|
|
1003
1354
|
const lines = ['AgentGuard found new threat-feed advisories that need manual review:'];
|
|
1004
1355
|
for (const advisory of advisories.slice(0, 10)) {
|
|
1005
1356
|
lines.push(`- ${advisory.id} [${advisory.severity}] ${advisory.summary}`);
|
|
1357
|
+
const remediation = formatAdvisoryRemediation(advisory);
|
|
1358
|
+
if (remediation) {
|
|
1359
|
+
lines.push(' Remediation guidance:');
|
|
1360
|
+
for (const line of remediation.split('\n')) {
|
|
1361
|
+
lines.push(` ${line}`);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1006
1364
|
}
|
|
1007
1365
|
if (advisories.length > 10) {
|
|
1008
1366
|
lines.push(`- ... ${advisories.length - 10} more`);
|
|
1009
1367
|
}
|
|
1010
|
-
lines.push('Run `agentguard subscribe --quiet` to execute the local self-check and report matches automatically.');
|
|
1011
1368
|
return lines.join('\n');
|
|
1012
1369
|
}
|
|
1370
|
+
function formatAdvisoryRemediation(advisory) {
|
|
1371
|
+
const remediation = advisory.selfCheck?.remediationMd?.trim();
|
|
1372
|
+
if (!remediation)
|
|
1373
|
+
return null;
|
|
1374
|
+
const compact = remediation
|
|
1375
|
+
.replace(/\r\n/g, '\n')
|
|
1376
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
1377
|
+
.trim();
|
|
1378
|
+
const maxLen = 1200;
|
|
1379
|
+
return compact.length > maxLen ? `${compact.slice(0, maxLen).trimEnd()}\n...` : compact;
|
|
1380
|
+
}
|
|
1013
1381
|
function formatThreatFeedNotification(results) {
|
|
1014
1382
|
const lines = ['AgentGuard threat-feed self-check found local matches:'];
|
|
1015
1383
|
for (const result of results) {
|