@bingran/sbti-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bingran You
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/NOTICE ADDED
@@ -0,0 +1,13 @@
1
+ This repository includes original code and documentation authored for `sbti-cli`,
2
+ which are released under the MIT License in `LICENSE`.
3
+
4
+ This repository may also contain third-party content derived from the upstream
5
+ SBTI website at https://sbti.fancc.de5.net/, including but not limited to:
6
+
7
+ - survey prompts
8
+ - result descriptions
9
+ - extracted character artwork / poster images
10
+
11
+ Those upstream materials remain subject to their original ownership and are not
12
+ relicensed by this repository unless the original rights holder states
13
+ otherwise.
package/README.md ADDED
@@ -0,0 +1,322 @@
1
+ <h1 align="center">SBTI CLI - Test SBTI for your agents.</h1>
2
+
3
+ <p align="center">
4
+ <em>SBTI CLI - Test SBTI for your agents.</em><br>
5
+ A Node.js CLI with <strong>offline-only execution</strong>, <strong>bundled survey data</strong>, and <strong>result-image export</strong>.
6
+ </p>
7
+
8
+ <p align="center">
9
+ <a href="https://sbti.fancc.de5.net"><img alt="Original test" src="https://img.shields.io/badge/original-sbti.fancc.de5.net-4CAF50?style=flat-square"></a>
10
+ <img alt="Node.js" src="https://img.shields.io/badge/Node.js-18%2B-339933?style=flat-square">
11
+ <img alt="Runtime mode" src="https://img.shields.io/badge/runtime-offline--only-blue?style=flat-square">
12
+ <img alt="Result images" src="https://img.shields.io/badge/result%20images-27-orange?style=flat-square">
13
+ <img alt="Questions" src="https://img.shields.io/badge/questions-30%20%2B%201%20hidden-purple?style=flat-square">
14
+ <img alt="License" src="https://img.shields.io/badge/license-MIT-red?style=flat-square">
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="./README.md"><img alt="English" src="https://img.shields.io/badge/English-current-111827?style=for-the-badge"></a>
19
+ <a href="./README.zh-CN.md"><img alt="简体中文" src="https://img.shields.io/badge/简体中文-click_to_switch-EF4444?style=for-the-badge"></a>
20
+ </p>
21
+
22
+ <p align="center">
23
+ <img src="assets/type-images/CTRL.png" width="130" alt="CTRL">
24
+ <img src="assets/type-images/BOSS.png" width="130" alt="BOSS">
25
+ <img src="assets/type-images/SEXY.png" width="130" alt="SEXY">
26
+ <img src="assets/type-images/MALO.png" width="130" alt="MALO">
27
+ <img src="assets/type-images/DRUNK.png" width="130" alt="DRUNK">
28
+ <img src="assets/type-images/HHHH.png" width="130" alt="HHHH">
29
+ </p>
30
+
31
+ ---
32
+
33
+ ## 📖 Table of Contents
34
+
35
+ - [📖 Table of Contents](#-table-of-contents)
36
+ - [🎯 What Is This](#-what-is-this)
37
+ - [🧭 Installation \& Setup](#-installation--setup)
38
+ - [🧪 Using the CLI](#-using-the-cli)
39
+ - [Common Commands](#common-commands)
40
+ - [Interactive Controls](#interactive-controls)
41
+ - [Typical Run](#typical-run)
42
+ - [🧬 Core Capabilities](#-core-capabilities)
43
+ - [🎭 Result Images \& Offline Resources](#-result-images--offline-resources)
44
+ - [Export All Result Images](#export-all-result-images)
45
+ - [🔬 Data Sources \& How It Works](#-data-sources--how-it-works)
46
+ - [Why It Can Match the Website So Closely](#why-it-can-match-the-website-so-closely)
47
+ - [Where the Poster Art Comes From](#where-the-poster-art-comes-from)
48
+ - [Most Important Files in This Repo](#most-important-files-in-this-repo)
49
+ - [🙏 Acknowledgements](#-acknowledgements)
50
+ - [📄 License](#-license)
51
+
52
+ ---
53
+
54
+ ## 🎯 What Is This
55
+
56
+ This repository turns **SBTI** into a local command-line runner.
57
+
58
+ Key traits:
59
+
60
+ - 🎲 **Website-equivalent question flow**
61
+ - 📊 **Website-equivalent scoring**
62
+ - 📴 **Offline-safe execution**
63
+ - 🖼️ **Exportable result posters**
64
+ - ✅ **Regression coverage**
65
+
66
+ ---
67
+
68
+ ## 🧭 Installation & Setup
69
+
70
+ ### Install From npm
71
+
72
+ For agent runs or normal CLI use, install the published package and run the binary directly:
73
+
74
+ ```bash
75
+ npm install -g @bingran/sbti-cli
76
+ sbti-cli
77
+ ```
78
+
79
+ or:
80
+
81
+ ```bash
82
+ npx @bingran/sbti-cli
83
+ ```
84
+
85
+ The npm install surface is intentionally small: the root `@bingran/sbti-cli` package only ships a tiny launcher, and npm pulls the matching platform-native binary package for your machine. The published package does **not** ship the repository source tree, tests, build scripts, or image assets.
86
+
87
+ Current native package targets:
88
+
89
+ - macOS `arm64`
90
+ - macOS `x64`
91
+ - Linux `arm64`
92
+ - Linux `x64`
93
+ - Windows `x64`
94
+
95
+ ### Clone The Repo For Development
96
+
97
+ If you want the test suite, image-export tooling, or source code itself, use the repository checkout:
98
+
99
+ ```bash
100
+ git clone https://github.com/bingran-you/sbti-cli.git
101
+ cd sbti-cli
102
+ npm install
103
+ npm test
104
+ ```
105
+
106
+ Then start the development CLI with:
107
+
108
+ ```bash
109
+ npm run sbti
110
+ ```
111
+
112
+ or:
113
+
114
+ ```bash
115
+ node src/cli.mjs
116
+ ```
117
+
118
+ > 💡 There is no database, browser driver, or `.env` setup required. If Node.js is installed, you can run either the published CLI or the repo-local development entry point.
119
+
120
+ ---
121
+
122
+ ## 🧪 Using the CLI
123
+
124
+ ### Common Commands
125
+
126
+ | Command | Purpose |
127
+ |---|---|
128
+ | `sbti-cli` | Start a normal interactive run from the published npm package |
129
+ | `sbti-cli --seed 42` | Use a deterministic shuffle seed |
130
+ | `sbti-cli --json` | Print the final result as JSON |
131
+ | `npm run sbti` | Start the repo-local development entry point |
132
+ | `npm run export-images` | Rebuild the local poster manifest and gallery from bundled assets in a repo checkout |
133
+
134
+ ### Interactive Controls
135
+
136
+ Once the CLI starts, you answer one question at a time:
137
+ Each answer is locked in for that run as soon as you submit it.
138
+
139
+ | Input | Action |
140
+ |---|---|
141
+ | `A / B / C / D` | Select the current option |
142
+ | `q` | Quit without submitting |
143
+
144
+ ### Typical Run
145
+
146
+ ```bash
147
+ sbti-cli
148
+ ```
149
+
150
+ ```text
151
+ SBTI CLI
152
+ Question source: bundled:sbti-main.js
153
+
154
+ Question 1 / 31 · dimension hidden
155
+ ...
156
+
157
+ Enter A/B/C/D, or q to quit.
158
+ > C
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 🧬 Core Capabilities
164
+
165
+ <table>
166
+ <tr>
167
+ <th>Area</th>
168
+ <th>Capability</th>
169
+ <th>Details</th>
170
+ </tr>
171
+ <tr>
172
+ <td><strong>🛟 Offline runtime</strong></td>
173
+ <td>Always runs from the bundled snapshot</td>
174
+ <td>The CLI never fetches the live website at runtime, so every questionnaire run stays fully local</td>
175
+ </tr>
176
+ <tr>
177
+ <td><strong>🖼️ Result-image export</strong></td>
178
+ <td>27 bundled posters can be indexed locally</td>
179
+ <td>The repo can rebuild a JSON manifest and an HTML gallery from the checked-in image files</td>
180
+ </tr>
181
+ <tr>
182
+ <td><strong>🧪 Regression tests</strong></td>
183
+ <td>Bundled-runtime verification</td>
184
+ <td>Includes runtime parity, 50 deterministic result cases, and offline asset coverage</td>
185
+ </tr>
186
+ <tr>
187
+ <td><strong>📦 Native npm release</strong></td>
188
+ <td>Small launcher plus platform-native binary packages</td>
189
+ <td>Installing from npm no longer ships the repo source tree, tests, or build scripts with the runnable CLI</td>
190
+ </tr>
191
+ </table>
192
+
193
+ ---
194
+
195
+ ## 🎭 Result Images & Offline Resources
196
+
197
+ This section is repository-only. The published npm CLI does not ship the poster assets or the export script.
198
+
199
+ <table>
200
+ <tr>
201
+ <td align="center" width="33%">
202
+ <a href="assets/type-images/index.html"><img src="assets/type-images/CTRL.png" width="180"><br><strong>Local result gallery</strong></a><br>
203
+ <sub>An HTML gallery generated from the extracted poster files</sub>
204
+ </td>
205
+ <td align="center" width="33%">
206
+ <a href="assets/type-images/manifest.json"><img src="assets/type-images/BOSS.png" width="180"><br><strong>Image manifest</strong></a><br>
207
+ <sub>File names, MIME types, and sizes for all exported posters</sub>
208
+ </td>
209
+ <td align="center" width="33%">
210
+ <a href="src/bundled-data.mjs"><img src="assets/type-images/SEXY.png" width="180"><br><strong>Offline snapshot</strong></a><br>
211
+ <sub>The built-in survey data used for every CLI run</sub>
212
+ </td>
213
+ </tr>
214
+ <tr>
215
+ <td align="center">
216
+ <a href="scripts/export-type-images.mjs"><img src="assets/type-images/MALO.png" width="180"><br><strong>Image export script</strong></a><br>
217
+ <sub>Rebuilds the local poster manifest and gallery from the checked-in image files</sub>
218
+ </td>
219
+ <td align="center">
220
+ <a href="test/runtime.test.mjs"><img src="assets/type-images/HHHH.png" width="180"><br><strong>Parity tests</strong></a><br>
221
+ <sub>Checks that CLI results stay aligned with the bundled scoring logic</sub>
222
+ </td>
223
+ </tr>
224
+ </table>
225
+
226
+ ### Export All Result Images
227
+
228
+ ```bash
229
+ npm run export-images
230
+ ```
231
+
232
+ This generates:
233
+
234
+ - [`assets/type-images/index.html`](assets/type-images/index.html) — local gallery
235
+ - [`assets/type-images/manifest.json`](assets/type-images/manifest.json) — poster manifest
236
+ - [`assets/type-images/`](assets/type-images/) — all decoded `.png` / `.jpg` files
237
+
238
+ ## 🔬 Data Sources & How It Works
239
+
240
+ ### Why It Can Match the Website So Closely
241
+
242
+ This repository stores a bundled snapshot of the survey data and scoring logic in [`src/bundled-data.mjs`](src/bundled-data.mjs). [`src/runtime.mjs`](src/runtime.mjs) turns that snapshot into a sandboxed runtime so the CLI can stay local while preserving the original question flow and result math.
243
+
244
+ The CLI therefore uses the same runtime objects throughout every run:
245
+
246
+ | Runtime object | Content |
247
+ |---|---|
248
+ | `dimensionMeta` | Chinese labels and model groups for the 15 dimensions |
249
+ | `questions` | The 30 regular questions |
250
+ | `specialQuestions` | The drink-gate question set |
251
+ | `TYPE_LIBRARY` | Codes, names, intros, and full descriptions for all 27 result types |
252
+ | `NORMAL_TYPES` | The 25 normal H / M / L templates |
253
+ | `DIM_EXPLANATIONS` | Dimension explanations for each L / M / H tier |
254
+ | `computeResult()` | The website's own result-selection branch logic |
255
+
256
+ That is why the CLI can stay aligned with:
257
+
258
+ - question shuffle
259
+ - drink-gate insertion and hidden question reveal
260
+ - 15-dimension scoring and bucketing
261
+ - normal-type ranking
262
+ - `DRUNK` override
263
+ - `HHHH` low-similarity fallback
264
+
265
+ ### Where the Poster Art Comes From
266
+
267
+ All 27 result posters are checked into [`assets/type-images/`](assets/type-images/). [`scripts/export-type-images.mjs`](scripts/export-type-images.mjs) rebuilds the manifest and gallery pages from those local files.
268
+
269
+ ### Most Important Files in This Repo
270
+
271
+ - [`src/cli.mjs`](src/cli.mjs) — CLI entry point and interactive questionnaire flow
272
+ - [`src/runtime.mjs`](src/runtime.mjs) — bundled runtime loading, sandbox evaluation, and result summarization
273
+ - [`src/bundled-data.mjs`](src/bundled-data.mjs) — bundled offline snapshot
274
+ - [`src/type-images.mjs`](src/type-images.mjs) — image helpers and local gallery generation
275
+ - [`bin/sbti-cli.cjs`](bin/sbti-cli.cjs) — npm launcher that resolves the installed native binary package
276
+ - [`lib/platform-package.cjs`](lib/platform-package.cjs) — supported-platform map and native-package resolution helpers
277
+ - [`dist/sbti-cli.cjs`](dist/sbti-cli.cjs) — CommonJS single-file bundle used as the native-binary build input
278
+ - [`scripts/build-dist.mjs`](scripts/build-dist.mjs) — generates the single-file ESM/CJS CLI bundles
279
+ - [`scripts/build-platform-packages.mjs`](scripts/build-platform-packages.mjs) — builds precompiled native npm subpackages for each platform target
280
+ - [`scripts/publish-packages.mjs`](scripts/publish-packages.mjs) — publishes the native platform packages and then the root `@bingran/sbti-cli` package
281
+ - [`scripts/export-type-images.mjs`](scripts/export-type-images.mjs) — local poster metadata rebuild
282
+ - [`test/runtime.test.mjs`](test/runtime.test.mjs) — bundled runtime parity tests
283
+ - [`test/launcher.test.mjs`](test/launcher.test.mjs) — native-launcher package resolution coverage
284
+ - [`test/package.test.mjs`](test/package.test.mjs) — publish-tarball surface checks
285
+ - [`test/type-images.test.mjs`](test/type-images.test.mjs) — offline asset coverage
286
+
287
+ ---
288
+
289
+ ## 🙏 Acknowledgements
290
+
291
+ <table>
292
+ <tr>
293
+ <th>Project</th>
294
+ <th>Author</th>
295
+ <th>Contribution</th>
296
+ </tr>
297
+ <tr>
298
+ <td><a href="https://sbti.fancc.de5.net"><strong>SBTI Personality Test</strong></a></td>
299
+ <td>Bilibili <a href="https://space.bilibili.com/417038183">@蛆肉儿串儿</a></td>
300
+ <td>Original survey author and source of the question text, result copy, and character artwork</td>
301
+ </tr>
302
+ <tr>
303
+ <td><a href="https://github.com/serenakeyitan/sbti-wiki"><strong>sbti-wiki</strong></a></td>
304
+ <td><a href="https://github.com/serenakeyitan">@serenakeyitan</a></td>
305
+ <td>The visual README format here was inspired by that project's centered hero, badges, image strip, and information-card layout</td>
306
+ </tr>
307
+ <tr>
308
+ <td><strong>sbti-cli</strong></td>
309
+ <td><a href="https://github.com/bingran-you">Bingran You (@bingran-you)</a></td>
310
+ <td>Built the sandboxed runtime loader, offline snapshot, image exporter, and regression test suite for a practical terminal workflow</td>
311
+ </tr>
312
+ </table>
313
+
314
+ > ⚠️ **For entertainment only**: the upstream site already warns against treating this as diagnosis, hiring criteria, relationship truth, fortune telling, or any serious judgment. This repo is a tooling and reference project, not a psychological assessment.
315
+
316
+ ---
317
+
318
+ ## 📄 License
319
+
320
+ The original code and documentation in this repository are released under the [MIT License](LICENSE).
321
+
322
+ Third-party survey prompts, result text, and extracted character artwork originate from the upstream SBTI website and remain subject to their original ownership. See [NOTICE](NOTICE) for attribution and scope.
@@ -0,0 +1,319 @@
1
+ <h1 align="center">SBTI CLI · 官网逻辑等效的命令行版</h1>
2
+
3
+ <p align="center">
4
+ <em>把 SBTI 人格测试搬进终端,同时尽量保留官网同一套运行时与结果资源。</em><br>
5
+ 一个支持 <strong>纯离线运行</strong>、<strong>内置题库快照</strong>、<strong>结果图导出</strong> 的 Node.js CLI。
6
+ </p>
7
+
8
+ <p align="center">
9
+ <a href="https://sbti.fancc.de5.net"><img alt="原测试" src="https://img.shields.io/badge/原测试-sbti.fancc.de5.net-4CAF50?style=flat-square"></a>
10
+ <img alt="Node.js" src="https://img.shields.io/badge/Node.js-18%2B-339933?style=flat-square">
11
+ <img alt="运行模式" src="https://img.shields.io/badge/运行模式-纯离线-blue?style=flat-square">
12
+ <img alt="结果图" src="https://img.shields.io/badge/结果图-27%20张-orange?style=flat-square">
13
+ <img alt="题目" src="https://img.shields.io/badge/题目-30%20%2B%201%20隐藏-purple?style=flat-square">
14
+ <img alt="许可证" src="https://img.shields.io/badge/许可证-MIT-red?style=flat-square">
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="./README.md"><img alt="English" src="https://img.shields.io/badge/English-click_to_switch-2563EB?style=for-the-badge"></a>
19
+ <a href="./README.zh-CN.md"><img alt="简体中文" src="https://img.shields.io/badge/简体中文-当前-F0522D?style=for-the-badge"></a>
20
+ </p>
21
+
22
+ <p align="center">
23
+ <img src="assets/type-images/CTRL.png" width="130" alt="CTRL">
24
+ <img src="assets/type-images/BOSS.png" width="130" alt="BOSS">
25
+ <img src="assets/type-images/SEXY.png" width="130" alt="SEXY">
26
+ <img src="assets/type-images/MALO.png" width="130" alt="MALO">
27
+ <img src="assets/type-images/DRUNK.png" width="130" alt="DRUNK">
28
+ <img src="assets/type-images/HHHH.png" width="130" alt="HHHH">
29
+ </p>
30
+
31
+ ---
32
+
33
+ ## 📖 目录
34
+
35
+ - [这是什么](#-这是什么)
36
+ - [如何安装与设置](#-如何安装与设置)
37
+ - [如何使用 CLI](#-如何使用-cli)
38
+ - [核心能力一览](#-核心能力一览)
39
+ - [结果图与离线资源](#-结果图与离线资源)
40
+ - [数据来源与原理](#-数据来源与原理)
41
+ - [鸣谢](#-鸣谢)
42
+ - [License](#-license)
43
+
44
+ ---
45
+
46
+ ## 🎯 这是什么
47
+
48
+ 本仓库把 [**sbti.fancc.de5.net**](https://sbti.fancc.de5.net) 的 SBTI 人格测试做成了一个可在本地终端运行的 CLI。它不是手写一套“像官网”的逻辑,而是把题库和结果逻辑打包成内置快照,在本地 sandbox 中运行,因此保留了这些关键特性:
49
+
50
+ - 🎲 **和官网一致的题目行为**:30 道常规题随机顺序、饮酒分支插入位置、隐藏题触发规则都保持一致
51
+ - 📊 **和官网一致的结果计算**:15 维打分、H / M / L 分档、25 个标准人格匹配、`DRUNK` 覆盖、`HHHH` 兜底都保持一致
52
+ - 📴 **每次都纯离线运行**:CLI 每次都直接使用仓库内置快照,不依赖 live website
53
+ - 🖼️ **结果图可本地整理**:27 张结果海报已随仓库提供,可重建本地 manifest 与画廊
54
+ - ✅ **有对齐测试兜底**:除了基础单测,还包含 50 组结果回归测试与离线资源完整性验证
55
+
56
+ 如果你想:
57
+
58
+ - 在终端里完整做一次 SBTI
59
+ - 用脚本研究结果逻辑
60
+ - 在完全离线的环境里继续跑测试
61
+ - 重建 27 张结果图的本地资源清单
62
+
63
+ 这个仓库就是干这个的。
64
+
65
+ ---
66
+
67
+ ## 🧭 如何安装与设置
68
+
69
+ ### 直接从 npm 安装
70
+
71
+ 如果只是想让 agent 或普通用户直接跑 CLI,用 npm 安装发布包即可:
72
+
73
+ ```bash
74
+ npm install -g @bingran/sbti-cli
75
+ sbti-cli
76
+ ```
77
+
78
+ 或者:
79
+
80
+ ```bash
81
+ npx @bingran/sbti-cli
82
+ ```
83
+
84
+ npm 安装面现在被刻意收紧了:根包 `@bingran/sbti-cli` 只带一个很小的 launcher,安装时会自动拉取当前平台对应的 native binary 子包。发布到 npm 的内容 **不会** 再带上仓库源码、测试、构建脚本或结果图资源。
85
+
86
+ 当前原生包目标平台:
87
+
88
+ - macOS `arm64`
89
+ - macOS `x64`
90
+ - Linux `arm64`
91
+ - Linux `x64`
92
+ - Windows `x64`
93
+
94
+ ### 克隆仓库用于开发
95
+
96
+ 如果你需要测试集、结果图导出工具或源码本身,再使用仓库 checkout:
97
+
98
+ ```bash
99
+ git clone https://github.com/bingran-you/sbti-cli.git
100
+ cd sbti-cli
101
+ npm install
102
+ npm test
103
+ ```
104
+
105
+ 然后可以用开发入口启动:
106
+
107
+ ```bash
108
+ npm run sbti
109
+ ```
110
+
111
+ 或者:
112
+
113
+ ```bash
114
+ node src/cli.mjs
115
+ ```
116
+
117
+ > 💡 这个项目不需要数据库、浏览器驱动或 `.env` 配置;只要有 Node.js,就能运行发布版 CLI 或仓库内开发入口。
118
+
119
+ ---
120
+
121
+ ## 🧪 如何使用 CLI
122
+
123
+ ### 常用命令
124
+
125
+ | 命令 | 作用 |
126
+ |---|---|
127
+ | `sbti-cli` | 从 npm 发布包直接开始一次测试 |
128
+ | `sbti-cli --seed 42` | 固定随机种子,方便复现题目顺序 |
129
+ | `sbti-cli --json` | 直接输出 JSON 结果 |
130
+ | `npm run sbti` | 运行仓库内的开发入口 |
131
+ | `npm run export-images` | 在仓库 checkout 里重建 manifest 和本地画廊 |
132
+
133
+ ### 交互方式
134
+
135
+ 运行测试后,CLI 会逐题提问。输入方式和导航规则如下:
136
+ 每个答案一旦提交,就会在本轮测试中锁定。
137
+
138
+ | 输入 | 作用 |
139
+ |---|---|
140
+ | `A / B / C / D` | 选择当前题的选项 |
141
+ | `q` | 中途退出,不提交结果 |
142
+
143
+ ### 一个最常见的流程
144
+
145
+ ```bash
146
+ sbti-cli
147
+ ```
148
+
149
+ ```text
150
+ SBTI 人格测试 CLI
151
+ 题库来源: bundled:sbti-main.js
152
+
153
+ 第 1 题 / 31 · 维度已隐藏
154
+ ...
155
+
156
+ 输入 A/B/C/D 选择,或输入 q 退出。
157
+ > C
158
+ ```
159
+
160
+ ## 🧬 核心能力一览
161
+
162
+ <table>
163
+ <tr>
164
+ <th>模块</th>
165
+ <th>能力</th>
166
+ <th>说明</th>
167
+ </tr>
168
+ <tr>
169
+ <td><strong>🛟 离线运行时</strong></td>
170
+ <td>始终直接加载内置快照</td>
171
+ <td>题目顺序、饮酒隐藏题、打分、匹配、特殊分支都走本地内置的同一套逻辑</td>
172
+ </tr>
173
+ <tr>
174
+ <td><strong>🖼️ 结果图资源</strong></td>
175
+ <td>27 张已收录海报可本地整理</td>
176
+ <td>支持生成 <code>manifest.json</code> 与本地 <code>index.html</code> 画廊</td>
177
+ </tr>
178
+ <tr>
179
+ <td><strong>🧪 回归测试</strong></td>
180
+ <td>内置运行时验证</td>
181
+ <td>包含 50 组结果回归、图片完整性测试与离线运行路径验证</td>
182
+ </tr>
183
+ <tr>
184
+ <td><strong>📦 原生 npm 发布</strong></td>
185
+ <td>小型 launcher + 平台 native binary 子包</td>
186
+ <td>通过 npm 安装时,不再把仓库源码、测试或构建脚本连同可运行 CLI 一起发出去</td>
187
+ </tr>
188
+ </table>
189
+
190
+ ---
191
+
192
+ ## 🎭 结果图与离线资源
193
+
194
+ 这一节是仓库开发资源,不属于发布到 npm 的 CLI 安装面。
195
+
196
+ <table>
197
+ <tr>
198
+ <td align="center" width="33%">
199
+ <a href="assets/type-images/index.html"><img src="assets/type-images/CTRL.png" width="180"><br><strong>本地结果图画廊</strong></a><br>
200
+ <sub>27 张海报导出后的可浏览 HTML 页面</sub>
201
+ </td>
202
+ <td align="center" width="33%">
203
+ <a href="assets/type-images/manifest.json"><img src="assets/type-images/BOSS.png" width="180"><br><strong>结果图清单</strong></a><br>
204
+ <sub>文件名、类型、字节数都收录在 manifest 里</sub>
205
+ </td>
206
+ <td align="center" width="33%">
207
+ <a href="src/bundled-data.mjs"><img src="assets/type-images/SEXY.png" width="180"><br><strong>离线快照</strong></a><br>
208
+ <sub>每次 CLI 运行都会直接使用的内置题库与结果数据</sub>
209
+ </td>
210
+ </tr>
211
+ <tr>
212
+ <td align="center">
213
+ <a href="scripts/export-type-images.mjs"><img src="assets/type-images/MALO.png" width="180"><br><strong>图片导出脚本</strong></a><br>
214
+ <sub>基于仓库内已有图片重建 manifest 和本地画廊</sub>
215
+ </td>
216
+ <td align="center">
217
+ <a href="test/runtime.test.mjs"><img src="assets/type-images/HHHH.png" width="180"><br><strong>对齐测试</strong></a><br>
218
+ <sub>确保 CLI 的结果分支和内置逻辑保持一致</sub>
219
+ </td>
220
+ </tr>
221
+ </table>
222
+
223
+ ### 导出结果图
224
+
225
+ ```bash
226
+ npm run export-images
227
+ ```
228
+
229
+ 执行后会生成:
230
+
231
+ - [`assets/type-images/index.html`](assets/type-images/index.html) — 本地画廊
232
+ - [`assets/type-images/manifest.json`](assets/type-images/manifest.json) — 27 张图片的清单
233
+ - [`assets/type-images/`](assets/type-images/) — 全部解码后的 `.png` / `.jpg` 文件
234
+
235
+ ## 🔬 数据来源与原理
236
+
237
+ ### 为什么能做到和官网行为一致
238
+
239
+ 本仓库把题库和结果逻辑存成 [`src/bundled-data.mjs`](src/bundled-data.mjs) 里的内置快照,再通过 [`src/runtime.mjs`](src/runtime.mjs) 用 Node.js 的 `vm` 模块放进一个很小的 sandbox 里运行。
240
+
241
+ CLI 不是手写“差不多”的逻辑,而是每次都直接跑这份内置快照里的同一套对象:
242
+
243
+ | 运行时对象 | 内容 |
244
+ |---|---|
245
+ | `dimensionMeta` | 15 个维度的中文名与模型分组 |
246
+ | `questions` | 30 道常规题 |
247
+ | `specialQuestions` | 饮酒隐藏题与其前置分支 |
248
+ | `TYPE_LIBRARY` | 27 个人格的代号、中文名、开场白、完整描述 |
249
+ | `NORMAL_TYPES` | 25 个标准人格的 H / M / L 模板 |
250
+ | `DIM_EXPLANATIONS` | 15 维 × 3 档的维度文案 |
251
+ | `computeResult()` | 官网自己的结果分支逻辑 |
252
+
253
+ 因此,CLI 可以和官网保持同一套:
254
+
255
+ - 题目 shuffle
256
+ - 饮酒题插入与隐藏题显示
257
+ - 15 维打分与分档
258
+ - 25 个标准人格匹配排序
259
+ - `DRUNK` 特殊覆盖
260
+ - `HHHH` 低匹配度兜底
261
+
262
+ ### 结果图从哪里来
263
+
264
+ 27 张人物海报已经随仓库一起存放在 [`assets/type-images/`](assets/type-images/) 中。[`scripts/export-type-images.mjs`](scripts/export-type-images.mjs) 负责根据这些本地文件重建 manifest 和本地画廊。
265
+
266
+ ### 本仓库里最关键的文件
267
+
268
+ - [`src/cli.mjs`](src/cli.mjs) — 命令行交互入口
269
+ - [`src/runtime.mjs`](src/runtime.mjs) — 内置运行时加载、sandbox 运行、结果汇总
270
+ - [`src/bundled-data.mjs`](src/bundled-data.mjs) — CLI 使用的内置快照
271
+ - [`src/type-images.mjs`](src/type-images.mjs) — 图片工具与本地画廊生成
272
+ - [`bin/sbti-cli.cjs`](bin/sbti-cli.cjs) — npm launcher,会解析当前平台对应的 native binary 子包
273
+ - [`lib/platform-package.cjs`](lib/platform-package.cjs) — 支持的平台映射与 native package 解析逻辑
274
+ - [`dist/sbti-cli.cjs`](dist/sbti-cli.cjs) — 用来生成 native binary 的 CommonJS 单文件入口
275
+ - [`scripts/build-dist.mjs`](scripts/build-dist.mjs) — 生成 ESM/CJS 两份单文件 CLI 构建产物
276
+ - [`scripts/build-platform-packages.mjs`](scripts/build-platform-packages.mjs) — 生成各平台预编译 native npm 子包
277
+ - [`scripts/publish-packages.mjs`](scripts/publish-packages.mjs) — 先发布平台子包,再发布根包 `@bingran/sbti-cli`
278
+ - [`scripts/export-type-images.mjs`](scripts/export-type-images.mjs) — 重建结果图 manifest 与画廊
279
+ - [`test/runtime.test.mjs`](test/runtime.test.mjs) — 内置运行时对齐测试
280
+ - [`test/launcher.test.mjs`](test/launcher.test.mjs) — launcher 与 native package 解析测试
281
+ - [`test/package.test.mjs`](test/package.test.mjs) — npm tarball 发布面检查
282
+ - [`test/type-images.test.mjs`](test/type-images.test.mjs) — 图片资源完整性测试
283
+
284
+ ---
285
+
286
+ ## 🙏 鸣谢
287
+
288
+ <table>
289
+ <tr>
290
+ <th>项目</th>
291
+ <th>作者</th>
292
+ <th>贡献</th>
293
+ </tr>
294
+ <tr>
295
+ <td><a href="https://sbti.fancc.de5.net"><strong>SBTI 人格测试</strong></a></td>
296
+ <td>B 站 <a href="https://space.bilibili.com/417038183">@蛆肉儿串儿</a></td>
297
+ <td>原测试作者,提供题目、27 个人格文案与角色插画</td>
298
+ </tr>
299
+ <tr>
300
+ <td><a href="https://github.com/serenakeyitan/sbti-wiki"><strong>sbti-wiki</strong></a></td>
301
+ <td><a href="https://github.com/serenakeyitan">@serenakeyitan</a></td>
302
+ <td>这次 README 改版的版式参考来源:中心 Hero、徽章、图卡与信息分段方式</td>
303
+ </tr>
304
+ <tr>
305
+ <td><strong>sbti-cli</strong></td>
306
+ <td><a href="https://github.com/bingran-you">Bingran You (@bingran-you)</a></td>
307
+ <td>把题库快照 sandbox 化,补上离线运行、结果图整理与测试体系,提供一个可直接使用的 CLI</td>
308
+ </tr>
309
+ </table>
310
+
311
+ > ⚠️ **仅供娱乐**:原测试首页已经写得很明确了,不要把它当成诊断、面试、相亲、分手、招魂、算命或人生判决书。本仓库也只是一个便于运行、研究和导出资源的工具。
312
+
313
+ ---
314
+
315
+ ## 📄 License
316
+
317
+ 本仓库中由本项目原创的代码与文档采用 [MIT License](LICENSE) 发布。
318
+
319
+ 与此同时,来自上游 SBTI 网站的题目文案、结果文案以及角色插画仍然归原作者所有,并不因为放进这个仓库就自动转成 MIT。具体归属说明见 [NOTICE](NOTICE)。
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require('node:child_process');
4
+ const process = require('node:process');
5
+
6
+ const { resolveInstalledBinaryPath } = require('../lib/platform-package.cjs');
7
+
8
+ let resolvedBinary;
9
+
10
+ try {
11
+ resolvedBinary = resolveInstalledBinaryPath((request) => require.resolve(request));
12
+ } catch (error) {
13
+ console.error(error.message);
14
+ process.exit(1);
15
+ }
16
+
17
+ const child = spawn(resolvedBinary.binaryPath, process.argv.slice(2), {
18
+ stdio: 'inherit'
19
+ });
20
+
21
+ child.on('error', (error) => {
22
+ console.error(error.message);
23
+ process.exit(1);
24
+ });
25
+
26
+ child.on('exit', (code, signal) => {
27
+ if (signal) {
28
+ console.error(`sbti-cli terminated with signal ${signal}.`);
29
+ process.exit(1);
30
+ }
31
+
32
+ process.exit(code ?? 0);
33
+ });
@@ -0,0 +1,117 @@
1
+ const path = require('node:path');
2
+
3
+ const SUPPORTED_PLATFORMS = [
4
+ {
5
+ id: 'darwin-arm64',
6
+ platform: 'darwin',
7
+ arch: 'arm64',
8
+ packageName: '@bingran/sbti-cli-darwin-arm64',
9
+ pkgTarget: 'node18-macos-arm64',
10
+ binaryName: 'sbti-cli'
11
+ },
12
+ {
13
+ id: 'darwin-x64',
14
+ platform: 'darwin',
15
+ arch: 'x64',
16
+ packageName: '@bingran/sbti-cli-darwin-x64',
17
+ pkgTarget: 'node18-macos-x64',
18
+ binaryName: 'sbti-cli'
19
+ },
20
+ {
21
+ id: 'linux-arm64',
22
+ platform: 'linux',
23
+ arch: 'arm64',
24
+ packageName: '@bingran/sbti-cli-linux-arm64',
25
+ pkgTarget: 'node18-linux-arm64',
26
+ binaryName: 'sbti-cli'
27
+ },
28
+ {
29
+ id: 'linux-x64',
30
+ platform: 'linux',
31
+ arch: 'x64',
32
+ packageName: '@bingran/sbti-cli-linux-x64',
33
+ pkgTarget: 'node18-linux-x64',
34
+ binaryName: 'sbti-cli'
35
+ },
36
+ {
37
+ id: 'win32-x64',
38
+ platform: 'win32',
39
+ arch: 'x64',
40
+ packageName: '@bingran/sbti-cli-win32-x64',
41
+ pkgTarget: 'node18-win-x64',
42
+ binaryName: 'sbti-cli.exe'
43
+ }
44
+ ];
45
+
46
+ function formatSupportedPlatforms() {
47
+ return SUPPORTED_PLATFORMS.map(
48
+ ({ platform, arch, packageName }) => `- ${platform}/${arch}: ${packageName}`
49
+ ).join('\n');
50
+ }
51
+
52
+ function getPlatformPackageInfo(platform, arch) {
53
+ return (
54
+ SUPPORTED_PLATFORMS.find(
55
+ (entry) => entry.platform === platform && entry.arch === arch
56
+ ) ?? null
57
+ );
58
+ }
59
+
60
+ function getCurrentPlatformPackageInfo() {
61
+ return getPlatformPackageInfo(process.platform, process.arch);
62
+ }
63
+
64
+ function getBinaryPath(packageRoot, platformInfo) {
65
+ if (!platformInfo) {
66
+ throw new Error('A supported platform package definition is required.');
67
+ }
68
+
69
+ return path.join(packageRoot, 'bin', platformInfo.binaryName);
70
+ }
71
+
72
+ function resolveInstalledBinaryPath(
73
+ resolveRequest,
74
+ platform = process.platform,
75
+ arch = process.arch
76
+ ) {
77
+ if (typeof resolveRequest !== 'function') {
78
+ throw new TypeError('resolveRequest must be a function.');
79
+ }
80
+
81
+ const platformInfo = getPlatformPackageInfo(platform, arch);
82
+
83
+ if (!platformInfo) {
84
+ throw new Error(
85
+ `Unsupported platform ${platform}/${arch}.\nSupported native packages:\n${formatSupportedPlatforms()}`
86
+ );
87
+ }
88
+
89
+ let packageJsonPath;
90
+
91
+ try {
92
+ packageJsonPath = resolveRequest(`${platformInfo.packageName}/package.json`);
93
+ } catch (error) {
94
+ if (error?.code === 'MODULE_NOT_FOUND') {
95
+ throw new Error(
96
+ `Missing native binary package ${platformInfo.packageName} for ${platform}/${arch}. ` +
97
+ 'Reinstall @bingran/sbti-cli without omitting optional dependencies.'
98
+ );
99
+ }
100
+
101
+ throw error;
102
+ }
103
+
104
+ return {
105
+ platformInfo,
106
+ binaryPath: getBinaryPath(path.dirname(packageJsonPath), platformInfo)
107
+ };
108
+ }
109
+
110
+ module.exports = {
111
+ SUPPORTED_PLATFORMS,
112
+ formatSupportedPlatforms,
113
+ getBinaryPath,
114
+ getCurrentPlatformPackageInfo,
115
+ getPlatformPackageInfo,
116
+ resolveInstalledBinaryPath
117
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@bingran/sbti-cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Native-binary CLI runner for the public SBTI survey.",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/bingran-you/sbti-cli",
8
+ "bugs": {
9
+ "url": "https://github.com/bingran-you/sbti-cli/issues"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/bingran-you/sbti-cli.git"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "lib",
18
+ "LICENSE",
19
+ "NOTICE",
20
+ "README.md",
21
+ "README.zh-CN.md"
22
+ ],
23
+ "bin": {
24
+ "sbti-cli": "./bin/sbti-cli.cjs"
25
+ },
26
+ "optionalDependencies": {
27
+ "@bingran/sbti-cli-darwin-arm64": "0.1.0",
28
+ "@bingran/sbti-cli-darwin-x64": "0.1.0",
29
+ "@bingran/sbti-cli-linux-arm64": "0.1.0",
30
+ "@bingran/sbti-cli-linux-x64": "0.1.0",
31
+ "@bingran/sbti-cli-win32-x64": "0.1.0"
32
+ },
33
+ "scripts": {
34
+ "build:dist": "node scripts/build-dist.mjs",
35
+ "build:platform-packages": "node scripts/build-platform-packages.mjs",
36
+ "publish:packages": "node scripts/publish-packages.mjs",
37
+ "start": "node src/cli.mjs",
38
+ "sbti": "node src/cli.mjs",
39
+ "export-images": "node scripts/export-type-images.mjs",
40
+ "test": "node --test test/*.test.mjs"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ }
45
+ }