self-evolve-framework 1.1.0 → 1.2.0

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 (220) hide show
  1. package/package.json +1 -1
  2. package/template/skills/impeccable/SKILL.md +174 -47
  3. package/template/skills/impeccable/agents/impeccable_asset_producer.toml +92 -0
  4. package/template/skills/impeccable/agents/impeccable_manual_edit_applier.toml +95 -0
  5. package/template/skills/impeccable/agents/openai.yaml +4 -0
  6. package/template/skills/impeccable/reference/adapt.md +311 -0
  7. package/template/skills/impeccable/reference/animate.md +201 -0
  8. package/template/skills/impeccable/reference/audit.md +133 -0
  9. package/template/skills/impeccable/reference/bolder.md +113 -0
  10. package/template/skills/impeccable/reference/brand.md +108 -0
  11. package/template/skills/impeccable/reference/clarify.md +288 -0
  12. package/template/skills/impeccable/reference/codex.md +105 -0
  13. package/template/skills/impeccable/reference/colorize.md +257 -0
  14. package/template/skills/impeccable/reference/craft.md +123 -0
  15. package/template/skills/impeccable/reference/critique.md +790 -0
  16. package/template/skills/impeccable/reference/delight.md +302 -0
  17. package/template/skills/impeccable/reference/distill.md +111 -0
  18. package/template/skills/impeccable/reference/document.md +429 -0
  19. package/template/skills/impeccable/reference/extract.md +69 -0
  20. package/template/skills/impeccable/reference/harden.md +347 -0
  21. package/template/skills/impeccable/reference/hooks.md +90 -0
  22. package/template/skills/impeccable/reference/init.md +172 -0
  23. package/template/skills/impeccable/reference/interaction-design.md +189 -0
  24. package/template/skills/impeccable/reference/layout.md +161 -0
  25. package/template/skills/impeccable/reference/live.md +720 -0
  26. package/template/skills/impeccable/reference/onboard.md +234 -0
  27. package/template/skills/impeccable/reference/optimize.md +258 -0
  28. package/template/skills/impeccable/reference/overdrive.md +130 -0
  29. package/template/skills/impeccable/reference/polish.md +241 -0
  30. package/template/skills/impeccable/reference/product.md +60 -0
  31. package/template/skills/impeccable/reference/quieter.md +99 -0
  32. package/template/skills/impeccable/reference/shape.md +165 -0
  33. package/template/skills/impeccable/reference/typeset.md +279 -0
  34. package/template/skills/impeccable/scripts/command-metadata.json +94 -0
  35. package/template/skills/impeccable/scripts/context-signals.mjs +225 -0
  36. package/template/skills/impeccable/scripts/context.mjs +961 -0
  37. package/template/skills/impeccable/scripts/critique-storage.mjs +242 -0
  38. package/template/skills/impeccable/scripts/detect-csp.mjs +198 -0
  39. package/template/skills/impeccable/scripts/detect.mjs +21 -0
  40. package/template/skills/impeccable/scripts/detector/browser/injected/index.mjs +1937 -0
  41. package/template/skills/impeccable/scripts/detector/cli/main.mjs +290 -0
  42. package/template/skills/impeccable/scripts/detector/design-system.mjs +750 -0
  43. package/template/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +5138 -0
  44. package/template/skills/impeccable/scripts/detector/detect-antipatterns.mjs +50 -0
  45. package/template/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +277 -0
  46. package/template/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +568 -0
  47. package/template/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +1015 -0
  48. package/template/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +234 -0
  49. package/template/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
  50. package/template/skills/impeccable/scripts/detector/findings.mjs +12 -0
  51. package/template/skills/impeccable/scripts/detector/node/file-system.mjs +198 -0
  52. package/template/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
  53. package/template/skills/impeccable/scripts/detector/registry/antipatterns.mjs +448 -0
  54. package/template/skills/impeccable/scripts/detector/rules/checks.mjs +2671 -0
  55. package/template/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
  56. package/template/skills/impeccable/scripts/detector/shared/constants.mjs +101 -0
  57. package/template/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  58. package/template/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
  59. package/template/skills/impeccable/scripts/hook-admin.mjs +661 -0
  60. package/template/skills/impeccable/scripts/hook-before-edit.mjs +476 -0
  61. package/template/skills/impeccable/scripts/hook-lib.mjs +1632 -0
  62. package/template/skills/impeccable/scripts/hook.mjs +61 -0
  63. package/template/skills/impeccable/scripts/lib/design-parser.mjs +842 -0
  64. package/template/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  65. package/template/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  66. package/template/skills/impeccable/scripts/lib/is-generated.mjs +69 -0
  67. package/template/skills/impeccable/scripts/lib/target-args.mjs +42 -0
  68. package/template/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  69. package/template/skills/impeccable/scripts/live/completion.mjs +19 -0
  70. package/template/skills/impeccable/scripts/live/event-validation.mjs +137 -0
  71. package/template/skills/impeccable/scripts/live/insert-ui.mjs +458 -0
  72. package/template/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
  73. package/template/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  74. package/template/skills/impeccable/scripts/live/manual-edits-buffer.mjs +152 -0
  75. package/template/skills/impeccable/scripts/live/session-store.mjs +289 -0
  76. package/template/skills/impeccable/scripts/live/svelte-component.mjs +826 -0
  77. package/template/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
  78. package/template/skills/impeccable/scripts/live/ui-core.mjs +180 -0
  79. package/template/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
  80. package/template/skills/impeccable/scripts/live-accept.mjs +812 -0
  81. package/template/skills/impeccable/scripts/live-browser-dom.js +146 -0
  82. package/template/skills/impeccable/scripts/live-browser-session.js +123 -0
  83. package/template/skills/impeccable/scripts/live-browser.js +11173 -0
  84. package/template/skills/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
  85. package/template/skills/impeccable/scripts/live-complete.mjs +75 -0
  86. package/template/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
  87. package/template/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
  88. package/template/skills/impeccable/scripts/live-inject.mjs +583 -0
  89. package/template/skills/impeccable/scripts/live-insert.mjs +272 -0
  90. package/template/skills/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
  91. package/template/skills/impeccable/scripts/live-poll.mjs +384 -0
  92. package/template/skills/impeccable/scripts/live-resume.mjs +94 -0
  93. package/template/skills/impeccable/scripts/live-server.mjs +1135 -0
  94. package/template/skills/impeccable/scripts/live-status.mjs +61 -0
  95. package/template/skills/impeccable/scripts/live-target.mjs +30 -0
  96. package/template/skills/impeccable/scripts/live-wrap.mjs +894 -0
  97. package/template/skills/impeccable/scripts/live.mjs +297 -0
  98. package/template/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  99. package/template/skills/impeccable/scripts/palette.mjs +633 -0
  100. package/template/skills/impeccable/scripts/pin.mjs +214 -0
  101. package/template/rules/CodeGraph.mdc +0 -23
  102. package/template/rules/Svelte_5.mdc +0 -167
  103. package/template/rules/Svelte_Flow.mdc +0 -176
  104. package/template/rules/Tailwind_CSS_v4.mdc +0 -187
  105. package/template/rules/Tauri.mdc +0 -145
  106. package/template/rules/app-error-pattern.mdc +0 -65
  107. package/template/rules/invoke-safe-pattern.mdc +0 -53
  108. package/template/rules/js.mdc +0 -10
  109. package/template/rules/powershell.mdc +0 -9
  110. package/template/rules//346/227/245/345/277/227.mdc +0 -15
  111. package/template/rules//350/257/267/346/261/202.mdc +0 -49
  112. package/template/skills/caveman/SKILL.md +0 -49
  113. package/template/skills/check/SKILL.md +0 -393
  114. package/template/skills/check/agents/reviewer-architecture.md +0 -39
  115. package/template/skills/check/agents/reviewer-security.md +0 -39
  116. package/template/skills/check/references/persona-catalog.md +0 -56
  117. package/template/skills/check/references/project-context.md +0 -120
  118. package/template/skills/check/references/public-reply.md +0 -14
  119. package/template/skills/check/scripts/audit_signals.py +0 -666
  120. package/template/skills/check/scripts/run-tests.sh +0 -19
  121. package/template/skills/design/SKILL.md +0 -173
  122. package/template/skills/design/references/design-aesthetic-quality.md +0 -67
  123. package/template/skills/design/references/design-data-viz.md +0 -34
  124. package/template/skills/design/references/design-reference.md +0 -295
  125. package/template/skills/design/references/design-tokens.md +0 -45
  126. package/template/skills/design/references/design-traps.md +0 -43
  127. package/template/skills/design-an-interface/SKILL.md +0 -94
  128. package/template/skills/diagnose/SKILL.md +0 -117
  129. package/template/skills/diagnose/scripts/hitl-loop.template.sh +0 -41
  130. package/template/skills/edit-article/SKILL.md +0 -14
  131. package/template/skills/git-guardrails-claude-code/SKILL.md +0 -95
  132. package/template/skills/git-guardrails-claude-code/scripts/block-dangerous-git.sh +0 -25
  133. package/template/skills/grill-me/SKILL.md +0 -10
  134. package/template/skills/grill-with-docs/ADR-FORMAT.md +0 -47
  135. package/template/skills/grill-with-docs/CONTEXT-FORMAT.md +0 -60
  136. package/template/skills/grill-with-docs/SKILL.md +0 -88
  137. package/template/skills/handoff/SKILL.md +0 -15
  138. package/template/skills/health/SKILL.md +0 -260
  139. package/template/skills/health/agents/inspector-context.md +0 -119
  140. package/template/skills/health/agents/inspector-control.md +0 -84
  141. package/template/skills/health/agents/inspector-maintainability.md +0 -55
  142. package/template/skills/health/scripts/check-agent-context.sh +0 -5
  143. package/template/skills/health/scripts/check-doc-refs.sh +0 -8
  144. package/template/skills/health/scripts/check-maintainability.sh +0 -8
  145. package/template/skills/health/scripts/check-verifier-output.sh +0 -5
  146. package/template/skills/health/scripts/check_agent_context.py +0 -444
  147. package/template/skills/health/scripts/check_doc_refs.py +0 -110
  148. package/template/skills/health/scripts/check_maintainability.py +0 -635
  149. package/template/skills/health/scripts/check_verifier_output.py +0 -116
  150. package/template/skills/health/scripts/collect-data.sh +0 -751
  151. package/template/skills/hunt/SKILL.md +0 -232
  152. package/template/skills/hunt/references/failure-patterns.md +0 -138
  153. package/template/skills/hunt/references/ime-unicode.md +0 -58
  154. package/template/skills/hunt/references/logging-techniques.md +0 -72
  155. package/template/skills/hunt/references/rendering-debug.md +0 -34
  156. package/template/skills/improve-codebase-architecture/DEEPENING.md +0 -37
  157. package/template/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  158. package/template/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +0 -44
  159. package/template/skills/improve-codebase-architecture/LANGUAGE.md +0 -53
  160. package/template/skills/improve-codebase-architecture/SKILL.md +0 -81
  161. package/template/skills/learn/SKILL.md +0 -140
  162. package/template/skills/migrate-to-shoehorn/SKILL.md +0 -118
  163. package/template/skills/obsidian-vault/SKILL.md +0 -59
  164. package/template/skills/prototype/LOGIC.md +0 -79
  165. package/template/skills/prototype/SKILL.md +0 -30
  166. package/template/skills/prototype/UI.md +0 -112
  167. package/template/skills/qa/SKILL.md +0 -130
  168. package/template/skills/read/SKILL.md +0 -141
  169. package/template/skills/read/references/read-methods.md +0 -129
  170. package/template/skills/read/scripts/fetch.sh +0 -106
  171. package/template/skills/read/scripts/fetch_feishu.py +0 -251
  172. package/template/skills/read/scripts/fetch_local.py +0 -218
  173. package/template/skills/read/scripts/fetch_weixin.py +0 -107
  174. package/template/skills/request-refactor-plan/SKILL.md +0 -68
  175. package/template/skills/review/SKILL.md +0 -78
  176. package/template/skills/rust-auto-fix/SKILL.md +0 -94
  177. package/template/skills/scaffold-exercises/SKILL.md +0 -106
  178. package/template/skills/sdd-dev/SKILL.md +0 -114
  179. package/template/skills/setup-matt-pocock-skills/SKILL.md +0 -121
  180. package/template/skills/setup-matt-pocock-skills/domain.md +0 -51
  181. package/template/skills/setup-matt-pocock-skills/issue-tracker-github.md +0 -22
  182. package/template/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +0 -23
  183. package/template/skills/setup-matt-pocock-skills/issue-tracker-local.md +0 -19
  184. package/template/skills/setup-matt-pocock-skills/triage-labels.md +0 -15
  185. package/template/skills/setup-pre-commit/SKILL.md +0 -91
  186. package/template/skills/svelte-warnings-fix/SKILL.md +0 -94
  187. package/template/skills/tauri-nsis-installer-icon/SKILL.md +0 -92
  188. package/template/skills/tauri-nsis-installer-icon/references/tauri-nsis-schema.md +0 -71
  189. package/template/skills/tb/SKILL.md +0 -62
  190. package/template/skills/tdd/SKILL.md +0 -109
  191. package/template/skills/tdd/deep-modules.md +0 -33
  192. package/template/skills/tdd/interface-design.md +0 -31
  193. package/template/skills/tdd/mocking.md +0 -59
  194. package/template/skills/tdd/refactoring.md +0 -10
  195. package/template/skills/tdd/tests.md +0 -61
  196. package/template/skills/teach/GLOSSARY-FORMAT.md +0 -35
  197. package/template/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  198. package/template/skills/teach/MISSION-FORMAT.md +0 -31
  199. package/template/skills/teach/RESOURCES-FORMAT.md +0 -32
  200. package/template/skills/teach/SKILL.md +0 -91
  201. package/template/skills/think/SKILL.md +0 -184
  202. package/template/skills/to-issues/SKILL.md +0 -83
  203. package/template/skills/to-prd/SKILL.md +0 -74
  204. package/template/skills/triage/AGENT-BRIEF.md +0 -168
  205. package/template/skills/triage/OUT-OF-SCOPE.md +0 -101
  206. package/template/skills/triage/SKILL.md +0 -103
  207. package/template/skills/ubiquitous-language/SKILL.md +0 -93
  208. package/template/skills/ver/SKILL.md +0 -62
  209. package/template/skills/write/SKILL.md +0 -209
  210. package/template/skills/write/references/write-en.md +0 -199
  211. package/template/skills/write/references/write-product-localization.md +0 -43
  212. package/template/skills/write/references/write-zh-bilingual.md +0 -59
  213. package/template/skills/write/references/write-zh-prose.md +0 -50
  214. package/template/skills/write/references/write-zh-release-notes.md +0 -40
  215. package/template/skills/write/references/write-zh.md +0 -721
  216. package/template/skills/write-a-skill/SKILL.md +0 -117
  217. package/template/skills/writing-beats/SKILL.md +0 -52
  218. package/template/skills/writing-fragments/SKILL.md +0 -75
  219. package/template/skills/writing-shape/SKILL.md +0 -64
  220. package/template/skills/zoom-out/SKILL.md +0 -7
