create-metamynd-agent 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.mjs +47 -15
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -12,13 +12,13 @@
12
12
  // npx create-metamynd-agent
13
13
  // npx create-metamynd-agent --api http://localhost:9926/api/v1 --email you@x.com \
14
14
  // --name "Support Bot" --scope flight-purchase --per-txn-max 500 --out ./support-bot --yes
15
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
15
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
16
16
  import { join, resolve } from 'node:path';
17
17
  import readline from 'node:readline';
18
18
  import crypto from 'node:crypto';
19
19
 
20
20
  const GUARD_PKG = '@metamynd/agentsafe-guard';
21
- const GUARD_VERSION = '^0.1.0';
21
+ const GUARD_VERSION = '^0.3.0';
22
22
  const DEFAULT_API = 'https://metamynd.ai/api/v1';
23
23
 
24
24
  // ---------- tiny ANSI ----------
@@ -39,6 +39,8 @@ function parseArgs(argv) {
39
39
  if (a === '-h' || a === '--help') { out.help = true; continue; }
40
40
  if (a === '-v' || a === '--version') { out.version = true; continue; }
41
41
  if (a === '-y' || a === '--yes' || a === '--non-interactive') { out.yes = true; continue; }
42
+ // Explicit, so `--force ./dir` cannot swallow the path as this flag's value.
43
+ if (a === '-f' || a === '--force') { out.force = true; continue; }
42
44
  if (a.startsWith('--')) {
43
45
  const eq = a.indexOf('=');
44
46
  if (eq !== -1) { out[a.slice(2, eq)] = a.slice(eq + 1); continue; }
@@ -62,6 +64,7 @@ ${c.b('Options')}
62
64
  --request Delegated: request an agent for an owner's org (--owner <email>, +--byok)
63
65
  --claim [--watch] Delegated: claim the config once the owner approves (reads metamynd-request.json)
64
66
  --owner <email> Target owner's email (with --request)
67
+ --force, -f Scaffold into a non-empty directory, overwriting existing files
65
68
  --api <url> API base (default ${DEFAULT_API})
66
69
  --email <email> Owner login email
67
70
  --password <pw> Owner password (prefer the interactive prompt or METAMYND_PASSWORD)
@@ -272,22 +275,48 @@ function gitignore() {
272
275
  return `node_modules/\nagent.metamynd.json\n.env\n`;
273
276
  }
274
277
 
275
- function writeFileSafe(dir, name, content) {
278
+ function writeFileSafe(dir, name, content, force = false) {
276
279
  const p = join(dir, name);
277
- if (existsSync(p)) { console.log(` ${c.yellow('skip')} ${name} ${c.dim('(exists)')}`); return; }
280
+ const exists = existsSync(p);
281
+ if (exists && !force) { console.log(` ${c.yellow('skip')} ${name} ${c.dim('(exists)')}`); return; }
278
282
  writeFileSync(p, content);
279
- console.log(` ${c.green('create')} ${name}`);
283
+ console.log(` ${exists ? c.yellow('overwrite') : c.green('create')} ${name}`);
284
+ }
285
+
286
+ /**
287
+ * Refuse to scaffold into a non-empty directory unless --force.
288
+ *
289
+ * Silently skipping an existing agent.metamynd.json is worse than it sounds:
290
+ * provisioning has already minted a NEW agent server-side, so the scaffold prints
291
+ * success while leaving the OLD config in place. Every later gate call then runs as
292
+ * the previous identity, against whatever apiBase that file happens to carry — which
293
+ * is exactly how a stale http:// base survived a re-scaffold and 404'd every call.
294
+ */
295
+ function assertScaffoldTarget(outDir, force) {
296
+ if (force || !existsSync(outDir)) return;
297
+ const entries = readdirSync(outDir);
298
+ if (entries.length === 0) return;
299
+ const rel = outDir.replace(resolve('.'), '.').replace(/\\/g, '/');
300
+ fail(
301
+ `${rel} is not empty (${entries.length} item${entries.length === 1 ? '' : 's'}).\n\n` +
302
+ ` Scaffolding here would KEEP the existing files — including any agent.metamynd.json —\n` +
303
+ ` so this project would keep running as the identity in that file, against the apiBase\n` +
304
+ ` in that file, and the newly provisioned agent would go unused.\n\n` +
305
+ ` Scaffold somewhere new: --out ./another-dir\n` +
306
+ ` or overwrite this one on purpose: --force`,
307
+ );
280
308
  }
281
309
 
282
310
  /** Write the scaffolded project + print next steps. Shared by the provision and sandbox paths. */
283
- function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox }) {
311
+ function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox, force = false }) {
312
+ assertScaffoldTarget(outDir, force);
284
313
  console.log(`\n ${c.b('Scaffolding')} ${c.dim(outDir)}`);
285
314
  if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
286
- writeFileSafe(outDir, 'agent.metamynd.json', JSON.stringify(config, null, 2) + '\n');
287
- writeFileSafe(outDir, 'index.mjs', exampleIndex(scope, perTxnMax));
288
- writeFileSafe(outDir, 'package.json', examplePackageJson(slug));
289
- writeFileSafe(outDir, '.gitignore', gitignore());
290
- writeFileSafe(outDir, 'README.md', exampleReadme(slug, scope));
315
+ writeFileSafe(outDir, 'agent.metamynd.json', JSON.stringify(config, null, 2) + '\n', force);
316
+ writeFileSafe(outDir, 'index.mjs', exampleIndex(scope, perTxnMax), force);
317
+ writeFileSafe(outDir, 'package.json', examplePackageJson(slug), force);
318
+ writeFileSafe(outDir, '.gitignore', gitignore(), force);
319
+ writeFileSafe(outDir, 'README.md', exampleReadme(slug, scope), force);
291
320
 
292
321
  const rel = outDir.replace(resolve('.'), '.').replace(/\\/g, '/');
293
322
  console.log(`\n${c.green(c.b(' ✓ Done.'))} Your governed agent is ready.\n`);
@@ -307,6 +336,10 @@ function scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox }) {
307
336
  async function runSandbox(args) {
308
337
  const apiRaw = (typeof args.api === 'string' ? args.api : undefined) ?? process.env.METAMYND_API ?? DEFAULT_API;
309
338
  const base = String(apiRaw).replace(/\/+$/, '');
339
+ // Check the target BEFORE provisioning: refusing afterwards would mint an agent
340
+ // server-side and then throw it away.
341
+ const outDir = resolve(String(args.out || './metamynd-sandbox'));
342
+ assertScaffoldTarget(outDir, !!args.force);
310
343
  console.log(c.dim(` → requesting a sandbox agent from ${base} …`));
311
344
  const provisioned = await apiPost(base, '/onboarding/sandbox', {}, null);
312
345
  const config = provisioned?.data;
@@ -314,8 +347,7 @@ async function runSandbox(args) {
314
347
  console.log(` ${c.green('✓')} sandbox agent ${c.b(config.agentDid)} ${c.dim('(shared test identity)')}`);
315
348
  const scope = config.mandate?.scope || 'flight-purchase';
316
349
  const perTxnMax = Number(config.perTxnMax) || 500;
317
- const outDir = resolve(String(args.out || './metamynd-sandbox'));
318
- scaffoldProject({ outDir, config, slug: 'metamynd-sandbox', scope, perTxnMax, sandbox: true });
350
+ scaffoldProject({ outDir, config, slug: 'metamynd-sandbox', scope, perTxnMax, sandbox: true, force: !!args.force });
319
351
  }
320
352
 
321
353
  // ---------- delegated issuance (#6) ----------
@@ -429,7 +461,7 @@ async function runClaim(args) {
429
461
 
430
462
  const slug = slugify(state.name || 'metamynd-agent');
431
463
  const outDir = resolve(String(args.out || `./${slug}`));
432
- scaffoldProject({ outDir, config, slug, scope: state.scope || config.mandate?.scope || 'flight-purchase', perTxnMax: Number(state.perTxnMax) || 500, sandbox: false });
464
+ scaffoldProject({ outDir, config, slug, scope: state.scope || config.mandate?.scope || 'flight-purchase', perTxnMax: Number(state.perTxnMax) || 500, sandbox: false, force: !!args.force });
433
465
  }
434
466
 
435
467
  // ---------- main ----------
@@ -536,7 +568,7 @@ async function main() {
536
568
  }
537
569
 
538
570
  // 4. Scaffold + next steps
539
- scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox: false });
571
+ scaffoldProject({ outDir, config, slug, scope, perTxnMax, sandbox: false, force: !!args.force });
540
572
  }
541
573
 
542
574
  main().catch((e) => fail(e?.stack || e?.message || String(e)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-metamynd-agent",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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, and drops a runnable example.",
5
5
  "type": "module",
6
6
  "bin": {