@shirlytaylor73/smart-search 0.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/README.zh-CN.md +180 -0
- package/npm/bin/smart-search.js +68 -0
- package/npm/scripts/postinstall.js +87 -0
- package/npm/scripts/resolve-prerelease-version.js +108 -0
- package/npm/scripts/set-package-version.js +35 -0
- package/npm/scripts/sync-python-version.js +22 -0
- package/npm/scripts/test-wrapper-repair.js +137 -0
- package/npm/scripts/test.js +76 -0
- package/package.json +42 -0
- package/pyproject.toml +36 -0
- package/skills/smart-search-cli/SKILL.md +41 -0
- package/skills/smart-search-cli/agents/openai.yaml +3 -0
- package/skills/smart-search-cli/references/cli-contract.md +7 -0
- package/skills/smart-search-cli/references/cli-core.md +5 -0
- package/skills/smart-search-cli/references/command-patterns.md +12 -0
- package/skills/smart-search-cli/references/provider-routing.md +3 -0
- package/skills/smart-search-cli/references/regression-release.md +28 -0
- package/skills/smart-search-cli/references/setup-config.md +3 -0
- package/src/smart_search/__init__.py +1 -0
- package/src/smart_search/assets/skills/smart-search-cli/SKILL.md +41 -0
- package/src/smart_search/assets/skills/smart-search-cli/agents/openai.yaml +3 -0
- package/src/smart_search/assets/skills/smart-search-cli/references/cli-contract.md +7 -0
- package/src/smart_search/assets/skills/smart-search-cli/references/cli-core.md +5 -0
- package/src/smart_search/assets/skills/smart-search-cli/references/command-patterns.md +12 -0
- package/src/smart_search/assets/skills/smart-search-cli/references/provider-routing.md +3 -0
- package/src/smart_search/assets/skills/smart-search-cli/references/regression-release.md +28 -0
- package/src/smart_search/assets/skills/smart-search-cli/references/setup-config.md +3 -0
- package/src/smart_search/cli.py +3118 -0
- package/src/smart_search/config.py +809 -0
- package/src/smart_search/embedding_presets.py +40 -0
- package/src/smart_search/intent_router.py +757 -0
- package/src/smart_search/logger.py +43 -0
- package/src/smart_search/providers/__init__.py +20 -0
- package/src/smart_search/providers/base.py +41 -0
- package/src/smart_search/providers/context7.py +141 -0
- package/src/smart_search/providers/exa.py +206 -0
- package/src/smart_search/providers/jina.py +136 -0
- package/src/smart_search/providers/openai_compatible.py +541 -0
- package/src/smart_search/providers/xai_responses.py +117 -0
- package/src/smart_search/providers/zhipu.py +143 -0
- package/src/smart_search/providers/zhipu_mcp.py +230 -0
- package/src/smart_search/service.py +3445 -0
- package/src/smart_search/skill_installer.py +342 -0
- package/src/smart_search/sources.py +429 -0
- package/src/smart_search/utils.py +220 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 GuDaStudio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Smart Search
|
|
2
|
+
|
|
3
|
+
`smart-search` is a CLI-first web and technical-document retrieval tool for AI agents and terminal users. Agents choose task operations; provider selection, credentials, feature matching, timeouts, and fallback remain configuration concerns.
|
|
4
|
+
|
|
5
|
+
## Install and setup
|
|
6
|
+
|
|
7
|
+
`smart-search` is implemented in Python. The npm package is an npm wrapper
|
|
8
|
+
that creates an isolated Python runtime and installs the bundled Python CLI.
|
|
9
|
+
|
|
10
|
+
During the 0.2.0 preview, install the fork-owned package from npm dist-tag
|
|
11
|
+
`next`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @shirlytaylor73/smart-search@next
|
|
15
|
+
smart-search setup
|
|
16
|
+
smart-search doctor --format json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
After `v0.2.0` is published, stable installations can use
|
|
20
|
+
`@shirlytaylor73/smart-search@latest`.
|
|
21
|
+
|
|
22
|
+
Python source installation:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv tool install --editable .
|
|
26
|
+
# or
|
|
27
|
+
pip install -e .
|
|
28
|
+
smart-search --version
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Four query capabilities
|
|
32
|
+
|
|
33
|
+
### search
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
smart-search search answer "latest AI policy updates" --format json
|
|
37
|
+
smart-search search sources "agentic search papers" --limit 5 --mode semantic --include-highlights --format json
|
|
38
|
+
smart-search search similar https://example.com/article --limit 5 --format json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`search sources` supports provider-independent limit, semantic/keyword/auto mode, publication date, include/exclude domains, category, text, and highlights. Providers that do not support required features are excluded automatically.
|
|
42
|
+
|
|
43
|
+
### docs
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
smart-search docs resolve react hooks --format json
|
|
47
|
+
smart-search docs search "useEffect cleanup" --format json
|
|
48
|
+
smart-search docs search "recent important PRs" --source owner/repo --format json
|
|
49
|
+
smart-search docs tree owner/repo --path src --ref main --format json
|
|
50
|
+
smart-search docs read owner/repo README.md --ref main --format content
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### fetch
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
smart-search fetch content https://example.com/article --format content
|
|
57
|
+
smart-search fetch extract https://example.com/product --schema '{"type":"object"}' --format json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `fetch content` returns readable text or Markdown.
|
|
61
|
+
- `fetch extract` returns structured `data` and optional `raw_evidence`; it never substitutes ordinary Markdown for structured output.
|
|
62
|
+
|
|
63
|
+
### map
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
smart-search map site https://docs.example.com \
|
|
67
|
+
--instructions "find authentication and rate-limit pages" \
|
|
68
|
+
--max-depth 1 \
|
|
69
|
+
--max-breadth 20 \
|
|
70
|
+
--limit 50 \
|
|
71
|
+
--timeout 150 \
|
|
72
|
+
--format json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`map site` discovers URLs, paths, and link structure within a site. It is not page extraction or repository structure; use `fetch content` and `docs tree` for those tasks.
|
|
76
|
+
|
|
77
|
+
## Operation/provider mapping
|
|
78
|
+
|
|
79
|
+
| Operation | Internal candidates |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `search.answer` | xAI Responses, OpenAI-compatible |
|
|
82
|
+
| `search.sources` | Exa, Zhipu Web Search, Zhipu MCP, Tavily, Firecrawl |
|
|
83
|
+
| `search.similar` | Exa |
|
|
84
|
+
| `docs.resolve` | Context7 |
|
|
85
|
+
| `docs.search` | Context7, Exa, Zhipu MCP zread |
|
|
86
|
+
| `docs.tree` | Zhipu MCP zread |
|
|
87
|
+
| `docs.read` | Zhipu MCP zread |
|
|
88
|
+
| `fetch.content` | Tavily, Jina, Zhipu MCP Reader, Firecrawl |
|
|
89
|
+
| `fetch.extract` | Firecrawl structured extraction |
|
|
90
|
+
| `map.site` | Tavily |
|
|
91
|
+
|
|
92
|
+
Fallback stays within the same operation. `docs.tree` never falls back to Context7, and `fetch.extract` never falls back to readable Markdown.
|
|
93
|
+
|
|
94
|
+
Maintainers can configure per-operation ordering, disabled providers, timeout, and fallback through the JSON object in `SMART_SEARCH_OPERATION_CONFIG`. Agents do not manage this setting.
|
|
95
|
+
|
|
96
|
+
## Configuration and diagnostics
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
smart-search setup
|
|
100
|
+
smart-search config path
|
|
101
|
+
smart-search config list
|
|
102
|
+
smart-search config set KEY VALUE
|
|
103
|
+
smart-search config unset KEY
|
|
104
|
+
smart-search doctor --format markdown
|
|
105
|
+
|
|
106
|
+
smart-search diagnose search sources
|
|
107
|
+
smart-search diagnose docs tree
|
|
108
|
+
smart-search diagnose fetch extract
|
|
109
|
+
smart-search diagnose map site
|
|
110
|
+
smart-search diagnose provider openai-compatible
|
|
111
|
+
smart-search diagnose route "React API docs"
|
|
112
|
+
smart-search diagnose route-calibrate
|
|
113
|
+
smart-search diagnose smoke --mode mock
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Common credentials include `XAI_API_KEY`, `OPENAI_COMPATIBLE_API_URL`, `OPENAI_COMPATIBLE_API_KEY`, `EXA_API_KEY`, `CONTEXT7_API_KEY`, `ZHIPU_API_KEY`, `ZHIPU_MCP_API_KEY`, `JINA_API_KEY`, `TAVILY_API_KEY`, and `FIRECRAWL_API_KEY`. `config list` masks secrets.
|
|
117
|
+
|
|
118
|
+
## Output and exit codes
|
|
119
|
+
|
|
120
|
+
Query operations support:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
--format json|markdown|content
|
|
124
|
+
--output PATH
|
|
125
|
+
--debug
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The public JSON envelope contains `ok`, `capability`, `operation`, `content`, `sources`, and `elapsed_ms`. Exit codes: `0` success, `2` parameter error, `3` configuration error, `4` network/capability error, and `5` runtime error.
|
|
129
|
+
|
|
130
|
+
## Migration
|
|
131
|
+
|
|
132
|
+
### npm package ownership
|
|
133
|
+
|
|
134
|
+
This repository publishes `@shirlytaylor73/smart-search`. The upstream
|
|
135
|
+
`@konbakuyomu/smart-search` package is a separate package and does not
|
|
136
|
+
receive updates from this fork. Both packages expose the same
|
|
137
|
+
`smart-search` executable, so uninstall the upstream package first:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm uninstall -g @konbakuyomu/smart-search
|
|
141
|
+
npm install -g @shirlytaylor73/smart-search@next
|
|
142
|
+
smart-search -v
|
|
143
|
+
smart-search diagnose smoke --mode mock
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The configuration directory does not change, so existing credentials and
|
|
147
|
+
`SMART_SEARCH_OPERATION_CONFIG` values remain available.
|
|
148
|
+
|
|
149
|
+
### CLI contract
|
|
150
|
+
|
|
151
|
+
Legacy provider commands are hidden during the compatibility window and map to operations, for example `exa-search` → `search sources`, `exa-similar` → `search similar`, `context7-library` → `docs resolve`, legacy `fetch` → `fetch content`, and legacy `map` → `map site`.
|
|
152
|
+
|
|
153
|
+
The experimental vertical provider and Deep Research CLI were removed. The calling agent owns research decomposition, evidence comparison, and final writing; paper and vertical retrieval will be extended separately through paper-search.
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
smart-search dev regression
|
|
159
|
+
python -m compileall -q src tests
|
|
160
|
+
python -m pytest tests -q
|
|
161
|
+
npm test
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Release lanes
|
|
165
|
+
|
|
166
|
+
Beta releases are started manually through `workflow_dispatch` with an
|
|
167
|
+
explicit `target_ref`, version such as `0.2.0-beta.1`, and npm dist-tag
|
|
168
|
+
`next`. Stable releases use a matching tag such as `v0.2.0` and publish
|
|
169
|
+
to npm dist-tag `latest`. GitHub Actions reads the repository secret
|
|
170
|
+
`NPM_TOKEN` and publishes with provenance.
|
|
171
|
+
|
|
172
|
+
npm versions are immutable and cannot be renamed in place. Failed beta builds
|
|
173
|
+
must be superseded by a new `0.2.0-beta.N` version.
|
|
174
|
+
|
|
175
|
+
Release closeout checklist:
|
|
176
|
+
|
|
177
|
+
1. Compare npm versions/dist-tags and GitHub releases before publishing.
|
|
178
|
+
2. If Actions cannot create a prerelease, publish with
|
|
179
|
+
`create_github_release=false`, then run
|
|
180
|
+
`gh release create vX.Y.Z-beta.N --target <commit> --prerelease --latest=false`.
|
|
181
|
+
3. Treat npm `E409` during parallel backfills as a registry concurrency
|
|
182
|
+
failure and retry serially after checking whether the version exists.
|
|
183
|
+
4. Run a machine-readable gap check between expected npm beta versions and
|
|
184
|
+
GitHub prereleases.
|
|
185
|
+
5. Install the selected build with
|
|
186
|
+
`mise use -g "npm:@shirlytaylor73/smart-search@0.2.0-beta.1" -y --pin`,
|
|
187
|
+
then verify version, regression, smoke, and a non-ASCII JSON pipe through
|
|
188
|
+
PowerShell `ConvertFrom-Json`.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Smart Search
|
|
2
|
+
|
|
3
|
+
`smart-search` 是面向 AI Agent 和命令行用户的 CLI-first 网络与技术文档检索工具。Agent 只选择任务 operation;provider、凭据、优先级、feature 匹配、超时和 fallback 由配置与内部服务层管理。
|
|
4
|
+
|
|
5
|
+
## 安装与初始化
|
|
6
|
+
|
|
7
|
+
`smart-search` 的实际实现是 Python CLI。npm 包只是 npm 包装器:它会创建
|
|
8
|
+
隔离的 Python runtime,并安装随 npm 包发布的 Python 源码。
|
|
9
|
+
|
|
10
|
+
0.2.0 预览阶段从 npm `next` 安装当前 fork:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @shirlytaylor73/smart-search@next
|
|
14
|
+
smart-search setup
|
|
15
|
+
smart-search doctor --format json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`v0.2.0` 发布后,稳定版使用
|
|
19
|
+
`@shirlytaylor73/smart-search@latest`。
|
|
20
|
+
|
|
21
|
+
也可以直接从源码安装 Python 包:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv tool install --editable .
|
|
25
|
+
# 或
|
|
26
|
+
pip install -e .
|
|
27
|
+
smart-search --version
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 四大查询能力
|
|
31
|
+
|
|
32
|
+
### search:网络回答与来源发现
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
smart-search search answer "今天有哪些 AI 政策更新" --format json
|
|
36
|
+
smart-search search sources "agentic search papers" --limit 5 --mode semantic --include-highlights --format json
|
|
37
|
+
smart-search search similar https://example.com/article --limit 5 --format json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`search sources` 支持 provider 无关的 `--limit`、`--mode semantic|keyword|auto`、`--start-published-date`、`--include-domains`、`--exclude-domains`、`--category`、`--include-text`、`--include-highlights`。不支持必需 feature 的 provider 会被自动排除。
|
|
41
|
+
|
|
42
|
+
### docs:技术文档与代码仓库
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
smart-search docs resolve react hooks --format json
|
|
46
|
+
smart-search docs search "useEffect cleanup" --format json
|
|
47
|
+
smart-search docs search "最近的重要 PR" --source owner/repo --format json
|
|
48
|
+
smart-search docs tree owner/repo --path src --ref main --format json
|
|
49
|
+
smart-search docs read owner/repo README.md --ref main --format content
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### fetch:已知 URL 内容
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
smart-search fetch content https://example.com/article --format content
|
|
56
|
+
smart-search fetch extract https://example.com/product --schema '{"type":"object"}' --format json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `fetch content` 返回适合阅读、总结和引用的正文或 Markdown。
|
|
60
|
+
- `fetch extract` 返回结构化 `data` 和可选 `raw_evidence`,不会用普通 Markdown 冒充结构化结果。
|
|
61
|
+
|
|
62
|
+
### map:站点结构探索
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
smart-search map site https://docs.example.com \
|
|
66
|
+
--instructions "查找认证和限流页面" \
|
|
67
|
+
--max-depth 1 \
|
|
68
|
+
--max-breadth 20 \
|
|
69
|
+
--limit 50 \
|
|
70
|
+
--timeout 150 \
|
|
71
|
+
--format json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`map site` 发现站内 URL、路径和链接结构。它不是单页内容提取,也不是代码仓库目录读取;后两者分别使用 `fetch content` 和 `docs tree`。
|
|
75
|
+
|
|
76
|
+
## Operation 与 Provider
|
|
77
|
+
|
|
78
|
+
| Operation | 内部候选 |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `search.answer` | xAI Responses、OpenAI-compatible |
|
|
81
|
+
| `search.sources` | Exa、智谱 Web Search、智谱 MCP、Tavily、Firecrawl |
|
|
82
|
+
| `search.similar` | Exa |
|
|
83
|
+
| `docs.resolve` | Context7 |
|
|
84
|
+
| `docs.search` | Context7、Exa、智谱 MCP zread |
|
|
85
|
+
| `docs.tree` | 智谱 MCP zread |
|
|
86
|
+
| `docs.read` | 智谱 MCP zread |
|
|
87
|
+
| `fetch.content` | Tavily、Jina、智谱 MCP Reader、Firecrawl |
|
|
88
|
+
| `fetch.extract` | Firecrawl 结构化抽取 |
|
|
89
|
+
| `map.site` | Tavily |
|
|
90
|
+
|
|
91
|
+
Fallback 只在同一个 operation 内发生。例如 `docs.tree` 不会 fallback 到 Context7,`fetch.extract` 不会 fallback 到普通正文。
|
|
92
|
+
|
|
93
|
+
可用 `SMART_SEARCH_OPERATION_CONFIG` 配置 operation 顺序、禁用项、超时和 fallback,值为 JSON object;普通 Agent 无需读取或修改它。
|
|
94
|
+
|
|
95
|
+
## 配置与排查
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
smart-search setup
|
|
99
|
+
smart-search config path
|
|
100
|
+
smart-search config list
|
|
101
|
+
smart-search config set KEY VALUE
|
|
102
|
+
smart-search config unset KEY
|
|
103
|
+
smart-search doctor --format markdown
|
|
104
|
+
|
|
105
|
+
smart-search diagnose search sources
|
|
106
|
+
smart-search diagnose docs tree
|
|
107
|
+
smart-search diagnose fetch extract
|
|
108
|
+
smart-search diagnose map site
|
|
109
|
+
smart-search diagnose provider openai-compatible
|
|
110
|
+
smart-search diagnose route "React API docs"
|
|
111
|
+
smart-search diagnose route-calibrate
|
|
112
|
+
smart-search diagnose smoke --mode mock
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
主要凭据包括 `XAI_API_KEY`、`OPENAI_COMPATIBLE_API_URL`、`OPENAI_COMPATIBLE_API_KEY`、`EXA_API_KEY`、`CONTEXT7_API_KEY`、`ZHIPU_API_KEY`、`ZHIPU_MCP_API_KEY`、`JINA_API_KEY`、`TAVILY_API_KEY`、`FIRECRAWL_API_KEY`。`config list` 默认脱敏。
|
|
116
|
+
|
|
117
|
+
## 输出与退出码
|
|
118
|
+
|
|
119
|
+
所有查询 operation 支持:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
--format json|markdown|content
|
|
123
|
+
--output PATH
|
|
124
|
+
--debug
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
公共 JSON 包含 `ok`、`capability`、`operation`、`content`、`sources`、`elapsed_ms`。退出码:`0` 成功、`2` 参数错误、`3` 配置错误、`4` 网络/能力错误、`5` 运行时错误。
|
|
128
|
+
|
|
129
|
+
## 迁移
|
|
130
|
+
|
|
131
|
+
### npm 包归属
|
|
132
|
+
|
|
133
|
+
当前仓库发布 `@shirlytaylor73/smart-search`。上游
|
|
134
|
+
`@konbakuyomu/smart-search` 是另一个独立 npm 包,不会收到当前 fork 的
|
|
135
|
+
更新。两个包都会安装同名 `smart-search` 命令,因此必须先卸载旧包:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm uninstall -g @konbakuyomu/smart-search
|
|
139
|
+
npm install -g @shirlytaylor73/smart-search@next
|
|
140
|
+
smart-search -v
|
|
141
|
+
smart-search diagnose smoke --mode mock
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
配置目录不会变化,现有 API key 和 `SMART_SEARCH_OPERATION_CONFIG` 无需迁移。
|
|
145
|
+
|
|
146
|
+
### CLI 契约
|
|
147
|
+
|
|
148
|
+
旧 provider 命令在兼容期内隐藏并映射到新 operation,例如 `exa-search` → `search sources`、`exa-similar` → `search similar`、`context7-library` → `docs resolve`、旧 `fetch` → `fetch content`、旧 `map` → `map site`。
|
|
149
|
+
|
|
150
|
+
实验性垂直 provider 与 Deep Research CLI 已移除。研究分解、证据对比和最终写作由上层 Agent 负责;论文与垂直检索后续通过独立 paper-search 扩展。
|
|
151
|
+
|
|
152
|
+
## 开发验证
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
smart-search dev regression
|
|
156
|
+
python -m compileall -q src tests
|
|
157
|
+
python -m pytest tests -q
|
|
158
|
+
npm test
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 发布通道
|
|
162
|
+
|
|
163
|
+
beta 通过 `workflow_dispatch` 手动发布,需要指定 `target_ref`、类似
|
|
164
|
+
`0.2.0-beta.1` 的准确版本和 npm `next`。稳定版通过类似 `v0.2.0`
|
|
165
|
+
的 Git tag 发布到 npm `latest`。GitHub Actions 使用仓库 Secret
|
|
166
|
+
`NPM_TOKEN` 完成 npm 认证,并保留 provenance。
|
|
167
|
+
|
|
168
|
+
npm 版本不可变,已发布版本不能原地改名,只能用新的 beta 版本替代。
|
|
169
|
+
|
|
170
|
+
发布收尾检查:
|
|
171
|
+
|
|
172
|
+
1. 先运行 `npm view @shirlytaylor73/smart-search` 并通过
|
|
173
|
+
`gh release list --repo ShirlyTaylor73/smartsearch --limit 100` 核对现状。
|
|
174
|
+
2. 遇到 npm `E409` 时,先确认版本是否已经存在,再串行重试。
|
|
175
|
+
3. 兼容窗口内可运行旧入口 `smart-search regression` 和
|
|
176
|
+
`smart-search smoke --mock --format json` 验证迁移提示;新版入口分别是
|
|
177
|
+
`smart-search dev regression` 和
|
|
178
|
+
`smart-search diagnose smoke --mode mock --format json`。
|
|
179
|
+
4. Windows 包装层额外执行非 ASCII JSON 管道,并用
|
|
180
|
+
PowerShell `ConvertFrom-Json` 验证输出。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(__dirname, "..", "..");
|
|
8
|
+
const callerCwd = process.env.INIT_CWD || process.cwd();
|
|
9
|
+
const venvDir = path.join(packageRoot, ".smart-search-python");
|
|
10
|
+
const pythonPath =
|
|
11
|
+
process.platform === "win32"
|
|
12
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
13
|
+
: path.join(venvDir, "bin", "python");
|
|
14
|
+
|
|
15
|
+
function printReinstallHint() {
|
|
16
|
+
console.error("Repair it by reinstalling the package:");
|
|
17
|
+
console.error(" npm install -g @shirlytaylor73/smart-search");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(pythonPath)) {
|
|
21
|
+
const postinstall = path.join(packageRoot, "npm", "scripts", "postinstall.js");
|
|
22
|
+
console.error("smart-search Python runtime is missing; attempting repair...");
|
|
23
|
+
const repaired = spawnSync(process.execPath, [postinstall], {
|
|
24
|
+
cwd: packageRoot,
|
|
25
|
+
stdio: "inherit",
|
|
26
|
+
windowsHide: true
|
|
27
|
+
});
|
|
28
|
+
if (repaired.error) {
|
|
29
|
+
console.error(`smart-search runtime repair failed: ${repaired.error.message}`);
|
|
30
|
+
printReinstallHint();
|
|
31
|
+
process.exit(5);
|
|
32
|
+
}
|
|
33
|
+
if (repaired.status !== 0 || !fs.existsSync(pythonPath)) {
|
|
34
|
+
console.error("smart-search npm wrapper could not find its Python runtime.");
|
|
35
|
+
console.error(`Expected: ${pythonPath}`);
|
|
36
|
+
printReinstallHint();
|
|
37
|
+
process.exit(repaired.status || 5);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const child = spawn(
|
|
42
|
+
pythonPath,
|
|
43
|
+
["-m", "smart_search.cli", ...process.argv.slice(2)],
|
|
44
|
+
{
|
|
45
|
+
cwd: callerCwd,
|
|
46
|
+
stdio: "inherit",
|
|
47
|
+
env: {
|
|
48
|
+
...process.env,
|
|
49
|
+
SMART_SEARCH_PACKAGE_ROOT: packageRoot,
|
|
50
|
+
PYTHONIOENCODING: process.env.PYTHONIOENCODING || "utf-8",
|
|
51
|
+
PYTHONUTF8: process.env.PYTHONUTF8 || "1"
|
|
52
|
+
},
|
|
53
|
+
windowsHide: true
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
child.on("error", (error) => {
|
|
58
|
+
console.error(`Failed to start smart-search: ${error.message}`);
|
|
59
|
+
process.exit(5);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
child.on("close", (code, signal) => {
|
|
63
|
+
if (signal) {
|
|
64
|
+
process.kill(process.pid, signal);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
process.exit(code ?? 5);
|
|
68
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const { spawnSync } = require("node:child_process");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const packageRoot = path.resolve(__dirname, "..", "..");
|
|
6
|
+
const venvDir = path.join(packageRoot, ".smart-search-python");
|
|
7
|
+
|
|
8
|
+
function run(command, args, options = {}) {
|
|
9
|
+
const result = spawnSync(command, args, {
|
|
10
|
+
cwd: packageRoot,
|
|
11
|
+
stdio: options.stdio || "inherit",
|
|
12
|
+
encoding: "utf8",
|
|
13
|
+
windowsHide: true
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (result.error) {
|
|
17
|
+
return { ok: false, error: result.error };
|
|
18
|
+
}
|
|
19
|
+
return { ok: result.status === 0, status: result.status, stdout: result.stdout || "" };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function pythonCandidates() {
|
|
23
|
+
if (process.platform === "win32") {
|
|
24
|
+
return [
|
|
25
|
+
{ command: "py", args: ["-3"] },
|
|
26
|
+
{ command: "python", args: [] },
|
|
27
|
+
{ command: "python3", args: [] }
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
return [
|
|
31
|
+
{ command: "python3", args: [] },
|
|
32
|
+
{ command: "python", args: [] }
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function findPython() {
|
|
37
|
+
const probe = [
|
|
38
|
+
"-c",
|
|
39
|
+
"import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)"
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
for (const candidate of pythonCandidates()) {
|
|
43
|
+
const result = run(candidate.command, [...candidate.args, ...probe], { stdio: "pipe" });
|
|
44
|
+
if (result.ok) {
|
|
45
|
+
return candidate;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function venvPython() {
|
|
52
|
+
return process.platform === "win32"
|
|
53
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
54
|
+
: path.join(venvDir, "bin", "python");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const python = findPython();
|
|
58
|
+
if (!python) {
|
|
59
|
+
console.error("smart-search requires Python 3.10 or newer.");
|
|
60
|
+
console.error("Install Python, then run: npm install -g @shirlytaylor73/smart-search@latest");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!fs.existsSync(venvPython())) {
|
|
65
|
+
console.log("Creating smart-search Python runtime...");
|
|
66
|
+
const created = run(python.command, [...python.args, "-m", "venv", venvDir]);
|
|
67
|
+
if (!created.ok) {
|
|
68
|
+
console.error("Failed to create the smart-search Python virtual environment.");
|
|
69
|
+
process.exit(created.status || 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const py = venvPython();
|
|
74
|
+
|
|
75
|
+
console.log("Installing smart-search Python package...");
|
|
76
|
+
const install = run(py, [
|
|
77
|
+
"-m",
|
|
78
|
+
"pip",
|
|
79
|
+
"install",
|
|
80
|
+
"--disable-pip-version-check",
|
|
81
|
+
packageRoot
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
if (!install.ok) {
|
|
85
|
+
console.error("Failed to install the bundled smart-search Python package.");
|
|
86
|
+
process.exit(install.status || 1);
|
|
87
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { execFileSync } = require("node:child_process");
|
|
2
|
+
|
|
3
|
+
function parseArgs(argv) {
|
|
4
|
+
const args = {
|
|
5
|
+
prereleaseId: "beta",
|
|
6
|
+
versionsJson: ""
|
|
7
|
+
};
|
|
8
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
9
|
+
const arg = argv[index];
|
|
10
|
+
const next = argv[index + 1];
|
|
11
|
+
if (arg === "--package") {
|
|
12
|
+
args.packageName = next;
|
|
13
|
+
index += 1;
|
|
14
|
+
} else if (arg === "--base") {
|
|
15
|
+
args.baseVersion = next;
|
|
16
|
+
index += 1;
|
|
17
|
+
} else if (arg === "--id") {
|
|
18
|
+
args.prereleaseId = next;
|
|
19
|
+
index += 1;
|
|
20
|
+
} else if (arg === "--versions-json") {
|
|
21
|
+
args.versionsJson = next;
|
|
22
|
+
index += 1;
|
|
23
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
24
|
+
printUsage(0);
|
|
25
|
+
} else {
|
|
26
|
+
console.error(`Unknown argument: ${arg}`);
|
|
27
|
+
printUsage(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return args;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function printUsage(exitCode) {
|
|
34
|
+
console.error(
|
|
35
|
+
[
|
|
36
|
+
"Usage: node npm/scripts/resolve-prerelease-version.js --package <name> --base <version> [--id beta]",
|
|
37
|
+
"",
|
|
38
|
+
"Options:",
|
|
39
|
+
" --versions-json JSON Use an explicit version list instead of querying npm."
|
|
40
|
+
].join("\n")
|
|
41
|
+
);
|
|
42
|
+
process.exit(exitCode);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function escapeRegExp(value) {
|
|
46
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readPublishedVersions(packageName, versionsJson) {
|
|
50
|
+
if (versionsJson) {
|
|
51
|
+
return JSON.parse(versionsJson);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const output = execFileSync("npm", ["view", packageName, "versions", "--json"], {
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
57
|
+
});
|
|
58
|
+
return JSON.parse(output);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const stderr = String(error.stderr || "");
|
|
61
|
+
if (stderr.includes("E404") || stderr.includes("404 Not Found")) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolvePrereleaseVersion(versions, baseVersion, prereleaseId) {
|
|
69
|
+
const normalizedVersions = Array.isArray(versions) ? versions.filter((version) => typeof version === "string") : [];
|
|
70
|
+
const escapedBase = escapeRegExp(baseVersion);
|
|
71
|
+
const escapedId = escapeRegExp(prereleaseId);
|
|
72
|
+
const prereleasePattern = new RegExp(`^${escapedBase}-${escapedId}\\.(\\d+)$`);
|
|
73
|
+
const legacyDevPattern = new RegExp(`^${escapedBase}-dev\\..+$`);
|
|
74
|
+
const existingPrereleaseNumbers = [];
|
|
75
|
+
let legacyDevCount = 0;
|
|
76
|
+
|
|
77
|
+
for (const version of normalizedVersions) {
|
|
78
|
+
const prereleaseMatch = prereleasePattern.exec(version);
|
|
79
|
+
if (prereleaseMatch) {
|
|
80
|
+
existingPrereleaseNumbers.push(Number(prereleaseMatch[1]));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (legacyDevPattern.test(version)) {
|
|
84
|
+
legacyDevCount += 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const maxExistingPrerelease = existingPrereleaseNumbers.length > 0 ? Math.max(...existingPrereleaseNumbers) : 0;
|
|
89
|
+
const nextNumber = Math.max(maxExistingPrerelease, legacyDevCount) + 1;
|
|
90
|
+
return `${baseVersion}-${prereleaseId}.${nextNumber}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function main() {
|
|
94
|
+
const args = parseArgs(process.argv.slice(2));
|
|
95
|
+
if (!args.packageName || !args.baseVersion) {
|
|
96
|
+
printUsage(1);
|
|
97
|
+
}
|
|
98
|
+
const versions = readPublishedVersions(args.packageName, args.versionsJson);
|
|
99
|
+
process.stdout.write(resolvePrereleaseVersion(versions, args.baseVersion, args.prereleaseId));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (require.main === module) {
|
|
103
|
+
main();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
resolvePrereleaseVersion
|
|
108
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
const version = process.argv[2];
|
|
5
|
+
if (!version) {
|
|
6
|
+
console.error("Usage: node npm/scripts/set-package-version.js <version>");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.resolve(__dirname, "..", "..");
|
|
11
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
12
|
+
const packageLockPath = path.join(packageRoot, "package-lock.json");
|
|
13
|
+
const pyprojectPath = path.join(packageRoot, "pyproject.toml");
|
|
14
|
+
|
|
15
|
+
function writeJsonVersion(filePath) {
|
|
16
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
17
|
+
data.version = version;
|
|
18
|
+
if (data.packages && data.packages[""]) {
|
|
19
|
+
data.packages[""].version = version;
|
|
20
|
+
}
|
|
21
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
writeJsonVersion(packageJsonPath);
|
|
25
|
+
writeJsonVersion(packageLockPath);
|
|
26
|
+
|
|
27
|
+
const pyproject = fs.readFileSync(pyprojectPath, "utf8");
|
|
28
|
+
const versionPattern = /^version = ".*"$/m;
|
|
29
|
+
if (!versionPattern.test(pyproject)) {
|
|
30
|
+
console.error("Could not find the project.version field in pyproject.toml.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fs.writeFileSync(pyprojectPath, pyproject.replace(versionPattern, `version = "${version}"`));
|
|
35
|
+
console.log(`Set package version to ${version}.`);
|