@@ -1,106 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Fetch a URL as Markdown.
3
- #
4
- # Privacy-first cascade:
5
- # Default (no --use-proxy): local extractor only. URL is never sent to a
6
- # third party. Best quality when readability-lxml + html2text are pip-
7
- # installed; degrades to a stdlib-only stripper otherwise.
8
- #
9
- # With --use-proxy: tries local first, then defuddle.md, then r.jina.ai.
10
- # Use this for JS-heavy pages, X/Twitter, paywalls, or anything the local
11
- # extractor cannot reach. Be aware: the URL is sent to those third-party
12
- # services and may be cached or logged. Never feed sensitive URLs through
13
- # --use-proxy.
14
- #
15
- # Every tier writes a structured stderr line:
16
- # [fetch] tier=<local|defuddle|jina> status=<ok|fail|skip> reason="..."
17
- #
18
- # Special thanks to joeseesun for the qiaomu-markdown-proxy project, which
19
- # inspired the proxy cascade design:
20
- # https://github.com/joeseesun/qiaomu-markdown-proxy
21
- #
22
- # Usage:
23
- # fetch.sh <url> [proxy_url]
24
- # fetch.sh --use-proxy <url> [proxy_url]
25
- set -euo pipefail
26
-
27
- USE_PROXY=0
28
- if [ "${1:-}" = "--use-proxy" ]; then
29
- USE_PROXY=1
30
- shift
31
- fi
32
-
33
- URL="${1:?Usage: fetch.sh [--use-proxy] <url> [proxy_url]}"
34
- PROXY="${2:-}"
35
-
36
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
37
-
38
- LOCAL_ERR="$(mktemp)"
39
- trap 'rm -f "$LOCAL_ERR"' EXIT
40
-
41
- # shellcheck disable=SC2329,SC2317 # called indirectly via _with_retry / _try_once
42
- _curl() {
43
- if [ -n "$PROXY" ]; then
44
- https_proxy="$PROXY" http_proxy="$PROXY" curl -sfL --connect-timeout 10 --max-time 30 "$@"
45
- else
46
- curl -sfL --connect-timeout 10 --max-time 30 "$@"
47
- fi
48
- }
49
-
50
- _has_content() {
51
- local content="$1"
52
- [ "$(printf '%s' "$content" | wc -l)" -gt 5 ] || return 1
53
- # Reject pages dominated by login walls, captchas, or bot challenges that
54
- # otherwise pass the line-count check. Add new markers here, not new branches.
55
- if printf '%s' "$content" | grep -qE "Don't miss what's happening|Sign in to continue|Please sign in|Log in to continue|请登录|登录后查看|机器人验证|人机验证|Just a moment\.\.\.|Checking your browser" 2>/dev/null; then
56
- return 1
57
- fi
58
- return 0
59
- }
60
-
61
- _try_once() {
62
- local out
63
- out=$("$@" 2>/dev/null || true)
64
- if _has_content "$out"; then echo "$out"; return 0; fi
65
- return 1
66
- }
67
-
68
- _with_retry() {
69
- _try_once "$@" && return 0
70
- sleep 2
71
- _try_once "$@" && return 0
72
- return 1
73
- }
74
-
75
- # Tier 1: local extractor. Always tried first.
76
- if OUT=$(python3 "$SCRIPT_DIR/fetch_local.py" "$URL" 2>"$LOCAL_ERR"); then
77
- cat "$LOCAL_ERR" >&2 2>/dev/null || true
78
- echo "$OUT"
79
- exit 0
80
- fi
81
- cat "$LOCAL_ERR" >&2 2>/dev/null || true
82
-
83
- # Without --use-proxy, stop here. URL never leaves the machine.
84
- if [ "$USE_PROXY" -eq 0 ]; then
85
- echo "[fetch] status=fail reason=\"local extractor failed; rerun with --use-proxy to try defuddle.md and r.jina.ai (URL will be sent to those services)\"" >&2
86
- exit 1
87
- fi
88
-
89
- # Tier 2: defuddle.md (third party; user opted in via --use-proxy).
90
- if OUT=$(_with_retry _curl "https://defuddle.md/$URL"); then
91
- echo "[fetch] tier=defuddle status=ok" >&2
92
- echo "$OUT"
93
- exit 0
94
- fi
95
- echo "[fetch] tier=defuddle status=fail reason=\"empty or paywall-like response\"" >&2
96
-
97
- # Tier 3: r.jina.ai (third party; user opted in via --use-proxy).
98
- if OUT=$(_with_retry _curl "https://r.jina.ai/$URL"); then
99
- echo "[fetch] tier=jina status=ok" >&2
100
- echo "$OUT"
101
- exit 0
102
- fi
103
- echo "[fetch] tier=jina status=fail reason=\"empty or paywall-like response\"" >&2
104
-
105
- echo "[fetch] status=fail reason=\"all tiers (local, defuddle, jina) failed for $URL\"" >&2
106
- exit 1
@@ -1,251 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Fetch Feishu/Lark document as Markdown via Feishu Open API.
3
-
4
- Special thanks to joeseesun for the excellent qiaomu-markdown-proxy project,
5
- which inspired the Feishu API integration and document parsing approach here.
6
- https://github.com/joeseesun/qiaomu-markdown-proxy
7
-
8
- Requirements:
9
- pip install requests
10
-
11
- Setup:
12
- export FEISHU_APP_ID=your_app_id
13
- export FEISHU_APP_SECRET=your_app_secret
14
- App needs: docx:document:readonly, wiki:wiki:readonly
15
-
16
- Usage:
17
- python3 fetch_feishu.py <feishu_url>
18
- python3 fetch_feishu.py <feishu_url> --json
19
- """
20
-
21
- import sys
22
- import json
23
- import os
24
- import re
25
- import urllib.parse
26
-
27
- try:
28
- import requests
29
- except ImportError:
30
- print("Error: requests not installed. Run: pip install requests", file=sys.stderr)
31
- sys.exit(1)
32
-
33
- API = "https://open.feishu.cn/open-apis"
34
- TIMEOUT = 20
35
-
36
-
37
- def yaml_string(value):
38
- return json.dumps("" if value is None else str(value), ensure_ascii=False)
39
-
40
-
41
- def get_token():
42
- app_id = os.environ.get("FEISHU_APP_ID")
43
- app_secret = os.environ.get("FEISHU_APP_SECRET")
44
- if not app_id or not app_secret:
45
- return None, "FEISHU_APP_ID or FEISHU_APP_SECRET not set"
46
- resp = requests.post(f"{API}/auth/v3/tenant_access_token/internal",
47
- json={"app_id": app_id, "app_secret": app_secret},
48
- timeout=TIMEOUT)
49
- d = resp.json()
50
- if d.get("code") != 0:
51
- return None, f"Auth failed: {d.get('msg', resp.text)}"
52
- return d["tenant_access_token"], None
53
-
54
-
55
- def parse_url(url):
56
- patterns = [
57
- (r"feishu\.cn/docx/([A-Za-z0-9]+)", "docx"),
58
- (r"feishu\.cn/docs/([A-Za-z0-9]+)", "legacy_doc"),
59
- (r"feishu\.cn/wiki/([A-Za-z0-9]+)", "wiki"),
60
- (r"larksuite\.com/docx/([A-Za-z0-9]+)", "docx"),
61
- (r"larksuite\.com/docs/([A-Za-z0-9]+)", "legacy_doc"),
62
- (r"larksuite\.com/wiki/([A-Za-z0-9]+)", "wiki"),
63
- ]
64
- for pattern, doc_type in patterns:
65
- m = re.search(pattern, url)
66
- if m:
67
- return m.group(1), doc_type
68
- return url, "docx"
69
-
70
-
71
- def resolve_wiki(token, wiki_token):
72
- resp = requests.get(f"{API}/wiki/v2/spaces/get_node",
73
- headers={"Authorization": f"Bearer {token}"},
74
- params={"token": wiki_token},
75
- timeout=TIMEOUT)
76
- d = resp.json()
77
- if d.get("code") == 0:
78
- node = d["data"]["node"]
79
- return node.get("obj_token"), node.get("obj_type")
80
- return None, None
81
-
82
-
83
- def get_blocks(token, doc_id):
84
- blocks, page_token = [], None
85
- while True:
86
- params = {"page_size": 500}
87
- if page_token:
88
- params["page_token"] = page_token
89
- resp = requests.get(f"{API}/docx/v1/documents/{doc_id}/blocks",
90
- headers={"Authorization": f"Bearer {token}"},
91
- params=params,
92
- timeout=TIMEOUT)
93
- d = resp.json()
94
- if d.get("code") != 0:
95
- return None, f"Blocks fetch failed: {d.get('msg', resp.text)}"
96
- blocks.extend(d["data"].get("items", []))
97
- if not d["data"].get("has_more"):
98
- break
99
- page_token = d["data"].get("page_token")
100
- return blocks, None
101
-
102
-
103
- def extract_text(elements):
104
- if not elements:
105
- return ""
106
- parts = []
107
- for el in elements:
108
- if "text_run" in el:
109
- tr = el["text_run"]
110
- text = tr.get("content", "")
111
- s = tr.get("text_element_style", {})
112
- if s.get("bold"): text = f"**{text}**"
113
- if s.get("italic"): text = f"*{text}*"
114
- if s.get("inline_code"): text = f"`{text}`"
115
- if s.get("link", {}).get("url"):
116
- text = f"[{text}]({urllib.parse.unquote(s['link']['url'])})"
117
- parts.append(text)
118
- elif "mention_user" in el:
119
- parts.append(f"@{el['mention_user'].get('user_id', 'user')}")
120
- elif "equation" in el:
121
- parts.append(f"${el['equation'].get('content', '')}$")
122
- return "".join(parts)
123
-
124
-
125
- LANG_MAP = {
126
- 7: "bash", 8: "c", 9: "csharp", 10: "cpp", 14: "css", 19: "dockerfile",
127
- 25: "go", 29: "html", 31: "java", 32: "javascript", 33: "json",
128
- 35: "kotlin", 40: "markdown", 46: "php", 50: "python", 52: "ruby",
129
- 53: "rust", 58: "sql", 59: "swift", 62: "typescript", 68: "xml", 69: "yaml",
130
- }
131
-
132
-
133
- def blocks_to_md(blocks):
134
- lines = []
135
- counters = {}
136
- for block in blocks:
137
- bt = block.get("block_type")
138
- pid = block.get("parent_id", "")
139
-
140
- if bt == 2:
141
- text = extract_text(block.get("text", {}).get("elements", []))
142
- lines.append(text if text.strip() else "")
143
- elif bt in range(3, 10):
144
- level = bt - 2
145
- key = f"heading{level}"
146
- data = block.get(key) or block.get("heading", {})
147
- text = extract_text(data.get("elements", []))
148
- lines.append(f"{'#' * min(level, 6)} {text}")
149
- elif bt == 10:
150
- text = extract_text(block.get("bullet", {}).get("elements", []))
151
- lines.append(f"- {text}")
152
- elif bt == 11:
153
- text = extract_text(block.get("ordered", {}).get("elements", []))
154
- n = counters.get(pid, 0) + 1
155
- counters[pid] = n
156
- lines.append(f"{n}. {text}")
157
- elif bt == 12:
158
- code_data = block.get("code", {})
159
- text = extract_text(code_data.get("elements", []))
160
- lang = LANG_MAP.get(code_data.get("style", {}).get("language", 0), "")
161
- lines.extend([f"```{lang}", text, "```"])
162
- elif bt == 13:
163
- text = extract_text(block.get("quote", {}).get("elements", []))
164
- lines.append(f"> {text}")
165
- elif bt == 15:
166
- todo_data = block.get("todo", {})
167
- text = extract_text(todo_data.get("elements", []))
168
- done = todo_data.get("style", {}).get("done", False)
169
- lines.append(f"- {'[x]' if done else '[ ]'} {text}")
170
- elif bt == 16:
171
- lines.append("---")
172
- elif bt == 17:
173
- tok = block.get("image", {}).get("token", "")
174
- lines.append(f"![image](feishu-image://{tok})")
175
- elif bt == 1:
176
- pass
177
- else:
178
- for key, val in block.items():
179
- if isinstance(val, dict) and "elements" in val:
180
- text = extract_text(val["elements"])
181
- if text.strip():
182
- lines.append(text)
183
- break
184
-
185
- return "\n\n".join(lines)
186
-
187
-
188
- def fetch_feishu(url):
189
- doc_id, doc_type = parse_url(url)
190
-
191
- if doc_type == "legacy_doc":
192
- return {
193
- "error": (
194
- "Legacy Feishu /docs/ pages are not supported by this script. "
195
- "Convert the document to docx first, or use a public-page fallback if the page is accessible without the API."
196
- )
197
- }
198
-
199
- token, err = get_token()
200
- if err:
201
- return {"error": err}
202
-
203
- if doc_type == "wiki":
204
- real_id, real_type = resolve_wiki(token, doc_id)
205
- if not real_id:
206
- return {"error": f"Cannot resolve wiki node: {doc_id}"}
207
- doc_id, doc_type = real_id, real_type or "docx"
208
-
209
- info_resp = requests.get(f"{API}/docx/v1/documents/{doc_id}",
210
- headers={"Authorization": f"Bearer {token}"},
211
- timeout=TIMEOUT)
212
- doc_info = (info_resp.json().get("data") or {}).get("document") or {}
213
- title = doc_info.get("title", "")
214
-
215
- blocks, err = get_blocks(token, doc_id)
216
- if err:
217
- return {"error": err}
218
-
219
- return {"title": title, "document_id": doc_id, "url": url, "content": blocks_to_md(blocks)}
220
-
221
-
222
- def to_markdown(r):
223
- if "error" in r:
224
- return f"Error: {r['error']}"
225
- parts = [
226
- "---",
227
- f"title: {yaml_string(r.get('title', ''))}",
228
- f"document_id: {yaml_string(r.get('document_id', ''))}",
229
- f"url: {yaml_string(r.get('url', ''))}",
230
- "---",
231
- "",
232
- f"# {r['title']}" if r.get("title") else "",
233
- "",
234
- r.get("content", ""),
235
- ]
236
- return "\n".join(parts)
237
-
238
-
239
- if __name__ == "__main__":
240
- if len(sys.argv) < 2:
241
- print("Usage: fetch_feishu.py <feishu_url> [--json]", file=sys.stderr)
242
- print(" Requires: FEISHU_APP_ID, FEISHU_APP_SECRET", file=sys.stderr)
243
- sys.exit(1)
244
-
245
- result = fetch_feishu(sys.argv[1])
246
- if "--json" in sys.argv:
247
- print(json.dumps(result, ensure_ascii=False, indent=2))
248
- else:
249
- print(to_markdown(result))
250
- if "error" in result:
251
- sys.exit(1)
@@ -1,218 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Local URL → Markdown extractor. Privacy-preserving tier 1 for fetch.sh.
3
-
4
- Two paths, picked at runtime:
5
-
6
- 1. **Best path**: `readability-lxml` + `html2text` installed. Extract main
7
- content via readability scoring, convert to clean Markdown. Quality close
8
- to defuddle.md / r.jina.ai for static pages.
9
-
10
- 2. **Fallback path**: stdlib only. Strip HTML tags, collapse whitespace, drop
11
- <script>/<style>/<nav>/<footer>. Quality is poor for JS-heavy pages and
12
- complex layouts, but works for simple article-style HTML without any
13
- third-party dependency.
14
-
15
- JS-rendered pages (X/Twitter, paywalled news, SPA) are out of reach for both
16
- paths. Use `fetch.sh --use-proxy` for those.
17
-
18
- Exit codes:
19
- 0 success, markdown on stdout
20
- 1 fetch or extraction failed; reason on stderr
21
- 2 invocation error (missing URL)
22
- """
23
-
24
- from __future__ import annotations
25
-
26
- import argparse
27
- import re
28
- import sys
29
- import urllib.error
30
- import urllib.request
31
- from html.parser import HTMLParser
32
-
33
-
34
- USER_AGENT = (
35
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0) "
36
- "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36"
37
- )
38
- FETCH_TIMEOUT_SECS = 20
39
-
40
-
41
- def fetch_html(url: str) -> str:
42
- req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
43
- with urllib.request.urlopen(req, timeout=FETCH_TIMEOUT_SECS) as resp:
44
- raw = resp.read()
45
- # Detect charset from Content-Type header; fall back to utf-8 with replace.
46
- charset = "utf-8"
47
- ctype = resp.headers.get("Content-Type", "")
48
- m = re.search(r"charset=([\w\-]+)", ctype, re.IGNORECASE)
49
- if m:
50
- charset = m.group(1).lower()
51
- try:
52
- return raw.decode(charset, errors="replace")
53
- except LookupError:
54
- return raw.decode("utf-8", errors="replace")
55
-
56
-
57
- def extract_with_readability(html: str, url: str) -> str | None:
58
- """Best path: readability-lxml + html2text. Returns Markdown or None if
59
- deps missing. Raises only on genuine extraction failure."""
60
- try:
61
- from readability import Document # type: ignore
62
- import html2text # type: ignore
63
- except ImportError:
64
- return None
65
- doc = Document(html)
66
- cleaned_html = doc.summary(html_partial=True)
67
- title = (doc.short_title() or "").strip()
68
- converter = html2text.HTML2Text()
69
- converter.body_width = 0
70
- converter.unicode_snob = True
71
- converter.ignore_links = False
72
- converter.ignore_images = False
73
- body = converter.handle(cleaned_html).strip()
74
- if not body:
75
- return None
76
- header = ""
77
- if title:
78
- header = f"# {title}\n\n"
79
- return f"{header}> Source: {url}\n\n{body}\n"
80
-
81
-
82
- class _StdlibStripper(HTMLParser):
83
- """Fallback HTML → text converter using only stdlib. Quality is poor but
84
- deterministic; intended as a last resort when readability isn't installed.
85
- Drops common non-content blocks (script/style/nav/footer/aside)."""
86
-
87
- DROP_TAGS = {"script", "style", "nav", "footer", "aside", "noscript", "form"}
88
- BLOCK_TAGS = {
89
- "p", "div", "section", "article", "li", "ul", "ol",
90
- "h1", "h2", "h3", "h4", "h5", "h6", "br", "tr",
91
- }
92
-
93
- def __init__(self) -> None:
94
- super().__init__()
95
- self._buf: list[str] = []
96
- self._drop_depth = 0
97
- self._title = ""
98
- self._in_title = False
99
-
100
- def handle_starttag(self, tag, attrs):
101
- if tag in self.DROP_TAGS:
102
- self._drop_depth += 1
103
- elif tag == "title":
104
- self._in_title = True
105
- elif tag in self.BLOCK_TAGS:
106
- self._buf.append("\n")
107
-
108
- def handle_endtag(self, tag):
109
- if tag in self.DROP_TAGS:
110
- self._drop_depth = max(0, self._drop_depth - 1)
111
- elif tag == "title":
112
- self._in_title = False
113
- elif tag in self.BLOCK_TAGS:
114
- self._buf.append("\n")
115
-
116
- def handle_data(self, data):
117
- if self._drop_depth:
118
- return
119
- if self._in_title:
120
- self._title += data
121
- return
122
- self._buf.append(data)
123
-
124
- def text(self) -> str:
125
- raw = "".join(self._buf)
126
- lines = [line.strip() for line in raw.splitlines()]
127
- # Drop empty lines but keep paragraph breaks.
128
- out: list[str] = []
129
- prev_blank = False
130
- for line in lines:
131
- if not line:
132
- if not prev_blank:
133
- out.append("")
134
- prev_blank = True
135
- else:
136
- out.append(line)
137
- prev_blank = False
138
- return "\n".join(out).strip()
139
-
140
-
141
- def extract_with_stdlib(html: str, url: str) -> str:
142
- p = _StdlibStripper()
143
- p.feed(html)
144
- body = p.text()
145
- if not body:
146
- body = "(no text content extracted)"
147
- header = ""
148
- title = (p._title or "").strip()
149
- if title:
150
- header = f"# {title}\n\n"
151
- return f"{header}> Source: {url}\n\n{body}\n"
152
-
153
-
154
- def main() -> int:
155
- parser = argparse.ArgumentParser(description=__doc__)
156
- parser.add_argument("url", help="URL to fetch")
157
- parser.add_argument(
158
- "--prefer",
159
- choices=("auto", "readability", "stdlib"),
160
- default="auto",
161
- help="Force a specific extractor (default: auto = readability if installed, else stdlib)",
162
- )
163
- args = parser.parse_args()
164
-
165
- try:
166
- html = fetch_html(args.url)
167
- except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError) as exc:
168
- print(
169
- f"[fetch] tier=local status=fail reason=\"fetch failed: {exc}\"",
170
- file=sys.stderr,
171
- )
172
- return 1
173
-
174
- used = ""
175
- body: str | None = None
176
-
177
- if args.prefer in ("auto", "readability"):
178
- body = extract_with_readability(html, args.url)
179
- if body is not None:
180
- used = "readability"
181
- elif args.prefer == "readability":
182
- print(
183
- "[fetch] tier=local status=fail "
184
- "reason=\"--prefer readability but readability-lxml or html2text not installed; "
185
- "install with: pip install --user readability-lxml html2text\"",
186
- file=sys.stderr,
187
- )
188
- return 1
189
-
190
- if body is None:
191
- body = extract_with_stdlib(html, args.url)
192
- used = "stdlib"
193
-
194
- # Sanity floor: if even the stdlib extractor returns essentially nothing,
195
- # treat as failure so the proxy fallback (if --use-proxy) gets a chance.
196
- body_lines = [l for l in body.splitlines() if l.strip()]
197
- if len(body_lines) < 4:
198
- print(
199
- f"[fetch] tier=local status=fail reason=\"extractor={used} produced <4 non-empty lines\"",
200
- file=sys.stderr,
201
- )
202
- return 1
203
-
204
- if used == "stdlib":
205
- print(
206
- "[fetch] tier=local status=ok extractor=stdlib "
207
- "hint=\"install readability-lxml + html2text for cleaner output\"",
208
- file=sys.stderr,
209
- )
210
- else:
211
- print(f"[fetch] tier=local status=ok extractor={used}", file=sys.stderr)
212
-
213
- sys.stdout.write(body)
214
- return 0
215
-
216
-
217
- if __name__ == "__main__":
218
- sys.exit(main())
@@ -1,107 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Fetch WeChat public account article as Markdown using Playwright + BeautifulSoup.
3
-
4
- Special thanks to joeseesun for the excellent qiaomu-markdown-proxy project,
5
- which inspired the Playwright-based WeChat scraping approach in this script.
6
- https://github.com/joeseesun/qiaomu-markdown-proxy
7
-
8
- Requirements:
9
- pip install playwright beautifulsoup4 lxml
10
- playwright install chromium
11
-
12
- Usage:
13
- python3 fetch_weixin.py <url>
14
- python3 fetch_weixin.py <url> --json
15
- """
16
-
17
- import sys
18
- import json
19
- import asyncio
20
-
21
-
22
- def yaml_string(value: str) -> str:
23
- return json.dumps("" if value is None else str(value), ensure_ascii=False)
24
-
25
-
26
- async def fetch(url: str) -> dict:
27
- try:
28
- from playwright.async_api import async_playwright
29
- from bs4 import BeautifulSoup
30
- except ImportError as e:
31
- return {"error": str(e) + "\nRun: pip install playwright beautifulsoup4 lxml && playwright install chromium"}
32
-
33
- async with async_playwright() as p:
34
- browser = await p.chromium.launch(headless=True)
35
- page = await browser.new_page(
36
- user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
37
- )
38
- try:
39
- await page.goto(url, wait_until="domcontentloaded", timeout=30000)
40
- await page.wait_for_selector("#js_content", timeout=15000)
41
- html = await page.content()
42
- except Exception as e:
43
- await browser.close()
44
- return {"error": f"Page load failed: {e}"}
45
- await browser.close()
46
-
47
- soup = BeautifulSoup(html, "lxml")
48
-
49
- title = (soup.select_one("#activity-name") or soup.new_tag("x")).get_text(strip=True)
50
- author = (soup.select_one("#js_author_name") or soup.new_tag("x")).get_text(strip=True)
51
- date = (soup.select_one("#publish_time") or soup.new_tag("x")).get_text(strip=True)
52
-
53
- content_el = soup.select_one("#js_content")
54
- if not content_el:
55
- return {"error": "Could not find #js_content"}
56
-
57
- for tag in content_el.find_all(["script", "style"]):
58
- tag.decompose()
59
-
60
- for img in content_el.find_all("img"):
61
- src = img.get("data-src") or img.get("src") or ""
62
- img.replace_with(f"\n![image]({src})\n" if src else "")
63
-
64
- lines = []
65
- for el in content_el.find_all(["p", "h1", "h2", "h3", "h4", "section", "blockquote"]):
66
- text = el.get_text(strip=True)
67
- if not text:
68
- continue
69
- if el.name in ("h1", "h2", "h3", "h4"):
70
- lines.append(f"{'#' * int(el.name[1])} {text}")
71
- elif el.name == "blockquote":
72
- lines.append(f"> {text}")
73
- else:
74
- lines.append(text)
75
-
76
- content = "\n\n".join(lines) or content_el.get_text("\n", strip=True)
77
- return {"title": title, "author": author, "date": date, "url": url, "content": content}
78
-
79
-
80
- def to_markdown(r: dict) -> str:
81
- if "error" in r:
82
- return f"Error: {r['error']}"
83
- parts = [
84
- "---",
85
- f"title: {yaml_string(r.get('title', ''))}",
86
- *([f"author: {yaml_string(r['author'])}"] if r.get("author") else []),
87
- *([f"date: {yaml_string(r['date'])}"] if r.get("date") else []),
88
- f"url: {yaml_string(r.get('url', ''))}",
89
- "---",
90
- "",
91
- f"# {r['title']}" if r.get("title") else "",
92
- "",
93
- r.get("content", ""),
94
- ]
95
- return "\n".join(parts)
96
-
97
-
98
- if __name__ == "__main__":
99
- if len(sys.argv) < 2:
100
- print("Usage: fetch_weixin.py <url> [--json]", file=sys.stderr)
101
- sys.exit(1)
102
-
103
- result = asyncio.run(fetch(sys.argv[1]))
104
- if "--json" in sys.argv:
105
- print(json.dumps(result, ensure_ascii=False, indent=2))
106
- else:
107
- print(to_markdown(result))