@suveren/gateway 0.10.0 → 0.11.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/README.md +36 -0
- package/bin/suveren-gateway.js +117 -2
- package/content/integrations/crm.json +48 -3
- package/content/integrations/erp.json +51 -3
- package/content/integrations/index.json +2 -1
- package/content/integrations/mail.json +148 -0
- package/dist/control-plane/index.mjs +49 -6
- package/dist/mcp-server/http.mjs +150 -56
- package/dist/ui/assets/{index-B1qNl7zo.js → index-Bqb9azUL.js} +23 -23
- package/dist/ui/assets/index-DBdWDq4V.css +1 -0
- package/dist/ui/index.html +2 -2
- package/lib/config.d.mts +4 -0
- package/lib/config.mjs +16 -0
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/@types/node/zlib.d.ts +6 -0
- package/node_modules/follow-redirects/index.js +10 -1
- package/node_modules/follow-redirects/package.json +1 -1
- package/node_modules/ip-address/README.md +34 -34
- package/node_modules/ip-address/dist/ipv4.js +7 -0
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/package.json +1 -1
- package/package.json +1 -1
- package/profiles/customers/0.8.profile.json +187 -0
- package/profiles/email/0.7.profile.json +254 -0
- package/profiles/index.json +4 -1
- package/profiles/sales/0.2.profile.json +282 -0
- package/server.js +8 -1
- package/dist/ui/assets/index-BJqF5rmx.css +0 -1
package/README.md
CHANGED
|
@@ -185,6 +185,42 @@ See `suveren-gateway config help` for the full command reference.
|
|
|
185
185
|
|
|
186
186
|
---
|
|
187
187
|
|
|
188
|
+
## Simulation mode — block every real system
|
|
189
|
+
|
|
190
|
+
A mandate is bound to a *profile* (e.g. "sales"), not to a specific connector. If a real
|
|
191
|
+
connector (e.g. a live ERP account) and a simulated one (the built-in ERP/CRM/email
|
|
192
|
+
simulators) share a profile on one gateway, a mandate meant only for testing also
|
|
193
|
+
authorizes the real connector — nothing about the mandate says which one it's for.
|
|
194
|
+
|
|
195
|
+
Simulation mode closes that gap gateway-wide: turn it on and **every connector without a
|
|
196
|
+
manifest `simulation` marker is refused to even start** (the built-in ERP, CRM, and email
|
|
197
|
+
simulators all declare one; a connected Gmail or live ERP account does not), and every
|
|
198
|
+
connector that does declare one has its mode forced to `simulation`, overriding whatever
|
|
199
|
+
credential value is on file. Mandates and profiles are completely unaffected, and the
|
|
200
|
+
Authority Server never learns this is on — it's a purely local, gateway-side switch.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Start fresh with real systems blocked:
|
|
204
|
+
suveren-gateway start --simulation
|
|
205
|
+
|
|
206
|
+
# Or flip it on an existing install (takes effect on the next start/restart):
|
|
207
|
+
suveren-gateway simulation on
|
|
208
|
+
suveren-gateway restart
|
|
209
|
+
|
|
210
|
+
# Check the mode (the SAVED setting and, if the gateway is running, the LIVE one):
|
|
211
|
+
suveren-gateway simulation status
|
|
212
|
+
|
|
213
|
+
# Turning it off makes real systems reachable again, so it requires typed
|
|
214
|
+
# confirmation (or --confirm live for scripts) — turning it ON does not:
|
|
215
|
+
suveren-gateway simulation off
|
|
216
|
+
suveren-gateway restart
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`suveren-gateway status` also shows the current mode. See `suveren-gateway simulation help`
|
|
220
|
+
for the full command reference.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
188
224
|
## Technical Documentation
|
|
189
225
|
|
|
190
226
|
| Document | Contents |
|
package/bin/suveren-gateway.js
CHANGED
|
@@ -17,7 +17,8 @@ import { homedir, platform, userInfo } from 'node:os';
|
|
|
17
17
|
import { dirname, join, resolve } from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { buildLaunchAgentPlist, buildMacLauncher, buildSystemdUnit, buildWindowsTaskXml } from '../lib/autostart-templates.mjs';
|
|
20
|
-
import { DEFAULT_AS_URL, readConfig, writeConfig, validateAsUrl, validateCaFile, validatePinTls, resolveAsUrl, resolvePinTls, resolvePinTlsExpectedFingerprint } from '../lib/config.mjs';
|
|
20
|
+
import { DEFAULT_AS_URL, readConfig, writeConfig, validateAsUrl, validateCaFile, validatePinTls, resolveAsUrl, resolvePinTls, resolvePinTlsExpectedFingerprint, resolveSimulation } from '../lib/config.mjs';
|
|
21
|
+
import { createInterface } from 'node:readline/promises';
|
|
21
22
|
import { readPairing as readAsPairing, recordTlsPin, formatFingerprint, normalizeFingerprint } from '../lib/as-pairing.mjs';
|
|
22
23
|
|
|
23
24
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -210,6 +211,13 @@ async function start(args) {
|
|
|
210
211
|
pinTlsToSave = true;
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
// --simulation: the safe direction (turning OFF real systems), so — unlike
|
|
215
|
+
// `simulation off` — this needs no confirmation. Only ever turns it ON;
|
|
216
|
+
// there is no `start --no-simulation` (use `simulation off` for that, which
|
|
217
|
+
// DOES require confirmation, since that direction makes real systems
|
|
218
|
+
// reachable again).
|
|
219
|
+
const simulationFlag = args.includes('--simulation');
|
|
220
|
+
|
|
213
221
|
if (await isAlreadyRunning()) {
|
|
214
222
|
console.error(`suveren-gateway is already running (pid ${readPid()}). Use \`suveren-gateway stop\` first or \`suveren-gateway restart\`.`);
|
|
215
223
|
process.exit(1);
|
|
@@ -269,6 +277,10 @@ async function start(args) {
|
|
|
269
277
|
: ` Staged — the first sign-in's challenge must match it (refused otherwise); it then becomes the permanent pin.`,
|
|
270
278
|
);
|
|
271
279
|
}
|
|
280
|
+
if (simulationFlag) {
|
|
281
|
+
writeConfig(DATA_DIR, { simulation: true });
|
|
282
|
+
console.log(`Simulation mode: ON (saved) — every connector without a manifest "simulation" marker will be blocked.`);
|
|
283
|
+
}
|
|
272
284
|
|
|
273
285
|
if (detach) {
|
|
274
286
|
const out = openSync(LOG_FILE, 'a');
|
|
@@ -414,6 +426,7 @@ async function status() {
|
|
|
414
426
|
console.log('suveren-gateway: running (managed by the login service)');
|
|
415
427
|
console.log(` UI: http://localhost:${SUVEREN_PORT}`);
|
|
416
428
|
console.log(` Vault: ${body.vaultUnlocked ? 'unlocked' : 'locked'}`);
|
|
429
|
+
console.log(` Simulation: ${body.simulation ? 'on — real systems are blocked' : 'off'}`);
|
|
417
430
|
console.log(` Version: ${body.version ?? 'unknown'} (running)`);
|
|
418
431
|
console.log(` Service: suveren-gateway service status`);
|
|
419
432
|
return;
|
|
@@ -438,6 +451,7 @@ async function status() {
|
|
|
438
451
|
console.log(`suveren-gateway: running (pid ${pid})`);
|
|
439
452
|
console.log(` UI: http://localhost:${SUVEREN_PORT}`);
|
|
440
453
|
console.log(` Vault: ${body.vaultUnlocked ? 'unlocked' : 'locked'}`);
|
|
454
|
+
console.log(` Simulation: ${body.simulation ? 'on — real systems are blocked' : 'off'}`);
|
|
441
455
|
console.log(` Version: ${body.version ?? 'unknown'} (running)`);
|
|
442
456
|
if (CLI_VERSION) console.log(` ${CLI_VERSION} (installed CLI)`);
|
|
443
457
|
if (CLI_VERSION && body.version && body.version !== CLI_VERSION && body.version !== 'dev') {
|
|
@@ -634,6 +648,102 @@ async function config(args) {
|
|
|
634
648
|
if (sub !== undefined && sub !== 'help' && sub !== '--help' && sub !== '-h') process.exit(2);
|
|
635
649
|
}
|
|
636
650
|
|
|
651
|
+
// ─── simulation: gateway-wide switch that blocks every real system ──────
|
|
652
|
+
//
|
|
653
|
+
// A mandate is bound to a PROFILE, not a connector — see suveren-gateway's
|
|
654
|
+
// tool-proxy.ts (`profileMatches`). Put a real connector and a simulated one
|
|
655
|
+
// on the same profile, and one mandate authorizes both. Simulation mode
|
|
656
|
+
// closes that generically: ON blocks every connector whose manifest carries
|
|
657
|
+
// no `simulation` marker, and forces every connector that DOES have one into
|
|
658
|
+
// its simulated mode, regardless of what credential value is on file.
|
|
659
|
+
// Mandates and profiles are unaffected; the Authority Server never learns
|
|
660
|
+
// about this — it is a purely local, gateway-side switch. See
|
|
661
|
+
// apps/mcp-server/src/lib/simulation-mode.ts for enforcement.
|
|
662
|
+
|
|
663
|
+
function printSimulationHelp() {
|
|
664
|
+
console.log(`suveren-gateway simulation — gateway-wide switch that blocks every real system
|
|
665
|
+
|
|
666
|
+
Usage:
|
|
667
|
+
suveren-gateway simulation on Block every real connector (no confirmation — the safe direction)
|
|
668
|
+
suveren-gateway simulation off Make real systems reachable again (requires confirmation)
|
|
669
|
+
[--confirm live] Non-interactive confirmation
|
|
670
|
+
suveren-gateway simulation status Show the saved setting and (if running) the live one
|
|
671
|
+
|
|
672
|
+
Changes take effect on the next \`suveren-gateway start\` / \`restart\`.
|
|
673
|
+
`);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function printSimulationStatus() {
|
|
677
|
+
const saved = resolveSimulation(DATA_DIR);
|
|
678
|
+
console.log(`Saved: ${saved ? 'on' : 'off'}`);
|
|
679
|
+
try {
|
|
680
|
+
const res = await fetch(`http://localhost:${SUVEREN_PORT}/health`, { signal: AbortSignal.timeout(3000) });
|
|
681
|
+
if (res.ok) {
|
|
682
|
+
const body = await res.json();
|
|
683
|
+
const running = body.simulation === true;
|
|
684
|
+
console.log(`Running: ${running ? 'on' : 'off'}${running !== saved ? ' (restart to apply the saved value)' : ''}`);
|
|
685
|
+
}
|
|
686
|
+
} catch {
|
|
687
|
+
// Not reachable — the saved value is all there is to report.
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** Read one line from stdin, or undefined if there is no TTY to prompt on
|
|
692
|
+
* (so a non-interactive caller gets a clean abort instead of a hang). */
|
|
693
|
+
async function promptLine(question) {
|
|
694
|
+
if (!process.stdin.isTTY) return undefined;
|
|
695
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
696
|
+
try {
|
|
697
|
+
return (await rl.question(question)).trim();
|
|
698
|
+
} finally {
|
|
699
|
+
rl.close();
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async function simulation(args) {
|
|
704
|
+
const sub = args[0] ?? 'status';
|
|
705
|
+
|
|
706
|
+
if (sub === 'help' || sub === '--help' || sub === '-h') { printSimulationHelp(); return; }
|
|
707
|
+
|
|
708
|
+
if (sub === 'status') {
|
|
709
|
+
await printSimulationStatus();
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (sub === 'on') {
|
|
714
|
+
// The safe direction — blocking real systems needs no confirmation.
|
|
715
|
+
writeConfig(DATA_DIR, { simulation: true });
|
|
716
|
+
console.log('Simulation mode: ON (saved) — every connector without a manifest "simulation" marker will be blocked.');
|
|
717
|
+
console.log('Restart to pick it up: suveren-gateway restart');
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (sub === 'off') {
|
|
722
|
+
console.error('Turning simulation mode OFF makes every real system reachable again: any connector');
|
|
723
|
+
console.error('without a manifest "simulation" marker (e.g. a live Gmail or ERP account) will spawn');
|
|
724
|
+
console.error('and execute for real the next time this gateway starts.');
|
|
725
|
+
console.error('');
|
|
726
|
+
let typed = flagValue(args, '--confirm');
|
|
727
|
+
if (typed === undefined) {
|
|
728
|
+
typed = await promptLine('Type "live" to confirm, or anything else to abort: ');
|
|
729
|
+
}
|
|
730
|
+
if (typed !== 'live') {
|
|
731
|
+
console.error(typed === undefined
|
|
732
|
+
? 'No TTY to confirm on and no --confirm given — aborted. Simulation mode unchanged.'
|
|
733
|
+
: 'Aborted — simulation mode unchanged.');
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
writeConfig(DATA_DIR, { simulation: false });
|
|
737
|
+
console.log('Simulation mode: OFF (saved) — real systems become reachable on next restart.');
|
|
738
|
+
console.log('Restart to pick it up: suveren-gateway restart');
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
console.error(`Unknown: simulation ${sub}\n`);
|
|
743
|
+
printSimulationHelp();
|
|
744
|
+
process.exit(2);
|
|
745
|
+
}
|
|
746
|
+
|
|
637
747
|
// ─── service: install autostart-on-login (survives reboot) ──────────────
|
|
638
748
|
//
|
|
639
749
|
// Keeps the gateway PROCESS always running (starts on login, restarts on
|
|
@@ -1139,9 +1249,10 @@ Usage:
|
|
|
1139
1249
|
[--as-url <url>] Authority Server URL (saved for next time)
|
|
1140
1250
|
[--ca-file <path>] Internal CA bundle (saved for next time)
|
|
1141
1251
|
[--pin-tls --expect-fingerprint <hex>] Pin the AS TLS certificate (saved; https:// only — see below)
|
|
1252
|
+
[--simulation] Block every real system (saved — see \`simulation help\`)
|
|
1142
1253
|
suveren-gateway stop Stop a detached gateway
|
|
1143
1254
|
suveren-gateway restart Stop, then start --detach
|
|
1144
|
-
suveren-gateway status Show running state + health
|
|
1255
|
+
suveren-gateway status Show running state + health (incl. simulation mode)
|
|
1145
1256
|
suveren-gateway logs [--tail] Print or tail ~/.suveren/gateway.log
|
|
1146
1257
|
suveren-gateway service <cmd> Run as a login service that survives reboot
|
|
1147
1258
|
(install | uninstall | status)
|
|
@@ -1152,6 +1263,8 @@ Usage:
|
|
|
1152
1263
|
Pin the Authority Server's TLS certificate
|
|
1153
1264
|
(see \`suveren-gateway config help\` for the fingerprint check)
|
|
1154
1265
|
suveren-gateway config set pin-tls off Stop enforcing the TLS certificate pin
|
|
1266
|
+
suveren-gateway simulation on|off|status Block (or unblock) every real system
|
|
1267
|
+
(see \`suveren-gateway simulation help\`)
|
|
1155
1268
|
suveren-gateway help Print this help
|
|
1156
1269
|
|
|
1157
1270
|
Environment:
|
|
@@ -1159,6 +1272,7 @@ Environment:
|
|
|
1159
1272
|
SUVEREN_MCP_PORT MCP server port (default 3430)
|
|
1160
1273
|
SUVEREN_DATA_DIR Data directory (default ~/.suveren)
|
|
1161
1274
|
SUVEREN_AS_URL Authority Server URL — overrides the saved as-url
|
|
1275
|
+
SUVEREN_SIMULATION 1 to force simulation mode for this run — overrides the saved setting
|
|
1162
1276
|
|
|
1163
1277
|
Authority Server resolution order: --as-url flag > SUVEREN_AS_URL env >
|
|
1164
1278
|
saved as-url (\`config set as-url\`) > default (${DEFAULT_AS_URL}).
|
|
@@ -1181,6 +1295,7 @@ async function main() {
|
|
|
1181
1295
|
case 'logs': await logs(argv.slice(1)); break;
|
|
1182
1296
|
case 'service': await service(argv.slice(1)); break;
|
|
1183
1297
|
case 'config': await config(argv.slice(1)); break;
|
|
1298
|
+
case 'simulation': await simulation(argv.slice(1)); break;
|
|
1184
1299
|
case 'help':
|
|
1185
1300
|
case '--help':
|
|
1186
1301
|
case '-h':
|
|
@@ -18,12 +18,24 @@
|
|
|
18
18
|
"type": "text",
|
|
19
19
|
"placeholder": "Leave empty for local SQLite",
|
|
20
20
|
"optional": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "mode",
|
|
24
|
+
"label": "Mode (optional)",
|
|
25
|
+
"type": "text",
|
|
26
|
+
"placeholder": "simulation (default) — live needs an adapter for your CRM",
|
|
27
|
+
"optional": true
|
|
21
28
|
}
|
|
22
29
|
],
|
|
23
30
|
"envMapping": {
|
|
24
|
-
"DATABASE_URL": "databaseUrl"
|
|
31
|
+
"DATABASE_URL": "databaseUrl",
|
|
32
|
+
"CRM_MODE": "mode"
|
|
25
33
|
}
|
|
26
34
|
},
|
|
35
|
+
"simulation": {
|
|
36
|
+
"field": "mode",
|
|
37
|
+
"default": "simulation"
|
|
38
|
+
},
|
|
27
39
|
"personalDefault": true,
|
|
28
40
|
"oauth": null,
|
|
29
41
|
"toolGating": {
|
|
@@ -118,6 +130,15 @@
|
|
|
118
130
|
"category": "read",
|
|
119
131
|
"boundField": "export_access",
|
|
120
132
|
"requiredValue": "allowed"
|
|
133
|
+
},
|
|
134
|
+
"load_simulation": {
|
|
135
|
+
"executionMapping": {},
|
|
136
|
+
"staticExecution": {
|
|
137
|
+
"contact_type": "customer",
|
|
138
|
+
"action_type": "setup"
|
|
139
|
+
},
|
|
140
|
+
"actionLabel": "Test data loaded",
|
|
141
|
+
"hideUnlessAuthorized": true
|
|
121
142
|
}
|
|
122
143
|
}
|
|
123
144
|
},
|
|
@@ -130,7 +151,8 @@
|
|
|
130
151
|
"bounds": {
|
|
131
152
|
"read_access": "unlimited",
|
|
132
153
|
"write_daily_max": "50",
|
|
133
|
-
"delete_daily_max": "0"
|
|
154
|
+
"delete_daily_max": "0",
|
|
155
|
+
"setup_daily_max": "0"
|
|
134
156
|
},
|
|
135
157
|
"context": {
|
|
136
158
|
"contact_type": "customer,lead,partner,vendor"
|
|
@@ -150,7 +172,8 @@
|
|
|
150
172
|
"bounds": {
|
|
151
173
|
"read_access": "unlimited",
|
|
152
174
|
"write_daily_max": "50",
|
|
153
|
-
"delete_daily_max": "10"
|
|
175
|
+
"delete_daily_max": "10",
|
|
176
|
+
"setup_daily_max": "0"
|
|
154
177
|
},
|
|
155
178
|
"context": {
|
|
156
179
|
"contact_type": "customer,lead,partner,vendor"
|
|
@@ -162,6 +185,28 @@
|
|
|
162
185
|
"10 deletes/day",
|
|
163
186
|
"review mode"
|
|
164
187
|
]
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"name": "Load test data (with approval)",
|
|
191
|
+
"description": "Load a simulation package (customers and optional contacts) into the simulated CRM, for setting up a three-week test. No browsing, writing, or deleting — this template grants nothing but the one-time test-data load, and it still requires your approval.",
|
|
192
|
+
"risk": "high",
|
|
193
|
+
"mode": "review",
|
|
194
|
+
"bounds": {
|
|
195
|
+
"read_access": "none",
|
|
196
|
+
"write_daily_max": "0",
|
|
197
|
+
"delete_daily_max": "0",
|
|
198
|
+
"setup_daily_max": "1"
|
|
199
|
+
},
|
|
200
|
+
"context": {
|
|
201
|
+
"contact_type": "customer"
|
|
202
|
+
},
|
|
203
|
+
"intent": "Allow the agent to load one simulation package of test data into the simulated CRM, for my review before it happens. No other CRM action is granted by this mandate — set up test data separately from the mandate the agent uses for its actual work.",
|
|
204
|
+
"ttl": 2592000,
|
|
205
|
+
"tags": [
|
|
206
|
+
"setup only",
|
|
207
|
+
"review mode",
|
|
208
|
+
"1 load"
|
|
209
|
+
]
|
|
165
210
|
}
|
|
166
211
|
],
|
|
167
212
|
"setupHint": "No configuration needed — works out of the box.",
|
|
@@ -18,12 +18,24 @@
|
|
|
18
18
|
"type": "text",
|
|
19
19
|
"placeholder": "Leave empty for local SQLite",
|
|
20
20
|
"optional": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "mode",
|
|
24
|
+
"label": "Mode (optional)",
|
|
25
|
+
"type": "text",
|
|
26
|
+
"placeholder": "simulation (default) — live needs an adapter for your ERP",
|
|
27
|
+
"optional": true
|
|
21
28
|
}
|
|
22
29
|
],
|
|
23
30
|
"envMapping": {
|
|
24
|
-
"DATABASE_URL": "databaseUrl"
|
|
31
|
+
"DATABASE_URL": "databaseUrl",
|
|
32
|
+
"ERP_MODE": "mode"
|
|
25
33
|
}
|
|
26
34
|
},
|
|
35
|
+
"simulation": {
|
|
36
|
+
"field": "mode",
|
|
37
|
+
"default": "simulation"
|
|
38
|
+
},
|
|
27
39
|
"personalDefault": true,
|
|
28
40
|
"oauth": null,
|
|
29
41
|
"toolGating": {
|
|
@@ -115,6 +127,14 @@
|
|
|
115
127
|
"action_type": "order"
|
|
116
128
|
},
|
|
117
129
|
"actionLabel": "Quote converted to order"
|
|
130
|
+
},
|
|
131
|
+
"load_simulation": {
|
|
132
|
+
"executionMapping": {},
|
|
133
|
+
"staticExecution": {
|
|
134
|
+
"action_type": "setup"
|
|
135
|
+
},
|
|
136
|
+
"actionLabel": "Test data loaded",
|
|
137
|
+
"hideUnlessAuthorized": true
|
|
118
138
|
}
|
|
119
139
|
}
|
|
120
140
|
},
|
|
@@ -131,7 +151,8 @@
|
|
|
131
151
|
"order_value_daily_max": "0",
|
|
132
152
|
"quote_daily_max": "20",
|
|
133
153
|
"send_daily_max": "0",
|
|
134
|
-
"order_daily_max": "0"
|
|
154
|
+
"order_daily_max": "0",
|
|
155
|
+
"setup_daily_max": "0"
|
|
135
156
|
},
|
|
136
157
|
"context": {
|
|
137
158
|
"currency": "EUR"
|
|
@@ -156,7 +177,8 @@
|
|
|
156
177
|
"order_value_daily_max": "50000",
|
|
157
178
|
"quote_daily_max": "50",
|
|
158
179
|
"send_daily_max": "20",
|
|
159
|
-
"order_daily_max": "10"
|
|
180
|
+
"order_daily_max": "10",
|
|
181
|
+
"setup_daily_max": "0"
|
|
160
182
|
},
|
|
161
183
|
"context": {
|
|
162
184
|
"currency": "EUR"
|
|
@@ -168,6 +190,32 @@
|
|
|
168
190
|
"review mode",
|
|
169
191
|
"10 orders/day"
|
|
170
192
|
]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "Load test data (with approval)",
|
|
196
|
+
"description": "Load a simulation package (customers and products) into the simulated ERP, for setting up a three-week test. No quoting, sending, or ordering — this template grants nothing but the one-time test-data load, and it still requires your approval.",
|
|
197
|
+
"risk": "high",
|
|
198
|
+
"mode": "review",
|
|
199
|
+
"bounds": {
|
|
200
|
+
"read_access": "none",
|
|
201
|
+
"value_max": "0",
|
|
202
|
+
"discount_max": "0",
|
|
203
|
+
"order_value_daily_max": "0",
|
|
204
|
+
"quote_daily_max": "0",
|
|
205
|
+
"send_daily_max": "0",
|
|
206
|
+
"order_daily_max": "0",
|
|
207
|
+
"setup_daily_max": "1"
|
|
208
|
+
},
|
|
209
|
+
"context": {
|
|
210
|
+
"currency": "EUR"
|
|
211
|
+
},
|
|
212
|
+
"intent": "Allow the agent to load one simulation package of test data into the simulated ERP, for my review before it happens. No other ERP action is granted by this mandate — set up test data separately from the mandate the agent uses for its actual work.",
|
|
213
|
+
"ttl": 2592000,
|
|
214
|
+
"tags": [
|
|
215
|
+
"setup only",
|
|
216
|
+
"review mode",
|
|
217
|
+
"1 load"
|
|
218
|
+
]
|
|
171
219
|
}
|
|
172
220
|
],
|
|
173
221
|
"setupHint": "No configuration needed — works out of the box.",
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "mail",
|
|
3
|
+
"name": "Email (simulation)",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Simulated inbox for testing an agent's email replies — nothing leaves the laptop",
|
|
6
|
+
"icon": "mail",
|
|
7
|
+
"profile": "email",
|
|
8
|
+
"npmPackage": "@humanagencyp/email-mcp",
|
|
9
|
+
"mcp": {
|
|
10
|
+
"command": "email-mcp",
|
|
11
|
+
"args": []
|
|
12
|
+
},
|
|
13
|
+
"credentials": {
|
|
14
|
+
"fields": [
|
|
15
|
+
{
|
|
16
|
+
"key": "mode",
|
|
17
|
+
"label": "Mode (optional)",
|
|
18
|
+
"type": "text",
|
|
19
|
+
"placeholder": "simulation (default) — live needs a mailbox adapter",
|
|
20
|
+
"optional": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "databaseUrl",
|
|
24
|
+
"label": "Database URL (optional)",
|
|
25
|
+
"type": "text",
|
|
26
|
+
"placeholder": "Leave empty for local SQLite",
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"envMapping": {
|
|
31
|
+
"EMAIL_MODE": "mode",
|
|
32
|
+
"DATABASE_URL": "databaseUrl"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"simulation": {
|
|
36
|
+
"field": "mode",
|
|
37
|
+
"default": "simulation"
|
|
38
|
+
},
|
|
39
|
+
"personalDefault": false,
|
|
40
|
+
"oauth": null,
|
|
41
|
+
"toolGating": {
|
|
42
|
+
"default": {
|
|
43
|
+
"executionMapping": {},
|
|
44
|
+
"staticExecution": {}
|
|
45
|
+
},
|
|
46
|
+
"overrides": {
|
|
47
|
+
"list_messages": {
|
|
48
|
+
"category": "read",
|
|
49
|
+
"boundField": "read_access",
|
|
50
|
+
"requiredValue": "unlimited"
|
|
51
|
+
},
|
|
52
|
+
"get_message": {
|
|
53
|
+
"category": "read",
|
|
54
|
+
"boundField": "read_access",
|
|
55
|
+
"requiredValue": "unlimited"
|
|
56
|
+
},
|
|
57
|
+
"send_message": {
|
|
58
|
+
"executionMapping": {
|
|
59
|
+
"to": [
|
|
60
|
+
{
|
|
61
|
+
"field": "recipient_count",
|
|
62
|
+
"transform": "length"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"field": "allowed_recipients",
|
|
66
|
+
"transform": "join"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"field": "allowed_domains",
|
|
70
|
+
"transform": "join_domains"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"staticExecution": {
|
|
75
|
+
"action_type": "send"
|
|
76
|
+
},
|
|
77
|
+
"actionLabel": "Email sent"
|
|
78
|
+
},
|
|
79
|
+
"load_simulation": {
|
|
80
|
+
"executionMapping": {},
|
|
81
|
+
"staticExecution": {
|
|
82
|
+
"action_type": "setup"
|
|
83
|
+
},
|
|
84
|
+
"actionLabel": "Test data loaded",
|
|
85
|
+
"hideUnlessAuthorized": true
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"templates": [
|
|
90
|
+
{
|
|
91
|
+
"name": "Work the inbox",
|
|
92
|
+
"description": "Read the simulated inbox and send replies. Cannot load or replace test data.",
|
|
93
|
+
"risk": "low",
|
|
94
|
+
"mode": "automatic",
|
|
95
|
+
"bounds": {
|
|
96
|
+
"read_access": "unlimited",
|
|
97
|
+
"recipient_max": "1",
|
|
98
|
+
"send_daily_max": "50",
|
|
99
|
+
"setup_daily_max": "0"
|
|
100
|
+
},
|
|
101
|
+
"context": {
|
|
102
|
+
"allowed_recipients": "",
|
|
103
|
+
"allowed_domains": ""
|
|
104
|
+
},
|
|
105
|
+
"intent": "Allow the agent to read the simulated inbox and reply to messages on its own. It cannot load or replace the test data the inbox was seeded with.",
|
|
106
|
+
"ttl": 2592000,
|
|
107
|
+
"tags": [
|
|
108
|
+
"read + reply",
|
|
109
|
+
"automatic",
|
|
110
|
+
"no setup"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "Load test data (with approval)",
|
|
115
|
+
"description": "Load a simulation package into an empty inbox. No reading or sending — a separate, one-time setup mandate.",
|
|
116
|
+
"risk": "high",
|
|
117
|
+
"mode": "review",
|
|
118
|
+
"bounds": {
|
|
119
|
+
"read_access": "none",
|
|
120
|
+
"recipient_max": "0",
|
|
121
|
+
"send_daily_max": "0",
|
|
122
|
+
"setup_daily_max": "1"
|
|
123
|
+
},
|
|
124
|
+
"context": {
|
|
125
|
+
"allowed_recipients": "",
|
|
126
|
+
"allowed_domains": ""
|
|
127
|
+
},
|
|
128
|
+
"intent": "Allow the agent to load one simulation package into an empty inbox, with my review before it happens. No reading or sending under this mandate — grant it separately from the mandate the agent uses for its daily work.",
|
|
129
|
+
"ttl": 2592000,
|
|
130
|
+
"tags": [
|
|
131
|
+
"setup only",
|
|
132
|
+
"review mode",
|
|
133
|
+
"1 load/day"
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"setupHint": "No configuration needed — works out of the box, in simulation mode.",
|
|
138
|
+
"setupGuide": [
|
|
139
|
+
{
|
|
140
|
+
"title": "Ready to use",
|
|
141
|
+
"description": "This connector stores a simulated inbox locally on your machine. Nothing is sent anywhere until you load test data with a separate, reviewed setup mandate."
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"title": "Optional: use a shared database",
|
|
145
|
+
"description": "For team use, provide a PostgreSQL connection string in the Database URL field. Otherwise, leave it empty to use local SQLite storage."
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|