@topy-ai/maggie 0.7.44 → 0.7.45

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-zh-TW.md CHANGED
@@ -8,11 +8,29 @@ Codex、Claude Code 與相容的 coding agents。
8
8
  ## 安裝
9
9
 
10
10
  ```bash
11
- npx @topy-ai/maggie@0.7.44 init --agent all
12
- npx @topy-ai/maggie@0.7.44 doctor --project .
11
+ npm install --global @topy-ai/maggie
12
+ maggie init --scope user --agent all
13
+ maggie doctor --scope user --agent all
13
14
  ```
14
15
 
15
- 目前 release 是 `0.7.44`。完成 2026-09-24 booking feedback batch:MaggieDash
16
+ 這只會把可重用 skills 和 shared references 安裝到 user-level Claude Code/Codex
17
+ 目錄;`.maggie/`、evidence、contracts、templates、marketplace 和 host files
18
+ 仍然由各 project 自己管理。更新全域安裝:
19
+
20
+ ```bash
21
+ npm install --global @topy-ai/maggie@latest
22
+ maggie update --scope user --agent all
23
+ ```
24
+
25
+ 若要讓 repository 固定自己的 skill 版本,才使用 project-local 安裝:
26
+
27
+ ```bash
28
+ npx @topy-ai/maggie init --scope project --agent all
29
+ npx @topy-ai/maggie doctor --scope project --project .
30
+ ```
31
+
32
+ 目前 release 是 `0.7.45`。新增 Codex/Claude Code 的一次性 user-scope 安裝、bundled
33
+ workflow runner 與 user-scope maintenance commands;並完成 2026-09-24 booking feedback batch:MaggieDash
16
34
  Calendar availability grid/free-slot entry,以及 ORA modal focus restoration
17
35
  的驗證證據。另新增 secret-free Starter marketplace provisioning
18
36
  plan、versioned GitHub/Cloudflare/Starter contracts,以及 deterministic
@@ -57,8 +75,8 @@ maggie booking-delivery --policy .maggie/booking-delivery-policy.json
57
75
  常用流程:
58
76
 
59
77
  ```bash
60
- python3 tools/clis/maggie.py analyze . --json --save
61
- python3 tools/clis/maggie.py bootstrap interview .
78
+ maggie tool maggie.py analyze . --json --save
79
+ maggie tool maggie.py bootstrap interview .
62
80
  maggie memory context --project . --skill <skill-name>
63
81
  maggie doctor --project . --require-bootstrap --strict
64
82
  ```
@@ -82,7 +100,7 @@ structure drift 或重複 treatment product 時 fail closed。
82
100
  deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
83
101
  publish 與 production deployment 需要明確確認。
84
102
 
85
- 目前 source tree 的正式 release 是 `0.7.44`。本版完成 2026-09-24 booking feedback batch:Calendar grid/free-slot entry 與 host modal focus-restoration evidence。本版新增 Starter marketplace provisioning plan;本版完成 feedback batches 22、23:加入 declarative browser interaction evidence、design markup/style/content-key gate、compiler source anchoring、entity double-escape protection、`update --help` read-only、noindex hypothetical indexability、可過期 baseline acknowledgement、DOM-only link extraction regression,以及 MaggieDash 0.2.10 的 content override、短期 browser session、CDN-safe public-session cache contracts。authentication 和 isolated dev server 仍由 host 負責。`maggie-booking` workflow、傳統 email/password、Stripe、Resend、public booking flow 及既有安全邊界維持不變;provider-bound video generation 與 transactional delivery 仍記錄在 issue #39,前一批延後功能仍在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
103
+ 目前 source tree 的正式 release 是 `0.7.45`。本版完成 2026-09-24 booking feedback batch:Calendar grid/free-slot entry 與 host modal focus-restoration evidence。本版新增 Starter marketplace provisioning plan;本版完成 feedback batches 22、23:加入 declarative browser interaction evidence、design markup/style/content-key gate、compiler source anchoring、entity double-escape protection、`update --help` read-only、noindex hypothetical indexability、可過期 baseline acknowledgement、DOM-only link extraction regression,以及 MaggieDash 0.2.10 的 content override、短期 browser session、CDN-safe public-session cache contracts。authentication 和 isolated dev server 仍由 host 負責。`maggie-booking` workflow、傳統 email/password、Stripe、Resend、public booking flow 及既有安全邊界維持不變;provider-bound video generation 與 transactional delivery 仍記錄在 issue #39,前一批延後功能仍在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
86
104
  dispatch、`maggie --version`、feedback batch review 聚合與重複偵測、marketplace enrichment evidence、booking
