@topy-ai/maggie 0.7.42 → 0.7.44

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,15 @@ Codex、Claude Code 與相容的 coding agents。
8
8
  ## 安裝
9
9
 
10
10
  ```bash
11
- npx @topy-ai/maggie@0.7.42 init --agent all
12
- npx @topy-ai/maggie@0.7.42 doctor --project .
11
+ npx @topy-ai/maggie@0.7.44 init --agent all
12
+ npx @topy-ai/maggie@0.7.44 doctor --project .
13
13
  ```
14
14
 
15
- 目前 release 是 `0.7.42`。新增 redacted Gemini video-provider evidence gate,
15
+ 目前 release 是 `0.7.44`。完成 2026-09-24 booking feedback batch:MaggieDash
16
+ Calendar availability grid/free-slot entry,以及 ORA modal focus restoration
17
+ 的驗證證據。另新增 secret-free Starter marketplace provisioning
18
+ plan、versioned GitHub/Cloudflare/Starter contracts,以及 deterministic
19
+ `maggie marketplace plan` dry-run CLI。0.7.42 新增 redacted Gemini video-provider evidence gate,
16
20
  驗證 provider acknowledgement、idempotency、moderation、object-storage
17
21
  readback、bounded retry 與 git provenance,並拒絕 raw provider payload 或
18
22
  input asset 欄位。0.7.41 完成 feedback batches 22、23:加入 declarative
@@ -78,7 +82,7 @@ structure drift 或重複 treatment product 時 fail closed。
78
82
  deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
79
83
  publish 與 production deployment 需要明確確認。
80
84
 
81
- 目前 source tree 的正式 release 是 `0.7.41`。本版完成 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
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
82
86
  dispatch、`maggie --version`、feedback batch review 聚合與重複偵測、marketplace enrichment evidence、booking
83
87
  worker/resolver evidence,以及 read-only migration preflight。它也包含 host-owned mobile app surface contract、signed-in
84
88
  camera-state QA、直接 Astro route resolution、correlated feedback batch,以及
package/README.md CHANGED
@@ -60,7 +60,15 @@ Maggie keeps the existing project foundation and asks for decisions before
60
60
  shared routes, analytics, or publishing boundaries change. The current
61
61
  package ships 20 installable skills and a local-first MaggieDash foundation.
62
62
 
63
- The current release is `0.7.42`. It adds a redacted Gemini video-provider
63
+ The current release is `0.7.44`. It closes the 2026-09-24 booking feedback
64
+ batch with the canonical MaggieDash Calendar availability grid/free-slot
65
+ entry and documented ORA modal focus-restoration evidence. It also adds the
66
+ secret-free Starter marketplace
67
+ provisioning plan and bundles versioned GitHub repository, Cloudflare Pages,
68
+ Starter site, and Starter release contracts. `maggie marketplace plan` emits a
69
+ deterministic dry-run plan for a public Cal.com URL, static Cloudflare Pages,
70
+ no database, and ordered idempotent steps; it never stores credentials or
71
+ mutates provider state. The 0.7.42 release adds a redacted Gemini video-provider
64
72
  evidence gate for provider acknowledgement, idempotency, moderation,
65
73
  object-storage readback, bounded retry, and git provenance; unknown raw
66
74
  provider payload and input-asset fields fail validation. The 0.7.41 release
package/bin/maggie.js CHANGED
@@ -80,6 +80,7 @@ Usage:
80
80
  maggie remove [SKILL ...] [--project PATH] [--agent codex|claude|all]
81
81
  maggie cleanup --project PATH [--confirm]
82
82
  maggie marketplace add <template-url> clone <preview-url> --rename <id>
83
+ maggie marketplace plan --workspace-id ID --merchant-id ID --site-slug SLUG --cal-url URL [--output FILE]
83
84
  maggie marketplace list
84
85
  maggie marketplace suggest "premium London medical spa"
85
86
  maggie marketplace inspect <id>
@@ -124,6 +125,7 @@ Usage:
124
125
  maggie service convert-page <page-path> --project PATH
125
126
  maggie service match-pages --project PATH --pages-dir src/pages
126
127
  maggie deployment --project PATH --target vps-with-cloudflare-dns
128
+ maggie deployment worker-secrets --project PATH --environment staging --required RESEND_API_KEY
127
129
  maggie deployment readiness --project PATH
