harveyz-skill 0.11.0 → 0.12.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 (32) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/bin/cli.js +3 -1
  3. package/lib/vars.js +40 -1
  4. package/package.json +3 -3
  5. package/skills/{web-fetch → data-extraction}/article-fetcher/references/__pycache__/article_utils.cpython-314.pyc +0 -0
  6. package/skills/data-extraction/url-extract/scripts/detect_chrome_profile.py +105 -0
  7. package/skills/{web-fetch → data-extraction}/url-extract/vars.json +3 -2
  8. package/skills-index.json +2 -2
  9. /package/skills/{web-fetch → data-extraction}/article-fetcher/SKILL.md +0 -0
  10. /package/skills/{web-fetch → data-extraction}/article-fetcher/references/article_utils.py +0 -0
  11. /package/skills/{web-fetch → data-extraction}/article-fetcher/references/file-format.md +0 -0
  12. /package/skills/{web-fetch → data-extraction}/article-fetcher/scripts/import_reading.py +0 -0
  13. /package/skills/{web-fetch → data-extraction}/article-fetcher/scripts/init_db.py +0 -0
  14. /package/skills/{web-fetch → data-extraction}/article-fetcher/scripts/playwright_web.py +0 -0
  15. /package/skills/{web-fetch → data-extraction}/article-fetcher/scripts/playwright_xcom.py +0 -0
  16. /package/skills/{web-fetch → data-extraction}/article-fetcher/scripts/url-index.db +0 -0
  17. /package/skills/{web-fetch → data-extraction}/article-fetcher/scripts/url-index.db.bak +0 -0
  18. /package/skills/{web-fetch → data-extraction}/article-fetcher/scripts/validate_article.py +0 -0
  19. /package/skills/{web-fetch → data-extraction}/article-fetcher/tests/test_security.py +0 -0
  20. /package/skills/{web-fetch → data-extraction}/article-fetcher/vars.json +0 -0
  21. /package/skills/{web-fetch → data-extraction}/url-extract/SKILL.md +0 -0
  22. /package/skills/{web-fetch → data-extraction}/url-extract/platforms/SKILL.claude.md +0 -0
  23. /package/skills/{web-fetch → data-extraction}/url-extract/platforms/SKILL.codex.md +0 -0
  24. /package/skills/{web-fetch → data-extraction}/url-extract/platforms/SKILL.hermes.md +0 -0
  25. /package/skills/{web-fetch → data-extraction}/url-extract/references/__pycache__/article_utils.cpython-314.pyc +0 -0
  26. /package/skills/{web-fetch → data-extraction}/url-extract/references/article_utils.py +0 -0
  27. /package/skills/{web-fetch → data-extraction}/url-extract/references/core-flow.md +0 -0
  28. /package/skills/{web-fetch → data-extraction}/url-extract/references/file-format.md +0 -0
  29. /package/skills/{web-fetch → data-extraction}/url-extract/scripts/dedup_check.py +0 -0
  30. /package/skills/{web-fetch → data-extraction}/url-extract/scripts/playwright_web.py +0 -0
  31. /package/skills/{web-fetch → data-extraction}/url-extract/scripts/playwright_xcom.py +0 -0
  32. /package/skills/{web-fetch → data-extraction}/url-extract/scripts/validate_article.py +0 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.0] - 2026-06-09
11
+
12
+ ### Added
13
+ - `url-extract`:新增 `detect_chrome_profile.py` 脚本,扫描本机 Chrome profile 并检测 X.com 登录态
14
+ - `url-extract`:安装时 `CHROME_PROFILE` 变量改为 select 选择列表(自动列出所有 profile + 对应 Google 账号),保留手动输入兜底
15
+ - `hskill`:安装/卸载完成后新增 "按 Enter 返回列表" 提示,避免 summary 被 fzf 立即覆盖
16
+
17
+ ### Changed
18
+ - `url-extract` `vars.json`:`CHROME_PROFILE` 默认值从 `Profile 2` 更新为 `Default`
19
+
10
20
  ## [0.11.0] - 2026-06-09
11
21
 
12
22
  ### Added
package/bin/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { select } from '@inquirer/prompts'
2
+ import { select, input } from '@inquirer/prompts'
3
3
  import chalk from 'chalk'
4
4
  import { execSync, spawnSync } from 'child_process'
5
5
  import { createRequire } from 'module'
