@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/README.zh-CN.md +180 -0
  4. package/npm/bin/smart-search.js +68 -0
  5. package/npm/scripts/postinstall.js +87 -0
  6. package/npm/scripts/resolve-prerelease-version.js +108 -0
  7. package/npm/scripts/set-package-version.js +35 -0
  8. package/npm/scripts/sync-python-version.js +22 -0
  9. package/npm/scripts/test-wrapper-repair.js +137 -0
  10. package/npm/scripts/test.js +76 -0
  11. package/package.json +42 -0
  12. package/pyproject.toml +36 -0
  13. package/skills/smart-search-cli/SKILL.md +41 -0
  14. package/skills/smart-search-cli/agents/openai.yaml +3 -0
  15. package/skills/smart-search-cli/references/cli-contract.md +7 -0
  16. package/skills/smart-search-cli/references/cli-core.md +5 -0
  17. package/skills/smart-search-cli/references/command-patterns.md +12 -0
  18. package/skills/smart-search-cli/references/provider-routing.md +3 -0
  19. package/skills/smart-search-cli/references/regression-release.md +28 -0
  20. package/skills/smart-search-cli/references/setup-config.md +3 -0
  21. package/src/smart_search/__init__.py +1 -0
  22. package/src/smart_search/assets/skills/smart-search-cli/SKILL.md +41 -0
  23. package/src/smart_search/assets/skills/smart-search-cli/agents/openai.yaml +3 -0
  24. package/src/smart_search/assets/skills/smart-search-cli/references/cli-contract.md +7 -0
  25. package/src/smart_search/assets/skills/smart-search-cli/references/cli-core.md +5 -0
  26. package/src/smart_search/assets/skills/smart-search-cli/references/command-patterns.md +12 -0
  27. package/src/smart_search/assets/skills/smart-search-cli/references/provider-routing.md +3 -0
  28. package/src/smart_search/assets/skills/smart-search-cli/references/regression-release.md +28 -0
  29. package/src/smart_search/assets/skills/smart-search-cli/references/setup-config.md +3 -0
  30. package/src/smart_search/cli.py +3118 -0
  31. package/src/smart_search/config.py +809 -0
  32. package/src/smart_search/embedding_presets.py +40 -0
  33. package/src/smart_search/intent_router.py +757 -0
  34. package/src/smart_search/logger.py +43 -0
  35. package/src/smart_search/providers/__init__.py +20 -0
  36. package/src/smart_search/providers/base.py +41 -0
  37. package/src/smart_search/providers/context7.py +141 -0
  38. package/src/smart_search/providers/exa.py +206 -0
  39. package/src/smart_search/providers/jina.py +136 -0
  40. package/src/smart_search/providers/openai_compatible.py +541 -0
  41. package/src/smart_search/providers/xai_responses.py +117 -0
  42. package/src/smart_search/providers/zhipu.py +143 -0
  43. package/src/smart_search/providers/zhipu_mcp.py +230 -0
  44. package/src/smart_search/service.py +3445 -0
  45. package/src/smart_search/skill_installer.py +342 -0
  46. package/src/smart_search/sources.py +429 -0
  47. 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`.
@@ -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}.`);