create-metamynd-agent 0.8.0 → 0.10.3

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 +24 -4
  2. package/index.mjs +171 -18
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -236,6 +236,8 @@ METAMYND_PASSWORD='…' npx create-metamynd-agent --yes …
236
236
  | `--merchants <a,b>` | — | any |
237
237
  | `--byok` | — | generate the keypair locally, provision + prove control |
238
238
  | `--public-key <hex>` | — | BYOK with a key you already hold (you prove control yourself) |
239
+ | `--daemon-socket <p>` | — | `--byok` via an already-running agentsafe-signer daemon instead of locally (needs `--daemon-admin-socket` too) |
240
+ | `--daemon-admin-socket <p>` | — | that daemon's admin socket, for `generate-key` |
239
241
  | `--out <dir>` | — | `./<agent-slug>` |
240
242
  | `--yes`, `-y` | — | non-interactive |
241
243
 
@@ -284,10 +286,28 @@ npx create-metamynd-agent --byok --email you@example.com --name "Support Bot"
284
286
  ```
285
287
 
286
288
  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. The
288
- generated private key is written into `agent.metamynd.json` (gitignored). Pass `--public-key <hex>`
289
- instead to register a key you already hold elsewhere then you complete `verify-key` yourself (the CLI
290
- prints the challenge + endpoint).
289
+ proves control (signs the one-time challenge → `verify-key`). MetaMynd never sees the private key. By
290
+ default the generated private key is written into `agent.metamynd.json` (gitignored) the same
291
+ process this CLI runs in holds it, at least briefly. Pass `--public-key <hex>` instead to register a
292
+ key you already hold elsewhere — then you complete `verify-key` yourself (the CLI prints the challenge
293
+ + endpoint).
294
+
295
+ ### Keeping the key out of this process entirely (`--daemon-socket`)
296
+
297
+ If you already have an [agentsafe-signer](../agentsafe-signer/README.md) daemon running for this
298
+ agent (`agentsafe-signer start --admin`, per its own install guide), point `--byok` at it instead:
299
+
300
+ ```bash
301
+ npx create-metamynd-agent --byok --daemon-socket ./.agentsafe-signer/signer.sock \
302
+ --daemon-admin-socket ./.agentsafe-signer/signer-admin.sock \
303
+ --email you@example.com --name "Support Bot"
304
+ ```
305
+
306
+ The daemon generates the key and signs the `verify-key` challenge itself — the private key never
307
+ enters this CLI's process at all, not even briefly. `agent.metamynd.json` gets `keyProvider: 'daemon'`
308
+ + `daemonSocketPath` instead of a plaintext key (see `@metamynd/agentsafe-guard`'s `key-providers.mjs`
309
+ for how the scaffolded guard resolves that). Requires both flags together, and only applies when no
310
+ `--public-key` is given (an external key has nothing for the daemon to generate).
291
311
 
292
312
  ## Delegated issuance (`--request` / `--claim`)
293
313
 
package/index.mjs CHANGED
@@ -18,6 +18,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from
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
@@ -29,13 +30,39 @@ const GUARD_PKG = '@metamynd/agentsafe-guard';
29
30
  // 0.7.0 adds the opt-in `signContext` envelope signature (Tier 1 context-claim binding) —
30
31
  // no scaffolded behavior changes (off by default), but the floor must still cover the real
31
32
  // current version regardless, per this repo's standing internal-pin invariant.
32
- const GUARD_VERSION = '^0.7.0';
33
+ // 0.8.0 adds an optional `currency` scope to the amount-over/cumulative-over atoms
34
+ // (harnessDefaultSop, below, now sets it) — a guard below this version can't evaluate that
35
+ // field, so a scaffolded currency-scoped cap would silently never fire on a currency mismatch.
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';
33
51
  // The default hosted scaffold's SECOND process — the tool gateway (see scaffoldProject).
34
52
  const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
35
53
  // 0.2.0 adds requireAuthorization (closes replay + cumulative spend) — this scaffold sets that
36
54
  // option, so a range that could resolve below 0.2.0 would silently scaffold a no-op.
37
55
  // 0.3.0 adds the same amount-unknown atom as the guard, above — same reasoning, same miss.
38
- const MCP_GUARD_VERSION = '^0.3.0';
56
+ // 0.4.0 adds the same amount-over/cumulative-over `currency` scope as the guard, above —
57
+ // same reasoning, same miss.
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';
39
66
  const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
40
67
  // 0.2.0 fixes a confused-deputy gap (payload not bound to the signed request) — the CLI must
41
68
  // never scaffold a range that could resolve below it.
@@ -113,6 +140,13 @@ ${c.b('Options')}
113
140
  --byok Bring-your-own-key: generate the keypair locally, provision + prove control
114
141
  (MetaMynd never sees the private key). Overridden by --public-key.
115
142
  --public-key <hex> BYOK with a key you already hold (SPKI/raw hex); you prove control yourself
143
+ --daemon-socket <p> --byok only: use an already-running agentsafe-signer daemon (started
144
+ separately, e.g. \`agentsafe-signer start --admin\`) to generate the key and
145
+ prove control instead — the private key never enters this CLI's process at
146
+ all, and agent.metamynd.json gets keyProvider:'daemon' instead of a
147
+ plaintext key. Requires --daemon-admin-socket too. See README#byok.
148
+ --daemon-admin-socket <p> The same daemon's admin socket (for generate-key) — required with
149
+ --daemon-socket.
116
150
  --out <dir> Output project directory (default ./<agent-slug>)
117
151
  --no-gateway Hosted flow only: skip the separate tool-gateway process (see
118
152
  README#separate-tool-gateway-default) and scaffold the old
@@ -305,6 +339,72 @@ function signChallengeHex(privateKeyHex, challenge) {
305
339
  return crypto.sign(null, Buffer.from(challenge, 'utf8'), key).toString('hex');
306
340
  }
307
341
 
342
+ // ---------- BYOK via an already-running agentsafe-signer daemon (--daemon-socket) ----------
343
+ // Opt-in alternative to generateAgentKeypair() above: instead of generating the keypair in THIS
344
+ // process and writing it in plaintext into agent.metamynd.json, ask an already-running
345
+ // agentsafe-signer daemon (docs/design/agent-key-custody-local-signer-daemon-plan.md — started
346
+ // separately, e.g. `agentsafe-signer start --admin`) to generate the key and sign the
347
+ // proof-of-possession challenge. The private key never enters this process at all, and the
348
+ // scaffolded config gets `keyProvider: 'daemon'` instead of a plaintext `agentKey` — see
349
+ // agentsafe-guard/key-providers.mjs's resolveKeyProvider(), which reads that field exactly.
350
+ //
351
+ // Vendored rather than depending on @metamynd/agentsafe-signer or @metamynd/agentsafe-guard for
352
+ // it — this CLI is intentionally zero-dependency, and this is the SAME small, self-contained
353
+ // reimplementation of the daemon's local JSON-over-socket protocol that agentsafe-guard/
354
+ // key-providers.mjs and agentsafe-mcp-guard/key-providers.mjs already each carry their own copy
355
+ // of, rather than a fourth package depending on a signer package built for a persistent service,
356
+ // not a one-shot scaffolding command.
357
+ function toPlatformSocketPath(logicalPath) {
358
+ if (process.platform !== 'win32') return logicalPath;
359
+ const name = crypto.createHash('sha256').update(resolve(logicalPath)).digest('hex').slice(0, 32);
360
+ return `\\\\.\\pipe\\agentsafe-signer-${name}`;
361
+ }
362
+
363
+ function daemonRequest(socketPath, op, params, { connectTimeoutMs = 5000 } = {}) {
364
+ return new Promise((resolve_, reject) => {
365
+ const deadline = Date.now() + connectTimeoutMs;
366
+ let settled = false;
367
+ const overallTimer = setTimeout(() => {
368
+ settled = true;
369
+ reject(Object.assign(new Error(`agentsafe-signer daemon unreachable at ${socketPath}: timed out after ${connectTimeoutMs}ms`), { code: 'DAEMON_UNREACHABLE' }));
370
+ }, connectTimeoutMs);
371
+ function attempt() {
372
+ if (settled) return;
373
+ const sock = net.connect(toPlatformSocketPath(socketPath));
374
+ const requestId = crypto.randomUUID();
375
+ let buf = '';
376
+ const cleanup = () => sock.destroy();
377
+ sock.once('error', (err) => {
378
+ cleanup();
379
+ if (settled) return;
380
+ if (err.code === 'ENOENT' && Date.now() < deadline) { setTimeout(attempt, 20); return; }
381
+ settled = true;
382
+ clearTimeout(overallTimer);
383
+ reject(Object.assign(new Error(`agentsafe-signer daemon unreachable at ${socketPath}: ${err.message}`), { code: 'DAEMON_UNREACHABLE' }));
384
+ });
385
+ sock.once('connect', () => {
386
+ if (settled) return;
387
+ sock.write(JSON.stringify({ protocolVersion: 1, requestId, op, params }) + '\n');
388
+ });
389
+ sock.on('data', (chunk) => {
390
+ if (settled) return;
391
+ buf += chunk.toString('utf8');
392
+ const idx = buf.indexOf('\n');
393
+ if (idx === -1) return;
394
+ let res;
395
+ try { res = JSON.parse(buf.slice(0, idx)); }
396
+ catch (err) { cleanup(); settled = true; clearTimeout(overallTimer); reject(err); return; }
397
+ cleanup();
398
+ settled = true;
399
+ clearTimeout(overallTimer);
400
+ if (res.ok) resolve_(res.result);
401
+ else reject(Object.assign(new Error(res.error?.message || res.error?.code || 'daemon rejected request'), { code: res.error?.code }));
402
+ });
403
+ }
404
+ attempt();
405
+ });
406
+ }
407
+
308
408
  // ---------- API ----------
309
409
  async function apiPost(base, path, body, token) {
310
410
  let res;
@@ -517,7 +617,7 @@ const GATEWAY = process.env.GATEWAY_URL || 'http://localhost:${gatewayPort}';
517
617
  // --- the gateway atomically claim single-use execution, closing replay + cumulative spend, not
518
618
  // --- just re-checking policy. See ./gateway/README.md.
519
619
  async function bookFlightViaGateway(args, decision) {
520
- const signed = guard.buildSignedRequest({
620
+ const signed = await guard.buildSignedRequest({
521
621
  action: '${scope}',
522
622
  amount: args.amount,
523
623
  currency: 'USD',
@@ -693,7 +793,13 @@ function examplePackageJson(slug) {
693
793
  ) + '\n';
694
794
  }
695
795
 
696
- function exampleReadme(slug, scope, withGateway, gatewayPort) {
796
+ function exampleReadme(slug, scope, withGateway, gatewayPort, daemonKey = false) {
797
+ const configFileLine = daemonKey
798
+ ? `- \`agent.metamynd.json\` — your portable guard config (identity, mandate scope \`${scope}\`, issuer keys).
799
+ **Holds no secret key.** Signing goes through your already-running agentsafe-signer daemon
800
+ (\`daemonSocketPath\`) instead — see \`docs/integration/INSTALL-AGENTSAFE-SIGNER.md\`.`
801
+ : `- \`agent.metamynd.json\` — your portable guard config (identity, mandate scope \`${scope}\`, issuer keys).
802
+ **Contains the agent's secret key — never commit it.** It is already in \`.gitignore\`.`;
697
803
  const gatewaySection = withGateway