@@ -1069,6 +1069,8 @@ try {
1069
1069
  // In test mode with HSKILL_TEST_ACTION, run once and exit
1070
1070
  if (process.env.HSKILL_TEST_ACTION) break
1071
1071
 
1072
+ if (process.stdout.isTTY) await input({ message: chalk.dim('按 Enter 返回列表…'), default: '' })
1073
+
1072
1074
  // Loop back to skill selector automatically
1073
1075
  }
1074
1076
 
package/lib/vars.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import fs from 'fs-extra'
3
3
  import path from 'path'
4
4
  import os from 'os'
5
- import { input } from '@inquirer/prompts'
5
+ import { input, select } from '@inquirer/prompts'
6
6
 
7
7
  export function buildAutoVars() {
8
8
  return { HOME: os.homedir() }
@@ -20,10 +20,49 @@ export function substituteVars(text, varsMap) {
20
20
  return text.replace(/\{\{(\w+)\}\}/g, (_, name) => varsMap[name] ?? `{{${name}}}`)
21
21
  }
22
22
 
23
+ async function detectChromeProfiles() {
24
+ const chromeBase = path.join(os.homedir(), 'Library/Application Support/Google/Chrome')
25
+ if (!await fs.pathExists(chromeBase)) return []
26
+
27
+ const entries = await fs.readdir(chromeBase)
28
+ const profileDirs = entries.filter(e => e === 'Default' || e.startsWith('Profile '))
29
+ profileDirs.sort((a, b) => (a === 'Default' ? -1 : b === 'Default' ? 1 : a.localeCompare(b)))
30
+
31
+ const profiles = []
32
+ for (const name of profileDirs) {
33
+ const dir = path.join(chromeBase, name)
34
+ let email = ''
35
+ try {
36
+ const prefs = await fs.readJson(path.join(dir, 'Preferences'))
37
+ const accounts = prefs.account_info ?? []
38
+ email = accounts[0]?.email ?? prefs.user_name ?? ''
39
+ } catch { /* unreadable */ }
40
+ profiles.push({ name, dir, email })
41
+ }
42
+ return profiles
43
+ }
44
+
23
45
  export async function resolveVars(varDefs = [], autoVars) {
24
46
  const result = { ...autoVars }
25
47
  for (const def of varDefs) {
26
48
  const defaultVal = substituteVars(def.default ?? '', autoVars)
49
+
50
+ if (def.type === 'chrome_profile_select') {
51
+ const profiles = await detectChromeProfiles()
52
+ if (profiles.length > 0) {
53
+ const choices = profiles.map(p => ({
54
+ name: p.email ? `${p.name} (${p.email})` : p.name,
55
+ value: p.dir,
56
+ }))
57
+ choices.push({ name: '手动输入路径…', value: '__manual__' })
58
+ const selected = await select({ message: `${def.description}:`, choices })
59
+ if (selected !== '__manual__') {
60
+ result[def.name] = selected
61
+ continue
62
+ }
63
+ }
64
+ }
65
+
27
66
  const value = await input({
28
67
  message: `${def.description}:`,
29
68
  default: defaultVal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harveyz-skill",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Skill manager for Claude Code, Cursor, and Codex",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,8 +27,8 @@
27
27
  "skills/superpowers-fork/writing-plans/",
28
28
  "skills/task/pm-task-dispatch/",
29
29
  "skills/task/task-close/",
30
- "skills/web-fetch/article-fetcher/",
31
- "skills/web-fetch/url-extract/",
30
+ "skills/data-extraction/article-fetcher/",
31
+ "skills/data-extraction/url-extract/",
32
32
  "skills/data-extraction/vision-extract/",
33
33
  "skills/writing/mermaid-diagram/",
34
34
  "skills/design/sync-design-html/",
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 检测哪个 Chrome profile 登录了 X.com (Twitter)。
4
+ 用法: python3 detect_chrome_profile.py
5
+ """
6
+ import os
7
+ import shutil
8
+ import sqlite3
9
+ import tempfile
10
+ import json
11
+ from pathlib import Path
12
+
13
+ CHROME_BASE = Path.home() / "Library/Application Support/Google/Chrome"
14
+ XCOM_HOSTS = [".twitter.com", ".x.com"]
15
+ AUTH_COOKIES = {"auth_token", "ct0", "twid"}
16
+
17
+
18
+ def get_profile_email(profile_dir: Path) -> str:
19
+ prefs = profile_dir / "Preferences"
20
+ try:
21
+ data = json.loads(prefs.read_text(errors="ignore"))
22
+ accounts = data.get("account_info", [])
23
+ if accounts:
24
+ return accounts[0].get("email", "")
25
+ return data.get("user_name", "")
26
+ except Exception:
27
+ return ""
28
+
29
+
30
+ def check_xcom_cookies(profile_dir: Path) -> dict:
31
+ """返回在 X.com 找到的 auth cookie 名称集合(不解密,只检查存在性)"""
32
+ cookies_db = profile_dir / "Cookies"
33
+ if not cookies_db.exists():
34
+ return {}
35
+
36
+ # 复制到 /tmp 避免 Chrome 锁文件冲突
37
+ with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
38
+ tmp_path = f.name
39
+ try:
40
+ shutil.copy2(cookies_db, tmp_path)
41
+ conn = sqlite3.connect(tmp_path)
42
+ conn.row_factory = sqlite3.Row
43
+ cur = conn.cursor()
44
+ cur.execute(
45
+ "SELECT name, host_key FROM cookies WHERE host_key IN ({})".format(
46
+ ",".join("?" * len(XCOM_HOSTS))
47
+ ),
48
+ XCOM_HOSTS,
49
+ )
50
+ rows = cur.fetchall()
51
+ conn.close()
52
+ found = {row["name"] for row in rows}
53
+ return found
54
+ except Exception as e:
55
+ return {"_error": str(e)}
56
+ finally:
57
+ os.unlink(tmp_path)
58
+
59
+
60
+ def main():
61
+ if not CHROME_BASE.exists():
62
+ print("未找到 Chrome 目录:", CHROME_BASE)
63
+ return
64
+
65
+ profiles = sorted(
66
+ [d for d in CHROME_BASE.iterdir() if d.is_dir() and (d.name == "Default" or d.name.startswith("Profile"))],
67
+ key=lambda d: (d.name != "Default", d.name),
68
+ )
69
+
70
+ print(f"{'Profile':<12} {'账号':<38} {'X.com cookies':<30} {'推荐'}")
71
+ print("-" * 95)
72
+
73
+ best = None
74
+ for p in profiles:
75
+ email = get_profile_email(p) or "(未登录 Google)"
76
+ cookies = check_xcom_cookies(p)
77
+
78
+ if "_error" in cookies:
79
+ status = f"[错误: {cookies['_error'][:40]}]"
80
+ has_auth = False
81
+ else:
82
+ auth_found = AUTH_COOKIES & cookies
83
+ has_auth = bool(auth_found)
84
+ all_found = cookies
85
+ if all_found:
86
+ status = ", ".join(sorted(all_found))
87
+ else:
88
+ status = "(无 X.com cookie)"
89
+
90
+ recommend = "<-- 推荐" if has_auth and best is None else ""
91
+ if has_auth and best is None:
92
+ best = p
93
+
94
+ print(f"{p.name:<12} {email:<38} {status:<30} {recommend}")
95
+
96
+ print()
97
+ if best:
98
+ print(f"建议 CHROME_PROFILE 设置为:")
99
+ print(f" {best}")
100
+ else:
101
+ print("未在任何 profile 中找到 X.com auth_token,请先在 Chrome 中登录 X.com。")
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()
@@ -6,7 +6,8 @@
6
6
  },
7
7
  {
8
8
  "name": "CHROME_PROFILE",
9
- "description": "Chrome 用户配置目录(X.com 登录态所需)。macOS 默认:~/Library/Application Support/Google/Chrome/Profile 2",
10
- "default": "{{HOME}}/Library/Application Support/Google/Chrome/Profile 2"
9
+ "description": "Chrome 用户配置目录(X.com 登录态所需)",
10
+ "type": "chrome_profile_select",
11
+ "default": "{{HOME}}/Library/Application Support/Google/Chrome/Default"
11
12
  }
12
13
  ]
package/skills-index.json CHANGED
@@ -33,8 +33,8 @@
33
33
  { "path": "superpowers-fork/writing-plans", "bundle": "brainstorming" },
34
34
  { "path": "task/pm-task-dispatch", "bundle": "task" },
35
35
  { "path": "task/task-close", "bundle": "task" },
36
- { "path": "web-fetch/article-fetcher", "bundle": "data-extraction" },
37
- { "path": "web-fetch/url-extract", "bundle": "data-extraction" },
36
+ { "path": "data-extraction/article-fetcher", "bundle": "data-extraction" },
37
+ { "path": "data-extraction/url-extract", "bundle": "data-extraction" },
38
38
  { "path": "data-extraction/vision-extract", "bundle": "data-extraction" },
39
39
  { "path": "writing/mermaid-diagram", "bundle": "writing" },
40
40
  { "path": "design/sync-design-html", "bundle": "design" },