harveyz-skill 0.26.0 → 0.28.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 (107) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/package.json +5 -2
  3. package/skills/coding/explain-pm/SKILL.md +39 -13
  4. package/skills/mint/publish-skill/SKILL.md +36 -15
  5. package/skills/research/clip-url/SKILL.md +39 -19
  6. package/skills/research/clip-url/scripts/__pycache__/article_meta.cpython-314.pyc +0 -0
  7. package/skills/research/clip-url/scripts/__pycache__/browser_fetch_mcp_locate.cpython-314.pyc +0 -0
  8. package/skills/research/clip-url/scripts/__pycache__/dedup_check.cpython-314.pyc +0 -0
  9. package/skills/research/clip-url/scripts/__pycache__/mcp_debug_client.cpython-314.pyc +0 -0
  10. package/skills/research/clip-url/scripts/__pycache__/mcp_fetch_client.cpython-314.pyc +0 -0
  11. package/skills/research/clip-url/scripts/__pycache__/vault_config.cpython-314.pyc +0 -0
  12. package/skills/research/clip-url/scripts/__pycache__/write_meta_and_separate.cpython-314.pyc +0 -0
  13. package/skills/research/clip-url/scripts/browser_fetch_mcp_locate.py +48 -0
  14. package/skills/research/clip-url/scripts/chrome_profile_config.py +44 -5
  15. package/skills/research/clip-url/scripts/detect_xcom_chrome_profile.py +3 -4
  16. package/skills/research/clip-url/scripts/mcp_debug_client.py +3 -4
  17. package/skills/research/clip-url/scripts/mcp_fetch_client.py +2 -3
  18. package/skills/research/clip-url/tests/__pycache__/conftest.cpython-314-pytest-9.1.1.pyc +0 -0
  19. package/skills/research/clip-url/tests/__pycache__/test_article_meta.cpython-314-pytest-9.1.1.pyc +0 -0
  20. package/skills/research/clip-url/tests/__pycache__/test_browser_fetch_mcp_locate.cpython-314-pytest-9.1.1.pyc +0 -0
  21. package/skills/research/clip-url/tests/__pycache__/test_chrome_profile_config.cpython-314-pytest-9.1.1.pyc +0 -0
  22. package/skills/research/clip-url/tests/__pycache__/test_dedup_check.cpython-314-pytest-9.1.1.pyc +0 -0
  23. package/skills/research/clip-url/tests/__pycache__/test_detect_xcom_chrome_profile.cpython-314-pytest-9.1.1.pyc +0 -0
  24. package/skills/research/clip-url/tests/__pycache__/test_mcp_debug_client.cpython-314-pytest-9.1.1.pyc +0 -0
  25. package/skills/research/clip-url/tests/__pycache__/test_mcp_fetch_client.cpython-314-pytest-9.1.1.pyc +0 -0
  26. package/skills/research/clip-url/tests/__pycache__/test_vault_config.cpython-314-pytest-9.1.1.pyc +0 -0
  27. package/skills/research/clip-url/tests/__pycache__/test_write_meta_and_separate.cpython-314-pytest-9.1.1.pyc +0 -0
  28. package/skills/research/clip-url/tests/test_browser_fetch_mcp_locate.py +83 -0
  29. package/skills/research/clip-url/tests/test_chrome_profile_config.py +28 -0
  30. package/skills/research/extract-url/references/__pycache__/article_utils.cpython-314.pyc +0 -0
  31. package/skills/research/extract-url/scripts/__pycache__/config.cpython-314.pyc +0 -0
  32. package/skills/research/extract-url/scripts/__pycache__/migrate_to_folder_structure.cpython-314.pyc +0 -0
  33. package/skills/research/fetch-paper/SKILL.md +239 -0
  34. package/skills/research/learn-video/SKILL.md +58 -22
  35. package/skills/research/sync-xtimeline/SKILL.md +62 -0
  36. package/skills/research/sync-xtimeline/scripts/__pycache__/archive_tweets.cpython-314.pyc +0 -0
  37. package/skills/research/sync-xtimeline/scripts/__pycache__/browser_fetch_mcp_locate.cpython-314.pyc +0 -0
  38. package/skills/research/sync-xtimeline/scripts/__pycache__/fetch_new_tweets.cpython-314.pyc +0 -0
  39. package/skills/research/sync-xtimeline/scripts/__pycache__/mcp_timeline_client.cpython-314.pyc +0 -0
  40. package/skills/research/sync-xtimeline/scripts/__pycache__/render_digest.cpython-314.pyc +0 -0
  41. package/skills/research/sync-xtimeline/scripts/__pycache__/render_view.cpython-314.pyc +0 -0
  42. package/skills/research/sync-xtimeline/scripts/__pycache__/watchlist.cpython-314.pyc +0 -0
  43. package/skills/research/sync-xtimeline/scripts/archive_tweets.py +45 -0
  44. package/skills/research/sync-xtimeline/scripts/browser_fetch_mcp_locate.py +48 -0
  45. package/skills/research/sync-xtimeline/scripts/fetch_new_tweets.py +76 -0
  46. package/skills/research/sync-xtimeline/scripts/mcp_timeline_client.py +49 -0
  47. package/skills/research/sync-xtimeline/scripts/render_digest.py +106 -0
  48. package/skills/research/sync-xtimeline/scripts/render_view.py +269 -0
  49. package/skills/research/sync-xtimeline/scripts/watchlist.py +118 -0
  50. package/skills/research/sync-xtimeline/tests/__pycache__/conftest.cpython-314-pytest-9.1.1.pyc +0 -0
  51. package/skills/research/sync-xtimeline/tests/__pycache__/test_archive_tweets.cpython-314-pytest-9.1.1.pyc +0 -0
  52. package/skills/research/sync-xtimeline/tests/__pycache__/test_browser_fetch_mcp_locate.cpython-314-pytest-9.1.1.pyc +0 -0
  53. package/skills/research/sync-xtimeline/tests/__pycache__/test_fetch_new_tweets.cpython-314-pytest-9.1.1.pyc +0 -0
  54. package/skills/research/sync-xtimeline/tests/__pycache__/test_mcp_timeline_client.cpython-314-pytest-9.1.1.pyc +0 -0
  55. package/skills/research/sync-xtimeline/tests/__pycache__/test_render_digest.cpython-314-pytest-9.1.1.pyc +0 -0
  56. package/skills/research/sync-xtimeline/tests/__pycache__/test_render_view.cpython-314-pytest-9.1.1.pyc +0 -0
  57. package/skills/research/sync-xtimeline/tests/__pycache__/test_watchlist.cpython-314-pytest-9.1.1.pyc +0 -0
  58. package/skills/research/sync-xtimeline/tests/conftest.py +16 -0
  59. package/skills/research/sync-xtimeline/tests/test_archive_tweets.py +75 -0
  60. package/skills/research/sync-xtimeline/tests/test_browser_fetch_mcp_locate.py +12 -0
  61. package/skills/research/sync-xtimeline/tests/test_fetch_new_tweets.py +96 -0
  62. package/skills/research/sync-xtimeline/tests/test_mcp_timeline_client.py +30 -0
  63. package/skills/research/sync-xtimeline/tests/test_render_digest.py +222 -0
  64. package/skills/research/sync-xtimeline/tests/test_render_view.py +101 -0
  65. package/skills/research/sync-xtimeline/tests/test_watchlist.py +110 -0
  66. package/skills-index.json +30 -10
  67. package/tools/browser-fetch-mcp/browser-fetch-mcp.sh +36 -0
  68. package/tools/browser-fetch-mcp/browser_fetch_mcp/__init__.py +1 -0
  69. package/tools/browser-fetch-mcp/browser_fetch_mcp/__pycache__/__init__.cpython-314.pyc +0 -0
  70. package/tools/browser-fetch-mcp/browser_fetch_mcp/__pycache__/config.cpython-314.pyc +0 -0
  71. package/tools/browser-fetch-mcp/browser_fetch_mcp/__pycache__/cookies.cpython-314.pyc +0 -0
  72. package/tools/browser-fetch-mcp/browser_fetch_mcp/__pycache__/extractors.cpython-314.pyc +0 -0
  73. package/tools/browser-fetch-mcp/browser_fetch_mcp/__pycache__/images.cpython-314.pyc +0 -0
  74. package/tools/browser-fetch-mcp/browser_fetch_mcp/__pycache__/markdown.cpython-314.pyc +0 -0
  75. package/tools/browser-fetch-mcp/browser_fetch_mcp/__pycache__/profiles.cpython-314.pyc +0 -0
  76. package/tools/browser-fetch-mcp/browser_fetch_mcp/__pycache__/server.cpython-314.pyc +0 -0
  77. package/tools/browser-fetch-mcp/browser_fetch_mcp/config.py +25 -0
  78. package/tools/browser-fetch-mcp/browser_fetch_mcp/cookies.py +35 -0
  79. package/tools/browser-fetch-mcp/browser_fetch_mcp/extractors.py +759 -0
  80. package/tools/browser-fetch-mcp/browser_fetch_mcp/images.py +74 -0
  81. package/tools/browser-fetch-mcp/browser_fetch_mcp/markdown.py +96 -0
  82. package/tools/browser-fetch-mcp/browser_fetch_mcp/profiles.py +97 -0
  83. package/tools/browser-fetch-mcp/browser_fetch_mcp/server.py +592 -0
  84. package/tools/browser-fetch-mcp/pyproject.toml +26 -0
  85. package/tools/browser-fetch-mcp/tests/__pycache__/test_config.cpython-314-pytest-9.1.1.pyc +0 -0
  86. package/tools/browser-fetch-mcp/tests/__pycache__/test_cookies.cpython-314-pytest-9.1.1.pyc +0 -0
  87. package/tools/browser-fetch-mcp/tests/__pycache__/test_evaluate_js.cpython-314-pytest-9.1.1.pyc +0 -0
  88. package/tools/browser-fetch-mcp/tests/__pycache__/test_extractors.cpython-314-pytest-9.1.1.pyc +0 -0
  89. package/tools/browser-fetch-mcp/tests/__pycache__/test_extractors_timeline.cpython-314-pytest-9.1.1.pyc +0 -0
  90. package/tools/browser-fetch-mcp/tests/__pycache__/test_fetch_article.cpython-314-pytest-9.1.1.pyc +0 -0
  91. package/tools/browser-fetch-mcp/tests/__pycache__/test_fetch_user_timeline.cpython-314-pytest-9.1.1.pyc +0 -0
  92. package/tools/browser-fetch-mcp/tests/__pycache__/test_images.cpython-314-pytest-9.1.1.pyc +0 -0
  93. package/tools/browser-fetch-mcp/tests/__pycache__/test_markdown.cpython-314-pytest-9.1.1.pyc +0 -0
  94. package/tools/browser-fetch-mcp/tests/__pycache__/test_profiles.cpython-314-pytest-9.1.1.pyc +0 -0
  95. package/tools/browser-fetch-mcp/tests/__pycache__/test_server.cpython-314-pytest-9.1.1.pyc +0 -0
  96. package/tools/browser-fetch-mcp/tests/test_config.py +25 -0
  97. package/tools/browser-fetch-mcp/tests/test_cookies.py +35 -0
  98. package/tools/browser-fetch-mcp/tests/test_evaluate_js.py +63 -0
  99. package/tools/browser-fetch-mcp/tests/test_extractors.py +73 -0
  100. package/tools/browser-fetch-mcp/tests/test_extractors_timeline.py +170 -0
  101. package/tools/browser-fetch-mcp/tests/test_fetch_article.py +387 -0
  102. package/tools/browser-fetch-mcp/tests/test_fetch_user_timeline.py +80 -0
  103. package/tools/browser-fetch-mcp/tests/test_images.py +67 -0
  104. package/tools/browser-fetch-mcp/tests/test_markdown.py +130 -0
  105. package/tools/browser-fetch-mcp/tests/test_profiles.py +71 -0
  106. package/tools/browser-fetch-mcp/tests/test_server.py +126 -0
  107. package/tools/browser-fetch-mcp/tool.json +8 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.28.0] - 2026-08-15