698
804
  ? `## Run
699
805
 
@@ -716,8 +822,7 @@ an ESCALATE (high risk). The BLOCK and ESCALATE never reach the gateway at all
716
822
 
717
823
  ## Files
718
824
 
719
- - \`agent.metamynd.json\` — your portable guard config (identity, mandate scope \`${scope}\`, issuer keys).
720
- **Contains the agent's secret key — never commit it.** It is already in \`.gitignore\`.
825
+ ${configFileLine}
721
826
  - \`index.mjs\` — signs each request and calls \`./gateway\` for it; \`guard.guardTool()\` here is a
722
827
  fast local pre-check, not the enforcement boundary.
723
828
  - \`gateway/\` — a **separate process**. It holds the real tool and independently re-verifies every
@@ -738,8 +843,7 @@ You should see an ALLOW, a BLOCK (over the per-transaction cap), and an ESCALATE
738
843
 
739
844
  ## Files
740
845
 
741
- - \`agent.metamynd.json\` — your portable guard config (identity, mandate scope \`${scope}\`, issuer keys).
742
- **Contains the agent's secret key — never commit it.** It is already in \`.gitignore\`.
846
+ ${configFileLine}
743
847
  - \`index.mjs\` — wraps a tool with \`guard.guardTool(...)\`; the tool only runs when the gate allows.
744
848
 
745
849
  ## What this is not
@@ -1050,7 +1154,7 @@ function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox, with
1050
1154
  writeFileSafe(outDir, 'index.mjs', withGateway ? exampleIndex(scope, perTxnMax, gatewayPort) : exampleIndexNoGateway(scope, perTxnMax), force);
1051
1155
  writeFileSafe(outDir, 'package.json', examplePackageJson(slug), force);
1052
1156
  writeFileSafe(outDir, '.gitignore', gitignore(), force);
1053
- writeFileSafe(outDir, 'README.md', exampleReadme(slug, scope, withGateway, gatewayPort), force);
1157
+ writeFileSafe(outDir, 'README.md', exampleReadme(slug, scope, withGateway, gatewayPort, config.keyProvider === 'daemon'), force);
1054
1158
 
1055
1159
  if (withGateway) {
1056
1160
  const apiBase = config.apiBase ?? config.api ?? DEFAULT_API;
@@ -1067,6 +1171,8 @@ function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox, with
1067
1171
  console.log(`\n${c.green(c.b(' ✓ Done.'))} Your governed agent is ready.\n`);
1068
1172
  if (sandbox) {
1069
1173
  console.log(` ${c.dim('Shared sandbox agent — for trying MetaMynd only. Provision your own (drop --sandbox) for anything real.')}\n`);
1174
+ } else if (config.keyProvider === 'daemon') {
1175
+ 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`);
1070
1176
  } else if (config.agentKey) {
1071
1177
  console.log(` ${c.yellow('⚠ agent.metamynd.json holds the agent secret key')} — it is gitignored; never commit it.\n`);
1072
1178
  }
