create-metamynd-agent 0.13.2 → 0.14.2
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 +9 -2
- package/index.mjs +104 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -315,8 +315,15 @@ with the default hosted flow (login + provision):
|
|
|
315
315
|
- **No spend constraint anywhere.** The mandate carries none and the default SOP has no amount rule
|
|
316
316
|
(an `amount-unknown` block would refuse every action that carries no amount). Hosted: the
|
|
317
317
|
provisioning call sends no `currency` / `maxAmount` / `perTxnMax` at all — the backend treats their
|
|
318
|
-
absence as a non-financial mandate.
|
|
319
|
-
|
|
318
|
+
absence as a non-financial mandate.
|
|
319
|
+
- **Rule packs (hosted, since 0.14.0).** The CLI checks a `rulePack` against the platform's catalog
|
|
320
|
+
before provisioning. A pack that needs no spend limits (`comms`, `compliance`, the oversight packs, …)
|
|
321
|
+
is applied to a non-financial agent as-is, and its rules are printed and sent back as
|
|
322
|
+
`reviewedMoleculeIds`, so the platform provisions exactly those rules or nothing. A pack built from
|
|
323
|
+
spend limits (`spend-basic`, …) cannot govern a non-financial agent, and an unknown pack name is
|
|
324
|
+
refused: either stops the CLI before anything is provisioned, naming what would work. Against a
|
|
325
|
+
platform version that predates per-pack spend information, a non-financial agent's pack is still
|
|
326
|
+
dropped (and the CLI says so). `--harness` does not apply packs; list your rules under `rules`.
|
|
320
327
|
- **The demo is derived from your rules.** `npm start` runs one request that satisfies every rule,
|
|
321
328
|
then one per rule that should trip it, then an action nobody delegated. Each step states what it
|
|
322
329
|
expects and flags any surprise, so changing your rules visibly changes the outcome. Rules the demo
|
package/index.mjs
CHANGED
|
@@ -58,7 +58,8 @@ const GUARD_PKG = '@metamynd/agentsafe-guard';
|
|
|
58
58
|
// passes a request's jurisdiction that way, since the gate no longer reads one from the unsigned context.
|
|
59
59
|
// 0.17.0 signs the agent's context (envelopeSignature, MAGP 8.3.13) by default; the scaffolded agent gets it with no template
|
|
60
60
|
// change (signContext: false opts out). A daemon-custody agent needs agentsafe-signer >= 0.19.0 for its no-amount requests.
|
|
61
|
-
|
|
61
|
+
// 0.18.0 exports keepAliveFetch. No template change.
|
|
62
|
+
const GUARD_VERSION = '^0.18.0';
|
|
62
63
|
/** The harness entry point's config load, shared by both harness templates: a fresh clone has no
|
|
63
64
|
* agent.metamynd.json (it is gitignored), so say what to do instead of a bare ENOENT (BR-004). */
|
|
64
65
|
function harnessConfigLoad() {
|
|
@@ -117,7 +118,9 @@ const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
|
|
|
117
118
|
// the scaffolded agent now signs jurisdiction, which an older gateway would refuse SIGNATURE_INVALID.
|
|
118
119
|
// 0.17.0: verifies the agent's context signature (envelopeSignature) before judging its itinerary, and a
|
|
119
120
|
// requireContextSignature option. No template change (the scaffolded agent does not sign its context by default).
|
|
120
|
-
|
|
121
|
+
// 0.18.0: an opt-in bundleCache (push-invalidated by the issuer's events stream) and keepAliveFetch exported. No
|
|
122
|
+
// template change (the scaffolded gateways don't enable the cache).
|
|
123
|
+
const MCP_GUARD_VERSION = '^0.18.0';
|
|
121
124
|
const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
|
|
122
125
|
// 0.2.0 fixes a confused-deputy gap (payload not bound to the signed request) — the CLI must
|
|
123
126
|
// never scaffold a range that could resolve below it.
|
|
@@ -137,7 +140,8 @@ const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
|
|
|
137
140
|
// No template change.
|
|
138
141
|
// 0.16.0: the caller is answered first and the claimed hold is settled after (settleInBackground), retried on a
|
|
139
142
|
// transient failure. Template change: each scaffolded gateway drains settlements on SIGTERM/SIGINT (DRAIN_ON_SHUTDOWN).
|
|
140
|
-
|
|
143
|
+
// 0.17.0: depends on agentsafe-mcp-guard ^0.18.0 and exports keepAliveFetch. No template change.
|
|
144
|
+
const GATEWAY_VERSION = '^0.17.0';
|
|
141
145
|
|
|
142
146
|
/** Appended to every scaffolded gateway server: give hold settlements still running a bounded moment on shutdown. */
|
|
143
147
|
const DRAIN_ON_SHUTDOWN = `
|
|
@@ -307,8 +311,29 @@ function askHidden(query) {
|
|
|
307
311
|
});
|
|
308
312
|
}
|
|
309
313
|
|
|
314
|
+
/** Thrown by fail() to end the run; carries no message of its own (fail() already printed it). */
|
|
315
|
+
class CliExit extends Error {
|
|
316
|
+
constructor(code) {
|
|
317
|
+
super('create-metamynd-agent exit');
|
|
318
|
+
this.exitCode = code;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
310
322
|
function fail(msg) {
|
|
311
323
|
console.error(`\n${c.red('✖')} ${msg}\n`);
|
|
324
|
+
// Not process.exit(): after a fetch(), its keep-alive socket may still be closing, and exiting then
|
|
325
|
+
// aborts Node 24 on Windows (libuv "!(handle->flags & UV_HANDLE_CLOSING)", exit 0xC0000409) instead of
|
|
326
|
+
// exiting 1. Unwind instead and let the event loop drain; the unref'd timer only fires if something
|
|
327
|
+
// else would keep the process alive.
|
|
328
|
+
process.exitCode = 1;
|
|
329
|
+
setTimeout(() => process.exit(1), 5000).unref();
|
|
330
|
+
throw new CliExit(1);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** The run's last-resort handler: a CliExit already said why; anything else is a real crash. */
|
|
334
|
+
function onCliError(e) {
|
|
335
|
+
if (e instanceof CliExit) return;
|
|
336
|
+
console.error(`\n${c.red('✖')} ${e?.stack || e?.message || String(e)}\n`);
|
|
312
337
|
process.exit(1);
|
|
313
338
|
}
|
|
314
339
|
|
|
@@ -864,6 +889,65 @@ async function apiPost(base, path, body, token) {
|
|
|
864
889
|
return json;
|
|
865
890
|
}
|
|
866
891
|
|
|
892
|
+
/** GET a public, read-only endpoint; null on ANY failure (unreachable, non-2xx, non-JSON) — never fatal. */
|
|
893
|
+
async function softGet(base, path) {
|
|
894
|
+
try {
|
|
895
|
+
const res = await fetch(`${base}${path}`);
|
|
896
|
+
if (!res.ok) return { status: res.status, json: await res.json().catch(() => null) };
|
|
897
|
+
return { status: res.status, json: await res.json() };
|
|
898
|
+
} catch {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Decide what to do with a config file's `rulePack` BEFORE provisioning, from the platform's own catalog
|
|
905
|
+
* (GET /onboarding/rule-packs) and preview (GET /onboarding/rule-packs/:key/preview):
|
|
906
|
+
* - an unknown pack fails here, naming the packs the platform offers — the platform refuses it too
|
|
907
|
+
* (400 UNKNOWN_RULE_PACK; an older platform silently applied its default rules instead);
|
|
908
|
+
* - a pack built from spend limits cannot govern a non-financial agent: fail and say what would work;
|
|
909
|
+
* - any other pack is applied as-is, and its rules are sent back as `reviewedMoleculeIds`, so the
|
|
910
|
+
* platform provisions exactly the rules shown here or nothing at all.
|
|
911
|
+
* A platform whose catalog does not say which packs need spend limits (it predates FA-001) only applies a
|
|
912
|
+
* pack when spend limits are supplied, so for a non-financial agent the pack is dropped and that is said.
|
|
913
|
+
* Returns { drop: true } or { molecules, reviewedMoleculeIds? }.
|
|
914
|
+
*/
|
|
915
|
+
async function resolveHostedRulePack(base, key, { financial, perTxnMax, maxAmount, currency }) {
|
|
916
|
+
const catalogRes = await softGet(base, '/onboarding/rule-packs');
|
|
917
|
+
const catalog = Array.isArray(catalogRes?.json?.data) ? catalogRes.json.data : null;
|
|
918
|
+
const legacyDrop = () => {
|
|
919
|
+
console.log(` ${c.yellow('!')} the rule pack "${key}" cannot be applied to a non-financial agent on this platform version (it applies packs only when spend limits are supplied) — the default (an amount-free high-risk review) applies. List your rules under "rules" to set your own.`);
|
|
920
|
+
return { drop: true };
|
|
921
|
+
};
|
|
922
|
+
if (!catalog) {
|
|
923
|
+
// Catalog unavailable: send the pack for a financial agent (the platform validates it), keep the old drop otherwise.
|
|
924
|
+
return financial ? { molecules: null } : legacyDrop();
|
|
925
|
+
}
|
|
926
|
+
const entry = catalog.find((p) => p?.key === key);
|
|
927
|
+
if (!entry) {
|
|
928
|
+
fail(`Unknown rule pack "${key}". This platform offers: ${catalog.map((p) => p.key).join(', ')}. Fix "rulePack" in the config file, or list your rules under "rules".`);
|
|
929
|
+
}
|
|
930
|
+
const knowsSpend = catalog.some((p) => typeof p?.requiresSpend === 'boolean');
|
|
931
|
+
if (!financial) {
|
|
932
|
+
if (!knowsSpend) return legacyDrop();
|
|
933
|
+
if (entry.requiresSpend) {
|
|
934
|
+
const usable = catalog.filter((p) => p.requiresSpend === false).map((p) => p.key);
|
|
935
|
+
fail(`The rule pack "${key}" is built from spend limits, and this agent has none (non-financial). Choose a pack that needs no spend limits (${usable.join(', ')}), pass --financial with spend limits, or list your rules under "rules".`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
const query = financial
|
|
939
|
+
? `perTxnMax=${encodeURIComponent(perTxnMax)}&maxAmount=${encodeURIComponent(maxAmount)}¤cy=${encodeURIComponent(currency)}`
|
|
940
|
+
: 'financial=false';
|
|
941
|
+
const preview = await softGet(base, `/onboarding/rule-packs/${encodeURIComponent(key)}/preview?${query}`);
|
|
942
|
+
const molecules = Array.isArray(preview?.json?.data?.molecules) ? preview.json.data.molecules : null;
|
|
943
|
+
if (!molecules) {
|
|
944
|
+
if (!financial) fail(`Could not read the rules of the rule pack "${key}" from ${base} (HTTP ${preview?.status ?? 'unreachable'}${preview?.json?.message ? ': ' + preview.json.message : ''}) — nothing was provisioned.`);
|
|
945
|
+
return { molecules: null };
|
|
946
|
+
}
|
|
947
|
+
console.log(` ${c.green('✓')} rule pack "${entry.label ?? key}": ${molecules.map((m) => m.name ?? m.id).join(', ')}`);
|
|
948
|
+
return { molecules, ...(knowsSpend ? { reviewedMoleculeIds: molecules.map((m) => m.id) } : {}) };
|
|
949
|
+
}
|
|
950
|
+
|
|
867
951
|
/**
|
|
868
952
|
* POST /policy/counterparties for the gateway's own DID — best-effort, unlike apiPost above: a
|
|
869
953
|
* hosted financial gateway works fine on testnet with no registration at all (open by default), so
|
|
@@ -3837,13 +3921,18 @@ async function main() {
|
|
|
3837
3921
|
// (a per-transaction cap + high-risk review), same as before --config existed.
|
|
3838
3922
|
const sopFields = configFileSopFields(fileConfig);
|
|
3839
3923
|
if (sopFields.sop) console.log(` ${c.green('✓')} compiled ${sopFields.sop.documentJson.molecules.length} rule(s) from the config file`);
|
|
3840
|
-
console.log(c.dim(`\n → provisioning "${name}" (identity + mandate + SOP + Standards) …`));
|
|
3841
3924
|
// A non-financial agent sends NO spend fields at all (currency/maxAmount/perTxnMax omitted together
|
|
3842
|
-
// is how the backend recognises one).
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3925
|
+
// is how the backend recognises one). Whether a rule pack can govern it is the platform's answer.
|
|
3926
|
+
let packMolecules = null;
|
|
3927
|
+
if (sopFields.rulePack) {
|
|
3928
|
+
const pack = await resolveHostedRulePack(base, sopFields.rulePack, { financial, perTxnMax, maxAmount, currency });
|
|
3929
|
+
if (pack.drop) delete sopFields.rulePack;
|
|
3930
|
+
else {
|
|
3931
|
+
packMolecules = pack.molecules;
|
|
3932
|
+
if (pack.reviewedMoleculeIds) sopFields.reviewedMoleculeIds = pack.reviewedMoleculeIds;
|
|
3933
|
+
}
|
|
3846
3934
|
}
|
|
3935
|
+
console.log(c.dim(`\n → provisioning "${name}" (identity + mandate + SOP + Standards) …`));
|
|
3847
3936
|
const body = {
|
|
3848
3937
|
name, scope,
|
|
3849
3938
|
// Explicit, not left to the backend's own eligibility-based default (mainnet for a KYB-verified
|
|
@@ -3925,7 +4014,7 @@ async function main() {
|
|
|
3925
4014
|
// 4. Scaffold + next steps
|
|
3926
4015
|
// The demo comes from the rules actually provisioned: the caller's own, else the backend's default
|
|
3927
4016
|
// for a non-financial agent (the same amount-free review harnessDefaultSopNeutral describes).
|
|
3928
|
-
const demo = financial ? null : buildPolicyCases(sopFields.sop?.documentJson?.molecules ?? harnessDefaultSopNeutral().molecules);
|
|
4017
|
+
const demo = financial ? null : buildPolicyCases(sopFields.sop?.documentJson?.molecules ?? packMolecules ?? harnessDefaultSopNeutral().molecules);
|
|
3929
4018
|
if (demo) {
|
|
3930
4019
|
console.log(` ${c.green('✓')} derived ${demo.cases.length} demo case(s) from your rules`);
|
|
3931
4020
|
for (const n of demo.notDemonstrated) console.log(` ${c.yellow('!')} not staged in the demo: ${n.rule} ${c.dim('— ' + n.why + '; still enforced')}`);
|
|
@@ -3940,4 +4029,9 @@ export {
|
|
|
3940
4029
|
generateAgentKeypair, harnessAgentDid, harnessMandate, harnessRulesFile, harnessDefaultSopNeutral,
|
|
3941
4030
|
scaffoldProject, defaultNeutralDemo, exampleIndexNeutral, exampleReadmeNeutral, gatewayServerFileNeutral, gatewayReadmeNeutral,
|
|
3942
4031
|
};
|
|
3943
|
-
if (!process.env.CREATE_METAMYND_AGENT_NO_MAIN)
|
|
4032
|
+
if (!process.env.CREATE_METAMYND_AGENT_NO_MAIN) {
|
|
4033
|
+
// fail() can also be reached from a callback outside main()'s promise chain (readline, child process).
|
|
4034
|
+
process.on('uncaughtException', onCliError);
|
|
4035
|
+
process.on('unhandledRejection', onCliError);
|
|
4036
|
+
main().catch(onCliError);
|
|
4037
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-metamynd-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.2",
|
|
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": {
|