11
+
12
+ ### Added
13
+ - `sync-xtimeline`:新增 skill(原名 watch-x),批量追更一批固定的 X 博主推文,抓取时自动识别推文类型(post/repost/quote/reply),产出翻译摘要 + 累计静态 HTML 时间线视图(X 风格深色主题)
14
+ - `browser-fetch-mcp`:新增 `fetch_user_timeline` MCP 工具,支持抓取 X 用户时间线
15
+ - `fetch-paper`:新增 skill,贡献到 research bundle
16
+
17
+ ### Changed
18
+ - `explain-pm`:输出改为分层,界定技术语言边界,允许调研代码
19
+
20
+ ### Fixed
21
+ - `fetch-paper`:修复 PMC/OA 页面被反爬拦截无法抓取、PDF 二进制内容误用 Write 工具写盘、SKILL.md F3 违规
22
+ - `learn-video`:硬编码用户路径改用 `$HOME` + task_id 读取重试;改为后台执行+日志判定,避免阻塞调用
23
+
24
+ ## [0.27.0] - 2026-08-13
25
+
26
+ ### Added
27
+ - `clip-url`:新增 browser-fetch-mcp 前置可用性检测,抓取前先确认 MCP 可用,避免执行到中途才发现环境问题
28
+ - `clip-url`:正文开头新增流程概览(路线图),列出各步骤编号,减少多步骤执行时的偏差
29
+
30
+ ### Changed
31
+ - `clip-url`:优化 description 触发文案,定位为 extract-url 的通用替代品参与触发竞争,覆盖更多保存/归档/翻译存档场景
32
+
33
+ ### Fixed
34
+ - `clip-url`:chrome_profile 询问从「可能每次任务都问」改为「只在首次使用本 skill 时问一次」,无论用户当时是否设置了值,都会记住已经问过
35
+
36
+ ## [0.26.1] - 2026-08-13
37
+
38
+ ### Fixed
39
+ - `clip-url`:MCP 抓取脚本此前写死仓库内相对路径来定位 `browser-fetch-mcp`,在任何非源码 checkout 的已安装环境下都会 `FileNotFoundError`;改为 dev-mode(仓库内)优先、已安装模式(`~/.local/bin/browser-fetch-mcp`)兜底的探测逻辑,并把 `browser-fetch-mcp` 注册进 `skills-index.json` 的 `tools[]`(新增 `research-tools` bundle),使其首次可通过 `hskill install --tool browser-fetch-mcp` 安装
40
+
10
41
  ## [0.26.0] - 2026-08-12
