@follenfang/fupload 0.0.12 → 0.0.16
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/fupload/SKILL.md +11 -1
- package/fupload/references/dd.md +18 -3
- package/fupload/references/modus.md +228 -64
- package/fupload/scripts/fupload_cli/__init__.py +1 -1
- package/fupload/scripts/fupload_cli/cli.py +15 -3
- package/fupload/scripts/fupload_cli/dd.py +17 -0
- package/fupload/scripts/fupload_cli/dd_broker.py +58 -14
- package/fupload/scripts/fupload_cli/dd_sidecar.py +67 -8
- package/fupload/scripts/fupload_cli/io.py +20 -2
- package/fupload/scripts/fupload_cli/modus.py +279 -14
- package/fupload/scripts/fupload_cli/schema.py +53 -7
- package/fupload/scripts/fupload_cli/transport.py +13 -1
- package/npm/skill-manifest.json +26 -26
- package/package.json +1 -1
package/fupload/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: fupload
|
|
3
3
|
description: Explicit author-publishing workflow for World of Warcraft plugins, configuration shares, and WA/strings on NewBeeBox, NetEase DD, CurseForge, Heybox Workshop, and ModUs.Creator, including local Creator login reuse and plugin ZIP publishing. Use only when the user explicitly invokes `$fupload`, explicitly asks to use the Fupload Skill, or loads this Skill by path. Do not trigger from ordinary mentions of publishing, NewBeeBox, DD, CurseForge, Heybox, ModUs, plugins, configurations, or WA.
|
|
4
4
|
metadata:
|
|
5
|
-
version: "0.0.
|
|
5
|
+
version: "0.0.16"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Fupload
|
|
@@ -54,6 +54,8 @@ For NewBeeBox, prefer an existing official `ncc login`, then a caller-provided `
|
|
|
54
54
|
|
|
55
55
|
For CurseForge, use `~/.fupload/curseforge.env`, created idempotently by npm install/update, with `CURSEFORGE_AUTHOR_ID`, `CURSEFORGE_API_KEY`, and `CURSEFORGE_UPLOAD_TOKEN`. The author ID is non-secret: if absent, proactively ask for its numeric value or offer `--author-id`. The API key and upload token are secrets: never ask the user to paste either into the conversation; direct them to fill the local env file in their own editor or terminal, then run `curseforge session doctor`. Never print file contents or secret values. Process environment values may override the file without being shown.
|
|
56
56
|
|
|
57
|
+
For DD, reuse only the official client's local auto account and credential through `dd session start`. Never request or accept an email address, mobile number, password, token, credential value, or account-type override. Account and credential values must not appear in CLI output, plans, logs, tests, or analysis evidence; only the safe credential kind may be retained for diagnosis and verification.
|
|
58
|
+
|
|
57
59
|
For ModUs, authentication comes only from `%LOCALAPPDATA%\ModUs.Creator\auth\token.dat` under the current Windows user. `modus session doctor` reports only token presence, DPAPI decryption, nonempty plaintext, and authenticated API readiness. Never print, copy, hash, or persist the token, ciphertext, Bearer header, or signed upload URL.
|
|
58
60
|
|
|
59
61
|
If a token is pasted into the conversation, do not repeat or use it. Tell the user to revoke or regenerate it at its provider and configure the replacement locally. Check only whether the expected field is present, never its length, prefix, hash, or value.
|
|
@@ -107,8 +109,12 @@ For ModUs, require all four `modus session doctor` booleans to be true before an
|
|
|
107
109
|
|
|
108
110
|
Before any DD live GET or write, run `dd session doctor`. If it reports `gui_running=true`, tell the user that continuing will close the listed official DD GUI instances and ask for explicit consent. Without consent, do not run start, do not close a process, and do not issue a native login. After consent, run `dd session start --confirm-close-gui`; when no GUI is running, run `dd session start` without that flag.
|
|
109
111
|
|
|
112
|
+
`dd session start` automatically selects the native relogin flow from the official `Account.method`, `Cred.type`, and `Cred.modifier` enums. `urs + urs_token + normal` uses `UrsReLoginFlow` for an email session; `mobile + urs_mobile_token + mobile_password|mobile_uplink` uses `MobileReLoginFlow` for a mobile session. Missing, unknown, or contradictory combinations fail before any flow runs. Never infer the account kind from its text, coerce an enum, or try the other flow after a failure.
|
|
113
|
+
|
|
110
114
|
Keep the returned opaque `session_id` only in task memory and pass it as `--session <id>` to every DD GET, write, readback, status, and delete. Reuse this one session for the complete task, serialize all commands, stop on the first failure, and never start one session per item in a batch. In a `finally` path, always run `dd session stop --session <id>` and require `cleanup_complete=true`; the ten-minute idle timeout is only an abnormal-exit fallback.
|
|
111
115
|
|
|
116
|
+
Email and mobile differ only during native relogin. After a matching flow succeeds, both reuse the same JWT refresh, author API client, GET, upload, mutation, readback, logout, and cleanup path through that one task session.
|
|
117
|
+
|
|
112
118
|
For every DD configuration, first list backups, select `backup_sn`, then run `dd config backup-get --sn <backup>`. Select a WTF account/server/role before presenting that account's known/unknown WA choices. For a retail configuration, present the safe edit-mode and cooldown selector metadata. Send only stable IDs and returned selectors; never request, display, store, or reconstruct raw backup objects or retail import strings.
|
|
113
119
|
|
|
114
120
|
For a configuration, require a cloud backup already uploaded by the matching desktop client. If none exists, ask the user to upload one in the client and stop before writing.
|
|
@@ -147,6 +153,10 @@ For a Python delete, first run the resource `get` command, show the exact name a
|
|
|
147
153
|
|
|
148
154
|
Run writes serially. For official `ncc`, always request `-o json`, parse stdout as JSON, and treat stderr only as progress diagnostics. For Python, parse its stable JSON output. Never scrape human text. After each successful step, immediately run the corresponding info/get/list/versions/history command and compare the intended fields in the same DD session when applicable. DD performs a bounded GET-only readback poll and never resends a mutation during verification. For DD plugin update, use a nonempty `/addon/addon_versions` result only as a pre-upload duplicate guard; the matching author-list item's `latest_version` is the primary success confirmation and `detail_v2` is supplementary. Plugin edit also uses the same-SN author projection when detail remains stale. An empty history is diagnostic-only. Treat “submitted for review” and “under review” as distinct from “approved” or “publicly visible.”
|
|
149
155
|
|
|
156
|
+
When validating a DD authentication change, automated dispatch and constructor tests must cover every supported email and mobile enum combination, select exactly one matching flow with the correct arguments, and reject missing, unknown, or contradictory combinations without fallback. Exercise the shared JWT refresh, author API, resource, broker, sidecar, and logout path once through unified tests; do not require that common path to be repeated per credential kind.
|
|
157
|
+
|
|
158
|
+
Run the full isolated live matrix only with the official client's currently persisted account and record its safe credential kind; do not switch the persisted account merely to repeat the matrix. For every current non-exploration seasonal build, read the dependency graph and complete plugin and WA create, update, edit, readback, binary upload, delete, and final cleanup. Complete the same configuration matrix, including image upload, only for builds where `/backup/list` and `/backup/detail` provide a usable cloud backup. Record an explicit safe `N/A` reason for every build without one; never silently skip it or fabricate a backup selector. Evidence must record the executed commands, exit status, readback, `N/A` items, implementation commit, DD version, resource hashes, and cleanup result while redacting account names, credential values, tokens, Cookies, JWTs, signed URLs, `clientNo`, raw WA strings, and raw backup content. Cleanup may touch only objects created by that test run, and the run must finish with no sidecar, task broker, or live broker state.
|
|
159
|
+
|
|
150
160
|
For CurseForge, treat the Upload API's returned file ID as upload acceptance only. Record the ID and command result, then report that moderation, processing, manual release, and public visibility are separate states. On an interrupted or ambiguous upload, do not resend automatically because that can create a duplicate file; inspect the Authors project page or public file list before deciding on a new attempt. Follow the HTTP/error handling table in the CurseForge reference.
|
|
151
161
|
|
|
152
162
|
In the third-party Python NewBeeBox channel, public plugin publication is three atomic writes: create privately, upload and verify the first version, then edit to public with explicit review intent. Never send a public `share_state` during Python create. In the official channel, follow the installed `ncc docs` sequence for create, init, push, and visibility instead of applying Python wire rules.
|
package/fupload/references/dd.md
CHANGED
|
@@ -2,11 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Task session
|
|
4
4
|
|
|
5
|
-
Run `dd session doctor` first. Doctor only discovers the installation, verifies the Authenticode publisher, reports official DD processes and reads local broker state; it does not login. When `gui_running=true`, explain that the official DD GUI must close and obtain explicit user consent. Only then run `dd session start --confirm-close-gui`. When `gui_running=false`, run `dd session start` without the confirmation flag.
|
|
5
|
+
Run `dd session doctor` first. Doctor only discovers the installation, verifies the Authenticode publisher, reports official DD processes and reads local broker state; it does not login or run either native relogin flow. When `gui_running=true`, explain that the official DD GUI must close and obtain explicit user consent. Only then run `dd session start --confirm-close-gui`. When `gui_running=false`, run `dd session start` without the confirmation flag.
|
|
6
6
|
|
|
7
|
-
`start` closes only identity- and signature-verified official GUI processes, starts one task broker and
|
|
7
|
+
`start` closes only identity- and signature-verified official GUI processes, starts one task broker, and reads the official `AccountCredStorage` auto account and credential. It selects the native relogin flow only from this strict enum matrix:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- `Account.method=urs`, `Cred.type=urs_token`, `Cred.modifier=normal`: run `UrsReLoginFlow(controller, sdk, token, username)` for the email login state.
|
|
10
|
+
- `Account.method=mobile`, `Cred.type=urs_mobile_token`, `Cred.modifier=mobile_password|mobile_uplink`: run `MobileReLoginFlow(controller, sdk, cgi, netconfig, token, isPassword, username)` for the mobile login state. `isPassword` is true only for `mobile_password`.
|
|
11
|
+
|
|
12
|
+
Missing, unknown, or contradictory enum combinations fail before either flow is called. Do not infer account kind from the account string, coerce a value, or fall back to the other flow after a login failure. Do not request or accept manual account names, passwords, tokens, credential values, or account-type overrides.
|
|
13
|
+
|
|
14
|
+
After the selected flow succeeds, both credential kinds use the same JWT refresh, author API client, GET, parser, upload, mutation, readback, logout, and cleanup path. `start` returns an opaque `session_id`; pass it through `--session` to every DD read and write. All operations are serialized through this one task session and one native login. Run `dd session status --session <id>` for a local status check and always run `dd session stop --session <id>` in `finally`; successful stop reports `cleanup_complete=true`. The ten-minute idle timeout is only a crash fallback.
|
|
15
|
+
|
|
16
|
+
Do not request or expose account names, credential values, tokens, Cookies, JWTs, the credential database, signed URLs, `clientNo`, raw WA content, or raw backup objects. The safe credential kind may be reported without its account identity or value. The device state remains under Windows Known Folder Roaming AppData at `CCVoiceHub/Fupload/sidecar-device.json`.
|
|
17
|
+
|
|
18
|
+
## Authentication compatibility verification
|
|
19
|
+
|
|
20
|
+
Any change to DD authentication or session dispatch requires automated tests for every supported email and mobile enum combination. Each case must select exactly one matching flow, verify its constructor arguments, and reject missing, unknown, or contradictory combinations before login without fallback. Unified tests cover the common JWT refresh, author API client, resource operations, single broker/sidecar/login lifecycle, and explicit logout path after dispatch; this common path does not need to be repeated per credential kind.
|
|
21
|
+
|
|
22
|
+
Run the full isolated live matrix only under the account currently persisted by the official DD client, and record its safe credential kind. Do not switch that account merely to repeat the matrix for another credential kind. In one task session, read the live dependency graph for every current non-exploration seasonal build, then cover plugin and WA create, update, edit, readback, binary upload, delete, and final cleanup. For configuration, run the same CRUD, image-upload, readback, and cleanup matrix on every build where `/backup/list` returns a cloud backup whose `/backup/detail` supports valid selections. When a build has no usable cloud backup, record an explicit safe `N/A` reason; never silently skip it or fabricate a backup selector.
|
|
23
|
+
|
|
24
|
+
Verification evidence may contain only the safe credential kind, executed command, exit status, readback result, `N/A` items, implementation commit, DD version, resource hashes, and cleanup result. Redact account names, credential values, passwords, tokens, Cookies, JWTs, signed URLs, `clientNo`, signatures, device proofs, raw WA strings, and raw configuration backup content. Cleanup may remove only the isolated objects recorded as created by that run; do not modify pre-existing remote resources. A completed, failed, or timed-out run must verify that no DD sidecar, task broker, or live broker state remains.
|
|
10
25
|
|
|
11
26
|
## GET before one final JSON
|
|
12
27
|
|
|
@@ -35,31 +35,42 @@ The complete project model includes `project_id`, `name`, `alt_name`, `summary`,
|
|
|
35
35
|
|
|
36
36
|
### Project field contract
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
38
|
+
The table below is the A2 field inventory. `C` means create, `U` update/edit,
|
|
39
|
+
and `D` delete. The current CLI's Creator endpoints are `C POST
|
|
40
|
+
/game/data/author/project/release`, `U POST /game/data/author/project/update`,
|
|
41
|
+
`D POST /game/data/author/project/delete/project/{projectId}`, and readback is
|
|
42
|
+
`GET /game/data/author/project/detail/{projectId}` unless stated otherwise.
|
|
43
|
+
|
|
44
|
+
| CLI/state field | Creator wire name | JSON type | Required/default | Enum/source | State/linkage rule | Write endpoint | Detail/readback location |
|
|
45
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
46
|
+
| `schema` | not sent | string | required for each CLI write | schema registry | must match action | local validation only | not applicable |
|
|
47
|
+
| `project_id` | `projectId` | positive integer | U/D required; C server-assigned | project list/detail | outer document, not `project_state` | U/D | `projectId` |
|
|
48
|
+
| `game` | not sent by current CLI | object or non-empty string | required; no default | Creator game selector | first `choose_game` state; stable key/ID required | local state only | `project_state.game`; server version binding is separate |
|
|
49
|
+
| `name` | `name` | non-empty string, max 120 | required; no default | free text | `basic_info` | C/U | `name` |
|
|
50
|
+
| `alt_name` | `altName` | string/null, max 120 | optional; omitted preserves | free text | U clear becomes literal `<null>` | C/U | `altName` |
|
|
51
|
+
| `summary` | `summary` | non-empty string, max 500 | required; no default | free text | `basic_info` | C/U | `summary` |
|
|
52
|
+
| `categories` | comma-delimited `categories` | array of 1-5 positive IDs | required; no default | `GET /plugin/list/Categories` | ID `998` forces BigFoot-only | C/U | comma-delimited `categories` / category detail |
|
|
53
|
+
| `publish_platforms` | derives `synchronizationType` | unique array | required; no default | fixed `modus`, `bigfoot` | at least one; `[modus]=>1`, `[bigfoot]=>2`, both `=>3` | C/U through derived field | `synchronizationType` |
|
|
54
|
+
| `synchronization_type` | `synchronizationType` | integer `1..3` | derived; caller value replaced | derived from `publish_platforms` | must equal platform selection | C/U | `synchronizationType` |
|
|
55
|
+
| `required_tier_id` | `requiredTierId` | positive integer/null | default none: C omits; U null sends `<null>` | `GET /user/author/subscription/tiers` | BigFoot branch requires none | C/U | `requiredTierId`; absence is no-tier |
|
|
56
|
+
| `repo_url` | `repoUrl` | string/null, max 500 | optional | free URL text | C empty omitted; U clear sends `<null>` | C/U | `repoUrl` |
|
|
57
|
+
| `logo_base64` | `screenshotBase64sReqs.screenshotBase64s` | non-empty base64 string | C optional; default `""` | local image | C only; companion name fixed `logo.webp` | C | server-managed `logo` path |
|
|
58
|
+
| `screenshot_base64s` | same create logo member | base64 string array | compatibility alias; default `[]` | local images | current CLI consumes only first item when `logo_base64` absent | C | server-managed `logo` path |
|
|
59
|
+
| `license.type` | JSON-string `license.type` | non-empty string | required | Creator license selector | final `license` state | C/U | parse JSON-string `license` |
|
|
60
|
+
| `license.holder` | JSON-string `license.holder` | non-empty string | optional | free text | license step | C/U | parse JSON-string `license.holder` |
|
|
61
|
+
| `license.year` | JSON-string `license.year` | non-empty string | optional | free text | license step | C/U | parse JSON-string `license.year` |
|
|
62
|
+
| `license.content` | JSON-string `license.content` | non-empty string | custom license requires content | free text | license step/custom branch | C/U | parse JSON-string `license.content` |
|
|
63
|
+
| `description` | `description` | string/null, max 100000 | U only; omitted preserves | rich-text editor | clear sends `<null>` | U | `description` |
|
|
64
|
+
| `required_dependencies` | `requiredDependencies` | string/null, max 4000 | U only; omitted preserves | `POST /game/data/author/project/dependency/query` | clear sends `<null>` | U | `requiredDependencies` |
|
|
65
|
+
| `images` | `images` | non-negative integer | U required with `image_ops` | current detail count | count must agree with requested operations | U | `images` |
|
|
66
|
+
| `image_ops[].op` | `imagesOps[].op` | enum string | U operation required | fixed `upload`, `delete`, `rename` | upload/delete use `name`; rename uses `from/to` | U | `images`, logo/image paths |
|
|
67
|
+
| `image_ops[].name` | `imagesOps[].name` | non-empty string | upload/delete required | current object name | forbidden for rename | U | resulting image path/count |
|
|
68
|
+
| `image_ops[].base64` | `imagesOps[].base64` | base64 string | upload required; delete forbidden | local image | upload only | U | resulting image path/count |
|
|
69
|
+
| `image_ops[].from/to` | `imagesOps[].from/to` | non-empty strings | rename required | current/target name | rename only | U | resulting image paths |
|
|
70
|
+
| `cf_url` | `cfUrl` | string/null | read-only | server | never sent | none | `cfUrl` |
|
|
71
|
+
| `logo` | `logo` | string/object/null | read-only | server | C uses base64 input instead | none | `logo` |
|
|
72
|
+
| `status` | `status` | integer/null | read-only | server status enum | delete/publication state | none | `status` |
|
|
73
|
+
| `project_state` | not sent | object | required; state=`complete` | CLI state machine | exact order `choose_game -> basic_info -> license`; unknown fields rejected | local validation only | persisted CLI snapshot |
|
|
63
74
|
|
|
64
75
|
Create-only, edit-only, and read-only fields may be preserved in a resumable
|
|
65
76
|
snapshot, but `_project_wire` sends only fields supported by the selected API
|
|
@@ -82,8 +93,9 @@ named `logo.webp`; `screenshot_base64s` remains the compatibility alias.
|
|
|
82
93
|
Edit sends only changed Creator fields plus `projectId`. `description`, cleared
|
|
83
94
|
`repo_url`, cleared `alt_name`, null `required_tier_id`, and cleared
|
|
84
95
|
`required_dependencies` use Creator's literal `<null>` marker. `image_ops` maps
|
|
85
|
-
to `imagesOps` entries with `op` (`upload` or `
|
|
86
|
-
`base64`; `images` is required with
|
|
96
|
+
to `imagesOps` entries with `op` (`upload`, `delete`, or `rename`), `name` and
|
|
97
|
+
upload-only `base64`, or rename-only `from`/`to`; `images` is required with
|
|
98
|
+
image operations. `cf_url`, returned `logo`,
|
|
87
99
|
and `status` are read-only/server-managed. `publish_platforms` and `game` are
|
|
88
100
|
local form state, not project update keys.
|
|
89
101
|
`project_state` is a resumable `choose_game` -> `basic_info` -> `license` ->
|
|
@@ -101,6 +113,35 @@ Deletion requires `confirm: "DELETE"`.
|
|
|
101
113
|
`project_id`, `file_id`, `version`, `type`, `supported_game_versions`, `md5`,
|
|
102
114
|
`zip_size`, `unzip_size`, `path`, `toc_version`, `changelog`, and `file`.
|
|
103
115
|
`transaction_log` optionally selects the redacted transaction record path.
|
|
116
|
+
|
|
117
|
+
### Creator release/version field contract
|
|
118
|
+
|
|
119
|
+
This is the A4 inventory. Metadata create uses `POST
|
|
120
|
+
/game/data/author/project/upload`; metadata update/edit uses `POST
|
|
121
|
+
/game/data/author/project/file/update`; delete uses `POST
|
|
122
|
+
/game/data/author/project/delete`; detail is `GET
|
|
123
|
+
/game/data/author/project/file/detail/{fileId}` and history/list is `POST
|
|
124
|
+
/game/data/author/project/file/list`. File-ID allocation and the binary object
|
|
125
|
+
PUT are separate stages.
|
|
126
|
+
|
|
127
|
+
| CLI field | Creator wire name | JSON type | Required/default | Enum/source | State/linkage rule | Write endpoint | Detail/readback location |
|
|
128
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
129
|
+
| `schema` | not sent | string | required | schema registry | must match release action | local validation | not applicable |
|
|
130
|
+
| `project_id` | `projectId` | positive integer | all release writes required | project detail | parent project must exist | metadata create/update/delete; signature path | `projectId` in list/detail |
|
|
131
|
+
| `file_id` | `fileId` | positive integer | update/edit/delete required; upload allocates | `GET /game/data/author/project/fileId/{projectId}` | allocated ID is used by signature; create metadata omits it | update/delete/signature | `id`/`fileId` in release detail |
|
|
132
|
+
| `version` | `version` | non-empty string, max 120 | create/upload required after ZIP preflight | ZIP `.toc` plus caller label | caller value preserved | metadata create/update | `version` |
|
|
133
|
+
| `type` | `type` | non-empty string, max 40 | create/upload required | Creator release-type choices/API | must be a server-supported value | metadata create/update | `type` |
|
|
134
|
+
| `supported_game_versions` | `supportedGameVersionsReqs[]` | non-empty object array | required; ZIP-derived | installed `.toc` `Interface` values and game config | each entry requires string `gameVersion` and `server`; explicit caller value must match ZIP | metadata create/update | `supportedGameVersionsReqs`/version compatibility rows |
|
|
135
|
+
| `md5` | `md5` | hex string, max 64 | derived from exact ZIP bytes | local ZIP preflight | must describe bytes later PUT | metadata create/update | `md5` |
|
|
136
|
+
| `zip_size` | `zipSize` | non-negative integer | derived | local ZIP stat | must describe exact ZIP | metadata create/update | `zipSize` |
|
|
137
|
+
| `unzip_size` | `unzipSize` | non-negative integer | derived | ZIP central directory | bounded by ZIP preflight | metadata create/update | `unzipSize` |
|
|
138
|
+
| `path` | `path` | string, max 500 | metadata optional | Creator/object response | local path is never binary upload | metadata create/update | `path` |
|
|
139
|
+
| `toc_version` | `tocVersion` | string, max 80 | derived | every ZIP `.toc` `Interface` | incompatible/missing/mixed values fail before write | metadata create/update | `tocVersion` |
|
|
140
|
+
| `changelog` | `changelog` | string/null, max 10000 | optional | free text | omitted/empty behavior is action-specific | metadata create/update | `changelog` |
|
|
141
|
+
| `file` | not in JSON; raw bytes | local path | create/upload required | local filesystem | valid ZIP, max 200 MiB; bytes PUT only after metadata/signature | signed URL from `GET .../upload/signature/{projectId}/{fileId}`, then HTTP `PUT` | object upload status plus release detail hashes/sizes |
|
|
142
|
+
| `transaction_log` | not sent | local path string | optional | caller | redacted durable stage record | local record only | transaction JSON |
|
|
143
|
+
| derived signature URL | not returned to caller | URL | required for PUT | signature endpoint | query/userinfo redacted and never persisted | object-store `PUT application/zip` | HTTP status only; then release detail |
|
|
144
|
+
|
|
104
145
|
Successful records include the completed `file_id`, metadata, signature, and
|
|
105
146
|
binary upload stages that ran; failed records include `failed_stage`, a redacted
|
|
106
147
|
error, and whether the ZIP was retained. The transaction record is established
|
|
@@ -134,26 +175,55 @@ The configuration surface uses:
|
|
|
134
175
|
* `POST /system/user/share/create`, `PUT /system/user/share/update`,
|
|
135
176
|
`DELETE /system/user/share/delete/{id}`
|
|
136
177
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
`
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
`
|
|
152
|
-
`
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
178
|
+
### Configuration list/filter contract
|
|
179
|
+
|
|
180
|
+
| CLI field | Wire/header name | JSON type | Required/default | Enum/source | State/linkage rule | Request endpoint | Readback location |
|
|
181
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
182
|
+
| `page_num` | `pageNum` | positive integer | default `1` | caller | list only | `POST /system/user/share/list` | pagination response |
|
|
183
|
+
| `page_size` | `pageSize` | positive integer | default `20` | caller | list only | same | pagination response |
|
|
184
|
+
| `server_type` / `server` | body `server`; header `X-Server-Type` | integer | local selected Build, fallback `0` | fixed Build table below | body and header must be identical | list; header on all config calls | list/detail records by Build |
|
|
185
|
+
| `mine` | `mine` | boolean | default `false` | fixed boolean | list only | list | returned rows |
|
|
186
|
+
| `share_type` | `shareType` | non-negative integer | default `0` | service/UI | list filter | list | returned rows |
|
|
187
|
+
| `platform` | `platform` | integer | list default `0` | service; write choices `1`,`3` | not Build | list | `platform` |
|
|
188
|
+
| `keyword` | `keyword` | string | optional | caller | list filter | list | returned rows |
|
|
189
|
+
| `status` | `status` | integer | optional | service status values | list filter | list | `status` |
|
|
190
|
+
| `tags` | `tags` | string | optional | `config-tags` static rows | list filter | list | tags on rows/detail |
|
|
191
|
+
| `order_by` | `orderBy` | service value | optional | service/UI | list sort | list | result order |
|
|
192
|
+
| `is_public` | `isPublic` | integer `0/1` | optional | fixed boolean | list filter | list | `isPublic` |
|
|
193
|
+
| `is_paid` | `isPaid` | integer `0/1` | optional | fixed boolean | list filter | list | `isPaid` |
|
|
194
|
+
|
|
195
|
+
### Configuration write field contract
|
|
196
|
+
|
|
197
|
+
This is the A6 inventory. Create is `POST /system/user/share/create`, update/edit
|
|
198
|
+
is `PUT /system/user/share/update`, delete is `DELETE
|
|
199
|
+
/system/user/share/delete/{id}`, and readback is `POST
|
|
200
|
+
/system/user/share/detail` with `{"shareIds":[id]}`. Every call carries the
|
|
201
|
+
selected Build in `X-Server-Type`.
|
|
202
|
+
|
|
203
|
+
| CLI field | Wire name | JSON type | Required/default | Enum/source | State/linkage rule | Write endpoint | Detail/readback location |
|
|
204
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
205
|
+
| `schema` | not sent | string | required | schema registry | must match action | local validation | not applicable |
|
|
206
|
+
| `share_id` | `id` / path ID | opaque decimal string | U/D required; C server-assigned | create response/detail | preserve as string | U/D | detail `id` |
|
|
207
|
+
| `addons_id` | `addonsId` | string, max 10000 | C required | selected backup `knownAddons.projectids` | must correspond to backup selection | C/U | `addonsId` |
|
|
208
|
+
| `backup_id` | `backupId` | positive integer | C required | `GET /system/user/backup/list` | account/role choices derive from this backup | C/U | `backupId` |
|
|
209
|
+
| `account_name` | `accountName` | string/null, max 120 | conditional | selected backup `wtfAccounts` | required and non-empty when `exclude_wtf=0`; empty when `1` | C/U | `accountName` |
|
|
210
|
+
| `role_name` | `roleName` | string/null, max 120 | optional in schema; UI requires selection when chosen account has roles | backup account roles | empty when `exclude_wtf=1` | C/U | `roleName` |
|
|
211
|
+
| `exclude_wtf` | `excludeWtf` | integer `0/1` | C required; provider normalizes missing U to `0` | fixed boolean | `1` forbids account/role; `0` requires account | C/U | `excludeWtf` |
|
|
212
|
+
| `content` | `content` | non-empty HTML string | C required | rich-text editor | editor image keys are uploaded before submit | C/U | `content` hash/length, not raw evidence |
|
|
213
|
+
| `content_text` | `contentText` | non-empty string | C required; trimmed length >20 | derived editor plain text | must correspond to `content` | C/U | `contentText` hash/length |
|
|
214
|
+
| `image_url` | `imageUrl` | non-empty comma-delimited key string, max 1000 | C required; 1-10 images in 1.2.16 UI | `modus media upload` results | trim/filter keys; first is cover; delete/reorder updates CSV, no blob-delete API observed | C/U | `imageUrl` |
|
|
215
|
+
| `is_paid` | `isPaid` | integer `0` | CLI optional; 1.2.16 form fixes `0` | installed client fixed value | nonzero values fail before mutation | C/U | `isPaid` |
|
|
216
|
+
| `price` | `price` | number `0` | CLI optional; 1.2.16 form fixes `0` | installed client fixed value | nonzero values fail before mutation | C/U | `price` |
|
|
217
|
+
| `is_public` | `isPublic` | integer `0/1` | CLI optional; 1.2.16 form defaults public | fixed boolean | visibility/soft-delete state | C/U | `isPublic` |
|
|
218
|
+
| `share_type` | `shareType` | integer `0` | CLI optional; 1.2.16 form fixes `0` | installed client fixed value | nonzero values fail before mutation | C/U | `shareType` |
|
|
219
|
+
| `tags` | comma-delimited `tags` | string of 1-3 IDs | C required | `modus options config-tags` | no empty/duplicate positions | C/U | `tags` and/or `modusShareTags` |
|
|
220
|
+
| `title` | `title` | string, max 120 | C required; trimmed length >=6 | free text | none | C/U | `title` |
|
|
221
|
+
| `required_tier_id` | `requiredTierId` | positive integer/null | default none: omitted | normal subscription or season option | selected tier disables BigFoot platform; ID from active options | C/U | `requiredTierId`; absence means none |
|
|
222
|
+
| `sub_type` | `subType` | integer `0/1` | helper default `0` | fixed normal=`0`, season=`1` | determines tier option source | C/U | `subType` |
|
|
223
|
+
| `platform` | `platform` | integer `1/3` | provider C/U default `1` | ModUs=`1`, ModUs+BigFoot=`3` | at least ModUs; tier selection forces `1` | C/U | `platform` |
|
|
224
|
+
| `synchronization_type` | `synchronizationType` | integer `1/3` | C defaults from platform; U omitted unless explicit | derived from platform | C should equal platform; 1.2.16 config U form omits it | C/U | `synchronizationType` if returned |
|
|
225
|
+
| `server_type` | header `X-Server-Type` | integer `0..4` | local selected Build or fallback `0` | fixed Build table | not `platform`; applies to target namespace | C/U/D/detail | record returned under same Build |
|
|
226
|
+
| `confirm` | not sent | literal `"DELETE"` | D required | fixed CLI guard | destructive action only | local guard then D | detail `status=4,isPublic=0`; absent from active list |
|
|
157
227
|
|
|
158
228
|
Backup rename uses `backup_id`, `backup_name`, and `server_type`; backup
|
|
159
229
|
deletion uses `backup_id`, `confirm`, and `server_type`.
|
|
@@ -168,23 +238,117 @@ String articles use:
|
|
|
168
238
|
* `POST /system/user/import/version/publish`
|
|
169
239
|
* `DELETE /system/user/import/version/delete?versionId={id}`
|
|
170
240
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
`
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
`
|
|
180
|
-
`platform
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
241
|
+
### WA/string list/filter contract
|
|
242
|
+
|
|
243
|
+
| CLI field | Wire/header name | JSON type | Required/default | Enum/source | State/linkage rule | Request endpoint | Readback location |
|
|
244
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
245
|
+
| `page_num` | `pageNum` | positive integer | default `1` | caller | list only | `POST /system/user/import/list` | pagination response |
|
|
246
|
+
| `page_size` | `pageSize` | positive integer | default `10` | caller | list only | same | pagination response |
|
|
247
|
+
| `server_type` / `server` | body `server`; header `X-Server-Type` | integer | local Build, fallback `0` | fixed Build table | body/header must match | list; header on every WA call | list/detail records by Build |
|
|
248
|
+
| `mine` | `mine` | boolean | default `false` | fixed boolean | list only | list | rows |
|
|
249
|
+
| `status` | `status` | integer | default `1` | service status enum | list only | list | `status` |
|
|
250
|
+
| `platform` | `platform` | integer | list default `0` | service; write `1/3` | not Build | list | `platform` |
|
|
251
|
+
| `keyword` | `keyword` | string | optional | caller | list filter | list | rows |
|
|
252
|
+
| `support_addon` | `supportAddon` | string | optional | `wa-support-addons` static rows | list filter | list | `supportAddon` |
|
|
253
|
+
| `tags` | `tags` | string | optional | `wa-tags` static rows | list filter | list | tags on rows/detail |
|
|
254
|
+
| `is_paid` | `isPaid` | integer `0/1` | optional | fixed boolean | list filter | list | `isPaid` |
|
|
255
|
+
| `order_by` | `orderBy` | service value | optional | service/UI | list sort | list | result order |
|
|
256
|
+
|
|
257
|
+
### WA/string write field contract
|
|
258
|
+
|
|
259
|
+
This is the A8 inventory. Create is `POST /system/user/import/create`,
|
|
260
|
+
update/edit is `POST /system/user/import/update`, delete is `DELETE
|
|
261
|
+
/system/user/import/delete/{id}`, and readback is `POST
|
|
262
|
+
/system/user/import/detail` with `{"importIds":[id]}`.
|
|
263
|
+
|
|
264
|
+
| CLI field | Wire name | JSON type | Required/default | Enum/source | State/linkage rule | Write endpoint | Detail/readback location |
|
|
265
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
266
|
+
| `schema` | not sent | string | required | schema registry | must match action | local validation | not applicable |
|
|
267
|
+
| `import_id` | `id` / path ID | opaque decimal string | U/D required; C server-assigned | create response/detail | preserve as string | U/D | detail `id` |
|
|
268
|
+
| `support_addon` | `supportAddon` | non-empty string, max 120 | C required | `modus options wa-support-addons` | chosen name must resolve to an addon row | C/U | `supportAddon` |
|
|
269
|
+
| `addons_id` | `addonsId` | non-empty string, max 10000 | C required | ID on selected support-addon row | must match `support_addon` | C/U | `addonsId` |
|
|
270
|
+
| `code_text` | `codeText` | non-empty string | C required; version publish required | WA/string input | create version and later version payload must be deliberate | C/U/version publish | current top-level `codeText` is not echoed; after the next publish, the previous current value is read from `versionList[].codeText` |
|
|
271
|
+
| `version` | `version` | non-empty string, max 120 | C required; U form preserves current | free text/version list | new immutable versions use version-publish | C/U/version publish | `version`, `versionList[].version` |
|
|
272
|
+
| `content` | `content` | non-empty HTML string | C required | rich-text editor | editor images uploaded before submit | C/U | `content` hash/length |
|
|
273
|
+
| `content_text` | `contentText` | non-empty string | C required; trimmed length >20 | derived plain text | must correspond to HTML content | C/U | `contentText` hash/length |
|
|
274
|
+
| `file_path` | `filePath` | literal empty string | optional in CLI; UI fixed `""` | fixed client behavior | only `""` accepted | C/U | `filePath` |
|
|
275
|
+
| `image_url` | `imageUrl` | non-empty comma-delimited key string, max 1000 | C required; 1-10 images in 1.2.16 UI | `modus media upload` results | first key is cover; delete/reorder rewrites CSV, no blob-delete API observed | C/U | `imageUrl` |
|
|
276
|
+
| `tags` | comma-delimited `tags` | string of 1-3 IDs | C required | `modus options wa-tags` | no empty positions | C/U | `tags` |
|
|
277
|
+
| `title` | `title` | string, max 120 | C required; trimmed length >=6 | free text | none | C/U | `title` |
|
|
278
|
+
| `is_paid` | `isPaid` | integer `0` | CLI optional; 1.2.16 form fixes `0` | installed client fixed value | nonzero values fail before mutation | C/U | `isPaid` |
|
|
279
|
+
| `price` | `price` | number `0` | CLI optional; 1.2.16 form fixes `0` | installed client fixed value | nonzero values fail before mutation | C/U | `price` |
|
|
280
|
+
| `is_public` | `isPublic` | integer `0/1` | CLI optional; 1.2.16 form fixes `1` | fixed boolean | soft-delete later forces private | C/U | `isPublic` |
|
|
281
|
+
| `share_type` | `shareType` | integer `0` | CLI optional; 1.2.16 form fixes `0` | installed client fixed value | nonzero values fail before mutation | C/U | `shareType` |
|
|
282
|
+
| `required_tier_id` | `requiredTierId` | positive integer/null | default none: omitted | normal/season tier options | selected tier disables BigFoot | C/U | `requiredTierId`; absence means none |
|
|
283
|
+
| `sub_type` | `subType` | integer `0/1` | helper default `0` | normal=`0`, season=`1` | selects tier option source | C/U | `subType` |
|
|
284
|
+
| `platform` | `platform` | integer `1/3` | C/U default `1` | ModUs=`1`, ModUs+BigFoot=`3` | at least ModUs; tier selection forces `1` | C/U | `platform` |
|
|
285
|
+
| `synchronization_type` | `synchronizationType` | integer `1/3` | C/U defaults from platform | derived | 1.2.16 sends it on both C and U | C/U | `synchronizationType` |
|
|
286
|
+
| `server_type` | header `X-Server-Type` | integer `0..4` | local Build or fallback `0` | fixed Build table | target namespace, not platform | C/U/D/detail/version | record under same Build |
|
|
287
|
+
| `confirm` | not sent | literal `"DELETE"` | D required | fixed CLI guard | destructive action only | local guard then D | `status=4,isPublic=0`; absent active list |
|
|
288
|
+
|
|
289
|
+
Version history is separate from article metadata:
|
|
290
|
+
|
|
291
|
+
| CLI field | Wire name | JSON type | Required/default | Enum/source | State/linkage rule | Write endpoint | Detail/readback location |
|
|
292
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
293
|
+
| `import_id` | `importId` | opaque decimal string | publish required | article detail | parent must exist | `POST /system/user/import/version/publish` | `versionList[].importId`/parent detail |
|
|
294
|
+
| `version` | `version` | non-empty string, max 120 | publish required | caller | expected immutable version label | same | `versionList[].version` |
|
|
295
|
+
| `code_text` | `codeText` | non-empty string | publish required | caller | content hash/length must match evidence | same | current top-level value is not echoed; publish the next version, then read the previous value from `versionList[].codeText` |
|
|
296
|
+
| `changelog` | `changelog` | string, max 10000 | optional; empty omitted | caller | version-specific | same | `versionList[].changelog` |
|
|
297
|
+
| `version_id` | query `versionId` | opaque identifier | delete required | `versionList[].versionId`/`id` | target exact version | `DELETE /system/user/import/version/delete?versionId={id}` | version absent from refreshed list |
|
|
298
|
+
| `confirm` | not sent | literal `"DELETE"` | version delete required | fixed CLI guard | destructive action only | local guard | refreshed `versionList` |
|
|
299
|
+
|
|
300
|
+
## Image upload and delete contract
|
|
301
|
+
|
|
302
|
+
There are three different image surfaces; they must not be collapsed into one
|
|
303
|
+
generic "image URL" test.
|
|
304
|
+
|
|
305
|
+
| Surface/CLI field | Official 1.2.16 wire | Input/preprocessing | Required headers | Response/reference | Delete behavior | Current CLI status/readback |
|
|
306
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
307
|
+
| Creator logo: `logo_base64` / `screenshot_base64s` | C project JSON `screenshotBase64sReqs:{name:"logo.webp",screenshotBase64s:<base64>}` | prepared WebP base64 | Creator JSON auth | project `logo` path | U `imagesOps` upload/delete `logo.webp` | live create, replace, delete, restore and detail readback passed |
|
|
308
|
+
| Creator screenshots | U project JSON `imagesOps` | prepared WebP base64 for upload; names for delete/rename | Creator JSON auth | project `images` count | upload/delete use `name`; rename uses `from`/`to` | two-image upload, rename, single delete and final delete all passed with detail counts |
|
|
309
|
+
| Config cover/gallery: `image_url` | `POST /game/data/file/upload/file/image`, multipart field exactly `file`; one request per image | original local file; up to 10 comma-delimited references | raw `Authorization`, optional `X-Device-Id`; no `X-Server-Type` | key priority `cosStoreKey`, `cosStoreUrl`, `key`, then pathname of `downloadUrl/url/fileUrl`; CSV first key is cover | remove/reorder key in CSV and submit config U; no object-delete endpoint observed | two distinct local images uploaded with byte hashes; returned keys passed config C/U detail readback |
|
|
310
|
+
| WA cover/gallery: `image_url` | same endpoint and multipart `file`; one request per image | original local file; up to 10 comma-delimited references | same; no `X-Server-Type` | same key priority and CSV ordering | remove/reorder key in CSV and submit WA U; no object-delete endpoint observed | two distinct local images uploaded with byte hashes; returned keys passed WA C/U detail readback |
|
|
311
|
+
|
|
312
|
+
The 1.2.16 upload helper is `FormData.append("file", File)` and sets
|
|
313
|
+
`Content-Type: multipart/form-data`; the browser adapter supplies the boundary,
|
|
314
|
+
filename, and part MIME. The CLI multipart helper supplies a generated boundary,
|
|
315
|
+
uses the local basename, derives MIME through `mimetypes.guess_type`, and fixes
|
|
316
|
+
`.webp` to `image/webp`. A key-only response is accepted and its display URL is
|
|
317
|
+
synthesized. The CLI does not reproduce the UI's optional client-side image
|
|
318
|
+
compression; the final implementation's original-file upload was accepted by
|
|
319
|
+
the live service and the returned object keys were used in real C/U requests.
|
|
320
|
+
|
|
321
|
+
## Build and linked-state regression matrix
|
|
322
|
+
|
|
323
|
+
This is the A10 branch inventory. Positive rows were sent/read back where the
|
|
324
|
+
installed clients expose a writable branch; negative rows fail locally before
|
|
325
|
+
remote mutation.
|
|
326
|
+
|
|
327
|
+
| Branch | Positive cases | Negative case | Current implementation/gate |
|
|
328
|
+
| --- | --- | --- | --- |
|
|
329
|
+
| Build | `0 retail`, `1 classic_era`, `2 classic`, `3 classic_titan`, `4 anniversary`; for each, backup/config/WA list sends identical body `server` and `X-Server-Type` | `<0`, `>4`, non-integer | `_build` and schema reject outside `0..4`; all five live list triplets passed |
|
|
330
|
+
| Creator publish platform | ModUs-only `1`, BigFoot-only `2`, both `3` | empty, unknown, duplicate | state machine covers at-least-one/enum/duplicate |
|
|
331
|
+
| Creator category | 1-5 IDs from live categories | empty, >5, unknown/non-ID; `998` with ModUs/both | local count/linkage plus write preflight membership against `plugin/list/Categories`; real unknown positive ID fails before project mutation |
|
|
332
|
+
| Config/WA platform | omitted defaults ModUs `1`; explicit `1`; explicit ModUs+BigFoot `3` | `0`, `2`, other; mismatched platform/synchronization pairs | schema choices cover numeric invalids; write normalization requires the pair to be present together and equal; it is distinct from Build |
|
|
333
|
+
| Tier mode | normal `subType=0`, season `1`; each allows none by omitting `requiredTierId`; selected tier is positive dynamic ID | zero/negative/non-ID; ID absent from current normal/season options | write preflight reads the selected tier option set and rejects unknown IDs before mutation; the tested account exposes no tier, so real positive writes cover the no-tier branch |
|
|
334
|
+
| Tier + platform | no-tier allows platform `1/3`; selected tier forces platform `1` in UI | selected tier with platform/synchronization `3` | schema rejects tier+BigFoot; current account's live options are empty so real writes use the confirmed no-tier branch |
|
|
335
|
+
| Config WTF | `exclude_wtf=1` with empty account/role; `0` with an account and, when available, one of its roles | `1` with account/role; `0` without account; backup/account/role absent from the selected Build hierarchy; missing role when the account has roles | write preflight reads the selected Build backup hierarchy and rejects every invalid linkage before mutation |
|
|
336
|
+
| Tags | 1, 2, and 3 dynamic IDs | 0, 4, empty/duplicate CSV position, ID absent from the current config/WA tag options | schema covers count/shape/duplicates; write preflight binds every ID to the selected Build's current config/WA options before mutation |
|
|
337
|
+
| Paid/price | installed 1.2.16 forms submit fixed free `is_paid=0,price=0,share_type=0` | paid value, positive/negative price, or nonzero share type | schema/write preflight enforce the installed UI's fixed free branch; real field cycles cover exactly those values and do not claim an unavailable paid branch |
|
|
338
|
+
| Visibility | public `1`, private `0` where UI exposes it | other integer | schema enum covered; WA 1.2.16 form fixes public |
|
|
339
|
+
| Config/WA image CSV | 1 and 10 keys; first key is cover | empty C, >10, empty CSV entries, upload business code >=400 | schema enforces 1-10 non-empty references; media error and live C/U readback covered |
|
|
340
|
+
| Creator image ops | upload, delete, rename with matching required members | missing/extra member, upload without base64, delete with base64 | local validation plus live upload/rename/delete/readback covered |
|
|
341
|
+
|
|
342
|
+
### Acceptance coverage crosswalk
|
|
343
|
+
|
|
344
|
+
| Acceptance item | Matrix/evidence required here | Documentation status |
|
|
345
|
+
| --- | --- | --- |
|
|
346
|
+
| A2/A3 | Creator project table plus per-field C/U/readback/restore and logo/screenshot/image operations | `analyze/modus-creator/iteration4-live-regression.json`: passed, 140 steps; SHA-256 `7C7BE87959024A1431BC154B13830178815FFF32D23D55E4EAAED75B96B56E4B`; 126 positive exits `0`, 14 expected negative/not-found exits `2`, and unknown positive category membership fails before project mutation; CDN bytes matched local image SHA-256 after create/replace/delete/restore, then final project deletion made the restored logo return 404 |
|
|
347
|
+
| A4 | allocation, metadata, two distinct ZIP PUTs, detail, type/version/path edit/restore and cleanup | same evidence; ZIP SHA-256 and changed MD5/size recorded |
|
|
348
|
+
| A5-A9 | five Builds, config/WA per-field cycles, media upload, version rollover/readback/delete and cleanup | `analyze/modus/live-main-crud-builds-20260826.json`: passed, 209 steps/40 field checks; SHA-256 `298CD33D8E9B72E5AE7B63B6A82627622493CB2DC7476AFE61434E395312BB78`; 191 positive exits `0` and 18 negative exits `2`/`validation_error`; four media uploads carry nonempty byte/SHA-256 evidence for two different binaries, while account/role evidence stores presence only |
|
|
349
|
+
| A10 | linked-state table above, dynamic options and invalid combinations | live option selection plus schema/negative regression passed |
|
|
350
|
+
| A11 | commands, redacted input/response, exit status and content/image/ZIP digest evidence | both evidence records retain hashes instead of credentials or raw content |
|
|
351
|
+
| A19 | complete field names, wire names, types, defaults, sources, dependencies, endpoints and readback locations | matrices in this document |
|
|
188
352
|
|
|
189
353
|
Both main-client modules support explicit `confirm: "DELETE"` on destructive
|
|
190
354
|
commands. List operations send only server fields (`pageNum`, `pageSize`,
|
|
@@ -248,7 +248,13 @@ def _modus_tree(platforms: argparse._SubParsersAction) -> None:
|
|
|
248
248
|
)
|
|
249
249
|
groups = root.add_subparsers(dest="resource_command", required=True)
|
|
250
250
|
session = groups.add_parser("session", help="Local authentication diagnostics").add_subparsers(dest="action_command", required=True)
|
|
251
|
-
_read_leaf(session, "doctor", "Check
|
|
251
|
+
doctor = _read_leaf(session, "doctor", "Check one local ModUs token store, DPAPI decryption, and authenticated API readiness without exposing credentials.", platform="modus", resource="session", action="doctor")
|
|
252
|
+
doctor.add_argument(
|
|
253
|
+
"--client",
|
|
254
|
+
choices=("creator", "main"),
|
|
255
|
+
default="creator",
|
|
256
|
+
help="Authenticated client session to diagnose (technical default: creator).",
|
|
257
|
+
)
|
|
252
258
|
_read_leaf(groups, "builds", "List the fixed ModUs WoW Build ids, codes, names, and the locally selected current Build.", platform="modus", resource="builds", action="list")
|
|
253
259
|
|
|
254
260
|
account = groups.add_parser("account", help="ModUs author account and statistics").add_subparsers(dest="action_command", required=True)
|
|
@@ -303,6 +309,9 @@ def _modus_tree(platforms: argparse._SubParsersAction) -> None:
|
|
|
303
309
|
leaf = _read_leaf(plugin, "get", "Read one ModUs plugin release detail.", platform="modus", resource="plugin", action="get"); leaf.add_argument("--project-id", type=_positive, required=True); leaf.add_argument("--file-id", type=_positive, required=True); _modus_server_flag(leaf)
|
|
304
310
|
leaf = _read_leaf(plugin, "versions", "List releases for one ModUs plugin project.", platform="modus", resource="plugin", action="versions"); leaf.add_argument("--project-id", type=_positive, required=True); _modus_server_flag(leaf); _page_flags(leaf)
|
|
305
311
|
|
|
312
|
+
media = groups.add_parser("media", help="ModUs main-client image uploads").add_subparsers(dest="action_command", required=True)
|
|
313
|
+
_write_leaf(media, "modus", "media", "upload", "Upload one local image and return its reusable ModUs object key and URL.")
|
|
314
|
+
|
|
306
315
|
config = groups.add_parser("config", help="ModUs main-client configuration shares and backups").add_subparsers(dest="action_command", required=True)
|
|
307
316
|
for action, text in (("create", "Create a configuration share from an existing ModUs cloud backup."), ("update", "Update a configuration share."), ("edit", "Edit a configuration share."), ("delete", "Delete one explicitly confirmed configuration share."), ("backup-edit", "Rename an existing ModUs cloud backup."), ("backup-delete", "Delete one explicitly confirmed ModUs cloud backup.")):
|
|
308
317
|
_write_leaf(config, "modus", "config", action, text)
|
|
@@ -378,7 +387,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
378
387
|
if args.dry_run:
|
|
379
388
|
write_output(platform, operation, _dry_run_data(doc, schema.name), dry_run=True)
|
|
380
389
|
return 0
|
|
381
|
-
provider = NewBee() if platform == "newbee" else (DD() if platform == "dd" else (Blackbox() if platform == "blackbox" else (CurseForge() if platform == "curseforge" else _modus_provider(main_session=resource in ("config", "wa")))))
|
|
390
|
+
provider = NewBee() if platform == "newbee" else (DD() if platform == "dd" else (Blackbox() if platform == "blackbox" else (CurseForge() if platform == "curseforge" else _modus_provider(main_session=resource in ("media", "config", "wa")))))
|
|
382
391
|
try:
|
|
383
392
|
if platform == "dd":
|
|
384
393
|
data = provider.execute_write(resource, action, doc, getattr(args, "session", None))
|
|
@@ -391,7 +400,10 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
391
400
|
write_output(platform, operation, data)
|
|
392
401
|
return 0
|
|
393
402
|
modus_doctor = platform == "modus" and resource == "session" and action == "doctor"
|
|
394
|
-
|
|
403
|
+
modus_main_session = resource in ("addon", "plugin", "config", "wa", "builds") or (
|
|
404
|
+
modus_doctor and getattr(args, "client", "creator") == "main"
|
|
405
|
+
)
|
|
406
|
+
provider = NewBee() if platform == "newbee" else (DD() if platform == "dd" else (Blackbox() if platform == "blackbox" else (CurseForge() if platform == "curseforge" else _modus_provider(authenticate=not modus_doctor, main_session=modus_main_session))))
|
|
395
407
|
try:
|
|
396
408
|
if platform == "dd":
|
|
397
409
|
data = provider.execute_read(resource, action, args, getattr(args, "session", None))
|
|
@@ -245,6 +245,7 @@ class Sidecar:
|
|
|
245
245
|
self.dd_dir, self.signature = discover_dd_info()
|
|
246
246
|
self.process: Optional[subprocess.Popen[str]] = None
|
|
247
247
|
self.counter = 0
|
|
248
|
+
self.credential_kind: Optional[str] = None
|
|
248
249
|
self.lock_handle = None
|
|
249
250
|
self.responses: queue.Queue[Any] = queue.Queue()
|
|
250
251
|
self.reader_thread: Optional[threading.Thread] = None
|
|
@@ -276,6 +277,15 @@ class Sidecar:
|
|
|
276
277
|
kind="authentication_error",
|
|
277
278
|
stage="session",
|
|
278
279
|
)
|
|
280
|
+
credential_kind = ready.get("credential_kind")
|
|
281
|
+
if credential_kind not in ("email", "mobile"):
|
|
282
|
+
self.close()
|
|
283
|
+
raise FuploadError(
|
|
284
|
+
"DD sidecar returned an invalid credential kind",
|
|
285
|
+
kind="sidecar_error",
|
|
286
|
+
stage="session",
|
|
287
|
+
)
|
|
288
|
+
self.credential_kind = credential_kind
|
|
279
289
|
return self
|
|
280
290
|
|
|
281
291
|
def _lock(self) -> None:
|
|
@@ -1781,6 +1791,13 @@ def config_readback_projection(value: Any) -> Mapping[str, Any]:
|
|
|
1781
1791
|
projected = dict(value)
|
|
1782
1792
|
if "need_buy" in projected:
|
|
1783
1793
|
projected["need_buy"] = 1 if projected["need_buy"] else 0
|
|
1794
|
+
for name in ("known_addon", "unknown_addon", "material", "font", "known_wa", "unknown_wa"):
|
|
1795
|
+
if projected.get(name) is None:
|
|
1796
|
+
projected[name] = {"items": [], "inner_version": {}}
|
|
1797
|
+
if projected.get("wtf") is None:
|
|
1798
|
+
projected["wtf"] = {"accounts": []}
|
|
1799
|
+
if projected.get("vip_levels") is None:
|
|
1800
|
+
projected["vip_levels"] = []
|
|
1784
1801
|
return projected
|
|
1785
1802
|
|
|
1786
1803
|
|