@@ -1128,12 +1234,21 @@ async function runSandbox(args) {
1128
1234
  * entirely client-side with no schema boundary in front of it, so nothing stops a caller from
1129
1235
  * passing amount: "5000" (a string) or omitting amount entirely — `amount-over` silently does
1130
1236
  * not fire on either (`typeof c.amount === 'number'` is false), so the cap passes untested,
1131
- * not safe. Ordering amount-unknown first blocks that instead of letting it through. */
1132
- function harnessDefaultSop(perTxnMax) {
1237
+ * not safe. Ordering amount-unknown first blocks that instead of letting it through.
1238
+ *
1239
+ * The per-transaction cap is scoped to `currency` (atom-catalog.ts's `amount-over` currency
1240
+ * config), mirroring the hosted default exactly — see mandate-eval.ts for why an
1241
+ * unscoped numeric cap can be cleared just by naming a different currency. The atom's
1242
+ * `currency` config is declared `type: 'string[]'` (always an array, e.g. `['USD']`, never
1243
+ * a bare string — see atom-catalog.ts's own field doc); `harnessMandate` below correctly
1244
+ * passes the bare string straight through to the ODRL `unit` field, which is a DIFFERENT,
1245
+ * genuinely string-or-array field — the two must not be confused (see the backend's own
1246
+ * currencyScopeFor/currencyUnitFor split in currency-unit.ts for the same distinction). */
1247
+ function harnessDefaultSop(perTxnMax, currency) {
1133
1248
  return {
1134
1249
  molecules: [
1135
1250
  { id: 'amount-known', name: 'Amount must be determinable', combinator: 'any', atoms: [{ id: 'a0', predicate: 'amount-unknown' }], decision: 'block', reasonCode: 'AMOUNT_NOT_DETERMINABLE' },
1136
- { id: 'cap', name: 'Per-transaction cap', combinator: 'any', atoms: [{ id: 'a1', predicate: 'amount-over', config: { limit: perTxnMax } }], decision: 'block', reasonCode: 'SOP_SPEND_CAP' },
1251
+ { id: 'cap', name: 'Per-transaction cap', combinator: 'any', atoms: [{ id: 'a1', predicate: 'amount-over', config: { limit: perTxnMax, currency: [currency] } }], decision: 'block', reasonCode: 'SOP_SPEND_CAP' },
1137
1252
  { id: 'review', name: 'High-risk review', combinator: 'any', atoms: [{ id: 'a2', predicate: 'risk-at-or-above', config: { level: 'high' } }], decision: 'escalate', reasonCode: 'RISK_REVIEW' },
1138
1253
  ],
1139
1254
  };
@@ -1716,7 +1831,7 @@ const GATEWAY = process.env.HARNESS_GATEWAY_URL || 'http://localhost:${gatewayPo
1716
1831
  // issuer): it builds and signs the same canonical message a real gate would verify, entirely
1717
1832
  // offline, using this agent's own did:key — the gateway verifies that signature for itself.
1718
1833
  async function callGateway(path, action, args) {
1719
- const signed = guard.buildSignedRequest({ action, amount: args.amount, currency: 'USD', merchant: args.merchant, context: { tool: '${scope}', riskLevel: args.riskLevel ?? 'low' } });
1834
+ const signed = await guard.buildSignedRequest({ action, amount: args.amount, currency: 'USD', merchant: args.merchant, context: { tool: '${scope}', riskLevel: args.riskLevel ?? 'low' } });
1720
1835
  const res = await fetch(GATEWAY + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ signed, args }) });
1721
1836
  const body = await res.json().catch(() => null);
1722
1837
  if (!res.ok) {
@@ -1988,7 +2103,7 @@ async function runHarness(args) {
1988
2103
  console.log(` ${c.green('✓')} local agent ${c.b(agentDid)}`);
1989
2104
 
1990
2105
  const sopFields = configFileSopFields(fileConfig);
1991
- const sopDocument = sopFields.sop ? sopFields.sop.documentJson : harnessDefaultSop(perTxnMax);
2106
+ const sopDocument = sopFields.sop ? sopFields.sop.documentJson : harnessDefaultSop(perTxnMax, currency);
1992
2107
  if (sopFields.sop) console.log(` ${c.green('✓')} compiled ${sopDocument.molecules.length} rule(s) from the config file`);
1993
2108
  const mandate = harnessMandate({ scope, currency, maxAmount, perTxnMax, merchants });
1994
2109
 
@@ -2210,11 +2325,35 @@ async function main() {
2210
2325
  );
2211
2326
  const merchants = String(merchantsRaw).split(',').map((s) => s.trim()).filter(Boolean);
2212
2327
 
2213
- // BYOK: --byok generates a keypair on THIS machine (MetaMynd never sees the private key). An
2214
- // explicit --public-key means the caller holds the key elsewhere and will prove it themselves.
2328
+ // BYOK: --byok generates a keypair on THIS machine (MetaMynd never sees the private key)
2329
+ // either locally in this process (default) or, opt-in, via an already-running agentsafe-signer
2330
+ // daemon (--daemon-socket + --daemon-admin-socket, see their own help text) so the private key
2331
+ // never enters this process at all. An explicit --public-key means the caller holds the key
2332
+ // elsewhere and will prove it themselves — daemon flags are meaningless with it.
2215
2333
  let publicKey = typeof args['public-key'] === 'string' ? args['public-key'] : undefined;
2334
+ const daemonSocket = typeof args['daemon-socket'] === 'string' ? args['daemon-socket'] : undefined;
2335
+ const daemonAdminSocket = typeof args['daemon-admin-socket'] === 'string' ? args['daemon-admin-socket'] : undefined;
2336
+ if (Boolean(daemonSocket) !== Boolean(daemonAdminSocket)) {
2337
+ rl?.close();
2338
+ fail('--daemon-socket and --daemon-admin-socket must be used together.');
2339
+ }
2340
+ if (daemonSocket && !args.byok) {
2341
+ rl?.close();
2342
+ fail('--daemon-socket requires --byok.');
2343
+ }
2344
+ if (daemonSocket && publicKey) {
2345
+ rl?.close();
2346
+ fail('--daemon-socket generates its own key — pass --byok alone, not --public-key.');
2347
+ }
2216
2348
  let generatedKey = null;
2217
- if (args.byok && !publicKey) {
2349
+ let daemonPublicKeyHex = null;
2350
+ if (args.byok && !publicKey && daemonSocket) {
2351
+ console.log(c.dim(' → asking the agentsafe-signer daemon to generate a key …'));
2352
+ const { publicKeyHex } = await daemonRequest(daemonAdminSocket, 'generate-key', { allowRekey: false });
2353
+ daemonPublicKeyHex = publicKeyHex;
2354
+ publicKey = publicKeyHex;
2355
+ console.log(` ${c.green('✓')} generated an Ed25519 keypair via the signer daemon ${c.dim('(the private key never left it)')}`);
2356
+ } else if (args.byok && !publicKey) {
2218
2357
  generatedKey = generateAgentKeypair();
2219
2358
  publicKey = generatedKey.publicKeyHex;
2220
2359
  console.log(` ${c.green('✓')} generated an Ed25519 keypair locally ${c.dim('(private key stays on this machine)')}`);
@@ -2239,7 +2378,21 @@ async function main() {
2239
2378
  if (config.standards?.length) console.log(` ${c.green('✓')} enforced Standards: ${config.standards.join(', ')}`);
2240
2379
 
2241
2380
  // 3b. BYOK: prove control of the key (verify-key), else the gate blocks with AGENT_KEY_UNVERIFIED.
2242
- if (generatedKey) {
2381
+ if (daemonPublicKeyHex) {
2382
+ // The daemon holds the private key — it never entered this process. Point the scaffolded
2383
+ // guard at the daemon instead of embedding a plaintext key (agentsafe-guard/key-providers.mjs's
2384
+ // resolveKeyProvider() reads these two fields and never looks for `agentKey` when present).
2385
+ config.keyProvider = 'daemon';
2386
+ config.daemonSocketPath = daemonSocket;
2387
+ if (config.challenge) {
2388
+ console.log(c.dim(' → proving key control via the daemon (verify-key) …'));
2389
+ const { signature } = await daemonRequest(daemonSocket, 'sign-key-control-challenge', { challenge: config.challenge });
2390
+ await apiPost(base, `/agent-identity/${encodeURIComponent(config.identityId)}/verify-key`, { signature }, token);
2391
+ config.keyVerified = true;
2392
+ delete config.challenge; // one-time; consumed
2393
+ console.log(` ${c.green('✓')} key verified — MetaMynd never saw your private key, and neither did this CLI`);
2394
+ }
2395
+ } else if (generatedKey) {
2243
2396
  // We hold the private key — inject it into the config so the scaffolded guard can sign, and
2244
2397
  // prove possession by signing the issued challenge.
2245
2398
  config.agentKey = generatedKey.privateKeyHex;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "create-metamynd-agent",
3
- "version": "0.8.0",
3
+ "version": "0.10.3",
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",