@follenfang/fupload 0.0.16 → 0.0.17

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.16"
5
+ version: "0.0.17"
6
6
  ---
7
7
 
8
8
  # Fupload
@@ -1,3 +1,3 @@
1
1
  """Fupload Python CLI."""
2
2
 
3
- __version__ = "0.0.16"
3
+ __version__ = "0.0.17"
@@ -240,6 +240,20 @@ def state_dir() -> Path:
240
240
  return result
241
241
 
242
242
 
243
+ def _sidecar_startup_error(error: Mapping[str, Any]) -> FuploadError:
244
+ message = str(error.get("message") or "DD sidecar failed to start")
245
+ return FuploadError(
246
+ message,
247
+ kind=str(error.get("kind") or "authentication_error"),
248
+ stage=str(error.get("stage") or "session"),
249
+ endpoint=error.get("endpoint"),
250
+ http_status=error.get("http_status"),
251
+ business_code=error.get("business_code"),
252
+ verification_required=bool(error.get("verification_required")),
253
+ details=error.get("details") if isinstance(error.get("details"), dict) else None,
254
+ )
255
+
256
+
243
257
  class Sidecar:
244
258
  def __init__(self) -> None:
245
259
  self.dd_dir, self.signature = discover_dd_info()
@@ -272,11 +286,8 @@ class Sidecar:
272
286
  raise
273
287
  if not ready.get("ready"):
274
288
  self.close()
275
- raise FuploadError(
276
- str((ready.get("error") or {}).get("message") or "DD sidecar failed to start"),
277
- kind="authentication_error",
278
- stage="session",
279
- )
289
+ error = ready.get("error") if isinstance(ready.get("error"), dict) else {}
290
+ raise _sidecar_startup_error(error)
280
291
  credential_kind = ready.get("credential_kind")
281
292
  if credential_kind not in ("email", "mobile"):
282
293
  self.close()
@@ -13,7 +13,7 @@ import time
13
13
  import uuid
14
14
  from pathlib import Path
15
15
  from types import SimpleNamespace
16
- from typing import Any, Dict, List, Optional
16
+ from typing import Any, Dict, List, Mapping, Optional
17
17
 
18
18
  from .errors import FuploadError, redact
19
19
  from .trust import verify_dd_executable
@@ -406,6 +406,17 @@ def _send(value: Dict[str, Any], timeout: float = 300) -> Dict[str, Any]:
406
406
  return response.get("data")
407
407
 
408
408
 
409
+ def _startup_error(pending: Mapping[str, Any]) -> FuploadError:
410
+ error = pending.get("error")
411
+ if isinstance(error, dict):
412
+ return FuploadError.from_dict(error)
413
+ return FuploadError(
414
+ str(error or "DD task session failed to start"),
415
+ kind="session_start_failed",
416
+ stage="session",
417
+ )
418
+
419
+
409
420
  def start(confirm_close_gui: bool) -> Dict[str, Any]:
410
421
  existing = _load_live_state()
411
422
  if existing:
@@ -447,7 +458,7 @@ def start(confirm_close_gui: bool) -> Dict[str, Any]:
447
458
  creationflags=flags,
448
459
  )
449
460
  deadline = time.time() + 90
450
- last_error = ""
461
+ last_error: Any = ""
451
462
  while time.time() < deadline:
452
463
  state = _load_live_state()
453
464
  if state and state.get("startup_id") == startup_id:
@@ -465,7 +476,7 @@ def start(confirm_close_gui: bool) -> Dict[str, Any]:
465
476
  if startup.exists():
466
477
  try:
467
478
  pending = _read_json(startup)
468
- last_error = str(pending.get("error") or "")
479
+ last_error = pending.get("error") or ""
469
480
  except FuploadError:
470
481
  pass
471
482
  if process.poll() is not None:
@@ -487,10 +498,11 @@ def start(confirm_close_gui: bool) -> Dict[str, Any]:
487
498
  try:
488
499
  pending = _read_json(startup)
489
500
  if pending.get("startup_id") == startup_id:
501
+ last_error = pending.get("error") or last_error
490
502
  startup.unlink()
491
503
  except (FuploadError, OSError):
492
504
  pass
493
- raise FuploadError(last_error or "DD task session failed to start", kind="session_start_failed", stage="session")
505
+ raise _startup_error({"error": last_error})
494
506
 
495
507
 
