create-metamynd-agent 0.9.1 → 0.10.5
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 +32 -6
- package/index.mjs +201 -51
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -104,7 +104,11 @@ Answer a few prompts (API, owner email/password, agent name, scope, per-transact
|
|
|
104
104
|
|
|
105
105
|
```
|
|
106
106
|
my-agent/
|
|
107
|
-
├─ agent.metamynd.json # portable guard config — HOLDS THE AGENT SECRET KEY (gitignored)
|
|
107
|
+
├─ agent.metamynd.json # portable guard config — HOLDS THE AGENT SECRET KEY (gitignored). This
|
|
108
|
+
│ # is the freshly-minted key from THIS scaffold, not a re-download — the
|
|
109
|
+
│ # dashboard's own "Redownload config" for an EXISTING agent never re-issues
|
|
110
|
+
│ # the key into a downloaded file (it's excluded there by design). --byok /
|
|
111
|
+
│ # --daemon-socket instead keep the key off this CLI's process entirely.
|
|
108
112
|
├─ index.mjs # runnable example: signs + calls ./gateway; guardTool() here is a
|
|
109
113
|
│ # fast local pre-check, NOT the enforcement boundary
|
|
110
114
|
├─ package.json # depends on @metamynd/agentsafe-guard
|
|
@@ -220,10 +224,12 @@ METAMYND_PASSWORD='…' npx create-metamynd-agent --yes …
|
|
|
220
224
|
| Flag | Env | Default |
|
|
221
225
|
|---|---|---|
|
|
222
226
|
| `--harness` | — | off (no login/KYB/network at all; free local governance — see above) |
|
|
227
|
+
| `--gateway` | — | off — `--harness` only; ALSO scaffold a second local process (still zero network, zero account) that independently re-verifies every request via the real `@metamynd/agentsafe-mcp-guard`. Does not close nonce replay/cumulative spend — see the generated `harness-gateway/README.md#--gateway`. |
|
|
223
228
|
| `--sandbox` | — | off (skips login/KYB; shared sandbox agent, still hosted) |
|
|
224
229
|
| `--config <file>` | — | a JSON policy file — see [Policy config file](#policy-config-file---config) |
|
|
225
230
|
| `--no-gateway` | — | off — hosted flow only; skips the default separate tool gateway (see above) |
|
|
226
|
-
| `--gateway-port <n>` | — | `4401` — hosted flow
|
|
231
|
+
| `--gateway-port <n>` | — | `4401` — hosted flow or `--harness --gateway`, the gateway process's port |
|
|
232
|
+
| `--force`, `-f` | — | off — scaffold into a non-empty directory, overwriting existing files |
|
|
227
233
|
| `--port <n>` | — | `4400` — `--harness` only, the local dashboard's port |
|
|
228
234
|
| `--api <url>` | `METAMYND_API` | `https://metamynd.ai/api/v1` |
|
|
229
235
|
| `--email <email>` | `METAMYND_EMAIL` | — (required) |
|
|
@@ -236,6 +242,8 @@ METAMYND_PASSWORD='…' npx create-metamynd-agent --yes …
|
|
|
236
242
|
| `--merchants <a,b>` | — | any |
|
|
237
243
|
| `--byok` | — | generate the keypair locally, provision + prove control |
|
|
238
244
|
| `--public-key <hex>` | — | BYOK with a key you already hold (you prove control yourself) |
|
|
245
|
+
| `--daemon-socket <p>` | — | `--byok` via an already-running agentsafe-signer daemon instead of locally (needs `--daemon-admin-socket` too) |
|
|
246
|
+
| `--daemon-admin-socket <p>` | — | that daemon's admin socket, for `generate-key` |
|
|
239
247
|
| `--out <dir>` | — | `./<agent-slug>` |
|
|
240
248
|
| `--yes`, `-y` | — | non-interactive |
|
|
241
249
|
|
|
@@ -284,10 +292,28 @@ npx create-metamynd-agent --byok --email you@example.com --name "Support Bot"
|
|
|
284
292
|
```
|
|
285
293
|
|
|
286
294
|
Generates an Ed25519 keypair **on your machine**, provisions the agent with only the public key, then
|
|
287
|
-
proves control (signs the one-time challenge → `verify-key`). MetaMynd never sees the private key.
|
|
288
|
-
generated private key is written into `agent.metamynd.json` (gitignored)
|
|
289
|
-
|
|
290
|
-
prints the challenge
|
|
295
|
+
proves control (signs the one-time challenge → `verify-key`). MetaMynd never sees the private key. By
|
|
296
|
+
default the generated private key is written into `agent.metamynd.json` (gitignored) — the same
|
|
297
|
+
process this CLI runs in holds it, at least briefly. Pass `--public-key <hex>` instead to register a
|
|
298
|
+
key you already hold elsewhere — then you complete `verify-key` yourself (the CLI prints the challenge
|
|
299
|
+
+ endpoint).
|
|
300
|
+
|
|
301
|
+
### Keeping the key out of this process entirely (`--daemon-socket`)
|
|
302
|
+
|
|
303
|
+
If you already have an [agentsafe-signer](../agentsafe-signer/README.md) daemon running for this
|
|
304
|
+
agent (`agentsafe-signer start --admin`, per its own install guide), point `--byok` at it instead:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
npx create-metamynd-agent --byok --daemon-socket ./.agentsafe-signer/signer.sock \
|
|
308
|
+
--daemon-admin-socket ./.agentsafe-signer/signer-admin.sock \
|
|
309
|
+
--email you@example.com --name "Support Bot"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The daemon generates the key and signs the `verify-key` challenge itself — the private key never
|
|
313
|
+
enters this CLI's process at all, not even briefly. `agent.metamynd.json` gets `keyProvider: 'daemon'`
|
|
314
|
+
+ `daemonSocketPath` instead of a plaintext key (see `@metamynd/agentsafe-guard`'s `key-providers.mjs`
|
|
315
|
+
for how the scaffolded guard resolves that). Requires both flags together, and only applies when no
|
|
316
|
+
`--public-key` is given (an external key has nothing for the daemon to generate).
|
|
291
317
|
|
|
292
318
|
## Delegated issuance (`--request` / `--claim`)
|
|
293
319
|
|
package/index.mjs
CHANGED
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
// npx create-metamynd-agent
|
|
15
15
|
// npx create-metamynd-agent --api http://localhost:9926/api/v1 --email you@x.com \
|
|
16
16
|
// --name "Support Bot" --scope flight-purchase --per-txn-max 500 --out ./support-bot --yes
|
|
17
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
|
|
17
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, chmodSync } from 'node:fs';
|
|
18
18
|
import { join, resolve } from 'node:path';
|
|
19
19
|
import readline from 'node:readline';
|
|
20
20
|
import crypto from 'node:crypto';
|
|
21
|
+
import net from 'node:net';
|
|
21
22
|
|
|
22
23
|
const GUARD_PKG = '@metamynd/agentsafe-guard';
|
|
23
24
|
// Must track the guard's MINOR line, not just its major. On a 0.x package `^0.4.0` means
|
|
@@ -32,7 +33,21 @@ const GUARD_PKG = '@metamynd/agentsafe-guard';
|
|
|
32
33
|
// 0.8.0 adds an optional `currency` scope to the amount-over/cumulative-over atoms
|
|
33
34
|
// (harnessDefaultSop, below, now sets it) — a guard below this version can't evaluate that
|
|
34
35
|
// field, so a scaffolded currency-scoped cap would silently never fire on a currency mismatch.
|
|
35
|
-
|
|
36
|
+
// 0.9.0 makes buildSignedRequest() async (the keyProvider seam, docs/design/
|
|
37
|
+
// agent-key-custody-local-signer-daemon-plan.md) — this scaffold's own bookFlightViaGateway/
|
|
38
|
+
// callGateway templates now `await` it, so a guard below this version would hand back a
|
|
39
|
+
// Promise object where a signed request is expected instead of failing loudly.
|
|
40
|
+
// 0.10.0 adds `resource` as a genuinely signed field (mirrors the mandate's own
|
|
41
|
+
// ResourceService.scopeConstraint()) — no scaffolded template passes it yet, but the floor
|
|
42
|
+
// must still cover the real current version regardless, per this repo's standing
|
|
43
|
+
// internal-pin invariant.
|
|
44
|
+
// 0.11.0 adds `signLocalDecision` support to `createDaemonKeyProvider` — this scaffold's
|
|
45
|
+
// `--byok --daemon-socket` path can now get local-decision audit reporting too, but no
|
|
46
|
+
// template code changes yet; the floor must still cover the real current version.
|
|
47
|
+
// 0.12.0 adds passphrase-encrypted managed key delivery (createGuardFromConfig's
|
|
48
|
+
// `{ passphrase }`) — no scaffolded template passes one yet, but the floor must still cover
|
|
49
|
+
// the real current version regardless, per this repo's standing internal-pin invariant.
|
|
50
|
+
const GUARD_VERSION = '^0.12.0';
|
|
36
51
|
// The default hosted scaffold's SECOND process — the tool gateway (see scaffoldProject).
|
|
37
52
|
const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
|
|
38
53
|
// 0.2.0 adds requireAuthorization (closes replay + cumulative spend) — this scaffold sets that
|
|
@@ -40,7 +55,14 @@ const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
|
|
|
40
55
|
// 0.3.0 adds the same amount-unknown atom as the guard, above — same reasoning, same miss.
|
|
41
56
|
// 0.4.0 adds the same amount-over/cumulative-over `currency` scope as the guard, above —
|
|
42
57
|
// same reasoning, same miss.
|
|
43
|
-
|
|
58
|
+
// 0.5.0 adds the keyProvider seam alongside the guard's own 0.9.0 (same design doc) — this
|
|
59
|
+
// scaffold's createMcpGuard() calls never use a handshake here, so no template code changes,
|
|
60
|
+
// but the floor must still cover the real current version regardless, per this repo's
|
|
61
|
+
// standing internal-pin invariant.
|
|
62
|
+
// 0.6.0 brings buildAuthMessage's `resource` field and buildLocalDecisionMessage into this
|
|
63
|
+
// package's own bundled policy-core.mjs (alongside the guard's own 0.10.0) — no scaffolded
|
|
64
|
+
// template code changes, but the floor must still cover the real current version.
|
|
65
|
+
const MCP_GUARD_VERSION = '^0.6.0';
|
|
44
66
|
const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
|
|
45
67
|
// 0.2.0 fixes a confused-deputy gap (payload not bound to the signed request) — the CLI must
|
|
46
68
|
// never scaffold a range that could resolve below it.
|
|
@@ -49,7 +71,10 @@ const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
|
|
|
49
71
|
// is the actual fix: requires amount/merchant specifically, whenever the signature names a real
|
|
50
72
|
// value for them. Re-tested live and closed same day; ^0.3.0 here would still resolve to the
|
|
51
73
|
// broken version.
|
|
52
|
-
|
|
74
|
+
// 0.5.0 adds the OPTIONAL Credential Vault `resolveCredential` hook on createHttpGateway (Module
|
|
75
|
+
// G) — additive and backward-compatible (every existing consumer sees zero behavior change), but
|
|
76
|
+
// the floor must still cover the real current version per this repo's own package-version check.
|
|
77
|
+
const GATEWAY_VERSION = '^0.5.0';
|
|
53
78
|
const DEFAULT_API = 'https://metamynd.ai/api/v1';
|
|
54
79
|
const DEFAULT_GATEWAY_PORT = 4401; // distinct from --harness's dashboard (4400)
|
|
55
80
|
|
|
@@ -118,6 +143,13 @@ ${c.b('Options')}
|
|
|
118
143
|
--byok Bring-your-own-key: generate the keypair locally, provision + prove control
|
|
119
144
|
(MetaMynd never sees the private key). Overridden by --public-key.
|
|
120
145
|
--public-key <hex> BYOK with a key you already hold (SPKI/raw hex); you prove control yourself
|
|
146
|
+
--daemon-socket <p> --byok only: use an already-running agentsafe-signer daemon (started
|
|
147
|
+
separately, e.g. \`agentsafe-signer start --admin\`) to generate the key and
|
|
148
|
+
prove control instead — the private key never enters this CLI's process at
|
|
149
|
+
all, and agent.metamynd.json gets keyProvider:'daemon' instead of a
|
|
150
|
+
plaintext key. Requires --daemon-admin-socket too. See README#byok.
|
|
151
|
+
--daemon-admin-socket <p> The same daemon's admin socket (for generate-key) — required with
|
|
152
|
+
--daemon-socket.
|
|
121
153
|
--out <dir> Output project directory (default ./<agent-slug>)
|
|
122
154
|
--no-gateway Hosted flow only: skip the separate tool-gateway process (see
|
|
123
155
|
README#separate-tool-gateway-default) and scaffold the old
|
|
@@ -310,6 +342,72 @@ function signChallengeHex(privateKeyHex, challenge) {
|
|
|
310
342
|
return crypto.sign(null, Buffer.from(challenge, 'utf8'), key).toString('hex');
|
|
311
343
|
}
|
|
312
344
|
|
|
345
|
+
// ---------- BYOK via an already-running agentsafe-signer daemon (--daemon-socket) ----------
|
|
346
|
+
// Opt-in alternative to generateAgentKeypair() above: instead of generating the keypair in THIS
|
|
347
|
+
// process and writing it in plaintext into agent.metamynd.json, ask an already-running
|
|
348
|
+
// agentsafe-signer daemon (docs/design/agent-key-custody-local-signer-daemon-plan.md — started
|
|
349
|
+
// separately, e.g. `agentsafe-signer start --admin`) to generate the key and sign the
|
|
350
|
+
// proof-of-possession challenge. The private key never enters this process at all, and the
|
|
351
|
+
// scaffolded config gets `keyProvider: 'daemon'` instead of a plaintext `agentKey` — see
|
|
352
|
+
// agentsafe-guard/key-providers.mjs's resolveKeyProvider(), which reads that field exactly.
|
|
353
|
+
//
|
|
354
|
+
// Vendored rather than depending on @metamynd/agentsafe-signer or @metamynd/agentsafe-guard for
|
|
355
|
+
// it — this CLI is intentionally zero-dependency, and this is the SAME small, self-contained
|
|
356
|
+
// reimplementation of the daemon's local JSON-over-socket protocol that agentsafe-guard/
|
|
357
|
+
// key-providers.mjs and agentsafe-mcp-guard/key-providers.mjs already each carry their own copy
|
|
358
|
+
// of, rather than a fourth package depending on a signer package built for a persistent service,
|
|
359
|
+
// not a one-shot scaffolding command.
|
|
360
|
+
function toPlatformSocketPath(logicalPath) {
|
|
361
|
+
if (process.platform !== 'win32') return logicalPath;
|
|
362
|
+
const name = crypto.createHash('sha256').update(resolve(logicalPath)).digest('hex').slice(0, 32);
|
|
363
|
+
return `\\\\.\\pipe\\agentsafe-signer-${name}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function daemonRequest(socketPath, op, params, { connectTimeoutMs = 5000 } = {}) {
|
|
367
|
+
return new Promise((resolve_, reject) => {
|
|
368
|
+
const deadline = Date.now() + connectTimeoutMs;
|
|
369
|
+
let settled = false;
|
|
370
|
+
const overallTimer = setTimeout(() => {
|
|
371
|
+
settled = true;
|
|
372
|
+
reject(Object.assign(new Error(`agentsafe-signer daemon unreachable at ${socketPath}: timed out after ${connectTimeoutMs}ms`), { code: 'DAEMON_UNREACHABLE' }));
|
|
373
|
+
}, connectTimeoutMs);
|
|
374
|
+
function attempt() {
|
|
375
|
+
if (settled) return;
|
|
376
|
+
const sock = net.connect(toPlatformSocketPath(socketPath));
|
|
377
|
+
const requestId = crypto.randomUUID();
|
|
378
|
+
let buf = '';
|
|
379
|
+
const cleanup = () => sock.destroy();
|
|
380
|
+
sock.once('error', (err) => {
|
|
381
|
+
cleanup();
|
|
382
|
+
if (settled) return;
|
|
383
|
+
if (err.code === 'ENOENT' && Date.now() < deadline) { setTimeout(attempt, 20); return; }
|
|
384
|
+
settled = true;
|
|
385
|
+
clearTimeout(overallTimer);
|
|
386
|
+
reject(Object.assign(new Error(`agentsafe-signer daemon unreachable at ${socketPath}: ${err.message}`), { code: 'DAEMON_UNREACHABLE' }));
|
|
387
|
+
});
|
|
388
|
+
sock.once('connect', () => {
|
|
389
|
+
if (settled) return;
|
|
390
|
+
sock.write(JSON.stringify({ protocolVersion: 1, requestId, op, params }) + '\n');
|
|
391
|
+
});
|
|
392
|
+
sock.on('data', (chunk) => {
|
|
393
|
+
if (settled) return;
|
|
394
|
+
buf += chunk.toString('utf8');
|
|
395
|
+
const idx = buf.indexOf('\n');
|
|
396
|
+
if (idx === -1) return;
|
|
397
|
+
let res;
|
|
398
|
+
try { res = JSON.parse(buf.slice(0, idx)); }
|
|
399
|
+
catch (err) { cleanup(); settled = true; clearTimeout(overallTimer); reject(err); return; }
|
|
400
|
+
cleanup();
|
|
401
|
+
settled = true;
|
|
402
|
+
clearTimeout(overallTimer);
|
|
403
|
+
if (res.ok) resolve_(res.result);
|
|
404
|
+
else reject(Object.assign(new Error(res.error?.message || res.error?.code || 'daemon rejected request'), { code: res.error?.code }));
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
attempt();
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
313
411
|
// ---------- API ----------
|
|
314
412
|
async function apiPost(base, path, body, token) {
|
|
315
413
|
let res;
|
|
@@ -343,7 +441,7 @@ async function apiPost(base, path, body, token) {
|
|
|
343
441
|
* it — the same shape of gap --harness's README documents. See exampleIndex() below, which is
|
|
344
442
|
* what the real (non-sandbox) flow scaffolds by default instead.
|
|
345
443
|
*/
|
|
346
|
-
function exampleIndexNoGateway(scope, perTxnMax) {
|
|
444
|
+
function exampleIndexNoGateway(scope, perTxnMax, currency, merchant) {
|
|
347
445
|
const under = Math.max(1, Math.round(perTxnMax * 0.5));
|
|
348
446
|
const over = Math.round(perTxnMax + 100);
|
|
349
447
|
return `// index.mjs — your agent, governed by MetaMynd/AgentSafe.
|
|
@@ -369,7 +467,7 @@ const gatedBookFlight = guard.guardTool(
|
|
|
369
467
|
bookFlight,
|
|
370
468
|
(a) => ({ // map tool args → gate inputs
|
|
371
469
|
amount: a.amount,
|
|
372
|
-
currency: '
|
|
470
|
+
currency: '${currency}',
|
|
373
471
|
merchant: a.merchant,
|
|
374
472
|
context: { tool: 'book-flight', riskLevel: a.riskLevel ?? 'low' },
|
|
375
473
|
}),
|
|
@@ -386,7 +484,7 @@ const gatedRaiseOwnLimit = guard.guardTool(
|
|
|
386
484
|
raiseOwnLimit,
|
|
387
485
|
(a) => ({
|
|
388
486
|
amount: a.amount,
|
|
389
|
-
currency: '
|
|
487
|
+
currency: '${currency}',
|
|
390
488
|
merchant: a.merchant,
|
|
391
489
|
context: { tool: 'permissions-update' },
|
|
392
490
|
}),
|
|
@@ -399,7 +497,7 @@ const rule = (n) => ' ' + '-'.repeat(n);
|
|
|
399
497
|
// Plain-English meaning for the reason codes this demo can produce.
|
|
400
498
|
const WHY = {
|
|
401
499
|
AUTHORIZED: 'inside the mandate and under the SOP spend cap',
|
|
402
|
-
SOP_SPEND_CAP: 'your SOP caps a single transaction at
|
|
500
|
+
SOP_SPEND_CAP: 'your SOP caps a single transaction at ${currency} ${perTxnMax}',
|
|
403
501
|
RISK_REVIEW: 'your SOP sends high-risk actions to a human first',
|
|
404
502
|
MERCHANT_NOT_ALLOWED: 'the mandate lists which merchants this agent may pay',
|
|
405
503
|
// Both say the same thing from where you are standing: the mandate does not cover that
|
|
@@ -430,7 +528,7 @@ console.log(dim(' 4. each attempt is signed here, then decided by MetaMynd rem
|
|
|
430
528
|
console.log(dim(' 5. your tool runs ONLY if that decision is ALLOW'));
|
|
431
529
|
console.log('');
|
|
432
530
|
console.log(dim(' scope ${scope}'));
|
|
433
|
-
console.log(dim(' cap
|
|
531
|
+
console.log(dim(' cap ${currency} ${perTxnMax} per transaction, set by your SOP'));
|
|
434
532
|
|
|
435
533
|
// ---------------------------------------------------------------- 3. THE STEPS
|
|
436
534
|
async function attempt(n, intent, args, tool = gatedBookFlight) {
|
|
@@ -458,13 +556,13 @@ async function attempt(n, intent, args, tool = gatedBookFlight) {
|
|
|
458
556
|
|
|
459
557
|
console.log('');
|
|
460
558
|
console.log(rule(66));
|
|
461
|
-
await attempt(1, 'a
|
|
462
|
-
await attempt(2, 'a
|
|
463
|
-
await attempt(3, 'a
|
|
559
|
+
await attempt(1, 'a ${currency} ${under} booking, low risk. Expected to pass.', { amount: ${under}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'low' });
|
|
560
|
+
await attempt(2, 'a ${currency} ${over} booking, deliberately over the cap.', { amount: ${over}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'low' });
|
|
561
|
+
await attempt(3, 'a ${currency} ${under} booking, but flagged high risk.', { amount: ${under}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'high' });
|
|
464
562
|
await attempt(
|
|
465
563
|
4,
|
|
466
564
|
'the agent stops booking flights and asks to raise its OWN limit.',
|
|
467
|
-
{ amount: 100000, merchant: '
|
|
565
|
+
{ amount: 100000, currency: '${currency}', merchant: '${merchant}' },
|
|
468
566
|
gatedRaiseOwnLimit,
|
|
469
567
|
);
|
|
470
568
|
console.log('');
|
|
@@ -500,7 +598,7 @@ console.log('');
|
|
|
500
598
|
* directly: it only exists in ./gateway, which independently re-verifies every request against
|
|
501
599
|
* this agent's own policy bundle before it runs, and holds any real credentials the tool needs.
|
|
502
600
|
*/
|
|
503
|
-
function exampleIndex(scope, perTxnMax, gatewayPort) {
|
|
601
|
+
function exampleIndex(scope, perTxnMax, gatewayPort, currency, merchant) {
|
|
504
602
|
const under = Math.max(1, Math.round(perTxnMax * 0.5));
|
|
505
603
|
const over = Math.round(perTxnMax + 100);
|
|
506
604
|
return `// index.mjs — your agent, governed by MetaMynd/AgentSafe.
|
|
@@ -522,10 +620,10 @@ const GATEWAY = process.env.GATEWAY_URL || 'http://localhost:${gatewayPort}';
|
|
|
522
620
|
// --- the gateway atomically claim single-use execution, closing replay + cumulative spend, not
|
|
523
621
|
// --- just re-checking policy. See ./gateway/README.md.
|
|
524
622
|
async function bookFlightViaGateway(args, decision) {
|
|
525
|
-
const signed = guard.buildSignedRequest({
|
|
623
|
+
const signed = await guard.buildSignedRequest({
|
|
526
624
|
action: '${scope}',
|
|
527
625
|
amount: args.amount,
|
|
528
|
-
currency: '
|
|
626
|
+
currency: args.currency ?? '${currency}',
|
|
529
627
|
merchant: args.merchant,
|
|
530
628
|
context: { tool: 'book-flight', riskLevel: args.riskLevel ?? 'low' },
|
|
531
629
|
});
|
|
@@ -552,7 +650,7 @@ const gatedBookFlight = guard.guardTool(
|
|
|
552
650
|
bookFlightViaGateway,
|
|
553
651
|
(a) => ({ // map tool args → gate inputs
|
|
554
652
|
amount: a.amount,
|
|
555
|
-
currency: '
|
|
653
|
+
currency: a.currency ?? '${currency}',
|
|
556
654
|
merchant: a.merchant,
|
|
557
655
|
context: { tool: 'book-flight', riskLevel: a.riskLevel ?? 'low' },
|
|
558
656
|
}),
|
|
@@ -569,7 +667,7 @@ const gatedRaiseOwnLimit = guard.guardTool(
|
|
|
569
667
|
raiseOwnLimit,
|
|
570
668
|
(a) => ({
|
|
571
669
|
amount: a.amount,
|
|
572
|
-
currency: '
|
|
670
|
+
currency: a.currency ?? '${currency}',
|
|
573
671
|
merchant: a.merchant,
|
|
574
672
|
context: { tool: 'permissions-update' },
|
|
575
673
|
}),
|
|
@@ -616,7 +714,7 @@ console.log(dim(' 5. the gateway independently re-verifies before your tool ru
|
|
|
616
714
|
console.log(dim(' 6. there is no local bookFlight() to call directly - only the gateway has it'));
|
|
617
715
|
console.log('');
|
|
618
716
|
console.log(dim(' scope ${scope}'));
|
|
619
|
-
console.log(dim(' cap
|
|
717
|
+
console.log(dim(' cap ${currency} ${perTxnMax} per transaction, set by your SOP'));
|
|
620
718
|
console.log(dim(' gateway ' + GATEWAY + ' (run it in a separate terminal - see ./gateway)'));
|
|
621
719
|
|
|
622
720
|
// ---------------------------------------------------------------- 3. THE STEPS
|
|
@@ -645,13 +743,13 @@ async function attempt(n, intent, args, tool = gatedBookFlight) {
|
|
|
645
743
|
|
|
646
744
|
console.log('');
|
|
647
745
|
console.log(rule(66));
|
|
648
|
-
await attempt(1, 'a
|
|
649
|
-
await attempt(2, 'a
|
|
650
|
-
await attempt(3, 'a
|
|
746
|
+
await attempt(1, 'a ${currency} ${under} booking, low risk. Expected to pass.', { amount: ${under}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'low' });
|
|
747
|
+
await attempt(2, 'a ${currency} ${over} booking, deliberately over the cap.', { amount: ${over}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'low' });
|
|
748
|
+
await attempt(3, 'a ${currency} ${under} booking, but flagged high risk.', { amount: ${under}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'high' });
|
|
651
749
|
await attempt(
|
|
652
750
|
4,
|
|
653
751
|
'the agent stops booking flights and asks to raise its OWN limit.',
|
|
654
|
-
{ amount: 100000, merchant: '
|
|
752
|
+
{ amount: 100000, currency: '${currency}', merchant: '${merchant}' },
|
|
655
753
|
gatedRaiseOwnLimit,
|
|
656
754
|
);
|
|
657
755
|
console.log('');
|
|
@@ -698,7 +796,13 @@ function examplePackageJson(slug) {
|
|
|
698
796
|
) + '\n';
|
|
699
797
|
}
|
|
700
798
|
|
|
701
|
-
function exampleReadme(slug, scope, withGateway, gatewayPort) {
|
|
799
|
+
function exampleReadme(slug, scope, withGateway, gatewayPort, daemonKey = false) {
|
|
800
|
+
const configFileLine = daemonKey
|
|
801
|
+
? `- \`agent.metamynd.json\` — your portable guard config (identity, mandate scope \`${scope}\`, issuer keys).
|
|
802
|
+
**Holds no secret key.** Signing goes through your already-running agentsafe-signer daemon
|
|
803
|
+
(\`daemonSocketPath\`) instead — see \`docs/integration/INSTALL-AGENTSAFE-SIGNER.md\`.`
|
|
804
|
+
: `- \`agent.metamynd.json\` — your portable guard config (identity, mandate scope \`${scope}\`, issuer keys).
|
|
805
|
+
**Contains the agent's secret key — never commit it.** It is already in \`.gitignore\`.`;
|
|
702
806
|
const gatewaySection = withGateway
|
|
703
807
|
? `## Run
|
|
704
808
|
|
|
@@ -721,8 +825,7 @@ an ESCALATE (high risk). The BLOCK and ESCALATE never reach the gateway at all
|
|
|
721
825
|
|
|
722
826
|
## Files
|
|
723
827
|
|
|
724
|
-
|
|
725
|
-
**Contains the agent's secret key — never commit it.** It is already in \`.gitignore\`.
|
|
828
|
+
${configFileLine}
|
|
726
829
|
- \`index.mjs\` — signs each request and calls \`./gateway\` for it; \`guard.guardTool()\` here is a
|
|
727
830
|
fast local pre-check, not the enforcement boundary.
|
|
728
831
|
- \`gateway/\` — a **separate process**. It holds the real tool and independently re-verifies every
|
|
@@ -743,8 +846,7 @@ You should see an ALLOW, a BLOCK (over the per-transaction cap), and an ESCALATE
|
|
|
743
846
|
|
|
744
847
|
## Files
|
|
745
848
|
|
|
746
|
-
|
|
747
|
-
**Contains the agent's secret key — never commit it.** It is already in \`.gitignore\`.
|
|
849
|
+
${configFileLine}
|
|
748
850
|
- \`index.mjs\` — wraps a tool with \`guard.guardTool(...)\`; the tool only runs when the gate allows.
|
|
749
851
|
|
|
750
852
|
## What this is not
|
|
@@ -1009,11 +1111,16 @@ and \`demo/duffel-mcp-gateway\` in the AgentSafe repo for the fuller pattern thi
|
|
|
1009
1111
|
`;
|
|
1010
1112
|
}
|
|
1011
1113
|
|
|
1012
|
-
function writeFileSafe(dir, name, content, force = false) {
|
|
1114
|
+
function writeFileSafe(dir, name, content, force = false, mode) {
|
|
1013
1115
|
const p = join(dir, name);
|
|
1014
1116
|
const exists = existsSync(p);
|
|
1015
1117
|
if (exists && !force) { console.log(` ${c.yellow('skip')} ${name} ${c.dim('(exists)')}`); return; }
|
|
1016
|
-
|
|
1118
|
+
// `mode` (e.g. 0o600 for a private-key-bearing file) only narrows perms at CREATE time —
|
|
1119
|
+
// writeFileSync ignores its own `mode` option on an existing file, so an --force overwrite
|
|
1120
|
+
// needs an explicit chmod or a stale world-readable mode from the file's first creation
|
|
1121
|
+
// would otherwise survive untouched.
|
|
1122
|
+
writeFileSync(p, content, mode !== undefined ? { mode } : undefined);
|
|
1123
|
+
if (mode !== undefined) chmodSync(p, mode);
|
|
1017
1124
|
console.log(` ${exists ? c.yellow('overwrite') : c.green('create')} ${name}`);
|
|
1018
1125
|
}
|
|
1019
1126
|
|
|
@@ -1047,15 +1154,15 @@ function assertScaffoldTarget(outDir, force) {
|
|
|
1047
1154
|
* enforcement boundary. Off for --sandbox (shared demo identity, never real credentials
|
|
1048
1155
|
* anyway) and --no-gateway (opt out, e.g. you're already running your own separate gateway).
|
|
1049
1156
|
*/
|
|
1050
|
-
function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox, withGateway, gatewayPort = DEFAULT_GATEWAY_PORT, force = false }) {
|
|
1157
|
+
function scaffoldProject({ outDir, config, slug, scope, perTxnMax, currency = 'USD', merchant = 'skyward-air', sandbox, withGateway, gatewayPort = DEFAULT_GATEWAY_PORT, force = false }) {
|
|
1051
1158
|
assertScaffoldTarget(outDir, force);
|
|
1052
1159
|
console.log(`\n ${c.b('Scaffolding')} ${c.dim(outDir)}`);
|
|
1053
1160
|
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
1054
|
-
writeFileSafe(outDir, 'agent.metamynd.json', JSON.stringify(config, null, 2) + '\n', force);
|
|
1055
|
-
writeFileSafe(outDir, 'index.mjs', withGateway ? exampleIndex(scope, perTxnMax, gatewayPort) : exampleIndexNoGateway(scope, perTxnMax), force);
|
|
1161
|
+
writeFileSafe(outDir, 'agent.metamynd.json', JSON.stringify(config, null, 2) + '\n', force, 0o600);
|
|
1162
|
+
writeFileSafe(outDir, 'index.mjs', withGateway ? exampleIndex(scope, perTxnMax, gatewayPort, currency, merchant) : exampleIndexNoGateway(scope, perTxnMax, currency, merchant), force);
|
|
1056
1163
|
writeFileSafe(outDir, 'package.json', examplePackageJson(slug), force);
|
|
1057
1164
|
writeFileSafe(outDir, '.gitignore', gitignore(), force);
|
|
1058
|
-
writeFileSafe(outDir, 'README.md', exampleReadme(slug, scope, withGateway, gatewayPort), force);
|
|
1165
|
+
writeFileSafe(outDir, 'README.md', exampleReadme(slug, scope, withGateway, gatewayPort, config.keyProvider === 'daemon'), force);
|
|
1059
1166
|
|
|
1060
1167
|
if (withGateway) {
|
|
1061
1168
|
const apiBase = config.apiBase ?? config.api ?? DEFAULT_API;
|
|
@@ -1072,6 +1179,8 @@ function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox, with
|
|
|
1072
1179
|
console.log(`\n${c.green(c.b(' ✓ Done.'))} Your governed agent is ready.\n`);
|
|
1073
1180
|
if (sandbox) {
|
|
1074
1181
|
console.log(` ${c.dim('Shared sandbox agent — for trying MetaMynd only. Provision your own (drop --sandbox) for anything real.')}\n`);
|
|
1182
|
+
} else if (config.keyProvider === 'daemon') {
|
|
1183
|
+
console.log(` ${c.dim('agent.metamynd.json holds no secret key — signing goes through your agentsafe-signer daemon at')} ${c.b(config.daemonSocketPath)}${c.dim('.')}\n`);
|
|
1075
1184
|
} else if (config.agentKey) {
|
|
1076
1185
|
console.log(` ${c.yellow('⚠ agent.metamynd.json holds the agent secret key')} — it is gitignored; never commit it.\n`);
|
|
1077
1186
|
}
|
|
@@ -1691,7 +1800,7 @@ setInterval(refresh, 3000);
|
|
|
1691
1800
|
`;
|
|
1692
1801
|
}
|
|
1693
1802
|
|
|
1694
|
-
function harnessIndexFile(scope, perTxnMax, port, withGateway, gatewayPort) {
|
|
1803
|
+
function harnessIndexFile(scope, perTxnMax, port, withGateway, gatewayPort, currency, merchant) {
|
|
1695
1804
|
const under = Math.max(1, Math.round(perTxnMax * 0.5));
|
|
1696
1805
|
const over = Math.round(perTxnMax + 100);
|
|
1697
1806
|
return `// index.mjs — your agent, governed entirely on this machine. No account, no network call
|
|
@@ -1730,7 +1839,7 @@ const GATEWAY = process.env.HARNESS_GATEWAY_URL || 'http://localhost:${gatewayPo
|
|
|
1730
1839
|
// issuer): it builds and signs the same canonical message a real gate would verify, entirely
|
|
1731
1840
|
// offline, using this agent's own did:key — the gateway verifies that signature for itself.
|
|
1732
1841
|
async function callGateway(path, action, args) {
|
|
1733
|
-
const signed = guard.buildSignedRequest({ action, amount: args.amount, currency: '
|
|
1842
|
+
const signed = await guard.buildSignedRequest({ action, amount: args.amount, currency: args.currency ?? '${currency}', merchant: args.merchant, context: { tool: '${scope}', riskLevel: args.riskLevel ?? 'low' } });
|
|
1734
1843
|
const res = await fetch(GATEWAY + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ signed, args }) });
|
|
1735
1844
|
const body = await res.json().catch(() => null);
|
|
1736
1845
|
if (!res.ok) {
|
|
@@ -1753,6 +1862,7 @@ const gatedBookFlight = guard.guardToolLocal(
|
|
|
1753
1862
|
${withGateway ? `(args) => callGateway('/book-flight', '${scope}', args)` : 'bookFlight'},
|
|
1754
1863
|
(a) => ({ // map tool args → gate inputs
|
|
1755
1864
|
amount: a.amount,
|
|
1865
|
+
currency: a.currency ?? '${currency}',
|
|
1756
1866
|
merchant: a.merchant,
|
|
1757
1867
|
context: { tool: 'book-flight', riskLevel: a.riskLevel ?? 'low' },
|
|
1758
1868
|
}),
|
|
@@ -1768,7 +1878,7 @@ async function raiseOwnLimit(args) {
|
|
|
1768
1878
|
const gatedRaiseOwnLimit = guard.guardToolLocal(
|
|
1769
1879
|
'permissions.update', // an action NOT in the mandate
|
|
1770
1880
|
${withGateway ? `(args) => callGateway('/raise-limit', 'permissions.update', args)` : 'raiseOwnLimit'},
|
|
1771
|
-
(a) => ({ amount: a.amount, merchant: a.merchant, context: { tool: 'permissions-update' } }),
|
|
1881
|
+
(a) => ({ amount: a.amount, currency: a.currency ?? '${currency}', merchant: a.merchant, context: { tool: 'permissions-update' } }),
|
|
1772
1882
|
getBundle,
|
|
1773
1883
|
);
|
|
1774
1884
|
|
|
@@ -1778,7 +1888,7 @@ const rule = (n) => ' ' + '-'.repeat(n);
|
|
|
1778
1888
|
|
|
1779
1889
|
const WHY = {
|
|
1780
1890
|
AUTHORIZED: 'inside the mandate and under the SOP spend cap',
|
|
1781
|
-
SOP_SPEND_CAP: 'your SOP caps a single transaction at
|
|
1891
|
+
SOP_SPEND_CAP: 'your SOP caps a single transaction at ${currency} ${perTxnMax}',
|
|
1782
1892
|
RISK_REVIEW: 'your SOP sends high-risk actions to a human first',
|
|
1783
1893
|
MERCHANT_NOT_ALLOWED: 'the mandate lists which merchants this agent may pay',
|
|
1784
1894
|
NO_PERMISSION_FOR_ACTION: 'the mandate never granted this action - at any amount',
|
|
@@ -1824,14 +1934,14 @@ console.log(' for something never granted at all - the one a prompt could not h
|
|
|
1824
1934
|
console.log(' because the decision is not made inside your program, and not on a server either.');
|
|
1825
1935
|
console.log('');
|
|
1826
1936
|
console.log(dim(' scope ${scope}'));
|
|
1827
|
-
console.log(dim(' cap
|
|
1937
|
+
console.log(dim(' cap ${currency} ${perTxnMax} per transaction, from ./metamynd-rules.json'));
|
|
1828
1938
|
|
|
1829
1939
|
console.log('');
|
|
1830
1940
|
console.log(rule(66));
|
|
1831
|
-
await attempt(1, 'a
|
|
1832
|
-
await attempt(2, 'a
|
|
1833
|
-
await attempt(3, 'a
|
|
1834
|
-
await attempt(4, 'the agent stops booking flights and asks to raise its OWN limit.', 'permissions.update', { amount: 100000, merchant: '
|
|
1941
|
+
await attempt(1, 'a ${currency} ${under} booking, low risk. Expected to pass.', '${scope}', { amount: ${under}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'low' });
|
|
1942
|
+
await attempt(2, 'a ${currency} ${over} booking, deliberately over the cap.', '${scope}', { amount: ${over}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'low' });
|
|
1943
|
+
await attempt(3, 'a ${currency} ${under} booking, but flagged high risk.', '${scope}', { amount: ${under}, currency: '${currency}', merchant: '${merchant}', riskLevel: 'high' });
|
|
1944
|
+
await attempt(4, 'the agent stops booking flights and asks to raise its OWN limit.', 'permissions.update', { amount: 100000, currency: '${currency}', merchant: '${merchant}' }, gatedRaiseOwnLimit);
|
|
1835
1945
|
console.log('');
|
|
1836
1946
|
console.log(rule(66));
|
|
1837
1947
|
|
|
@@ -2008,10 +2118,10 @@ async function runHarness(args) {
|
|
|
2008
2118
|
|
|
2009
2119
|
console.log(`\n ${c.b('Scaffolding')} ${c.dim(outDir)}`);
|
|
2010
2120
|
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
2011
|
-
writeFileSafe(outDir, 'agent.metamynd.json', JSON.stringify({ agentDid, agentKey: privateKeyHex, mode: 'harness' }, null, 2) + '\n', !!args.force);
|
|
2121
|
+
writeFileSafe(outDir, 'agent.metamynd.json', JSON.stringify({ agentDid, agentKey: privateKeyHex, mode: 'harness' }, null, 2) + '\n', !!args.force, 0o600);
|
|
2012
2122
|
writeFileSafe(outDir, 'metamynd-rules.json', harnessRulesFile(mandate, sopDocument), !!args.force);
|
|
2013
2123
|
writeFileSafe(outDir, 'harness-server.mjs', harnessServerFile(), !!args.force);
|
|
2014
|
-
writeFileSafe(outDir, 'index.mjs', harnessIndexFile(scope, perTxnMax, port, withGateway, gatewayPort), !!args.force);
|
|
2124
|
+
writeFileSafe(outDir, 'index.mjs', harnessIndexFile(scope, perTxnMax, port, withGateway, gatewayPort, currency, merchants[0] || 'demo-merchant'), !!args.force);
|
|
2015
2125
|
writeFileSafe(outDir, 'package.json', harnessPackageJson(slug), !!args.force);
|
|
2016
2126
|
writeFileSafe(outDir, '.gitignore', gitignore(), !!args.force);
|
|
2017
2127
|
writeFileSafe(outDir, 'README.md', harnessReadme(slug, scope, port, withGateway, gatewayPort), !!args.force);
|
|
@@ -2103,7 +2213,9 @@ async function runRequest(args) {
|
|
|
2103
2213
|
const d = res.data;
|
|
2104
2214
|
const state = { api: base, requestId: d.requestId, claimToken: d.claimToken, byok: !!generated, privateKey: generated?.privateKeyHex ?? null, name, scope, perTxnMax };
|
|
2105
2215
|
const file = resolve(String(args.out || '.'), REQUEST_STATE_FILE);
|
|
2106
|
-
|
|
2216
|
+
// May carry a BYOK private key (state.privateKey) — same 0600 treatment as agent.metamynd.json.
|
|
2217
|
+
writeFileSync(file, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
|
|
2218
|
+
if (existsSync(file)) chmodSync(file, 0o600);
|
|
2107
2219
|
|
|
2108
2220
|
console.log(` ${c.green('✓')} request ${c.b(d.requestId)} submitted — awaiting ${owner}'s approval`);
|
|
2109
2221
|
console.log(` ${c.yellow('⚠ saved the one-time claim token to')} ${file.replace(resolve('.'), '.').replace(/\\/g, '/')} ${c.dim('(secret — do not commit)')}\n`);
|
|
@@ -2224,11 +2336,35 @@ async function main() {
|
|
|
2224
2336
|
);
|
|
2225
2337
|
const merchants = String(merchantsRaw).split(',').map((s) => s.trim()).filter(Boolean);
|
|
2226
2338
|
|
|
2227
|
-
// BYOK: --byok generates a keypair on THIS machine (MetaMynd never sees the private key)
|
|
2228
|
-
//
|
|
2339
|
+
// BYOK: --byok generates a keypair on THIS machine (MetaMynd never sees the private key) —
|
|
2340
|
+
// either locally in this process (default) or, opt-in, via an already-running agentsafe-signer
|
|
2341
|
+
// daemon (--daemon-socket + --daemon-admin-socket, see their own help text) so the private key
|
|
2342
|
+
// never enters this process at all. An explicit --public-key means the caller holds the key
|
|
2343
|
+
// elsewhere and will prove it themselves — daemon flags are meaningless with it.
|
|
2229
2344
|
let publicKey = typeof args['public-key'] === 'string' ? args['public-key'] : undefined;
|
|
2345
|
+
const daemonSocket = typeof args['daemon-socket'] === 'string' ? args['daemon-socket'] : undefined;
|
|
2346
|
+
const daemonAdminSocket = typeof args['daemon-admin-socket'] === 'string' ? args['daemon-admin-socket'] : undefined;
|
|
2347
|
+
if (Boolean(daemonSocket) !== Boolean(daemonAdminSocket)) {
|
|
2348
|
+
rl?.close();
|
|
2349
|
+
fail('--daemon-socket and --daemon-admin-socket must be used together.');
|
|
2350
|
+
}
|
|
2351
|
+
if (daemonSocket && !args.byok) {
|
|
2352
|
+
rl?.close();
|
|
2353
|
+
fail('--daemon-socket requires --byok.');
|
|
2354
|
+
}
|
|
2355
|
+
if (daemonSocket && publicKey) {
|
|
2356
|
+
rl?.close();
|
|
2357
|
+
fail('--daemon-socket generates its own key — pass --byok alone, not --public-key.');
|
|
2358
|
+
}
|
|
2230
2359
|
let generatedKey = null;
|
|
2231
|
-
|
|
2360
|
+
let daemonPublicKeyHex = null;
|
|
2361
|
+
if (args.byok && !publicKey && daemonSocket) {
|
|
2362
|
+
console.log(c.dim(' → asking the agentsafe-signer daemon to generate a key …'));
|
|
2363
|
+
const { publicKeyHex } = await daemonRequest(daemonAdminSocket, 'generate-key', { allowRekey: false });
|
|
2364
|
+
daemonPublicKeyHex = publicKeyHex;
|
|
2365
|
+
publicKey = publicKeyHex;
|
|
2366
|
+
console.log(` ${c.green('✓')} generated an Ed25519 keypair via the signer daemon ${c.dim('(the private key never left it)')}`);
|
|
2367
|
+
} else if (args.byok && !publicKey) {
|
|
2232
2368
|
generatedKey = generateAgentKeypair();
|
|
2233
2369
|
publicKey = generatedKey.publicKeyHex;
|
|
2234
2370
|
console.log(` ${c.green('✓')} generated an Ed25519 keypair locally ${c.dim('(private key stays on this machine)')}`);
|
|
@@ -2253,7 +2389,21 @@ async function main() {
|
|
|
2253
2389
|
if (config.standards?.length) console.log(` ${c.green('✓')} enforced Standards: ${config.standards.join(', ')}`);
|
|
2254
2390
|
|
|
2255
2391
|
// 3b. BYOK: prove control of the key (verify-key), else the gate blocks with AGENT_KEY_UNVERIFIED.
|
|
2256
|
-
if (
|
|
2392
|
+
if (daemonPublicKeyHex) {
|
|
2393
|
+
// The daemon holds the private key — it never entered this process. Point the scaffolded
|
|
2394
|
+
// guard at the daemon instead of embedding a plaintext key (agentsafe-guard/key-providers.mjs's
|
|
2395
|
+
// resolveKeyProvider() reads these two fields and never looks for `agentKey` when present).
|
|
2396
|
+
config.keyProvider = 'daemon';
|
|
2397
|
+
config.daemonSocketPath = daemonSocket;
|
|
2398
|
+
if (config.challenge) {
|
|
2399
|
+
console.log(c.dim(' → proving key control via the daemon (verify-key) …'));
|
|
2400
|
+
const { signature } = await daemonRequest(daemonSocket, 'sign-key-control-challenge', { challenge: config.challenge });
|
|
2401
|
+
await apiPost(base, `/agent-identity/${encodeURIComponent(config.identityId)}/verify-key`, { signature }, token);
|
|
2402
|
+
config.keyVerified = true;
|
|
2403
|
+
delete config.challenge; // one-time; consumed
|
|
2404
|
+
console.log(` ${c.green('✓')} key verified — MetaMynd never saw your private key, and neither did this CLI`);
|
|
2405
|
+
}
|
|
2406
|
+
} else if (generatedKey) {
|
|
2257
2407
|
// We hold the private key — inject it into the config so the scaffolded guard can sign, and
|
|
2258
2408
|
// prove possession by signing the issued challenge.
|
|
2259
2409
|
config.agentKey = generatedKey.privateKeyHex;
|
|
@@ -2274,7 +2424,7 @@ async function main() {
|
|
|
2274
2424
|
}
|
|
2275
2425
|
|
|
2276
2426
|
// 4. Scaffold + next steps
|
|
2277
|
-
scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox: false, withGateway: !args['no-gateway'], gatewayPort: Number(args['gateway-port']) || DEFAULT_GATEWAY_PORT, force: !!args.force });
|
|
2427
|
+
scaffoldProject({ outDir, config, slug, scope, perTxnMax, currency, merchant: merchants[0] || 'demo-merchant', sandbox: false, withGateway: !args['no-gateway'], gatewayPort: Number(args['gateway-port']) || DEFAULT_GATEWAY_PORT, force: !!args.force });
|
|
2278
2428
|
}
|
|
2279
2429
|
|
|
2280
2430
|
main().catch((e) => fail(e?.stack || e?.message || String(e)));
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-metamynd-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Scaffold a MetaMynd/AgentSafe-governed AI agent in one command — logs in, provisions the agent (identity + mandate + SOP + Standards) in a single call, writes agent.metamynd.json plus a runnable agent + separate tool-gateway process that closes direct-call, confused-deputy, replay, and cumulative-spend bypasses. --harness scaffolds a free, local, zero-network governance harness instead.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-metamynd-agent": "index.mjs"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "node harness-gateway.smoke.mjs"
|
|
10
|
+
"test": "node harness-gateway.smoke.mjs && node byok-daemon.smoke.mjs"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"index.mjs",
|