128
130
  maggie deployment parity --project PATH --evidence FILE --output FILE
129
131
  maggie deployment canary --project PATH --asset URL=SHA256 --render-report report.json --output docs/deployment-canary.json
@@ -0,0 +1,90 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/cloudflare-pages-v1.schema.json",
4
+ "title": "Maggie marketplace Cloudflare Pages adapter result v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "provider", "state", "sourceRevision", "retry", "privacy"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-cloudflare-pages.v1"},
10
+ "provider": {"const": "cloudflare-pages"},
11
+ "state": {"enum": ["succeeded", "retryable", "dead-letter"]},
12
+ "projectOutcome": {"enum": ["created", "reused", "updated"]},
13
+ "domainOutcome": {"enum": ["created", "reused", "updated"]},
14
+ "project": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["externalReference", "name", "productionBranch", "repositoryOwner", "repositoryName", "sourceType"],
18
+ "properties": {
19
+ "externalReference": {"type": "string", "minLength": 1, "maxLength": 200},
20
+ "name": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$"},
21
+ "productionBranch": {"type": "string", "pattern": "^[A-Za-z0-9._/-]+$"},
22
+ "repositoryOwner": {"type": "string", "minLength": 1, "maxLength": 100},
23
+ "repositoryName": {"type": "string", "minLength": 1, "maxLength": 100},
24
+ "sourceType": {"enum": ["github", "direct-upload"]},
25
+ "canonicalUrl": {"type": ["string", "null"], "format": "uri", "pattern": "^https://"}
26
+ }
27
+ },
28
+ "domain": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["externalReference", "name", "status", "validationStatus", "verificationStatus"],
32
+ "properties": {
33
+ "externalReference": {"type": "string", "minLength": 1, "maxLength": 255},
34
+ "name": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.bea\\.gift$"},
35
+ "status": {"enum": ["initializing", "pending", "active", "deactivated", "blocked", "error", "unknown"]},
36
+ "validationStatus": {"type": "string", "minLength": 1, "maxLength": 80},
37
+ "verificationStatus": {"type": "string", "minLength": 1, "maxLength": 80}
38
+ }
39
+ },
40
+ "deployment": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": ["externalReference", "environment", "stageStatus", "skipped"],
44
+ "properties": {
45
+ "externalReference": {"type": "string", "minLength": 1, "maxLength": 200},
46
+ "environment": {"enum": ["production", "preview", "unknown"]},
47
+ "sourceRevision": {"type": ["string", "null"], "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
48
+ "branch": {"type": ["string", "null"], "pattern": "^[A-Za-z0-9._/-]+$"},
49
+ "stageStatus": {"enum": ["success", "idle", "active", "failure", "canceled", "unknown"]},
50
+ "url": {"type": ["string", "null"], "format": "uri", "pattern": "^https://"},
51
+ "skipped": {"type": "boolean"}
52
+ }
53
+ },
54
+ "publicReadback": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["url", "status", "https", "canonical"],
58
+ "properties": {
59
+ "url": {"type": "string", "format": "uri", "pattern": "^https://"},
60
+ "status": {"type": "integer", "minimum": 100, "maximum": 599},
61
+ "https": {"const": true},
62
+ "canonical": {"const": true},
63
+ "contentType": {"type": ["string", "null"], "maxLength": 160}
64
+ }
65
+ },
66
+ "sourceRevision": {"type": "string", "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
67
+ "errorCode": {"type": "string", "pattern": "^cloudflare_[a-z0-9_]{1,80}$"},
68
+ "retry": {
69
+ "type": "object",
70
+ "additionalProperties": false,
71
+ "required": ["allowed", "attempt", "maxAttempts"],
72
+ "properties": {
73
+ "allowed": {"type": "boolean"},
74
+ "attempt": {"type": "integer", "minimum": 1, "maximum": 3},
75
+ "maxAttempts": {"const": 3},
76
+ "afterSeconds": {"type": "integer", "minimum": 0, "maximum": 900}
77
+ }
78
+ },
79
+ "privacy": {
80
+ "type": "object",
81
+ "additionalProperties": false,
82
+ "required": ["tokenIncluded", "rawResponseIncluded", "secretFree"],
83
+ "properties": {
84
+ "tokenIncluded": {"const": false},
85
+ "rawResponseIncluded": {"const": false},
86
+ "secretFree": {"const": true}
87
+ }
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/enrichment-v1.schema.json",
4
+ "title": "Maggie marketplace enrichment evidence v1",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "candidateId", "status", "passed", "errors", "crawl", "audit", "snapshot", "seoBackfill", "retry", "privacy"],
7
+ "properties": {
8
+ "schemaVersion": {"const": "maggie-marketplace-enrichment.v1"},
9
+ "candidateId": {"type": "string", "minLength": 1},
10
+ "status": {"enum": ["passed", "failed"]}, "passed": {"type": "boolean"},
11
+ "errors": {"type": "array", "items": {"type": "string"}},
12
+ "crawl": {"type": "object"}, "audit": {"type": "object"}, "snapshot": {"type": "object"}, "seoBackfill": {"type": "object"},
13
+ "retry": {"type": "object", "required": ["allowed"]},
14
+ "privacy": {"type": "object", "required": ["rawHtmlIncluded", "responseBodiesIncluded", "localPathsIncluded", "secretsRedacted"]}
15
+ },
16
+ "additionalProperties": false
17
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/github-repository-v1.schema.json",
4
+ "title": "Maggie marketplace GitHub repository adapter result v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "provider", "state", "sourceRevision", "retry", "privacy"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-github-repository.v1"},
10
+ "provider": {"const": "github"},
11
+ "state": {"enum": ["succeeded", "retryable", "dead-letter"]},
12
+ "outcome": {"enum": ["created", "reused", "updated"]},
13
+ "repository": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["externalReference", "owner", "name", "private", "defaultBranch", "htmlUrl"],
17
+ "properties": {
18
+ "externalReference": {"type": "string", "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"},
19
+ "owner": {"type": "string", "pattern": "^[A-Za-z0-9-]{1,39}$"},
20
+ "name": {"type": "string", "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$"},
21
+ "private": {"type": "boolean"},
22
+ "defaultBranch": {"type": "string", "pattern": "^[A-Za-z0-9._/-]+$"},
23
+ "htmlUrl": {"type": "string", "format": "uri", "pattern": "^https://github\\.com/"}
24
+ }
25
+ },
26
+ "sourceRevision": {"type": "string", "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
27
+ "errorCode": {"type": "string", "pattern": "^github_[a-z0-9_]{1,80}$"},
28
+ "retry": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["allowed", "attempt", "maxAttempts"],
32
+ "properties": {
33
+ "allowed": {"type": "boolean"},
34
+ "attempt": {"type": "integer", "minimum": 1, "maximum": 3},
35
+ "maxAttempts": {"const": 3},
36
+ "afterSeconds": {"type": "integer", "minimum": 0, "maximum": 900}
37
+ }
38
+ },
39
+ "privacy": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["tokenIncluded", "rawResponseIncluded", "secretFree"],
43
+ "properties": {
44
+ "tokenIncluded": {"const": false},
45
+ "rawResponseIncluded": {"const": false},
46
+ "secretFree": {"const": true}
47
+ }
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/starter-provisioning-v1.schema.json",
4
+ "title": "Maggie marketplace Starter provisioning plan v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "plan", "mode", "workspace", "site", "booking", "steps", "security", "nextActions"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-starter-provisioning.v1"},
10
+ "plan": {"const": "starter"},
11
+ "mode": {"const": "dry-run"},
12
+ "workspace": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["workspaceId", "merchantId"],
16
+ "properties": {
17
+ "workspaceId": {"type": "string", "minLength": 1, "maxLength": 128, "pattern": "^[A-Za-z0-9_-]+$"},
18
+ "merchantId": {"type": "string", "minLength": 1, "maxLength": 128, "pattern": "^[A-Za-z0-9_-]+$"}
19
+ }
20
+ },
21
+ "site": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "required": ["slug", "hostname", "repository", "branch", "runtime", "database"],
25
+ "properties": {
26
+ "slug": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},
27
+ "hostname": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.bea\\.gift$"},
28
+ "repository": {"type": "string", "pattern": "^bea-merchant-[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},
29
+ "templateId": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$"},
30
+ "branch": {"type": "string", "pattern": "^[A-Za-z0-9._/-]+$"},
31
+ "runtime": {"const": "cloudflare-pages-static"},
32
+ "database": {"const": "none"}
33
+ }
34
+ },
35
+ "booking": {
36
+ "type": "object",
37
+ "additionalProperties": false,
38
+ "required": ["mode", "sourceOfTruth", "publicUrl", "embed"],
39
+ "properties": {
40
+ "mode": {"const": "cal-public"},
41
+ "sourceOfTruth": {"const": "cal.com"},
42
+ "publicUrl": {"type": "string", "format": "uri", "pattern": "^https://"},
43
+ "embed": {"enum": ["link", "iframe"]}
44
+ }
45
+ },
46
+ "steps": {
47
+ "type": "array",
48
+ "minItems": 6,
49
+ "items": {
50
+ "type": "object",
51
+ "additionalProperties": false,
52
+ "required": ["id", "status", "idempotencyKey"],
53
+ "properties": {
54
+ "id": {"enum": ["entitlement", "repository", "site-deploy", "hostname", "booking", "release-readback"]},
55
+ "status": {"const": "planned"},
56
+ "idempotencyKey": {"type": "string", "pattern": "^starter:[a-f0-9]{64}$"}
57
+ }
58
+ }
59
+ },
60
+ "security": {
61
+ "type": "object",
62
+ "additionalProperties": false,
63
+ "required": ["secretsInArtifact", "calCredentialsStored", "databaseRequired"],
64
+ "properties": {
65
+ "secretsInArtifact": {"const": false},
66
+ "calCredentialsStored": {"const": false},
67
+ "databaseRequired": {"const": false}
68
+ }
69
+ },
70
+ "nextActions": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}
71
+ }
72
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/starter-release-v1.schema.json",
4
+ "title": "Maggie marketplace Starter release evidence v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "status", "checkedAt", "sourceRevision", "site", "booking", "artifact", "repository", "cloudflare", "errors", "security"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-starter-release.v1"},
10
+ "status": {"enum": ["passed", "blocked"]},
11
+ "checkedAt": {"type": "string", "format": "date-time"},
12
+ "sourceRevision": {"type": "string", "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
13
+ "site": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["slug", "hostname", "templateId"],
17
+ "properties": {
18
+ "slug": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},
19
+ "hostname": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.bea\\.gift$"},
20
+ "templateId": {"type": "string", "minLength": 1, "maxLength": 120}
21
+ }
22
+ },
23
+ "booking": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["mode", "publicUrl", "embed", "readback"],
27
+ "properties": {
28
+ "mode": {"const": "cal-public"},
29
+ "publicUrl": {"type": "string", "format": "uri", "pattern": "^https://"},
30
+ "embed": {"enum": ["link", "iframe"]},
31
+ "readback": {"enum": ["passed", "failed"]}
32
+ }
33
+ },
34
+ "artifact": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["readback", "fileCount"],
38
+ "properties": {"readback": {"enum": ["passed", "failed"]}, "fileCount": {"type": "integer", "minimum": 1}}
39
+ },
40
+ "repository": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": ["readback", "externalReference", "branch"],
44
+ "properties": {
45
+ "readback": {"enum": ["passed", "failed"]},
46
+ "externalReference": {"type": ["string", "null"], "maxLength": 200},
47
+ "branch": {"type": ["string", "null"], "pattern": "^[A-Za-z0-9._/-]+$"},
48
+ "commitSha": {"type": ["string", "null"], "pattern": "^[A-Fa-f0-9]{7,64}$"}
49
+ }
50
+ },
51
+ "cloudflare": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["readback", "projectReference", "deploymentReference", "hostnameReference", "publicUrl"],
55
+ "properties": {
56
+ "readback": {"enum": ["passed", "failed"]},
57
+ "projectReference": {"type": ["string", "null"], "maxLength": 200},
58
+ "deploymentReference": {"type": ["string", "null"], "maxLength": 200},
59
+ "hostnameReference": {"type": ["string", "null"], "maxLength": 255},
60
+ "publicUrl": {"type": ["string", "null"], "format": "uri", "pattern": "^https://"}
61
+ }
62
+ },
63
+ "errors": {"type": "array", "items": {"type": "string", "pattern": "^[a-z0-9_]{1,100}$"}},
64
+ "security": {
65
+ "type": "object",
66
+ "additionalProperties": false,
67
+ "required": ["secretsInEvidence", "databaseRequired"],
68
+ "properties": {"secretsInEvidence": {"const": false}, "databaseRequired": {"const": false}}
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://maggie.noblox.app/contracts/maggie-marketplace/starter-site-v1.schema.json",
4
+ "title": "Maggie marketplace Starter site artifact v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "templateId", "sourceRevision", "site", "booking", "files", "security"],
8
+ "properties": {
9
+ "schemaVersion": {"const": "maggie-marketplace-starter-site.v1"},
10
+ "templateId": {"type": "string", "minLength": 1, "maxLength": 120},
11
+ "sourceRevision": {"type": "string", "pattern": "^[A-Za-z0-9._:/-]{1,200}$"},
12
+ "site": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["slug", "hostname"],
16
+ "properties": {
17
+ "slug": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},
18
+ "hostname": {"type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.bea\\.gift$"}
19
+ }
20
+ },
21
+ "booking": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "required": ["mode", "publicUrl", "embed"],
25
+ "properties": {
26
+ "mode": {"const": "cal-public"},
27
+ "publicUrl": {"type": "string", "format": "uri", "pattern": "^https://"},
28
+ "embed": {"enum": ["link", "iframe"]}
29
+ }
30
+ },
31
+ "files": {
32
+ "type": "array",
33
+ "minItems": 1,
34
+ "items": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["path", "sha256", "bytes"],
38
+ "properties": {
39
+ "path": {"type": "string", "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9_./-]+$"},
40
+ "sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
41
+ "bytes": {"type": "integer", "minimum": 0}
42
+ }
43
+ }
44
+ },
45
+ "security": {
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "required": ["runtimeSecrets", "credentialsStored", "databaseRequired"],
49
+ "properties": {
50
+ "runtimeSecrets": {"const": false},
51
+ "credentialsStored": {"const": false},
52
+ "databaseRequired": {"const": false}
53
+ }
54
+ }
55
+ }
56
+ }
@@ -145,6 +145,24 @@ This command never deploys. Production execution still requires the shared
145
145
  decision loop, explicit confirmation, migration/rollback details, and the