496
508
  def status(session_id: Optional[str] = None) -> Dict[str, Any]:
@@ -655,9 +667,17 @@ def _serve(startup_id: str) -> int:
655
667
  connection.sendall((json.dumps(response, ensure_ascii=False, separators=(",", ":")) + "\n").encode("utf-8"))
656
668
  return 0
657
669
  except Exception as exc:
670
+ if isinstance(exc, FuploadError):
671
+ error = exc.as_dict()
672
+ else:
673
+ error = FuploadError(
674
+ redact(str(exc))[:400] or "DD task session failed to start",
675
+ kind="session_start_failed",
676
+ stage="session",
677
+ ).as_dict()
658
678
  _atomic_json(startup_path, {
659
679
  "startup_id": startup_id,
660
- "error": redact(str(exc))[:400],
680
+ "error": error,
661
681
  })
662
682
  return 1
663
683
  finally:
@@ -362,12 +362,13 @@ def _validation_details(probe):
362
362
  return result
363
363
 
364
364
 
365
- def install_response_probe(client):
365
+ def install_response_probe(client, capture_success=False):
366
366
  """Capture response objects and HTTPError bodies without changing DD behavior."""
367
367
  session = getattr(client, "_session", None)
368
368
  restore = getattr(client, "_fupload_restore_response_probe", None)
369
369
  if callable(restore):
370
370
  restore()
371
+ client._fupload_capture_success = bool(capture_success)
371
372
  original_opener_open = urllib.request.OpenerDirector.open
372
373
 
373
374
  def opener_open(opener, *args, **kwargs):
@@ -400,6 +401,7 @@ def install_response_probe(client):
400
401
  if getattr(urllib.request.OpenerDirector, "open", None) is opener_open:
401
402
  urllib.request.OpenerDirector.open = original_opener_open
402
403
  client._fupload_restore_response_probe = None
404
+ client._fupload_capture_success = False
403
405
 
404
406
  client._fupload_restore_response_probe = restore_probe
405
407
  for method_name in ("get", "post"):
@@ -415,7 +417,7 @@ def install_response_probe(client):
415
417
  status = int(getattr(response, "status_code", 0) or 0)
416
418
  except (TypeError, ValueError):
417
419
  status = 0
418
- if status >= 400:
420
+ if status >= 400 or getattr(client, "_fupload_capture_success", False):
419
421
  try:
420
422
  body = str(getattr(response, "text", "") or "")
421
423
  except Exception:
@@ -507,6 +509,59 @@ def failure_from_exception(exc, stage, response=None):
507
509
  )
508
510
 
509
511
 
512
+ def author_login_failure(client):
513
+ probe = getattr(client, "_fupload_last_response_error", None)
514
+ return failure_from_exception(
515
+ RuntimeError("DD author API login failed"), "session", probe,
516
+ )
517
+
518
+
519
+ def _last_response_payload(client):
520
+ probe = getattr(client, "_fupload_last_response_error", None)
521
+ if not isinstance(probe, dict) or not probe.get("body"):
522
+ return None
523
+ try:
524
+ payload = json.loads(probe["body"])
525
+ except (TypeError, ValueError):
526
+ return None
527
+ return payload if isinstance(payload, dict) else None
528
+
529
+
530
+ def login_author_client(client):
531
+ try:
532
+ if client.login():
533
+ return
534
+ if _retryable_get_signature_rejection(_last_response_payload(client)):
535
+ client._fupload_last_response_error = None
536
+ if client.login():
537
+ return
538
+ raise author_login_failure(client)
539
+ finally:
540
+ client._fupload_capture_success = False
541
+
542
+
543
+ def author_session_or_cleanup(qt, container, flow, jwt_helper, client):
544
+ session = (qt, container, flow, jwt_helper, client)
545
+ try:
546
+ login_author_client(client)
547
+ except Exception:
548
+ try:
549
+ close_session(session)
550
+ except Exception:
551
+ pass
552
+ raise
553
+ return session
554
+
555
+
556
+ def startup_error_payload(exc):
557
+ if isinstance(exc, SidecarFailure):
558
+ return exc.as_dict()
559
+ return {
560
+ "type": type(exc).__name__,
561
+ "message": safe_exception_message(exc)[:400],
562
+ }
563
+
564
+
510
565
  def bootstrap():
511
566
  resource = os.path.join(DD_DIR, "ccvoicehub.res")
