@follenfang/fupload 0.0.20 → 0.0.22

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 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.20"
5
+ version: "0.0.22"
6
6
  ---
7
7
 
8
8
  # Fupload
@@ -22,7 +22,7 @@ Map natural language consistently:
22
22
 
23
23
  - `create`: create the main record. If first content is a separate platform action, plan it as a following atomic step.
24
24
  - `update`: publish a plugin version, change selected configuration-backup content, or publish a WA/string version.
25
- - `edit`: change fields allowed by the target platform's action allowlist. For DD plugins, this includes `description` and existing-record commercial, association, room/channel, and creation-statement settings; other first-publication metadata, categories, media, and version fields are create/update-only. Description edits read both detail and author-list projections; either may confirm the value, but if neither matches, report verification-required.
25
+ - `edit`: change fields allowed by the target platform's action allowlist. For DD plugins, this includes `description`, `html_desc`, and existing-record commercial, association, room/channel, and creation-statement settings; other first-publication metadata, categories, media, and version fields are create/update-only. Description edits read both detail and author-list projections; either may confirm the value, but if neither matches, report verification-required.
26
26
  - `delete`: delete one main record only. It never deletes an individual version, media object, or associated record.
27
27
 
28
28
  Never offer bulk delete, drafts, guides, messages, or GUI control automation. Heybox supports individual version deletion with asynchronous readback; whole-module deletion is not supported. The DD task-session command may close verified official GUI processes only after the consent flow below.
@@ -73,7 +73,7 @@ A published version's `update_desc` cannot be corrected through the CLI: `plugin
73
73
 
74
74
  Every path in `detail_img_files` must exist as a regular local file. Validation reports the exact indexed path, such as `$.detail_img_files[0]`, before any upload or mutation.
75
75
 
76
- `game_type` and the outer free/paid state are create-only. `plugin update` requires `sn`, `game_versions`, `version`, and `update_desc`; optional `file`, `detail_url`, and `release_type` publish a new version. `plugin edit` requires `sn` and accepts `description` plus the official existing-record commercial and association controls (`scope`, payment/lifetime/VIP fields, room/channel linkage, `creation_statement`, and associated content). Other first-publication metadata (`addon_type`, `name`, logo, detail images, categories, and `html_desc`) and version fields are not edit fields. The CLI verifies a description edit through both detail and author-list readback; if DD accepts the request but leaves the remote value unchanged, it reports a verification-required error rather than false success.
76
+ `game_type` and the outer free/paid state are create-only. `plugin update` requires `sn`, `game_versions`, `version`, and `update_desc`; optional `file`, `detail_url`, and `release_type` publish a new version. `plugin edit` requires `sn` and accepts `description`, `html_desc`, plus the official existing-record commercial and association controls (`scope`, payment/lifetime/VIP fields, room/channel linkage, `creation_statement`, and associated content). Other first-publication metadata (`addon_type`, `name`, logo, detail images, and categories) and version fields are not edit fields. The CLI verifies description edits through both detail and author-list readback; if DD accepts the request but leaves the remote value unchanged, it reports a verification-required error rather than false success.
77
77
 
78
78
  When `assign_user_sn` is present, the official UI exposes only the public scope option. Python validates the final rebuilt form and rejects a private scope before upload or mutation.
79
79
 
@@ -1,3 +1,3 @@
1
1
  """Fupload Python CLI."""
2
2
 
