@follenfang/fupload 0.0.8 → 0.0.10
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.md +1 -1
- package/fupload/SKILL.md +17 -4
- package/fupload/references/modus.md +77 -0
- package/fupload/scripts/fupload_cli/__init__.py +1 -1
- package/fupload/scripts/fupload_cli/cli.py +70 -2
- package/fupload/scripts/fupload_cli/io.py +3 -1
- package/fupload/scripts/fupload_cli/modus.py +768 -0
- package/fupload/scripts/fupload_cli/modus_zip.py +175 -0
- package/fupload/scripts/fupload_cli/schema.py +141 -2
- package/fupload/scripts/fupload_cli/state_machine.py +346 -0
- package/npm/lib/python.mjs +56 -7
- package/npm/skill-manifest.json +33 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ fupload/
|
|
|
42
42
|
- 网易 DD:Windows、Python 3、已安装并登录官方客户端
|
|
43
43
|
- Heybox Workshop:Windows、Python 3、已安装并登录 Heybox 桌面客户端
|
|
44
44
|
|
|
45
|
-
npm 安装和 `fupload update` 会在用户状态目录中创建 Fuploader 专用 Python venv
|
|
45
|
+
npm 安装和 `fupload update` 会在用户状态目录中创建 Fuploader 专用 Python venv,优先使用可用的 `uv` 向其中安装固定版本依赖,并自动安装 Heybox ZIP 上传所需的腾讯官方 COS SDK;未安装 `uv` 时回退到 venv 自带的 pip,不会修改系统 Python。使用 `npm --ignore-scripts` 安装时,首次运行实际 CLI 命令会校验并补齐该 runtime。
|
|
46
46
|
|
|
47
47
|
官方 `ncc` 的安装命令为 `npm i -g @newbeebox/newbeebox-creator-center-cli@latest`。Fuploader 不读取其凭据文件;用户在自己的终端完成 `ncc login`,Agent 仅用 `ncc whoami -o json` 验证。自动化可在启动 Agent 前注入 `NCC_TOKEN`,令牌不得写入命令参数、项目文件或 Git。
|
|
48
48
|
|
package/fupload/SKILL.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fupload
|
|
3
|
-
description: Explicit author-publishing workflow for World of Warcraft plugins, configuration shares, and WA/strings on NewBeeBox, NetEase DD, CurseForge,
|
|
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.10"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Fupload
|
|
9
9
|
|
|
10
|
-
Act as the publishing operator. For NewBeeBox, prefer the official `ncc` CLI whenever it is installed unless the user explicitly requests the third-party Python management tool. For DD, CurseForge, Heybox Workshop, and an explicitly selected third-party NewBeeBox workflow, use the bundled Python CLI. Keep investigation, choices, planning, confirmation, and recovery in the conversation.
|
|
10
|
+
Act as the publishing operator. For NewBeeBox, prefer the official `ncc` CLI whenever it is installed unless the user explicitly requests the third-party Python management tool. For DD, CurseForge, Heybox Workshop, ModUs.Creator, and an explicitly selected third-party NewBeeBox workflow, use the bundled Python CLI. Keep investigation, choices, planning, confirmation, and recovery in the conversation.
|
|
11
11
|
|
|
12
12
|
When installed from npm, interpret `<fupload-cli>` as `fupload`. Only while maintaining the source repository, directly installing this Skill, or when `fupload` is absent from PATH, use `python <skill-root>/scripts/fupload.py`. Keep the user's current project as the working directory in either mode.
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ When installed from npm, interpret `<fupload-cli>` as `fupload`. Only while main
|
|
|
15
15
|
|
|
16
16
|
When the user has not already stated both dimensions, ask in one short message:
|
|
17
17
|
|
|
18
|
-
1. Platform: NewBeeBox, DD, CurseForge,
|
|
18
|
+
1. Platform: NewBeeBox, DD, CurseForge, Heybox Workshop, or ModUs.
|
|
19
19
|
2. Resource and action: plugin/configuration/WA, then create/content update/metadata edit/delete. CurseForge supports public project listing and uploading a plugin ZIP to an existing project only.
|
|
20
20
|
|
|
21
21
|
Map natural language consistently:
|
|
@@ -35,6 +35,8 @@ For DD, use the bundled Python CLI.
|
|
|
35
35
|
|
|
36
36
|
For CurseForge, use the bundled Python CLI. Do not substitute direct HTTP calls or the CurseForge for Studios API for uploads: public project lookup uses the Core API, while game-version lookup and upload use the separate Upload API and credential.
|
|
37
37
|
|
|
38
|
+
For ModUs, use the bundled Python CLI and the local ModUs.Creator login state. Do not use browser cookies, accept a caller-supplied token, or handcraft API requests.
|
|
39
|
+
|
|
38
40
|
For NewBeeBox, apply this order exactly:
|
|
39
41
|
|
|
40
42
|
1. If the user explicitly requests the third-party Python management tool, use the bundled Python CLI even when `ncc` is installed.
|
|
@@ -52,6 +54,8 @@ For NewBeeBox, prefer an existing official `ncc login`, then a caller-provided `
|
|
|
52
54
|
|
|
53
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.
|
|
54
56
|
|
|
57
|
+
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
|
+
|
|
55
59
|
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.
|
|
56
60
|
|
|
57
61
|
## Locate the Fuploader CLI
|
|
@@ -70,6 +74,7 @@ For npm package maintenance, use `fupload update` to update the CLI, managed Pyt
|
|
|
70
74
|
- Read [references/dd.md](references/dd.md) only for DD.
|
|
71
75
|
- Read [references/curseforge.md](references/curseforge.md) for every CurseForge lookup or upload.
|
|
72
76
|
- Read [references/blackbox.md](references/blackbox.md) for Heybox Workshop plugin list/detail, metadata edit, version upload/edit/delete, and managed web-session authentication.
|
|
77
|
+
- Read [references/modus.md](references/modus.md) for every ModUs project, addon discovery, release, upload, update, or delete workflow.
|
|
73
78
|
- For Python, run the exact leaf command with `--help` before creating its input. Treat help as the executable schema contract.
|
|
74
79
|
|
|
75
80
|
## Investigate
|
|
@@ -90,10 +95,14 @@ For every existing DD update or edit, GET the detail first and rebuild the offic
|
|
|
90
95
|
|
|
91
96
|
For Heybox Workshop, use Fuploader's managed Chromium web profile. If the profile is missing or the creator API reports an expired session, Fuploader opens the Workshop login page visibly for the user to complete login, then reuses the same profile headlessly for signed protocol requests. Never read the desktop client config or import another browser profile, and never accept a caller-supplied profile, cookie, token, signature, or endpoint. `blackbox plugin list`, `get`, and `versions` are read-only; `plugin edit` preserves omitted module fields by reading the complete module first; `plugin update` and `version-edit` upload or reuse a ZIP URL and verify the version readback; `version-delete` waits for asynchronous soft deletion and retries once when the audit state temporarily returns active. Do not print cookies, tokens, signatures, or temporary COS credentials, and do not delete a whole module.
|
|
92
97
|
|
|
98
|
+
For ModUs, run `modus session doctor`, then read account info, project list/detail, categories, `game-versions --key wow_builds`, subscription tiers, and the target's release list/detail before writing. Project create/edit requires a completed persisted state in the order `choose_game -> basic_info -> license -> complete`. Publish targets are a nonempty multi-select: ModUs and BigFoot may coexist; ModUs-only, BigFoot-only, and both derive synchronization values 1, 2, and 3. Category `998` is BigFoot-only. Use the returned category IDs. When subscription tiers returns `[]`, “none” is the complete choice and `required_tier_id` remains null.
|
|
99
|
+
|
|
93
100
|
For DD WA create, collect user choices before generating JSON, then let the Python CLI supply only the official create defaults for omitted form values: seven-day share and purchase lifetimes, `need_buy=false`, category `ui_original`, `Interface/Addons`, empty VIP levels, and version `0`. Submit category IDs as strings even when a discovery response represents them numerically. WA create/update versions contain digits only; update must be numerically greater than the current remote value. Every `!WA:2!` create/update/edit is reparsed by the installed official `WowUIInterface.parseWa` chain, including unchanged edit content. Do not carry create defaults into WA update/edit; omitted existing fields preserve their remote value.
|
|
94
101
|
|
|
95
102
|
For official NewBeeBox, use only `ncc whoami -o json` to verify authentication; do not inspect its credential files. For third-party Python NewBeeBox, run `newbee session doctor`; credentials must come from the Windows Known Folder auth-store and all authenticated requests must use fixed official HTTPS origins. For DD, run `dd session doctor`; discovery must accept only an Authenticode-valid executable from an allowed official NetEase publisher. Doctor is local-only and must not start a native login. Do not set endpoint, credential-directory, or DD executable-path environment overrides.
|
|
96
103
|
|
|
104
|
+
For ModUs, require all four `modus session doctor` booleans to be true before an authenticated read or write. A false result means the local Creator session must be repaired in ModUs.Creator; do not ask for or accept token input.
|
|
105
|
+
|
|
97
106
|
## Own one DD task session
|
|
98
107
|
|
|
99
108
|
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.
|
|
@@ -110,6 +119,8 @@ Expose every business field writable through the selected channel. Do not silent
|
|
|
110
119
|
|
|
111
120
|
Use existing remote values only for omitted fields in `edit` or `update`, where omission means preserve. Show candidates by human name plus ID/SN and relevant status. Ask only choices that cannot be determined from the user's explicit request, local artifacts, or remote reads. For CurseForge upload, collect project ID, ZIP path, changelog, release type, version IDs/names or parent file choice, and each requested optional metadata field; do not infer visibility or approval from upload acceptance. For DD, do not draft the executable JSON incrementally: close the full dependency graph first, then generate one final JSON containing parent fields and stable child IDs/selectors. Python repeats the live GETs before upload or mutation and rejects missing or cross-parent selections; detail is authoritative when DD list and detail timestamps differ. When an existing plugin or WA has `assign_user_sn`, only public scope is selectable and the final rebuilt form must remain public.
|
|
112
121
|
|
|
122
|
+
For ModUs project create/edit, collect the game object; name, alias, summary, category IDs, publish platforms, repository URL, description, dependency IDs, logo/screenshots; and license type, holder, year, and content. Preserve server-managed `logo`, `status`, and `game` detail fields. Project edits use explicit Creator null markers for cleared description, dependencies, and tier. For releases, collect project/file IDs, ZIP, version, release type, changelog, and transaction log path; ZIP parsing derives MD5, sizes, TOC, and supported game versions.
|
|
123
|
+
|
|
113
124
|
## Prepare and confirm
|
|
114
125
|
|
|
115
126
|
Create a durable release directory under the target project's root, never under or beside the installed Skill. Use `publish/<YYYYMMDD-HHmmss>-<platform>-<resource>-<action>/`; if that name already exists, append `-2`, `-3`, and so on instead of reusing it. Put every atomic step in a versioned JSON file ordered as `01-<action>.json`, `02-<action>.json`, and so on. A retry or readback for the same plan reuses its directory; a new independent publishing plan gets a new directory. Use JSON, not YAML. Keep the directory after execution as the target project's publishing record, and do not change its ignore rules unless the user asks.
|
|
@@ -140,6 +151,8 @@ For CurseForge, treat the Upload API's returned file ID as upload acceptance onl
|
|
|
140
151
|
|
|
141
152
|
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.
|
|
142
153
|
|
|
154
|
+
For ModUs `plugin create|upload|update`, require the success transaction to record the actual file ID, metadata, signature, and binary-upload stages without the signed URL. A metadata-only `plugin edit` must record no binary upload. Read back with `plugin get`, `list`, and `versions`; for deletion, delete the release before its project and require negative detail readback for each object.
|
|
155
|
+
|
|
143
156
|
Stop on the first failure. Report completed steps, retained IDs/SNs or media references, the redacted failure stage, whether verification is required, and the smallest safe retry. Explicit DD HTTP/business rejection has `verification_required=false`; interrupted PUT/mutation or failed readback after an accepted write has `verification_required=true`. Native DD failures may include a bounded message, HTTP status, native business code, field validation hints, and `details.log_path`. When `log_path` is present, read only the referenced Fuploader JSONL record under the DD version directory; do not inspect DD's other logs or any credential files. The Fuploader record contains sanitized request and response JSON/body plus byte counts and truncation flags, with signed URLs, cookies, JWTs, credentials, client identifiers, signatures, and tokens recursively redacted. Read back after an uncertain write before resending it. Official NewBeeBox exit codes are `0` success, `1` business error, `2` authentication failure, and `3` network error. Never loop on `quota_exceeded`.
|
|
144
157
|
|
|
145
158
|
Never print or persist tokens, cookies, JWTs, signed URLs, DD `clientNo`, raw WA strings, or raw configuration backup contents.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ModUs.Creator
|
|
2
|
+
|
|
3
|
+
The ModUs provider reuses the local Windows Creator login state from
|
|
4
|
+
`%LOCALAPPDATA%\ModUs.Creator\auth\token.dat`. The file is decrypted with
|
|
5
|
+
CurrentUser DPAPI and the fixed entropy `ModUs.Creator.TokenStore.v1`; token
|
|
6
|
+
contents, cookies, and signed URLs are never accepted as input or printed.
|
|
7
|
+
|
|
8
|
+
## Read operations
|
|
9
|
+
|
|
10
|
+
`modus session doctor` reports only `token_present`, `token_decrypted`,
|
|
11
|
+
`token_nonempty`, and `api_ready`. API readiness is a real read-only
|
|
12
|
+
`system/user/getInfo` probe.
|
|
13
|
+
`modus project list|get` reads author projects. `modus plugin list|get|versions`
|
|
14
|
+
reads release records. `modus options categories|game-versions|subscription-tiers`
|
|
15
|
+
reads dynamic choices returned by the service. `categories` uses Creator's
|
|
16
|
+
`GET plugin/list/Categories`; `subscription-tiers` uses `GET
|
|
17
|
+
user/author/subscription/tiers`; `game-versions` uses `POST
|
|
18
|
+
game/data/config/detail` with a JSON `{"keys": [...]}` body. Repeat `--key`
|
|
19
|
+
to request selected server-defined configuration keys; at least one key is
|
|
20
|
+
required because the service rejects an empty list.
|
|
21
|
+
|
|
22
|
+
## Project writes
|
|
23
|
+
|
|
24
|
+
`modus project create|edit|delete` uses versioned JSON through `--input`.
|
|
25
|
+
The complete project model includes `project_id`, `name`, `alt_name`, `summary`,
|
|
26
|
+
`categories`, `synchronization_type`, `license`, `images`, `repo_url`,
|
|
27
|
+
`required_tier_id`, `required_dependencies`, `cf_url`, returned `logo`, `status`,
|
|
28
|
+
`game`, and `description`. Write inputs use `project_state`, `publish_platforms`,
|
|
29
|
+
`logo_base64`, the compatibility `screenshot_base64s`, and edit-only `image_ops`.
|
|
30
|
+
`publish_platforms` is a non-empty array containing `modus`,
|
|
31
|
+
`bigfoot`, or both; the two values are not mutually exclusive. Creator derives
|
|
32
|
+
wire `synchronizationType` as 1 for ModUs, 2 for BigFoot, and 3 for both.
|
|
33
|
+
When BigFoot is selected, `required_tier_id` must be null. Category ID 998 is
|
|
34
|
+
BigFoot-exclusive and forces BigFoot as the only platform. `categories` contains
|
|
35
|
+
one to five positive integer IDs.
|
|
36
|
+
|
|
37
|
+
Create sends `name`, `altName`, `summary`, comma-delimited `categories`,
|
|
38
|
+
`synchronizationType`, `license`, `images: 0`, `screenshotBase64sReqs`, optional
|
|
39
|
+
`repoUrl`, and optional positive `requiredTierId`. `logo_base64` is the payload
|
|
40
|
+
named `logo.webp`; `screenshot_base64s` remains the compatibility alias.
|
|
41
|
+
`license` is the JSON string generated from `type`, `holder`, `year`, and
|
|
42
|
+
`content`.
|
|
43
|
+
|
|
44
|
+
Edit sends only changed Creator fields plus `projectId`. `description`, cleared
|
|
45
|
+
`repo_url`, cleared `alt_name`, null `required_tier_id`, and cleared
|
|
46
|
+
`required_dependencies` use Creator's literal `<null>` marker. `image_ops` maps
|
|
47
|
+
to `imagesOps` entries with `op` (`upload` or `delete`), `name`, and upload-only
|
|
48
|
+
`base64`; `images` is required with image operations. `cf_url`, returned `logo`,
|
|
49
|
+
and `status` are read-only/server-managed. `publish_platforms` and `game` are
|
|
50
|
+
local form state, not project update keys.
|
|
51
|
+
`project_state` is a resumable `choose_game` -> `basic_info` -> `license` ->
|
|
52
|
+
`complete` snapshot and is required for both create and edit; incomplete or
|
|
53
|
+
missing snapshots are rejected before dry-run or network access. Each step preserves
|
|
54
|
+
its state on validation failure. `required_tier_id` is null for the explicit
|
|
55
|
+
no-tier branch or a positive dropdown ID.
|
|
56
|
+
The write document also accepts `confirm` for destructive operations.
|
|
57
|
+
Deletion requires `confirm: "DELETE"`.
|
|
58
|
+
|
|
59
|
+
## Plugin release writes
|
|
60
|
+
|
|
61
|
+
`modus plugin create|upload|update|edit|delete` uses the release fields
|
|
62
|
+
`project_id`, `file_id`, `version`, `type`, `supported_game_versions`, `md5`,
|
|
63
|
+
`zip_size`, `unzip_size`, `path`, `toc_version`, `changelog`, and `file`.
|
|
64
|
+
`transaction_log` optionally selects the redacted transaction record path.
|
|
65
|
+
Successful records include the completed `file_id`, metadata, signature, and
|
|
66
|
+
binary upload stages that ran; failed records include `failed_stage`, a redacted
|
|
67
|
+
error, and whether the ZIP was retained. The provider parses every ZIP `.toc`
|
|
68
|
+
`Interface` value to derive
|
|
69
|
+
`toc_version` and `supported_game_versions`; explicit caller values must match.
|
|
70
|
+
The provider validates the ZIP, computes `md5`, `zip_size`, and `unzip_size`,
|
|
71
|
+
registers metadata, obtains a signed URL, then uploads the ZIP bytes with HTTP
|
|
72
|
+
`PUT` and `Content-Type: application/zip`. A local path is metadata only; it is
|
|
73
|
+
never substituted for the binary upload. Release deletion requires
|
|
74
|
+
`confirm: "DELETE"`.
|
|
75
|
+
|
|
76
|
+
All write commands support `--dry-run`, which validates the versioned JSON and
|
|
77
|
+
local files without constructing an authenticated provider or making a request.
|
|
@@ -30,6 +30,13 @@ schema exposes them. The calling Skill must show the complete plan and obtain co
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def _modus_provider(*, authenticate: bool = True) -> Any:
|
|
34
|
+
# Keep ModUs optional at import time so existing platform commands remain
|
|
35
|
+
# usable while the provider is installed or upgraded independently.
|
|
36
|
+
from .modus import Modus
|
|
37
|
+
return Modus(authenticate=authenticate)
|
|
38
|
+
|
|
39
|
+
|
|
33
40
|
def _parser(**kwargs: Any) -> argparse.ArgumentParser:
|
|
34
41
|
return argparse.ArgumentParser(
|
|
35
42
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
@@ -221,6 +228,65 @@ def _blackbox_tree(platforms: argparse._SubParsersAction) -> None:
|
|
|
221
228
|
_write_leaf(plugin, "blackbox", "version", "delete", "Soft-delete one Heybox Workshop plugin version and verify its deleted state.", command="version-delete")
|
|
222
229
|
|
|
223
230
|
|
|
231
|
+
def _modus_tree(platforms: argparse._SubParsersAction) -> None:
|
|
232
|
+
root = platforms.add_parser(
|
|
233
|
+
"modus", help="ModUs.Creator author plugin operations",
|
|
234
|
+
description="Reuse the local ModUs.Creator Windows login state; token and signed upload material are never accepted as input.",
|
|
235
|
+
)
|
|
236
|
+
groups = root.add_subparsers(dest="resource_command", required=True)
|
|
237
|
+
session = groups.add_parser("session", help="Local authentication diagnostics").add_subparsers(dest="action_command", required=True)
|
|
238
|
+
_read_leaf(session, "doctor", "Check the local ModUs.Creator token store, DPAPI decryption, and authenticated API readiness without exposing credentials.", platform="modus", resource="session", action="doctor")
|
|
239
|
+
|
|
240
|
+
account = groups.add_parser("account", help="ModUs author account and statistics").add_subparsers(dest="action_command", required=True)
|
|
241
|
+
_read_leaf(account, "info", "Read the current ModUs account capability flags.", platform="modus", resource="account", action="info")
|
|
242
|
+
_read_leaf(account, "subscription-count", "Read the active author subscription count.", platform="modus", resource="account", action="subscription-count")
|
|
243
|
+
_read_leaf(account, "statistics", "Read current author project statistics.", platform="modus", resource="account", action="statistics")
|
|
244
|
+
|
|
245
|
+
addon = groups.add_parser("addon", help="ModUs addon discovery and history APIs").add_subparsers(dest="action_command", required=True)
|
|
246
|
+
leaf = _read_leaf(addon, "info", "Resolve addon directories to project records.", platform="modus", resource="addon", action="info")
|
|
247
|
+
leaf.add_argument("--directory", dest="directories", action="append", required=True)
|
|
248
|
+
leaf.add_argument("--server-type", type=int, default=1)
|
|
249
|
+
leaf = _read_leaf(addon, "project-info", "Resolve addon project IDs to names.", platform="modus", resource="addon", action="project-info")
|
|
250
|
+
leaf.add_argument("--project-id", dest="project_ids", action="append", type=_positive, required=True)
|
|
251
|
+
leaf.add_argument("--server-type", type=int, default=1)
|
|
252
|
+
leaf = _read_leaf(addon, "history", "Read addon project version history.", platform="modus", resource="addon", action="history")
|
|
253
|
+
leaf.add_argument("--project-id", type=_positive, required=True)
|
|
254
|
+
leaf.add_argument("--server-type", type=int, default=1)
|
|
255
|
+
_page_flags(leaf)
|
|
256
|
+
|
|
257
|
+
options = groups.add_parser("options", help="Read ModUs dynamic choices").add_subparsers(dest="option_action", required=True)
|
|
258
|
+
_read_leaf(options, "categories", "List plugin categories returned by ModUs.", platform="modus", resource="options", action="categories")
|
|
259
|
+
game_versions = _read_leaf(options, "game-versions", "List supported ModUs game versions.", platform="modus", resource="options", action="game-versions")
|
|
260
|
+
game_versions.add_argument("--key", dest="keys", action="append", required=True, help="Request one game config key; repeatable.")
|
|
261
|
+
_read_leaf(options, "subscription-tiers", "List author subscription tiers returned by ModUs.", platform="modus", resource="options", action="subscription-tiers")
|
|
262
|
+
|
|
263
|
+
project = groups.add_parser("project", help="ModUs plugin project records").add_subparsers(dest="action_command", required=True)
|
|
264
|
+
for action, text in (
|
|
265
|
+
("create", "Create a ModUs plugin project."),
|
|
266
|
+
("edit", "Edit ModUs plugin project metadata."),
|
|
267
|
+
("delete", "Delete one explicitly confirmed ModUs plugin project."),
|
|
268
|
+
):
|
|
269
|
+
_write_leaf(project, "modus", "project", action, text)
|
|
270
|
+
leaf = _read_leaf(project, "list", "List plugin projects owned by the current ModUs author.", platform="modus", resource="project", action="list"); _list_flags(leaf)
|
|
271
|
+
leaf = _read_leaf(project, "get", "Read one ModUs plugin project detail.", platform="modus", resource="project", action="get"); leaf.add_argument("--project-id", type=_positive, required=True)
|
|
272
|
+
leaf = _read_leaf(project, "dependencies", "Query ModUs project dependency candidates.", platform="modus", resource="project", action="dependencies")
|
|
273
|
+
leaf.add_argument("--query", help="Dependency search text or JSON request body.")
|
|
274
|
+
leaf.add_argument("--project-id", type=_positive)
|
|
275
|
+
|
|
276
|
+
plugin = groups.add_parser("plugin", help="ModUs plugin releases and ZIP uploads").add_subparsers(dest="action_command", required=True)
|
|
277
|
+
for action, text in (
|
|
278
|
+
("create", "Create and upload the first ModUs plugin release."),
|
|
279
|
+
("upload", "Upload a ModUs plugin ZIP and register its release metadata."),
|
|
280
|
+
("update", "Publish a new ModUs plugin release version."),
|
|
281
|
+
("edit", "Edit ModUs plugin release metadata."),
|
|
282
|
+
("delete", "Delete one explicitly confirmed ModUs plugin release."),
|
|
283
|
+
):
|
|
284
|
+
_write_leaf(plugin, "modus", "plugin", action, text)
|
|
285
|
+
leaf = _read_leaf(plugin, "list", "List releases for one ModUs plugin project.", platform="modus", resource="plugin", action="list"); leaf.add_argument("--project-id", type=_positive, required=True); _page_flags(leaf)
|
|
286
|
+
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)
|
|
287
|
+
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); _page_flags(leaf)
|
|
288
|
+
|
|
289
|
+
|
|
224
290
|
def build_parser() -> argparse.ArgumentParser:
|
|
225
291
|
parser = _parser(
|
|
226
292
|
prog="fupload",
|
|
@@ -233,6 +299,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
233
299
|
_dd_tree(platforms)
|
|
234
300
|
_curseforge_tree(platforms)
|
|
235
301
|
_blackbox_tree(platforms)
|
|
302
|
+
_modus_tree(platforms)
|
|
236
303
|
return parser
|
|
237
304
|
|
|
238
305
|
|
|
@@ -280,7 +347,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
280
347
|
if args.dry_run:
|
|
281
348
|
write_output(platform, operation, _dry_run_data(doc, schema.name), dry_run=True)
|
|
282
349
|
return 0
|
|
283
|
-
provider = NewBee() if platform == "newbee" else (DD() if platform == "dd" else (Blackbox() if platform == "blackbox" else CurseForge()))
|
|
350
|
+
provider = NewBee() if platform == "newbee" else (DD() if platform == "dd" else (Blackbox() if platform == "blackbox" else (CurseForge() if platform == "curseforge" else _modus_provider())))
|
|
284
351
|
try:
|
|
285
352
|
if platform == "dd":
|
|
286
353
|
data = provider.execute_write(resource, action, doc, getattr(args, "session", None))
|
|
@@ -292,7 +359,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
292
359
|
close()
|
|
293
360
|
write_output(platform, operation, data)
|
|
294
361
|
return 0
|
|
295
|
-
|
|
362
|
+
modus_doctor = platform == "modus" and resource == "session" and action == "doctor"
|
|
363
|
+
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))))
|
|
296
364
|
try:
|
|
297
365
|
if platform == "dd":
|
|
298
366
|
data = provider.execute_read(resource, action, args, getattr(args, "session", None))
|
|
@@ -89,7 +89,9 @@ def sanitize_output(value: Any) -> Any:
|
|
|
89
89
|
result: Dict[str, Any] = {}
|
|
90
90
|
for key, item in value.items():
|
|
91
91
|
normalized = str(key).replace("-", "_").lower()
|
|
92
|
-
if normalized in
|
|
92
|
+
if normalized in {"token_present", "token_decrypted", "token_nonempty", "api_ready"} and isinstance(item, bool):
|
|
93
|
+
result[key] = item
|
|
94
|
+
elif normalized in _SENSITIVE_KEYS or any(
|
|
93
95
|
marker in normalized
|
|
94
96
|
for marker in ("token", "cookie", "credential", "signature", "password", "secret")
|
|
95
97
|
):
|