@topy-ai/maggie 0.7.44 → 0.7.46

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 (39) hide show
  1. package/README-zh-TW.md +29 -6
  2. package/README.md +86 -12
  3. package/bin/maggie.js +207 -58
  4. package/bundled-contracts/google-integrations/capability-report-v2.schema.json +76 -0
  5. package/bundled-contracts/google-integrations/external-write-readback-v1.schema.json +36 -0
  6. package/bundled-contracts/maggie-deployment/deployer-delegation-v1.schema.json +20 -0
  7. package/bundled-contracts/maggie-deployment/release-profile-v1.schema.json +34 -0
  8. package/bundled-contracts/maggie-design/browser-capability-v1.schema.json +33 -0
  9. package/bundled-contracts/maggie-feedback/evidence-bundle-v1.schema.json +38 -0
  10. package/bundled-references/browser-inspection.md +17 -0
  11. package/bundled-references/google-integrations-runbook.md +8 -1
  12. package/bundled-skills/maggie-blog-bootstrap/SKILL.md +7 -7
  13. package/bundled-skills/maggie-clone/SKILL.md +21 -14
  14. package/bundled-skills/maggie-clone-to-template/SKILL.md +11 -11
  15. package/bundled-skills/maggie-clone-to-template/references/workflow.md +2 -2
  16. package/bundled-skills/maggie-content-localization/SKILL.md +10 -10
  17. package/bundled-skills/maggie-deployment/SKILL.md +57 -23
  18. package/bundled-skills/maggie-deployment/references/vps.md +4 -4
  19. package/bundled-skills/maggie-design/SKILL.md +12 -12
  20. package/bundled-skills/maggie-feedback/SKILL.md +23 -0
  21. package/bundled-skills/maggie-marketplace/SKILL.md +12 -12
  22. package/bundled-skills/maggie-ops/SKILL.md +28 -6
  23. package/bundled-skills/maggie-project-context/SKILL.md +1 -1
  24. package/bundled-skills/maggie-seo-geo/SKILL.md +6 -3
  25. package/bundled-skills/maggie-service-booking/SKILL.md +24 -24
  26. package/bundled-skills/maggie-social-share/SKILL.md +2 -2
  27. package/bundled-skills/maggie-template/SKILL.md +5 -5
  28. package/bundled-tools/clis/maggie_analytics.py +8 -0
  29. package/bundled-tools/clis/maggie_browser_audit.py +13 -2
  30. package/bundled-tools/clis/maggie_deployment.py +167 -6
  31. package/bundled-tools/clis/maggie_feedback.py +115 -1
  32. package/bundled-tools/clis/maggie_ops.py +33 -0
  33. package/bundled-tools/clis/maggie_release.py +124 -19
  34. package/bundled-tools/runtime/browser_capability.py +143 -0
  35. package/bundled-tools/runtime/external_write.py +122 -0
  36. package/bundled-tools/runtime/google_capabilities.py +37 -5
  37. package/package.json +1 -1
  38. package/references/browser-inspection.md +17 -0
  39. package/references/google-integrations-runbook.md +8 -1
package/bin/maggie.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { createHash } from "node:crypto";
5
5
  import { spawnSync } from "node:child_process";
6
+ import { homedir } from "node:os";
6
7
  import { basename, dirname, join, relative, resolve } from "node:path";
7
8
  import { fileURLToPath } from "node:url";
8
9
 
@@ -16,6 +17,8 @@ const MARKETPLACE_ROOT = join(PACKAGE_ROOT, "bundled-marketplace");
16
17
  const CONTRACTS_ROOT = join(PACKAGE_ROOT, "bundled-contracts");
17
18
  const TEMPLATES_ROOT = join(PACKAGE_ROOT, "bundled-templates");
18
19
  const STATE_DIR = ".maggie";
