create-metamynd-agent 0.6.2 → 0.7.1

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 (3) hide show
  1. package/README.md +79 -15
  2. package/index.mjs +490 -19
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -2,8 +2,13 @@
2
2
 
3
3
  Scaffold a **MetaMynd/AgentSafe-governed** AI agent in one command. It logs you in, provisions the
4
4
  agent in a **single call** (identity + mandate + starter SOP + all enforced Standards), writes a
5
- portable `agent.metamynd.json`, and drops a runnable example that gates a tool through the
6
- [`@metamynd/agentsafe-guard`](https://www.npmjs.com/package/@metamynd/agentsafe-guard).
5
+ portable `agent.metamynd.json`, and drops a runnable agent that gates a tool through the
6
+ [`@metamynd/agentsafe-guard`](https://www.npmjs.com/package/@metamynd/agentsafe-guard) — **plus, by
7
+ default, a second `gateway/` process** built on
8
+ [`@metamynd/agentsafe-mcp-guard`](https://www.npmjs.com/package/@metamynd/agentsafe-mcp-guard) and
9
+ [`@metamynd/agentsafe-http-gateway`](https://www.npmjs.com/package/@metamynd/agentsafe-http-gateway).
10
+ The agent's own `guardTool()` call is a fast, local, client-side check; the gateway is the real
11
+ enforcement boundary — see [Separate tool gateway](#separate-tool-gateway-default) below.
7
12
 
8
13
  > **Prerequisite:** an agent is always owned by a **KYB-verified owner** — a person/org with a
9
14
  > MetaMynd account. If that's you and you're verified, you're ready. Verify once in the dashboard if
@@ -11,6 +16,10 @@ portable `agent.metamynd.json`, and drops a runnable example that gates a tool t
11
16
 
12
17
  ## Free local harness (no account, no network, `--harness`)
13
18
 
19
+ > **Without MetaMynd, you can be bypassed.** `--harness` proves your policy logic works — it does
20
+ > not enforce it against a caller trying to get around it. See [What this is not](#what-this-is-not)
21
+ > below before you rely on it for anything beyond testing rules.
22
+
14
23
  ```bash
15
24
  npm create metamynd-agent@latest -- --harness # or: npx create-metamynd-agent --harness
16
25
  ```
@@ -34,6 +43,8 @@ my-agent/
34
43
 
35
44
  ### What this is not
36
45
 
46
+ **Without MetaMynd, you can be bypassed.** Everything below is why, precisely.
47
+
37
48
  No anchored or cross-party-verifiable identity, no dashboard reachable when your machine is off, no
38
49
  owner queue someone *else* can approve from, no anchored evidence, no enforced platform Standards.
39
50
  That set of things is the hosted platform — and getting there later is a **config change, not a
@@ -45,13 +56,13 @@ It is also **not a separate enforcement boundary**, and this matters more than t
45
56
  `guardToolLocal()` is a cooperative library your own process embeds — call the raw handler directly
46
57
  instead of the guarded one and nothing stops you, because there is no second party in the loop to
47
58
  disagree with you. Confirmed by direct testing: a bypass attempt (skip the guard, call the tool
48
- function underneath it) succeeds every time, structurally, not as a bug. In the hosted flow this is
49
- what the **counterparty** is for the MCP/tool service independently re-verifies the agent's signed
50
- authority for itself rather than trusting that the agent's own guard ran, which is why a compromised
51
- or dishonest agent still can't get an honest service to act (see the three-party demo at
52
- [metamynd.ai/developers/quickstart](https://metamynd.ai/developers/quickstart)). `--harness` has no
53
- counterparty, so it can't have that property. Use it to govern your own agent's own honest behavior
54
- not as a defense against an agent (or a person) that's actively trying to get around it.
59
+ function underneath it) succeeds every time, structurally, not as a bug. What actually closes this
60
+ is a **counterparty** a separate process holding the tool, that independently re-verifies the
61
+ agent's signed authority for itself rather than trusting that the agent's own guard ran. `--harness`
62
+ never has one, by design (there's no second party on one machine with no network). **Just dropping
63
+ `--harness` is not enough on its own to get one either** — see
64
+ [Separate tool gateway](#separate-tool-gateway-default) below for what actually provides it, and
65
+ `--no-gateway`'s own caveat for what happens if you opt out of it.
55
66
 
56
67
  Works with `--config` too — its `rules` become the harness's starter rules file, same as the hosted
57
68
  flow. See [Policy config file](#policy-config-file---config) below.
@@ -74,6 +85,12 @@ call the hosted API (a shared demo identity) — it's a first look at the *hoste
74
85
  local/offline mode. Great for a first look; use the full flow below when you want your own governed
75
86
  agent with your own limits.
76
87
 
88
+ `--sandbox` always scaffolds the single-process shape (no `gateway/`) — it's a shared identity never
89
+ meant to hold real credentials, so there's nothing here worth a separate enforcement boundary for.
90
+ Note this is a narrower gap than `--harness`'s: `--sandbox` DOES call MetaMynd for the decision, it
91
+ just still has nowhere else for the tool to live, so the same in-process bypass applies regardless.
92
+ The generated project's own README says so. The full flow below is what scaffolds one by default.
93
+
77
94
  ## Use
78
95
 
79
96
  ```bash
@@ -82,18 +99,32 @@ npm create metamynd-agent@latest
82
99
  npx create-metamynd-agent
83
100
  ```
84
101
 
85
- Answer a few prompts (API, owner email/password, agent name, scope, per-transaction cap) and you get:
102
+ Answer a few prompts (API, owner email/password, agent name, scope, per-transaction cap) and you get
103
+ **two** scaffolded projects — the agent, and its tool gateway:
86
104
 
87
105
  ```
88
106
  my-agent/
89
107
  ├─ agent.metamynd.json # portable guard config — HOLDS THE AGENT SECRET KEY (gitignored)
90
- ├─ index.mjs # runnable example: ALLOW · BLOCK (over cap) · ESCALATE (high risk)
108
+ ├─ index.mjs # runnable example: signs + calls ./gateway; guardTool() here is a
109
+ │ # fast local pre-check, NOT the enforcement boundary
91
110
  ├─ package.json # depends on @metamynd/agentsafe-guard
92
111
  ├─ .gitignore
93
- └─ README.md
112
+ ├─ README.md
113
+ └─ gateway/ # a SEPARATE process — the real enforcement boundary. Read its
114
+ ├─ server.mjs # README first if you only read one.
115
+ ├─ package.json # depends on @metamynd/agentsafe-mcp-guard + @metamynd/agentsafe-http-gateway
116
+ ├─ .env.example # real tool credentials go here, never in the agent directory
117
+ ├─ .gitignore
118
+ └─ README.md
94
119
  ```
95
120
 
96
- Then:
121
+ Then, in **two terminals** — the gateway first:
122
+
123
+ ```bash
124
+ cd my-agent/gateway
125
+ npm install
126
+ npm start
127
+ ```
97
128
 
98
129
  ```bash
99
130
  cd my-agent
@@ -101,6 +132,32 @@ npm install
101
132
  npm start
102
133
  ```
103
134
 
135
+ ### Separate tool gateway (default)
136
+
137
+ This is the other half of **without MetaMynd, you can be bypassed**: WITH it — specifically, with
138
+ `gateway/`, the second process this scaffolds by default — you can't be, the same way the hosted
139
+ platform's own MCP counterparty can't be talked around by a compromised agent.
140
+
141
+ `guard.guardTool()` in `index.mjs` still runs — it's a fast, local, client-side pre-check that gives
142
+ good UX (fail fast on an obviously-blocked call, no round trip) — but it is **not** what stops a
143
+ bypass. It still calls its handler in the SAME process regardless of where the decision came from,
144
+ so anything able to call that handler directly gets the same result the gate would have given it.
145
+
146
+ What actually stops a bypass is that `bookFlight()` doesn't exist in the agent's process at all.
147
+ It exists only in `gateway/server.mjs` — a separate process, started separately, holding any real
148
+ tool credentials the agent process never sees — which independently re-verifies every request
149
+ against the agent's own published policy bundle before running it (same shape as the mutual
150
+ counterparty check in [`@metamynd/agentsafe-mcp-guard`](https://www.npmjs.com/package/@metamynd/agentsafe-mcp-guard),
151
+ built with [`@metamynd/agentsafe-http-gateway`](https://www.npmjs.com/package/@metamynd/agentsafe-http-gateway)).
152
+ It's a minimal slice of the same pattern proven end to end in `demo/duffel-mcp-gateway` in the
153
+ AgentSafe repo (mutual handshake, x402 payment binding, capability tokens) — this scaffold gives you
154
+ just the part that closes the bypass, not the whole protocol.
155
+
156
+ Pass `--no-gateway` to opt out and get the old single-process scaffold instead — e.g. if you're
157
+ already running your own separate gateway and don't need this one. **You are back to being
158
+ bypassable if you do**, for the same structural reason `--harness` is; the generated project's own
159
+ README says so plainly.
160
+
104
161
  ## Non-interactive
105
162
 
106
163
  Every prompt has a flag or environment-variable fallback, so it scripts cleanly in CI:
@@ -123,6 +180,8 @@ METAMYND_PASSWORD='…' npx create-metamynd-agent --yes …
123
180
  | `--harness` | — | off (no login/KYB/network at all; free local governance — see above) |
124
181
  | `--sandbox` | — | off (skips login/KYB; shared sandbox agent, still hosted) |
125
182
  | `--config <file>` | — | a JSON policy file — see [Policy config file](#policy-config-file---config) |
183
+ | `--no-gateway` | — | off — hosted flow only; skips the default separate tool gateway (see above) |
184
+ | `--gateway-port <n>` | — | `4401` — hosted flow only, the gateway process's port |
126
185
  | `--port <n>` | — | `4400` — `--harness` only, the local dashboard's port |
127
186
  | `--api <url>` | `METAMYND_API` | `https://metamynd.ai/api/v1` |
128
187
  | `--email <email>` | `METAMYND_EMAIL` | — (required) |
@@ -200,14 +259,19 @@ npx create-metamynd-agent --claim --watch
200
259
  ```
201
260
 
202
261
  `--request` submits the request (as your own authed user) and stores the claim token locally; `--claim`
203
- polls until the owner approves, then scaffolds the project. With `--byok` the keypair is generated
204
- locally and control is proven on claim MetaMynd never sees the private key.
262
+ polls until the owner approves, then scaffolds the project (the same default two-process shape as
263
+ the full flow above `--no-gateway`/`--gateway-port` work here too). With `--byok` the keypair is
264
+ generated locally and control is proven on claim — MetaMynd never sees the private key.
205
265
 
206
266
  ## Security
207
267
 
208
268
  `agent.metamynd.json` contains the agent's **secret key** (a managed key, or — with `--byok` — the one
209
269
  generated locally). The scaffolded project gitignores it. Never commit it or paste it anywhere public.
210
270
 
271
+ Any REAL tool credential (an airline API key, a payment key, ...) belongs in `gateway/.env` — never
272
+ in the agent directory. That's the whole point of the default two-process shape: the agent process
273
+ should never be able to hold, or leak, a credential it doesn't have.
274
+
211
275
  ## Full guide
212
276
 
213
277
  `docs/integration/INTEGRATE-WITH-METAMYND.md` — the complete integration front-door (payments,
package/index.mjs CHANGED
@@ -3,8 +3,10 @@
3
3
  //
4
4
  // Logs a KYB-verified owner in, provisions the agent in ONE call
5
5
  // (POST /onboarding/agent → identity + mandate + starter SOP + enforced Standards),
6
- // writes the portable `agent.metamynd.json`, and drops a runnable example that gates a
7
- // tool through the guard (allow / block / escalate).
6
+ // writes the portable `agent.metamynd.json` and a runnable agent example, PLUS (by default)
7
+ // a separate `gateway/` process — a second, independent guard that re-verifies every request
8
+ // and holds the real tool, so the agent's own guardTool() call is a convenience, not the
9
+ // enforcement boundary. `--no-gateway` skips it (see README#separate-tool-gateway-default).
8
10
  //
9
11
  // ZERO dependencies: Node ≥ 18 built-ins only (fetch, readline).
10
12
  //
@@ -22,7 +24,13 @@ const GUARD_PKG = '@metamynd/agentsafe-guard';
22
24
  // >=0.4.0 <0.5.0, so leaving this at ^0.4.0 would scaffold an agent whose `npm test` runs
23
25
  // `agentsafe-guard verify` against a guard that has no such command.
24
26
  const GUARD_VERSION = '^0.5.0';
27
+ // The default hosted scaffold's SECOND process — the tool gateway (see scaffoldProject).
28
+ const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
29
+ const MCP_GUARD_VERSION = '^0.1.0';
30
+ const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
31
+ const GATEWAY_VERSION = '^0.1.0';
25
32
  const DEFAULT_API = 'https://metamynd.ai/api/v1';
33
+ const DEFAULT_GATEWAY_PORT = 4401; // distinct from --harness's dashboard (4400)
26
34
 
27
35
  // ---------- tiny ANSI ----------
28
36
  const c = {
@@ -88,6 +96,10 @@ ${c.b('Options')}
88
96
  (MetaMynd never sees the private key). Overridden by --public-key.
89
97
  --public-key <hex> BYOK with a key you already hold (SPKI/raw hex); you prove control yourself
90
98
  --out <dir> Output project directory (default ./<agent-slug>)
99
+ --no-gateway Hosted flow only: skip the separate tool-gateway process (see
100
+ README#separate-tool-gateway-default) and scaffold the old
101
+ single-process example instead. Not a separate enforcement boundary.
102
+ --gateway-port <n> Hosted flow only: the gateway process's port (default 4401)
91
103
  --port <n> --harness only: the local dashboard's port (default 4400)
92
104
  --yes, -y Non-interactive: use flags/env/defaults, never prompt
93
105
  -h, --help Show this help
@@ -99,7 +111,9 @@ ${c.b('Environment')}
99
111
  ${c.b('What it does')}
100
112
  1. Logs in as a KYB-verified owner → owner access token
101
113
  2. POST /onboarding/agent (one call) → identity + mandate + SOP + Standards
102
- 3. Writes agent.metamynd.json + a runnable example that gates a tool through the guard.
114
+ 3. Writes agent.metamynd.json + index.mjs, PLUS (by default) a separate gateway/ process
115
+ the real enforcement boundary, not index.mjs's own guard.guardTool() call. --no-gateway
116
+ skips it.
103
117
  `;
104
118
 
105
119
  // ---------- prompts ----------
@@ -257,7 +271,17 @@ async function apiPost(base, path, body, token) {
257
271
  }
258
272
 
259
273
  // ---------- scaffolding ----------
260
- function exampleIndex(scope, perTxnMax) {
274
+ /**
275
+ * The --no-gateway / --sandbox variant: the tool is a local function in the SAME process as
276
+ * guard.guardTool(). Fine for a demo with nothing real behind it (--sandbox always uses this —
277
+ * it's a shared identity, never meant to hold real credentials). For anything that touches a
278
+ * real credential, guard.guardTool() alone is a client-side convenience, not a boundary: it
279
+ * still calls this handler in-process regardless of where the decision came from, so an agent
280
+ * that skips it and calls bookFlight() directly gets the same result the gate would have given
281
+ * it — the same shape of gap --harness's README documents. See exampleIndex() below, which is
282
+ * what the real (non-sandbox) flow scaffolds by default instead.
283
+ */
284
+ function exampleIndexNoGateway(scope, perTxnMax) {
261
285
  const under = Math.max(1, Math.round(perTxnMax * 0.5));
262
286
  const over = Math.round(perTxnMax + 100);
263
287
  return `// index.mjs — your agent, governed by MetaMynd/AgentSafe.
@@ -268,6 +292,11 @@ import { createGuardFromConfig } from '${GUARD_PKG}';
268
292
  const guard = await createGuardFromConfig('./agent.metamynd.json'); // no env vars
269
293
 
270
294
  // --- Your real tool. Replace the body with your actual implementation. ---
295
+ // --- If that implementation touches a real credential, this in-process call is NOT an
296
+ // --- enforcement boundary: guard.guardTool() below still calls this function directly in
297
+ // --- THIS process regardless of the decision's source, so anything that can call it directly
298
+ // --- gets the same result the gate would have given it. A real (non --sandbox) scaffold
299
+ // --- without --no-gateway moves this behind a separate process instead. See README.
271
300
  async function bookFlight(args) {
272
301
  return { pnr: 'PNR-DEMO', ...args };
273
302
  }
@@ -391,6 +420,193 @@ console.log(dim(' - the blocked call never reached your tool at all.'));
391
420
  console.log(dim(' - every decision was recorded as tamper-evident evidence.'));
392
421
  console.log(dim(' - if the gate were unreachable the guard fails CLOSED: it blocks.'));
393
422
  console.log('');
423
+ console.log(bold(' Without MetaMynd, you can be bypassed.') + ' bookFlight() runs in THIS process -');
424
+ console.log(dim(' call it directly instead of gatedBookFlight and nothing above stops you.'));
425
+ console.log(dim(' Re-scaffold without --sandbox/--no-gateway for the default shape, which does.'));
426
+ console.log('');
427
+ console.log(' Change the cap in the dashboard (Legal Entity -> SOPs) and run again.');
428
+ console.log(dim(' The outcome changes. This file does not. That is the point.'));
429
+ console.log('');
430
+ `;
431
+ }
432
+
433
+ /**
434
+ * The DEFAULT hosted scaffold: the tool lives in a separate process (./gateway), not here.
435
+ * guard.guardTool() below is still called — it is a fast, local, client-side pre-check that
436
+ * gives good UX (fail fast, no round trip for an obviously-blocked call) — but it is not what
437
+ * stops a bypass. What stops a bypass is that there is no bookFlight() in THIS process to call
438
+ * directly: it only exists in ./gateway, which independently re-verifies every request against
439
+ * this agent's own policy bundle before it runs, and holds any real credentials the tool needs.
440
+ */
441
+ function exampleIndex(scope, perTxnMax, gatewayPort) {
442
+ const under = Math.max(1, Math.round(perTxnMax * 0.5));
443
+ const over = Math.round(perTxnMax + 100);
444
+ return `// index.mjs — your agent, governed by MetaMynd/AgentSafe.
445
+ // Every governed tool call is checked TWICE before it runs: once here (fast, local, client-side),
446
+ // and independently again by ./gateway — a SEPARATE process that holds the real tool and its
447
+ // credentials. That second check is the actual enforcement boundary; see ./gateway/README.md.
448
+ import { createGuardFromConfig } from '${GUARD_PKG}';
449
+
450
+ // Loads agent.metamynd.json: the agent's DID, its signing key, and the gate to call.
451
+ const guard = await createGuardFromConfig('./agent.metamynd.json'); // no env vars
452
+
453
+ const GATEWAY = process.env.GATEWAY_URL || 'http://localhost:${gatewayPort}';
454
+
455
+ // --- Calls the gateway process instead of a local function. There is no raw bookFlight() in
456
+ // --- this file to call directly — the tool, and any real credentials it needs, live only in
457
+ // --- ./gateway, which independently re-verifies this signed request itself.
458
+ async function bookFlightViaGateway(args) {
459
+ const signed = guard.buildSignedRequest({
460
+ action: '${scope}',
461
+ amount: args.amount,
462
+ currency: 'USD',
463
+ merchant: args.merchant,
464
+ context: { tool: 'book-flight', riskLevel: args.riskLevel ?? 'low' },
465
+ });
466
+ const res = await fetch(GATEWAY + '/book-flight', {
467
+ method: 'POST',
468
+ headers: { 'content-type': 'application/json', 'x-magp-request': JSON.stringify(signed) },
469
+ body: JSON.stringify(args),
470
+ });
471
+ const body = await res.json().catch(() => null);
472
+ if (!res.ok) {
473
+ const err = new Error('gateway ' + res.status + ': ' + (body?.reasonCode ?? 'refused'));
474
+ err.name = 'GovernanceBlocked';
475
+ err.governance = { decision: body?.decision ?? 'block', reasonCode: body?.reasonCode ?? 'GATEWAY_ERROR' };
476
+ throw err;
477
+ }
478
+ return body;
479
+ }
480
+
481
+ // --- The GATED version. Register THIS with your agent instead of calling the gateway directly.
482
+ // --- This local check and the gateway's own re-check are independent; neither trusts the other.
483
+ const gatedBookFlight = guard.guardTool(
484
+ '${scope}', // = your mandate scope
485
+ bookFlightViaGateway,
486
+ (a) => ({ // map tool args → gate inputs
487
+ amount: a.amount,
488
+ currency: 'USD',
489
+ merchant: a.merchant,
490
+ context: { tool: 'book-flight', riskLevel: a.riskLevel ?? 'low' },
491
+ }),
492
+ );
493
+
494
+ // --- A tool the agent was NEVER granted. Wrapping it is the demonstration: there is no
495
+ // --- rule anywhere forbidding this. The mandate simply never mentioned the action.
496
+ async function raiseOwnLimit(args) {
497
+ return { updated: true, ...args }; // never runs, and that is the point
498
+ }
499
+
500
+ const gatedRaiseOwnLimit = guard.guardTool(
501
+ 'permissions.update', // an action NOT in the mandate
502
+ raiseOwnLimit,
503
+ (a) => ({
504
+ amount: a.amount,
505
+ currency: 'USD',
506
+ merchant: a.merchant,
507
+ context: { tool: 'permissions-update' },
508
+ }),
509
+ );
510
+
511
+ const dim = (t) => '\\x1b[2m' + t + '\\x1b[0m';
512
+ const bold = (t) => '\\x1b[1m' + t + '\\x1b[0m';
513
+ const rule = (n) => ' ' + '-'.repeat(n);
514
+
515
+ // Plain-English meaning for the reason codes this demo can produce. The gateway re-evaluates
516
+ // the SAME policy bundle with the SAME evaluator the gate uses, so it produces these same codes.
517
+ const WHY = {
518
+ AUTHORIZED: 'inside the mandate and under the SOP spend cap',
519
+ SOP_SPEND_CAP: 'your SOP caps a single transaction at $${perTxnMax}',
520
+ RISK_REVIEW: 'your SOP sends high-risk actions to a human first',
521
+ MERCHANT_NOT_ALLOWED: 'the mandate lists which merchants this agent may pay',
522
+ // Both say the same thing from where you are standing: the mandate does not cover that
523
+ // action. Which one you see depends on whether the verdict was reached here or at the
524
+ // gate, and neither of them depends on the amount.
525
+ NO_PERMISSION_FOR_ACTION: 'the mandate never granted this action - at any amount',
526
+ NO_MANDATE: 'there is no mandate for this action at all',
527
+ };
528
+
529
+ // ---------------------------------------------------------------- 1. CONTEXT
530
+ console.log('');
531
+ console.log(bold(' What this simulation shows'));
532
+ console.log('');
533
+ console.log(' An agent should not be the thing that decides what it is allowed to do — and');
534
+ console.log(' it should not be the thing that RUNS what it decided, either. This run makes');
535
+ console.log(' both concrete. Three attempts take the SAME code path and produce three');
536
+ console.log(' different outcomes. The fourth asks for something the agent was never granted');
537
+ console.log(' at all - and that is the one a prompt could not have stopped, because the');
538
+ console.log(' decision is not made inside your program, and the tool is not either.');
539
+
540
+ // ---------------------------------------------------------------- 2. MECHANISM
541
+ console.log('');
542
+ console.log(bold(' How it does that'));
543
+ console.log('');
544
+ console.log(dim(' 1. this project holds an agent identity (a DID) and its signing key'));
545
+ console.log(dim(' 2. that agent has a mandate - a scope it may act in, and a spend cap'));
546
+ console.log(dim(' 3. guardTool() wraps your tool call, giving a fast local pre-check'));
547
+ console.log(dim(' 4. each attempt is ALSO signed and sent to ./gateway - a separate process'));
548
+ console.log(dim(' 5. the gateway independently re-verifies before your tool runs there'));
549
+ console.log(dim(' 6. there is no local bookFlight() to call directly - only the gateway has it'));
550
+ console.log('');
551
+ console.log(dim(' scope ${scope}'));
552
+ console.log(dim(' cap $${perTxnMax} per transaction, set by your SOP'));
553
+ console.log(dim(' gateway ' + GATEWAY + ' (run it in a separate terminal - see ./gateway)'));
554
+
555
+ // ---------------------------------------------------------------- 3. THE STEPS
556
+ async function attempt(n, intent, args, tool = gatedBookFlight) {
557
+ console.log('');
558
+ console.log(bold(' Step ' + n + ' of 4') + ' - ' + intent);
559
+ console.log(dim(' signing the request locally, then asking the gate to decide...'));
560
+ try {
561
+ const r = await tool(args);
562
+ console.log('\\x1b[32m ALLOWED\\x1b[0m your tool ran (in ./gateway) and returned ' + (r.pnr ?? 'ok'));
563
+ console.log(dim(' ' + WHY.AUTHORIZED));
564
+ } catch (e) {
565
+ const g = e.governance ?? {};
566
+ const why = WHY[g.reasonCode] ?? e.message;
567
+ if (g.decision === 'escalate') {
568
+ console.log('\\x1b[33m ESCALATED\\x1b[0m held for a human - ' + g.reasonCode);
569
+ console.log(dim(' ' + why));
570
+ console.log(dim(' not a failure: approve it in the dashboard and the action resumes.'));
571
+ } else {
572
+ console.log('\\x1b[31m BLOCKED\\x1b[0m ' + (g.reasonCode ?? 'refused'));
573
+ console.log(dim(' ' + why));
574
+ console.log(dim(' your tool never ran - refused before execution.'));
575
+ }
576
+ }
577
+ }
578
+
579
+ console.log('');
580
+ console.log(rule(66));
581
+ await attempt(1, 'a $${under} booking, low risk. Expected to pass.', { amount: ${under}, merchant: 'skyward-air', riskLevel: 'low' });
582
+ await attempt(2, 'a $${over} booking, deliberately over the cap.', { amount: ${over}, merchant: 'skyward-air', riskLevel: 'low' });
583
+ await attempt(3, 'a $${under} booking, but flagged high risk.', { amount: ${under}, merchant: 'skyward-air', riskLevel: 'high' });
584
+ await attempt(
585
+ 4,
586
+ 'the agent stops booking flights and asks to raise its OWN limit.',
587
+ { amount: 100000, merchant: 'skyward-air' },
588
+ gatedRaiseOwnLimit,
589
+ );
590
+ console.log('');
591
+ console.log(rule(66));
592
+
593
+ // ---------------------------------------------------------------- 4. RESULT
594
+ console.log('');
595
+ console.log(bold(' What this proved'));
596
+ console.log('');
597
+ console.log(dim(' - one code path, three outcomes. The rules decided, not this file'));
598
+ console.log(dim(' and not the model driving it.'));
599
+ console.log(dim(' - step 1 ran in ./gateway, a process this file cannot reach into. There'));
600
+ console.log(dim(' is no rawBookFlight() here to call instead - that is what actually'));
601
+ console.log(dim(' stops a bypass, not the guardTool() call above it.'));
602
+ console.log(dim(' - step 4 needed no rule to stop it. The agent could not widen its own'));
603
+ console.log(dim(' authority, because it cannot name an action nobody delegated to it.'));
604
+ console.log(dim(' - every blocked/escalated call never reached a real tool at all.'));
605
+ console.log(dim(' - if the gate were unreachable the guard fails CLOSED: it blocks.'));
606
+ console.log('');
607
+ console.log(bold(' With MetaMynd, you can\\'t be bypassed.') + ' ./gateway is why - it independently');
608
+ console.log(dim(' re-verified step 1 before running it, and holds the tool this file never can.'));
609
+ console.log('');
394
610
  console.log(' Change the cap in the dashboard (Legal Entity -> SOPs) and run again.');
395
611
  console.log(dim(' The outcome changes. This file does not. That is the point.'));
396
612
  console.log('');
@@ -415,12 +631,41 @@ function examplePackageJson(slug) {
415
631
  ) + '\n';
416
632
  }
417
633
 
418
- function exampleReadme(slug, scope) {
419
- return `# ${slug}
634
+ function exampleReadme(slug, scope, withGateway, gatewayPort) {
635
+ const gatewaySection = withGateway
636
+ ? `## Run
420
637
 
421
- A MetaMynd/AgentSafe-governed agent, scaffolded with \`create-metamynd-agent\`.
638
+ Two processes — start the gateway first, in its own terminal:
422
639
 
423
- ## Run
640
+ \`\`\`bash
641
+ cd gateway && npm install && npm start # the REAL enforcement boundary — see gateway/README.md
642
+ \`\`\`
643
+
644
+ Then, in this directory:
645
+
646
+ \`\`\`bash
647
+ npm install
648
+ npm start
649
+ \`\`\`
650
+
651
+ You should see an ALLOW (fulfilled by \`./gateway\`), a BLOCK (over the per-transaction cap), and
652
+ an ESCALATE (high risk). The BLOCK and ESCALATE never reach the gateway at all — this file's own
653
+ \`guard.guardTool()\` refuses them first. Only the ALLOW crosses into the other process.
654
+
655
+ ## Files
656
+
657
+ - \`agent.metamynd.json\` — your portable guard config (identity, mandate scope \`${scope}\`, issuer keys).
658
+ **Contains the agent's secret key — never commit it.** It is already in \`.gitignore\`.
659
+ - \`index.mjs\` — signs each request and calls \`./gateway\` for it; \`guard.guardTool()\` here is a
660
+ fast local pre-check, not the enforcement boundary.
661
+ - \`gateway/\` — a **separate process**. It holds the real tool and independently re-verifies every
662
+ request against this agent's own policy before running it. See \`gateway/README.md\` — read that
663
+ one first if you're only going to read one.
664
+
665
+ ## What this is not
666
+
667
+ `
668
+ : `## Run
424
669
 
425
670
  \`\`\`bash
426
671
  npm install
@@ -435,6 +680,36 @@ You should see an ALLOW, a BLOCK (over the per-transaction cap), and an ESCALATE
435
680
  **Contains the agent's secret key — never commit it.** It is already in \`.gitignore\`.
436
681
  - \`index.mjs\` — wraps a tool with \`guard.guardTool(...)\`; the tool only runs when the gate allows.
437
682
 
683
+ ## What this is not
684
+
685
+ `;
686
+ return `# ${slug}
687
+
688
+ A MetaMynd/AgentSafe-governed agent, scaffolded with \`create-metamynd-agent\`.
689
+
690
+ ${gatewaySection}${
691
+ withGateway
692
+ ? `**With MetaMynd's gateway, you can't be bypassed** — that's what this section is about.
693
+ This scaffold's default shape (agent + separate gateway process, port ${gatewayPort} by
694
+ default) is the actual enforcement boundary: \`guard.guardTool()\` in \`index.mjs\` is a
695
+ client-side convenience, not a boundary — it still runs its handler in-process regardless of
696
+ where the decision came from. What actually stops a bypass is that \`bookFlight()\` itself only
697
+ exists in \`./gateway\`, a process this one cannot reach into, which independently re-verifies
698
+ every request against this agent's own policy bundle. Re-scaffold with \`--no-gateway\` for the
699
+ old single-process shape — it is NOT a separate enforcement boundary; see its own generated
700
+ README for why.`
701
+ : `**Without MetaMynd, you can be bypassed** — this is that case. This scaffold has no
702
+ separate gateway process (either \`--sandbox\`, which never provisions real credentials, or
703
+ \`--no-gateway\` was passed): \`guard.guardTool()\` wraps a tool in the SAME process as the check
704
+ itself. That is a client-side convenience, not a boundary — it still runs your tool's handler
705
+ in-process regardless of where the decision came from, so anything able to call \`bookFlight()\`
706
+ directly gets the same result the gate would have given it. If this tool ever holds a real
707
+ credential, provision for real (drop \`--sandbox\`) without \`--no-gateway\` for the default
708
+ shape, which puts the tool behind a separate process instead. This is the same structural gap
709
+ \`--harness\`'s README documents, for the same reason: a cooperative in-process check has no
710
+ counterparty to disagree with a caller that skips it.`
711
+ }
712
+
438
713
  ## Change the rules
439
714
 
440
715
  Edit the agent's SOPs in the dashboard (Legal Entity → SOPs). The agent's behaviour changes live —
@@ -448,6 +723,173 @@ function gitignore() {
448
723
  return `node_modules/\nagent.metamynd.json\n.env\n`;
449
724
  }
450
725
 
726
+ // ---------- the default hosted scaffold's second process: a separate tool gateway ----------
727
+ //
728
+ // Not a new protocol — @metamynd/agentsafe-mcp-guard (trustless verifyRequest, already public)
729
+ // and @metamynd/agentsafe-http-gateway (the generic reverse-proxy built on it, already public)
730
+ // do the real work. This just wires up the smallest useful shape: one protected route, one
731
+ // tool, re-verified independently of the agent that's calling it. See demo/duffel-mcp-gateway
732
+ // in the AgentSafe repo for the full pattern (mutual handshake, x402 payment, capability
733
+ // binding) this is a minimal slice of.
734
+
735
+ function gatewayServerFile(scope, port, apiBase) {
736
+ return `#!/usr/bin/env node
737
+ // gateway/server.mjs — the REAL enforcement boundary for this agent's tool(s).
738
+ //
739
+ // This is a SEPARATE process from the agent. It holds the tool's real credentials (the agent
740
+ // process never does), and it independently re-verifies every request against this agent's OWN
741
+ // published policy bundle — it does not trust the agent's own guard.guardTool() check. A
742
+ // compromised or dishonest agent calling its own local function gets nothing here, because
743
+ // there is no local function: the tool only runs in this process.
744
+ import http from 'node:http';
745
+ import { createMcpGuard } from '${MCP_GUARD_PKG}';
746
+ import { createHttpGateway } from '${GATEWAY_PKG}';
747
+
748
+ const PORT = Number(process.env.PORT || ${port});
749
+ const MAGP_API = process.env.MAGP_API || '${apiBase}';
750
+
751
+ // --- Your real tool. Real credentials (an airline API key, a payment key, ...) belong ONLY
752
+ // --- here, read from process.env (see .env.example) — never in the agent process.
753
+ async function bookFlight(args) {
754
+ return { pnr: 'PNR-DEMO', ...args };
755
+ }
756
+
757
+ // One protected route: only a request signed by this agent, for exactly this action, and
758
+ // re-verified against this agent's own mandate/SOP, reaches bookFlight() below.
759
+ const routes = [{ method: 'POST', path: '/book-flight', action: '${scope}' }];
760
+
761
+ // No serviceKey: this minimal gateway only calls verifyRequest() (re-check a signed request),
762
+ // not the mutual-handshake methods, which are the only thing that needs it.
763
+ const guard = createMcpGuard({ serviceDid: 'did:local:${scope}-gateway', issuerApi: MAGP_API });
764
+
765
+ const gateway = createHttpGateway({
766
+ guard,
767
+ routes,
768
+ forward: async (req) => {
769
+ let args = {};
770
+ try { args = JSON.parse(req.rawBody?.toString('utf8') || '{}'); } catch { /* empty body */ }
771
+ const result = await bookFlight(args);
772
+ return { status: 200, body: result };
773
+ },
774
+ // This gateway IS the tool, not a proxy in front of one — an unmatched path has nothing to
775
+ // pass through TO. Without this, any path a route doesn't match falls through ungoverned
776
+ // straight to forward() above, which would run bookFlight() with no check at all.
777
+ denyByDefault: true,
778
+ });
779
+
780
+ function readBody(req) {
781
+ return new Promise((resolve, reject) => {
782
+ const chunks = [];
783
+ req.on('data', (c) => chunks.push(c));
784
+ req.on('end', () => resolve(Buffer.concat(chunks)));
785
+ req.on('error', reject);
786
+ });
787
+ }
788
+
789
+ const server = http.createServer(async (req, res) => {
790
+ try {
791
+ const rawBody = await readBody(req);
792
+ const result = await gateway({ method: req.method, path: req.url, headers: req.headers, rawBody });
793
+ const headers = { 'content-type': 'application/json' };
794
+ if (result.governance) headers['x-agentsafe-decision'] = result.governance.decision;
795
+ res.writeHead(result.status, headers);
796
+ res.end(JSON.stringify(result.body ?? {}));
797
+ } catch (err) {
798
+ // Fail CLOSED on any gateway error.
799
+ res.writeHead(502, { 'content-type': 'application/json' });
800
+ res.end(JSON.stringify({ decision: 'block', reasonCode: 'GATEWAY_ERROR', error: String(err?.message ?? err) }));
801
+ }
802
+ });
803
+
804
+ server.listen(PORT, () => {
805
+ console.log('[gateway] listening on :' + PORT + ' -> the only place bookFlight() runs.');
806
+ console.log('[gateway] every request is independently re-verified against this agent\\'s own policy.');
807
+ });
808
+ `;
809
+ }
810
+
811
+ function gatewayPackageJson(slug) {
812
+ return JSON.stringify(
813
+ {
814
+ name: slug + '-gateway',
815
+ version: '0.1.0',
816
+ private: true,
817
+ type: 'module',
818
+ scripts: { start: 'node server.mjs' },
819
+ dependencies: { [MCP_GUARD_PKG]: MCP_GUARD_VERSION, [GATEWAY_PKG]: GATEWAY_VERSION },
820
+ },
821
+ null,
822
+ 2,
823
+ ) + '\n';
824
+ }
825
+
826
+ function gatewayEnvExample() {
827
+ return `# Real tool credentials belong HERE, read from process.env in server.mjs — never in the
828
+ # agent process one directory up.
829
+ # AIRLINE_API_KEY=
830
+ `;
831
+ }
832
+
833
+ function gatewayGitignore() {
834
+ return `node_modules/\n.env\n`;
835
+ }
836
+
837
+ function gatewayReadme(slug, scope, port) {
838
+ return `# ${slug}-gateway
839
+
840
+ **With MetaMynd, you can't be bypassed.** This process is why. It is the **real enforcement
841
+ boundary** for \`${slug}\`'s tool(s) — not \`../index.mjs\`.
842
+
843
+ ## Why this exists
844
+
845
+ \`guard.guardTool()\` in the agent's \`index.mjs\` is a client-side convenience: it gives fast,
846
+ local ALLOW/BLOCK/ESCALATE feedback, but it still runs its handler in the SAME process
847
+ regardless of where that decision came from. Anything able to call the agent's tool function
848
+ directly — a bug, a compromised dependency, a dishonest fork of the agent's own code — gets the
849
+ same result the gate would have given it. That is not a defect in \`guardTool()\`; a cooperative
850
+ in-process check has no counterparty to disagree with a caller that skips it. See \`--harness\`'s
851
+ own README for the same structural point in the free local-demo mode.
852
+
853
+ This process closes that gap by being a **separate** one. The agent has no way to reach into it
854
+ and call \`bookFlight()\` directly, because \`bookFlight()\` doesn't exist in the agent's process —
855
+ it exists only here, and every request that reaches it has already been independently
856
+ re-verified against this agent's OWN published policy bundle, fetched over the network by THIS
857
+ process, not trusted from the agent's say-so.
858
+
859
+ ## Run
860
+
861
+ \`\`\`bash
862
+ npm install
863
+ npm start
864
+ \`\`\`
865
+
866
+ Listens on \`:${port}\` by default (\`PORT\` env var to change it — keep \`../index.mjs\`'s
867
+ \`GATEWAY_URL\` in sync if you do).
868
+
869
+ ## Add real credentials
870
+
871
+ Edit \`server.mjs\`'s \`bookFlight()\` with your real implementation, reading any credentials it
872
+ needs from \`process.env\` (see \`.env.example\`). Load \`.env\` however you prefer (e.g.
873
+ \`node --env-file=.env server.mjs\`, Node ≥ 20.6) — it is already in \`.gitignore\`. The agent
874
+ directory one level up must never hold these credentials; if it needs to call a DIFFERENT tool,
875
+ add another protected route here rather than adding a local function back in \`index.mjs\`.
876
+
877
+ ## Files
878
+
879
+ - \`server.mjs\` — the gateway: one protected route (\`POST /book-flight\`, action \`${scope}\`),
880
+ \`@metamynd/agentsafe-mcp-guard\`'s \`verifyRequest()\` re-checking every request, and the real
881
+ \`bookFlight()\`.
882
+ - \`.env.example\` — where real tool credentials go (copy to \`.env\`, fill in, never commit).
883
+
884
+ ## Beyond this minimal slice
885
+
886
+ This gateway only re-verifies a signed request (§9.3/§9.6 of the MAGP spec). It does not do the
887
+ mutual DID handshake, x402 payment binding, or commitment-bound capability tokens that a
888
+ production Service integration would add — see \`@metamynd/agentsafe-mcp-guard\`'s own README for
889
+ those, and \`demo/duffel-mcp-gateway\` in the AgentSafe repo for a full worked example.
890
+ `;
891
+ }
892
+
451
893
  function writeFileSafe(dir, name, content, force = false) {
452
894
  const p = join(dir, name);
453
895
  const exists = existsSync(p);
@@ -480,16 +922,32 @@ function assertScaffoldTarget(outDir, force) {
480
922
  );
481
923
  }
482
924
 
483
- /** Write the scaffolded project + print next steps. Shared by the provision and sandbox paths. */
484
- function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox, force = false }) {
925
+ /**
926
+ * Write the scaffolded project + print next steps. Shared by the provision and sandbox paths.
927
+ * `withGateway`: scaffold the default two-process shape (agent + ./gateway) — the real
928
+ * enforcement boundary. Off for --sandbox (shared demo identity, never real credentials
929
+ * anyway) and --no-gateway (opt out, e.g. you're already running your own separate gateway).
930
+ */
931
+ function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox, withGateway, gatewayPort = DEFAULT_GATEWAY_PORT, force = false }) {
485
932
  assertScaffoldTarget(outDir, force);
486
933
  console.log(`\n ${c.b('Scaffolding')} ${c.dim(outDir)}`);
487
934
  if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
488
935
  writeFileSafe(outDir, 'agent.metamynd.json', JSON.stringify(config, null, 2) + '\n', force);
489
- writeFileSafe(outDir, 'index.mjs', exampleIndex(scope, perTxnMax), force);
936
+ writeFileSafe(outDir, 'index.mjs', withGateway ? exampleIndex(scope, perTxnMax, gatewayPort) : exampleIndexNoGateway(scope, perTxnMax), force);
490
937
  writeFileSafe(outDir, 'package.json', examplePackageJson(slug), force);
491
938
  writeFileSafe(outDir, '.gitignore', gitignore(), force);
492
- writeFileSafe(outDir, 'README.md', exampleReadme(slug, scope), force);
939
+ writeFileSafe(outDir, 'README.md', exampleReadme(slug, scope, withGateway, gatewayPort), force);
940
+
941
+ if (withGateway) {
942
+ const apiBase = config.apiBase ?? config.api ?? DEFAULT_API;
943
+ const gwDir = join(outDir, 'gateway');
944
+ if (!existsSync(gwDir)) mkdirSync(gwDir, { recursive: true });
945
+ writeFileSafe(gwDir, 'server.mjs', gatewayServerFile(scope, gatewayPort, apiBase), force);
946
+ writeFileSafe(gwDir, 'package.json', gatewayPackageJson(slug), force);
947
+ writeFileSafe(gwDir, '.env.example', gatewayEnvExample(), force);
948
+ writeFileSafe(gwDir, '.gitignore', gatewayGitignore(), force);
949
+ writeFileSafe(gwDir, 'README.md', gatewayReadme(slug, scope, gatewayPort), force);
950
+ }
493
951
 
494
952
  const rel = outDir.replace(resolve('.'), '.').replace(/\\/g, '/');
495
953
  console.log(`\n${c.green(c.b(' ✓ Done.'))} Your governed agent is ready.\n`);
@@ -498,7 +956,13 @@ function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox, forc
498
956
  } else if (config.agentKey) {
499
957
  console.log(` ${c.yellow('⚠ agent.metamynd.json holds the agent secret key')} — it is gitignored; never commit it.\n`);
500
958
  }
959
+ if (withGateway) {
960
+ console.log(` ${c.yellow('⚠ two processes now')} — \`gateway/\` is the real enforcement boundary, not \`index.mjs\`. Read \`gateway/README.md\`.\n`);
961
+ }
501
962
  console.log(` Next:`);
963
+ if (withGateway) {
964
+ console.log(c.cyan(` cd ${rel}/gateway && npm install && npm start`) + c.dim(' (separate terminal — start this first)'));
965
+ }
502
966
  console.log(c.cyan(` cd ${rel}`));
503
967
  console.log(c.cyan(` npm install`));
504
968
  // The example runs FOUR attempts. This summary promised three, so the one carrying the
@@ -524,7 +988,7 @@ async function runSandbox(args) {
524
988
  console.log(` ${c.green('✓')} sandbox agent ${c.b(config.agentDid)} ${c.dim('(shared test identity)')}`);
525
989
  const scope = config.mandate?.scope || 'flight-purchase';
526
990
  const perTxnMax = Number(config.perTxnMax) || 500;
527
- scaffoldProject({ outDir, config, slug: 'metamynd-sandbox', scope, perTxnMax, sandbox: true, force: !!args.force });
991
+ scaffoldProject({ outDir, config, slug: 'metamynd-sandbox', scope, perTxnMax, sandbox: true, withGateway: false, force: !!args.force });
528
992
  }
529
993
 
530
994
  // ---------- --harness: a free, local, zero-network governance harness ----------
@@ -1098,12 +1562,17 @@ console.log(dim(' authority, because it cannot name an action nobody delegat
1098
1562
  console.log(dim(' - the blocked call never reached your tool at all.'));
1099
1563
  console.log(dim(' - every decision is in ./metamynd-harness.log.jsonl - yours, locally.'));
1100
1564
  console.log('');
1565
+ console.log(bold(' Without MetaMynd, you can be bypassed.') + ' bookFlight() above runs in THIS');
1566
+ console.log(dim(' process - call it directly instead of gatedBookFlight and nothing stops you.'));
1567
+ console.log(dim(' --harness proves your policy logic; it does not enforce it against that.'));
1568
+ console.log('');
1101
1569
  console.log(' Edit ./metamynd-rules.json (or the dashboard) and run again - the outcome');
1102
1570
  console.log(dim(' changes. This file does not. That is the point.'));
1103
1571
  console.log('');
1104
1572
  console.log(dim(' Ready for more than one machine, a queue someone else can approve from,'));
1105
- console.log(dim(' anchored evidence, or KYC/KYB-backed identity? That is the hosted platform -'));
1106
- console.log(dim(' same guardTool() call, same rules shape, drop --harness and provision there.'));
1573
+ console.log(dim(' anchored evidence, or KYC/KYB-backed identity, AND a separate gateway process'));
1574
+ console.log(dim(' that closes the bypass above? That is the hosted platform - drop --harness'));
1575
+ console.log(dim(' and provision there; the same guardTool() call keeps working.'));
1107
1576
  console.log('');
1108
1577
  dashboard.close();
1109
1578
  `;
@@ -1153,6 +1622,8 @@ open the dashboard to approve it), and a BLOCK (an action outside the mandate en
1153
1622
 
1154
1623
  ## What this is not
1155
1624
 
1625
+ **Without MetaMynd, you can be bypassed.** Everything below is why, precisely.
1626
+
1156
1627
  No anchored/verifiable identity, no cross-party trust, no evidence anyone but you can audit,
1157
1628
  no dashboard reachable when this machine is off, no owner queue someone else can approve from.
1158
1629
  That's the hosted platform (\`npx create-metamynd-agent\`, without \`--harness\`) — same
@@ -1163,8 +1634,8 @@ cooperative library this process embeds — call the tool handler directly inste
1163
1634
  one and nothing stops you, because there is no second party in the loop to disagree with you.
1164
1635
  That's structural, not a bug: use this harness to govern your own agent's own honest behavior,
1165
1636
  not as a defense against an agent (or a person) actively trying to get around it. The hosted
1166
- platform's \`guardTool()\` doesn't have this gap, because the MCP/tool service re-verifies the
1167
- agent's signed authority for itself instead of trusting that the agent's own guard ran.
1637
+ platform's default scaffold doesn't have this gap, because a SEPARATE gateway process re-verifies
1638
+ the agent's signed authority for itself instead of trusting that the agent's own guard ran.
1168
1639
  `;
1169
1640
  }
1170
1641
 
@@ -1336,7 +1807,7 @@ async function runClaim(args) {
1336
1807
 
1337
1808
  const slug = slugify(state.name || 'metamynd-agent');
1338
1809
  const outDir = resolve(String(args.out || `./${slug}`));
1339
- scaffoldProject({ outDir, config, slug, scope: state.scope || config.mandate?.scope || 'flight-purchase', perTxnMax: Number(state.perTxnMax) || 500, sandbox: false, force: !!args.force });
1810
+ scaffoldProject({ outDir, config, slug, scope: state.scope || config.mandate?.scope || 'flight-purchase', perTxnMax: Number(state.perTxnMax) || 500, sandbox: false, withGateway: !args['no-gateway'], gatewayPort: Number(args['gateway-port']) || DEFAULT_GATEWAY_PORT, force: !!args.force });
1340
1811
  }
1341
1812
 
1342
1813
  // ---------- main ----------
@@ -1459,7 +1930,7 @@ async function main() {
1459
1930
  }
1460
1931
 
1461
1932
  // 4. Scaffold + next steps
1462
- scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox: false, force: !!args.force });
1933
+ scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox: false, withGateway: !args['no-gateway'], gatewayPort: Number(args['gateway-port']) || DEFAULT_GATEWAY_PORT, force: !!args.force });
1463
1934
  }
1464
1935
 
1465
1936
  main().catch((e) => fail(e?.stack || e?.message || String(e)));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-metamynd-agent",
3
- "version": "0.6.2",
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, and drops a runnable example. --harness scaffolds a free, local, zero-network governance harness instead.",
3
+ "version": "0.7.1",
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 (the real enforcement boundary). --harness scaffolds a free, local, zero-network governance harness instead.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-metamynd-agent": "index.mjs"