512
567
  pyqt_dir = os.path.join(DD_DIR, "ccsub64", "PyQt5")
@@ -643,10 +698,8 @@ def open_session(timeout=45):
643
698
  raise RuntimeError("DD NEP module is not initialized")
644
699
  client = UiApiClient(nep, login_cookie=getattr(controller, "_cookie", None))
645
700
  client._session.headers["User-Agent"] = USER_AGENT
646
- if not client.login():
647
- raise RuntimeError("DD author API login failed")
648
- install_response_probe(client)
649
- return qt, container, flow, jwt_helper, client
701
+ install_response_probe(client, capture_success=True)
702
+ return author_session_or_cleanup(qt, container, flow, jwt_helper, client)
650
703
 
651
704
 
652
705
  def api_result(payload):
@@ -905,10 +958,7 @@ def main():
905
958
  "error": error})
906
959
  return 0
907
960
  except Exception as exc:
908
- output({"ready": False, "error": {
909
- "type": type(exc).__name__,
910
- "message": safe_exception_message(exc)[:400],
911
- }})
961
+ output({"ready": False, "error": startup_error_payload(exc)})
912
962
  return 1
913
963
  finally:
914
964
  machine_data.clientNo = original
@@ -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.16",
5
- "skill_version": "0.0.16",
6
- "tree_sha256": "aba774b14679854ac01a09711aa10b2f7266af2c43e8220cc67c64b9559bfb51",
4
+ "package_version": "0.0.17",
5
+ "skill_version": "0.0.17",
6
+ "tree_sha256": "064aeb1a1291d240a3bea7e71458846395abe29c6e91f682d269ec0e684c4294",
7
7
  "files": [
8
8
  {
9
9
  "path": "agents/openai.yaml",
@@ -113,7 +113,7 @@
113
113
  {
114
114
  "path": "scripts/fupload_cli/__init__.py",
115
115
  "bytes": 50,
116
- "sha256": "f0d2cbd76206a61d5fd78eef58ad808108f9538963da2c0ea67df79c8a41e104"
116
+ "sha256": "8e72acb94a18f173cf9cbbf0436b20f64e1991f54a3537b7e930b4196bab8439"
117
117
  },
118
118
  {
119
119
  "path": "scripts/fupload_cli/blackbox_web.py",
@@ -137,18 +137,18 @@
137
137
  },
138
138
  {
139
139
  "path": "scripts/fupload_cli/dd_broker.py",
140
- "bytes": 27637,
141
- "sha256": "1ba00dcaea80905769bb2e00a6e965f2a1e9bb46a33534d1e77a6564158605a8"
140
+ "bytes": 28248,
141
+ "sha256": "6bbae4ed7aef4feca552816d7dbefa6069dc3d1d9a39a4af35582a06db4ce2d5"
142
142
  },
143
143
  {
144
144
  "path": "scripts/fupload_cli/dd_sidecar.py",
145
- "bytes": 37603,
146
- "sha256": "6ad1ab9e2145757f44b410d87d554485566812d9584820180ff3bb7b5bc7763a"
145
+ "bytes": 39150,
146
+ "sha256": "42791c82e5518838aedbe209823b94deb7de09b2f3eccb4fe563364f58becc6c"
147
147
  },
148
148
  {
149
149
  "path": "scripts/fupload_cli/dd.py",
150
- "bytes": 113914,
151
- "sha256": "31e349cfee243c1ae8afefd74acfe3f5d79771a1c23ed1892aaf9d8e9b416922"
150
+ "bytes": 114434,
151
+ "sha256": "cf808efdd4a93d80bddb533af4ac14696e889e0fd359acdacd58a7a49ce050b0"
152
152
  },
153
153
  {
154
154
  "path": "scripts/fupload_cli/errors.py",
@@ -208,7 +208,7 @@
208
208
  {
209
209
  "path": "SKILL.md",
210
210
  "bytes": 28383,
211
- "sha256": "2477e603ca826b9bd31533995f2217bfd9239c070adbaa2aa4b829fb6a89fb7f"
211
+ "sha256": "9aebce883e6d83f0e3c8b36f497e59eb6f4df5e1e4af231df60ca3299188fb86"
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.16",
3
+ "version": "0.0.17",
4
4
  "description": "Install and run the Fuploader Agent Skill and Python CLI.",
5
5
  "type": "module",
6
6
  "bin": {