3
- __version__ = "0.0.20"
3
+ __version__ = "0.0.22"
@@ -1465,10 +1465,11 @@ PLUGIN_CREATE_DEFAULTS = {
1465
1465
 
1466
1466
  # DD's modify form is not a second create form. The official edit page
1467
1467
  # rebuilds the payload from existing commercial/association controls. The
1468
- # brief description is the supported metadata exception and is verified by
1469
- # detail and author-list readback after every edit.
1468
+ # brief and rich-text detail descriptions are the supported metadata
1469
+ # exceptions and are verified by detail and author-list readback after every
1470
+ # edit. Other first-publication metadata remains create-only.
1470
1471
  PLUGIN_EDIT_FIELDS = (
1471
- "description",
1472
+ "description", "html_desc",
1472
1473
  "scope", "share_code_life_type", "need_buy", "price_fen", "buy_life_type",
1473
1474
  "jump_room", "room_id", "channel_id", "channel_type", "sync_room",
1474
1475
  "creation_statement", "with_associate", "associated_acts", "need_anchor_vip",
@@ -1485,13 +1486,15 @@ def plugin_form(
1485
1486
  if isinstance(author_value, Mapping) and isinstance(author_value.get("latest_version"), dict)
1486
1487
  else {}
1487
1488
  )
1488
- # Official detail dialog projection followed by the editor's pick list.
1489
+ # The author list is the live publication projection. DD detail_v2 can
1490
+ # retain an older latest_version, so an available author-list value wins
1491
+ # over detail for the version/package fields used to rebuild modify form.
1489
1492
  source = {name: copy.deepcopy(value[name]) for name in PLUGIN_FIELDS if name in value}
1490
1493
  for name in ("detail_url", "release_type", "version"):
1491
1494
  latest_name = {"detail_url": "file_path"}.get(name, name)
1492
- projected = latest.get(latest_name)
1495
+ projected = author_latest.get(latest_name)
1493
1496
  if projected is None:
1494
- projected = author_latest.get(latest_name)
1497
+ projected = latest.get(latest_name)
1495
1498
  if projected is None:
1496
1499
  source.pop(name, None)
1497
1500
  else:
@@ -1516,22 +1519,29 @@ def plugin_form(
1516
1519
  return form
1517
1520
 
1518
1521
 
1519
- def plugin_version_projection(value: Mapping[str, Any]) -> Dict[str, Any]:
1522
+ def plugin_version_projection(
1523
+ value: Mapping[str, Any], author_value: Optional[Mapping[str, Any]] = None,
1524
+ ) -> Dict[str, Any]:
1520
1525
  """Project version fields for readback without feeding them into modify."""
1521
1526
  latest = value.get("latest_version") if isinstance(value.get("latest_version"), dict) else {}
1527
+ author_latest = (
1528
+ author_value.get("latest_version")
1529
+ if isinstance(author_value, Mapping) and isinstance(author_value.get("latest_version"), dict)
1530
+ else {}
1531
+ )
1522
1532
  projection: Dict[str, Any] = {}
1523
1533
  sources = {
1524
- "game_versions": (latest, "game_versions", value, "game_versions"),
1525
- "detail_url": (latest, "file_path", value, "detail_url"),
1526
- "release_type": (latest, "release_type", value, "release_type"),
1527
- "version": (latest, "version", value, "version"),
1528
- "update_desc": (latest, "update_desc", value, "update_desc"),
1534
+ "game_versions": ((author_latest, "game_versions"), (latest, "game_versions"), (value, "game_versions")),
1535
+ "detail_url": ((author_latest, "file_path"), (latest, "file_path"), (value, "detail_url")),
1536
+ "release_type": ((author_latest, "release_type"), (latest, "release_type"), (value, "release_type")),
1537
+ "version": ((author_latest, "version"), (latest, "version"), (value, "version")),
1538
+ "update_desc": ((author_latest, "update_desc"), (latest, "update_desc"), (value, "update_desc")),
1529
1539
  }
1530
- for name, (preferred, preferred_name, fallback, fallback_name) in sources.items():
1531
- if preferred.get(preferred_name) is not None:
1532
- projection[name] = copy.deepcopy(preferred[preferred_name])
1533
- elif fallback.get(fallback_name) is not None:
1534
- projection[name] = copy.deepcopy(fallback[fallback_name])
1540
+ for name, candidates in sources.items():
1541
+ for source, source_name in candidates:
1542
+ if source.get(source_name) is not None:
1543
+ projection[name] = copy.deepcopy(source[source_name])
1544
+ break
1535
1545
  return projection
1536
1546
 
1537
1547
 
@@ -2087,11 +2097,33 @@ class DD:
2087
2097
  apply_present(form, doc, PLUGIN_FIELDS)
2088
2098
  else:
2089
2099
  current = self._fresh_detail(session, "plugin", doc["sn"])
2100
+ if current.get("assign_user_sn") and doc.get("scope") == "private":
2101
+ raise ValidationError("assigned plugins can only use public scope", path="$.scope")
2090
2102
  game_type = current.get("game_type") or (current.get("game_types") or [None])[0]
2091
2103
  listing = author_item(
2092
2104
  session, "plugin", str(doc["sn"]), str(current.get("name") or ""), game_type,
2093
2105
  )
2094
- form = plugin_form(current, listing)
2106
+ if action == "edit":
2107
+ if not listing:
2108
+ raise FuploadError(
2109
+ "DD author-list projection is required to preserve the published plugin version",
2110
+ kind="verification_required", stage="dependency_get", endpoint="/addon/list",
2111
+ verification_required=True,
2112
+ )
2113
+ published_version = plugin_version_projection({}, listing)
2114
+ missing_published = [
2115
+ name for name in ("detail_url", "release_type", "version")
2116
+ if name not in published_version
2117
+ ]
2118
+ if missing_published:
2119
+ raise FuploadError(
2120
+ "DD author-list projection is missing published plugin field(s): %s"
2121
+ % ", ".join(missing_published),
2122
+ kind="verification_required", stage="dependency_get", endpoint="/addon/list",
2123
+ verification_required=True,
2124
+ details={"fields": missing_published},
2125
+ )
2126
+ form = plugin_form(current, listing) if action == "edit" else plugin_form(current)
2095
2127
  allowed = PLUGIN_EDIT_FIELDS if action == "edit" else ("game_versions", "detail_url", "release_type", "version", "update_desc")
2096
2128
  current_version = str(form.get("version") or "").strip()
2097
2129
  apply_present(form, doc, allowed)
@@ -2151,6 +2183,7 @@ class DD:
2151
2183
  "creation_statement", "need_buy",
2152
2184
  "price_fen", "buy_life_type", "jump_room", "room_id", "channel_id", "channel_type",
2153
2185
  "sync_room", "with_associate", "associated_acts", "need_anchor_vip", "vip_levels",
2186
+ "game_versions", "detail_url", "release_type", "version",
2154
2187
  )
2155
2188
  if action != "update":
2156
2189
  raw_readback: Any = {}
@@ -581,6 +581,10 @@ DD_PLUGIN_EDIT_META = {
581
581
  for name in DD_COMMERCIAL
582
582
  }
583
583
  DD_PLUGIN_EDIT_META["description"] = DD_PLUGIN_META["description"]
584
+ # DD's modify form accepts the existing plugin's rich-text detail body as
585
+ # metadata. Keep the other first-publication metadata (name, logo, images,
586
+ # categories, etc.) create-only, but allow an explicit html_desc edit.
587
+ DD_PLUGIN_EDIT_META["html_desc"] = DD_PLUGIN_META["html_desc"]
584
588
  DD_PLUGIN_VERSION = {
585
589
  "game_versions": f("array", nonempty=True), "detail_url": f("string"),
586
590
  "file": f("string", local_file=True), "release_type": f("integer", choices=(1, 2, 3)),
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema": "fupload.npm-skill-manifest.v1",
3
3
  "package_name": "@follenfang/fupload",
4
- "package_version": "0.0.20",
5
- "skill_version": "0.0.20",
6
- "tree_sha256": "bea57f52a133edcd0062736fb1f7c0c560293cb51454275bb1d7bf2234c2d67c",
4
+ "package_version": "0.0.22",
5
+ "skill_version": "0.0.22",
6
+ "tree_sha256": "fffd84093cea75f44ca8ad836f4cfb643e692cf121162201892ad130f23fbc5a",
7
7
  "files": [
8
8
  {
9
9
  "path": "agents/openai.yaml",
@@ -88,7 +88,7 @@
88
88
  {
89
89
  "path": "references/dd.md",
90
90
  "bytes": 20837,
91
- "sha256": "583a3531cfabf6749c31088a88b3bbad2c516e1eae0b100433305f26894c153d"
91
+ "sha256": "4a174fa9470a23aee5c607b85123575c157559570a2d3fd4caa9a0874f9ae934"
92
92
  },
93
93
  {
94
94
  "path": "references/modus.md",
@@ -113,7 +113,7 @@
113
113
  {
114
114
  "path": "scripts/fupload_cli/__init__.py",
115
115
  "bytes": 50,
116
- "sha256": "72c39ea05a04d5f631b53d5363ab86a8c974169c19c56245d7ab2c3c16498ba4"
116
+ "sha256": "0cec28503481955d702020619b9cd9b3ec7032c4d20be0bbad2aa9f4f5dac6f8"
117
117
  },
118
118
  {
119
119
  "path": "scripts/fupload_cli/blackbox_web.py",
@@ -147,8 +147,8 @@
147
147
  },
148
148
  {
149
149
  "path": "scripts/fupload_cli/dd.py",
150
- "bytes": 115390,
151
- "sha256": "e160eeebb2914016d99f6dad1ad38b61fa201c884bb2314090839408fa70cd58"
150
+ "bytes": 117372,
151
+ "sha256": "19a303e84ce08dd3af9a905d04d24f7224c3302f83f0d62f899618cf45fe0433"
152
152
  },
153
153
  {
154
154
  "path": "scripts/fupload_cli/errors.py",
@@ -182,8 +182,8 @@
182
182
  },
183
183
  {
184
184
  "path": "scripts/fupload_cli/schema.py",
185
- "bytes": 54628,
186
- "sha256": "6eead57520d6f1d0db62d6da764c4e419cca24a267483dd3708f212a492e396a"
185
+ "bytes": 54911,
186
+ "sha256": "bede6cc93de8a9d766e463cab0acb8725745da781aebabba73744394b594b93c"
187
187
  },
188
188
  {
189
189
  "path": "scripts/fupload_cli/state_machine.py",
@@ -207,8 +207,8 @@
207
207
  },
208
208
  {
209
209
  "path": "SKILL.md",
210
- "bytes": 28568,
211
- "sha256": "98335037547a916e251f65c39a686eb3c9a58468f113531c1e28a9ec225198e9"
210
+ "bytes": 28582,
211
+ "sha256": "925b6b480edae4272a6922200a7f046e95df96908edecd4660e1fc49bdb6688d"
212
212
  }
213
213
  ]
214
214
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@follenfang/fupload",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "description": "Install and run the Fuploader Agent Skill and Python CLI.",
5
5
  "type": "module",
6
6
  "bin": {