11
42
 
12
43
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harveyz-skill",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Skill manager for Claude Code, Cursor, Codex, OpenClaw, Hermes, OpenCode, and Pi",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "scripts": {
10
10
  "prepack": "node scripts/generate-npmignore.js",
11
- "test": "bats tests/ && bash scripts/run-skill-tests.sh && node --test tests/mcp.test.mjs"
11
+ "test": "bats tests/ && bash scripts/run-skill-tests.sh && node --test tests/mcp.test.mjs tests/harness/*.test.mjs"
12
12
  },
13
13
  "files": [
14
14
  "bin/",
@@ -22,10 +22,12 @@
22
22
  "skills/research/learn-video/",
23
23
  "skills/research/survey-skillrepo/",
24
24
  "skills/research/learn-paper/",
25
+ "skills/research/fetch-paper/",
25
26
  "skills/research/extract-cognition/",
26
27
  "skills/research/probe-session/",
27
28
  "skills/research/pdf-math-translate/",
28
29
  "skills/research/clip-url/",
30
+ "skills/research/sync-xtimeline/",
29
31
  "skills/creative/capture-todo/",
30
32
  "skills/creative/capture-insight/",
31
33
  "skills/coding/init-workflow/",
@@ -57,6 +59,7 @@
57
59
  "skills/coding/handoff/",
58
60
  "tools/hub/",
59
61
  "tools/sync-agent/",
62
+ "tools/browser-fetch-mcp/",
60
63
  "hooks/check-similar-branch/"
61
64
  ],
