toc-ocra 0.2.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 +21 -0
- package/README.md +70 -0
- package/assets/icon.svg +1 -0
- package/dist/plugin/renderer.css +2 -0
- package/dist/plugin/renderer.css.map +7 -0
- package/dist/plugin/renderer.js +2 -0
- package/dist/plugin/renderer.js.map +7 -0
- package/dist/src/config.d.ts +66 -0
- package/dist/src/config.js +173 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/control/pairing.d.ts +44 -0
- package/dist/src/control/pairing.js +99 -0
- package/dist/src/control/pairing.js.map +1 -0
- package/dist/src/exec/script-runner.d.ts +9 -0
- package/dist/src/exec/script-runner.js +110 -0
- package/dist/src/exec/script-runner.js.map +1 -0
- package/dist/src/health/server.d.ts +74 -0
- package/dist/src/health/server.js +248 -0
- package/dist/src/health/server.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +173 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logging/diagnostic-log.d.ts +16 -0
- package/dist/src/logging/diagnostic-log.js +128 -0
- package/dist/src/logging/diagnostic-log.js.map +1 -0
- package/dist/src/providers/cli/claude.d.ts +2 -0
- package/dist/src/providers/cli/claude.js +48 -0
- package/dist/src/providers/cli/claude.js.map +1 -0
- package/dist/src/providers/cli/codex.d.ts +2 -0
- package/dist/src/providers/cli/codex.js +67 -0
- package/dist/src/providers/cli/codex.js.map +1 -0
- package/dist/src/providers/cli/gemini.d.ts +2 -0
- package/dist/src/providers/cli/gemini.js +30 -0
- package/dist/src/providers/cli/gemini.js.map +1 -0
- package/dist/src/providers/cli/grok.d.ts +2 -0
- package/dist/src/providers/cli/grok.js +29 -0
- package/dist/src/providers/cli/grok.js.map +1 -0
- package/dist/src/providers/cli/opencode.d.ts +2 -0
- package/dist/src/providers/cli/opencode.js +29 -0
- package/dist/src/providers/cli/opencode.js.map +1 -0
- package/dist/src/providers/detection.d.ts +11 -0
- package/dist/src/providers/detection.js +96 -0
- package/dist/src/providers/detection.js.map +1 -0
- package/dist/src/providers/model-catalog-collector.d.ts +22 -0
- package/dist/src/providers/model-catalog-collector.js +66 -0
- package/dist/src/providers/model-catalog-collector.js.map +1 -0
- package/dist/src/providers/model-catalog.d.ts +45 -0
- package/dist/src/providers/model-catalog.js +382 -0
- package/dist/src/providers/model-catalog.js.map +1 -0
- package/dist/src/providers/provider.d.ts +49 -0
- package/dist/src/providers/provider.js +63 -0
- package/dist/src/providers/provider.js.map +1 -0
- package/dist/src/providers/registry.d.ts +31 -0
- package/dist/src/providers/registry.js +60 -0
- package/dist/src/providers/registry.js.map +1 -0
- package/dist/src/runs/orphan-registry.d.ts +24 -0
- package/dist/src/runs/orphan-registry.js +63 -0
- package/dist/src/runs/orphan-registry.js.map +1 -0
- package/dist/src/runs/process-tree.d.ts +21 -0
- package/dist/src/runs/process-tree.js +95 -0
- package/dist/src/runs/process-tree.js.map +1 -0
- package/dist/src/runs/run-supervisor.d.ts +63 -0
- package/dist/src/runs/run-supervisor.js +285 -0
- package/dist/src/runs/run-supervisor.js.map +1 -0
- package/dist/src/runtime/agent-runtime.d.ts +68 -0
- package/dist/src/runtime/agent-runtime.js +340 -0
- package/dist/src/runtime/agent-runtime.js.map +1 -0
- package/dist/src/runtime/agent-service.d.ts +33 -0
- package/dist/src/runtime/agent-service.js +181 -0
- package/dist/src/runtime/agent-service.js.map +1 -0
- package/dist/src/runtime/cli/hydrate-shell-path.d.ts +33 -0
- package/dist/src/runtime/cli/hydrate-shell-path.js +173 -0
- package/dist/src/runtime/cli/hydrate-shell-path.js.map +1 -0
- package/dist/src/session/terminal-line-sanitizer.d.ts +7 -0
- package/dist/src/session/terminal-line-sanitizer.js +21 -0
- package/dist/src/session/terminal-line-sanitizer.js.map +1 -0
- package/dist/src/session/terminal-tail-buffer.d.ts +70 -0
- package/dist/src/session/terminal-tail-buffer.js +149 -0
- package/dist/src/session/terminal-tail-buffer.js.map +1 -0
- package/dist/src/shared/cli-detection-types.d.ts +19 -0
- package/dist/src/shared/cli-detection-types.js +2 -0
- package/dist/src/shared/cli-detection-types.js.map +1 -0
- package/dist/src/shared/node-cli-command-resolution.d.ts +19 -0
- package/dist/src/shared/node-cli-command-resolution.js +173 -0
- package/dist/src/shared/node-cli-command-resolution.js.map +1 -0
- package/dist/src/transport/gateway-client.d.ts +38 -0
- package/dist/src/transport/gateway-client.js +73 -0
- package/dist/src/transport/gateway-client.js.map +1 -0
- package/dist/src/types/protocol.d.ts +172 -0
- package/dist/src/types/protocol.js +55 -0
- package/dist/src/types/protocol.js.map +1 -0
- package/dist/src/workspace/directory-browser.d.ts +17 -0
- package/dist/src/workspace/directory-browser.js +47 -0
- package/dist/src/workspace/directory-browser.js.map +1 -0
- package/dist/src/workspace/workspace-manager.d.ts +41 -0
- package/dist/src/workspace/workspace-manager.js +436 -0
- package/dist/src/workspace/workspace-manager.js.map +1 -0
- package/locales/en.json +66 -0
- package/locales/zh-CN.json +66 -0
- package/package.json +90 -0
- package/scripts/service.mjs +174 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 api_ocra contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# toc-ocra
|
|
2
|
+
|
|
3
|
+
运行在用户电脑上的 CLI 执行端。它接收 echo_admin 派发的任务,在本机以无界面模式运行 codex / claude / grok / gemini / opencode 等 CLI,并把运行状态和结果报回服务端。它也可以在设备开启相应开关后执行脚本和 SQL(SQL 执行器目前是预留接口)。
|
|
4
|
+
|
|
5
|
+
参照 Orca 和 `api/api_ocra` 重构而来;`api_ocra` 保持原样不动。
|
|
6
|
+
|
|
7
|
+
## 快速开始(本地开发)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
npm run build
|
|
12
|
+
|
|
13
|
+
# 1. 在 Echo 的「设备」页,打开设备的「连接与 CLI」,点「生成配对码」
|
|
14
|
+
# 2. 在这台电脑上配对(默认后端 http://127.0.0.1:3000)
|
|
15
|
+
node dist/src/index.js enroll --code ABCD-EFGH
|
|
16
|
+
# 3. 启动
|
|
17
|
+
node dist/src/index.js run
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
全局安装后命令是 `toc-ocra`:`toc-ocra enroll --code …`、`toc-ocra run`、`toc-ocra service:install`(安装为后台服务)。
|
|
21
|
+
|
|
22
|
+
其它命令:`toc-ocra code`(查看插件连接码)、`toc-ocra detect`(本机 CLI 探测)、`toc-ocra doctor`(配置 + 探测)、`toc-ocra version`。
|
|
23
|
+
|
|
24
|
+
## 桌面插件「设备代理」
|
|
25
|
+
|
|
26
|
+
同一个 npm 包也是 Echo 桌面插件 `echo.device-agent`(`package.json › tocPlugin`,界面源码在 `plugin/`,构建产物 `dist/plugin/renderer.js`)。插件本身不启动任何程序:
|
|
27
|
+
|
|
28
|
+
1. 用户自己启动本机服务:`npx toc-ocra@latest run`,或 `npm i -g toc-ocra && toc-ocra service:install`。未绑定时服务也会启动,等待插件绑定。
|
|
29
|
+
2. 插件通过 `http://127.0.0.1:17339` 连接本机服务,首次输入服务显示的 6 位连接码(`toc-ocra code` 可再次查看),换得本机控制令牌并保存在插件的安全存储里。
|
|
30
|
+
3. 插件以当前登录用户身份在当前工作空间创建(或选择)设备、生成配对码,交给本机服务完成绑定;也可以解除绑定、重新检测 CLI。
|
|
31
|
+
|
|
32
|
+
本机控制接口(`/control/*`)只接受 `127.0.0.1` / `localhost` 的 Host 和来源,除 `info`、`pair` 外都要 `Authorization: Bearer <控制令牌>`。控制令牌保存在数据目录的 `control-token`(0600),连接码在 `connection-code`(0600,使用后或连续输错 5 次后更换)。
|
|
33
|
+
|
|
34
|
+
发布:`npm run build && npm run validate:plugin`,再 `npm publish`;管理员在「系统管理 › 插件目录」收录包名 `toc-ocra` 并批准版本后,用户即可在插件市场安装。
|
|
35
|
+
|
|
36
|
+
## 配置
|
|
37
|
+
|
|
38
|
+
优先级:环境变量 > 配置文件(`enroll` 写入,权限 0600)> 默认值。
|
|
39
|
+
|
|
40
|
+
| 环境变量 | 配置文件键 | 默认 | 说明 |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `TOC_OCRA_SERVER_URL` | `serverUrl` | `http://127.0.0.1:3000` | echo_admin 地址 |
|
|
43
|
+
| `TOC_OCRA_TOKEN` | `token` | — | 设备凭据,配对时写入 |
|
|
44
|
+
| `TOC_OCRA_ALLOWED_ROOTS` | `allowedRoots` | 用户主目录 | CLI 可使用的工作目录根,PATH 风格列表 |
|
|
45
|
+
| `TOC_OCRA_MAX_RUNS` | `maxConcurrentRuns` | 4 | 本机同时运行的 CLI 回合上限 |
|
|
46
|
+
| `TOC_OCRA_MAX_RUN_SECONDS` | `maxRunSeconds` | 14400 | 单个回合最长时间,超时结束 |
|
|
47
|
+
| `TOC_OCRA_DISABLE_SCRIPT` / `TOC_OCRA_DISABLE_SQL` | `disableScript` / `disableSql` | false | 本机强制关闭脚本 / SQL(优先于服务端开关) |
|
|
48
|
+
| `TOC_OCRA_DATA_DIR` / `TOC_OCRA_CONFIG` | — | 见下 | 数据目录 / 配置文件路径 |
|
|
49
|
+
| `TOC_OCRA_HEALTH_HOST` / `TOC_OCRA_HEALTH_PORT` | `healthHost` / `healthPort` | 127.0.0.1 / 17339 | 本机状态页(仅本机可访问) |
|
|
50
|
+
| `TOC_OCRA_LOG_LEVEL` | `logLevel` | info | 诊断日志级别 |
|
|
51
|
+
| `TOC_OCRA_CODEX_PATH` 等 | — | 自动查找 | 指定某个 CLI 的可执行文件 |
|
|
52
|
+
|
|
53
|
+
数据目录:macOS `~/Library/Application Support/toc-ocra`,Linux `~/.local/state/toc-ocra`,Windows `%APPDATA%\toc-ocra`。与旧的 `echo-cli-agent` 互不影响,可以同时安装。
|
|
54
|
+
|
|
55
|
+
## 设计要点
|
|
56
|
+
|
|
57
|
+
- **不保持长连接**:每 30 秒一次心跳,另有一个长轮询拉指令(有指令立即返回,最长等 25 秒)。超过 90 秒没有心跳,服务端判定设备离线。
|
|
58
|
+
- **一个回合一个进程,进程退出即完成**:各 CLI 用无界面 JSON 模式运行,从输出中解析原生会话 ID 和最终回复。如果 CLI 已声明本轮结束、但进程 15 秒内没退出,会被结束,避免"做完了还占着工位"。
|
|
59
|
+
- **停止会结束整棵进程树**:先对进程组和所有子孙进程发 SIGTERM,3 秒后仍存活的发 SIGKILL(Windows 用 `taskkill /T /F`)。
|
|
60
|
+
- **服务端对账兜底**:心跳会上报本机正在运行的任务,服务端据此释放本机已经不存在的运行;Agent 崩溃重启时,会清理上次遗留的 CLI 进程。
|
|
61
|
+
- **加一个 CLI**:在 `src/providers/cli/` 新增一个文件,实现 `HeadlessProvider`(启动参数 + 解析每一行输出),再在 `src/providers/registry.ts` 加一行。
|
|
62
|
+
|
|
63
|
+
详见 [docs/architecture.md](docs/architecture.md)。
|
|
64
|
+
|
|
65
|
+
## 开发
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run typecheck
|
|
69
|
+
npm test # 包含用假 codex 测试停止杀进程树、完成后进程不退出等场景
|
|
70
|
+
```
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/><path d="m8.5 9 2 1.5-2 1.5M12.5 12h3"/></svg>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.da-app{--da-border: var(--border, rgba(127, 127, 127, .25));--da-muted: var(--muted, #888);--da-danger: var(--danger, #dc2626);display:grid;gap:12px;padding:20px 24px 40px;max-width:880px;color:var(--text)}.da-app>*{min-width:0}.da-text{margin:6px 0 10px;font-size:13px;line-height:1.6}.da-muted{margin:6px 0 0;font-size:12px;line-height:1.6;color:var(--da-muted)}.da-step{margin:10px 0 6px;font-size:12px;font-weight:600}.da-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;word-break:break-all}.da-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:10px}.da-command{display:flex;gap:8px;align-items:center}.da-command code{flex:1;min-width:0;overflow-x:auto;white-space:nowrap;padding:7px 10px;border:1px solid var(--da-border);border-radius:var(--radius-md, 8px);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.da-code-input{width:160px;letter-spacing:.3em;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.da-alert{padding:8px 12px;border:1px solid var(--da-danger);border-radius:var(--radius-md, 8px);color:var(--da-danger);font-size:12.5px}.da-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px 16px;margin:8px 0 0}.da-grid dt{font-size:11.5px;color:var(--da-muted)}.da-grid dd{margin:3px 0 0;font-size:13px}.da-list{list-style:none;margin:8px 0 0;padding:0;display:grid;gap:6px}.da-list li{display:flex;gap:10px;align-items:center;font-size:13px}.da-field{display:grid;gap:4px;margin-top:10px;font-size:12px}.da-field>span{color:var(--da-muted)}.da-select{height:32px;padding:0 8px;border:1px solid var(--da-border);border-radius:var(--radius-md, 8px);background:transparent;color:inherit;font:inherit;font-size:13px}
|
|
2
|
+
/*# sourceMappingURL=renderer.css.map */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../plugin/styles.css"],
|
|
4
|
+
"sourcesContent": ["/* Device agent plugin. Every class is prefixed `da-`; colors come from host variables. */\n.da-app {\n --da-border: var(--border, rgba(127, 127, 127, 0.25));\n --da-muted: var(--muted, #888);\n --da-danger: var(--danger, #dc2626);\n display: grid;\n gap: 12px;\n padding: 20px 24px 40px;\n max-width: 880px;\n color: var(--text);\n}\n.da-app > * { min-width: 0; }\n.da-text { margin: 6px 0 10px; font-size: 13px; line-height: 1.6; }\n.da-muted { margin: 6px 0 0; font-size: 12px; line-height: 1.6; color: var(--da-muted); }\n.da-step { margin: 10px 0 6px; font-size: 12px; font-weight: 600; }\n.da-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; word-break: break-all; }\n.da-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 10px; }\n.da-command { display: flex; gap: 8px; align-items: center; }\n.da-command code {\n flex: 1;\n min-width: 0;\n overflow-x: auto;\n white-space: nowrap;\n padding: 7px 10px;\n border: 1px solid var(--da-border);\n border-radius: var(--radius-md, 8px);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n}\n.da-code-input { width: 160px; letter-spacing: 0.3em; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n.da-alert {\n padding: 8px 12px;\n border: 1px solid var(--da-danger);\n border-radius: var(--radius-md, 8px);\n color: var(--da-danger);\n font-size: 12.5px;\n}\n.da-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px 16px; margin: 8px 0 0; }\n.da-grid dt { font-size: 11.5px; color: var(--da-muted); }\n.da-grid dd { margin: 3px 0 0; font-size: 13px; }\n.da-list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 6px; }\n.da-list li { display: flex; gap: 10px; align-items: center; font-size: 13px; }\n.da-field { display: grid; gap: 4px; margin-top: 10px; font-size: 12px; }\n.da-field > span { color: var(--da-muted); }\n.da-select {\n height: 32px;\n padding: 0 8px;\n border: 1px solid var(--da-border);\n border-radius: var(--radius-md, 8px);\n background: transparent;\n color: inherit;\n font: inherit;\n font-size: 13px;\n}\n"],
|
|
5
|
+
"mappings": "AACA,CAAC,OACC,aAAa,IAAI,QAAQ,EAAE,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAC/C,YAAY,IAAI,OAAO,EAAE,MACzB,aAAa,IAAI,QAAQ,EAAE,SAC3B,QAAS,KACT,IAAK,KANP,QAOW,KAAK,KAAK,KACnB,UAAW,MACX,MAAO,IAAI,OACb,CACA,CAVC,MAUO,CAAE,EAAI,UAAW,CAAG,CAC5B,CAAC,QAZD,OAYmB,IAAI,EAAE,KAAM,UAAW,KAAM,YAAa,GAAK,CAClE,CAAC,SAbD,OAaoB,IAAI,EAAE,EAAG,UAAW,KAAM,YAAa,IAAK,MAAO,IAAI,WAAa,CACxF,CAAC,QAdD,OAcmB,KAAK,EAAE,IAAK,UAAW,KAAM,YAAa,GAAK,CAClE,CAAC,QAAU,YAAa,YAAY,CAAE,cAAc,CAAE,KAAK,CAAE,UAAW,UAAW,KAAM,WAAY,SAAW,CAChH,CAAC,OAAS,QAAS,KAAM,UAAW,KAAM,IAAK,IAAK,YAAa,OAAQ,WAAY,IAAM,CAC3F,CAAC,WAAa,QAAS,KAAM,IAAK,IAAK,YAAa,MAAQ,CAC5D,CADC,WACW,KACV,KAAM,EACN,UAAW,EACX,WAAY,KACZ,YAAa,OAtBf,QAuBW,IAAI,KACb,OAAQ,IAAI,MAAM,IAAI,aACtB,cAAe,IAAI,WAAW,EAAE,KAChC,YAAa,YAAY,CAAE,cAAc,CAAE,KAAK,CAAE,UAClD,UAAW,IACb,CACA,CAAC,cAAgB,MAAO,MAAO,eAAgB,KAAO,YAAa,YAAY,CAAE,cAAc,CAAE,KAAK,CAAE,SAAW,CACnH,CAAC,SA9BD,QA+BW,IAAI,KACb,OAAQ,IAAI,MAAM,IAAI,aACtB,cAAe,IAAI,WAAW,EAAE,KAChC,MAAO,IAAI,aACX,UAAW,MACb,CACA,CAAC,QAAU,QAAS,KAAM,sBAAuB,OAAO,SAAS,CAAE,OAAO,KAAK,CAAE,MAAO,IAAK,KAAK,KArClG,OAqCgH,IAAI,EAAE,CAAG,CACzH,CADC,QACQ,GAAK,UAAW,OAAQ,MAAO,IAAI,WAAa,CACzD,CAFC,QAEQ,GAvCT,OAuCsB,IAAI,EAAE,EAAG,UAAW,IAAM,CAChD,CAAC,QAAU,WAAY,KAxCvB,OAwCqC,IAAI,EAAE,EAxC3C,QAwCuD,EAAG,QAAS,KAAM,IAAK,GAAK,CACnF,CADC,QACQ,GAAK,QAAS,KAAM,IAAK,KAAM,YAAa,OAAQ,UAAW,IAAM,CAC9E,CAAC,SAAW,QAAS,KAAM,IAAK,IAAK,WAAY,KAAM,UAAW,IAAM,CACxE,CADC,QACS,CAAE,KAAO,MAAO,IAAI,WAAa,CAC3C,CAAC,UACC,OAAQ,KA7CV,QA8CW,EAAE,IACX,OAAQ,IAAI,MAAM,IAAI,aACtB,cAAe,IAAI,WAAW,EAAE,KAChC,WAAY,YACZ,MAAO,QACP,KAAM,QACN,UAAW,IACb",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var Y=Object.create;var B=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Z=Object.getPrototypeOf,ee=Object.prototype.hasOwnProperty;var P=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(r){throw t=0,r}};var ne=(e,t,r,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of X(t))!ee.call(e,i)&&i!==r&&B(e,i,{get:()=>t[i],enumerable:!(a=Q(t,i))||a.enumerable});return e};var S=(e,t,r)=>(r=e!=null?Y(Z(e)):{},ne(t||!e||!e.__esModule?B(r,"default",{value:e,enumerable:!0}):r,e));var L=P((me,$)=>{var I=globalThis.__TOC_PLUGIN_SHARED__,D=I&&I.react;if(!D)throw new Error("The host does not provide react; this plugin needs a newer app version.");$.exports=D});var q=P((ve,F)=>{var M=globalThis.__TOC_PLUGIN_SHARED__,j=M&&M["@toc-starter/plugin-sdk/ui"];if(!j)throw new Error("The host does not provide @toc-starter/plugin-sdk/ui; this plugin needs a newer app version.");F.exports=j});var U=P((ye,K)=>{var G=globalThis.__TOC_PLUGIN_SHARED__,J=G&&G["react/jsx-runtime"];if(!J)throw new Error("The host does not provide react/jsx-runtime; this plugin needs a newer app version.");K.exports=J});var s=S(L(),1);var A=S(L(),1);function H(e){if(!e||typeof e.activate!="function")throw new TypeError("definePlugin() needs an object with an activate(context) function");return e}function T(e,t){return(0,A.useSyncExternalStore)(r=>e.core.onContextChange(t,r),()=>e.core.context(t))}var o=S(q(),1);var W="http://127.0.0.1:17339",f=class extends Error{constructor(r,a,i=""){super(a);this.kind=r;this.code=i}kind;code};async function _(e,t,r={}){let a=new Headers(r.headers);r.token&&a.set("Authorization",`Bearer ${r.token}`),r.body&&a.set("Content-Type","application/json");let i;try{i=await fetch(`${e.replace(/\/+$/,"")}${t}`,{...r,headers:a,cache:"no-store"})}catch{throw new f("offline","local service unreachable")}let p=await i.json().catch(()=>null);if(i.status===401)throw new f("unauthorized","unauthorized");if(!i.ok){let d=typeof p?.error=="string"?p.error:"",u=typeof p?.message=="string"?p.message:d||`HTTP ${i.status}`;throw new f("rejected",u,d)}return p}var y={info:e=>_(e,"/control/info"),pair:(e,t)=>_(e,"/control/pair",{method:"POST",body:JSON.stringify({code:t})}),status:(e,t)=>_(e,"/control/status",{token:t}),enroll:(e,t,r,a)=>_(e,"/control/enroll",{method:"POST",token:t,body:JSON.stringify({server_url:r,code:a})}),unpair:(e,t)=>_(e,"/control/unpair",{method:"POST",token:t}),detect:(e,t)=>_(e,"/control/detect",{method:"POST",token:t})};function E(e,t){let r=a=>{try{return new URL(a).origin.toLowerCase()}catch{return a.trim().replace(/\/+$/,"").toLowerCase()}};return!!(e&&t)&&r(e)===r(t)}var n=S(U(),1),z="controlToken",te="localUrl",re=3e3,ae=new URL("../../assets/icon.svg",import.meta.url).href;function oe(){return(0,n.jsx)("img",{src:ae,alt:"",width:16,height:16,draggable:!1})}function ie(e,t){return t instanceof f&&t.kind==="offline"?e.t("error.offline"):t instanceof Error?t.message:String(t)}function R({ctx:e,value:t}){let[r,a]=(0,s.useState)(!1);return(0,n.jsxs)("div",{className:"da-command",children:[(0,n.jsx)("code",{children:t}),(0,n.jsx)(o.Btn,{onClick:()=>{navigator.clipboard.writeText(t).then(()=>{a(!0),window.setTimeout(()=>a(!1),1500)})},children:r?e.t("common.copied"):e.t("common.copy")})]})}function de({ctx:e}){let t=T(e,"server"),r=T(e,"workspace"),[a]=(0,s.useState)(()=>e.storage.get(te,W)),[i,p]=(0,s.useState)(()=>e.secureStorage.get(z,"")),[d,u]=(0,s.useState)({kind:"checking"}),[h,b]=(0,s.useState)(""),[k,g]=(0,s.useState)(!1),m=(0,s.useRef)(!0),C=(0,s.useCallback)(async()=>{try{await y.info(a)}catch{m.current&&u({kind:"offline"});return}if(!i){m.current&&u({kind:"needsCode"});return}try{let l=await y.status(a,i);m.current&&u({kind:"ready",status:l})}catch(l){if(!m.current)return;l instanceof f&&l.kind==="unauthorized"?(e.secureStorage.remove(z),p(""),u({kind:"needsCode"})):u({kind:"offline"})}},[e,a,i]);(0,s.useEffect)(()=>{m.current=!0,C();let l=window.setInterval(()=>{C()},re);return()=>{m.current=!1,window.clearInterval(l)}},[C]),(0,s.useEffect)(()=>{if(d.kind==="ready"){let l=d.status;e.reportHealth(l.paired&&l.connected?{status:"healthy",summary:e.t("health.online",{name:l.device_name})}:{status:"degraded",summary:e.t(l.paired?"health.disconnected":"health.unpaired")})}else d.kind==="offline"&&e.reportHealth({status:"degraded",summary:e.t("health.offline")})},[e,d]);let x=async l=>{g(!0),b("");try{await l()}catch(v){b(ie(e,v))}finally{g(!1)}};return(0,n.jsxs)("div",{className:"da-app",children:[(0,n.jsx)(o.PageHeader,{title:e.t("page.title"),subtitle:e.t("page.subtitle")}),h&&(0,n.jsx)("div",{role:"alert",className:"da-alert",children:h}),d.kind==="checking"&&(0,n.jsx)(o.Card,{children:(0,n.jsx)("span",{className:"da-muted",children:e.t("common.checking")})}),d.kind==="offline"&&(0,n.jsx)(se,{ctx:e,localUrl:a}),d.kind==="needsCode"&&(0,n.jsx)(le,{ctx:e,busy:k,onPair:l=>x(async()=>{let v=await y.pair(a,l).catch(w=>{throw w instanceof f&&w.code==="code_wrong"?new Error(e.t("pair.wrong")):w instanceof f&&w.code==="locked"?new Error(e.t("pair.locked")):w});e.secureStorage.set(z,v.token),p(v.token)})}),d.kind==="ready"&&(0,n.jsx)(ce,{ctx:e,status:d.status,server:t,workspace:r,busy:k,run:x,localUrl:a,token:i,onStatus:l=>u({kind:"ready",status:l})})]})}function se({ctx:e,localUrl:t}){return(0,n.jsxs)(o.Card,{children:[(0,n.jsx)(o.CardTitle,{children:e.t("offline.title")}),(0,n.jsx)("p",{className:"da-text",children:e.t("offline.body",{url:t})}),(0,n.jsx)("div",{className:"da-step",children:e.t("offline.once")}),(0,n.jsx)(R,{ctx:e,value:"npx toc-ocra@latest run"}),(0,n.jsx)("div",{className:"da-step",children:e.t("offline.service")}),(0,n.jsx)(R,{ctx:e,value:"npm install -g toc-ocra@latest && toc-ocra service:install"}),(0,n.jsx)("p",{className:"da-muted",children:e.t("offline.node")})]})}function le({ctx:e,busy:t,onPair:r}){let[a,i]=(0,s.useState)(""),p=/^\d{6}$/.test(a);return(0,n.jsxs)(o.Card,{children:[(0,n.jsx)(o.CardTitle,{children:e.t("pair.title")}),(0,n.jsx)("p",{className:"da-text",children:e.t("pair.body")}),(0,n.jsx)(R,{ctx:e,value:"toc-ocra code"}),(0,n.jsxs)("form",{className:"da-row",onSubmit:d=>{d.preventDefault(),p&&r(a)},children:[(0,n.jsx)(o.Input,{value:a,onChange:d=>i(d.target.value.replace(/\D/g,"").slice(0,6)),placeholder:e.t("pair.placeholder"),inputMode:"numeric",autoComplete:"one-time-code","aria-label":e.t("pair.placeholder"),className:"da-code-input"}),(0,n.jsx)(o.Btn,{primary:!0,type:"submit",disabled:!p||t,children:e.t("pair.submit")})]})]})}function ce({ctx:e,status:t,server:r,workspace:a,busy:i,run:p,localUrl:d,token:u,onStatus:h}){let b=t.paired&&r&&!E(t.server_url,r.origin),k=()=>p(async()=>{t.device_id&&r&&E(t.server_url,r.origin)&&await e.core.request(`/devices/${encodeURIComponent(t.device_id)}/agent`,{method:"DELETE"}).catch(()=>{}),h(await y.unpair(d,u))});return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(o.Card,{children:[(0,n.jsx)(o.CardTitle,{children:e.t("status.title")}),(0,n.jsxs)("dl",{className:"da-grid",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("dt",{children:e.t("status.service")}),(0,n.jsxs)("dd",{children:["toc-ocra ",t.version," \xB7 ",t.hostname]})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)("dt",{children:e.t("status.binding")}),(0,n.jsx)("dd",{children:(0,n.jsx)(pe,{ctx:e,status:t})})]}),t.paired&&(0,n.jsxs)("div",{children:[(0,n.jsx)("dt",{children:e.t("status.device")}),(0,n.jsx)("dd",{children:t.device_name})]}),t.paired&&(0,n.jsxs)("div",{children:[(0,n.jsx)("dt",{children:e.t("status.server")}),(0,n.jsx)("dd",{className:"da-mono",children:t.server_url})]}),t.paired&&(0,n.jsxs)("div",{children:[(0,n.jsx)("dt",{children:e.t("status.runs")}),(0,n.jsx)("dd",{children:t.active_runs})]})]}),t.paired&&!t.connected&&t.connection_error&&(0,n.jsx)("p",{className:"da-muted",children:e.t("status.lastError",{error:t.connection_error})}),t.token_from_env&&(0,n.jsx)("p",{className:"da-muted",children:e.t("status.tokenFromEnv")}),t.paired&&(0,n.jsx)("div",{className:"da-row",children:(0,n.jsx)(o.Btn,{danger:!0,disabled:i||t.token_from_env,onClick:()=>{k()},children:e.t("status.unpair")})})]}),(!t.paired||t.unauthorized||b)&&(0,n.jsx)(ue,{ctx:e,status:t,server:r,workspace:a,busy:i,run:p,localUrl:d,token:u,onStatus:h,rebind:t.paired}),(0,n.jsxs)(o.Card,{children:[(0,n.jsx)(o.CardTitle,{children:e.t("cli.title")}),(0,n.jsxs)("ul",{className:"da-list",children:[t.providers.filter(g=>g.installed).map(g=>(0,n.jsxs)("li",{children:[(0,n.jsx)("span",{className:"da-mono",children:g.id}),(0,n.jsx)("span",{className:"da-muted",children:g.version}),g.runnable?(0,n.jsx)(o.Badge,{variant:"ok",children:e.t("cli.runnable")}):(0,n.jsx)(o.Badge,{variant:"muted",children:e.t("cli.detectOnly")})]},g.id)),!t.providers.some(g=>g.installed)&&(0,n.jsx)("li",{className:"da-muted",children:e.t("cli.none")})]}),(0,n.jsx)("div",{className:"da-row",children:(0,n.jsx)(o.Btn,{disabled:i,onClick:()=>{p(async()=>h(await y.detect(d,u)))},children:e.t("cli.redetect")})})]})]})}function pe({ctx:e,status:t}){return t.paired?t.unauthorized?(0,n.jsx)(o.Badge,{variant:"err",children:e.t("binding.revoked")}):t.connected?(0,n.jsx)(o.Badge,{variant:"ok",children:e.t("binding.online")}):(0,n.jsx)(o.Badge,{variant:"warn",children:e.t("binding.connecting")}):(0,n.jsx)(o.Badge,{variant:"muted",children:e.t("binding.unpaired")})}function ue({ctx:e,status:t,server:r,workspace:a,busy:i,run:p,localUrl:d,token:u,onStatus:h,rebind:b}){let[k,g]=(0,s.useState)([]),[m,C]=(0,s.useState)("new"),[x,l]=(0,s.useState)(t.hostname),[v,w]=(0,s.useState)(r?.origin??"");(0,s.useEffect)(()=>{r?.origin&&w(r.origin)},[r?.origin]),(0,s.useEffect)(()=>{if(!a?.id)return;let c=!1;return e.core.request(`/workspaces/${encodeURIComponent(a.id)}/devices`).then(N=>{c||g((N.items??[]).filter(O=>O.is_mine))}).catch(()=>{c||g([])}),()=>{c=!0}},[e,a?.id]);let V=()=>p(async()=>{if(!a?.id)throw new Error(e.t("bind.noWorkspace"));if(!v.trim())throw new Error(e.t("bind.noServer"));let c=m;m==="new"&&(c=(await e.core.request(`/workspaces/${encodeURIComponent(a.id)}/devices`,{method:"POST",body:JSON.stringify({name:x.trim()||t.hostname,location:"local",platform:t.platform,note:"",share_to_workspace:!1})})).id,C(c));let N=await e.core.request(`/devices/${encodeURIComponent(c)}/pairing-code`,{method:"POST"});h(await y.enroll(d,u,v.trim(),N.code)),e.notify({title:e.t("page.title"),body:e.t("bind.done",{server:r?.name??v})})});return(0,n.jsxs)(o.Card,{children:[(0,n.jsx)(o.CardTitle,{children:e.t(b?"bind.rebindTitle":"bind.title")}),b&&t.unauthorized&&(0,n.jsx)("p",{className:"da-text",children:e.t("bind.revoked")}),b&&!t.unauthorized&&(0,n.jsx)("p",{className:"da-text",children:e.t("bind.elsewhere",{bound:t.server_url,current:r?.origin??""})}),(0,n.jsx)("p",{className:"da-text",children:e.t("bind.body",{server:r?.name??"",workspace:a?.name??""})}),(0,n.jsxs)("label",{className:"da-field",children:[(0,n.jsx)("span",{children:e.t("bind.server")}),(0,n.jsx)(o.Input,{value:v,onChange:c=>w(c.target.value),spellCheck:!1,className:"da-mono"})]}),(0,n.jsxs)("label",{className:"da-field",children:[(0,n.jsx)("span",{children:e.t("bind.device")}),(0,n.jsxs)("select",{className:"da-select",value:m,onChange:c=>C(c.target.value),children:[(0,n.jsx)("option",{value:"new",children:e.t("bind.newDevice")}),k.map(c=>(0,n.jsxs)("option",{value:c.id,children:[c.name,c.paired?` \xB7 ${e.t("bind.replaces")}`:""]},c.id))]})]}),m==="new"&&(0,n.jsxs)("label",{className:"da-field",children:[(0,n.jsx)("span",{children:e.t("bind.name")}),(0,n.jsx)(o.Input,{value:x,onChange:c=>l(c.target.value),maxLength:100})]}),(0,n.jsx)("div",{className:"da-row",children:(0,n.jsx)(o.Btn,{primary:!0,disabled:i||!a?.id,onClick:()=>{V()},children:e.t(i?"bind.working":"bind.submit")})})]})}var xe=H({activate(e){e.registerNavigation("agent.nav",(0,n.jsx)(oe,{})),e.registerRoute("agent.home",(0,n.jsx)(de,{ctx:e}))}});export{xe as default};
|
|
2
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["toc-shared:react", "toc-shared:@toc-starter/plugin-sdk/ui", "toc-shared:react/jsx-runtime", "../../plugin/index.tsx", "../../node_modules/@toc-starter/plugin-sdk/index.js", "../../plugin/local.ts"],
|
|
4
|
+
"sourcesContent": ["var registry = globalThis[\"__TOC_PLUGIN_SHARED__\"];\nvar shared = registry && registry[\"react\"];\nif (!shared) throw new Error(\"The host does not provide react; this plugin needs a newer app version.\");\nmodule.exports = shared;", "var registry = globalThis[\"__TOC_PLUGIN_SHARED__\"];\nvar shared = registry && registry[\"@toc-starter/plugin-sdk/ui\"];\nif (!shared) throw new Error(\"The host does not provide @toc-starter/plugin-sdk/ui; this plugin needs a newer app version.\");\nmodule.exports = shared;", "var registry = globalThis[\"__TOC_PLUGIN_SHARED__\"];\nvar shared = registry && registry[\"react/jsx-runtime\"];\nif (!shared) throw new Error(\"The host does not provide react/jsx-runtime; this plugin needs a newer app version.\");\nmodule.exports = shared;", "import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { definePlugin, useHostContext, type PluginContext } from \"@toc-starter/plugin-sdk\";\nimport { Badge, Btn, Card, CardTitle, Input, PageHeader } from \"@toc-starter/plugin-sdk/ui\";\nimport \"./styles.css\";\nimport { DEFAULT_LOCAL_URL, LocalError, local, sameServer, type LocalStatus } from \"./local\";\n\nconst TOKEN_KEY = \"controlToken\";\nconst LOCAL_URL_KEY = \"localUrl\";\nconst POLL_MS = 3_000;\n\n/** Resolved against dist/plugin/renderer.js. */\nconst iconUrl = new URL(\"../../assets/icon.svg\", import.meta.url).href;\n\ninterface ServerContext { kind: \"cloud\" | \"lan\"; name: string; origin: string }\ninterface WorkspaceContext { id: string; name: string }\ninterface Device { id: string; name: string; platform: string; paired: boolean; status: string; is_mine: boolean }\n\ntype Phase =\n | { kind: \"checking\" }\n | { kind: \"offline\" }\n | { kind: \"needsCode\" }\n | { kind: \"ready\"; status: LocalStatus };\n\nfunction Icon() {\n return <img src={iconUrl} alt=\"\" width={16} height={16} draggable={false} />;\n}\n\nfunction errorText(ctx: PluginContext, error: unknown): string {\n if (error instanceof LocalError && error.kind === \"offline\") return ctx.t(\"error.offline\");\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction Command({ ctx, value }: { ctx: PluginContext; value: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <div className=\"da-command\">\n <code>{value}</code>\n <Btn onClick={() => void navigator.clipboard.writeText(value).then(() => { setCopied(true); window.setTimeout(() => setCopied(false), 1500); })}>\n {copied ? ctx.t(\"common.copied\") : ctx.t(\"common.copy\")}\n </Btn>\n </div>\n );\n}\n\nfunction AgentPage({ ctx }: { ctx: PluginContext }) {\n const server = useHostContext<ServerContext>(ctx, \"server\");\n const workspace = useHostContext<WorkspaceContext>(ctx, \"workspace\");\n const [localUrl] = useState(() => ctx.storage.get(LOCAL_URL_KEY, DEFAULT_LOCAL_URL));\n const [token, setToken] = useState(() => ctx.secureStorage.get(TOKEN_KEY, \"\"));\n const [phase, setPhase] = useState<Phase>({ kind: \"checking\" });\n const [error, setError] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const alive = useRef(true);\n\n const refresh = useCallback(async () => {\n try {\n await local.info(localUrl);\n } catch {\n if (alive.current) setPhase({ kind: \"offline\" });\n return;\n }\n if (!token) {\n if (alive.current) setPhase({ kind: \"needsCode\" });\n return;\n }\n try {\n const status = await local.status(localUrl, token);\n if (alive.current) setPhase({ kind: \"ready\", status });\n } catch (reason) {\n if (!alive.current) return;\n if (reason instanceof LocalError && reason.kind === \"unauthorized\") {\n // The service was reinstalled (new control token): pair again.\n ctx.secureStorage.remove(TOKEN_KEY);\n setToken(\"\");\n setPhase({ kind: \"needsCode\" });\n } else {\n setPhase({ kind: \"offline\" });\n }\n }\n }, [ctx, localUrl, token]);\n\n useEffect(() => {\n alive.current = true;\n void refresh();\n const timer = window.setInterval(() => void refresh(), POLL_MS);\n return () => { alive.current = false; window.clearInterval(timer); };\n }, [refresh]);\n\n useEffect(() => {\n if (phase.kind === \"ready\") {\n const s = phase.status;\n ctx.reportHealth(s.paired && s.connected ? { status: \"healthy\", summary: ctx.t(\"health.online\", { name: s.device_name }) } : { status: \"degraded\", summary: ctx.t(s.paired ? \"health.disconnected\" : \"health.unpaired\") });\n } else if (phase.kind === \"offline\") {\n ctx.reportHealth({ status: \"degraded\", summary: ctx.t(\"health.offline\") });\n }\n }, [ctx, phase]);\n\n const run = async (action: () => Promise<void>) => {\n setBusy(true);\n setError(\"\");\n try {\n await action();\n } catch (reason) {\n setError(errorText(ctx, reason));\n } finally {\n setBusy(false);\n }\n };\n\n return (\n <div className=\"da-app\">\n <PageHeader title={ctx.t(\"page.title\")} subtitle={ctx.t(\"page.subtitle\")} />\n {error && <div role=\"alert\" className=\"da-alert\">{error}</div>}\n {phase.kind === \"checking\" && <Card><span className=\"da-muted\">{ctx.t(\"common.checking\")}</span></Card>}\n {phase.kind === \"offline\" && <OfflineCard ctx={ctx} localUrl={localUrl} />}\n {phase.kind === \"needsCode\" && (\n <PairCard ctx={ctx} busy={busy} onPair={code => run(async () => {\n const result = await local.pair(localUrl, code).catch((reason: unknown) => {\n if (reason instanceof LocalError && reason.code === \"code_wrong\") throw new Error(ctx.t(\"pair.wrong\"));\n if (reason instanceof LocalError && reason.code === \"locked\") throw new Error(ctx.t(\"pair.locked\"));\n throw reason;\n });\n ctx.secureStorage.set(TOKEN_KEY, result.token);\n setToken(result.token);\n })} />\n )}\n {phase.kind === \"ready\" && (\n <ReadyView\n ctx={ctx}\n status={phase.status}\n server={server}\n workspace={workspace}\n busy={busy}\n run={run}\n localUrl={localUrl}\n token={token}\n onStatus={status => setPhase({ kind: \"ready\", status })}\n />\n )}\n </div>\n );\n}\n\nfunction OfflineCard({ ctx, localUrl }: { ctx: PluginContext; localUrl: string }) {\n return (\n <Card>\n <CardTitle>{ctx.t(\"offline.title\")}</CardTitle>\n <p className=\"da-text\">{ctx.t(\"offline.body\", { url: localUrl })}</p>\n <div className=\"da-step\">{ctx.t(\"offline.once\")}</div>\n <Command ctx={ctx} value=\"npx toc-ocra@latest run\" />\n <div className=\"da-step\">{ctx.t(\"offline.service\")}</div>\n <Command ctx={ctx} value=\"npm install -g toc-ocra@latest && toc-ocra service:install\" />\n <p className=\"da-muted\">{ctx.t(\"offline.node\")}</p>\n </Card>\n );\n}\n\nfunction PairCard({ ctx, busy, onPair }: { ctx: PluginContext; busy: boolean; onPair: (code: string) => void }) {\n const [code, setCode] = useState(\"\");\n const valid = /^\\d{6}$/.test(code);\n return (\n <Card>\n <CardTitle>{ctx.t(\"pair.title\")}</CardTitle>\n <p className=\"da-text\">{ctx.t(\"pair.body\")}</p>\n <Command ctx={ctx} value=\"toc-ocra code\" />\n <form className=\"da-row\" onSubmit={event => { event.preventDefault(); if (valid) onPair(code); }}>\n <Input\n value={code}\n onChange={event => setCode(event.target.value.replace(/\\D/g, \"\").slice(0, 6))}\n placeholder={ctx.t(\"pair.placeholder\")}\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n aria-label={ctx.t(\"pair.placeholder\")}\n className=\"da-code-input\"\n />\n <Btn primary type=\"submit\" disabled={!valid || busy}>{ctx.t(\"pair.submit\")}</Btn>\n </form>\n </Card>\n );\n}\n\nfunction ReadyView({ ctx, status, server, workspace, busy, run, localUrl, token, onStatus }: {\n ctx: PluginContext;\n status: LocalStatus;\n server: ServerContext | undefined;\n workspace: WorkspaceContext | undefined;\n busy: boolean;\n run: (action: () => Promise<void>) => Promise<void>;\n localUrl: string;\n token: string;\n onStatus: (status: LocalStatus) => void;\n}) {\n const boundElsewhere = status.paired && server && !sameServer(status.server_url, server.origin);\n const unpair = () => run(async () => {\n // Revoke on the server too when the device belongs to the server shown.\n if (status.device_id && server && sameServer(status.server_url, server.origin)) {\n await ctx.core.request(`/devices/${encodeURIComponent(status.device_id)}/agent`, { method: \"DELETE\" }).catch(() => undefined);\n }\n onStatus(await local.unpair(localUrl, token));\n });\n return (\n <>\n <Card>\n <CardTitle>{ctx.t(\"status.title\")}</CardTitle>\n <dl className=\"da-grid\">\n <div><dt>{ctx.t(\"status.service\")}</dt><dd>toc-ocra {status.version} \u00B7 {status.hostname}</dd></div>\n <div><dt>{ctx.t(\"status.binding\")}</dt><dd><BindingBadge ctx={ctx} status={status} /></dd></div>\n {status.paired && <div><dt>{ctx.t(\"status.device\")}</dt><dd>{status.device_name}</dd></div>}\n {status.paired && <div><dt>{ctx.t(\"status.server\")}</dt><dd className=\"da-mono\">{status.server_url}</dd></div>}\n {status.paired && <div><dt>{ctx.t(\"status.runs\")}</dt><dd>{status.active_runs}</dd></div>}\n </dl>\n {status.paired && !status.connected && status.connection_error && <p className=\"da-muted\">{ctx.t(\"status.lastError\", { error: status.connection_error })}</p>}\n {status.token_from_env && <p className=\"da-muted\">{ctx.t(\"status.tokenFromEnv\")}</p>}\n {status.paired && (\n <div className=\"da-row\">\n <Btn danger disabled={busy || status.token_from_env} onClick={() => void unpair()}>{ctx.t(\"status.unpair\")}</Btn>\n </div>\n )}\n </Card>\n\n {(!status.paired || status.unauthorized || boundElsewhere) && (\n <BindCard ctx={ctx} status={status} server={server} workspace={workspace} busy={busy} run={run} localUrl={localUrl} token={token} onStatus={onStatus} rebind={status.paired} />\n )}\n\n <Card>\n <CardTitle>{ctx.t(\"cli.title\")}</CardTitle>\n <ul className=\"da-list\">\n {status.providers.filter(provider => provider.installed).map(provider => (\n <li key={provider.id}>\n <span className=\"da-mono\">{provider.id}</span>\n <span className=\"da-muted\">{provider.version}</span>\n {provider.runnable ? <Badge variant=\"ok\">{ctx.t(\"cli.runnable\")}</Badge> : <Badge variant=\"muted\">{ctx.t(\"cli.detectOnly\")}</Badge>}\n </li>\n ))}\n {!status.providers.some(provider => provider.installed) && <li className=\"da-muted\">{ctx.t(\"cli.none\")}</li>}\n </ul>\n <div className=\"da-row\">\n <Btn disabled={busy} onClick={() => void run(async () => onStatus(await local.detect(localUrl, token)))}>{ctx.t(\"cli.redetect\")}</Btn>\n </div>\n </Card>\n </>\n );\n}\n\nfunction BindingBadge({ ctx, status }: { ctx: PluginContext; status: LocalStatus }) {\n if (!status.paired) return <Badge variant=\"muted\">{ctx.t(\"binding.unpaired\")}</Badge>;\n if (status.unauthorized) return <Badge variant=\"err\">{ctx.t(\"binding.revoked\")}</Badge>;\n if (status.connected) return <Badge variant=\"ok\">{ctx.t(\"binding.online\")}</Badge>;\n return <Badge variant=\"warn\">{ctx.t(\"binding.connecting\")}</Badge>;\n}\n\nfunction BindCard({ ctx, status, server, workspace, busy, run, localUrl, token, onStatus, rebind }: {\n ctx: PluginContext;\n status: LocalStatus;\n server: ServerContext | undefined;\n workspace: WorkspaceContext | undefined;\n busy: boolean;\n run: (action: () => Promise<void>) => Promise<void>;\n localUrl: string;\n token: string;\n onStatus: (status: LocalStatus) => void;\n rebind: boolean;\n}) {\n const [devices, setDevices] = useState<Device[]>([]);\n const [choice, setChoice] = useState(\"new\");\n const [name, setName] = useState(status.hostname);\n const [serverUrl, setServerUrl] = useState(server?.origin ?? \"\");\n useEffect(() => { if (server?.origin) setServerUrl(server.origin); }, [server?.origin]);\n\n useEffect(() => {\n if (!workspace?.id) return;\n let cancelled = false;\n ctx.core.request<{ items?: Device[] }>(`/workspaces/${encodeURIComponent(workspace.id)}/devices`)\n .then(result => { if (!cancelled) setDevices((result.items ?? []).filter(device => device.is_mine)); })\n .catch(() => { if (!cancelled) setDevices([]); });\n return () => { cancelled = true; };\n }, [ctx, workspace?.id]);\n\n const bind = () => run(async () => {\n if (!workspace?.id) throw new Error(ctx.t(\"bind.noWorkspace\"));\n if (!serverUrl.trim()) throw new Error(ctx.t(\"bind.noServer\"));\n let deviceId = choice;\n if (choice === \"new\") {\n const created = await ctx.core.request<{ id: string }>(`/workspaces/${encodeURIComponent(workspace.id)}/devices`, {\n method: \"POST\",\n body: JSON.stringify({ name: name.trim() || status.hostname, location: \"local\", platform: status.platform, note: \"\", share_to_workspace: false }),\n });\n deviceId = created.id;\n setChoice(deviceId);\n }\n const pairing = await ctx.core.request<{ code: string }>(`/devices/${encodeURIComponent(deviceId)}/pairing-code`, { method: \"POST\" });\n onStatus(await local.enroll(localUrl, token, serverUrl.trim(), pairing.code));\n ctx.notify({ title: ctx.t(\"page.title\"), body: ctx.t(\"bind.done\", { server: server?.name ?? serverUrl }) });\n });\n\n return (\n <Card>\n <CardTitle>{ctx.t(rebind ? \"bind.rebindTitle\" : \"bind.title\")}</CardTitle>\n {rebind && status.unauthorized && <p className=\"da-text\">{ctx.t(\"bind.revoked\")}</p>}\n {rebind && !status.unauthorized && <p className=\"da-text\">{ctx.t(\"bind.elsewhere\", { bound: status.server_url, current: server?.origin ?? \"\" })}</p>}\n <p className=\"da-text\">{ctx.t(\"bind.body\", { server: server?.name ?? \"\", workspace: workspace?.name ?? \"\" })}</p>\n <label className=\"da-field\">\n <span>{ctx.t(\"bind.server\")}</span>\n <Input value={serverUrl} onChange={event => setServerUrl(event.target.value)} spellCheck={false} className=\"da-mono\" />\n </label>\n <label className=\"da-field\">\n <span>{ctx.t(\"bind.device\")}</span>\n <select className=\"da-select\" value={choice} onChange={event => setChoice(event.target.value)}>\n <option value=\"new\">{ctx.t(\"bind.newDevice\")}</option>\n {devices.map(device => (\n <option key={device.id} value={device.id}>{device.name}{device.paired ? ` \u00B7 ${ctx.t(\"bind.replaces\")}` : \"\"}</option>\n ))}\n </select>\n </label>\n {choice === \"new\" && (\n <label className=\"da-field\">\n <span>{ctx.t(\"bind.name\")}</span>\n <Input value={name} onChange={event => setName(event.target.value)} maxLength={100} />\n </label>\n )}\n <div className=\"da-row\">\n <Btn primary disabled={busy || !workspace?.id} onClick={() => void bind()}>{ctx.t(busy ? \"bind.working\" : \"bind.submit\")}</Btn>\n </div>\n </Card>\n );\n}\n\nexport default definePlugin({\n activate(ctx) {\n ctx.registerNavigation(\"agent.nav\", <Icon />);\n ctx.registerRoute(\"agent.home\", <AgentPage ctx={ctx} />);\n },\n});\n", "/**\n * Runtime part of the SDK that ends up inside plugin bundles. Keep it tiny:\n * everything here is copied into every plugin. Build-time helpers live in lib/.\n * `react` resolves to the host's instance through `toc-plugin build`.\n */\nimport { useSyncExternalStore } from \"react\";\n\n/** Host contract this SDK targets. Must match PLUGIN_API_VERSION in lib/constants.mjs. */\nexport const PLUGIN_API_VERSION = \"1.0.0\";\n\n/**\n * Identity helper that gives a plugin module full type inference.\n *\n * @template {import(\"./index\").PluginModule} T\n * @param {T} plugin\n * @returns {T}\n */\nexport function definePlugin(plugin) {\n if (!plugin || typeof plugin.activate !== \"function\") {\n throw new TypeError(\"definePlugin() needs an object with an activate(context) function\");\n }\n return plugin;\n}\n\n/**\n * Re-renders when the app publishes a new value under `key`.\n *\n * @template T\n * @param {import(\"./index\").PluginContext} ctx\n * @param {string} key\n * @returns {T | undefined}\n */\nexport function useHostContext(ctx, key) {\n return useSyncExternalStore(\n listener => ctx.core.onContextChange(key, listener),\n () => ctx.core.context(key),\n );\n}\n", "/**\n * Client for the local toc-ocra service (`toc-ocra run`) on this computer.\n * The plugin pairs once with the 6-digit connection code and then sends the\n * control token as a bearer token.\n */\nexport const DEFAULT_LOCAL_URL = \"http://127.0.0.1:17339\";\n\nexport interface LocalInfo {\n name: string;\n version: string;\n protocol: number;\n paired: boolean;\n}\n\nexport interface LocalProvider {\n id: string;\n installed: boolean;\n runnable: boolean;\n version: string;\n}\n\nexport interface LocalStatus {\n version: string;\n hostname: string;\n platform: \"mac\" | \"win\" | \"linux\";\n paired: boolean;\n running: boolean;\n device_id: string;\n device_name: string;\n server_url: string;\n connected: boolean;\n unauthorized: boolean;\n connection_error: string;\n active_runs: number;\n allowed_roots: string[];\n token_from_env: boolean;\n providers: LocalProvider[];\n}\n\n/** Why a local call failed: the service is not running, the token is not accepted, or the service said no. */\nexport class LocalError extends Error {\n constructor(readonly kind: \"offline\" | \"unauthorized\" | \"rejected\", message: string, readonly code = \"\") {\n super(message);\n }\n}\n\nasync function call<T>(base: string, path: string, init: RequestInit & { token?: string } = {}): Promise<T> {\n const headers = new Headers(init.headers);\n if (init.token) headers.set(\"Authorization\", `Bearer ${init.token}`);\n if (init.body) headers.set(\"Content-Type\", \"application/json\");\n let response: Response;\n try {\n response = await fetch(`${base.replace(/\\/+$/, \"\")}${path}`, { ...init, headers, cache: \"no-store\" });\n } catch {\n throw new LocalError(\"offline\", \"local service unreachable\");\n }\n const body = (await response.json().catch(() => null)) as Record<string, unknown> | null;\n if (response.status === 401) throw new LocalError(\"unauthorized\", \"unauthorized\");\n if (!response.ok) {\n const code = typeof body?.error === \"string\" ? body.error : \"\";\n const message = typeof body?.message === \"string\" ? body.message : code || `HTTP ${response.status}`;\n throw new LocalError(\"rejected\", message, code);\n }\n return body as T;\n}\n\nexport const local = {\n info: (base: string) => call<LocalInfo>(base, \"/control/info\"),\n pair: (base: string, code: string) => call<{ token: string }>(base, \"/control/pair\", { method: \"POST\", body: JSON.stringify({ code }) }),\n status: (base: string, token: string) => call<LocalStatus>(base, \"/control/status\", { token }),\n enroll: (base: string, token: string, serverUrl: string, code: string) =>\n call<LocalStatus>(base, \"/control/enroll\", { method: \"POST\", token, body: JSON.stringify({ server_url: serverUrl, code }) }),\n unpair: (base: string, token: string) => call<LocalStatus>(base, \"/control/unpair\", { method: \"POST\", token }),\n detect: (base: string, token: string) => call<LocalStatus>(base, \"/control/detect\", { method: \"POST\", token }),\n};\n\n/** Two backend addresses name the same server (trailing slash and case insensitive). */\nexport function sameServer(left: string, right: string): boolean {\n const normalize = (value: string) => {\n try {\n return new URL(value).origin.toLowerCase();\n } catch {\n return value.trim().replace(/\\/+$/, \"\").toLowerCase();\n }\n };\n return Boolean(left && right) && normalize(left) === normalize(right);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["require_react", "__commonJSMin", "exports", "module", "registry", "shared", "require_ui", "__commonJSMin", "exports", "module", "registry", "shared", "require_jsx_runtime", "__commonJSMin", "exports", "module", "registry", "shared", "import_react", "import_react", "definePlugin", "plugin", "useHostContext", "ctx", "key", "listener", "import_ui", "DEFAULT_LOCAL_URL", "LocalError", "kind", "message", "code", "call", "base", "path", "init", "headers", "response", "body", "local", "token", "serverUrl", "sameServer", "left", "right", "normalize", "value", "import_jsx_runtime", "TOKEN_KEY", "LOCAL_URL_KEY", "POLL_MS", "iconUrl", "Icon", "errorText", "ctx", "error", "LocalError", "Command", "value", "copied", "setCopied", "AgentPage", "server", "useHostContext", "workspace", "localUrl", "DEFAULT_LOCAL_URL", "token", "setToken", "phase", "setPhase", "setError", "busy", "setBusy", "alive", "refresh", "local", "status", "reason", "timer", "s", "run", "action", "OfflineCard", "PairCard", "code", "result", "ReadyView", "onPair", "setCode", "valid", "event", "onStatus", "boundElsewhere", "sameServer", "unpair", "BindingBadge", "BindCard", "provider", "rebind", "devices", "setDevices", "choice", "setChoice", "name", "setName", "serverUrl", "setServerUrl", "cancelled", "device", "bind", "deviceId", "pairing", "index_default", "definePlugin"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { LogLevel } from './logging/diagnostic-log.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration, highest priority first: environment variables, the config
|
|
4
|
+
* file (written by `toc-ocra enroll`), defaults.
|
|
5
|
+
*
|
|
6
|
+
* TOC_OCRA_SERVER_URL echo_admin base URL (default http://127.0.0.1:3000)
|
|
7
|
+
* TOC_OCRA_TOKEN device token (normally stored by enroll)
|
|
8
|
+
* TOC_OCRA_ALLOWED_ROOTS work directory roots, PATH-style list (default: home)
|
|
9
|
+
* TOC_OCRA_MAX_RUNS concurrent CLI turns on this machine (default 4)
|
|
10
|
+
* TOC_OCRA_MAX_RUN_SECONDS longest single turn (default 4h)
|
|
11
|
+
* TOC_OCRA_DISABLE_SCRIPT / TOC_OCRA_DISABLE_SQL local hard off-switches
|
|
12
|
+
* TOC_OCRA_DATA_DIR / TOC_OCRA_CONFIG / TOC_OCRA_LOG_LEVEL
|
|
13
|
+
* TOC_OCRA_HEALTH_HOST / TOC_OCRA_HEALTH_PORT
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_SERVER_URL = "http://127.0.0.1:3000";
|
|
16
|
+
export type AgentConfig = {
|
|
17
|
+
serverUrl: string;
|
|
18
|
+
token: string;
|
|
19
|
+
deviceId: string;
|
|
20
|
+
deviceName: string;
|
|
21
|
+
hostname: string;
|
|
22
|
+
platform: NodeJS.Platform;
|
|
23
|
+
allowedRoots: string[];
|
|
24
|
+
maxConcurrentRuns: number;
|
|
25
|
+
maxRunSeconds: number;
|
|
26
|
+
disableScript: boolean;
|
|
27
|
+
disableSql: boolean;
|
|
28
|
+
dataDir: string;
|
|
29
|
+
configPath: string;
|
|
30
|
+
logFile: string;
|
|
31
|
+
healthHost: string;
|
|
32
|
+
healthPort: number;
|
|
33
|
+
logLevel: LogLevel;
|
|
34
|
+
};
|
|
35
|
+
export declare function defaultDataDir(): string;
|
|
36
|
+
export declare function defaultConfigPath(dataDir?: string): string;
|
|
37
|
+
export declare function normalizeServerUrl(value: string): string;
|
|
38
|
+
export type EnrollmentOptions = {
|
|
39
|
+
serverUrl?: string;
|
|
40
|
+
code: string;
|
|
41
|
+
configPath?: string;
|
|
42
|
+
agentVersion: string;
|
|
43
|
+
};
|
|
44
|
+
/** Exchanges a pairing code for the device token and stores it (0600). */
|
|
45
|
+
export declare function enrollDevice(options: EnrollmentOptions): Promise<{
|
|
46
|
+
deviceId: string;
|
|
47
|
+
deviceName: string;
|
|
48
|
+
configPath: string;
|
|
49
|
+
serverUrl: string;
|
|
50
|
+
}>;
|
|
51
|
+
export declare const NOT_ENROLLED_MESSAGE = "\u672C\u673A\u8FD8\u6CA1\u6709\u914D\u5BF9\uFF1A\u5728\u300C\u8BBE\u5907\u4EE3\u7406\u300D\u63D2\u4EF6\u91CC\u7ED1\u5B9A\uFF0C\u6216\u5728\u8BBE\u5907\u9875\u751F\u6210\u914D\u5BF9\u7801\u540E\u8FD0\u884C toc-ocra enroll --code <\u914D\u5BF9\u7801>";
|
|
52
|
+
/** Loads the configuration; throws when this machine is not enrolled yet. */
|
|
53
|
+
export declare function loadAgentConfig(): AgentConfig;
|
|
54
|
+
/**
|
|
55
|
+
* Loads the configuration without requiring enrollment (token may be empty),
|
|
56
|
+
* so the local service can start and wait to be paired from the plugin.
|
|
57
|
+
*/
|
|
58
|
+
export declare function loadBaseConfig(): AgentConfig;
|
|
59
|
+
/** Forgets this machine's enrollment (device token and identity). */
|
|
60
|
+
export declare function clearEnrollment(configPath?: string): Promise<void>;
|
|
61
|
+
export declare function redactedConfig(config: AgentConfig): Record<string, unknown>;
|
|
62
|
+
/**
|
|
63
|
+
* Adds a directory the user chose for this device (a directory-layout root)
|
|
64
|
+
* to the allowed CLI work directories, in memory and in the config file.
|
|
65
|
+
*/
|
|
66
|
+
export declare function addAllowedRoot(config: AgentConfig, root: string): Promise<void>;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { chmod, mkdir, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir, hostname, platform } from 'node:os';
|
|
4
|
+
import { delimiter, dirname, join, resolve, sep } from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
/**
|
|
7
|
+
* Configuration, highest priority first: environment variables, the config
|
|
8
|
+
* file (written by `toc-ocra enroll`), defaults.
|
|
9
|
+
*
|
|
10
|
+
* TOC_OCRA_SERVER_URL echo_admin base URL (default http://127.0.0.1:3000)
|
|
11
|
+
* TOC_OCRA_TOKEN device token (normally stored by enroll)
|
|
12
|
+
* TOC_OCRA_ALLOWED_ROOTS work directory roots, PATH-style list (default: home)
|
|
13
|
+
* TOC_OCRA_MAX_RUNS concurrent CLI turns on this machine (default 4)
|
|
14
|
+
* TOC_OCRA_MAX_RUN_SECONDS longest single turn (default 4h)
|
|
15
|
+
* TOC_OCRA_DISABLE_SCRIPT / TOC_OCRA_DISABLE_SQL local hard off-switches
|
|
16
|
+
* TOC_OCRA_DATA_DIR / TOC_OCRA_CONFIG / TOC_OCRA_LOG_LEVEL
|
|
17
|
+
* TOC_OCRA_HEALTH_HOST / TOC_OCRA_HEALTH_PORT
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_SERVER_URL = 'http://127.0.0.1:3000';
|
|
20
|
+
const FileConfigSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
serverUrl: z.string().url().optional(),
|
|
23
|
+
token: z.string().min(1).optional(),
|
|
24
|
+
deviceId: z.string().min(1).optional(),
|
|
25
|
+
deviceName: z.string().min(1).optional(),
|
|
26
|
+
allowedRoots: z.array(z.string()).optional(),
|
|
27
|
+
maxConcurrentRuns: z.number().int().positive().max(64).optional(),
|
|
28
|
+
maxRunSeconds: z.number().int().positive().max(7 * 86_400).optional(),
|
|
29
|
+
disableScript: z.boolean().optional(),
|
|
30
|
+
disableSql: z.boolean().optional(),
|
|
31
|
+
healthHost: z.string().optional(),
|
|
32
|
+
healthPort: z.number().int().min(0).max(65535).optional(),
|
|
33
|
+
logLevel: z.enum(['error', 'warn', 'info', 'debug']).optional()
|
|
34
|
+
})
|
|
35
|
+
.strict();
|
|
36
|
+
export function defaultDataDir() {
|
|
37
|
+
if (process.platform === 'darwin')
|
|
38
|
+
return join(homedir(), 'Library', 'Application Support', 'toc-ocra');
|
|
39
|
+
if (process.platform === 'win32')
|
|
40
|
+
return join(process.env.APPDATA ?? homedir(), 'toc-ocra');
|
|
41
|
+
return join(process.env.XDG_STATE_HOME ?? join(homedir(), '.local', 'state'), 'toc-ocra');
|
|
42
|
+
}
|
|
43
|
+
function dataDirFromEnv() {
|
|
44
|
+
return resolve(process.env.TOC_OCRA_DATA_DIR ?? defaultDataDir());
|
|
45
|
+
}
|
|
46
|
+
export function defaultConfigPath(dataDir = dataDirFromEnv()) {
|
|
47
|
+
return resolve(process.env.TOC_OCRA_CONFIG ?? join(dataDir, 'config.json'));
|
|
48
|
+
}
|
|
49
|
+
function readFileConfig(configPath) {
|
|
50
|
+
return existsSync(configPath) ? FileConfigSchema.parse(JSON.parse(readFileSync(configPath, 'utf8'))) : {};
|
|
51
|
+
}
|
|
52
|
+
async function writeFileConfig(configPath, value) {
|
|
53
|
+
await mkdir(dirname(configPath), { recursive: true, mode: 0o700 });
|
|
54
|
+
const temporary = `${configPath}.${process.pid}.tmp`;
|
|
55
|
+
await writeFile(temporary, JSON.stringify(value, null, 2), { mode: 0o600 });
|
|
56
|
+
await rename(temporary, configPath);
|
|
57
|
+
await chmod(configPath, 0o600);
|
|
58
|
+
}
|
|
59
|
+
export function normalizeServerUrl(value) {
|
|
60
|
+
const url = new URL(value);
|
|
61
|
+
url.pathname = url.pathname.replace(/\/+$/, '').replace(/\/api$/, '');
|
|
62
|
+
url.search = '';
|
|
63
|
+
url.hash = '';
|
|
64
|
+
return url.toString().replace(/\/+$/, '');
|
|
65
|
+
}
|
|
66
|
+
/** Exchanges a pairing code for the device token and stores it (0600). */
|
|
67
|
+
export async function enrollDevice(options) {
|
|
68
|
+
const configPath = resolve(options.configPath ?? defaultConfigPath());
|
|
69
|
+
const existing = readFileConfig(configPath);
|
|
70
|
+
const serverUrl = normalizeServerUrl(options.serverUrl ?? process.env.TOC_OCRA_SERVER_URL ?? existing.serverUrl ?? DEFAULT_SERVER_URL);
|
|
71
|
+
const response = await fetch(`${serverUrl}/api/agent/enroll`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'content-type': 'application/json' },
|
|
74
|
+
body: JSON.stringify({ code: options.code, hostname: hostname(), platform: platform(), agent_version: options.agentVersion })
|
|
75
|
+
});
|
|
76
|
+
const body = await response.json().catch(() => null);
|
|
77
|
+
if (!response.ok || !body?.success || !body.data) {
|
|
78
|
+
throw new Error(body?.message || `配对失败(HTTP ${response.status})`);
|
|
79
|
+
}
|
|
80
|
+
await writeFileConfig(configPath, {
|
|
81
|
+
...existing,
|
|
82
|
+
serverUrl,
|
|
83
|
+
token: body.data.token,
|
|
84
|
+
deviceId: body.data.device_id,
|
|
85
|
+
deviceName: body.data.device_name
|
|
86
|
+
});
|
|
87
|
+
return { deviceId: body.data.device_id, deviceName: body.data.device_name, configPath, serverUrl };
|
|
88
|
+
}
|
|
89
|
+
function parseRoots(value) {
|
|
90
|
+
if (!value?.trim())
|
|
91
|
+
return undefined;
|
|
92
|
+
const separator = process.platform === 'win32' ? ';' : delimiter;
|
|
93
|
+
return value.split(separator).map((item) => item.trim()).filter(Boolean);
|
|
94
|
+
}
|
|
95
|
+
function envInt(name) {
|
|
96
|
+
const value = process.env[name];
|
|
97
|
+
if (!value)
|
|
98
|
+
return undefined;
|
|
99
|
+
const parsed = Number.parseInt(value, 10);
|
|
100
|
+
if (!Number.isInteger(parsed) || parsed <= 0)
|
|
101
|
+
throw new Error(`${name} must be a positive integer`);
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
function envBool(name) {
|
|
105
|
+
const value = process.env[name]?.trim().toLowerCase();
|
|
106
|
+
if (!value)
|
|
107
|
+
return undefined;
|
|
108
|
+
return value === '1' || value === 'true' || value === 'yes';
|
|
109
|
+
}
|
|
110
|
+
export const NOT_ENROLLED_MESSAGE = '本机还没有配对:在「设备代理」插件里绑定,或在设备页生成配对码后运行 toc-ocra enroll --code <配对码>';
|
|
111
|
+
/** Loads the configuration; throws when this machine is not enrolled yet. */
|
|
112
|
+
export function loadAgentConfig() {
|
|
113
|
+
const config = loadBaseConfig();
|
|
114
|
+
if (!config.token)
|
|
115
|
+
throw new Error(NOT_ENROLLED_MESSAGE);
|
|
116
|
+
return config;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Loads the configuration without requiring enrollment (token may be empty),
|
|
120
|
+
* so the local service can start and wait to be paired from the plugin.
|
|
121
|
+
*/
|
|
122
|
+
export function loadBaseConfig() {
|
|
123
|
+
const dataDir = dataDirFromEnv();
|
|
124
|
+
const configPath = defaultConfigPath(dataDir);
|
|
125
|
+
const file = readFileConfig(configPath);
|
|
126
|
+
const token = process.env.TOC_OCRA_TOKEN ?? file.token ?? '';
|
|
127
|
+
const healthPort = envInt('TOC_OCRA_HEALTH_PORT') ?? file.healthPort ?? 17339;
|
|
128
|
+
if (healthPort > 65535)
|
|
129
|
+
throw new Error('TOC_OCRA_HEALTH_PORT must be between 0 and 65535');
|
|
130
|
+
const roots = parseRoots(process.env.TOC_OCRA_ALLOWED_ROOTS) ?? file.allowedRoots ?? [homedir()];
|
|
131
|
+
return {
|
|
132
|
+
serverUrl: normalizeServerUrl(process.env.TOC_OCRA_SERVER_URL ?? file.serverUrl ?? DEFAULT_SERVER_URL),
|
|
133
|
+
token,
|
|
134
|
+
deviceId: file.deviceId ?? '',
|
|
135
|
+
deviceName: file.deviceName ?? hostname(),
|
|
136
|
+
hostname: hostname(),
|
|
137
|
+
platform: platform(),
|
|
138
|
+
allowedRoots: roots.map((root) => resolve(root)),
|
|
139
|
+
maxConcurrentRuns: envInt('TOC_OCRA_MAX_RUNS') ?? file.maxConcurrentRuns ?? 4,
|
|
140
|
+
maxRunSeconds: envInt('TOC_OCRA_MAX_RUN_SECONDS') ?? file.maxRunSeconds ?? 4 * 3600,
|
|
141
|
+
disableScript: envBool('TOC_OCRA_DISABLE_SCRIPT') ?? file.disableScript ?? false,
|
|
142
|
+
disableSql: envBool('TOC_OCRA_DISABLE_SQL') ?? file.disableSql ?? false,
|
|
143
|
+
dataDir,
|
|
144
|
+
configPath,
|
|
145
|
+
logFile: join(dataDir, 'logs', 'agent.log'),
|
|
146
|
+
healthHost: process.env.TOC_OCRA_HEALTH_HOST ?? file.healthHost ?? '127.0.0.1',
|
|
147
|
+
healthPort,
|
|
148
|
+
logLevel: process.env.TOC_OCRA_LOG_LEVEL ?? file.logLevel ?? 'info'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Forgets this machine's enrollment (device token and identity). */
|
|
152
|
+
export async function clearEnrollment(configPath = defaultConfigPath()) {
|
|
153
|
+
const existing = readFileConfig(configPath);
|
|
154
|
+
const { token: _token, deviceId: _deviceId, deviceName: _deviceName, ...rest } = existing;
|
|
155
|
+
await writeFileConfig(configPath, rest);
|
|
156
|
+
}
|
|
157
|
+
export function redactedConfig(config) {
|
|
158
|
+
return { ...config, token: '[redacted]' };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Adds a directory the user chose for this device (a directory-layout root)
|
|
162
|
+
* to the allowed CLI work directories, in memory and in the config file.
|
|
163
|
+
*/
|
|
164
|
+
export async function addAllowedRoot(config, root) {
|
|
165
|
+
const resolved = resolve(root);
|
|
166
|
+
if (config.allowedRoots.some((item) => resolved === item || resolved.startsWith(item.endsWith(sep) ? item : item + sep)))
|
|
167
|
+
return;
|
|
168
|
+
config.allowedRoots.push(resolved);
|
|
169
|
+
const existing = readFileConfig(config.configPath);
|
|
170
|
+
const allowedRoots = [...new Set([...(existing.allowedRoots ?? []).map((item) => resolve(item)), resolved])];
|
|
171
|
+
await writeFileConfig(config.configPath, { ...existing, allowedRoots });
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAY,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAClE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAA;AAEzD,MAAM,gBAAgB,GAAG,CAAC;KACvB,MAAM,CAAC;IACN,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACtC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACtC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACxC,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC5C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACjE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,QAAQ,EAAE;IACrE,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACrC,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAClC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;IACzD,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;CAChE,CAAC;KACD,MAAM,EAAE,CAAA;AAwBX,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,UAAU,CAAC,CAAA;IACvG,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,EAAE,EAAE,UAAU,CAAC,CAAA;IAC3F,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,UAAU,CAAC,CAAA;AAC3F,CAAC;AAED,SAAS,cAAc;IACrB,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,cAAc,EAAE,CAAC,CAAA;AACnE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAO,GAAG,cAAc,EAAE;IAC1D,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAA;AAC7E,CAAC;AAED,SAAS,cAAc,CAAC,UAAkB;IACxC,OAAO,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC3G,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,UAAkB,EAAE,KAAiB;IAClE,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAClE,MAAM,SAAS,GAAG,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,MAAM,CAAA;IACpD,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC3E,MAAM,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;IACnC,MAAM,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAA;AAChC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;IAC1B,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;IACrE,GAAG,CAAC,MAAM,GAAG,EAAE,CAAA;IACf,GAAG,CAAC,IAAI,GAAG,EAAE,CAAA;IACb,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAC3C,CAAC;AAID,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA0B;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,IAAI,iBAAiB,EAAE,CAAC,CAAA;IACrE,MAAM,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAAA;IAC3C,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,QAAQ,CAAC,SAAS,IAAI,kBAAkB,CAAC,CAAA;IACtI,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,mBAAmB,EAAE;QAC5D,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC;KAC9H,CAAC,CAAA;IACF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAuC,CAAA;IAC1F,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,OAAO,IAAI,aAAa,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;IACnE,CAAC;IACD,MAAM,eAAe,CAAC,UAAU,EAAE;QAChC,GAAG,QAAQ;QACX,SAAS;QACT,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK;QACtB,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS;QAC7B,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW;KAClC,CAAC,CAAA;IACF,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,CAAA;AACpG,CAAC;AAED,SAAS,UAAU,CAAC,KAAyB;IAC3C,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,OAAO,SAAS,CAAA;IACpC,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;IAChE,OAAO,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AAC1E,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IACzC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,6BAA6B,CAAC,CAAA;IACnG,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACrD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,OAAO,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,KAAK,CAAA;AAC7D,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,iEAAiE,CAAA;AAErG,6EAA6E;AAC7E,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,cAAc,EAAE,CAAA;IAC/B,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;IACxD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,OAAO,GAAG,cAAc,EAAE,CAAA;IAChC,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,CAAA;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAA;IAC5D,MAAM,UAAU,GAAG,MAAM,CAAC,sBAAsB,CAAC,IAAI,IAAI,CAAC,UAAU,IAAI,KAAK,CAAA;IAC7E,IAAI,UAAU,GAAG,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;IAC3F,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,IAAI,CAAC,YAAY,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;IAChG,OAAO;QACL,SAAS,EAAE,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACtG,KAAK;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE;QAC7B,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,QAAQ,EAAE;QACzC,QAAQ,EAAE,QAAQ,EAAE;QACpB,QAAQ,EAAE,QAAQ,EAAE;QACpB,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,iBAAiB,EAAE,MAAM,CAAC,mBAAmB,CAAC,IAAI,IAAI,CAAC,iBAAiB,IAAI,CAAC;QAC7E,aAAa,EAAE,MAAM,CAAC,0BAA0B,CAAC,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,GAAG,IAAI;QACnF,aAAa,EAAE,OAAO,CAAC,yBAAyB,CAAC,IAAI,IAAI,CAAC,aAAa,IAAI,KAAK;QAChF,UAAU,EAAE,OAAO,CAAC,sBAAsB,CAAC,IAAI,IAAI,CAAC,UAAU,IAAI,KAAK;QACvE,OAAO;QACP,UAAU;QACV,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC;QAC3C,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,IAAI,CAAC,UAAU,IAAI,WAAW;QAC9E,UAAU;QACV,QAAQ,EAAG,OAAO,CAAC,GAAG,CAAC,kBAA2C,IAAI,IAAI,CAAC,QAAQ,IAAI,MAAM;KAC9F,CAAA;AACH,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,aAAqB,iBAAiB,EAAE;IAC5E,MAAM,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAAA;IAC3C,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAA;IACzF,MAAM,eAAe,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAmB;IAChD,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAmB,EAAE,IAAY;IACpE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;QAAE,OAAM;IAChI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAClC,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IAClD,MAAM,YAAY,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC5G,MAAM,eAAe,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,GAAG,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAA;AACzE,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pairing between the local service and the desktop plugin.
|
|
3
|
+
*
|
|
4
|
+
* The plugin proves it runs for the same person who started the service by
|
|
5
|
+
* entering a 6-digit connection code. The service shows the code on start and
|
|
6
|
+
* keeps it in `<dataDir>/connection-code` (0600) so `toc-ocra code` can print
|
|
7
|
+
* it later. A correct code returns the control token (32 random bytes kept in
|
|
8
|
+
* `<dataDir>/control-token`, 0600), which the plugin stores and sends as
|
|
9
|
+
* `Authorization: Bearer` on every control request.
|
|
10
|
+
*
|
|
11
|
+
* The code rotates after it is used and after five wrong attempts, and wrong
|
|
12
|
+
* attempts lock pairing for a short while.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CODE_FILE = "connection-code";
|
|
15
|
+
export declare const TOKEN_FILE = "control-token";
|
|
16
|
+
export type PairResult = {
|
|
17
|
+
ok: true;
|
|
18
|
+
token: string;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
error: 'code_wrong' | 'locked';
|
|
22
|
+
retryAfterMs?: number;
|
|
23
|
+
};
|
|
24
|
+
export declare class PairingStore {
|
|
25
|
+
private readonly dataDir;
|
|
26
|
+
private readonly now;
|
|
27
|
+
private token;
|
|
28
|
+
private code;
|
|
29
|
+
private failures;
|
|
30
|
+
private lockedUntil;
|
|
31
|
+
constructor(dataDir: string, now?: () => number);
|
|
32
|
+
/** Loads or creates the control token and issues a fresh connection code. */
|
|
33
|
+
init(): Promise<void>;
|
|
34
|
+
get connectionCode(): string;
|
|
35
|
+
/** Exchanges a connection code for the control token. */
|
|
36
|
+
pair(input: unknown): Promise<PairResult>;
|
|
37
|
+
/** Checks an `Authorization: Bearer <token>` header. */
|
|
38
|
+
authorize(header: string | undefined): boolean;
|
|
39
|
+
/** Removes the code file when the service stops. */
|
|
40
|
+
dispose(): Promise<void>;
|
|
41
|
+
private rotate;
|
|
42
|
+
}
|
|
43
|
+
/** Reads the code of the running service, for `toc-ocra code`. */
|
|
44
|
+
export declare function readConnectionCode(dataDir: string): string | undefined;
|