@follenfang/fupload 0.0.0-bootstrap.0 → 0.0.1

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.
Files changed (43) hide show
  1. package/README.md +208 -3
  2. package/fupload/SKILL.md +129 -0
  3. package/fupload/agents/openai.yaml +4 -0
  4. package/fupload/examples/dd-config-delete.json +5 -0
  5. package/fupload/examples/dd-config-update.json +25 -0
  6. package/fupload/examples/dd-plugin-delete.json +5 -0
  7. package/fupload/examples/dd-plugin-update.json +9 -0
  8. package/fupload/examples/dd-wa-delete.json +5 -0
  9. package/fupload/examples/dd-wa-edit.json +9 -0
  10. package/fupload/examples/newbee-config-delete.json +5 -0
  11. package/fupload/examples/newbee-config-update.json +10 -0
  12. package/fupload/examples/newbee-plugin-create.json +14 -0
  13. package/fupload/examples/newbee-plugin-delete.json +5 -0
  14. package/fupload/examples/newbee-wa-delete.json +5 -0
  15. package/fupload/examples/newbee-wa-update.json +8 -0
  16. package/fupload/references/dd.md +105 -0
  17. package/fupload/references/newbee-official-cli.md +288 -0
  18. package/fupload/references/newbee.md +80 -0
  19. package/fupload/references/workflow.md +67 -0
  20. package/fupload/scripts/fupload.py +17 -0
  21. package/fupload/scripts/fupload_cli/__init__.py +3 -0
  22. package/fupload/scripts/fupload_cli/cli.py +266 -0
  23. package/fupload/scripts/fupload_cli/dd.py +2406 -0
  24. package/fupload/scripts/fupload_cli/dd_broker.py +634 -0
  25. package/fupload/scripts/fupload_cli/dd_sidecar.py +860 -0
  26. package/fupload/scripts/fupload_cli/errors.py +94 -0
  27. package/fupload/scripts/fupload_cli/io.py +125 -0
  28. package/fupload/scripts/fupload_cli/newbee.py +1412 -0
  29. package/fupload/scripts/fupload_cli/newbee_auth.py +135 -0
  30. package/fupload/scripts/fupload_cli/schema.py +539 -0
  31. package/fupload/scripts/fupload_cli/transport.py +125 -0
  32. package/fupload/scripts/fupload_cli/trust.py +207 -0
  33. package/npm/bin/fupload.mjs +90 -0
  34. package/npm/lib/managed-install.mjs +86 -0
  35. package/npm/lib/options.mjs +38 -0
  36. package/npm/lib/python.mjs +45 -0
  37. package/npm/lib/skill-installer.mjs +228 -0
  38. package/npm/lib/uninstall.mjs +211 -0
  39. package/npm/lib/update.mjs +99 -0
  40. package/npm/lib/versions.mjs +63 -0
  41. package/npm/postinstall.mjs +18 -0
  42. package/npm/skill-manifest.json +164 -0
  43. package/package.json +50 -6