62
65
  "engines": {
@@ -1,34 +1,60 @@
1
1
  ---
2
2
  name: explain-pm
3
- description: "Restate a recent technical explanation from a product-manager's analytical lensuser experience, product philosophy, execution mechanics, and architectural healthrather than translating jargon, and flag product-perspective concerns like scope, over-engineering, priority, or drift from user need. Triggers: '/explain-pm', '/explain-pm <topic or file>', 'explain this like a PM', 'restate from a PM perspective'."
3
+ description: "Restate a technical explanation in language a product manager can act on what changes for users, which components are involved, and the call/timing dependencies between them keeping technical terms that carry architecture and dropping pure implementation mechanics. May read related code to fill in facts the original left out. Product-angle observations, if any, go in a separate optional section, never mixed into the restatement. Triggers: '/explain-pm', '/explain-pm <topic or file>', 'explain this like a PM', 'restate from a PM perspective'."
4
4
  user_invocable: true
5
- version: "1.1.1"
5
+ version: "1.2.0"
6
6
  ---
7
7
 
8
8
  # explain-pm — PM 视角复述
9
9
 
10
- 把一段技术性表述从 PM 视角复述:换成 PM 关注的分析维度,再视情况指出值得从产品角度重新考虑的地方。
10
+ 把一段技术性表述复述成 PM(产品经理)能听懂、能据以决策的版本。
11
+
12
+ 输出分两层,边界必须清晰:
13
+
14
+ 1. **核心复述**(必出)——严格不超出原材料的范围。
15
+ 2. **产品角度的延伸**(可选)——有才写,没有就整节不出现。
11
16
 
12
17
  ## 触发
13
18
 
14
19
  仅手动调用:`/explain-pm` 或 `/explain-pm <主题或文件路径>`。不自动检测触发,不主动插话。
15
20
 
16
- ## PM 视角的关注维度
21
+ ## 技术语言的保留边界
22
+
23
+ 不是把技术语言全部剥离——PM 本身熟悉基础技术语言,剥干净反而看不懂全貌。判据是:
24
+
25
+ > PM 依赖的是「什么在什么之后发生、谁依赖谁」,不依赖「这段代码是怎么写的」。
26
+
27
+ **保留**(撑起架构轮廓和调用关系的技术表述):
28
+
29
+ - 组件、服务、模块的名字
30
+ - 调用方向:谁调用谁,同步还是异步
31
+ - 依赖与时序:谁必须先就绪,哪一步是阻塞的
32
+ - 失败传播:某一环挂了,影响面到哪里为止
33
+
34
+ **剥离**(纯实现机制,对理解全貌没有帮助):
35
+
36
+ - 函数签名、参数、返回值类型
37
+ - 数据结构、字段名、序列化格式
38
+ - 算法实现、设计模式名称
39
+ - 具体代码怎么写的、用了哪个库的哪个 API
40
+
41
+ ## 核心复述的组织维度
17
42
 
18
- 不是语言转译——PM 本身熟悉基础技术语言。要转换的是关注点:
43
+ 分析时的参考,不必在回复里逐条列出,也不套固定小标题:
19
44
 
20
- - **用户体验**:这个决定最终会让用户感受到什么变化?
21
- - **产品哲学**:是否符合产品一贯的取舍和原则?
22
- - **执行机制与流程**:具体怎么落地——谁来做、分几步、影响什么协作节奏?
23
- - **架构健康度(次要)**:是否有明显的技术债或长期维护风险,只做提醒,不展开实现细节本身。
45
+ - **用户侧变化**:这件事最终让用户感受到什么?
46
+ - **参与者**:涉及哪些组件/角色,各自负责什么?
47
+ - **调用关系与时序依赖**:谁先谁后、谁依赖谁、哪一环可能卡住?
48
+ - **执行机制与影响面**:谁来做、分几步、影响什么协作节奏、边界在哪?
24
49
 
25
50
  ## 执行
26
51
 
27
52
  1. **取材料**:带参数用参数指定的主题或文件;无参数则取调用前的最后一条 assistant 消息。
28
- 2. **换视角**:对照上面的关注维度重新审视材料——不必在回复里逐条列出,只是分析时的参考。
29
- 3. **提出关注点(视情况)**:判断原表述从 PM 角度是否有值得重新审视的地方,比如是否偏离用户实际需求、范围是否合理、是否过度工程化、优先级是否搞错了。如果原表述本身已经很贴近产品目标,挑不出问题,就只做视角复述,不硬造关注点。关注点如果成立,放在回复最后,不要打断前面的视角复述。
30
- 4. **输出**:在对话中直接回复,简短(几句话量级)。默认不写文件;仅当用户明确要求存档时,才把这段评论写入用户指定的文件。
53
+ 2. **按需调研**:如果原表述没交代清楚 PM 需要知道的调用关系或依赖顺序,去读相关代码/文件把事实补齐。调研只为补核心复述的事实,不用于给延伸小节攒素材。
54
+ 3. **写核心复述**:对照上面的维度和保留边界重述,严格限定在原材料(含第 2 步补齐的事实)范围内,不做评价、不做建议。
55
+ 4. **写延伸(视情况)**:判断是否真有值得从产品角度说的——产品哲学上是否符合一贯取舍、有无明显技术债或长期维护风险、范围是否合理、优先级是否搞错、是否过度工程化。有就单独成节,与核心复述之间保持明确分隔;**挑不出就整节不写,不硬造**。
56
+ 5. **输出**:在对话中直接回复,简短(核心复述几句话量级)。默认不写文件;仅当用户明确要求存档时,才写入用户指定的文件。
31
57
 
32
58
  ## 不做
33
59
 
34
- 多轮追问式澄清、自动检测技术语言并主动触发、默认生成或保存文档、固定的输出小标题模板。
60
+ 多轮追问式澄清、自动检测技术语言并主动触发、默认生成或保存文档、在核心复述里夹带评价或改进建议、为了凑出延伸小节而硬造关注点。
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: publish-skill
3
- description: "Validate and publish a skill to the harveyz-skill repository. Checks SKILL.md format compliance (frontmatter fields, semver version, name-directory match, verb-noun naming convention) and registration in skills-index.json. Rules defined in docs/reference/skill-spec.md. Triggers: publish skill, register skill, validate skill format, check skill, add skill to index, is skill ready to publish."
3
+ description: "Validate and publish a skill to the harveyz-skill repository. Checks SKILL.md format compliance (frontmatter fields, semver version, name-directory match, verb-noun or single-verb naming convention) and registration in skills-index.json. Rules defined in docs/reference/skill-spec.md. Triggers: publish skill, register skill, validate skill format, check skill, add skill to index, is skill ready to publish."
4
4
  user_invocable: true
5
- version: "1.3.1"
5
+ version: "1.4.0"
6
6
  ---
7
7
 
8
8
  # skill-publish
@@ -59,8 +59,9 @@ cat /tmp/sv-unregistered.txt
59
59
  | F4 | `version` 字段 | 非空,格式为 `X.Y.Z`(semver) |
60
60
  | F5 | `user_invocable` 字段 | 显式声明 `true` 或 `false` |
61
61
  | F6 | 正文语言 | frontmatter 结束后的正文内容应为中文(含中文字符即视为合规) |
62
- | F7 | 目录命名规范 | skill 目录名须为 `<动词>-<名词>` 格式(2 词,连字符分隔,全小写),且动词必须在规范词表中;`archived/` 下的 skill 跳过此检查 |
62
+ | F7 | 目录命名规范 | skill 目录名须为 `<动词>-<名词>`(2 段)或单独 `<动词>`(1 段)格式,连字符分隔,全小写;第一段必须语义上是动词,名词(若存在)不得为工具/平台专有名;`archived/` 下的 skill 跳过此检查 |
63
63
  | F8 | 内容 hash | 若 `skills-index.json` 中有 `contentHash` 记录:hash 不同且 version 未变 → 报错;hash 不同且 version 已递增 → 通过,Step 7 更新;无记录 → 首次初始化,Step 7 写入 |
64
+ | F9 | 动词覆盖建议(警告,不阻断) | 目录名中的动词语义上合理但未命中规范词表时触发,建议评估是否补充进词表 |
64
65
 
65
66
  **F3 英文检测方法:** 提取 `description` 字段值,检查是否包含中文字符(`一-鿿` 范围)。有则报错。
66
67
 
@@ -69,19 +70,27 @@ cat /tmp/sv-unregistered.txt
69
70
  **F7 命名规范检测方法:**
70
71
 
71
72
  1. 取目录名(`path` 最后一段)
72
- 2. 按连字符分割,检查是否恰好 2 段
73
- 3. 检查第一段(动词)是否在规范词表中:
73
+ 2. 按连字符分割,允许恰好 1 段或 2 段
74
+ 3. 若 2 段:第一段为动词,第二段为名词;名词不得为工具/平台专有名(youtube、npm、diataxis 等)
75
+ 4. 若 1 段:整个目录名须是动词,不允许是单独名词(如 `diagram`、`config`)
76
+ 5. 动词判定(无论 1 段还是 2 段的第一段):
77
+ a. 先在规范词表中查找:
74
78
 
75
- ```
76
- extract learn forge draw manage migrate scout
77
- build sync publish archive contribute analyze clean
78
- release validate init dispatch close setup capture
79
- runby probe dedup fix survey
80
- ```
79
+ ```
80
+ fetch extract learn forge draw manage migrate
81
+ scout build sync publish archive contribute analyze
82
+ clean release validate init dispatch close setup
83
+ capture clip runby dedup
84
+ ```
85
+
86
+ 命中 → 直接合规。
87
+ b. 未命中 → 按语义判断该词是否为合理动词(而非名词/专有名词/工具名)。若是合理动词 → 合规,但记为 **F9 警告**(建议评估是否补充进词表);若明显不是动词 → **F7 违规**。
81
88
 
82
- 特殊模式:若目录名以 `runby-` 开头,直接视为合规(无需检查名词部分)。
89
+ 特殊模式:若目录名以 `runby-` 开头,直接视为合规(无需检查名词部分,也不参与动词判定)。
83
90
 
84
- 违规示例:`skill-analyzer`(动词不在词表)、`diagram`(单词,非 2 段)、`youtube-learner`(动词不在词表)
91
+ 违规示例:`skill-analyzer`(`skill` 是名词非动词)、`diagram`(单段但是名词非动词)、`youtube-learner`(`youtube` 是平台专有名非动词)
92
+
93
+ 合规但触发 F9 警告的示例:`probe-session`(`probe` 语义上是合理动词,未命中词表)、`handoff`(单段动词,语义完整,未命中词表)
85
94
 
86
95
  **F8 hash 计算方法:**
87
96
 
@@ -147,6 +156,11 @@ skill-publish 检查结果
147
156
  skills/meta/my-skill
148
157
  R1 未在 skills-index.json 中注册
149
158
 
159
+ 格式警告(不阻止通过,建议修复)
160
+ --------
161
+ skills/mint/probe-session
162
+ F9 动词 'probe' 未命中规范词表,语义合理,建议评估是否补充进词表
163
+
150
164
  注册警告(不阻止通过,建议修复)
151
165
  --------
152
166
  skills/coding/setup-debug
@@ -161,7 +175,7 @@ skill-publish 检查结果
161
175
  ...
162
176
 
163
177
  ========================
164
- 共发现 3 个问题(2 个格式,1 个注册),2 个警告
178
+ 共发现 3 个问题(2 个格式,1 个注册),3 个警告
165
179
  ```
166
180
 
167
181
  若全部通过(含警告):
@@ -169,7 +183,7 @@ skill-publish 检查结果
169
183
  ```
170
184
  skill-publish 检查结果
171
185
  ========================
172
- 所有 N 个 skill 格式与注册均符合规范。✓(2 个 R4 警告,建议填写 installScope)
186
+ 所有 N 个 skill 格式与注册均符合规范。✓(1F9 警告,1 个 R4 警告,建议评估补充词表 / 填写 installScope)
173
187
  ```
174
188
 
175
189
  若完全无问题无警告:
@@ -202,6 +216,13 @@ skill-publish 检查结果
202
216
  ```
203
217
  确认后执行 `git mv` 重命名目录,并更新 `skills-index.json` 中对应的 `path` 值,最后运行 `node scripts/generate-npmignore.js`。
204
218
 
219
+ **格式警告(F9)** — 动词未命中词表但语义合理,不阻止发布,仅询问是否顺带补充词表:
220
+ ```
221
+ F9 警告:动词 'probe' 未命中规范词表(语义合理)。
222
+ 是否将 'probe' 加入 docs/reference/skill-spec.md 的规范动词词表?(y/n/跳过)
223
+ ```
224
+ 选 `y` 则用 Edit 工具在 `docs/reference/skill-spec.md` 的词表中追加该动词及含义(需用户补充一句含义描述),并同步追加到本文件 Step 3 的词表副本;选 `n`/跳过则保留警告,不做修改。
225
+
205
226
  **格式问题(F8)** — 询问 bump 类型后自动更新版本号,随即执行 Step 7:
206
227
  ```
207
228
  修复 F8(内容 hash 不匹配):
@@ -1,21 +1,26 @@
1
1
  ---
2
2
  name: clip-url
3
- version: "0.7.0"
4
- description: "Fetches a URL through browser-fetch-mcp's fetch_article (site-aware extraction: generic/wechat/arxiv/xcom, with image download and a persisted default chrome_profile), tags against extract-url's shared fixed-tag vocabulary, translates, and saves origin + translation into extract-url's real shared Obsidian Vault (VAULT_PATH) with cross-skill URL dedup. Not extract-url's full equivalent yet (no frontmatter auto-repair), but writes real vault content, not a validation-only test directory."
3
+ version: "0.7.5"
4
+ description: "Use this the instant a URL is shared with any intent to save, archive, clip, or translate-and-keep it in Obsidian — a bare link with no comment, \"save this\", \"archive this\", \"clip this\", \"add to obsidian\", \"存一下\", \"存到 vault/obsidian\", \"留个档\", \"翻译一下存\", \"帮我存/抓一下这个链接\", or a request to fetch a page via browser-fetch-mcp. Covers arXiv papers, WeChat/公众号 posts, X/Twitter threads, Hacker News links, blog posts, news articles, and general webpages including sites needing special handling (login walls, images, JS-rendered content). Do not use for translate-or-summarize-only requests with no save intent, in-page actions like clicking buttons or filling forms, retagging or fixing metadata on an article already saved, links shared purely for reaction or jokes, or topic searches with no specific URL given."
5
5
  user_invocable: true
6
6
  ---
7
7
 
8
8
  # clip-url(Stage 4,验证性构建)
9
9
 
10
- 这是 [browser-fetch-mcp](../../../tools/browser-fetch-mcp/) 的验证性消费者,跟 extract-url 的 Subagent 1/2 结构对齐,做"抓取(MCP,经 fetch_article 做站点感知抽取)→ 打标 + 翻译 → 存文件"两阶段流程。URL 去重和固定标签词表与 extract-url 共用同一份 `~/.hskill/url-extract/config.json`(`VAULT_PATH`)和 `fixed_tags.txt`,两边抓过的文章互相认得出"已抓取"。仍不是 extract-url 的完全等价替代(例如没有 `validate_article.py` 那样的 frontmatter 自动修复),只用于验证 MCP 抓取链路能否支撑一个完整的两阶段 skill 流程并逐步对齐生产行为。抓取产出的原文文件名与 extract-url 一致,按标题命名(`Origin/<标题>.md`,Translation 沿用同一文件名),两者共存于同一个 `<hash8>/` 目录下,去重判定只看 `meta.json` 的 `source_url`,不受文件名影响。
10
+ 这是 [browser-fetch-mcp](../../../tools/browser-fetch-mcp/) 的验证性消费者,做"抓取(MCP,经 fetch_article 做站点感知抽取)→ 打标 + 翻译 → 存文件"两阶段流程,跟 extract-url Subagent 1/2 结构对齐。下文脚本路径均相对本 SKILL.md 所在目录。
11
11
 
12
- ## 路径变量
12
+ ## 执行流程
13
13
 
14
- ```
15
- SkillDir: skills/research/clip-url
16
- ```
14
+ 流程概览(各步骤的判断条件和细节以下方对应小节为准,这里只做路线图):
17
15
 
18
- ## 执行流程
16
+ 1. 净化 URL
17
+ 2. 确认默认 chrome_profile(只在第一次使用本 skill 时问一次)
18
+ 2.5. 确认共享配置存在(VAULT_PATH / 固定词表)
19
+ 3. 派发 Subagent 1:MCP 抓取
20
+ 4. 判断抓取结果,决定是否需要自优化
21
+ 4.5. 派发 Subagent 3:自优化(仅在步骤 4 判定需要时)
22
+ 5. 派发 Subagent 2:打标 + 翻译
23
+ 6. 向用户输出结果卡片
19
24
 
20
25
  ### 步骤 1:净化 URL
21
26
 
@@ -24,16 +29,26 @@ import re
24
29
  url_safe = re.sub(r'[\x00-\x1f\x7f]', '', url).strip()[:2048]
25
30
  ```
26
31
 
27
- ### 步骤 2:确认默认 chrome_profile(首次使用时设置一次,之后不再询问)
32
+ ### 步骤 1.5:确认 browser-fetch-mcp 可用
33
+
34
+ 运行 `python3 scripts/browser_fetch_mcp_locate.py`。
28
35
 
29
- 运行 `python3 SkillDir/scripts/chrome_profile_config.py get`。
36
+ - 若输出 `FOUND: <path>`:继续步骤 2。
37
+ - 若输出 `NOT_FOUND: <error>`(exit code 1):向用户报告"browser-fetch-mcp 未安装或未找到:{error}。若在本仓库 checkout 内运行,请确认 `tools/browser-fetch-mcp/browser-fetch-mcp.sh` 存在;若是通过 `hskill install` 安装的 skill 副本,请先运行 `hskill install --tool browser-fetch-mcp`",流程终止,不再执行后续步骤。
30
38
 
31
- - 若输出 `CONFIGURED: <path>`:已经配置过默认 profile,跳过下面的检测和提问,直接进入步骤 3。
32
- - 若输出 `NOT_CONFIGURED`(不论当前 URL 是什么网站,只要还没配置过就会命中这一分支):
33
- 1. 运行 `python3 SkillDir/scripts/detect_xcom_chrome_profile.py`,把完整输出(对比表 + `RECOMMENDED_PROFILE:` 那行)原样展示给用户。
34
- 2. 向用户提问:把推荐的 profile 设为以后的默认值?或输入一个替代路径?也可以选择这次先不设置。
35
- 3. 若用户提供了 profile 路径(推荐的或自己输入的):运行 `python3 SkillDir/scripts/chrome_profile_config.py set <path>` 持久化。此后所有网站的抓取都不会再触发这个设置流程。
36
- 4. 若用户选择不设置:不持久化任何值,本次继续(x.com 的 URL 会在 Subagent 1 里因为 browser-fetch-mcp `fetch_article` 报错而失败——x.com 没有匿名抓取选项;非 x.com 的 URL 正常匿名抓取,不受影响)。
39
+ ### 步骤 2:确认默认 chrome_profile(只在第一次使用本 skill 时问一次,之后不再询问)
40
+
41
+ 运行 `python3 scripts/chrome_profile_config.py get`。
42
+
43
+ - 若输出 `CONFIGURED: <path>`:已经配置过默认 profile,跳过下面的检测和提问,直接进入步骤 2.5。
44
+ - 若输出 `NOT_CONFIGURED`:运行 `python3 scripts/chrome_profile_config.py prompted`。
45
+ - 若输出 `YES`(之前已经问过一次,不管当时用户是设置了值还是选择不设置):跳过检测和提问,直接进入步骤 2.5。
46
+ - 若输出 `NO`(第一次遇到这个状态,不论当前 URL 是什么网站都会命中):
47
+ 1. 运行 `python3 scripts/detect_xcom_chrome_profile.py`,把完整输出(对比表 + `RECOMMENDED_PROFILE:` 那行)原样展示给用户。
48
+ 2. 向用户提问:把推荐的 profile 设为以后的默认值?或输入一个替代路径?也可以选择不设置。
49
+ 3. 不论用户如何回答,运行 `python3 scripts/chrome_profile_config.py mark-prompted`,记录"已经问过一次"——此后所有网站的抓取都不会再触发这个设置流程(包括用户当时选择不设置的情况)。
50
+ 4. 若用户提供了 profile 路径(推荐的或自己输入的):运行 `python3 scripts/chrome_profile_config.py set <path>` 持久化。
51
+ 5. 若用户选择不设置:不持久化 profile 值,本次继续(x.com 的 URL 会在 Subagent 1 里因为 `fetch_article` 报错而失败——x.com 没有匿名抓取选项;非 x.com 的 URL 正常匿名抓取,不受影响)。用户之后可随时手动运行 `chrome_profile_config.py set <path>` 补配置。
37
52
 
38
53
  **不允许**:跳过展示直接把探测到的 profile 设为默认值——必须等用户明确回答,且只有用户确认后才能调用 `chrome_profile_config.py set`。
39
54
 
@@ -43,7 +58,7 @@ url_safe = re.sub(r'[\x00-\x1f\x7f]', '', url).strip()[:2048]
43
58
  import subprocess
44
59
  result = subprocess.run(
45
60
  ['python3', '-c',
46
- 'import sys; sys.path.insert(0, "SkillDir/scripts"); import vault_config; print(vault_config.get_vault_path())'],
61
+ 'import sys; sys.path.insert(0, "scripts"); import vault_config; print(vault_config.get_vault_path())'],
47
62
  capture_output=True, text=True
48
63
  )
