protara 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -3
- package/dist/cli/index.cjs +430 -45
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,14 +19,74 @@ protara --version
|
|
|
19
19
|
|
|
20
20
|
CLI 用**独立的**登录态(`credentials.cli.json`),不与桌面端共享 —— 服务端的刷新令牌一次一换,共用一份会互相踢下线。
|
|
21
21
|
|
|
22
|
+
### 两种登录方式:`--login` 会问你一句
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
protara --login
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
protara: 登录方式:
|
|
30
|
+
1) 设备码登录 —— 在浏览器里确认一下(有浏览器时最省事)
|
|
31
|
+
2) 用户名密码登录 —— 没有浏览器时用(SSH / CI)
|
|
32
|
+
回车 = 1(设备码)
|
|
33
|
+
protara: 选择 [1/2]:
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **1 / 回车** → 设备码:浏览器里确认一下就完成
|
|
37
|
+
- **2** → 用户名密码:接着问用户名(回显),密码**不回显**
|
|
38
|
+
|
|
39
|
+
**为什么密码登录需要被发现**:SSH 到远程机器、CI、纯终端环境里拿不到浏览器时,设备码走不通
|
|
40
|
+
(`user_code` 可以手输,但那要另一台设备先打开浏览器)。这时密码登录是唯一可用的路径。
|
|
41
|
+
|
|
42
|
+
### 直接指定,跳过询问
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
protara --login --password # 跳过菜单,直接用账号密码
|
|
46
|
+
protara --login --password --username alice # 用户名也给定
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
⚠️ **非交互环境(CI / 管道)不会弹菜单**,自动按设备码走 —— 脚本里 `protara --login`
|
|
50
|
+
的行为与以前完全一致,不会被卡在等输入。
|
|
51
|
+
|
|
52
|
+
### 脚本化登录(CI / 自动化)
|
|
53
|
+
|
|
22
54
|
```bash
|
|
23
|
-
|
|
24
|
-
|
|
55
|
+
# 方式一:从 stdin 读(推荐,密码不进 shell 历史、不进 ps)
|
|
56
|
+
echo "$MY_PASSWORD" | protara --login --password-stdin --username alice
|
|
57
|
+
|
|
58
|
+
# 方式二:环境变量
|
|
59
|
+
PROTARA_PASSWORD="$MY_PASSWORD" protara --login --password --username alice
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
⚠️ **刻意没有 `--password <明文>`**:那会把密码写进 shell 历史、`ps` 的命令行、以及 CI 日志。
|
|
63
|
+
这不是没做,是不提供。
|
|
64
|
+
|
|
65
|
+
⚠️ 密码输入**一个字符都不回显**(连 `*` 都没有)—— 终端回滚、tmux `capture-pane`、
|
|
66
|
+
屏幕共享都可能留下屏幕内容,密码长度也是信息。
|
|
67
|
+
|
|
68
|
+
### 首登会被要求改密
|
|
69
|
+
|
|
70
|
+
如果输出里有这句:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
protara: ⚠️ 这个账号首次登录需要先改密码。请用浏览器登录平台改密后再回来用 CLI
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
说明服务端判了 `must_change_password`。**令牌是有效的**(登录确实成功了),但改密前多数
|
|
77
|
+
操作会被拒绝。去 Web 端改一次密码再回来。
|
|
78
|
+
|
|
79
|
+
### 排障
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
protara status # 登录态 / 凭据路径 / 作用域根 / 终端指纹
|
|
25
83
|
```
|
|
26
84
|
|
|
27
85
|
## 用法
|
|
28
86
|
|
|
29
87
|
```bash
|
|
88
|
+
protara agents # 列出可用 Agent(含 id / 默认模型)
|
|
89
|
+
protara --agent <id> -p "…" # 指定 Agent 提问
|
|
30
90
|
protara # 交互界面(需要真 TTY)
|
|
31
91
|
protara -p "帮我看下这个仓库" # 一次性提问;stdout 只有答案正文
|
|
32
92
|
cat log.txt | protara -p - # 从管道读正文
|
|
@@ -46,6 +106,43 @@ protara --help # 全部参数 + 退出码表
|
|
|
46
106
|
|
|
47
107
|
`-p` 模式下 **stdout 保证纯净**:所有诊断走 stderr 且带 `protara:` 前缀,或写进日志文件。
|
|
48
108
|
|
|
109
|
+
### 切换 Agent
|
|
110
|
+
|
|
111
|
+
先看有哪些(`--agent` 要的是 `id`,不是名字):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
protara agents
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
✓ 2 个可用 Agent
|
|
119
|
+
|
|
120
|
+
desktop 优联智能文档(默认)
|
|
121
|
+
面向内部文档平台的通用助手:能读写工作区文件、跑命令、查技能库
|
|
122
|
+
默认模型 claude-sonnet-4
|
|
123
|
+
可选模型 gpt-4o
|
|
124
|
+
|
|
125
|
+
code-reviewer 代码审查助手
|
|
126
|
+
默认模型 claude-sonnet-4
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
然后指定:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
protara --agent code-reviewer -p "审查一下这个 diff"
|
|
133
|
+
protara --agent code-reviewer --model gpt-4o -p "…" # 同时换模型
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**不给 `--agent` 时用内置的 `cli` Agent**(终端专用:工具清单里没有浏览器 / 文档编辑,
|
|
137
|
+
因为那两套运行时装不进这个 3.3 MB 的包)。它**不出现在上面的列表里** ——
|
|
138
|
+
它是 CLI 的实现细节,不是给你选的。
|
|
139
|
+
|
|
140
|
+
⚠️ **Agent 在「建会话」那一刻冻结**,之后不能中途切换 —— 想换就开一个新会话
|
|
141
|
+
(不加 `--session` / `--resume-last` 就是新会话)。这是服务端的口径:
|
|
142
|
+
会话的 `agent_id` 创建后不可变,因为权限档位、技能集、工具清单都在那时定下了。
|
|
143
|
+
|
|
144
|
+
`protara agents --json` 给脚本用:`{ ok: true, agents: [...] }`(`default: true` 标出默认那个)。
|
|
145
|
+
|
|
49
146
|
### 退出码
|
|
50
147
|
|
|
51
148
|
| 码 | 含义 |
|
|
@@ -86,7 +183,45 @@ $DESKTOP_HOME/logs/cli.log # 默认 ~/.desktop-agents/logs/cli.log
|
|
|
86
183
|
连接器要重新「去认证」一次。
|
|
87
184
|
3. **凭据目前是明文 `0600` 文件**(refresh 令牌寿命 30 天)。在共享机器上慎用 `--login`。
|
|
88
185
|
|
|
89
|
-
##
|
|
186
|
+
## 能做什么 / 不能做什么
|
|
187
|
+
|
|
188
|
+
CLI 与桌面端**共用同一份工具执行器**,但装配不同 —— 差别只有「有没有那个运行时」。
|
|
189
|
+
|
|
190
|
+
### ✅ 能用的本机工具(13 条)
|
|
191
|
+
|
|
192
|
+
| 类别 | 工具 |
|
|
193
|
+
|---|---|
|
|
194
|
+
| 文件读写 | `read` / `write` / `edit` / `delete`(废纸篓通道缺省,见下) |
|
|
195
|
+
| 查找 | `grep` / `find` / `glob` / `ls` |
|
|
196
|
+
| 执行 | `bash`(任意命令、可后台) |
|
|
197
|
+
| 技能 | `load_skill` |
|
|
198
|
+
| 传输 | `upload_file` / `download_file`(只要登录态) |
|
|
199
|
+
|
|
200
|
+
外加:**MCP 连接器工具**(`mcp.json` 与桌面端共享,但授权令牌不共享)、
|
|
201
|
+
`tool_search`(连接器工具的延迟加载检索)。
|
|
202
|
+
|
|
203
|
+
### ❌ 不能用的两条(会**明确告诉你**,不静默失败)
|
|
204
|
+
|
|
205
|
+
| 工具 | CLI 里的返回 |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `browser`(浏览器操作) | `本客户端没有装配浏览器通道(找不到随包分发的 bsk 运行时)…` |
|
|
208
|
+
| `univer`(文档编辑) | `本客户端没有装配文档编辑通道(找不到随包分发的 univer 运行时)…` |
|
|
209
|
+
|
|
210
|
+
**为什么**:这两个能力依赖**随包分发的大体积运行时**,而 npm 包只有 3.3 MB:
|
|
211
|
+
|
|
212
|
+
| 载荷 | 体积 |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `bsk`(浏览器操作二进制,含 Chromium 驱动) | 23 MB |
|
|
215
|
+
| `univer`(文档编辑:Node 运行时 + 公式/排版/导入导出四份原生模块) | **2.0 GB** |
|
|
216
|
+
|
|
217
|
+
把 2 GB 塞进 npm 包不现实(npm 单包上限 500 MB,且装一次要几分钟)。
|
|
218
|
+
所以 CLI 里这两条**如实拒绝**,⛔ 不会偷偷用 `bash` 去跑 —— 那会绕过整条权限口径。
|
|
219
|
+
|
|
220
|
+
> 需要浏览器自动化 / 文档编辑:请用**桌面端**(它随包带了这两套载荷)。
|
|
221
|
+
> 在 CLI 里模型会看到这两条工具(清单与桌面端同一份),调用后收到上面那句拒绝,
|
|
222
|
+
> 然后自己改用 `bash` + 其他方式,或者告诉你「这个我做不了」。
|
|
223
|
+
|
|
224
|
+
### ❌ 其他没做的
|
|
90
225
|
|
|
91
226
|
连接器「去认证」交互面板、计划条、作业角标面板、diff 分页、图片拖入、多行编辑框
|
|
92
227
|
(TUI 逐行提交)、Windows 上的沙箱助手。
|
package/dist/cli/index.cjs
CHANGED
|
@@ -51188,6 +51188,9 @@ var DEFAULT = {
|
|
|
51188
51188
|
forceHeadless: false,
|
|
51189
51189
|
verbose: false,
|
|
51190
51190
|
login: false,
|
|
51191
|
+
loginMethod: "ask",
|
|
51192
|
+
username: null,
|
|
51193
|
+
passwordFromStdin: false,
|
|
51191
51194
|
logout: false,
|
|
51192
51195
|
version: false,
|
|
51193
51196
|
help: false,
|
|
@@ -51287,6 +51290,19 @@ function parseArgs(argv) {
|
|
|
51287
51290
|
case "--login":
|
|
51288
51291
|
out.login = true;
|
|
51289
51292
|
break;
|
|
51293
|
+
case "--password":
|
|
51294
|
+
out.loginMethod = "password";
|
|
51295
|
+
out.login = true;
|
|
51296
|
+
break;
|
|
51297
|
+
case "--username":
|
|
51298
|
+
out.username = next("--username");
|
|
51299
|
+
break;
|
|
51300
|
+
case "--password-stdin":
|
|
51301
|
+
out.passwordFromStdin = true;
|
|
51302
|
+
out.loginMethod = "password";
|
|
51303
|
+
out.login = true;
|
|
51304
|
+
break;
|
|
51305
|
+
break;
|
|
51290
51306
|
case "--logout":
|
|
51291
51307
|
out.logout = true;
|
|
51292
51308
|
break;
|
|
@@ -51313,7 +51329,7 @@ function parseArgs(argv) {
|
|
|
51313
51329
|
}
|
|
51314
51330
|
if (positional.length > 0) {
|
|
51315
51331
|
const [first, ...rest] = positional;
|
|
51316
|
-
if (first === "login" || first === "logout" || first === "status") {
|
|
51332
|
+
if (first === "login" || first === "logout" || first === "status" || first === "agents") {
|
|
51317
51333
|
out.command = first;
|
|
51318
51334
|
} else if (first !== void 0 && !out.prompt && !out.promptFromStdin) {
|
|
51319
51335
|
out.prompt = [first, ...rest].join(" ");
|
|
@@ -51374,8 +51390,16 @@ var HELP_TEXT = `protara \u2014 \u4F18\u8054\u667A\u80FD\u6587\u6863\u7684\u7EC8
|
|
|
51374
51390
|
-v, --verbose \u628A\u8BCA\u65AD\u4E5F\u8F93\u51FA\u5230 stderr\uFF08\u9ED8\u8BA4\u53EA\u5199\u65E5\u5FD7\u6587\u4EF6\uFF09
|
|
51375
51391
|
--approval <mode> deny\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u4EBA\u5E94\u7B54\u5373\u62D2\u6279\uFF09| remote\uFF08\u8D70\u6D4F\u89C8\u5668\u8FDC\u7A0B\u5BA1\u6279\uFF09
|
|
51376
51392
|
--origin <url> \u5E73\u53F0\u5730\u5740
|
|
51377
|
-
--login / --logout \u53EA\u767B\u5F55 / \u53EA\u9000\u51FA
|
|
51393
|
+
--login / --logout \u53EA\u767B\u5F55 / \u53EA\u9000\u51FA
|
|
51394
|
+
\u4E0D\u52A0\u522B\u7684\u53C2\u6570\u65F6\uFF0C\u4F1A\u95EE\u300C1 \u8BBE\u5907\u7801 / 2 \u8D26\u53F7\u5BC6\u7801\u300D\uFF0C
|
|
51395
|
+
\u975E\u4EA4\u4E92\u73AF\u5883\uFF08CI / \u7BA1\u9053\uFF09\u81EA\u52A8\u6309\u8BBE\u5907\u7801\u8D70\uFF0C\u4E0D\u4F1A\u505C\u4E0B\u6765\u7B49\u8F93\u5165
|
|
51396
|
+
--password \u8DF3\u8FC7\u8BE2\u95EE\uFF0C\u76F4\u63A5\u7528**\u8D26\u53F7\u5BC6\u7801**\u767B\u5F55\uFF08SSH / CI \u91CC\u6CA1\u6709\u6D4F\u89C8\u5668\u65F6\u7528\uFF09
|
|
51397
|
+
--username <name> \u8D26\u53F7\u5BC6\u7801\u767B\u5F55\u7684\u7528\u6237\u540D\uFF08\u4E0D\u7ED9\u5C31\u4EA4\u4E92\u8F93\u5165\uFF09
|
|
51398
|
+
--password-stdin \u4ECE stdin \u8BFB\u5BC6\u7801\uFF08\u811A\u672C\u7528\uFF1Aecho "$PW" | protara login --password-stdin\uFF09
|
|
51399
|
+
\u26A0\uFE0F \u523B\u610F**\u4E0D\u63D0\u4F9B** "--password <\u660E\u6587>"\uFF1A\u90A3\u4F1A\u7559\u5728 shell \u5386\u53F2\u91CC\u3002
|
|
51400
|
+
\u975E\u4EA4\u4E92\u4E5F\u53EF\u7528\u73AF\u5883\u53D8\u91CF PROTARA_PASSWORD\u3002
|
|
51378
51401
|
status \u6253\u5370\u767B\u5F55\u6001 / \u51ED\u636E\u8DEF\u5F84 / \u7EC8\u7AEF\u6307\u7EB9\uFF08\u53EA\u8BFB\uFF0C\u6392\u969C\u7528\uFF09
|
|
51402
|
+
agents \u5217\u51FA\u5F53\u524D\u8EAB\u4EFD\u53EF\u7528\u7684 Agent\uFF08id / \u540D\u79F0 / \u9ED8\u8BA4\u6A21\u578B\uFF09
|
|
51379
51403
|
|
|
51380
51404
|
\u9000\u51FA\u7801\uFF1A
|
|
51381
51405
|
0 \u56DE\u5408\u6B63\u5E38\u6536\u5C3E 1 \u56DE\u5408\u5931\u8D25\u6216\u88AB\u505C\u6B62 2 \u7528\u6CD5\u9519\u8BEF 3 \u672A\u767B\u5F55 / \u51ED\u636E\u5931\u6548
|
|
@@ -88449,6 +88473,47 @@ var plaintextCodec = {
|
|
|
88449
88473
|
decrypt: () => null
|
|
88450
88474
|
};
|
|
88451
88475
|
|
|
88476
|
+
// src/cli/agent-selection.ts
|
|
88477
|
+
var BUILTIN_CLI_AGENT_ID = "cli";
|
|
88478
|
+
function isAgentUnavailable(err) {
|
|
88479
|
+
if (typeof err !== "object" || err === null) return false;
|
|
88480
|
+
const candidate = err;
|
|
88481
|
+
if (candidate.status === 404) return true;
|
|
88482
|
+
const text2 = typeof candidate.message === "string" ? candidate.message : String(err);
|
|
88483
|
+
return /agent[_ -]?not[_ -]?found|不存在.*agent|agent.*不存在|没有可用的 agent/i.test(text2);
|
|
88484
|
+
}
|
|
88485
|
+
async function createSessionForCli(deps) {
|
|
88486
|
+
const explicit = deps.explicit?.trim();
|
|
88487
|
+
if (explicit) {
|
|
88488
|
+
const session = await deps.create(explicit);
|
|
88489
|
+
return { sessionId: session.id, choice: { agentId: explicit, source: "explicit" } };
|
|
88490
|
+
}
|
|
88491
|
+
try {
|
|
88492
|
+
const session = await deps.create(BUILTIN_CLI_AGENT_ID);
|
|
88493
|
+
return {
|
|
88494
|
+
sessionId: session.id,
|
|
88495
|
+
choice: { agentId: BUILTIN_CLI_AGENT_ID, source: "builtin" }
|
|
88496
|
+
};
|
|
88497
|
+
} catch (err) {
|
|
88498
|
+
if (!isAgentUnavailable(err)) throw err;
|
|
88499
|
+
const agents = await deps.listAgents();
|
|
88500
|
+
const first = agents[0]?.id;
|
|
88501
|
+
if (!first) {
|
|
88502
|
+
throw err;
|
|
88503
|
+
}
|
|
88504
|
+
const session = await deps.create(first);
|
|
88505
|
+
return {
|
|
88506
|
+
sessionId: session.id,
|
|
88507
|
+
choice: {
|
|
88508
|
+
agentId: first,
|
|
88509
|
+
source: "fallback-first",
|
|
88510
|
+
warning: `\u8FD9\u4E2A\u670D\u52A1\u7AEF\u6CA1\u6709\u53EF\u7528\u7684\u5185\u7F6E CLI Agent\uFF08id="${BUILTIN_CLI_AGENT_ID}"\uFF09\uFF0C\u5DF2\u6539\u7528 "${first}"\u3002
|
|
88511
|
+
\u26A0\uFE0F \u5B83\u7684\u5DE5\u5177\u6E05\u5355\u91CC\u53EF\u80FD\u6709\u6D4F\u89C8\u5668 / \u6587\u6863\u7F16\u8F91\u5DE5\u5177\uFF0C\u800C\u7EC8\u7AEF\u91CC\u8DD1\u4E0D\u4E86\uFF08\u4F1A\u8FD4\u56DE\u300C\u672C\u5BA2\u6237\u7AEF\u6CA1\u6709\u88C5\u914D\u2026\u901A\u9053\u300D\uFF09\u3002`
|
|
88512
|
+
}
|
|
88513
|
+
};
|
|
88514
|
+
}
|
|
88515
|
+
}
|
|
88516
|
+
|
|
88452
88517
|
// src/core/host.ts
|
|
88453
88518
|
function isNormalEnd(reason) {
|
|
88454
88519
|
return reason === "reply" || reason === "done" || reason === "attached";
|
|
@@ -88979,21 +89044,21 @@ async function runOnce(options) {
|
|
|
88979
89044
|
}
|
|
88980
89045
|
}
|
|
88981
89046
|
if (!sessionId) {
|
|
88982
|
-
const
|
|
88983
|
-
|
|
88984
|
-
|
|
88985
|
-
|
|
88986
|
-
|
|
88987
|
-
|
|
88988
|
-
|
|
88989
|
-
|
|
88990
|
-
|
|
88991
|
-
|
|
88992
|
-
|
|
88993
|
-
|
|
88994
|
-
permission: args.permission ? { preset: args.permission, overrides: {} } : null
|
|
89047
|
+
const { sessionId: created, choice } = await createSessionForCli({
|
|
89048
|
+
explicit: args.agentId,
|
|
89049
|
+
listAgents: () => runtime.api.agentsList(),
|
|
89050
|
+
create: (agentId) => runtime.api.sessionsCreate({
|
|
89051
|
+
agent_id: agentId,
|
|
89052
|
+
model_id: args.modelId ?? null,
|
|
89053
|
+
workspace: args.workspace ?? process.cwd(),
|
|
89054
|
+
// ⭐ 声明原样转出去(`sessionsCreate` 是唯一那一处;D10 静态守着)。
|
|
89055
|
+
// ⚠️ `overrides` 是**必填**(服务端把它整段写进 metadata):CLI 没有逐项微调,
|
|
89056
|
+
// 给空对象 = 「只声明档位,不覆盖任何单项」。⛔ 别传 `null`(那等于没声明)。
|
|
89057
|
+
permission: args.permission ? { preset: args.permission, overrides: {} } : null
|
|
89058
|
+
})
|
|
88995
89059
|
});
|
|
88996
|
-
sessionId =
|
|
89060
|
+
sessionId = created;
|
|
89061
|
+
if (choice.warning) log("warn", choice.warning);
|
|
88997
89062
|
}
|
|
88998
89063
|
} catch (err) {
|
|
88999
89064
|
return await fail2(err, null);
|
|
@@ -89631,20 +89696,19 @@ async function runTui(options) {
|
|
|
89631
89696
|
const runTurn = async (message) => {
|
|
89632
89697
|
if (!sessionId) {
|
|
89633
89698
|
try {
|
|
89634
|
-
const
|
|
89635
|
-
|
|
89636
|
-
|
|
89637
|
-
|
|
89638
|
-
|
|
89639
|
-
|
|
89640
|
-
|
|
89641
|
-
|
|
89642
|
-
|
|
89643
|
-
model_id: options.args.modelId ?? null,
|
|
89644
|
-
workspace: options.args.workspace ?? process.cwd(),
|
|
89645
|
-
permission: options.args.permission ? { preset: options.args.permission, overrides: {} } : null
|
|
89699
|
+
const { sessionId: created, choice } = await createSessionForCli({
|
|
89700
|
+
explicit: options.args.agentId,
|
|
89701
|
+
listAgents: () => runtime.api.agentsList(),
|
|
89702
|
+
create: (agentId) => runtime.api.sessionsCreate({
|
|
89703
|
+
agent_id: agentId,
|
|
89704
|
+
model_id: options.args.modelId ?? null,
|
|
89705
|
+
workspace: options.args.workspace ?? process.cwd(),
|
|
89706
|
+
permission: options.args.permission ? { preset: options.args.permission, overrides: {} } : null
|
|
89707
|
+
})
|
|
89646
89708
|
});
|
|
89647
|
-
sessionId =
|
|
89709
|
+
sessionId = created;
|
|
89710
|
+
agentLabel = choice.agentId;
|
|
89711
|
+
if (choice.warning) commitLive([` \u26A0\uFE0F ${choice.warning.replace(/\n/g, "\n ")}`, ""]);
|
|
89648
89712
|
await runtime.prefs.rememberWorkspace(options.args.workspace ?? process.cwd());
|
|
89649
89713
|
} catch (err) {
|
|
89650
89714
|
commitLive([
|
|
@@ -89866,6 +89930,238 @@ async function printAuthStatus(runtime, options) {
|
|
|
89866
89930
|
`);
|
|
89867
89931
|
return EXIT.OK;
|
|
89868
89932
|
}
|
|
89933
|
+
async function printAgents(runtime, options) {
|
|
89934
|
+
const { writers, json } = options;
|
|
89935
|
+
let agents;
|
|
89936
|
+
try {
|
|
89937
|
+
agents = await runtime.api.agentsList();
|
|
89938
|
+
} catch (err) {
|
|
89939
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
89940
|
+
if (json) {
|
|
89941
|
+
writers.out(`${JSON.stringify({ ok: false, error: message })}
|
|
89942
|
+
`);
|
|
89943
|
+
}
|
|
89944
|
+
writers.err(
|
|
89945
|
+
`protara: \u53D6 Agent \u5217\u8868\u5931\u8D25\uFF1A${message}
|
|
89946
|
+
protara: \u8BF7\u786E\u8BA4\u5DF2\u767B\u5F55\uFF08protara status\uFF09\u4E14\u80FD\u8FDE\u4E0A\u5E73\u53F0\uFF08--origin\uFF09
|
|
89947
|
+
`
|
|
89948
|
+
);
|
|
89949
|
+
return EXIT.TURN_FAILED;
|
|
89950
|
+
}
|
|
89951
|
+
if (json) {
|
|
89952
|
+
writers.out(
|
|
89953
|
+
`${JSON.stringify({
|
|
89954
|
+
ok: true,
|
|
89955
|
+
agents: agents.map((a) => ({ ...a, default: false }))
|
|
89956
|
+
})}
|
|
89957
|
+
`
|
|
89958
|
+
);
|
|
89959
|
+
return EXIT.OK;
|
|
89960
|
+
}
|
|
89961
|
+
if (agents.length === 0) {
|
|
89962
|
+
writers.err("protara: \u8FD9\u4E2A\u8EAB\u4EFD\u6CA1\u6709\u53EF\u7528\u7684 Agent\uFF08\u627E\u7BA1\u7406\u5458\u786E\u8BA4\u6743\u9650\uFF09\n");
|
|
89963
|
+
return EXIT.OK;
|
|
89964
|
+
}
|
|
89965
|
+
writers.out(`\u2713 ${agents.length} \u4E2A\u53EF\u7528 Agent
|
|
89966
|
+
|
|
89967
|
+
`);
|
|
89968
|
+
for (const agent of agents) {
|
|
89969
|
+
writers.out(` ${agent.id.padEnd(18)}${agent.name}
|
|
89970
|
+
`);
|
|
89971
|
+
if (agent.description && agent.description !== agent.name) {
|
|
89972
|
+
writers.out(` ${wrapOneLine(agent.description, 66)}
|
|
89973
|
+
`);
|
|
89974
|
+
}
|
|
89975
|
+
const defaultModel = agent.default_model?.id ?? null;
|
|
89976
|
+
if (defaultModel) writers.out(` \u9ED8\u8BA4\u6A21\u578B ${defaultModel}
|
|
89977
|
+
`);
|
|
89978
|
+
const others = agent.models.map((m) => m.id).filter((id) => id !== defaultModel);
|
|
89979
|
+
if (others.length > 0) {
|
|
89980
|
+
writers.out(` \u53EF\u9009\u6A21\u578B ${wrapOneLine(others.join(", "), 66)}
|
|
89981
|
+
`);
|
|
89982
|
+
}
|
|
89983
|
+
writers.out("\n");
|
|
89984
|
+
}
|
|
89985
|
+
writers.out(
|
|
89986
|
+
` \u5185\u7F6E ${BUILTIN_CLI_AGENT_ID.padEnd(18)}\u7EC8\u7AEF\u4E13\u7528\uFF08\u9ED8\u8BA4\uFF09 \u2190 \u4E0D\u5728\u4E0A\u9762\u7684\u5217\u8868\u91CC
|
|
89987
|
+
`
|
|
89988
|
+
);
|
|
89989
|
+
writers.out("\n");
|
|
89990
|
+
writers.out('\u7528\u6CD5\uFF1Aprotara --agent <id> -p "\u2026" \u6307\u5B9A Agent\uFF08\u7701\u7565 = \u7528\u5185\u7F6E\u7684 cli\uFF09\n');
|
|
89991
|
+
writers.out(' protara --agent <id> --model <id> -p "\u2026" \u540C\u65F6\u6307\u5B9A\u6A21\u578B\n');
|
|
89992
|
+
return EXIT.OK;
|
|
89993
|
+
}
|
|
89994
|
+
function wrapOneLine(text2, width) {
|
|
89995
|
+
const oneLine2 = text2.replace(/\r?\n/g, " ").trim();
|
|
89996
|
+
if (displayWidth(oneLine2) <= width) return oneLine2;
|
|
89997
|
+
const parts = [];
|
|
89998
|
+
let current2 = "";
|
|
89999
|
+
for (const ch of oneLine2) {
|
|
90000
|
+
if (displayWidth(current2 + ch) > width) {
|
|
90001
|
+
parts.push(current2);
|
|
90002
|
+
current2 = ch;
|
|
90003
|
+
} else {
|
|
90004
|
+
current2 += ch;
|
|
90005
|
+
}
|
|
90006
|
+
}
|
|
90007
|
+
if (current2) parts.push(current2);
|
|
90008
|
+
return parts.join("\n ");
|
|
90009
|
+
}
|
|
90010
|
+
function readSecret(prompt, io) {
|
|
90011
|
+
const stdin = io?.stdin ?? process.stdin;
|
|
90012
|
+
const stderr = io?.stderr ?? process.stderr;
|
|
90013
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
90014
|
+
return Promise.reject(
|
|
90015
|
+
new Error(
|
|
90016
|
+
"\u9700\u8981\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u624D\u80FD\u9690\u85CF\u8F93\u5165\u5BC6\u7801\u3002\u811A\u672C\u5316\u767B\u5F55\u8BF7\u7528\u73AF\u5883\u53D8\u91CF PROTARA_PASSWORD\uFF08\u6216 --password-stdin \u4ECE\u7BA1\u9053\u8BFB\uFF09\u3002"
|
|
90017
|
+
)
|
|
90018
|
+
);
|
|
90019
|
+
}
|
|
90020
|
+
stderr.write(prompt);
|
|
90021
|
+
return new Promise((resolve23, reject) => {
|
|
90022
|
+
let buffer = "";
|
|
90023
|
+
const previousRaw = stdin.isRaw;
|
|
90024
|
+
const cleanup = () => {
|
|
90025
|
+
stdin.setRawMode?.(previousRaw ?? false);
|
|
90026
|
+
stdin.removeListener("data", onData);
|
|
90027
|
+
stdin.pause();
|
|
90028
|
+
};
|
|
90029
|
+
const onData = (chunk) => {
|
|
90030
|
+
const text2 = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
90031
|
+
for (const ch of text2) {
|
|
90032
|
+
if (ch === "") {
|
|
90033
|
+
cleanup();
|
|
90034
|
+
stderr.write("\n");
|
|
90035
|
+
reject(new Error("\u5DF2\u53D6\u6D88\uFF08Ctrl-C\uFF09"));
|
|
90036
|
+
return;
|
|
90037
|
+
}
|
|
90038
|
+
if (ch === "\r" || ch === "\n") {
|
|
90039
|
+
cleanup();
|
|
90040
|
+
stderr.write("\n");
|
|
90041
|
+
resolve23(buffer);
|
|
90042
|
+
return;
|
|
90043
|
+
}
|
|
90044
|
+
if (ch === "\x7F" || ch === "\b") {
|
|
90045
|
+
buffer = buffer.slice(0, -1);
|
|
90046
|
+
continue;
|
|
90047
|
+
}
|
|
90048
|
+
if (ch < " ") continue;
|
|
90049
|
+
buffer += ch;
|
|
90050
|
+
}
|
|
90051
|
+
};
|
|
90052
|
+
stdin.setRawMode(true);
|
|
90053
|
+
stdin.resume();
|
|
90054
|
+
stdin.setEncoding("utf8");
|
|
90055
|
+
stdin.on("data", onData);
|
|
90056
|
+
});
|
|
90057
|
+
}
|
|
90058
|
+
async function runPasswordLogin(runtime, options) {
|
|
90059
|
+
const { writers, log } = options;
|
|
90060
|
+
const username = (options.username ?? "").trim() || await askUsername(options);
|
|
90061
|
+
if (!username) {
|
|
90062
|
+
writers.err("protara: usage: \u672A\u63D0\u4F9B\u7528\u6237\u540D\n");
|
|
90063
|
+
writers.err("protara: \u7528 `protara login --password --username <\u540D\u5B57>`\uFF0C\u6216\u4E0D\u5E26 --username \u4EA4\u4E92\u8F93\u5165\n");
|
|
90064
|
+
return EXIT.USAGE;
|
|
90065
|
+
}
|
|
90066
|
+
let password = options.password ?? "";
|
|
90067
|
+
if (!password) {
|
|
90068
|
+
try {
|
|
90069
|
+
const read = options.readPassword ?? ((prompt) => readSecret(prompt));
|
|
90070
|
+
password = await read(`protara: ${username} \u7684\u5BC6\u7801\uFF08\u8F93\u5165\u4E0D\u56DE\u663E\uFF09: `);
|
|
90071
|
+
} catch (err) {
|
|
90072
|
+
writers.err(`protara: auth-failed: ${err instanceof Error ? err.message : String(err)}
|
|
90073
|
+
`);
|
|
90074
|
+
return EXIT.AUTH;
|
|
90075
|
+
}
|
|
90076
|
+
}
|
|
90077
|
+
if (!password) {
|
|
90078
|
+
writers.err("protara: usage: \u5BC6\u7801\u4E3A\u7A7A\n");
|
|
90079
|
+
return EXIT.USAGE;
|
|
90080
|
+
}
|
|
90081
|
+
try {
|
|
90082
|
+
const status = await runtime.auth.loginWithPassword(username, password);
|
|
90083
|
+
log("info", `[auth] \u8D26\u53F7\u5BC6\u7801\u767B\u5F55\u6210\u529F\uFF1A${username}\uFF08channel=password\uFF09`);
|
|
90084
|
+
const who = status.display_name ? `${status.display_name}${status.user_id ? `\uFF08u${status.user_id}\uFF09` : ""}` : `\u7528\u6237 ${username}`;
|
|
90085
|
+
writers.err(`protara: \u5DF2\u767B\u5F55\uFF1A${who}
|
|
90086
|
+
`);
|
|
90087
|
+
writers.err(`protara: \u51ED\u636E\u5199\u5165 ${runtime.store.path()}\uFF08CLI \u72EC\u5360\uFF0C\u4E0E\u684C\u9762\u7AEF\u4E92\u4E0D\u5F71\u54CD\uFF09
|
|
90088
|
+
`);
|
|
90089
|
+
if (status.must_change_password) {
|
|
90090
|
+
writers.err(
|
|
90091
|
+
"protara: \u26A0\uFE0F \u8FD9\u4E2A\u8D26\u53F7\u9996\u6B21\u767B\u5F55\u9700\u8981\u5148\u6539\u5BC6\u7801\u3002\u8BF7\u7528\u6D4F\u89C8\u5668\u767B\u5F55\u5E73\u53F0\u6539\u5BC6\u540E\u518D\u56DE\u6765\u7528 CLI\uFF08\u6539\u5BC6\u524D\u591A\u6570\u64CD\u4F5C\u4F1A\u88AB\u62D2\u7EDD\uFF09\u3002\n"
|
|
90092
|
+
);
|
|
90093
|
+
}
|
|
90094
|
+
return EXIT.OK;
|
|
90095
|
+
} catch (err) {
|
|
90096
|
+
const status = err.status;
|
|
90097
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
90098
|
+
const hasChineseDetail = /[一-龥]/.test(detail) && detail.length >= 4;
|
|
90099
|
+
const hint = hasChineseDetail ? "" : status === 401 ? "\uFF08\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF\uFF09" : status === 403 ? "\uFF08\u8D26\u53F7\u5DF2\u88AB\u7981\u7528\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\uFF09" : status === 422 ? "\uFF08\u7528\u6237\u540D\u6216\u5BC6\u7801\u683C\u5F0F\u4E0D\u5408\u6CD5\uFF09" : "";
|
|
90100
|
+
writers.err(`protara: auth-failed: ${detail}${hint}
|
|
90101
|
+
`);
|
|
90102
|
+
log("warn", `[auth] \u8D26\u53F7\u5BC6\u7801\u767B\u5F55\u5931\u8D25\uFF1A${username} status=${status ?? "?"}`);
|
|
90103
|
+
return EXIT.AUTH;
|
|
90104
|
+
}
|
|
90105
|
+
}
|
|
90106
|
+
async function askUsername(options) {
|
|
90107
|
+
if (options.readUsername) {
|
|
90108
|
+
return (await options.readUsername("protara: \u7528\u6237\u540D: ") ?? "").trim();
|
|
90109
|
+
}
|
|
90110
|
+
const answer = await readLineOnce({ prompt: "protara: \u7528\u6237\u540D: " });
|
|
90111
|
+
return (answer ?? "").trim();
|
|
90112
|
+
}
|
|
90113
|
+
async function readLineOnce(options) {
|
|
90114
|
+
const input = options.input ?? process.stdin;
|
|
90115
|
+
if (!input.isTTY) return null;
|
|
90116
|
+
const output = options.output ?? process.stderr;
|
|
90117
|
+
const { createInterface: createInterface4 } = await import("node:readline");
|
|
90118
|
+
return await new Promise((resolve23) => {
|
|
90119
|
+
const rl = createInterface4({
|
|
90120
|
+
input,
|
|
90121
|
+
output,
|
|
90122
|
+
terminal: options.terminal ?? true
|
|
90123
|
+
});
|
|
90124
|
+
let answered = false;
|
|
90125
|
+
rl.question(options.prompt, (answer) => {
|
|
90126
|
+
answered = true;
|
|
90127
|
+
resolve23(answer);
|
|
90128
|
+
rl.close();
|
|
90129
|
+
});
|
|
90130
|
+
rl.once("close", () => {
|
|
90131
|
+
if (!answered) resolve23(null);
|
|
90132
|
+
});
|
|
90133
|
+
});
|
|
90134
|
+
}
|
|
90135
|
+
async function askLoginMethod(options) {
|
|
90136
|
+
const { writers } = options;
|
|
90137
|
+
const read = options.readLine ?? defaultReadLine;
|
|
90138
|
+
writers.err("protara: \u767B\u5F55\u65B9\u5F0F\uFF1A\n");
|
|
90139
|
+
writers.err(" 1) \u8BBE\u5907\u7801\u767B\u5F55 \u2014\u2014 \u5728\u6D4F\u89C8\u5668\u91CC\u786E\u8BA4\u4E00\u4E0B\uFF08\u6709\u6D4F\u89C8\u5668\u65F6\u6700\u7701\u4E8B\uFF09\n");
|
|
90140
|
+
writers.err(" 2) \u7528\u6237\u540D\u5BC6\u7801\u767B\u5F55 \u2014\u2014 \u6CA1\u6709\u6D4F\u89C8\u5668\u65F6\u7528\uFF08SSH / CI\uFF09\n");
|
|
90141
|
+
writers.err(" \u56DE\u8F66 = 1\uFF08\u8BBE\u5907\u7801\uFF09\n");
|
|
90142
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
90143
|
+
const answer = await read("protara: \u9009\u62E9 [1/2]: ");
|
|
90144
|
+
if (answer === null) {
|
|
90145
|
+
writers.err("\nprotara: \u5DF2\u53D6\u6D88\u767B\u5F55\n");
|
|
90146
|
+
return null;
|
|
90147
|
+
}
|
|
90148
|
+
const text2 = answer.trim();
|
|
90149
|
+
if (text2 === "" || text2 === "1") return "device";
|
|
90150
|
+
if (text2 === "2") return "password";
|
|
90151
|
+
writers.err(`protara: \u53EA\u80FD\u8F93\u5165 1 \u6216 2\uFF08\u6536\u5230 ${JSON.stringify(text2)}\uFF09
|
|
90152
|
+
`);
|
|
90153
|
+
}
|
|
90154
|
+
writers.err("protara: \u8FDE\u7EED 3 \u6B21\u6CA1\u542C\u61C2\uFF0C\u5148\u6309\u8BBE\u5907\u7801\u767B\u5F55\uFF08\u8981\u5BC6\u7801\u767B\u5F55\u8BF7\u52A0 --password\uFF09\n");
|
|
90155
|
+
return "device";
|
|
90156
|
+
}
|
|
90157
|
+
async function defaultReadLine(prompt) {
|
|
90158
|
+
return await readLineOnce({ prompt });
|
|
90159
|
+
}
|
|
90160
|
+
function resolveLoginMethod(method, options) {
|
|
90161
|
+
if (method !== "ask") return method;
|
|
90162
|
+
if (!options.stdinIsTty || !options.stdoutIsTty) return "device";
|
|
90163
|
+
return "ask";
|
|
90164
|
+
}
|
|
89869
90165
|
|
|
89870
90166
|
// src/core/file-logger.ts
|
|
89871
90167
|
var import_promises37 = require("node:fs/promises");
|
|
@@ -89910,25 +90206,47 @@ function createFileLogger(options) {
|
|
|
89910
90206
|
for (let i = keep - 1; i >= 1; i -= 1) {
|
|
89911
90207
|
const from = i === 1 ? file : `${file}.${i - 1}`;
|
|
89912
90208
|
const to = `${file}.${i}`;
|
|
89913
|
-
|
|
89914
|
-
|
|
90209
|
+
if (i === keep - 1) {
|
|
90210
|
+
await (0, import_promises37.unlink)(to).catch(() => {
|
|
89915
90211
|
});
|
|
89916
|
-
await (0, import_promises37.rename)(from, to);
|
|
89917
|
-
} catch {
|
|
89918
90212
|
}
|
|
90213
|
+
await (0, import_promises37.rename)(from, to).catch(() => {
|
|
90214
|
+
});
|
|
89919
90215
|
}
|
|
89920
90216
|
bytes = 0;
|
|
89921
90217
|
};
|
|
89922
|
-
|
|
89923
|
-
|
|
89924
|
-
|
|
90218
|
+
let dirReady = false;
|
|
90219
|
+
const ensureDir = async () => {
|
|
90220
|
+
if (dirReady) return;
|
|
90221
|
+
await (0, import_promises37.mkdir)((0, import_node_path48.dirname)(file), { recursive: true });
|
|
90222
|
+
dirReady = true;
|
|
90223
|
+
};
|
|
90224
|
+
const writeBatch = async (lines) => {
|
|
90225
|
+
if (lines.length === 0) return;
|
|
89925
90226
|
try {
|
|
89926
|
-
await (
|
|
89927
|
-
|
|
89928
|
-
|
|
89929
|
-
|
|
89930
|
-
|
|
89931
|
-
|
|
90227
|
+
await ensureDir();
|
|
90228
|
+
let text2 = "";
|
|
90229
|
+
for (const line of lines) {
|
|
90230
|
+
const formatted = format(line);
|
|
90231
|
+
const lineSize = Buffer.byteLength(formatted, "utf8");
|
|
90232
|
+
if (bytes + Buffer.byteLength(text2, "utf8") + lineSize > maxBytes) {
|
|
90233
|
+
if (text2) {
|
|
90234
|
+
await (0, import_promises37.appendFile)(file, text2, "utf8");
|
|
90235
|
+
bytes += Buffer.byteLength(text2, "utf8");
|
|
90236
|
+
text2 = "";
|
|
90237
|
+
}
|
|
90238
|
+
await rotateIfNeeded(lineSize);
|
|
90239
|
+
}
|
|
90240
|
+
text2 += formatted;
|
|
90241
|
+
}
|
|
90242
|
+
if (text2) {
|
|
90243
|
+
await (0, import_promises37.appendFile)(file, text2, "utf8");
|
|
90244
|
+
bytes += Buffer.byteLength(text2, "utf8");
|
|
90245
|
+
}
|
|
90246
|
+
written += lines.length;
|
|
90247
|
+
if (options.alsoStderr) {
|
|
90248
|
+
for (const line of lines) stderrWrite(format(line));
|
|
90249
|
+
}
|
|
89932
90250
|
} catch (err) {
|
|
89933
90251
|
if (!reportedFailure) {
|
|
89934
90252
|
reportedFailure = true;
|
|
@@ -89940,6 +90258,23 @@ function createFileLogger(options) {
|
|
|
89940
90258
|
}
|
|
89941
90259
|
}
|
|
89942
90260
|
};
|
|
90261
|
+
let buffer = [];
|
|
90262
|
+
const MAX_BATCH = 64;
|
|
90263
|
+
let flushScheduled = false;
|
|
90264
|
+
const flushBuffer = () => {
|
|
90265
|
+
if (buffer.length === 0) return;
|
|
90266
|
+
const batch = buffer;
|
|
90267
|
+
buffer = [];
|
|
90268
|
+
tail = tail.then(() => writeBatch(batch));
|
|
90269
|
+
};
|
|
90270
|
+
const scheduleFlush = () => {
|
|
90271
|
+
if (flushScheduled) return;
|
|
90272
|
+
flushScheduled = true;
|
|
90273
|
+
queueMicrotask(() => {
|
|
90274
|
+
flushScheduled = false;
|
|
90275
|
+
flushBuffer();
|
|
90276
|
+
});
|
|
90277
|
+
};
|
|
89943
90278
|
const log = (level, message, detail) => {
|
|
89944
90279
|
if (!enabled) return;
|
|
89945
90280
|
const suffix = detailTextOf(detail);
|
|
@@ -89947,8 +90282,9 @@ function createFileLogger(options) {
|
|
|
89947
90282
|
const stack = level === "error" && detail instanceof Error && detail.stack ? `
|
|
89948
90283
|
${detail.stack.split("\n").slice(1).join("\n ")}` : "";
|
|
89949
90284
|
const text2 = suffix ? `${message} ${suffix}${stack}` : `${message}${stack}`;
|
|
89950
|
-
|
|
89951
|
-
|
|
90285
|
+
buffer.push({ level, text: text2 });
|
|
90286
|
+
if (buffer.length >= MAX_BATCH) flushBuffer();
|
|
90287
|
+
else scheduleFlush();
|
|
89952
90288
|
};
|
|
89953
90289
|
return {
|
|
89954
90290
|
log,
|
|
@@ -89957,6 +90293,7 @@ function createFileLogger(options) {
|
|
|
89957
90293
|
written: () => written,
|
|
89958
90294
|
flush: async (timeoutMs = 2e3) => {
|
|
89959
90295
|
const deadline = Date.now() + timeoutMs;
|
|
90296
|
+
flushBuffer();
|
|
89960
90297
|
await Promise.race([
|
|
89961
90298
|
initialSizeDone,
|
|
89962
90299
|
new Promise((resolve23) => setTimeout(resolve23, Math.max(0, deadline - Date.now())).unref?.())
|
|
@@ -90065,7 +90402,7 @@ function installCrashLogging(log) {
|
|
|
90065
90402
|
|
|
90066
90403
|
// src/cli/index.ts
|
|
90067
90404
|
function cliVersion() {
|
|
90068
|
-
if ("0.3.
|
|
90405
|
+
if ("0.3.7") return "0.3.7";
|
|
90069
90406
|
try {
|
|
90070
90407
|
const here = typeof __dirname === "string" ? `${__dirname}/../../package.json` : "";
|
|
90071
90408
|
const pkg = JSON.parse((0, import_node_fs11.readFileSync)(here, "utf8"));
|
|
@@ -90114,6 +90451,37 @@ async function main(argv, env = process.env) {
|
|
|
90114
90451
|
return EXIT.OK;
|
|
90115
90452
|
}
|
|
90116
90453
|
if (args.command === "login" || args.login) {
|
|
90454
|
+
let method = resolveLoginMethod(args.loginMethod, {
|
|
90455
|
+
stdinIsTty: Boolean(process.stdin.isTTY),
|
|
90456
|
+
stdoutIsTty: Boolean(process.stdout.isTTY)
|
|
90457
|
+
});
|
|
90458
|
+
if (method === "ask") {
|
|
90459
|
+
const choice = await askLoginMethod({ writers });
|
|
90460
|
+
if (choice === null) return EXIT.AUTH;
|
|
90461
|
+
method = choice;
|
|
90462
|
+
}
|
|
90463
|
+
if (method === "password") {
|
|
90464
|
+
let password = null;
|
|
90465
|
+
if (args.passwordFromStdin) {
|
|
90466
|
+
password = await readPasswordFromStdin();
|
|
90467
|
+
if (password === null) {
|
|
90468
|
+
writers.err("protara: usage: --password-stdin \u4F46\u6CA1\u6709\u4ECE stdin \u8BFB\u5230\u5185\u5BB9\n");
|
|
90469
|
+
return EXIT.USAGE;
|
|
90470
|
+
}
|
|
90471
|
+
} else if (env.PROTARA_PASSWORD) {
|
|
90472
|
+
password = env.PROTARA_PASSWORD;
|
|
90473
|
+
}
|
|
90474
|
+
return await withRuntime(
|
|
90475
|
+
args,
|
|
90476
|
+
(runtime) => runPasswordLogin(runtime, {
|
|
90477
|
+
writers,
|
|
90478
|
+
log,
|
|
90479
|
+
username: args.username,
|
|
90480
|
+
password
|
|
90481
|
+
}),
|
|
90482
|
+
{ writers, log, flushLog: () => fileLogger.flush() }
|
|
90483
|
+
);
|
|
90484
|
+
}
|
|
90117
90485
|
return await withRuntime(args, (runtime) => runDeviceLogin(runtime, { writers, log }), {
|
|
90118
90486
|
writers,
|
|
90119
90487
|
log,
|
|
@@ -90134,6 +90502,13 @@ async function main(argv, env = process.env) {
|
|
|
90134
90502
|
flushLog: () => fileLogger.flush()
|
|
90135
90503
|
});
|
|
90136
90504
|
}
|
|
90505
|
+
if (args.command === "agents") {
|
|
90506
|
+
return await withRuntime(args, (runtime) => printAgents(runtime, { writers, json: args.mode === "json" }), {
|
|
90507
|
+
writers,
|
|
90508
|
+
log,
|
|
90509
|
+
flushLog: () => fileLogger.flush()
|
|
90510
|
+
});
|
|
90511
|
+
}
|
|
90137
90512
|
const resolved = resolveMode(args, {
|
|
90138
90513
|
stdoutIsTty: Boolean(process.stdout.isTTY),
|
|
90139
90514
|
stdinIsTty: Boolean(process.stdin.isTTY)
|
|
@@ -90287,6 +90662,16 @@ function readStdin(timeoutMs = 0) {
|
|
|
90287
90662
|
process.stdin.on("error", finish3);
|
|
90288
90663
|
});
|
|
90289
90664
|
}
|
|
90665
|
+
async function readPasswordFromStdin() {
|
|
90666
|
+
const chunks = [];
|
|
90667
|
+
for await (const chunk of process.stdin) {
|
|
90668
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
90669
|
+
if (Buffer.concat(chunks).includes(10)) break;
|
|
90670
|
+
}
|
|
90671
|
+
const text2 = Buffer.concat(chunks).toString("utf8");
|
|
90672
|
+
if (!text2) return null;
|
|
90673
|
+
return text2.replace(/\r?\n[\s\S]*$/, "");
|
|
90674
|
+
}
|
|
90290
90675
|
function makeAsk() {
|
|
90291
90676
|
if (!process.stdin.isTTY) return void 0;
|
|
90292
90677
|
return (question) => new Promise((resolve23) => {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"//name": "⭐ 裸包名(2026-11-12 查过 registry.npmjs.org 上未被占用)。⚠️ npm 包名必须**全小写** —— 上级目录那个 `Protara`(大写 P)在 npm 上是**非法**的,`npm publish` 会直接 400。这也是必须拆独立包的原因之一。",
|
|
3
3
|
"name": "protara",
|
|
4
4
|
"//version": "⚠️ **由 `scripts/publish-cli.mjs` 在打包时写入**(取 `desktop-app/package.json` 的版本),⛔ 不要手改:两处版本漂移的症状是「npm 上是 0.3.6、包里 `--version` 打印 0.3.5」,而两个都看起来正常。",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.7",
|
|
6
6
|
"description": "Protara 的终端客户端:在终端里与 Agent 对话、跑本机工具、审批权限变更。无 GUI、可管道、退出码规范。",
|
|
7
7
|
"//type": "CLI 产物是 CJS(`dist/cli/index.cjs`),而 bin 垫片是 ESM(`.mjs`)。`type: module` 让 `.mjs` 名正言顺,同时 `.cjs` 扩展名保证 CJS 解析。",
|
|
8
8
|
"type": "module",
|
|
@@ -40,4 +40,4 @@
|
|
|
40
40
|
"terminal",
|
|
41
41
|
"desktop-agents"
|
|
42
42
|
]
|
|
43
|
-
}
|
|
43
|
+
}
|