agentgate-runtime-control 2.13.11 → 2.13.12

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.
Files changed (2) hide show
  1. package/bin/agentgate.js +95 -3
  2. package/package.json +1 -1
package/bin/agentgate.js CHANGED
@@ -35,6 +35,7 @@ Usage:
35
35
  agentgate egress <response.json> [--strict]
36
36
  agentgate policy <create|list|test|activate|rollback|diff> <name> ...
37
37
  agentgate tenant <create|list|key|revoke|rotate> ...
38
+ agentgate approval <list|approve|deny> [id] [reason] [--url <url>] [--key <apiKey>]
38
39
  agentgate --help
39
40
  agentgate doctor
40
41
  agentgate simulate
@@ -108,9 +109,22 @@ export { pack };
108
109
  } else if (cmd === 'doctor') {
109
110
  const configPath = path.resolve(process.cwd(), 'agentgate.config.mjs');
110
111
  let config = { mode: 'enforce', policies: { productionBlock: true, autoApproveAmount: 500, approvalAmount: 5000 }, authRequired: true };
111
- try { const mod = await import(`file://${configPath}?doctor=${Date.now()}`); const gate = mod.agentgate || {}; config = { ...config, ...(mod.config || {}), ...(gate.config || {}), ...(gate.mode ? { mode: gate.mode } : {}), ...(gate.policies ? { policies: gate.policies } : {}) }; } catch {}
112
+ let foundConfig = false;
113
+ try { const mod = await import(`file://${configPath}?doctor=${Date.now()}`); const gate = mod.agentgate || {}; config = { ...config, ...(mod.config || {}), ...(gate.config || {}), ...(gate.mode ? { mode: gate.mode } : {}), ...(gate.policies ? { policies: gate.policies } : {}) }; foundConfig = true; } catch {}
112
114
  const report = runDoctorChecks({ config });
113
- console.log(JSON.stringify(report, null, 2)); process.exitCode = report.ok ? 0 : 2;
115
+ console.log(JSON.stringify(report, null, 2));
116
+ if (!foundConfig) {
117
+ console.error('\nNo agentgate.config.mjs found here — checked built-in defaults instead. Run `agentgate init` first to create one for your own policies.');
118
+ }
119
+ if (config.mode === 'observe') {
120
+ console.error('\n⚠️ WARNING: mode is "observe". Decisions are being recorded but NOTHING is actually blocked or held for approval yet — destructive tools will still execute. Set mode: \'enforce\' in agentgate.config.mjs once you are ready to protect real tools.');
121
+ }
122
+ if (report.ok) {
123
+ console.error('\nNext: protect your first tool — see examples/protect-first-tool.mjs for a worked example (read/delete/refund/export), or run `agentgate attack --config agentgate.config.mjs` to test your policy against the built-in attack scenarios.');
124
+ } else {
125
+ console.error('\nFix the failing checks above, then run `agentgate doctor` again.');
126
+ }
127
+ process.exitCode = report.ok ? 0 : 2;
114
128
  } else if (cmd === 'simulate') {
115
129
  const policy = { productionBlock: true, autoApproveAmount: 500, approvalAmount: 5000 };
116
130
  console.log(JSON.stringify(simulatePolicyMatrix({ policies: policy }), null, 2));
@@ -287,7 +301,85 @@ export { pack };
287
301
  ? `import { createAgentGate, getPolicyPack } from 'agentgate-runtime-control';\n\nconst pack = getPolicyPack('${pack.id}');\n\nexport const agentgate = createAgentGate({\n agent: 'SupportAgent',\n mode: 'observe',\n policies: pack.policies\n});\n\nexport { pack };\n`
288
302
  : `import { createAgentGate } from 'agentgate-runtime-control';\n\nexport const agentgate = createAgentGate({\n agent: 'MyAgent',\n mode: 'enforce',\n policies: {\n productionBlock: true,\n autoApproveAmount: 500,\n approvalAmount: 5000\n }\n});\n`;
289
303
  try { await fs.access(file); console.error('agentgate.config.mjs already exists'); process.exitCode = 1; }
290
- catch { await fs.writeFile(file, content, 'utf8'); console.log(`Created ${file}${pack ? ` from ${pack.name} (enforce mode)` : ''}`); }
304
+ catch {
305
+ await fs.writeFile(file, content, 'utf8');
306
+ console.log(`Created ${file}${pack ? ` from ${pack.name} (enforce mode)` : ''}`);
307
+ console.log('\nNext: run `agentgate doctor` to check this config, then see examples/protect-first-tool.mjs to protect your first tool.');
308
+ }
309
+ }
310
+ } else if (cmd === 'approval') {
311
+ const sub = process.argv[3];
312
+ const urlIndex = process.argv.indexOf('--url');
313
+ const baseUrl = (urlIndex >= 0 ? process.argv[urlIndex + 1] : (process.env.AGENTGATE_URL || 'http://localhost:8787')).replace(/\/$/, '');
314
+ const keyIndex = process.argv.indexOf('--key');
315
+ const apiKey = keyIndex >= 0 ? process.argv[keyIndex + 1] : (process.env.AGENTGATE_API_KEY || null);
316
+
317
+ async function authHeaders() {
318
+ if (apiKey) return { 'x-agentgate-key': apiKey, 'content-type': 'application/json' };
319
+ // No API key given — try to pick up the local session cookie that `agentgate dev`
320
+ // hands out on GET '/', so this CLI can talk to a locally running dev server
321
+ // without any extra setup.
322
+ try {
323
+ const res = await fetch(`${baseUrl}/`);
324
+ const cookie = res.headers.get('set-cookie');
325
+ const match = cookie && cookie.match(/agentgate_local_session=[^;]+/);
326
+ if (match) return { cookie: match[0], 'content-type': 'application/json' };
327
+ } catch {}
328
+ return { 'content-type': 'application/json' };
329
+ }
330
+
331
+ async function call(apiPath, method = 'GET', body) {
332
+ let headers;
333
+ try { headers = await authHeaders(); }
334
+ catch (err) { return { status: 0, json: { error: `Could not reach ${baseUrl}: ${err.message}` } }; }
335
+ try {
336
+ const res = await fetch(`${baseUrl}${apiPath}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
337
+ let json;
338
+ try { json = await res.json(); } catch { json = { error: `Non-JSON response (status ${res.status})` }; }
339
+ return { status: res.status, json };
340
+ } catch (err) {
341
+ return { status: 0, json: { error: `Could not reach ${baseUrl}: ${err.message}` } };
342
+ }
343
+ }
344
+
345
+ const unreachable = (result) => {
346
+ console.error(`Could not reach AgentGate at ${baseUrl}${result.status ? ` (HTTP ${result.status})` : ''}: ${result.json.error || 'unknown error'}`);
347
+ console.error('Is `agentgate dev` running? Point at it with --url <http://host:port>, or pass --key <apiKey> if auth is required.');
348
+ process.exitCode = 1;
349
+ };
350
+
351
+ if (sub === 'list') {
352
+ const statusIndex = process.argv.indexOf('--status');
353
+ const status = statusIndex >= 0 ? process.argv[statusIndex + 1] : undefined;
354
+ const result = await call(`/api/approvals${status ? `?status=${encodeURIComponent(status)}` : ''}`);
355
+ if (result.status !== 200) unreachable(result);
356
+ else {
357
+ const approvals = result.json.approvals || [];
358
+ if (!approvals.length) console.log('No approvals found.');
359
+ else console.table(approvals.map(a => ({ id: a.id, status: a.status, action: a.metadata?.action || a.request?.action || '', createdAt: a.createdAt, expiresAt: a.expiresAt || 'never' })));
360
+ }
361
+ } else if (sub === 'approve' || sub === 'deny') {
362
+ const id = process.argv[4];
363
+ if (!id) { console.error(`Usage: agentgate approval ${sub} <approvalId>${sub === 'deny' ? ' [reason]' : ''} [--url <url>] [--key <apiKey>]`); process.exitCode = 1; }
364
+ else {
365
+ const reason = sub === 'deny' ? process.argv[5] : undefined;
366
+ const apiPath = sub === 'approve' ? '/api/approvals/approve' : '/api/approvals/deny';
367
+ const result = await call(apiPath, 'POST', sub === 'approve' ? { approvalId: id } : { approvalId: id, reason });
368
+ if (result.status !== 200 || result.json.ok === false) {
369
+ if (result.status === 0 || result.status === 401 || result.status === 403) unreachable(result);
370
+ else { console.error(`Could not ${sub} ${id}: ${result.json.error || `HTTP ${result.status}`}`); process.exitCode = 1; }
371
+ } else {
372
+ console.log(JSON.stringify(result.json, null, 2));
373
+ }
374
+ }
375
+ } else {
376
+ console.error(`Usage: agentgate approval <list|approve|deny> ...
377
+ agentgate approval list [--status pending|approved|denied|expired] [--url <url>] [--key <apiKey>]
378
+ agentgate approval approve <approvalId> [--url <url>] [--key <apiKey>]
379
+ agentgate approval deny <approvalId> [reason] [--url <url>] [--key <apiKey>]
380
+
381
+ By default this talks to a locally running \`agentgate dev\` server at http://localhost:8787.`);
382
+ process.exitCode = 1;
291
383
  }
292
384
  } else if (cmd === 'dev') {
293
385
  const htmlPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../standalone.html');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgate-runtime-control",
3
- "version": "2.13.11",
3
+ "version": "2.13.12",
4
4
  "type": "module",
5
5
  "description": "Runtime control plane and SaaS governance layer for AI agent tool execution",
6
6
  "exports": {