20
+ const USER_SCOPE = "user";
21
+ const PROJECT_SCOPE = "project";
19
22
  function loadSkillNames() {
20
23
  try {
21
24
  return JSON.parse(readFileSync(join(SKILLS_ROOT, "catalog.json"), "utf8")).skills.map((skill) => skill.name);
@@ -45,17 +48,23 @@ const RETIRED_PATHS = [
45
48
  "docs/ai-cmo-emdash",
46
49
  ];
47
50
  const RETIRED_REFERENCE_FILES = ["CLAUDE.md", ".claude/CLAUDE.md", ".agents/AGENTS.md", ".agents/README.md"];
51
+ const USER_RETIRED_PATHS = [
52
+ "skills/maggie-emdash",
53
+ "skills/building-emdash-site",
54
+ "skills/creating-plugins",
55
+ "skills/emdash-cli",
56
+ "references/emdash-integration.md",
57
+ ];
48
58
 
49
59
  function usage() {
50
60
  console.log(`Maggie Skills installer
51
61
 
52
62
  Usage:
53
63
  maggie --version | version
54
- maggie init [--project PATH] [--agent auto|codex|claude|all] [--skills LIST]
55
- maggie install [SKILL ...] [--project PATH] [--agent auto|codex|claude|all]
56
- maggie update [SKILL ...] [--project PATH] [--agent auto|codex|claude|all] [--force]
64
+ maggie init|install [--scope project|user|--global] [--project PATH] [--agent auto|codex|claude|all] [--skills LIST]
65
+ maggie update [SKILL ...] [--scope project|user|--global] [--project PATH] [--agent auto|codex|claude|all] [--force]
57
66
  maggie list
58
- maggie doctor [--project PATH]
67
+ maggie doctor [--scope project|user|--global] [--project PATH]
59
68
  maggie bootstrap interview [project]
60
69
  maggie dash init|install|status|migrate|transition|variant|cms --project PATH [options]
61
70
  maggie booking install|worker-schedule|inspect|setup|contract|runtime-validate|ops-validate|release-gate|stripe-audit|tasks --project PATH [options]
@@ -77,8 +86,9 @@ Usage:
77
86
  maggie content FILE --source PROVIDER --project PATH --confirm
78
87
  maggie agent-content write --url URL --allowed-origin ORIGIN --token-env ENV --payload FILE
79
88
  maggie bootstrap phase <start|pass|fail|status> <phase> [project]
80
- maggie remove [SKILL ...] [--project PATH] [--agent codex|claude|all]
81
- maggie cleanup --project PATH [--confirm]
89
+ maggie remove [SKILL ...] [--scope project|user|--global] [--project PATH] [--agent codex|claude|all]
90
+ maggie cleanup [--scope project|user|--global] [--project PATH] [--confirm]
91
+ maggie tool <bundled-cli.py> [arguments]
82
92
  maggie marketplace add <template-url> clone <preview-url> --rename <id>
83
93
  maggie marketplace plan --workspace-id ID --merchant-id ID --site-slug SLUG --cal-url URL [--output FILE]
84
94
  maggie marketplace list
@@ -143,7 +153,7 @@ Usage:
143
153
  maggie brand-kit --manifest FILE | --project PATH --design DESIGN.md [--write --confirm]
144
154
  maggie npm11 --project PATH [--smoke --confirm]
145
155
  maggie booking-delivery [--policy FILE]
146
- maggie release PATH --environment staging --target vps-with-cloudflare-dns
156
+ maggie release PATH --profile .maggie/release-profile.json --environment staging --target vps-with-cloudflare-dns
147
157
  maggie api lifecycle --project PATH [--execute --allow-quota]
148
158
  maggie memory <init|list|search|context|add|record-error|transition|export> --project PATH
149
159
  maggie localization <extract|plan|generate|preview|validate|review|publish|stale|glossary> [options]
@@ -153,7 +163,7 @@ Usage:
153
163
  maggie site-audit URL [--crawl] [--access-log FILE] [--require-sitemap-request] [--languages en-GB,es-MX,ja-JP] [--check-hreflang] [--check-internal-links]
154
164
  maggie site-audit URL --crawl --save-baseline FILE --reviewer NAME
155
165
  maggie site-audit URL --crawl --baseline FILE [--reason-all REASON]
156
- maggie browser-audit URL --browse PATH --output DIR --required SELECTOR [--sticky SELECTOR] [--interactions FILE]
166
+ maggie browser-audit URL --browse PATH --output DIR [--required SELECTOR] [--sticky SELECTOR] [--interactions FILE] [--check-browser]
157
167
  maggie verification coverage --contract FILE --evidence FILE
158
168
  maggie ops audit|preflight|verify|lockfiles|seed-manifest|google-capabilities|favicon-check --project PATH
159
169
  maggie ops google-capabilities --project PATH --report FILE [--output FILE]
@@ -180,6 +190,29 @@ function projectRoot(args) {
180
190
  return resolve(option(args, "--project", process.cwd()));
181
191
  }
182
192
 
193
+ function installScope(args) {
194
+ const explicit = option(args, "--scope");
195
+ const globalFlag = args.includes("--global");
196
+ if (globalFlag && explicit && explicit !== USER_SCOPE) throw new Error("--global cannot be combined with --scope project");
197
+ const scope = globalFlag ? USER_SCOPE : (explicit || PROJECT_SCOPE);
198
+ if (![PROJECT_SCOPE, USER_SCOPE].includes(scope)) throw new Error(`unknown install scope: ${scope}`);
199
+ return scope;
200
+ }
201
+
202
+ function userAgentRoot(agent) {
203
+ if (agent === "codex") return resolve(process.env.CODEX_HOME || join(homedir(), ".codex"));
204
+ if (agent === "claude") return resolve(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"));
205
+ throw new Error(`unknown agent: ${agent}`);
206
+ }
207
+
208
+ function userStateRoot() {
209
+ return resolve(process.env.MAGGIE_HOME || join(homedir(), ".config", "maggie"));
210
+ }
211
+
212
+ function installManifestPath(root, scope) {
213
+ return scope === USER_SCOPE ? join(userStateRoot(), "install.json") : join(root, STATE_DIR, "install.json");
214
+ }
215
+
183
216
  function selectedSkills(args) {
184
217
  const requested = values(args, "--skills").concat(args.filter((item) => SKILL_NAMES.includes(item)));
185
218
  const unique = [...new Set(requested)];
@@ -190,8 +223,12 @@ function selectedSkills(args) {
190
223
  }
191
224
 
192
225
  function diskSkillNames(root) {
226
+ return diskSkillNamesFromRoots([join(root, ".agents"), join(root, ".claude")]);
227
+ }
228
+
229
+ function diskSkillNamesFromRoots(agentRoots) {
193
230
  const installed = new Set();
194
- for (const agentRoot of [join(root, ".agents"), join(root, ".claude")]) {
231
+ for (const agentRoot of agentRoots) {
195
232
  const skillsRoot = join(agentRoot, "skills");
196
233
  if (!existsSync(skillsRoot)) continue;
197
234
  for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
@@ -216,9 +253,29 @@ function mergedSkillNames(root, selected) {
216
253
  .filter((skill) => SKILL_NAMES.includes(skill)).sort();
217
254
  }
218
255
 
219
- function agentRoots(args, root) {
256
+ function mergedUserSkillNames(roots) {
257
+ return [...new Set(Object.values(userSkillInventory(roots)).flat())].sort();
258
+ }
259
+
260
+ function userSkillInventory(roots) {
261
+ return Object.fromEntries(roots.map((agentRoot) => [
262
+ agentRoot,
263
+ [...diskSkillNamesFromRoots([agentRoot])].filter((skill) => SKILL_NAMES.includes(skill)).sort(),
264
+ ]));
265
+ }
266
+
267
+ function allUserAgentRoots() {
268
+ return [userAgentRoot("codex"), userAgentRoot("claude")];
269
+ }
270
+
271
+ function agentRoots(args, root, scope = PROJECT_SCOPE) {
220
272
  const requested = option(args, "--agent", "auto");
221
273
  if (!["auto", "codex", "claude", "all"].includes(requested)) throw new Error(`unknown agent: ${requested}`);
274
+ if (scope === USER_SCOPE) {
275
+ if (requested === "codex") return [userAgentRoot("codex")];
276
+ if (requested === "claude") return [userAgentRoot("claude")];
277
+ return [userAgentRoot("codex"), userAgentRoot("claude")];
278
+ }
222
279
  if (requested === "codex") return [join(root, ".agents")];
223
280
  if (requested === "claude") return [join(root, ".claude")];
224
281
  if (requested === "all") return [join(root, ".agents"), join(root, ".claude")];
@@ -367,26 +424,45 @@ function managedFileDigests(root, roots, skills, previousManagedFiles = {}) {
367
424
  return Object.fromEntries(Object.entries(managedFiles).sort(([a], [b]) => a.localeCompare(b)));
368
425
  }
369
426
 
370
- function readInstallManifest(root) {
427
+ function userManagedPairs(roots, skills) {
428
+ const pairs = [];
429
+ const add = (source, target) => { if (existsSync(source) && existsSync(target)) pairs.push([source, target]); };
430
+ for (const agentRoot of roots) {
431
+ for (const skill of skills) add(join(SKILLS_ROOT, skill), join(agentRoot, "skills", skill));
432
+ add(REFERENCES_ROOT, join(agentRoot, "references"));
433
+ }
434
+ return pairs;
435
+ }
436
+
437
+ function userManagedFileDigests(roots, skills, previousManagedFiles = {}) {
438
+ const managedFiles = {};
439
+ for (const [source, target] of userManagedPairs(roots, skills)) collectManagedFiles(source, target, "/", managedFiles, previousManagedFiles);
440
+ return Object.fromEntries(Object.entries(managedFiles).sort(([a], [b]) => a.localeCompare(b)));
441
+ }
442
+
443
+ function readInstallManifest(root, scope = PROJECT_SCOPE) {
371
444
  try {
372
- const path = join(root, STATE_DIR, "install.json");
445
+ const path = installManifestPath(root, scope);
373
446
  return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
374
447
  } catch {
375
448
  return {};
376
449
  }
377
450
  }
378
451
 
379
- function writeInstallManifest(root, data) {
380
- const stateDir = join(root, STATE_DIR);
452
+ function writeInstallManifest(root, data, scope = PROJECT_SCOPE) {
453
+ const stateDir = scope === USER_SCOPE ? userStateRoot() : join(root, STATE_DIR);
381
454
  mkdirSync(stateDir, { recursive: true });
382
455
  writeFileSync(join(stateDir, "install.json"), JSON.stringify(data, null, 2) + "\n");
383
456
  }
384
457
 
385
458
  function install(args) {
459
+ const scope = installScope(args);
386
460
  const root = projectRoot(args);
387
461
  const skills = selectedSkills(args);
388
462
  const force = args.includes("--force");
389
- const roots = agentRoots(args, root);
463
+ const roots = agentRoots(args, root, scope);
464
+ const previousManifest = scope === USER_SCOPE ? readInstallManifest(root, scope) : {};
465
+ const previousManagedFiles = previousManifest.managedFiles && typeof previousManifest.managedFiles === "object" ? previousManifest.managedFiles : {};
390
466
  if (!existsSync(SKILLS_ROOT)) throw new Error("bundled skills are missing; run npm pack from the package source");
391
467
  for (const agentRoot of roots) {
392
468
  for (const skill of skills) {
@@ -395,25 +471,32 @@ function install(args) {
395
471
  }
396
472
  if (existsSync(REFERENCES_ROOT)) copyIfMissing(REFERENCES_ROOT, join(agentRoot, "references"), force);
397
473
  }
398
- const tools = join(root, "tools");
399
- if (existsSync(TOOLS_ROOT)) {
400
- for (const group of ["clis", "runtime", "integrations"]) {
401
- if (existsSync(join(TOOLS_ROOT, group))) {
402
- const target = join(tools, group);
403
- if (existsSync(target)) syncTree(join(TOOLS_ROOT, group), target, force);
404
- else copyIfMissing(join(TOOLS_ROOT, group), target, force);
474
+ if (scope === PROJECT_SCOPE) {
475
+ const tools = join(root, "tools");
476
+ if (existsSync(TOOLS_ROOT)) {
477
+ for (const group of ["clis", "runtime", "integrations"]) {
478
+ if (existsSync(join(TOOLS_ROOT, group))) {
479
+ const target = join(tools, group);
480
+ if (existsSync(target)) syncTree(join(TOOLS_ROOT, group), target, force);
481
+ else copyIfMissing(join(TOOLS_ROOT, group), target, force);
482
+ }
405
483
  }
406
484
  }
485
+ if (existsSync(join(DESIGN_ROOT, "SPA-DESIGN.md"))) {
486
+ copyIfMissing(join(DESIGN_ROOT, "SPA-DESIGN.md"), join(root, ".maggie", "design-reference", "SPA-DESIGN.md"));
487
+ }
488
+ if (existsSync(MARKETPLACE_ROOT)) copyIfMissing(MARKETPLACE_ROOT, join(root, "marketplace"));
489
+ if (existsSync(CONTRACTS_ROOT)) copyIfMissing(CONTRACTS_ROOT, join(root, "contracts"));
490
+ if (existsSync(join(TEMPLATES_ROOT, "maggiedash"))) copyIfMissing(join(TEMPLATES_ROOT, "maggiedash"), join(root, "templates", "maggiedash"));
407
491
  }
408
- if (existsSync(join(DESIGN_ROOT, "SPA-DESIGN.md"))) {
409
- copyIfMissing(join(DESIGN_ROOT, "SPA-DESIGN.md"), join(root, ".maggie", "design-reference", "SPA-DESIGN.md"));
410
- }
411
- if (existsSync(MARKETPLACE_ROOT)) copyIfMissing(MARKETPLACE_ROOT, join(root, "marketplace"));
412
- if (existsSync(CONTRACTS_ROOT)) copyIfMissing(CONTRACTS_ROOT, join(root, "contracts"));
413
- if (existsSync(join(TEMPLATES_ROOT, "maggiedash"))) copyIfMissing(join(TEMPLATES_ROOT, "maggiedash"), join(root, "templates", "maggiedash"));
414
- writeInstallManifest(root, { version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills: mergedSkillNames(root, skills), managedFiles: managedFileDigests(root, roots, skills), installed_at: new Date().toISOString() });
415
- console.log(`Maggie installed in ${root}`);
416
- console.log("Run `maggie doctor --project .` before using mutating workflows.");
492
+ const manifestRoots = scope === USER_SCOPE ? allUserAgentRoots() : roots;
493
+ const installedUserSkills = scope === USER_SCOPE ? mergedUserSkillNames(manifestRoots) : [];
494
+ const manifest = scope === USER_SCOPE
495
+ ? { scope, version: PACKAGE_VERSION, agents: manifestRoots, skills: installedUserSkills, skillsByAgent: userSkillInventory(manifestRoots), managedFiles: userManagedFileDigests(manifestRoots, installedUserSkills, previousManagedFiles), installed_at: new Date().toISOString() }
496
+ : { scope, version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills: mergedSkillNames(root, skills), managedFiles: managedFileDigests(root, roots, skills), installed_at: new Date().toISOString() };
497
+ writeInstallManifest(root, manifest, scope);
498
+ console.log(scope === USER_SCOPE ? `Maggie installed for this user in ${userStateRoot()}` : `Maggie installed in ${root}`);
499
+ console.log(scope === USER_SCOPE ? "Project state remains local; run `maggie doctor --scope user` to verify the user install." : "Run `maggie doctor --project .` before using mutating workflows.");
417
500
  }
418
501
 
419
502
  function update(args) {
@@ -421,14 +504,16 @@ function update(args) {
421
504
  usage();
422
505
  return;
423
506
  }
507
+ const scope = installScope(args);
424
508
  const root = projectRoot(args);
425
509
  const skills = selectedSkills(args);
426
510
  const force = args.includes("--force");
427
- const roots = agentRoots(args, root);
428
- const previousManifest = readInstallManifest(root);
511
+ const roots = agentRoots(args, root, scope);
512
+ const previousManifest = readInstallManifest(root, scope);
429
513
  const previousManagedFiles = previousManifest.managedFiles && typeof previousManifest.managedFiles === "object" ? previousManifest.managedFiles : {};
430
514
  if (!existsSync(SKILLS_ROOT)) throw new Error("bundled skills are missing; run npm pack from the package source");
431
515
  let updated = 0;
516
+ const managedRoot = scope === USER_SCOPE ? "/" : root;
432
517
  for (const agentRoot of roots) {
433
518
  for (const skill of skills) {
434
519
  const source = join(SKILLS_ROOT, skill);
@@ -437,23 +522,30 @@ function update(args) {
437
522
  console.log(`absent ${target} (run install to add it)`);
438
523
  continue;
439
524
  }
440
- updated += syncTree(source, target, force, root, previousManagedFiles);
525
+ updated += syncTree(source, target, force, managedRoot, previousManagedFiles);
441
526
  }
442
- if (existsSync(REFERENCES_ROOT) && existsSync(join(agentRoot, "references"))) updated += syncTree(REFERENCES_ROOT, join(agentRoot, "references"), force, root, previousManagedFiles);
527
+ if (existsSync(REFERENCES_ROOT) && existsSync(join(agentRoot, "references"))) updated += syncTree(REFERENCES_ROOT, join(agentRoot, "references"), force, managedRoot, previousManagedFiles);
443
528
  }
444
- const tools = join(root, "tools");
445
- if (existsSync(TOOLS_ROOT)) for (const group of ["clis", "runtime", "integrations"]) if (existsSync(join(TOOLS_ROOT, group))) {
446
- const target = join(tools, group);
447
- if (existsSync(target)) updated += syncTree(join(TOOLS_ROOT, group), target, force, root, previousManagedFiles);
448
- }
449
- if (existsSync(join(DESIGN_ROOT, "SPA-DESIGN.md")) && existsSync(join(root, ".maggie", "design-reference"))) {
450
- updated += syncTree(DESIGN_ROOT, join(root, ".maggie", "design-reference"), force, root, previousManagedFiles);
529
+ if (scope === PROJECT_SCOPE) {
530
+ const tools = join(root, "tools");
531
+ if (existsSync(TOOLS_ROOT)) for (const group of ["clis", "runtime", "integrations"]) if (existsSync(join(TOOLS_ROOT, group))) {
532
+ const target = join(tools, group);
533
+ if (existsSync(target)) updated += syncTree(join(TOOLS_ROOT, group), target, force, root, previousManagedFiles);
534
+ }
535
+ if (existsSync(join(DESIGN_ROOT, "SPA-DESIGN.md")) && existsSync(join(root, ".maggie", "design-reference"))) {
536
+ updated += syncTree(DESIGN_ROOT, join(root, ".maggie", "design-reference"), force, root, previousManagedFiles);
537
+ }
538
+ if (existsSync(MARKETPLACE_ROOT) && existsSync(join(root, "marketplace"))) updated += syncTree(MARKETPLACE_ROOT, join(root, "marketplace"), force, root, previousManagedFiles);
539
+ if (existsSync(CONTRACTS_ROOT) && existsSync(join(root, "contracts"))) updated += syncTree(CONTRACTS_ROOT, join(root, "contracts"), force, root, previousManagedFiles);
540
+ if (existsSync(join(TEMPLATES_ROOT, "maggiedash")) && existsSync(join(root, "templates", "maggiedash"))) updated += syncTree(join(TEMPLATES_ROOT, "maggiedash"), join(root, "templates", "maggiedash"), force, root, previousManagedFiles);
451
541
  }
452
- if (existsSync(MARKETPLACE_ROOT) && existsSync(join(root, "marketplace"))) updated += syncTree(MARKETPLACE_ROOT, join(root, "marketplace"), force, root, previousManagedFiles);
453
- if (existsSync(CONTRACTS_ROOT) && existsSync(join(root, "contracts"))) updated += syncTree(CONTRACTS_ROOT, join(root, "contracts"), force, root, previousManagedFiles);
454
- if (existsSync(join(TEMPLATES_ROOT, "maggiedash")) && existsSync(join(root, "templates", "maggiedash"))) updated += syncTree(join(TEMPLATES_ROOT, "maggiedash"), join(root, "templates", "maggiedash"), force, root, previousManagedFiles);
455
- writeInstallManifest(root, { version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills: mergedSkillNames(root, skills), managedFiles: managedFileDigests(root, roots, skills, previousManagedFiles), updated_at: new Date().toISOString() });
456
- console.log(`Maggie update complete: ${updated} files changed`);
542
+ const manifestRoots = scope === USER_SCOPE ? allUserAgentRoots() : roots;
543
+ const updatedUserSkills = scope === USER_SCOPE ? mergedUserSkillNames(manifestRoots) : [];
544
+ const manifest = scope === USER_SCOPE
545
+ ? { scope, version: PACKAGE_VERSION, agents: manifestRoots, skills: updatedUserSkills, skillsByAgent: userSkillInventory(manifestRoots), managedFiles: userManagedFileDigests(manifestRoots, updatedUserSkills, previousManagedFiles), updated_at: new Date().toISOString() }
546
+ : { scope, version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills: mergedSkillNames(root, skills), managedFiles: managedFileDigests(root, roots, skills, previousManagedFiles), updated_at: new Date().toISOString() };
547
+ writeInstallManifest(root, manifest, scope);
548
+ console.log(`Maggie ${scope} update complete: ${updated} files changed`);
457
549
  if (!force) console.log("Local files with changes were preserved. Review the output and rerun with --force only when replacement is intended.");
458
550
  }
459
551
 
@@ -505,6 +597,23 @@ function workflowCli(name, args) {
505
597
  process.exitCode = result.status ?? 1;
506
598
  }
507
599
 
600
+ function tool(args) {
601
+ const [name, ...forwarded] = args;
602
+ if (!name || name.includes("/") || name.includes("\\") || name.includes("..") || !name.endsWith(".py")) {
603
+ throw new Error("tool requires one bundled CLI filename such as maggie_design.py");
604
+ }
605
+ const root = projectRoot(forwarded);
606
+ const script = toolScript(name, root);
607
+ if (!existsSync(script)) throw new Error(`bundled CLI is missing: ${name}`);
608
+ const result = spawnSync("python3", [script, ...forwarded], {
609
+ stdio: "inherit",
610
+ cwd: root,
611
+ env: { ...process.env, MAGGIE_VERSION: PACKAGE_VERSION },
612
+ });
613
+ if (result.error) throw result.error;
614
+ process.exitCode = result.status ?? 1;
615
+ }
616
+
508
617
  function toolScript(name, root) {
509
618
  const bundled = join(TOOLS_ROOT, "clis", name);
510
619
  return existsSync(bundled) ? bundled : join(root, "tools", "clis", name);
@@ -515,7 +624,25 @@ function version() {
515
624
  }
516
625
 
517
626
  function doctor(args) {
627
+ const scope = installScope(args);
518
628
  const root = projectRoot(args);
629
+ if (scope === USER_SCOPE) {
630
+ const roots = agentRoots(args, root, scope);
631
+ const checks = roots.map((agentRoot) => [agentRoot, existsSync(join(agentRoot, "skills"))]);
632
+ for (const [agentRoot, passed] of checks) console.log(`${passed ? "PASS" : "INFO"} ${agentRoot}/skills`);
633
+ const manifest = readInstallManifest(root, scope);
634
+ const expected = manifest?.skillsByAgent && typeof manifest.skillsByAgent === "object"
635
+ ? [...new Set(roots.flatMap((agentRoot) => Array.isArray(manifest.skillsByAgent[agentRoot]) ? manifest.skillsByAgent[agentRoot] : []))].filter((skill) => SKILL_NAMES.includes(skill)).sort()
636
+ : Array.isArray(manifest?.skills) ? [...new Set(manifest.skills)].filter((skill) => SKILL_NAMES.includes(skill)) : null;
637
+ const installedNames = new Set([...diskSkillNamesFromRoots(roots)].filter((skill) => SKILL_NAMES.includes(skill)));
638
+ const missing = expected ? expected.filter((skill) => !installedNames.has(skill)) : [];
639
+ const inventoryState = expected && missing.length ? "INCOMPLETE" : "PASS";
640
+ console.log(`${inventoryState} user-skill-inventory manifest=${expected?.length ?? "unknown"} on-disk=${installedNames.size}${missing.length ? ` missing=${missing.join(",")}` : ""}`);
641
+ console.log(`INFO user-install-manifest=${installManifestPath(root, scope)}`);
642
+ console.log(`INFO installed-skills=${installedNames.size}`);
643
+ if (inventoryState === "INCOMPLETE") process.exitCode = 1;
644
+ return;
645
+ }
519
646
  const checks = [
520
647
  ["project", existsSync(root)],
521
648
  ["bootstrap-state", existsSync(join(root, ".maggie", "bootstrap-state.json"))],
@@ -538,9 +665,11 @@ function doctor(args) {
538
665
  }
539
666
 
540
667
  function remove(args) {
668
+ const scope = installScope(args);
541
669
  const root = projectRoot(args);
542
670
  const skills = selectedSkills(args);
543
- for (const agentRoot of agentRoots(args, root)) {
671
+ const roots = agentRoots(args, root, scope);
672
+ for (const agentRoot of roots) {
544
673
  for (const skill of skills) {
545
674
  const target = join(agentRoot, "skills", skill);
546
675
  if (existsSync(target)) {
@@ -549,31 +678,50 @@ function remove(args) {
549
678
  } else console.log(`absent ${target}`);
550
679
  }
551
680
  }
681
+ if (scope === USER_SCOPE) {
682
+ const manifestRoots = allUserAgentRoots();
683
+ const remainingSkills = mergedUserSkillNames(manifestRoots);
684
+ writeInstallManifest(root, {
685
+ scope,
686
+ version: PACKAGE_VERSION,
687
+ agents: manifestRoots,
688
+ skills: remainingSkills,
689
+ skillsByAgent: userSkillInventory(manifestRoots),
690
+ managedFiles: userManagedFileDigests(manifestRoots, remainingSkills),
691
+ updated_at: new Date().toISOString(),
692
+ }, scope);
693
+ }
552
694
  }
553
695
 
554
696
  function cleanup(args) {
697
+ const scope = installScope(args);
555
698
  const root = projectRoot(args);
556
- const existing = RETIRED_PATHS
557
- .map((relativePath) => ({ relativePath, target: join(root, relativePath) }))
558
- .filter(({ target }) => existsSync(target));
699
+ const roots = scope === USER_SCOPE ? agentRoots(args, root, scope) : [root];
700
+ const retiredPaths = scope === USER_SCOPE ? USER_RETIRED_PATHS : RETIRED_PATHS;
701
+ const existing = roots.flatMap((base) => retiredPaths
702
+ .map((relativePath) => ({ relativePath, target: join(base, relativePath) }))
703
+ .filter(({ target }) => existsSync(target)));
704
+
559
705
  if (!existing.length) {
560
- console.log("Maggie cleanup: no retired artifacts found");
706
+ console.log(`Maggie cleanup (${scope}): no retired artifacts found`);
561
707
  } else {
562
708
  for (const { relativePath, target } of existing) console.log(`${args.includes("--confirm") ? "removing" : "would remove"} ${relativePath}`);
563
709
  if (args.includes("--confirm")) {
564
710
  for (const { target } of existing) rmSync(target, { recursive: true, force: false });
565
- console.log(`Maggie cleanup complete: removed ${existing.length} retired artifact(s)`);
711
+ console.log(`Maggie cleanup (${scope}) complete: removed ${existing.length} retired artifact(s)`);
566
712
  } else {
567
713
  console.log("Nothing was deleted. Rerun with --confirm to remove these retired artifacts.");
568
714
  }
569
715
  }
570
- for (const relativePath of RETIRED_REFERENCE_FILES) {
571
- const target = join(root, relativePath);
572
- if (existsSync(target) && /emdash|em-dash|building-emdash-site|creating-plugins/i.test(readFileSync(target, "utf8"))) {
573
- console.log(`review required ${relativePath}: contains a retired EmDash reference`);
716
+ if (scope === PROJECT_SCOPE) {
717
+ for (const relativePath of RETIRED_REFERENCE_FILES) {
718
+ const target = join(root, relativePath);
719
+ if (existsSync(target) && /emdash|em-dash|building-emdash-site|creating-plugins/i.test(readFileSync(target, "utf8"))) {
720
+ console.log(`review required ${relativePath}: contains a retired EmDash reference`);
721
+ }
574
722
  }
723
+ console.log("Migration history under docs/de-emdash-* and marketplace/.maggie were preserved.");
575
724
  }
576
- console.log("Migration history under docs/de-emdash-* and marketplace/.maggie were preserved.");
577
725
  }
578
726
 
579
727
  const [command = "help", ...args] = process.argv.slice(2);
@@ -623,6 +771,7 @@ try {
623
771
  else if (command === "booking") workflowCli("maggie_booking.py", args);
624
772
  else if (command === "agent-content") workflowCli("maggie_agent_content.py", args);
625
773
  else if (command === "content") workflowCli("maggie_content.py", args);
774
+ else if (command === "tool") tool(args);
626
775
  else if (command === "init" || command === "install") install(args);
627
776
  else if (command === "update") update(args);
628
777
  else if (command === "doctor") doctor(args);
@@ -0,0 +1,76 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/google-integrations/capability-report-v2.json",
4
+ "title": "Maggie Google integrations capability report v2",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "generatedAt", "providerResults", "passed", "mutationsAllowed"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-google-capability-report.v2"},
10
+ "generatedAt": {"type": "string", "minLength": 1},
11
+ "providerResults": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/providerResult"}},
12
+ "passed": {"type": "boolean"},
13
+ "mutationsAllowed": {"const": false}
14
+ },
15
+ "$defs": {
16
+ "providerResult": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "required": ["provider", "resource", "activeAccount", "target", "authMode", "scopes", "productRole", "evidence", "capabilities", "nextAction"],
20
+ "properties": {
21
+ "provider": {"enum": ["gsc", "ga4", "gtm", "google-ads", "firebase"]},
22
+ "resource": {"type": "string", "minLength": 1, "maxLength": 200},
23
+ "activeAccount": {"$ref": "#/$defs/verifiedRef"},
24
+ "target": {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "required": ["id", "kind", "verified"],
28
+ "properties": {
29
+ "id": {"type": "string", "minLength": 1, "maxLength": 200},
30
+ "kind": {"enum": ["property", "container", "customer", "project", "site", "account"]},
31
+ "verified": {"const": true}
32
+ }
33
+ },
34
+ "authMode": {"enum": ["desktop-oauth", "service-account-impersonation", "none"]},
35
+ "scopes": {"type": "array", "items": {"type": "string", "minLength": 1}, "uniqueItems": true},
36
+ "productRole": {"type": "string", "minLength": 1, "maxLength": 200},
37
+ "evidence": {"$ref": "#/$defs/evidence"},
38
+ "capabilities": {"$ref": "#/$defs/capabilities"},
39
+ "nextAction": {"type": "string", "minLength": 1, "maxLength": 500}
40
+ }
41
+ },
42
+ "verifiedRef": {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "required": ["id", "verified"],
46
+ "properties": {
47
+ "id": {"type": "string", "pattern": "^[^@\\s]{1,200}$"},
48
+ "verified": {"const": true}
49
+ }
50
+ },
51
+ "evidence": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["verified", "endpoint", "statusCode", "readOnly"],
55
+ "properties": {
56
+ "verified": {"type": "boolean"},
57
+ "endpoint": {"type": "string", "minLength": 1, "maxLength": 500},
58
+ "statusCode": {"type": "integer", "minimum": 100, "maximum": 599},
59
+ "readOnly": {"type": "boolean"},
60
+ "writeTest": {"type": "object"},
61
+ "adsPrerequisites": {"type": "object"}
62
+ }
63
+ },
64
+ "capabilities": {
65
+ "type": "object",
66
+ "additionalProperties": false,
67
+ "required": ["read", "report", "edit", "publish"],
68
+ "properties": {
69
+ "read": {"enum": ["verified", "not_tested", "not_available", "blocked"]},
70
+ "report": {"enum": ["verified", "not_tested", "not_available", "blocked"]},
71
+ "edit": {"enum": ["verified", "not_tested", "not_available", "blocked"]},
72
+ "publish": {"enum": ["verified", "not_tested", "not_available", "blocked"]}
73
+ }
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/google-integrations/external-write-readback-v1.json",
4
+ "title": "Maggie external provider write readback evidence v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "generatedAt", "provider", "resource", "operation", "idempotencyKey", "retry", "mutation", "readback", "duplicates", "orphaned", "cleanup", "mutationExecuted"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-external-write-readback.v1"},
10
+ "generatedAt": {"type": "string", "minLength": 1},
11
+ "provider": {"enum": ["ga4", "gtm", "gsc", "google-ads", "firebase"]},
12
+ "resource": {"type": "string", "minLength": 1, "maxLength": 200},
13
+ "operation": {"type": "string", "pattern": "^[a-z][a-z0-9_.-]{1,63}$"},
14
+ "idempotencyKey": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{2,127}$"},
15
+ "retry": {
16
+ "type": "object", "additionalProperties": false, "required": ["attempt", "maxAttempts", "sameKeyOnRetry"],
17
+ "properties": {"attempt": {"type": "integer", "minimum": 1, "maximum": 3}, "maxAttempts": {"type": "integer", "minimum": 1, "maximum": 3}, "sameKeyOnRetry": {"const": true}}
18
+ },
19
+ "mutation": {
20
+ "type": "object", "additionalProperties": false, "required": ["status", "statusCode", "explicitConfirmation"],
21
+ "properties": {"status": {"enum": ["created", "updated", "no-op"]}, "statusCode": {"type": "integer", "minimum": 200, "maximum": 299}, "explicitConfirmation": {"const": true}}
22
+ },
23
+ "readback": {
24
+ "type": "object", "additionalProperties": false, "required": ["passed", "endpoint", "checks"],
25
+ "properties": {
26
+ "passed": {"const": true},
27
+ "endpoint": {"type": "string", "pattern": "^https://[^\\s?#]+$"},
28
+ "checks": {"type": "array", "minItems": 1, "items": {"type": "object"}}
29
+ }
30
+ },
31
+ "duplicates": {"type": "array", "maxItems": 0},
32
+ "orphaned": {"type": "array", "maxItems": 0},
33
+ "cleanup": {"type": "object", "additionalProperties": false, "required": ["status", "checked"], "properties": {"status": {"enum": ["not-needed", "passed"]}, "checked": {"const": true}}},
34
+ "mutationExecuted": {"const": true}
35
+ }
36
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/deployer-delegation-v1.schema.json",
4
+ "title": "Maggie deployment deployer delegation evidence v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "host", "user", "releaseRoot", "service", "state", "passed", "checks", "mutation"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-deployment-delegation.v1"},
10
+ "host": {"type": "string", "minLength": 1},
11
+ "user": {"type": "string", "pattern": "^[a-z_][a-z0-9_-]{0,31}\\$?$"},
12
+ "releaseRoot": {"type": "string", "pattern": "^/[^\\r\\n]*$"},
13
+ "service": {"type": "string", "pattern": "^[A-Za-z0-9_.@-]+$"},
14
+ "state": {"enum": ["passed", "failed", "transient", "inconclusive"]},
15
+ "passed": {"type": "boolean"},
16
+ "checks": {"type": "object"},
17
+ "errors": {"type": "array", "items": {"type": "string"}},
18
+ "mutation": {"const": "not executed"}
19
+ }
20
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggiedash.noblox.app/contracts/release-profile-v1.schema.json",
4
+ "title": "Maggie release profile v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "profile", "capabilities"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-release-profile.v1"},
10
+ "profile": {"type": "string", "pattern": "^[a-z][a-z0-9-]{1,63}$"},
11
+ "projectType": {"type": "string", "enum": ["astro", "nextjs", "sveltekit", "nuxt", "vite-react", "vite-vue", "other"]},
12
+ "capabilities": {
13
+ "type": "array",
14
+ "minItems": 1,
15
+ "uniqueItems": true,
16
+ "items": {
17
+ "enum": [
18
+ "deployment",
19
+ "scenario-qa",
20
+ "social-card-audit",
21
+ "migration",
22
+ "maggiedash-health",
23
+ "schedule",
24
+ "analytics-contract",
25
+ "analytics-release-gate",
26
+ "service-facts",
27
+ "maggiedash-schema",
28
+ "editorial-review",
29
+ "durable-site-evidence"
30
+ ]
31
+ }
32
+ }
33
+ }
34
+ }