146
146
  provider-specific deploy command.
147
147
 
148
+ For Cloudflare Workers, check the required secret *names* before a canary. The
149
+ command never reads, prints, uploads, or changes secret values:
150
+
151
+ ```bash
152
+ maggie deployment worker-secrets --project . \
153
+ --environment staging \
154
+ --required DATABASE_URL \
155
+ --required BOOKING_TOKEN_SECRET \
156
+ --required RESEND_API_KEY \
157
+ --required RESEND_FROM_EMAIL \
158
+ --output .maggie/deployment/worker-secrets.json
159
+ ```
160
+
161
+ It calls `wrangler secret list --format json` and stores only configured and
162
+ missing binding names. A missing required name fails closed. Use a redacted
163
+ JSON name snapshot with `--snapshot` for CI or a host adapter that cannot
164
+ expose Cloudflare credentials to Maggie; the snapshot must contain names only.
165
+
148
166
  For a VPS adapter, check the project `.env` by variable name before reporting
149
167
  credentials as unavailable:
150
168
 
@@ -77,6 +77,110 @@ def credential_preflight_main(argv: list[str]) -> int:
77
77
  return 0 if result["passed"] else 1
78
78
 
79
79
 
80
+ def _secret_names(payload: object) -> list[str]:
81
+ """Extract only binding names from a Wrangler secret-list response."""
82
+ entries: object = payload
83
+ if isinstance(payload, dict):
84
+ entries = payload.get("secrets", payload.get("result", []))
85
+ if not isinstance(entries, list):
86
+ return []
87
+ names: set[str] = set()
88
+ for entry in entries:
89
+ if isinstance(entry, str):
90
+ name = entry
91
+ elif isinstance(entry, dict):
92
+ name = entry.get("name", entry.get("key", ""))
93
+ else:
94
+ continue
95
+ if isinstance(name, str) and re.fullmatch(r"[A-Z][A-Z0-9_]{1,127}", name):
96
+ names.add(name)
97
+ return sorted(names)
98
+
99
+
100
+ def worker_secret_preflight(
101
+ project: Path,
102
+ environment: str,
103
+ required: list[str],
104
+ snapshot: Path | None = None,
105
+ worker_name: str | None = None,
106
+ wrangler_command: list[str] | None = None,
107
+ ) -> dict:
108
+ """Read Worker secret names without reading or mutating secret values."""
109
+ required_names = sorted({name.strip() for name in required if name.strip()})
110
+ errors: list[str] = []
111
+ if not required_names:
112
+ errors.append("at least one --required secret name is required")
113
+ if any(not re.fullmatch(r"[A-Z][A-Z0-9_]{1,127}", name) for name in required_names):
114
+ errors.append("required secret names must match [A-Z][A-Z0-9_]{1,127}")
115
+
116
+ source = "cloudflare-wrangler-secret-list"
117
+ configured: list[str] = []
118
+ if not errors and snapshot:
119
+ source = "redacted-secret-name-snapshot"
120
+ try:
121
+ configured = _secret_names(json.loads(snapshot.read_text(encoding="utf-8")))
122
+ except (OSError, json.JSONDecodeError) as error:
123
+ errors.append(f"secret-name snapshot is unreadable: {error}")
124
+ elif not errors:
125
+ command = list(wrangler_command or ["npx", "wrangler"])
126
+ command.extend(["secret", "list", "--env", environment, "--format", "json"])
127
+ if worker_name:
128
+ command.extend(["--name", worker_name])
129
+ try:
130
+ completed = subprocess.run(command, cwd=project, capture_output=True, text=True, check=False, timeout=30)
131
+ except (OSError, subprocess.SubprocessError):
132
+ completed = None
133
+ if completed is None or completed.returncode != 0:
134
+ errors.append("Wrangler secret-list command failed; inspect authentication and Worker target")
135
+ else:
136
+ try:
137
+ configured = _secret_names(json.loads(completed.stdout))
138
+ except json.JSONDecodeError:
139
+ errors.append("Wrangler secret-list response was not valid JSON")
140
+
141
+ missing = sorted(set(required_names) - set(configured))
142
+ errors.extend(f"required Worker secret is missing: {name}" for name in missing)
143
+ return {
144
+ "schemaVersion": "maggie-worker-secrets.v1",
145
+ "project": str(project),
146
+ "provider": "cloudflare-workers",
147
+ "environment": environment,
148
+ "worker": worker_name,
149
+ "required": required_names,
150
+ "configured": configured,
151
+ "missing": missing,
152
+ "source": source,
153
+ "valuesReturned": False,
154
+ "passed": not errors,
155
+ "errors": errors,
156
+ "mutation": "not executed",
157
+ }
158
+
159
+
160
+ def worker_secret_preflight_main(argv: list[str]) -> int:
161
+ parser = argparse.ArgumentParser(description="Check Cloudflare Worker secret names without reading or changing secret values.")
162
+ parser.add_argument("--project", default=".")
163
+ parser.add_argument("--environment", choices=("staging", "production"), default="staging")
164
+ parser.add_argument("--required", action="append", default=[], help="required secret name; repeat for each binding")
165
+ parser.add_argument("--worker-name")
166
+ parser.add_argument("--snapshot", help="offline JSON containing only secret names")
167
+ parser.add_argument("--output")
168
+ args = parser.parse_args(argv)
169
+ result = worker_secret_preflight(
170
+ Path(args.project).expanduser().resolve(),
171
+ args.environment,
172
+ args.required,
173
+ Path(args.snapshot).expanduser().resolve() if args.snapshot else None,
174
+ args.worker_name,
175
+ )
176
+ if args.output:
177
+ output = Path(args.output).resolve()
178
+ output.parent.mkdir(parents=True, exist_ok=True)
179
+ output.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
180
+ print(json.dumps(result, indent=2, ensure_ascii=False))
181
+ return 0 if result["passed"] else 1
182
+
183
+
80
184
  def preflight(project: Path, target: str, environment: str) -> dict:
81
185
  package_path = project / "package.json"
82
186
  package = json.loads(package_path.read_text(encoding="utf-8")) if package_path.exists() else {}
@@ -390,6 +494,8 @@ def write_release_runner(path: Path, plan: dict) -> None:
390
494
  def main() -> int:
391
495
  if sys.argv[1:2] == ["credential-preflight"]:
392
496
  return credential_preflight_main(sys.argv[2:])
497
+ if sys.argv[1:2] == ["worker-secrets"]:
498
+ return worker_secret_preflight_main(sys.argv[2:])
393
499
  parser = argparse.ArgumentParser(description=__doc__)
394
500
  parser.add_argument("project", nargs="?", default=".")
395
501
  parser.add_argument("--target", choices=("detected", "cloudflare", "vps", "vps-with-cloudflare-dns", "gcp", "aws"), default="detected")
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import hashlib
6
7
  import json
7
8
  import re
8
9
  import shutil
@@ -10,6 +11,7 @@ import subprocess
10
11
  import sys
11
12
  import urllib.error
12
13
  import urllib.request
14
+ from urllib.parse import urlparse
13
15
  from datetime import datetime, timezone
14
16
  from html.parser import HTMLParser
15
17
  from html import escape, unescape
@@ -57,6 +59,137 @@ def now() -> str:
57
59
  return datetime.now(timezone.utc).isoformat()