87
105
  worker/resolver evidence,以及 read-only migration preflight。它也包含 host-owned mobile app surface contract、signed-in
88
106
  camera-state QA、直接 Astro route resolution、correlated feedback batch,以及
package/README.md CHANGED
@@ -12,7 +12,36 @@ composable skills for project bootstrap, authorized website cloning, design
12
12
  systems, SEO/GEO, content operations, marketplace templates, deployment, and
13
13
  persistent project memory.
14
14
 
15
- ## Install
15
+ ## Install once for every project
16
+
17
+ Install the CLI globally and place the reusable skills in the user-level
18
+ directories used by Codex and Claude Code:
19
+
20
+ ```bash
21
+ npm install --global @topy-ai/maggie
22
+ maggie init --scope user --agent all
23
+ maggie doctor --scope user --agent all
24
+ ```
25
+
26
+ This writes only agent skills, shared references, and a user install manifest.
27
+ Project `.maggie/` state, evidence, contracts, templates, marketplace data,
28
+ and host files remain local to each project. Use `maggie tool` when a workflow
29
+ needs a bundled Python CLI; it runs the package copy with the current project
30
+ as its working directory.
31
+
32
+ Upgrade the CLI and user skills together:
33
+
34
+ ```bash
35
+ npm install --global @topy-ai/maggie@latest
36
+ maggie update --scope user --agent all
37
+ ```
38
+
39
+ `--global` is an alias for `--scope user`. Use `--force` only to replace an
40
+ intentionally customized user skill.
41
+
42
+ ## Project-local install
43
+
44
+ Use project scope when a repository must pin or customize its skill files:
16
45
 
17
46
  `site-audit --crawl --save-baseline FILE --reviewer NAME` records a reviewed
18
47
  site contract; `site-audit --crawl --baseline FILE` fails on URL, metadata,
@@ -25,14 +54,14 @@ with explicit `--confirm`, resumable draft batches and no automatic publishing.
25
54
  See the installed localization skill for the adapter protocol and examples.
26
55
 
27
56
  ```bash
28
- npx @topy-ai/maggie init --agent codex
57
+ npx @topy-ai/maggie init --scope project --agent codex
29
58
  ```
30
59
 
31
60
  Install for Claude Code or both supported agent directories:
32
61
 
33
62
  ```bash
34
- npx @topy-ai/maggie init --agent claude
35
- npx @topy-ai/maggie init --agent all
63
+ npx @topy-ai/maggie init --scope project --agent claude
64
+ npx @topy-ai/maggie init --scope project --agent all
36
65
  ```
37
66
 
38
67
  Update an existing project while preserving local customisations by default:
@@ -60,7 +89,9 @@ Maggie keeps the existing project foundation and asks for decisions before
60
89
  shared routes, analytics, or publishing boundaries change. The current
61
90
  package ships 20 installable skills and a local-first MaggieDash foundation.
62
91
 
63
- The current release is `0.7.44`. It closes the 2026-09-24 booking feedback
92
+ The current release is `0.7.45`. It adds one-time user-scope installation for
93
+ Codex and Claude Code, a bundled workflow runner, and scoped maintenance
94
+ commands. It also closes the 2026-09-24 booking feedback
64
95
  batch with the canonical MaggieDash Calendar availability grid/free-slot
65
96
  entry and documented ORA modal focus-restoration evidence. It also adds the
66
97
  secret-free Starter marketplace
@@ -313,8 +344,8 @@ For a genuinely empty project, create the host framework first, then bootstrap
313
344
  Maggie in this order:
314
345
 
315
346
  ```bash
316
- python3 tools/clis/maggie.py analyze . --json --save
317
- python3 tools/clis/maggie.py bootstrap interview .
347
+ maggie tool maggie.py analyze . --json --save
348
+ maggie tool maggie.py bootstrap interview .
318
349
  maggie dash install --project . --confirm
319
350
  maggie dash init --project . --confirm
320
351
  maggie dash migrate --project . --confirm
@@ -336,8 +367,9 @@ The CLI provides the installer plus durable workflow commands:
336
367
 
337
368
  ```text