@@ -0,0 +1,94 @@
1
+ """Stable, redacted CLI errors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any, Dict, Optional
7
+
8
+
9
+ _BEARER = re.compile(r"(?i)Bearer\s+[A-Za-z0-9._~+/=-]+")
10
+ _JWT = re.compile(r"\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+\b")
11
+ _WA_VALUE = re.compile(r"!WA:\d+![^\s\"']+")
12
+ _SIGNED = re.compile(r"(?i)(X-Amz-(?:Credential|Signature)=)[^&\s]+")
13
+ _SIGNED_TOKEN = re.compile(r"(?i)(X-Amz-(?:Security-Token|Date|Expires)=)[^&\s]+")
14
+ _URL_TOKEN = re.compile(r"(?i)([?&](?:token|jwt|clientNo|client_no|signature|credential)=)[^&\s]+")
15
+ _COOKIE = re.compile(r"(?i)(Cookie|Set-Cookie|Authentication|Authorization)(\s*[:=]\s*)[^\s;]+")
16
+ _SECRET_NAME = (
17
+ r"(?:access[_-]?token|refresh[_-]?token|resource[_-]?token|token|clientNo|client_no|"
18
+ r"clientId|client_id|client[_-]?secret|device[_-]?(?:id|proof)|login[_-]?code|jwt|"
19
+ r"credential|signature|api[_-]?key|auth[_-]?key|password|secret|cookie|set[_-]?cookie|"
20
+ r"authorization|authentication|signed[_-]?url|upload[_-]?url|presigned[_-]?(?:uri|url))"
21
+ )
22
+ _TOKEN_PAIR = re.compile(
23
+ r'''(?i)((?:["']?''' + _SECRET_NAME + r'''["']?)\s*[:=]\s*["']?)[^"',}&\s]+'''
24
+ )
25
+
26
+
27
+ def redact(value: str) -> str:
28
+ value = _BEARER.sub("Bearer [REDACTED]", str(value))
29
+ value = _JWT.sub("[REDACTED_JWT]", value)
30
+ value = _WA_VALUE.sub("[REDACTED_WA]", value)
31
+ value = _SIGNED.sub(r"\1[REDACTED]", value)
32
+ value = _SIGNED_TOKEN.sub(r"\1[REDACTED]", value)
33
+ value = _URL_TOKEN.sub(r"\1[REDACTED]", value)
34
+ value = _COOKIE.sub(r"\1\2[REDACTED]", value)
35
+ return _TOKEN_PAIR.sub(r"\1[REDACTED]", value)[:1000]
36
+
37
+
38
+ class FuploadError(Exception):
39
+ def __init__(
40
+ self,
41
+ message: str,
42
+ *,
43
+ kind: str = "operation_failed",
44
+ stage: Optional[str] = None,
45
+ endpoint: Optional[str] = None,
46
+ http_status: Optional[int] = None,
47
+ business_code: Optional[Any] = None,
48
+ verification_required: bool = False,
49
+ details: Optional[Dict[str, Any]] = None,
50
+ ) -> None:
51
+ super().__init__(redact(message))
52
+ self.kind = kind
53
+ self.stage = stage
54
+ self.endpoint = endpoint
55
+ self.http_status = http_status
56
+ self.business_code = business_code
57
+ self.verification_required = verification_required
58
+ self.details = details or {}
59
+
60
+ def as_dict(self) -> Dict[str, Any]:
61
+ result: Dict[str, Any] = {
62
+ "kind": self.kind,
63
+ "message": redact(str(self)),
64
+ "verification_required": bool(self.verification_required),
65
+ }
66
+ if self.stage:
67
+ result["stage"] = self.stage
68
+ if self.endpoint:
69
+ result["endpoint"] = self.endpoint
70
+ if self.http_status is not None:
71
+ result["http_status"] = self.http_status
72
+ if self.business_code is not None:
73
+ result["business_code"] = self.business_code
74
+ if self.details:
75
+ result["details"] = self.details
76
+ return result
77
+
78
+ @classmethod
79
+ def from_dict(cls, value: Dict[str, Any]) -> "FuploadError":
80
+ return cls(
81
+ str(value.get("message") or "operation failed"),
82
+ kind=str(value.get("kind") or "operation_failed"),
83
+ stage=str(value.get("stage")) if value.get("stage") else None,
84
+ endpoint=str(value.get("endpoint")) if value.get("endpoint") else None,
85
+ http_status=value.get("http_status"),
86
+ business_code=value.get("business_code"),
87
+ verification_required=bool(value.get("verification_required")),
88
+ details=value.get("details") if isinstance(value.get("details"), dict) else None,
89
+ )
90
+
91
+
92
+ class ValidationError(FuploadError):
93
+ def __init__(self, message: str, *, path: str = "$") -> None:
94
+ super().__init__(message, kind="validation_error", stage="dependency_get", details={"path": path})
@@ -0,0 +1,125 @@
1
+ """Input and stable output helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any, Dict
10
+
11
+ from .errors import FuploadError, ValidationError, redact
12
+
13
+
14
+ OUTPUT_SCHEMA = "fupload.output.v1"
15
+ _SENSITIVE_KEYS = {
16
+ "token", "access_token", "refresh_token", "resource_token", "jwt", "jwttoken",
17
+ "cookie", "set_cookie", "authorization", "authentication", "clientno", "client_no",
18
+ "clientid", "client_id", "client_secret", "device_id", "device_proof", "cred",
19
+ "credential", "signature", "x_amz_credential", "x_amz_signature",
20
+ "x_amz_security_token", "signed_url", "upload_url", "presigneduri", "presigned_uri",
21
+ "api_key", "auth_key", "password", "secret",
22
+ }
23
+ _RAW_CONTENT_KEYS = {
24
+ "content", "wa_str", "t_wa_str", "raw_wtf", "wtf_zip", "download_url",
25
+ "import_string",
26
+ }
27
+
28
+
29
+ class _DuplicateKey(ValueError):
30
+ pass
31
+
32
+
33
+ def _strict_object(pairs: Any) -> Dict[str, Any]:
34
+ result: Dict[str, Any] = {}
35
+ for key, value in pairs:
36
+ if key in result:
37
+ raise _DuplicateKey(str(key))
38
+ result[key] = value
39
+ return result
40
+
41
+
42
+ def _reject_constant(value: str) -> Any:
43
+ raise ValueError(value)
44
+
45
+
46
+ def read_json(path: str) -> Dict[str, Any]:
47
+ if not path:
48
+ raise ValidationError("--input is required", path="--input")
49
+ try:
50
+ text = sys.stdin.read() if path == "-" else Path(path).read_text(encoding="utf-8-sig")
51
+ text = text.lstrip("\ufeff")
52
+ except OSError as exc:
53
+ raise ValidationError("cannot read input: %s" % exc, path="--input") from exc
54
+ try:
55
+ value = json.loads(
56
+ text,
57
+ object_pairs_hook=_strict_object,
58
+ parse_constant=_reject_constant,
59
+ )
60
+ except _DuplicateKey as exc:
61
+ raise ValidationError("input contains duplicate key: %s" % exc, path="--input") from exc
62
+ except json.JSONDecodeError as exc:
63
+ raise ValidationError(
64
+ "input must be valid JSON: line %d column %d" % (exc.lineno, exc.colno),
65
+ path="--input",
66
+ ) from exc
67
+ except ValueError as exc:
68
+ raise ValidationError("input contains a non-standard numeric value", path="--input") from exc
69
+ if not isinstance(value, dict):
70
+ raise ValidationError("input document must be a JSON object")
71
+ return value
72
+
73
+
74
+ def write_output(platform: str, operation: str, data: Any, *, dry_run: bool = False) -> None:
75
+ payload = {
76
+ "schema": OUTPUT_SCHEMA,
77
+ "platform": platform,
78
+ "operation": operation,
79
+ "success": True,
80
+ "dry_run": bool(dry_run),
81
+ "data": sanitize_output(data),
82
+ }
83
+ # Stable ASCII JSON avoids inheriting a Windows console code-page contract.
84
+ print(json.dumps(payload, ensure_ascii=True, sort_keys=True, separators=(",", ":"), allow_nan=False))
85
+
86
+
87
+ def sanitize_output(value: Any) -> Any:
88
+ if isinstance(value, dict):
89
+ result: Dict[str, Any] = {}
90
+ for key, item in value.items():
91
+ normalized = str(key).replace("-", "_").lower()
92
+ if normalized in _SENSITIVE_KEYS or any(
93
+ marker in normalized
94
+ for marker in ("token", "cookie", "credential", "signature", "password", "secret")
95
+ ):
96
+ result[key] = "[REDACTED]"
97
+ elif normalized in _RAW_CONTENT_KEYS:
98
+ text = str(item or "").encode("utf-8")
99
+ result[str(key) + "_summary"] = {
100
+ "bytes": len(text),
101
+ "sha256": hashlib.sha256(text).hexdigest(),
102
+ }
103
+ else:
104
+ result[key] = sanitize_output(item)
105
+ return result
106
+ if isinstance(value, list):
107
+ return [sanitize_output(item) for item in value]
108
+ if isinstance(value, str):
109
+ return redact(value)
110
+ return value
111
+
112
+
113
+ def write_error(platform: str, operation: str, error: BaseException) -> None:
114
+ if isinstance(error, FuploadError):
115
+ detail = sanitize_output(error.as_dict())
116
+ else:
117
+ detail = sanitize_output(FuploadError(str(error)).as_dict())
118
+ payload = {
119
+ "schema": OUTPUT_SCHEMA,
120
+ "platform": platform,
121
+ "operation": operation,
122
+ "success": False,
123
+ "error": detail,
124
+ }
125
+ print(json.dumps(payload, ensure_ascii=True, sort_keys=True, separators=(",", ":"), allow_nan=False))