58
60
 
59
61
 
62
+ STARTER_PLAN_SCHEMA_VERSION = "maggie-marketplace-starter-provisioning.v1"
63
+ STARTER_STEP_IDS = (
64
+ "entitlement",
65
+ "repository",
66
+ "site-deploy",
67
+ "hostname",
68
+ "booking",
69
+ "release-readback",
70
+ )
71
+
72
+
73
+ def _starter_slug(value: str) -> str:
74
+ slug = str(value or "").strip().lower()
75
+ if not re.fullmatch(r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?", slug):
76
+ raise ValueError("Starter site slug must be 1-63 lowercase letters, numbers, or hyphens")
77
+ return slug
78
+
79
+
80
+ def _starter_public_url(value: str) -> str:
81
+ url = str(value or "").strip()
82
+ parsed = urlparse(url)
83
+ if parsed.scheme != "https" or not parsed.netloc or parsed.username or parsed.password or parsed.fragment:
84
+ raise ValueError("Starter Cal public URL must be an https URL without credentials or a fragment")
85
+ return url
86
+
87
+
88
+ def build_starter_plan(
89
+ workspace_id: str,
90
+ merchant_id: str,
91
+ slug: str,
92
+ cal_url: str,
93
+ embed: str = "link",
94
+ branch: str = "main",
95
+ ) -> dict:
96
+ workspace = str(workspace_id or "").strip()
97
+ merchant = str(merchant_id or "").strip()
98
+ if not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", workspace) or not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", merchant):
99
+ raise ValueError("workspace and merchant IDs must use letters, numbers, underscores, or hyphens")
100
+ site_slug = _starter_slug(slug)
101
+ public_url = _starter_public_url(cal_url)
102
+ if embed not in {"link", "iframe"}:
103
+ raise ValueError("Starter booking embed must be link or iframe")
104
+ if not re.fullmatch(r"[A-Za-z0-9._/-]+", branch or ""):
105
+ raise ValueError("Starter branch contains unsupported characters")
106
+ fingerprint = hashlib.sha256(
107
+ "|".join((workspace, merchant, site_slug, public_url, embed, branch)).encode("utf-8")
108
+ ).hexdigest()
109
+ return {
110
+ "schemaVersion": STARTER_PLAN_SCHEMA_VERSION,
111
+ "plan": "starter",
112
+ "mode": "dry-run",
113
+ "workspace": {"workspaceId": workspace, "merchantId": merchant},
114
+ "site": {
115
+ "slug": site_slug,
116
+ "hostname": f"{site_slug}.bea.gift",
117
+ "repository": f"bea-merchant-{site_slug}",
118
+ "branch": branch,
119
+ "runtime": "cloudflare-pages-static",
120
+ "database": "none",
121
+ },
122
+ "booking": {"mode": "cal-public", "sourceOfTruth": "cal.com", "publicUrl": public_url, "embed": embed},
123
+ "steps": [
124
+ {"id": step_id, "status": "planned", "idempotencyKey": f"starter:{fingerprint}"}
125
+ for step_id in STARTER_STEP_IDS
126
+ ],
127
+ "security": {
128
+ "secretsInArtifact": False,
129
+ "calCredentialsStored": False,
130
+ "databaseRequired": False,
131
+ },
132
+ "nextActions": [
133
+ "Confirm the merchant owns the supplied Cal public booking URL",
134
+ "Create or reconcile the GitHub repository and static site artifact",
135
+ "Deploy the generated site to Cloudflare Pages and read back the hostname",
136
+ ],
137
+ }
138
+
139
+
140
+ def validate_starter_plan(plan: dict) -> list[str]:
141
+ errors = []
142
+ if not isinstance(plan, dict):
143
+ return ["plan must be an object"]
144
+ if plan.get("schemaVersion") != STARTER_PLAN_SCHEMA_VERSION:
145
+ errors.append("unsupported Starter plan schema")
146
+ if plan.get("plan") != "starter" or plan.get("mode") != "dry-run":
147
+ errors.append("Starter plan must be a dry-run")
148
+ site = plan.get("site") if isinstance(plan.get("site"), dict) else {}
149
+ if site.get("runtime") != "cloudflare-pages-static" or site.get("database") != "none":
150
+ errors.append("Starter site must be static Cloudflare Pages with no database")
151
+ booking = plan.get("booking") if isinstance(plan.get("booking"), dict) else {}
152
+ if booking.get("mode") != "cal-public" or booking.get("sourceOfTruth") != "cal.com":
153
+ errors.append("Starter booking must use a public Cal URL")
154
+ try:
155
+ _starter_public_url(booking.get("publicUrl", ""))
156
+ except ValueError as error:
157
+ errors.append(str(error))
158
+ security = plan.get("security") if isinstance(plan.get("security"), dict) else {}
159
+ for field in ("secretsInArtifact", "calCredentialsStored", "databaseRequired"):
160
+ if security.get(field) is not False:
161
+ errors.append(f"security.{field} must be false")
162
+ steps = plan.get("steps")
163
+ if not isinstance(steps, list) or [item.get("id") for item in steps if isinstance(item, dict)] != list(STARTER_STEP_IDS):
164
+ errors.append("Starter steps must be ordered and complete")
165
+ else:
166
+ for item in steps:
167
+ if item.get("status") != "planned" or not re.fullmatch(r"starter:[a-f0-9]{64}", str(item.get("idempotencyKey", ""))):
168
+ errors.append(f"invalid Starter step: {item.get('id')}")
169
+ return errors
170
+
171
+
172
+ def starter_plan(args) -> int:
173
+ plan = build_starter_plan(
174
+ args.workspace_id,
175
+ args.merchant_id,
176
+ args.site_slug,
177
+ args.cal_url,
178
+ args.embed,
179
+ args.branch,
180
+ )
181
+ errors = validate_starter_plan(plan)
182
+ if errors:
183
+ raise ValueError("invalid generated Starter plan: " + "; ".join(errors))
184
+ rendered = json.dumps(plan, indent=2, ensure_ascii=False) + "\n"
185
+ if args.output:
186
+ output = args.output.resolve()
187
+ output.parent.mkdir(parents=True, exist_ok=True)
188
+ output.write_text(rendered, encoding="utf-8")
189
+ print(rendered, end="")
190
+ return 0
191
+
192
+
60
193
  def repo_root() -> Path:
61
194
  return Path(__file__).resolve().parents[2]
62
195
 
@@ -2140,6 +2273,16 @@ def main() -> int:
2140
2273
  add.add_argument("--sample", type=Path)
2141
2274
  add.add_argument("--force", action="store_true")
2142
2275
  add.set_defaults(func=lambda args: run_marketplace_job(argparse.Namespace(**{**vars(args), "template_url": args.template_url, "preview_url": args.preview_url})))
2276
+ plan = sub.add_parser("plan", help="build a secret-free Starter provisioning plan")
2277
+ plan.add_argument("--project", type=Path, default=Path.cwd(), help="host project path; planning is read-only")
2278
+ plan.add_argument("--workspace-id", required=True)
2279
+ plan.add_argument("--merchant-id", required=True)
2280
+ plan.add_argument("--site-slug", required=True)
2281
+ plan.add_argument("--cal-url", required=True)
2282
+ plan.add_argument("--embed", choices=("link", "iframe"), default="link")
2283
+ plan.add_argument("--branch", default="main")
2284
+ plan.add_argument("--output", type=Path)
2285
+ plan.set_defaults(func=starter_plan)
2143
2286
  val = sub.add_parser("validate")
2144
2287
  val.add_argument("template", type=Path)
2145
2288
  val.add_argument(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topy-ai/maggie",
3
- "version": "0.7.42",
3
+ "version": "0.7.44",
4
4
  "description": "Install and manage Maggie Skills for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",