49
64
  ```
@@ -64,7 +79,7 @@ result = subprocess.run(
64
79
  从报告中读取 `RESULT:` 那行。
65
80
 
66
81
  - 若 `RESULT: SKIPPED`:该 URL 已经抓取过(去重命中),提取 `META_PATH:` 那行的值记作 meta_path,向用户输出「已跳过」卡片(步骤 6 卡片格式,标题用 url_safe),流程终止,不再派发 Subagent 2 或 Subagent 3。
67
- - 若 `RESULT: OK` 且(`CONTENT_THIN: False`,或 `CONTENT_THIN: True` 但 `THIN_RETRY_USED: False`):提取 `ORIGIN_PATH:`/`TITLE:`/`CODE_BLOCK_COUNT:`/`IMAGE_COUNT:` 那几行的值,分别记作 origin_path / title / code_block_count / image_count——步骤 6 的完成卡片要用,此后各步骤间一直带着这四个值。跳到步骤 5。`CONTENT_THIN: True` 且 `THIN_RETRY_USED: False` 的情况(例如文章本来就短、或没有配置 chrome_profile 因而从未触发过认证重试)不算需要自优化——没有更多现有手段可以尝试,按正常内容处理。
82
+ - 若 `RESULT: OK` 且(`CONTENT_THIN: False`,或 `CONTENT_THIN: True` 但 `THIN_RETRY_USED: False`):提取 `ORIGIN_PATH:`/`TITLE:`/`CODE_BLOCK_COUNT:`/`IMAGE_COUNT:` 那几行的值,分别记作 origin_path / title / code_block_count / image_count——步骤 6 的完成卡片要用,此后各步骤间一直带着这四个值。跳到步骤 5。`CONTENT_THIN: True` 且 `THIN_RETRY_USED: False` 不算需要自优化——没有更多现有手段可以尝试,按正常内容处理。
68
83
  - 若 `RESULT: OK` 且 `CONTENT_THIN: True` 且 `THIN_RETRY_USED: True`,或 `RESULT: FAILED`:`RESULT: OK` 时提取 `TITLE:` 那行的值记作 title(`RESULT: FAILED` 时没有 title,title 留空,卡片渲染时用 url_safe 代替);进入步骤 4.5(自优化)。本次 URL 最多只走一次步骤 4.5——若步骤 4.5 重试后仍然满足这个条件,直接终止流程,向用户输出「失败」卡片,不再第二次派发自优化 subagent。
69
84
 
70
85
  ### 步骤 4.5:派发 Subagent 3(自优化,仅在步骤 4 判定需要时执行)
@@ -125,6 +140,10 @@ result = subprocess.run(
125
140
 
126
141
  任意状态下,若本次运行中步骤 4.5 曾经出现过 `RESULT: SOLIDIFIED`,在卡片后额外报告一行:本次抓取新增了未合并分支 `<BRANCH>`,需要用户决定后续(合并/PR/保留)。
127
142
 
143
+ ## 边界
144
+
145
+ 仍不是 extract-url 的完全等价替代,只用于验证 MCP 抓取链路能否支撑一个完整的两阶段 skill 流程并逐步对齐生产行为。跟 extract-url 共用同一份存储与去重索引:URL 去重和固定标签词表读同一份 `~/.hskill/url-extract/config.json`(`VAULT_PATH`)和 `fixed_tags.txt`;抓取产出的原文文件名与 extract-url 一致,按标题命名(`Origin/<标题>.md`,Translation 沿用同一文件名),两者共存于同一个 `<hash8>/` 目录下,去重判定只看 `meta.json` 的 `source_url`,不受文件名影响——两边抓过的文章互相认得出"已抓取"。
146
+
128
147
  ## 参考文件
129
148
 
130
149
  | 文件 | 用途 |
@@ -132,6 +151,7 @@ result = subprocess.run(
132
151
  | `references/subagent1-fetch-prompt.md` | Subagent 1(MCP 抓取)派发 prompt 模板,含去重检查 |
133
152
  | `references/subagent2-tag-translate-prompt.md` | Subagent 2(两阶段打标 + 翻译)派发 prompt 模板 |
134
153
  | `references/subagent-self-optimize-prompt.md` | Subagent 3(自优化,抓取失败/过薄时触发)派发 prompt 模板 |
154
+ | `scripts/browser_fetch_mcp_locate.py` | 步骤 1.5 前置检测:定位 browser-fetch-mcp launcher(dev-mode 优先,已安装模式兜底),也被下面几个 MCP client 脚本共用 |
135
155
  | `scripts/vault_config.py` | 读共享 `VAULT_PATH`(`~/.hskill/url-extract/config.json`),计算文章路径 |
136
156
  | `scripts/dedup_check.py` | URL 去重检查(读 `<hash8>/meta.json`) |
137
157
  | `scripts/article_meta.py` | 去重索引写入 + 固定词表兜底移位(纯函数库) |
@@ -139,4 +159,4 @@ result = subprocess.run(
139
159
  | `scripts/mcp_fetch_client.py` | 核心脚本:真实 MCP client,调用 browser-fetch-mcp 的 `fetch_article`,`fetch_and_report` 额外返回诊断字段 |
140
160
  | `scripts/mcp_debug_client.py` | 自优化 subagent 用的调试客户端,包装 browser-fetch-mcp 的 `fetch_page`/`evaluate_js` |
141
161
  | `scripts/detect_xcom_chrome_profile.py` | 通过 browser-fetch-mcp 的 `list_chrome_profiles` MCP 工具检测哪些 Chrome profile 登录了 x.com,仅供用户确认用,不自动使用检测结果 |
142
- | `scripts/chrome_profile_config.py` | 读写 browser-fetch-mcp 持久化的默认 chrome_profile(`get`/`set` 子命令) |
162
+ | `scripts/chrome_profile_config.py` | 读写 browser-fetch-mcp 持久化的默认 chrome_profile(`get`/`set` 子命令),以及本地记录"是否已问过一次"的标记(`prompted`/`mark-prompted` 子命令) |
@@ -0,0 +1,48 @@
1
+ """Locates the browser-fetch-mcp launcher for clip-url's MCP client scripts.
2
+
3
+ Two supported layouts:
4
+ - Dev mode: this skill runs from inside a harveyz-skill git checkout, where
5
+ tools/browser-fetch-mcp/browser-fetch-mcp.sh sits four directories above
6
+ scripts/ (skills/research/clip-url/scripts -> repo root).
7
+ - Installed mode: this skill was installed via `hskill install` (to
8
+ ~/.claude/skills, ~/.pi/agent/skills, etc.), and browser-fetch-mcp was
9
+ separately installed as a tool — its launcher lands at
10
+ ~/.local/bin/browser-fetch-mcp (see tools/browser-fetch-mcp/tool.json).
11
+ """
12
+ import shutil
13
+ import sys
14
+ from pathlib import Path
15
+
16
+
17
+ def find_browser_fetch_mcp() -> str:
18
+ dev_path = Path(__file__).resolve().parents[4] / "tools" / "browser-fetch-mcp" / "browser-fetch-mcp.sh"
19
+ if dev_path.exists():
20
+ return str(dev_path)
21
+
22
+ on_path = shutil.which("browser-fetch-mcp")
23
+ if on_path:
24
+ return on_path
25
+
26
+ installed_path = Path.home() / ".local" / "bin" / "browser-fetch-mcp"
27
+ if installed_path.exists():
28
+ return str(installed_path)
29
+
30
+ raise FileNotFoundError(
31
+ "browser-fetch-mcp launcher not found. Run clip-url from a harveyz-skill "
32
+ "git checkout, or run `hskill install` and select the browser-fetch-mcp tool."
33
+ )
34
+
35
+
36
+ def main():
37
+ """CLI preflight check for SKILL.md: prints FOUND/NOT_FOUND instead of
38
+ letting the FileNotFoundError surface as a raw traceback from whichever
39
+ client script happens to import this module first."""
40
+ try:
41
+ print(f"FOUND: {find_browser_fetch_mcp()}")
42
+ except FileNotFoundError as e:
43
+ print(f"NOT_FOUND: {e}")
44
+ sys.exit(1)
45
+
46
+
47
+ if __name__ == "__main__":
48
+ main()
@@ -4,13 +4,23 @@ default Chrome profile (get_default_chrome_profile / set_default_chrome_profile
4
4
  tools). Written from scratch, same stdio_client pattern as mcp_fetch_client.py
5
5
  and detect_xcom_chrome_profile.py.
6
6
 
7
+ Also tracks, purely locally (no MCP call involved), whether clip-url has
8
+ already asked the user about chrome_profile once — so SKILL.md's step 2
9
+ only prompts on the very first run, regardless of whether the user set a
10
+ profile or declined, instead of re-prompting on every run until a profile
11
+ happens to get set.
12
+
7
13
  Usage:
8
14
  python3 chrome_profile_config.py get
9
15
  python3 chrome_profile_config.py set <profile_path>
16
+ python3 chrome_profile_config.py prompted
17
+ python3 chrome_profile_config.py mark-prompted
10
18
 
11
19
  get prints "CONFIGURED: <path>" or "NOT_CONFIGURED".
12
20
  set prints "OK" on success; on failure, prints the error to stderr and
13
21
  exits 1 (e.g. profile_path doesn't exist or isn't a directory).
22
+ prompted prints "YES" or "NO".
23
+ mark-prompted records that the user has been asked and prints "OK".
14
24
  """
15
25
  import asyncio
16
26
  import json
@@ -21,9 +31,25 @@ from pathlib import Path
21
31
  from mcp import ClientSession, StdioServerParameters
22
32
  from mcp.client.stdio import stdio_client
23
33
 
24
- BROWSER_FETCH_MCP_SH = (
25
- Path(__file__).resolve().parents[4] / "tools" / "browser-fetch-mcp" / "browser-fetch-mcp.sh"
26
- )
34
+ from browser_fetch_mcp_locate import find_browser_fetch_mcp
35
+
36
+ BROWSER_FETCH_MCP_SH = find_browser_fetch_mcp()
37
+
38
+
39
+ def _prompted_marker_path() -> Path:
40
+ env_dir = os.environ.get("HSKILL_CLIP_URL_DATA_DIR")
41
+ base = Path(env_dir) if env_dir else Path.home() / ".hskill" / "clip-url"
42
+ return base / "chrome_profile_prompted"
43
+
44
+
45
+ def get_prompted() -> bool:
46
+ return _prompted_marker_path().exists()
47
+
48
+
49
+ def mark_prompted() -> None:
50
+ marker = _prompted_marker_path()
51
+ marker.parent.mkdir(parents=True, exist_ok=True)
52
+ marker.write_text("", encoding="utf-8")
27
53
 
28
54
 
29
55
  async def _get() -> str:
@@ -57,14 +83,27 @@ async def _set(profile_path: str) -> str:
57
83
 
58
84
 
59
85
  def main():
60
- if len(sys.argv) < 2 or sys.argv[1] not in ("get", "set"):
61
- print("Usage: chrome_profile_config.py get | set <profile_path>", file=sys.stderr)
86
+ valid = ("get", "set", "prompted", "mark-prompted")
87
+ if len(sys.argv) < 2 or sys.argv[1] not in valid:
88
+ print(
89
+ "Usage: chrome_profile_config.py get | set <profile_path> | prompted | mark-prompted",
90
+ file=sys.stderr,
91
+ )
62
92
  sys.exit(1)
63
93
 
64
94
  if sys.argv[1] == "get":
65
95
  print(asyncio.run(_get()))
66
96
  return
67
97
 
98
+ if sys.argv[1] == "prompted":
99
+ print("YES" if get_prompted() else "NO")
100
+ return
101
+
102
+ if sys.argv[1] == "mark-prompted":
103
+ mark_prompted()
104
+ print("OK")
105
+ return
106
+
68
107
  if len(sys.argv) < 3:
69
108
  print("Usage: chrome_profile_config.py set <profile_path>", file=sys.stderr)
70
109
  sys.exit(1)
@@ -23,14 +23,13 @@ isError/structuredContent), same as mcp_fetch_client.py.
23
23
  import asyncio
24
24
  import json
25
25
  import os
26
- from pathlib import Path
27
26
 
28
27
  from mcp import ClientSession, StdioServerParameters
29
28
  from mcp.client.stdio import stdio_client
30
29
 
31
- BROWSER_FETCH_MCP_SH = (
32
- Path(__file__).resolve().parents[4] / "tools" / "browser-fetch-mcp" / "browser-fetch-mcp.sh"
33
- )
30
+ from browser_fetch_mcp_locate import find_browser_fetch_mcp
31
+
32
+ BROWSER_FETCH_MCP_SH = find_browser_fetch_mcp()
34
33
 
35
34
  HOST_KEYS = [".x.com", ".twitter.com"]
36
35
  COOKIE_NAMES = ["auth_token", "ct0", "twid"]
@@ -10,15 +10,14 @@ mcp 1.28.1 camelCase note as mcp_fetch_client.py).
10
10
  """
11
11
  import json
12
12
  import os
13
- from pathlib import Path
14
13
  from typing import Optional
15
14
 
16
15
  from mcp import ClientSession, StdioServerParameters
17
16
  from mcp.client.stdio import stdio_client
18
17
 
19
- BROWSER_FETCH_MCP_SH = (
20
- Path(__file__).resolve().parents[4] / "tools" / "browser-fetch-mcp" / "browser-fetch-mcp.sh"
21
- )
18
+ from browser_fetch_mcp_locate import find_browser_fetch_mcp
19
+
20
+ BROWSER_FETCH_MCP_SH = find_browser_fetch_mcp()
22
21
 
23
22
 
24
23
  async def _call_tool(tool_name: str, tool_args: dict) -> dict:
@@ -33,10 +33,9 @@ from mcp import ClientSession, StdioServerParameters
33
33
  from mcp.client.stdio import stdio_client
34
34
 
35
35
  import vault_config
36
+ from browser_fetch_mcp_locate import find_browser_fetch_mcp
36
37
 
37
- BROWSER_FETCH_MCP_SH = (
38
- Path(__file__).resolve().parents[4] / "tools" / "browser-fetch-mcp" / "browser-fetch-mcp.sh"
39
- )
38
+ BROWSER_FETCH_MCP_SH = find_browser_fetch_mcp()
40
39
 
41
40
 
42
41
  async def fetch_and_report(url: str, chrome_profile: Optional[str] = None) -> dict: