argo-search 2.9.7 → 2.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +58 -145
- package/README.es.md +58 -145
- package/README.ja.md +64 -151
- package/README.ko.md +63 -150
- package/README.md +65 -154
- package/SKILL.md +3 -3
- package/assets/readme/hero.svg +2 -2
- package/backends/domain_profiles.json +48 -0
- package/backends/engine_registry.yaml +118 -4
- package/backends/quota_profiles.json +92 -12
- package/backends/source_types_cn.json +27 -1
- package/bin/argo +74 -5
- package/bin/argo.js +33 -2
- package/config.yaml +219 -60
- package/docs/ENGINE_CATALOG.md +50 -39
- package/docs/MCP_SETUP.md +3 -1
- package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
- package/engines/specs/cfets.yaml +41 -0
- package/engines/specs/defillama.yaml +38 -0
- package/engines/specs/firecrawl.yaml +1 -1
- package/engines/specs/fxtwitter.yaml +5 -0
- package/engines/specs/gleif.yaml +55 -0
- package/engines/specs/hkex.yaml +40 -0
- package/engines/specs/keenable_free.yaml +36 -0
- package/engines/specs/shfe.yaml +39 -0
- package/engines/specs/stackexchange.yaml +2 -2
- package/engines/specs/tavily_free.yaml +56 -0
- package/engines/specs/yuanbao.yaml +93 -0
- package/mcp/clients.yaml +32 -0
- package/package.json +6 -2
- package/packages/dsh-plugin/README.md +23 -5
- package/packages/dsh-plugin/dsh/client.js +304 -15
- package/packages/dsh-plugin/dsh/diagnostics.js +54 -0
- package/packages/dsh-plugin/dsh/index.js +112 -1875
- package/packages/dsh-plugin/dsh/native-tools.mjs +7 -1
- package/packages/dsh-plugin/dsh/runner.js +666 -0
- package/packages/dsh-plugin/dsh/wide-research.js +878 -0
- package/packages/dsh-plugin/package.json +2 -2
- package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
- package/packages/dsh-plugin/test/client.test.js +175 -1
- package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
- package/packages/dsh-plugin/test/index.test.js +44 -0
- package/scripts/adaptive.py +24 -1
- package/scripts/article_meta.py +131 -0
- package/scripts/budget.py +247 -0
- package/scripts/budget_cli.py +93 -0
- package/scripts/build_release_assets.sh +65 -0
- package/scripts/cache.py +60 -72
- package/scripts/ci_drift_gate.py +90 -0
- package/scripts/content_boundary.py +46 -0
- package/scripts/content_signals.py +26 -5
- package/scripts/cross_lingual_trace.py +293 -0
- package/scripts/dispatch_result.py +98 -0
- package/scripts/engine_dispatch.py +49 -86
- package/scripts/engine_env.py +5 -0
- package/scripts/engine_families.py +6 -0
- package/scripts/engine_policy.py +7 -7
- package/scripts/engines.py +21 -0
- package/scripts/engines_base.py +3 -3
- package/scripts/engines_builders.py +12 -0
- package/scripts/engines_builders_batch10.py +48 -3
- package/scripts/engines_builders_batch14.py +656 -0
- package/scripts/engines_builders_batch15.py +249 -0
- package/scripts/engines_builders_keyless.py +111 -0
- package/scripts/engines_builders_tech.py +0 -1
- package/scripts/evidence.py +31 -3
- package/scripts/evidence_loop.py +54 -22
- package/scripts/fetch_v3.py +25 -0
- package/scripts/gen_routing_snapshot.py +11 -0
- package/scripts/install.ps1 +18 -0
- package/scripts/install.sh +16 -1
- package/scripts/job.py +3 -3
- package/scripts/local_seek.py +41 -5
- package/scripts/macro_countries.py +15 -0
- package/scripts/matrix_search_eval.py +82 -0
- package/scripts/mcp_compact.py +234 -0
- package/scripts/mcp_handlers.py +60 -642
- package/scripts/mcp_launch.sh +10 -8
- package/scripts/mcp_local.py +241 -0
- package/scripts/mcp_server.py +19 -14
- package/scripts/mcp_setup.py +33 -9
- package/scripts/mcp_tools.py +1 -0
- package/scripts/mcp_transport.py +1 -1
- package/scripts/mcp_warm.py +127 -0
- package/scripts/output_transparency.py +200 -0
- package/scripts/paid_policy.py +98 -0
- package/scripts/quota.py +158 -13
- package/scripts/recovery_runner.py +150 -0
- package/scripts/replay_eval.py +18 -0
- package/scripts/research.py +37 -21
- package/scripts/research_expand.py +24 -1
- package/scripts/retry_policy.py +76 -0
- package/scripts/route.py +6 -6
- package/scripts/route_cache.py +18 -0
- package/scripts/route_combo.py +7 -0
- package/scripts/route_domains.py +32 -19
- package/scripts/search.py +15 -27
- package/scripts/search_attribution.py +100 -3
- package/scripts/search_cli.py +24 -11
- package/scripts/search_entry.py +7 -1
- package/scripts/search_output.py +99 -15
- package/scripts/search_pipeline.py +42 -193
- package/scripts/search_types.py +107 -9
- package/scripts/semantic_evidence.py +179 -3
- package/scripts/serp_guard.py +115 -7
- package/scripts/serp_kill_log.py +25 -2
- package/scripts/stats_cli.py +13 -1
- package/scripts/wide_research.py +45 -0
- package/scripts/zhihu_quota.py +139 -0
- package/sub-skills/local-search/bm25.py +27 -3
- package/sub-skills/local-search/cli_runner.py +331 -0
- package/sub-skills/local-search/parsers.py +328 -0
- package/sub-skills/local-search/search_v3.py +31 -674
- package/sub-skills/local-seek/scripts/seek_rank.py +7 -3
- package/assets/readme/settings-keys.png +0 -0
- package/assets/readme/settings-tools.png +0 -0
- package/scripts/image_vision/vision_probe +0 -0
- package/scripts/local_engines.py +0 -321
- package/sub-skills/ego-search/references/browser-runtime.md +0 -43
- package/sub-skills/ego-search/references/install.md +0 -67
- package/sub-skills/ego-search/references/original-ego-upgrade.md +0 -51
- package/sub-skills/ego-search/tests/test_query_normalize.py +0 -68
- package/sub-skills/js-run/tests/fixtures/challenge_like.js +0 -27
- package/sub-skills/js-run/tests/test_jsrun.py +0 -165
- package/sub-skills/local-seek/references/auto-behaviors.md +0 -24
- package/sub-skills/local-seek/references/fd-mdfind-recipes.md +0 -62
- package/sub-skills/local-seek/references/git-integration.md +0 -54
- package/sub-skills/local-seek/references/remote-handoff.md +0 -46
- package/sub-skills/local-seek/references/rg-recipes.md +0 -96
- package/sub-skills/local-seek/references/strategies.md +0 -52
- package/sub-skills/local-seek/references/structural-search.md +0 -59
- package/sub-skills/local-seek/tests/test_content_rank.py +0 -222
- package/sub-skills/local-seek/tests/test_determinism_20261006.py +0 -145
- package/sub-skills/local-seek/tests/test_noise_tier.py +0 -101
- package/sub-skills/local-seek/tests/test_pcre2_probe.py +0 -136
- package/sub-skills/local-seek/tests/test_rank_pinyin.py +0 -77
- package/sub-skills/local-seek/tests/test_regression_20261003.py +0 -169
- package/sub-skills/local-seek/tests/test_regression_20261006.py +0 -146
- package/sub-skills/local-seek/tests/test_seek_degradation.py +0 -111
- package/sub-skills/local-seek/tests/test_seek_fts.py +0 -313
|
@@ -82,6 +82,7 @@ _TOPIC_PRIOR_WEIGHT = 1.2
|
|
|
82
82
|
# 量级与一个中频词元命中相当,够翻二元组的歧义、翻不了多词全覆盖行。
|
|
83
83
|
_PHRASE_BONUS = 3.0
|
|
84
84
|
_PHRASE_BONUS_CAP = 6.0
|
|
85
|
+
_NAME_PHRASE_BONUS = 4.0 # 文件名直接命中查询短语时的定位加成
|
|
85
86
|
|
|
86
87
|
|
|
87
88
|
def _split_camel(run: str) -> list[str]:
|
|
@@ -178,9 +179,12 @@ def rank_rows(rows: list[tuple[str, int, str]], raw_query: str,
|
|
|
178
179
|
total += w * qtf * (sat + _DELTA) * idf
|
|
179
180
|
total += _TOPIC_PRIOR_WEIGHT * math.log1p(file_hits[rows[i][0]])
|
|
180
181
|
if phrases:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
low_line = (rows[i][2] or "").lower()
|
|
183
|
+
fp_low = (rows[i][0] or "").lower()
|
|
184
|
+
name_low = fp_low.rpartition("/")[2] or fp_low
|
|
185
|
+
name_hits = sum(1 for ph in phrases if ph in name_low)
|
|
186
|
+
line_hits = sum(1 for ph in phrases if ph in low_line)
|
|
187
|
+
total += min(name_hits, 2) * _NAME_PHRASE_BONUS + min(line_hits, 2) * _PHRASE_BONUS
|
|
184
188
|
if boost_fn is not None:
|
|
185
189
|
try:
|
|
186
190
|
total += boost_fn(rows[i][0])
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/scripts/local_engines.py
DELETED
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""本地个性化引擎插件 — 不随公开仓库提交。
|
|
3
|
-
|
|
4
|
-
包含漏洞查询类引擎(OSV.dev + CISA KEV),因启发式杀毒检测
|
|
5
|
-
(HEUR:HackTool/VulnScan)从公开构建器中移出。
|
|
6
|
-
本地文件,不提交 git;删除后对应引擎自动消失,不影响其他功能。
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import re
|
|
13
|
-
import time
|
|
14
|
-
import urllib.request
|
|
15
|
-
from typing import Any
|
|
16
|
-
|
|
17
|
-
from engines_base import safe_search, rank_score, http_open, note_failure
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# ── OSV.dev 开源漏洞库(免 key,必须 POST)─────────────────────────────────
|
|
21
|
-
|
|
22
|
-
_OSV_ECOSYSTEM_PREFIX: dict[str, str] = {
|
|
23
|
-
"pypi": "PyPI", "npm": "npm", "go": "Go", "crates": "crates.io",
|
|
24
|
-
"cargo": "crates.io", "maven": "Maven", "nuget": "NuGet",
|
|
25
|
-
"packagist": "Packagist", "composer": "Packagist",
|
|
26
|
-
"rubygems": "RubyGems", "gem": "RubyGems", "hex": "Hex",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
_OSV_NPM_HINTS: frozenset[str] = frozenset({
|
|
30
|
-
"lodash", "react", "vue", "express", "axios", "next", "webpack",
|
|
31
|
-
"typescript", "eslint", "vite", "jest", "moment", "jquery", "angular",
|
|
32
|
-
"svelte", "nuxt", "rollup", "babel", "prettier", "cheerio", "socket.io",
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
_OSV_GO_HINTS: frozenset[str] = frozenset({
|
|
36
|
-
"golang.org/x/net", "golang.org/x/crypto", "golang.org/x/text",
|
|
37
|
-
"github.com/gin-gonic/gin", "github.com/gorilla/websocket",
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _osv_resolve_ecosystem(query: str) -> tuple[str, str]:
|
|
42
|
-
q = (query or "").strip()
|
|
43
|
-
if ":" in q:
|
|
44
|
-
head, _, tail = q.partition(":")
|
|
45
|
-
eco = _OSV_ECOSYSTEM_PREFIX.get(head.strip().lower())
|
|
46
|
-
if eco and tail.strip():
|
|
47
|
-
return tail.strip(), eco
|
|
48
|
-
name = q
|
|
49
|
-
if name.startswith("@") or "/" in name:
|
|
50
|
-
if name.startswith("@"):
|
|
51
|
-
return name, "npm"
|
|
52
|
-
if name.startswith(("github.com/", "gitlab.com/", "golang.org/")):
|
|
53
|
-
return name, "Go"
|
|
54
|
-
return name, "npm"
|
|
55
|
-
if name.lower() in _OSV_NPM_HINTS:
|
|
56
|
-
return name, "npm"
|
|
57
|
-
if name.lower() in _OSV_GO_HINTS:
|
|
58
|
-
return name, "Go"
|
|
59
|
-
return name, "PyPI"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _build_osv_engine(spec: dict[str, Any]) -> Any:
|
|
63
|
-
"""OSV.dev 漏洞查询(按包名,POST /v1/query;免 key、免限次)。"""
|
|
64
|
-
timeout = spec.get("timeout", 15)
|
|
65
|
-
|
|
66
|
-
@safe_search
|
|
67
|
-
def _engine(query: str, n: int = 5, _timeout: float | None = None,
|
|
68
|
-
**kwargs) -> list[dict[str, Any]]:
|
|
69
|
-
to = _timeout or timeout
|
|
70
|
-
q = (query or "").strip()
|
|
71
|
-
if not q:
|
|
72
|
-
return []
|
|
73
|
-
if re.match(r"^CVE-\d{4}-\d{4,}$", q, re.I):
|
|
74
|
-
return []
|
|
75
|
-
pkg, eco = _osv_resolve_ecosystem(q)
|
|
76
|
-
body = json.dumps({"package": {"name": pkg, "ecosystem": eco}}).encode("utf-8")
|
|
77
|
-
req = urllib.request.Request(
|
|
78
|
-
"https://api.osv.dev/v1/query", data=body,
|
|
79
|
-
headers={"Content-Type": "application/json"})
|
|
80
|
-
eng_name = spec.get("_name") or "osv"
|
|
81
|
-
try:
|
|
82
|
-
with http_open(req, timeout=to, engine=eng_name) as resp:
|
|
83
|
-
data = json.loads(resp.read().decode("utf-8"))
|
|
84
|
-
except Exception as e:
|
|
85
|
-
if isinstance(e, (json.JSONDecodeError, UnicodeDecodeError)):
|
|
86
|
-
note_failure(eng_name, "parse", "json_decode", str(e))
|
|
87
|
-
import logging
|
|
88
|
-
logging.getLogger("unified_search.engines").warning(f"OSV 失败: {e}")
|
|
89
|
-
return []
|
|
90
|
-
results = []
|
|
91
|
-
for i, v in enumerate((data.get("vulns") or [])[:n]):
|
|
92
|
-
vid = str(v.get("id") or "").strip()
|
|
93
|
-
if not vid:
|
|
94
|
-
continue
|
|
95
|
-
summary = str(v.get("summary") or "").strip()
|
|
96
|
-
affected_bits = []
|
|
97
|
-
for aff in (v.get("affected") or [])[:2]:
|
|
98
|
-
if not isinstance(aff, dict):
|
|
99
|
-
continue
|
|
100
|
-
for rng in (aff.get("ranges") or [])[:2]:
|
|
101
|
-
if not isinstance(rng, dict):
|
|
102
|
-
continue
|
|
103
|
-
events = rng.get("events") or []
|
|
104
|
-
introduced = next((e.get("introduced") for e in events
|
|
105
|
-
if isinstance(e, dict) and e.get("introduced")), "")
|
|
106
|
-
fixed = next((e.get("fixed") for e in events
|
|
107
|
-
if isinstance(e, dict) and e.get("fixed")), "")
|
|
108
|
-
if introduced or fixed:
|
|
109
|
-
affected_bits.append(
|
|
110
|
-
f"{introduced or '?'} ≤ 受影响 < {fixed or '未修复'}")
|
|
111
|
-
aliases = [a for a in (v.get("aliases") or []) if a][:3]
|
|
112
|
-
snippet = " · ".join(p for p in (
|
|
113
|
-
summary, f"生态 {eco}",
|
|
114
|
-
affected_bits[0] if affected_bits else "",
|
|
115
|
-
" ".join(aliases),
|
|
116
|
-
) if p)[:300]
|
|
117
|
-
results.append({
|
|
118
|
-
"title": f"{vid} [{eco}] {summary}"[:500] or vid,
|
|
119
|
-
"url": f"https://osv.dev/vulnerability/{vid}",
|
|
120
|
-
"snippet": snippet,
|
|
121
|
-
"source": "osv",
|
|
122
|
-
"published_at": str(v.get("published") or "")[:10],
|
|
123
|
-
"score": rank_score(0.85, i),
|
|
124
|
-
})
|
|
125
|
-
return results
|
|
126
|
-
return _engine
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# ── CISA KEV 已知在野利用漏洞(免 key,全量文件 + 本地过滤)──────────────
|
|
130
|
-
|
|
131
|
-
_KEV_CACHE: dict[str, Any] = {"at": 0.0, "items": None}
|
|
132
|
-
_KEV_TTL_S = 6 * 3600
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _kev_disk_path():
|
|
136
|
-
try:
|
|
137
|
-
from argo_paths import state_path
|
|
138
|
-
return state_path("cisa_kev_cache.json")
|
|
139
|
-
except Exception:
|
|
140
|
-
return None
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _kev_load(timeout: float) -> list[dict[str, Any]]:
|
|
144
|
-
now = time.time()
|
|
145
|
-
cached = _KEV_CACHE.get("items")
|
|
146
|
-
if cached and (now - float(_KEV_CACHE.get("at") or 0)) < _KEV_TTL_S:
|
|
147
|
-
return cached
|
|
148
|
-
disk_p = _kev_disk_path()
|
|
149
|
-
if disk_p is not None and disk_p.exists():
|
|
150
|
-
try:
|
|
151
|
-
st = disk_p.stat()
|
|
152
|
-
if (now - st.st_mtime) < _KEV_TTL_S:
|
|
153
|
-
disk_items = json.loads(disk_p.read_text(encoding="utf-8"))
|
|
154
|
-
if isinstance(disk_items, list) and disk_items:
|
|
155
|
-
_KEV_CACHE["items"] = disk_items
|
|
156
|
-
_KEV_CACHE["at"] = st.st_mtime
|
|
157
|
-
return disk_items
|
|
158
|
-
except Exception:
|
|
159
|
-
pass
|
|
160
|
-
req = urllib.request.Request(
|
|
161
|
-
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
|
|
162
|
-
headers={"User-Agent": "argo-search/1.0 (+cisa-kev)", "Accept": "application/json"})
|
|
163
|
-
try:
|
|
164
|
-
with http_open(req, timeout=timeout, engine="cisa_kev") as resp:
|
|
165
|
-
data = json.loads(resp.read().decode("utf-8", "replace"))
|
|
166
|
-
except Exception as e:
|
|
167
|
-
if isinstance(e, (json.JSONDecodeError, UnicodeDecodeError)):
|
|
168
|
-
note_failure("cisa_kev", "parse", "json_decode", str(e))
|
|
169
|
-
import logging
|
|
170
|
-
logging.getLogger("unified_search.engines").warning(f"CISA KEV 失败: {e}")
|
|
171
|
-
return cached or []
|
|
172
|
-
items = data.get("vulnerabilities") or []
|
|
173
|
-
if not isinstance(items, list):
|
|
174
|
-
return cached or []
|
|
175
|
-
_KEV_CACHE["items"] = items
|
|
176
|
-
_KEV_CACHE["at"] = now
|
|
177
|
-
if disk_p is not None and items:
|
|
178
|
-
try:
|
|
179
|
-
from argo_paths import atomic_write_json
|
|
180
|
-
atomic_write_json(disk_p, items)
|
|
181
|
-
except Exception:
|
|
182
|
-
pass
|
|
183
|
-
return items
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def _build_cisa_kev_engine(spec: dict[str, Any]) -> Any:
|
|
187
|
-
"""CISA KEV 在野利用漏洞目录(本地过滤全量目录)。"""
|
|
188
|
-
timeout = spec.get("timeout", 30)
|
|
189
|
-
|
|
190
|
-
@safe_search
|
|
191
|
-
def _engine(query: str, n: int = 5, _timeout: float | None = None,
|
|
192
|
-
**kwargs) -> list[dict[str, Any]]:
|
|
193
|
-
to = _timeout or timeout
|
|
194
|
-
q = (query or "").strip()
|
|
195
|
-
if not q:
|
|
196
|
-
return []
|
|
197
|
-
items = _kev_load(to)
|
|
198
|
-
if not items:
|
|
199
|
-
return []
|
|
200
|
-
cve_q = q.upper() if re.match(r"^CVE-\d{4}-\d{4,}$", q.strip(), re.I) else ""
|
|
201
|
-
matched = []
|
|
202
|
-
for v in items:
|
|
203
|
-
if not isinstance(v, dict):
|
|
204
|
-
continue
|
|
205
|
-
if cve_q:
|
|
206
|
-
if str(v.get("cveID") or "").strip().upper() == cve_q:
|
|
207
|
-
matched.append(v)
|
|
208
|
-
continue
|
|
209
|
-
terms = [t for t in re.split(r"[\s,]+", q.lower()) if t]
|
|
210
|
-
hay = " ".join(str(v.get(k) or "") for k in (
|
|
211
|
-
"cveID", "vendorProject", "product", "vulnerabilityName",
|
|
212
|
-
"shortDescription", "requiredAction", "cwes")).lower()
|
|
213
|
-
if all(t in hay for t in terms):
|
|
214
|
-
matched.append(v)
|
|
215
|
-
matched.sort(key=lambda x: str(x.get("dateAdded") or ""), reverse=True)
|
|
216
|
-
results = []
|
|
217
|
-
for i, v in enumerate(matched[:n]):
|
|
218
|
-
cve = str(v.get("cveID") or "").strip()
|
|
219
|
-
if not cve:
|
|
220
|
-
continue
|
|
221
|
-
vendor = str(v.get("vendorProject") or "").strip()
|
|
222
|
-
product = str(v.get("product") or "").strip()
|
|
223
|
-
name = str(v.get("vulnerabilityName") or "").strip()
|
|
224
|
-
desc = str(v.get("shortDescription") or "").strip()
|
|
225
|
-
due = str(v.get("dueDate") or "").strip()
|
|
226
|
-
ransom = str(v.get("knownRansomwareCampaignUse") or "").strip()
|
|
227
|
-
bits = [b for b in (
|
|
228
|
-
f"{vendor} {product}".strip(),
|
|
229
|
-
f"修复期限 {due}" if due else "",
|
|
230
|
-
"已知勒索软件利用" if ransom.lower() == "known" else "",
|
|
231
|
-
desc,
|
|
232
|
-
) if b]
|
|
233
|
-
results.append({
|
|
234
|
-
"title": f"{cve} — {name or product or vendor}"[:500],
|
|
235
|
-
"url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog",
|
|
236
|
-
"snippet": " · ".join(bits)[:300],
|
|
237
|
-
"source": "cisa_kev",
|
|
238
|
-
"published_at": str(v.get("dateAdded") or "")[:10],
|
|
239
|
-
"score": rank_score(0.9, i),
|
|
240
|
-
})
|
|
241
|
-
return results
|
|
242
|
-
return _engine
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
# ── 引擎声明(本地个性化配置,不提交公开仓库)────────────────────────────────
|
|
246
|
-
#
|
|
247
|
-
# 字段与原 engines/specs/osv.yaml、engines/specs/cisa_kev.yaml 逐项对齐——
|
|
248
|
-
# 那两个声明已加 _ 前缀改名停用(公开仓每次启动刷两行「未知引擎类型」warning),
|
|
249
|
-
# 元数据随之并入本文件,本机路由行为(explicit_only / family / priority / qps
|
|
250
|
-
# 等)与改名前完全一致。
|
|
251
|
-
|
|
252
|
-
_LOCAL_ENGINE_SPECS: dict[str, dict[str, Any]] = {
|
|
253
|
-
"osv": {
|
|
254
|
-
"type": "osv",
|
|
255
|
-
"enabled": True,
|
|
256
|
-
"explicit_only": True,
|
|
257
|
-
"cost_tier": "free",
|
|
258
|
-
"label": "OSV 开源漏洞库",
|
|
259
|
-
"family": "security",
|
|
260
|
-
"priority": 44,
|
|
261
|
-
"qps": 2,
|
|
262
|
-
"period": "second",
|
|
263
|
-
"cost_per_call": 0.0,
|
|
264
|
-
"cost_unit": "free",
|
|
265
|
-
"method": "POST",
|
|
266
|
-
"url": "https://api.osv.dev/v1/query",
|
|
267
|
-
"timeout": 15,
|
|
268
|
-
"canary_query": "requests",
|
|
269
|
-
"desc": "OSV.dev 开源软件漏洞库(按包查已知漏洞与影响版本区间,免 key;生态 PyPI/npm/Go/crates 等)",
|
|
270
|
-
"description": "OSV.dev 开源漏洞查询(按包名)",
|
|
271
|
-
"coverage": ["security", "package"],
|
|
272
|
-
"recommended": False,
|
|
273
|
-
"latency_ms": 1500,
|
|
274
|
-
"headers": {"Content-Type": "application/json"},
|
|
275
|
-
"quality_queries": [
|
|
276
|
-
{"query": "requests"},
|
|
277
|
-
{"query": "lodash"},
|
|
278
|
-
{"query": "log4j-core"},
|
|
279
|
-
],
|
|
280
|
-
},
|
|
281
|
-
"cisa_kev": {
|
|
282
|
-
"type": "cisa_kev",
|
|
283
|
-
"enabled": True,
|
|
284
|
-
"explicit_only": True,
|
|
285
|
-
"cost_tier": "free",
|
|
286
|
-
"label": "CISA KEV 在野利用漏洞",
|
|
287
|
-
"family": "security",
|
|
288
|
-
"priority": 47,
|
|
289
|
-
"qps": 1,
|
|
290
|
-
"period": "second",
|
|
291
|
-
"cost_per_call": 0.0,
|
|
292
|
-
"cost_unit": "free",
|
|
293
|
-
"timeout": 30,
|
|
294
|
-
"canary_query": "chrome",
|
|
295
|
-
"desc": "CISA 已知被在野利用漏洞目录(官方确认已被实际攻击利用的 CVE;全量 1.35MB 本地过滤)",
|
|
296
|
-
"description": "CISA KEV 在野利用漏洞目录",
|
|
297
|
-
"coverage": ["security"],
|
|
298
|
-
"recommended": False,
|
|
299
|
-
"latency_ms": 4000,
|
|
300
|
-
"quality_queries": [
|
|
301
|
-
{"query": "chrome"},
|
|
302
|
-
{"query": "CVE-2021-44228"},
|
|
303
|
-
],
|
|
304
|
-
},
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def register(builders: dict[str, Any], engine_specs: dict[str, Any]) -> None:
|
|
309
|
-
"""将本地引擎注册到全局 builders 和 engine_specs 中。
|
|
310
|
-
|
|
311
|
-
在 engines.py 的 _load_registry() 中、构建 registry 之前调用。
|
|
312
|
-
文件不存在时 engines.py 跳过 import,不影响公开仓库。
|
|
313
|
-
外置 spec(engines/specs/*.yaml)已有的字段保留,不抹除 explicit_only/family 等元数据。
|
|
314
|
-
"""
|
|
315
|
-
builders["osv"] = _build_osv_engine
|
|
316
|
-
builders["cisa_kev"] = _build_cisa_kev_engine
|
|
317
|
-
for name, spec in _LOCAL_ENGINE_SPECS.items():
|
|
318
|
-
base = dict(engine_specs.get(name) or {})
|
|
319
|
-
for k, v in spec.items():
|
|
320
|
-
base.setdefault(k, v)
|
|
321
|
-
engine_specs[name] = base
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# ego-search 浏览器运行时参考(低频:复杂交互才需要)
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
## 直接浏览器操作
|
|
5
|
-
|
|
6
|
-
复杂交互路径用浏览器运行时命令 `ego-browser nodejs` heredoc 直接编排(本子技能继承完整
|
|
7
|
-
运行时)。核心纪律:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
ego-browser nodejs <<'EOF'
|
|
11
|
-
// 同名任务空间跨轮次复用;同一用户目标不新建空间
|
|
12
|
-
const task = await useOrCreateTaskSpace('竞品调研')
|
|
13
|
-
await openOrReuseTab('https://example.com', { wait: true, timeout: 20 })
|
|
14
|
-
cliLog(await snapshotText()) // 语义快照,带 [ref=N, loc=..., url=...]
|
|
15
|
-
EOF
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Helpers 速查**:
|
|
19
|
-
|
|
20
|
-
- 任务空间:`useOrCreateTaskSpace` / `listTaskSpaces` / `claimTaskSpace` / `handOffTaskSpace` / `takeOverTaskSpace` / `completeTaskSpace`
|
|
21
|
-
- 导航:`openOrReuseTab` / `gotoAndWait` / `pageInfo` / `listTabs` / `switchTab`
|
|
22
|
-
- 观察:`snapshotText` / `captureScreenshot` / `drainEvents`
|
|
23
|
-
- 动作:`click` / `fillInput` / `typeText` / `pressKey` / `scrollToBottomUntil` / `hover` / `uploadFile`
|
|
24
|
-
- 等待:`wait` / `waitForElement` / `waitForNetworkIdle`
|
|
25
|
-
- 提取:`js`(页面内 JS)/ `cdp`(浏览器协议)
|
|
26
|
-
- 输出:`cliLog`(唯一输出通道;`-e` 模式下输出到 stderr)
|
|
27
|
-
|
|
28
|
-
**关键纪律**:
|
|
29
|
-
|
|
30
|
-
1. **任务空间隔离 + 登录态继承**:Agent 在独立空间操作,不抢用户标签页;登录态默认继承,可访问已登录站点。
|
|
31
|
-
2. **跨轮次复用**:Node 运行时每次 heredoc 退出即释放,后续轮次用 `useOrCreateTaskSpace(同名)` 或
|
|
32
|
-
`takeOverTaskSpace`(用户确认继续后)恢复。
|
|
33
|
-
3. **归属权**:用户接管空间时("user is controlling")是硬停——问用户并等待,不重试不抢回;
|
|
34
|
-
`handOffTaskSpace` 交还用户后,只有用户明确确认(Ask 的 Continue)才 `takeOverTaskSpace`。
|
|
35
|
-
4. **收尾**:任务完成必须 `completeTaskSpace(name, { keep: false })` 关掉空间;
|
|
36
|
-
仅当用户明确要求保留页面/需人工操作/结果无法用 URL 交付时才 `{ keep: true }`。
|
|
37
|
-
5. **`js()` 用法**:页面内逻辑包成单个 IIFE 一次返回;`js()` 返回求值结果而非 JSON 字符串,
|
|
38
|
-
不要包 `JSON.parse`;模板字符串里正则反斜杠要双写或 `String.raw`。
|
|
39
|
-
6. **输出通道**:heredoc 模式 `cliLog` 输出到 stdout;`ego-browser nodejs -e "..."` 模式输出到 **stderr**。
|
|
40
|
-
|
|
41
|
-
**三种工作流**:普通 DOM 页用语义流(`snapshotText` + `@N`/`loc=` 引用);canvas/富编辑器
|
|
42
|
-
(Google Docs/Notion/Figma 等)用视觉流(截图 + 坐标 + 键盘);需要浏览器态/紧凑数据提取用
|
|
43
|
-
直接 DOM/CDP 流(`js`/`cdp`)。
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# Install ego lite
|
|
2
|
-
|
|
3
|
-
Read this file only when ego lite isn't installed yet, or when the user asks to install ego lite. For day-to-day browser work, go back to `SKILL.md`.
|
|
4
|
-
|
|
5
|
-
The ego-browser skill depends on the ego lite browser: the `ego-browser` command is provided by the ego lite app. Once ego lite is installed and you've gone through onboarding once, the environment is ready and there are no further environment issues.
|
|
6
|
-
|
|
7
|
-
ego lite website: https://lite.ego.app/
|
|
8
|
-
|
|
9
|
-
## Install steps (macOS only)
|
|
10
|
-
|
|
11
|
-
The install script lives at `scripts/install.sh` in this skill and supports macOS only. It will:
|
|
12
|
-
|
|
13
|
-
- Download the ego lite installer (a DMG) for your CPU architecture (arm64 / x64).
|
|
14
|
-
- Install `ego lite.app` to `/Applications` (falling back to `~/Applications` when needed).
|
|
15
|
-
- Strip the quarantine attribute to keep Gatekeeper from blocking the first launch.
|
|
16
|
-
- After installing, launch the `ego lite` app.
|
|
17
|
-
|
|
18
|
-
Run the script (use the script's actual path under this skill's directory):
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
sh skills/ego-browser/scripts/install.sh
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
After installing, the script opens the ego lite app directly. If ego lite is already installed, the script skips the download and opens the app directly.
|
|
25
|
-
|
|
26
|
-
After the script opens the ego lite app, the user completes the first-run onboarding in the app:
|
|
27
|
-
|
|
28
|
-
- Choose to import data from Chrome or another browser as needed.
|
|
29
|
-
- Onboarding registers the `ego-browser` command on the PATH (usually under `~/.local/bin`).
|
|
30
|
-
|
|
31
|
-
Onboarding is a step the user completes in the GUI. After the script opens ego lite, wait for the user to confirm they've finished onboarding before continuing.
|
|
32
|
-
|
|
33
|
-
## After installing: confirm `ego-browser` is available
|
|
34
|
-
|
|
35
|
-
Once the user has finished onboarding, confirm the command is ready:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
command -v ego-browser
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
If it reports that the command isn't found, `~/.local/bin` is most likely not on the current PATH. Fix it temporarily and retry:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
export PATH="$HOME/.local/bin:$PATH"
|
|
45
|
-
command -v ego-browser
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Once the command exists, verify the runtime with a minimal heredoc:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
ego-browser nodejs <<'EOF'
|
|
52
|
-
cliLog('ego-browser ready')
|
|
53
|
-
EOF
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Printing `ego-browser ready` means the environment is ready.
|
|
57
|
-
|
|
58
|
-
## After that, return to the original task
|
|
59
|
-
|
|
60
|
-
Once the environment is ready, return to the user's original task and continue with the task space flow in `SKILL.md` — start from `useOrCreateTaskSpace(name)` and proceed as usual.
|
|
61
|
-
|
|
62
|
-
## Troubleshooting
|
|
63
|
-
|
|
64
|
-
- **Not macOS**: the script supports macOS only (`uname -s` is `Darwin`). On other platforms, have the user download and install from the ego lite website at https://lite.ego.app/.
|
|
65
|
-
- **Download failed**: the script retries 3 times automatically; if it still fails, it's usually a network issue — have the user check their network and retry.
|
|
66
|
-
- **Gatekeeper still blocks it**: the script already tries to strip quarantine; if the first launch is still blocked, have the user allow ego lite manually under System Settings → Privacy & Security.
|
|
67
|
-
- **Command still unavailable after onboarding**: confirm `~/.local/bin` is on the PATH (see above); or have the user reopen ego lite, finish onboarding, and retry.
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# 原版 ego 技能升级要点(与本子技能配套)
|
|
2
|
-
|
|
3
|
-
原版 `ego-browser` skill 仍是**通用浏览器自动化**真源;本文件只补「与登录态专业搜索完全态」对齐的用法,不替换原版 SKILL 正文。
|
|
4
|
-
|
|
5
|
-
## 与 ego-search 分工
|
|
6
|
-
|
|
7
|
-
| 场景 | 用谁 |
|
|
8
|
-
|------|------|
|
|
9
|
-
| 搜 SERP / 抓正文 / 已知 API / 登录墙取证 | **ego-search**(双运行时 auto) |
|
|
10
|
-
| 复杂点选、填表、多步、截图工作流 | **原版 ego-browser** heredoc |
|
|
11
|
-
| 无 ego App、只有用户 Chrome | **ego-search --runtime webbridge** |
|
|
12
|
-
|
|
13
|
-
## 建议启用但常被忽略的能力
|
|
14
|
-
|
|
15
|
-
在 `ego-browser nodejs` 中(打开目标站后):
|
|
16
|
-
|
|
17
|
-
```js
|
|
18
|
-
// 站点经验包(若该站有 learnings)
|
|
19
|
-
const ctx = await learnContext()
|
|
20
|
-
cliLog(JSON.stringify(ctx))
|
|
21
|
-
// 例如 google 搜索抽取(以实际 manifest 为准)
|
|
22
|
-
// await runSiteTool('google', 'search_and_extract', { query: '...' })
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
站点包通常在应用内 `learnings/`(google / x-com / github 等)。
|
|
26
|
-
|
|
27
|
-
## 输出建议(与隔离/融合对齐)
|
|
28
|
-
|
|
29
|
-
原版 heredoc 若产出可并入分析的结果,请在 `cliLog` 的 JSON 中尽量带:
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{
|
|
33
|
-
"login_state_used": true,
|
|
34
|
-
"auth_partition": "login",
|
|
35
|
-
"cache_eligible": false,
|
|
36
|
-
"search_partition": "login",
|
|
37
|
-
"merge_with_public_ok": true,
|
|
38
|
-
"runtime": "ego",
|
|
39
|
-
"source": "ego-browser"
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
勿把登录态 body 写入 argo 公共 `SearchCache`。
|
|
44
|
-
|
|
45
|
-
## 探测
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
python3 sub-skills/ego-search/scripts/ego_search.py status
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
`runtimes.preferred` / `login_search_ready` 表示登录态专业搜索是否可跑。
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""ego-search query 归一化前处理单测(保持一致 argo 核心 query_enhance)
|
|
3
|
-
|
|
4
|
-
设计约束:
|
|
5
|
-
- 只做词形规范化(全角→半角、拆斜杠、压空格)
|
|
6
|
-
- 必须保留平台结构化语法(from:/site:/filter: …)——ego 是站内通道,剥语法会丢能力
|
|
7
|
-
- 核心 query_enhance 不可用时降级为原 query(不破坏)
|
|
8
|
-
|
|
9
|
-
运行:
|
|
10
|
-
cd ~/.agents/skills/argo
|
|
11
|
-
python3 -m pytest sub-skills/ego-search/tests/test_query_normalize.py -q
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import sys
|
|
17
|
-
import unittest
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
SCRIPTS = Path(__file__).resolve().parent.parent / "scripts"
|
|
21
|
-
sys.path.insert(0, str(SCRIPTS))
|
|
22
|
-
|
|
23
|
-
import ego_search as es # noqa: E402
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class TestQueryNormalize(unittest.TestCase):
|
|
27
|
-
def test_full_width_to_half(self):
|
|
28
|
-
self.assertEqual(es._normalized_query("“GPT-5”,价格"), '"GPT-5",价格')
|
|
29
|
-
|
|
30
|
-
def test_slash_split(self):
|
|
31
|
-
self.assertEqual(es._normalized_query("LongCat-2.0/1.6 万亿"), "LongCat-2.0 1.6 万亿")
|
|
32
|
-
|
|
33
|
-
def test_keep_platform_syntax(self):
|
|
34
|
-
self.assertEqual(
|
|
35
|
-
es._normalized_query('from:OpenAI "GPT-5" filter:images'),
|
|
36
|
-
'from:OpenAI "GPT-5" filter:images',
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
def test_plain_unchanged(self):
|
|
40
|
-
self.assertEqual(es._normalized_query("openai api"), "openai api")
|
|
41
|
-
|
|
42
|
-
def test_plain_slash_not_split(self):
|
|
43
|
-
# 核心规则:只拆「点号版本」斜杠(LongCat-2.0/1.6),普通斜杠可能是
|
|
44
|
-
# 路径/日期/分数,必须原样保留。本测试曾检查 "a/b c" → "a b c",
|
|
45
|
-
# 是旧实现(全斜杠拆分)的遗留,与核心 normalize_query 矛盾,
|
|
46
|
-
# 且子技能测试不在主检查收集范围,红了一年没人看见。
|
|
47
|
-
self.assertEqual(es._normalized_query("a/b c"), "a/b c")
|
|
48
|
-
|
|
49
|
-
def test_import_fallback(self):
|
|
50
|
-
# normalize_query 缺失时应回退原 query(真正模拟核心不可用)
|
|
51
|
-
orig = es.normalize_query
|
|
52
|
-
try:
|
|
53
|
-
es.normalize_query = None
|
|
54
|
-
self.assertEqual(es._normalized_query("a/b c"), "a/b c")
|
|
55
|
-
self.assertEqual(es._normalized_query("LongCat-2.0/1.6"), "LongCat-2.0/1.6")
|
|
56
|
-
finally:
|
|
57
|
-
es.normalize_query = orig
|
|
58
|
-
|
|
59
|
-
def test_core_scripts_path_is_argo_root(self):
|
|
60
|
-
# 回归锚:相对路径必须指向 argo 根/scripts(parents[3])。
|
|
61
|
-
# 曾用 parents[2] 指向 sub-skills/scripts(不存在)→ normalize_query
|
|
62
|
-
# 静默降级为 None,子技能归一化整条失效。
|
|
63
|
-
self.assertTrue(es._CORE_SCRIPTS.exists(), es._CORE_SCRIPTS)
|
|
64
|
-
self.assertEqual(es._CORE_SCRIPTS.name, "scripts")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if __name__ == "__main__":
|
|
68
|
-
unittest.main()
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// challenge_like.js — 金标考卷 #1:挑战页形态的 JS(离线可复跑)
|
|
2
|
-
// 形态参照真实反爬挑战脚本的三段式:环境探测 → 字符串运算 → 发通行证。
|
|
3
|
-
// 只用 v0 垫片保证过的面:navigator/screen/btoa/document.cookie/定时器。
|
|
4
|
-
(function () {
|
|
5
|
-
var probe = [
|
|
6
|
-
navigator.userAgent.length,
|
|
7
|
-
navigator.platform,
|
|
8
|
-
navigator.language,
|
|
9
|
-
screen.width * screen.height,
|
|
10
|
-
navigator.hardwareConcurrency,
|
|
11
|
-
Number(!navigator.webdriver),
|
|
12
|
-
].join("|");
|
|
13
|
-
|
|
14
|
-
// 字符串运算(模拟混淆层的算子链)
|
|
15
|
-
var salt = "";
|
|
16
|
-
for (var i = 0; i < probe.length; i += 7) salt += String.fromCharCode(probe.charCodeAt(i) ^ 0x1f);
|
|
17
|
-
var token = btoa(salt + "|" + probe.length);
|
|
18
|
-
|
|
19
|
-
// 定时器:真浏览器里等 3 秒才发 cookie;逻辑时间下瞬间完成
|
|
20
|
-
var issued = false;
|
|
21
|
-
setTimeout(function () {
|
|
22
|
-
document.cookie = "__jsl_clearance=" + token + "; path=/";
|
|
23
|
-
issued = true;
|
|
24
|
-
}, 3000);
|
|
25
|
-
|
|
26
|
-
return { probe: probe, token: token, issued: issued };
|
|
27
|
-
})();
|