338
369
  maggie --version | version
339
- maggie init | install | update | remove | list | doctor
340
- maggie cleanup --project . [--confirm]
370
+ maggie init | install | update | remove | list | doctor [--scope project|user]
371
+ maggie cleanup [--scope project|user] --project . [--confirm]
372
+ maggie tool <bundled-cli.py> [arguments]
341
373
  maggie bootstrap interview | phase ...
342
374
  maggie dash install | init | status | migrate | cms ...
343
375
  maggie dash transition ... # explicit content approval transition
@@ -599,8 +631,8 @@ artifact schemas.
599
631
  Recommended upgrade sequence for the current release:
600
632
 
601
633
  ```bash
602
- npx @topy-ai/maggie@0.7.39 update --project . --force
603
- npx @topy-ai/maggie@0.7.39 cleanup --project .
634
+ npx @topy-ai/maggie@0.7.45 update --scope project --project . --force
635
+ npx @topy-ai/maggie@0.7.45 cleanup --scope project --project .
604
636
  ```
605
637
 
606
638
  Maintainers should pass npm credentials through the repository helper, never
@@ -1028,7 +1060,7 @@ See the [NoBlox preview gallery](https://github.com/TOPY-AI-LTD/NoBlox-App-Templ
1028
1060
  For an explicit brand change, run Maggie Design rebrand before `review`:
1029
1061
 
1030
1062
  ```bash
1031
- python3 tools/clis/maggie_design.py rebrand \
1063
+ maggie tool maggie_design.py rebrand \
1032
1064
  --template marketplace/templates/<id> \
1033
1065
  --source-brand "Source Brand" --brand "Target Brand"
1034
1066
  ```
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
@@ -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);
@@ -14,14 +14,14 @@ project-specific design, service, SEO, operations, and deployment adapters.
14
14
 
15
15
  ## Workflow
16
16
 
17
- 1. Run `python3 tools/clis/maggie.py analyze PROJECT --json --save` and record
17
+ 1. Run `maggie tool maggie.py analyze PROJECT --json --save` and record
18
18
  evidence for framework, language, UI, icons, data, content, routes, and
19
19
  deployment.
20
- 2. Run `python3 tools/clis/maggie.py bootstrap interview PROJECT`; review the
20
+ 2. Run `maggie tool maggie.py bootstrap interview PROJECT`; review the
21
21
  checkpoint and confirm foundation, experience, data, and publishing choices.
22
22
  3. For a new MaggieDash project, run:
23
- `python3 tools/clis/maggie_dash.py init --project PROJECT --confirm` and
24
- `python3 tools/clis/maggie_dash.py migrate --project PROJECT --confirm`.
23
+ `maggie tool maggie_dash.py init --project PROJECT --confirm` and
24
+ `maggie tool maggie_dash.py migrate --project PROJECT --confirm`.
25
25
  If the first-party dashboard is installed, also run
26
26
  `maggie dash install --project PROJECT --confirm`; this copies the
27
27
  provider-neutral backend type boundary and idempotent starter schema beside
@@ -30,8 +30,8 @@ project-specific design, service, SEO, operations, and deployment adapters.
30
30
  4. Advance the ordered foundation gate:
31
31
 
32
32
  ```bash
33
- python3 tools/clis/maggie.py bootstrap phase start maggiedash-foundation PROJECT
34
- python3 tools/clis/maggie.py bootstrap phase pass maggiedash-foundation PROJECT \
33
+ maggie tool maggie.py bootstrap phase start maggiedash-foundation PROJECT
34
+ maggie tool maggie.py bootstrap phase pass maggiedash-foundation PROJECT \
35
35
  --validation "maggie analyze PROJECT; npm run build"
36
36
  ```
37
37
 
@@ -41,7 +41,7 @@ project-specific design, service, SEO, operations, and deployment adapters.
41
41
  7. Import external content through the provider-neutral draft workflow:
42
42
 
43
43
  ```bash
44
- python3 tools/clis/maggie_content.py records.json --project PROJECT \
44
+ maggie tool maggie_content.py records.json --project PROJECT \
45
45
  --source PROVIDER --confirm
46
46
  ```
47
47