@tuuject/surowan-utils 0.0.0-canary.24.1.g5ba29720fe68
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 +201 -0
- package/README.md +250 -0
- package/dist/args.d.ts +96 -0
- package/dist/args.js +618 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +4 -0
- package/dist/client.d.ts +36 -0
- package/dist/client.js +228 -0
- package/dist/commands.d.ts +19 -0
- package/dist/commands.js +403 -0
- package/dist/credentials.d.ts +40 -0
- package/dist/credentials.js +172 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +15 -0
- package/dist/login.d.ts +2 -0
- package/dist/login.js +65 -0
- package/dist/media.d.ts +9 -0
- package/dist/media.js +220 -0
- package/dist/run.d.ts +14 -0
- package/dist/run.js +195 -0
- package/package.json +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# @tuuject/surowan-utils
|
|
2
|
+
|
|
3
|
+
Plastic Wan Admin API 工具客户端,包内命令为 `surowan-utils`。它是 monorepo 工作区包(仓库根与其它 workspace 仍为 private),已配置为可公开发布到 npm;除 Invocation 的 `list`/`get`/`prompts`/`preflight`/`media`/`replay` 六个子命令外,还提供 `models list`(只读列出当前 active 配置中可接受文本输入的已配置模型)、`config show`、`prompt get global|group`、`login`(把 endpoint 与 API key 成对保存到本机)与 `doctor`(一次只读请求验证连通与鉴权);`invocation preflight`/`replay` 支持临时模型/thinking 覆盖(`--provider`/`--model`/`--thinking-level`)。群聊管理等其它能力尚未实现,也不包含 SDK 或 Eval 能力;`login` 只保存本机凭据,不能创建、查看或撤销服务端密钥(密钥管理仍只在 Admin Panel 进行)。API key 的权限面是 Invocation 读/重放加只读 inspection:配置与 prompt 视图、可用模型列表、场景所用的当前 prompt 与重放预检、Invocation 媒体导出;其余接口(含面板写端点与密钥管理)仍返回 403。
|
|
4
|
+
|
|
5
|
+
它与服务端入口 `plasticwan`(`node src/cli.ts`)不是同一个程序:本客户端只通过 Admin API 做只读检查、查询、媒体导出与重放,不包含 `serve`、`check-config`、`backup`、`configure` 等服务命令;客户端自己的 `doctor` 也不执行服务端 Doctor 的 SQLite、媒体、Telegram、Provider 与 MCP 探针,只检查 Admin API 连通与鉴权。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
发布可用后从 npm 全局安装(Node.js ≥ 24,无额外运行时依赖;命令名为 `surowan-utils`):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @tuuject/surowan-utils # 稳定版,dist-tag latest
|
|
13
|
+
npm install -g @tuuject/surowan-utils@canary # main 分支的 canary 构建
|
|
14
|
+
surowan-utils --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
canary 版本号形如 `0.0.0-canary.<run>.<attempt>.g<sha12>`。默认安装使用 `latest`;workflow 只把 canary 发布到独立的 `canary` 标签,不移动 `latest`,因此试用时须显式指定 `@canary`。
|
|
18
|
+
|
|
19
|
+
registry 上还没有该包(`npm install` 返回 404)或需要离线安装时,从源码检出打包后本地安装:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm install --frozen-lockfile
|
|
23
|
+
pnpm --filter @tuuject/surowan-utils pack --pack-destination "$PWD/dist/npm"
|
|
24
|
+
npm install -g ./dist/npm/tuuject-surowan-utils-0.1.0.tgz # 文件名以实际版本为准
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
不全局安装时,可在源码仓库内运行 `node packages/cli/dist/bin.js …`(先 `pnpm cli:build`)。
|
|
28
|
+
|
|
29
|
+
包以编译后的 ESM JavaScript 分发;不新增任何运行时依赖(只用标准库 `node:util` parseArgs 与全局 `fetch`)。源码内的相对导入带 `.ts` 后缀,由 `rewriteRelativeImportExtensions` 在编译产物中改写为 `.js`,因此 `node_modules` 内不会出现 Node type stripping 拒绝的 `.ts` 文件。
|
|
30
|
+
|
|
31
|
+
## 配套 Agent Skill
|
|
32
|
+
|
|
33
|
+
源码仓库根目录的 [`agents/skills/surowan-utils/SKILL.md`](../../.agents/skills/surowan-utils/SKILL.md) 给外部 Agent 使用:每次任务先运行 `surowan-utils doctor --json` 验证凭据与连通,再定位 Invocation、关联模型/工具/真实发送证据(只读核对可用 `config show`、`prompt get`、`models list`、`invocation prompts` 与 `invocation preflight`),最后在明确授权后重放(可带临时模型/thinking 覆盖)或分层比较 global/group prompt。它不是 Bot 的 `system:///` Skill,不需要服务器源码或数据库,也不增加 CLI 命令。
|
|
34
|
+
|
|
35
|
+
Skill 采用渐进披露:`SKILL.md` 是轻量入口,主题细节按当前任务加载子文档——审计流程在 `references/invocations.md`,重放的保真限制、授权要求与错误处理在 `references/replay.md`——不要一次加载全部内容。入口要求只有 `doctor` 以退出码 `0` 返回 `status: "ok"` 时才继续;失败即停止并请操作员用 `login` 修复(Agent 不代为执行、不重试、不读取或索取密钥),`--help` 只用于诊断 CLI 缺失或命令不匹配。
|
|
36
|
+
|
|
37
|
+
npm 包 **不再内置这个 Skill**;它随源码仓库根目录的 `.agents/skills/surowan-utils/` 分发。要给外部 Agent 使用,从源码仓库把整个目录复制到目标项目的根目录(以 [Codex 的项目级 Skill 目录](https://developers.openai.com/codex/build-skills) 为例);如果目标已存在,先比较版本,不直接覆盖。未克隆仓库时,可从 GitHub 下载该目录(`SKILL.md`、`references/invocations.md`、`references/replay.md` 与 `agents/openai.yaml` 都要保留)。最终布局应为 `.agents/skills/surowan-utils/SKILL.md`,不要多嵌套一层目录。其他 Agent 按其自身 Skill 导入机制注册,不假定兼容 Codex 的目录。
|
|
38
|
+
|
|
39
|
+
重新打开 Agent 会话,确认发现 `surowan-utils` 后可请求:
|
|
40
|
+
|
|
41
|
+
> 使用 $surowan-utils 审计 Invocation 12345,说明为什么没有回复;先只查询,不执行重放。
|
|
42
|
+
|
|
43
|
+
由操作员准备凭据:向 Agent 的命令执行环境安全注入 `SUROWAN_ENDPOINT` 与 `SUROWAN_API_KEY`,或由操作员在本机运行 `surowan-utils login` 保存到凭据文件。Agent 只运行 `doctor` 与查询命令,不自己读取、打印或修改凭据,也不要把 key 放入 Skill 或聊天。Skill 副本不随 CLI 升级自动更新;同步仓库更新时一并检查 Skill 版本。是否允许注册、执行命令及访问网络,仍取决于宿主权限。
|
|
44
|
+
|
|
45
|
+
## 用法
|
|
46
|
+
|
|
47
|
+
先在 Admin Panel 的 **Manage → API keys** 创建密钥(明文只在创建弹窗出现一次,关闭后无法再取回),再在本机保存凭据并验证连通。推荐顺序:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 1. 交互式登录:逐项提示 endpoint 与 API key(key 隐藏输入、不回显),无须参数或环境变量
|
|
51
|
+
surowan-utils login
|
|
52
|
+
|
|
53
|
+
# 2. 验证连通与鉴权;只有退出码 0 才继续
|
|
54
|
+
surowan-utils doctor --json
|
|
55
|
+
|
|
56
|
+
# 3. 只读检查:运行中/文件配置、全局与按群 prompt、可用模型、场景所用的当前 prompt 与重放预检
|
|
57
|
+
surowan-utils config show --json
|
|
58
|
+
surowan-utils config show --source file --json
|
|
59
|
+
surowan-utils prompt get global --json
|
|
60
|
+
surowan-utils prompt get group --chat -1001234567890 --source file --json
|
|
61
|
+
surowan-utils models list --json
|
|
62
|
+
surowan-utils invocation prompts 12345 --json
|
|
63
|
+
surowan-utils invocation preflight 12345 --json
|
|
64
|
+
|
|
65
|
+
# 4. 查询、导出媒体与重放
|
|
66
|
+
surowan-utils invocation list --limit 20 --state completed --chat -1001234567890 --json
|
|
67
|
+
surowan-utils invocation list --cursor 12345 --json
|
|
68
|
+
# 按关键词与公开消息时间定位 Invocation;--at 与 --from/--to 互斥
|
|
69
|
+
surowan-utils invocation list --search '关键词' --at '2026-09-10 07:59' --json
|
|
70
|
+
surowan-utils invocation list --search '关键词' --from '2026-09-10 07:00' --to '2026-09-10 08:00' --json
|
|
71
|
+
surowan-utils invocation get 12345 --json
|
|
72
|
+
surowan-utils invocation media 12345 --json
|
|
73
|
+
surowan-utils invocation replay 12345 --json
|
|
74
|
+
surowan-utils invocation replay 12345 --global-prompt prompt.txt --json
|
|
75
|
+
printf '%s' '临时替换的 global prompt' | surowan-utils invocation replay 12345 --global-prompt - --json
|
|
76
|
+
surowan-utils invocation replay 12345 --group-prompt group.txt --json
|
|
77
|
+
# 切片重放:以某次成功 Bot 发言为边界重建输入窗口,需显式确认计费
|
|
78
|
+
surowan-utils invocation preflight 12345 --before-send 678 --json
|
|
79
|
+
surowan-utils invocation replay 12345 --before-send 678 --confirm-paid --json
|
|
80
|
+
# 临时模型/thinking 覆盖:--provider 与 --model 必须成对,预检与重放必须使用同一选择
|
|
81
|
+
surowan-utils invocation preflight 12345 --provider openrouter --model deepseek/deepseek-v4-flash-0731 --thinking-level high --json
|
|
82
|
+
surowan-utils invocation replay 12345 --provider openrouter --model deepseek/deepseek-v4-flash-0731 --thinking-level high --json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
无人值守或 CI 环境改用已注入的环境变量,或让 key 只经标准输入进入(示例中的变量都由安全渠道注入,不含明文 key):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# endpoint 与 key 已在环境变量里;login 读取它们并写入凭据文件
|
|
89
|
+
surowan-utils login --json
|
|
90
|
+
|
|
91
|
+
# 或让 key 只经标准输入进入:读到 EOF 才停(不是读完一行就停),累计不超过 4098 字节,
|
|
92
|
+
# 读取结束后只去掉一个尾部 LF/CRLF,其余换行/空白/控制字符非法(受 --timeout-ms 约束;不是 --api-key -)
|
|
93
|
+
# SECRET_KEY 由安全渠道注入;它不是 CLI 读取的环境变量,CLI 只从 stdin 收下这段内容
|
|
94
|
+
printf '%s' "$SECRET_KEY" | surowan-utils login --endpoint "$SUROWAN_ENDPOINT" --api-key-stdin --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
不要手动 `export` 明文 key:含明文 key 的 `export` 会进入 shell 历史,真实凭据应由安全渠道注入。
|
|
98
|
+
|
|
99
|
+
`login` 把 endpoint 与 key 成对保存到固定路径 `~/.config/surowan-utils/credentials.json`(所有平台一致;与 Bot 的 `config.jsonc`、`key.json` 无关,同一时间只保留最近一次 `login` 的一对)。文件是 JSON 对象,字段为 `endpoint` 与 `apiKey`,**未加密**;POSIX 上目录 `0700`、文件 `0600`(更窄的权限同样接受),Windows 不做 POSIX mode 检查、权限由用户目录 ACL 继承(工具不承诺 `chmod` 级别的保护)。既有文件是符号链接、不是普通文件或权限不安全时,读写都拒绝且**不自动修复或替换**,需人工处理。`login` 不访问服务器,也不读取旧凭据内容;保存成功不代表在线鉴权通过,请接着运行 `doctor`。序列化后的凭据超过 16384 字节会被拒绝(`credentials_too_large`,退出码 2)且不触碰旧文件;写入经同目录临时文件 `rename` 落地,任何失败都不会覆盖旧文件。
|
|
100
|
+
|
|
101
|
+
凭据解析顺序(`doctor`、`config show`、`prompt get`、`models list` 与 `invocation list/get/prompts/preflight/media/replay` 相同):命令行参数 > 环境变量 > 保存文件。参数或环境变量显式给出的空串(含只含空白的 endpoint)按缺失/非法处理,**不会回退**到下一来源或改为提示(解析命令报 `missing_endpoint`/`missing_api_key`,`login` 报 `invalid_endpoint`/`invalid_api_key`)。endpoint 与 key 都由参数或环境变量明确给出时完全不读取凭据文件,文件损坏、是符号链接或权限不安全都不影响这次调用;只给其一才读文件,此时文件本身的问题以 `invalid_credentials` 失败(退出码 1)。保存的 key 只属于它保存时的规范化 endpoint:当前 endpoint 与文件中的 endpoint 在 `parseEndpoint` 规范化后不一致(例如换了主机)就不会使用该 key,缺少显式 key 会以 `missing_api_key` 失败(退出码 2),更换服务必须同时显式提供 key 或重新 `login`;规范化后相同(如仅尾斜杠差异)则仍可使用。参数与环境变量不会被写回文件(只有 `login` 写文件)。`login --json` 输出 `{"status":"saved","endpoint":…,"credentials_file":…}`,不含 key;`endpoint` 是规范化后的基地址。
|
|
102
|
+
|
|
103
|
+
`doctor` 按同一顺序解析凭据后恰好发起一次 `GET /api/invocations?limit=1`——没有 replay、没有模型/Provider 调用,也不发送 Telegram 消息;`--json` 成功时 stdout 为 `{"status":"ok","endpoint":…,"credential_sources":{"endpoint":"argument|environment|file","api_key":…}}`,不输出 Invocation 正文,失败不自动重试;鉴权仍会在服务端更新该密钥的 `last_used_at`。失败时 stderr 为 `{"error","message"}`:参数/环境变量给出的凭据缺失或不合法(`missing_endpoint`、`missing_api_key`、`invalid_api_key` 等)退出码 `2`,保存文件无效(`invalid_credentials`)或请求失败退出码 `1`。
|
|
104
|
+
|
|
105
|
+
### 选项
|
|
106
|
+
|
|
107
|
+
| 选项 | 说明 |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `--endpoint <url>` / `SUROWAN_ENDPOINT` | Admin Panel 基地址;解析顺序为参数 > 环境变量 > `login` 保存的凭据文件,三处都没有时按缺失报错,显式空串或纯空白按缺失报错(`missing_endpoint`)、不回退。明文 `http` 仅允许 loopback(`127.0.0.0/8`、`::1`、`localhost`);远端必须 `https`;禁止 URL 内凭据、query 与 fragment;保存与比较都用 `parseEndpoint` 规范化后的 URL(trim、补尾斜杠、去默认端口) |
|
|
110
|
+
| `--api-key <key>` / `SUROWAN_API_KEY` | API key:1–4096 字符,不得含空白或控制字符(参数/环境变量报 `invalid_api_key`,保存文件内的非法值报 `invalid_credentials`);与 endpoint 同一解析顺序,命令行参数优先于环境变量,显式空串不回退。仅当解析出的 endpoint 与文件 endpoint 规范化后相同才使用文件中的 key,否则缺少显式 key 报 `missing_api_key` |
|
|
111
|
+
| `--api-key-stdin` | 仅 `login`:把标准输入读到 EOF(不是读完一行就停),累计不超过 4098 字节,读取结束后只去掉一个尾部 LF/CRLF,剩余换行、空白或控制字符整体非法(`invalid_api_key`,退出码 2);TTY 上拒绝;整次读取受 `--timeout-ms` 约束,超时 `timeout`(退出码 1)且不保存;不能与 `--api-key` 或 `SUROWAN_API_KEY` 同时给出;不是 `--api-key -`,其它子命令不从标准输入读取 key |
|
|
112
|
+
| `--source <active\|file>` | 仅 `config show` 与 `prompt get`:`active`(默认)读运行中的配置快照,`file` 读磁盘上的配置文件并可与 active 对比;其它子命令带 `--source` 报 `unexpected_option`。两者不会互相替代,`file` 需要服务端已接线配置加载 |
|
|
113
|
+
| `--variant <original\|preview>` | 仅 `invocation media`:下载原始文件(默认)或服务端规范化预览;preview 只对 photo/sticker 可用,单文件与总量上限见下文 |
|
|
114
|
+
| `--timeout-ms <ms>` | 请求/输入超时;默认除 replay(300s)外都是 30s。stdin 读取单独使用同一上限,超时返回 `timeout`(退出码 1)且不发请求;HTTP 超时会中止请求,均**不自动重试** |
|
|
115
|
+
| `--json` | 稳定 JSON 输出;成功时 stdout 恰好一个 JSON 文档 |
|
|
116
|
+
| `--limit` / `--cursor` / `--state` | 透传给 `GET /api/invocations` 的过滤参数,本地先做形状校验(limit 1–100;Invocation ID/cursor 为非负十进制,限制在有符号 64 位范围) |
|
|
117
|
+
| `--chat <id>` | `invocation list` 的 Chat 过滤,或 `prompt get group` 必填的群 ID;必须是可带负号的有符号 64 位十进制,缺失时 `prompt get group` 报 `missing_argument` |
|
|
118
|
+
| `--search <keyword>` | 仅 `invocation list`:按字面关键词匹配公开消息(1–100 字符;`%`、`_` 按字面处理)。候选是冻结 `invocation_messages`(`section=new`)的群友消息 text/caption,或同 Invocation 成功 `telegram_sends` 的 text/caption;与时间过滤同用时关键词与时间必须命中同一条消息。带搜索或时间过滤时每个 item 追加 `matched_messages`,不带时维持原形状 |
|
|
119
|
+
| `--at <time>` / `--from <time>` / `--to <time>` | 仅 `invocation list`:公开消息时间过滤。`--at` 命中写出的整个精度窗口(`HH:mm` 为整分钟、`HH:mm:ss` 为整秒、小数 1/2/3 位分别对应 100/10/1 毫秒窗口),`--from`/`--to` 组成半开区间 `[from, to)`;`--at` 不能与 `--from`/`--to` 同用。格式 `YYYY-MM-DD[空格或T]HH:mm[:ss[.1-3位]][Z\|±HH:mm]`;不带 offset 的时间在服务端按 `--chat` 对应 Chat 的时区解析、否则用全局时区,非法日历与 DST 不存在/重复时间被拒绝 |
|
|
120
|
+
| `--before-send <send-id>` | 仅 `invocation preflight` 与 `invocation replay`:以该 `telegram_sends` 内部 ID(必须是本 Invocation 的成功发送)作为切片边界;preflight 与 replay 必须使用同一选择 |
|
|
121
|
+
| `--confirm-paid` | 仅 `invocation replay`:切片重放(`--before-send`)需显式确认真实模型计费;缺失时以 `confirm_paid_required`、退出码 2 拒绝,不读 prompt 或 stdin,也不发预检或 POST。未切片重放不要求该 flag |
|
|
122
|
+
| `--global-prompt <file\|->` | 仅 replay:临时替换 global prompt 层;`-` 从非终端标准输入读取,去空白后不能为空、最多 65,536 字符,否则在发请求前以退出码 2 失败 |
|
|
123
|
+
| `--group-prompt <file\|->` | 仅 replay:临时替换 group 层;允许空内容以显式清空该层,最多 65,536 字符;与 `--global-prompt -` 不能同时从 stdin 读取(`conflicting_prompt_input`)。标准输入只用于 `login` 的 key 与 replay 的 prompt |
|
|
124
|
+
| `--provider <alias>` | 仅 `invocation preflight` 与 `invocation replay`:临时模型覆盖的 Provider alias;必须与 `--model` 成对(`invalid_model_override`,退出码 2),1–256 字符、不含控制字符 |
|
|
125
|
+
| `--model <id>` | 仅 `invocation preflight` 与 `invocation replay`:临时模型覆盖的模型 ID;必须与 `--provider` 成对,规则同上 |
|
|
126
|
+
| `--thinking-level <level>` | 仅 `invocation preflight` 与 `invocation replay`:临时 thinking 覆盖,取 `off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`(`invalid_thinking_level`,退出码 2)。可单独使用(只临时改 thinking,模型沿用当前 Chat 配置);与模型对同给时按该模型校验级别,给模型对而不给 thinking 时使用该模型支持的最弱级别(非推理模型为 `off`),模型不支持的级别由服务端拒绝。预检与重放必须使用同一选择 |
|
|
127
|
+
|
|
128
|
+
## API 契约
|
|
129
|
+
|
|
130
|
+
| 命令 | 请求 | 成功响应形状 |
|
|
131
|
+
| --- | --- | --- |
|
|
132
|
+
| `login` | 不访问服务器,只写凭据文件 | `{ status: "saved", endpoint, credentials_file }`(不含 key) |
|
|
133
|
+
| `doctor` | `GET /api/invocations?limit=1`(只验证连通与鉴权) | `{ status: "ok", endpoint, credential_sources: { endpoint, api_key } }`,来源取值为 `argument` / `environment` / `file` |
|
|
134
|
+
| `config show` | `GET /api/config/view?source` | `{ source, generation, active_hash, file_hash, restart_required, config }`;`config` 是服务端脱敏投影(不含 key、SecretRef 内容与 prompt 正文) |
|
|
135
|
+
| `models list` | `GET /api/models`(无查询参数) | `{ source: "active", generation, models: [{ provider, model, name, context_window, max_tokens, input, reasoning, thinking_levels }] }`;只含 active 配置中接受 `text` 输入的已配置模型,不发起 Provider 请求,不代表上游完整目录或连通保证 |
|
|
136
|
+
| `prompt get global` | `GET /api/prompts/global?source` | `{ source, scope: "global", chat_id: null, prompt, core_read_only: true, generation, active_hash, file_hash, restart_required }` |
|
|
137
|
+
| `prompt get group --chat <id>` | `GET /api/prompts/group?source&chat` | 同上,`scope: "group"`,另有 `configured_chat_id`(Chat 迁移后可分辨配置 ID) |
|
|
138
|
+
| `invocation list` | `GET /api/invocations?limit&cursor&state&chat&search&at&from&to` | `{ items: [...], next_cursor: string \| null }`;带搜索或时间过滤时每个 item 追加 `matched_messages`(最新 5 条命中,按时间从新到旧;`{ source: "incoming"\|"bot", telegram_message_id, telegram_send_id, at, text }`,`text` 是截断预览,bot 条目的 `telegram_send_id` 是 `--before-send` 候选,仍须用同一 ID 预检;跨 Conversation 的发送不能作为切片目标) |
|
|
139
|
+
| `invocation get <id>` | `GET /api/invocations/<id>` | invocation 详情对象(含 `id: string`),原样输出 |
|
|
140
|
+
| `invocation prompts <id>` | `GET /api/invocations/<id>/prompts` | `{ source: "active", source_invocation_id, global_prompt, group_prompt, template_values, core_read_only: true }`;当前场景所用模板与变量,不是历史 prompt |
|
|
141
|
+
| `invocation preflight <id>` | `GET /api/invocations/<id>/replay-preflight[?before_send_id][&provider&model&thinking_level]` | `{ available, reason, message, prompt_overrides_available, omitted_images, scene?, model?, fidelity }`;基于保留的公开场景,不依赖报文录制;切片选择时 `scene.slice` 为 `{ before_send_id, before_message_id, after_bot_message_id }`;带临时模型/thinking 选择时 `model` 为将使用的 `{ provider, id, thinking_level }`,`fidelity.model_selection` 为 `current_chat_config` 或 `temporary_override`;不调用模型、不写生产业务状态 |
|
|
142
|
+
| `invocation media <id>` | `GET /api/invocations/<id>/media`,再对每项逐个 `GET /api/invocations/<id>/media/<media_id>/content?variant=` | 二进制逐项落盘;stdout 为 manifest 文档(见「输出与退出码」) |
|
|
143
|
+
| `invocation replay <id>` | 先 `GET /api/invocations/<id>/replay-preflight[?before_send_id][&provider&model&thinking_level]`;随后 `POST /api/invocations/<id>/replay`,body `{}`、`{ "global_prompt": "...", "group_prompt": "..." }`、`{ "before_send_id": "<十进制字符串>" }` 或带 `provider`/`model`/`thinking_level` 的任意组合(模型对与 thinking 同预检) | 对象;`error` 非 null 表示 replay 未完成,`model` 为实际使用的 `{ provider, id, thinking_level }`,`overrides` 报告哪两层被替换以及临时覆盖的 `provider`/`model`/`thinking_level` 布尔,`fidelity.model_selection` 区分 `current_chat_config`/`temporary_override`;切片重放另在 `scene.slice` 报告边界 |
|
|
144
|
+
|
|
145
|
+
响应边界只做最小校验:必须是 JSON 对象,且 list 的 `items` 为数组、`next_cursor` 为字符串或 null,get 必须含字符串 `id`,`config show` / `prompt get` / `invocation prompts` / `invocation preflight` 各自校验顶层字段,`models list` 校验 `source`/`generation` 与每个模型的字段形状(provider/model/name、`input` 含 text、thinking 级别在枚举内等),媒体列表校验每项的 ID、变体与大小形状;校验失败以 `invalid_response`(退出码 1)结束且不重试。JSON 响应体不设客户端大小上限,完整读取后解析,仍受请求超时约束;媒体内容单独按每文件 20 MiB、单次运行 100 MiB 设限。`doctor` 不输出 Invocation 正文,不调用模型、不发送 Telegram 消息、不重试;鉴权仍会更新密钥的 `last_used_at`。
|
|
146
|
+
|
|
147
|
+
## 输出与退出码
|
|
148
|
+
|
|
149
|
+
- 成功:stdout 一个 JSON 文档(`--json`)或简洁的人类可读输出;`login` 与 `doctor` 的成功文档不含 key。
|
|
150
|
+
- 失败:stderr 一个 JSON 文档 `{"error": "<code>", "message": "<message>"}`;错误信息经 key 脱敏,key 不会出现在任何输出中。
|
|
151
|
+
- replay 的响应即使 `error` 非 null 也会完整保留结构并写在 stdout,同时以非零退出码结束;stderr 仍是 JSON,`error` 为 `replay_failed`,`message` 以 `replay did not complete: ...` 开头。
|
|
152
|
+
- `invocation replay` 在 POST 前先调用一次免费的 `GET /api/invocations/<id>/replay-preflight`(切片重放带同一 `before_send_id`,临时模型/thinking 选择也带同一 `provider`/`model`/`thinking_level`):不可重放时直接用引擎的稳定错误码失败(不发送 replay);请求了 prompt 覆盖而预检的 `prompt_overrides_available` 为 false 时以 `replay_prompt_parts_unavailable` 失败,同样不 POST。覆盖内容在预检前读取并本地校验(global 为空或超过 65,536 字符等以退出码 2 失败);`before_send_id` 是重放边界而不是 prompt 覆盖,不要求该权限;模型选择也不是 prompt 覆盖,但选中模型/级别在预检不可用时按预检原因码失败。切片重放还需显式 `--confirm-paid` 确认本次真实计费。
|
|
153
|
+
- `invocation media` 顺序下载到一个新建的私有临时目录(创建失败即失败;目录绝对路径写在 manifest 的 `directory` 字段),每个文件与 `manifest.json` 在 POSIX 上以 `0600` 创建(Windows 沿用用户目录 ACL,不做 POSIX mode 保证)。stdout 输出 manifest `{ invocation_id, variant, directory, items[] }`,每项为 `{ id, message_id, revision_id, kind, status, path, mime_type, bytes, sha256, error }`(`message_id` 为内部消息 ID);`--json` 是单行 JSON,默认是缩进 JSON。全部成功退出码 0;部分失败保留已下载文件、写 `manifest.json` 并输出 manifest,以 `media_download_failed`(退出码 1)结束;全部失败先尝试删除整个目录,删除成功后把 manifest 输出到 stdout 并以 `media_download_failed` 失败;若文件系统拒绝清理,命令失败、目录可能残留,stdout 不保证含 manifest。下载前先拒绝超过 32 项或声明总量超过 100 MiB 的列表(`media_too_many_items` / `media_total_too_large`,不发生任何下载);单项超过 20 MiB、变体不存在、超时、重定向与写入失败只记录为该条的 `error` 并继续下一项;manifest 写入失败先尝试删除目录,清理成功后以 `media_manifest_write_failed` 失败;清理受阻时同样不保证目录已删除。manifest 等文本输出与落盘元数据经 API key 脱敏;媒体二进制保留响应原字节,不做文本脱敏,`bytes` 与 `sha256` 对应实际保存的文件。
|
|
154
|
+
- 退出码:`0` 成功;`1` 请求/服务端/replay/媒体下载失败,保存文件无效(`invalid_credentials`:JSON 损坏、符号链接、权限不安全等)与写入失败(`credentials_write_failed`)也走 `1`;`2` 参数、输入或凭据不合法(`missing_endpoint`、`missing_api_key`、`invalid_api_key`、`credentials_too_large`、模型选择的 `invalid_model_override`/`invalid_thinking_level`、prompt 覆盖的 `*_prompt_empty`/`*_prompt_too_large` 等)。`login` 写入失败不覆盖旧凭据。
|
|
155
|
+
- `doctor` 成功只说明当前凭据能连通并通过鉴权,不代表后续查询的数据仍在保留期内;它不输出 Invocation 正文,也不重试。
|
|
156
|
+
- 标准输入只用于 `login` 的 `--api-key-stdin` 与 replay 的 `--global-prompt -` / `--group-prompt -`(两者不能同时)。
|
|
157
|
+
- 请求不跟随重定向(`redirect: "error"`),只允许 `http(s)`,未知参数会直接报错而不是忽略。
|
|
158
|
+
|
|
159
|
+
## 发布(维护者)
|
|
160
|
+
|
|
161
|
+
### 自动化发布
|
|
162
|
+
|
|
163
|
+
`.github/workflows/npm.yml` 负责发布,独立于 Docker workflow(不等待 Docker gates):
|
|
164
|
+
|
|
165
|
+
- `main` 的 push:版本 `0.0.0-canary.<run_number>.<run_attempt>.g<12 位提交 SHA>`,dist-tag `canary`。
|
|
166
|
+
- 严格 `vMAJOR.MINOR.PATCH` 的 tag(无前导零、无 prerelease、无 build 后缀):tag 中的版本号即发布版本,dist-tag `latest`。
|
|
167
|
+
- 版本只在 CI 的一次性 checkout 内改写(在 `packages/cli` 执行 `npm version --no-git-tag-version`),不回写、不提交源码;稳定 tag 是稳定版本的唯一来源。
|
|
168
|
+
- `pnpm --filter @tuuject/surowan-utils pack` 之后,dry-run 与实际暂存使用同一个 tarball:先 `npm stage publish <tgz> --dry-run --access public --tag <dist-tag> --ignore-scripts` 检查内容,再以 `--provenance` 暂存同一文件。
|
|
169
|
+
- CI 只做 staged publishing:`npm stage publish` 把版本放进 registry 的待审批区,不对外可见;维护者需用 `npm stage list @tuuject/surowan-utils` 查看,`npm stage approve <stage-id>`(需 2FA)后才真正发布,或用 `npm stage reject <stage-id>` 丢弃。dist-tag 在暂存时就固定,审批时不能更改。每次 `main` push 都会暂存一个 canary,不审批就不会出现在 `canary` dist-tag 上。
|
|
170
|
+
- 认证使用 GitHub OIDC(job 持有 `id-token: write`):不使用 `NPM_TOKEN`,不依赖 GitHub Environment。跑在 Node 24 上,发布前安装 npm 11.16.0(trusted publishing 要求 npm CLI ≥ 11.5.1、Node ≥ 22.14.0)。
|
|
171
|
+
- 只有 `tuuject/surowan` 的 push 能进入发布 job;版本 guard(`scripts/npm-release.ts`)再次校验事件、仓库与 ref,不支持的输入会被拒绝。
|
|
172
|
+
- 发布串行且不取消正在运行的任务;`queue: max` 最多保留 100 个等待任务,避免后续 main push 顶掉等待中的稳定发布。队满时 GitHub 会取消新增任务,须人工检查并重跑。
|
|
173
|
+
|
|
174
|
+
稳定发布由维护者手动打 tag 触发(以下仅为示例,不在本仓库自动执行):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
git tag v1.0.0
|
|
178
|
+
git push origin v1.0.0
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
push 后到 Actions 日志确认暂存成功,用 `npm stage approve` 审批,再到 npm 包页面确认版本、dist-tag 与 provenance。几点注意:
|
|
182
|
+
|
|
183
|
+
- prerelease tag(如 `v1.0.0-rc.1`)虽会匹配 workflow 的 trigger,但会被版本 guard 拒绝,不会发布。
|
|
184
|
+
- Docker workflow 用 `GITHUB_TOKEN` 推送的 `v0.0.0-next-<UTC 时间戳>` tag 不会触发本 workflow。
|
|
185
|
+
- 每次稳定发布都会显式移动 `latest`,不会按版本大小跳过:在 `v2.0.0` 之后发布 `v1.2.4` 会把 `latest` 回退到 `1.2.4`。本 workflow 不区分旧维护线,打 tag 前必须确认这是预期。
|
|
186
|
+
- dry-run 检查 tarball 内容(npm 会顺带拒绝已存在的版本),但不验证 OIDC 与 trusted publisher 权限;这些只能在真实暂存时暴露。
|
|
187
|
+
|
|
188
|
+
### 首次发布 bootstrap
|
|
189
|
+
|
|
190
|
+
trusted publisher 只能配置到 npm 上已存在的包,因此第一次发布必须由对 `@tuuject` scope 有写权限的维护者在本机手工完成。不要把 token 写进命令或仓库。
|
|
191
|
+
|
|
192
|
+
1. 本机安装 Node.js ≥ 24,在仓库根目录安装依赖:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pnpm install --frozen-lockfile
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
2. 运行静态检查与相关测试(跨模块改动再跑完整 `pnpm test`):
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pnpm run check
|
|
202
|
+
pnpm test test/invocation-cli.test.ts test/npm-release.test.ts
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
3. 打包到明确的输出目录(用绝对路径,避免相对路径歧义;产物名随版本变化):
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
pnpm --filter @tuuject/surowan-utils pack --pack-destination "$PWD/dist/npm"
|
|
209
|
+
# 形如 dist/npm/tuuject-surowan-utils-0.1.0.tgz
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
4. 先用 dry-run 检查 tarball 内容:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npm publish ./dist/npm/tuuject-surowan-utils-0.1.0.tgz --dry-run --access public --tag latest --ignore-scripts
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
5. 交互式登录 npm 并确认本机账号(`npm whoami` 只确认本机身份,不是 OIDC/trusted publishing 的验证):
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npm login --registry=https://registry.npmjs.org
|
|
222
|
+
npm whoami
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
账号需要 `@tuuject` scope 的写权限,并在发布时启用 2FA。
|
|
226
|
+
|
|
227
|
+
6. 确认 dry-run 内容无误后,发布同一个 tarball:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npm publish ./dist/npm/tuuject-surowan-utils-0.1.0.tgz --access public --tag latest --ignore-scripts
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
npm 的包名与版本组合不可复用,即使撤包也不能重发同版本。`0.1.0` 用过之后,既不能用它再次手动发布,也不能再用 `v0.1.0` tag 发它;后续稳定版本选择 `v0.1.1` 或 `v1.0.0` 这类未使用过的版本。
|
|
234
|
+
|
|
235
|
+
### 配置 trusted publisher
|
|
236
|
+
|
|
237
|
+
1. 首次手工发布成功后,在 npm 打开该包的 Settings → Trusted publishing → GitHub Actions,填写:
|
|
238
|
+
- Organization or user:`tuuject`
|
|
239
|
+
- Repository:`surowan`
|
|
240
|
+
- Workflow filename:`npm.yml`(只填文件名,不能带 `.github/workflows/` 路径,必须带 `.yml`)
|
|
241
|
+
- Environment:留空
|
|
242
|
+
2. Allowed actions 只开启 stage publish(npm 新配置的默认值),不开启 **Allow npm publish**:CI 只能暂存,真正发布必须由维护者 2FA 审批。本 workflow 用 `npm stage publish --tag <dist-tag>`,没有单独的 `npm dist-tag` 步骤。不要以 dry-run 推断权限已经满足,须在首次真实暂存并审批后核对目标 dist-tag。
|
|
243
|
+
3. trusted publisher 配置保存后不可编辑:填错只能删除后重建。
|
|
244
|
+
|
|
245
|
+
配置完成后,用一次真实的 `main` push 和一次稳定 tag 验证暂存、审批与 provenance——CI 里的 dry-run 覆盖不到这条路径。成功后建议:
|
|
246
|
+
|
|
247
|
+
- 在包 Settings → Publishing access 选择 **Require two-factor authentication and disallow tokens**;这仍然允许 OIDC/trusted publishing,只是关闭长期 token 发布。
|
|
248
|
+
- 对 `main` 分支与 `v*` tag 启用仓库保护,限制谁能修改发布 workflow、创建或移动稳定 tag。
|
|
249
|
+
|
|
250
|
+
官方参考:[npm trusted publishing](https://docs.npmjs.com/trusted-publishers/)、[`npm trust` 前提条件](https://docs.npmjs.com/cli/v11/commands/npm-trust/)。
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type Credentials } from './credentials.ts';
|
|
2
|
+
export declare const MAX_PAGE_LIMIT = 100;
|
|
3
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
4
|
+
export declare const DEFAULT_REPLAY_TIMEOUT_MS = 300000;
|
|
5
|
+
export declare const MAX_TIMEOUT_MS = 3600000;
|
|
6
|
+
export declare const MAX_SEARCH_LENGTH = 100;
|
|
7
|
+
export type ConfigSource = 'active' | 'file';
|
|
8
|
+
export type PromptScope = 'global' | 'group';
|
|
9
|
+
export type MediaVariant = 'original' | 'preview';
|
|
10
|
+
export interface ListCommand {
|
|
11
|
+
readonly kind: 'list';
|
|
12
|
+
readonly limit: number | undefined;
|
|
13
|
+
readonly cursor: string | undefined;
|
|
14
|
+
readonly state: string | undefined;
|
|
15
|
+
readonly chat: string | undefined;
|
|
16
|
+
readonly search: string | undefined;
|
|
17
|
+
readonly at: string | undefined;
|
|
18
|
+
readonly from: string | undefined;
|
|
19
|
+
readonly to: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface GetCommand {
|
|
22
|
+
readonly kind: 'get';
|
|
23
|
+
readonly id: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PromptsCommand {
|
|
26
|
+
readonly kind: 'prompts';
|
|
27
|
+
readonly id: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ReplayModelSelection {
|
|
30
|
+
readonly provider: string | undefined;
|
|
31
|
+
readonly model: string | undefined;
|
|
32
|
+
readonly thinkingLevel: string | undefined;
|
|
33
|
+
}
|
|
34
|
+
export interface ModelsListCommand {
|
|
35
|
+
readonly kind: 'models-list';
|
|
36
|
+
}
|
|
37
|
+
export interface PreflightCommand extends ReplayModelSelection {
|
|
38
|
+
readonly kind: 'preflight';
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly beforeSendId: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
export interface MediaCommand {
|
|
43
|
+
readonly kind: 'media';
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly variant: MediaVariant;
|
|
46
|
+
}
|
|
47
|
+
export interface ReplayCommand extends ReplayModelSelection {
|
|
48
|
+
readonly kind: 'replay';
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly globalPromptSource: string | undefined;
|
|
51
|
+
readonly groupPromptSource: string | undefined;
|
|
52
|
+
readonly beforeSendId: string | undefined;
|
|
53
|
+
}
|
|
54
|
+
export interface ConfigShowCommand {
|
|
55
|
+
readonly kind: 'config-show';
|
|
56
|
+
readonly source: ConfigSource | undefined;
|
|
57
|
+
}
|
|
58
|
+
export interface PromptGetCommand {
|
|
59
|
+
readonly kind: 'prompt-get';
|
|
60
|
+
readonly scope: PromptScope;
|
|
61
|
+
readonly chat: string | undefined;
|
|
62
|
+
readonly source: ConfigSource | undefined;
|
|
63
|
+
}
|
|
64
|
+
export interface LoginCommand {
|
|
65
|
+
readonly kind: 'login';
|
|
66
|
+
readonly apiKeyStdin: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface DoctorCommand {
|
|
69
|
+
readonly kind: 'doctor';
|
|
70
|
+
}
|
|
71
|
+
export type InvocationCommand = ListCommand | GetCommand | PromptsCommand | PreflightCommand | MediaCommand | ReplayCommand;
|
|
72
|
+
export type CliCommand = InvocationCommand | ModelsListCommand | ConfigShowCommand | PromptGetCommand;
|
|
73
|
+
export type Command = CliCommand | LoginCommand | DoctorCommand;
|
|
74
|
+
export type CredentialSource = 'argument' | 'environment' | 'file';
|
|
75
|
+
export interface ParsedCli {
|
|
76
|
+
readonly help: boolean;
|
|
77
|
+
readonly json: boolean;
|
|
78
|
+
readonly command: Command | undefined;
|
|
79
|
+
readonly endpointRaw: string | undefined;
|
|
80
|
+
readonly apiKeyRaw: string | undefined;
|
|
81
|
+
readonly timeoutRaw: string | undefined;
|
|
82
|
+
}
|
|
83
|
+
export interface ResolvedCli {
|
|
84
|
+
readonly command: Command;
|
|
85
|
+
readonly endpoint: URL;
|
|
86
|
+
readonly apiKey: string;
|
|
87
|
+
readonly json: boolean;
|
|
88
|
+
readonly timeoutMs: number;
|
|
89
|
+
readonly credentialSources: {
|
|
90
|
+
readonly endpoint: CredentialSource;
|
|
91
|
+
readonly api_key: CredentialSource;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export declare function parseCli(argv: readonly string[]): ParsedCli;
|
|
95
|
+
export declare function resolveCli(parsed: ParsedCli, env: Record<string, string | undefined>, saved?: Credentials): ResolvedCli;
|
|
96
|
+
export declare function parseTimeout(text: string | undefined, replay: boolean): number;
|