@jackwener/opencli 1.0.3 → 1.0.4
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/.github/workflows/build-extension.yml +21 -3
- package/.github/workflows/docs.yml +52 -0
- package/README.md +28 -28
- package/README.zh-CN.md +28 -28
- package/dist/browser/cdp.d.ts +16 -1
- package/dist/browser/cdp.js +124 -80
- package/dist/browser/daemon-client.d.ts +3 -1
- package/dist/browser/daemon-client.js +4 -0
- package/dist/browser/dom-helpers.d.ts +20 -0
- package/dist/browser/dom-helpers.js +109 -0
- package/dist/browser/mcp.d.ts +1 -0
- package/dist/browser/mcp.js +10 -5
- package/dist/browser/page.d.ts +7 -0
- package/dist/browser/page.js +37 -100
- package/dist/browser.test.js +7 -0
- package/dist/build-manifest.js +3 -1
- package/dist/build-manifest.test.js +34 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +30 -0
- package/dist/capabilityRouting.test.d.ts +1 -0
- package/dist/capabilityRouting.test.js +42 -0
- package/dist/chaoxing.test.js +11 -4
- package/dist/cli-manifest.json +635 -1
- package/dist/cli.js +45 -8
- package/dist/clis/antigravity/serve.d.ts +14 -0
- package/dist/clis/antigravity/serve.js +263 -0
- package/dist/clis/bilibili/download.js +4 -14
- package/dist/clis/boss/resume.d.ts +1 -0
- package/dist/clis/boss/resume.js +249 -0
- package/dist/clis/hf/top.d.ts +1 -0
- package/dist/clis/hf/top.js +119 -0
- package/dist/clis/jike/comment.d.ts +1 -0
- package/dist/clis/jike/comment.js +107 -0
- package/dist/clis/jike/create.d.ts +1 -0
- package/dist/clis/jike/create.js +106 -0
- package/dist/clis/jike/feed.d.ts +1 -0
- package/dist/clis/jike/feed.js +67 -0
- package/dist/clis/jike/like.d.ts +1 -0
- package/dist/clis/jike/like.js +61 -0
- package/dist/clis/jike/notifications.d.ts +1 -0
- package/dist/clis/jike/notifications.js +169 -0
- package/dist/clis/jike/post.yaml +58 -0
- package/dist/clis/jike/repost.d.ts +1 -0
- package/dist/clis/jike/repost.js +103 -0
- package/dist/clis/jike/search.d.ts +1 -0
- package/dist/clis/jike/search.js +67 -0
- package/dist/clis/jike/shared.d.ts +19 -0
- package/dist/clis/jike/shared.js +25 -0
- package/dist/clis/jike/topic.yaml +52 -0
- package/dist/clis/jike/user.yaml +51 -0
- package/dist/clis/smzdm/search.js +28 -39
- package/dist/clis/stackoverflow/bounties.yaml +29 -0
- package/dist/clis/stackoverflow/hot.yaml +28 -0
- package/dist/clis/stackoverflow/search.yaml +32 -0
- package/dist/clis/stackoverflow/unanswered.yaml +28 -0
- package/dist/clis/twitter/download.js +6 -16
- package/dist/clis/xiaohongshu/download.js +3 -3
- package/dist/clis/zhihu/download.js +3 -3
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +16 -0
- package/dist/download/index.d.ts +12 -8
- package/dist/download/index.js +11 -3
- package/dist/download/index.test.d.ts +1 -0
- package/dist/download/index.test.js +14 -0
- package/dist/engine.js +5 -5
- package/dist/explore.d.ts +1 -0
- package/dist/explore.js +3 -3
- package/dist/generate.js +1 -0
- package/dist/interceptor.js +3 -2
- package/dist/output.d.ts +1 -0
- package/dist/output.js +3 -1
- package/dist/pipeline/executor.test.js +1 -0
- package/dist/pipeline/steps/download.js +14 -18
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +5 -2
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +2 -2
- package/dist/types.d.ts +12 -0
- package/dist/verify.d.ts +6 -1
- package/dist/verify.js +54 -2
- package/docs/.vitepress/config.mts +193 -0
- package/docs/adapters/browser/apple-podcasts.md +28 -0
- package/docs/adapters/browser/bbc.md +26 -0
- package/docs/adapters/browser/bilibili.md +38 -0
- package/docs/adapters/browser/boss.md +28 -0
- package/docs/adapters/browser/coupang.md +28 -0
- package/docs/adapters/browser/ctrip.md +27 -0
- package/docs/adapters/browser/github.md +26 -0
- package/docs/adapters/browser/hackernews.md +26 -0
- package/docs/adapters/browser/linkedin.md +27 -0
- package/docs/adapters/browser/reddit.md +41 -0
- package/docs/adapters/browser/reuters.md +27 -0
- package/docs/adapters/browser/smzdm.md +27 -0
- package/docs/adapters/browser/twitter.md +47 -0
- package/docs/adapters/browser/v2ex.md +32 -0
- package/docs/adapters/browser/weibo.md +27 -0
- package/docs/adapters/browser/xiaohongshu.md +32 -0
- package/docs/adapters/browser/xiaoyuzhou.md +28 -0
- package/docs/adapters/browser/xueqiu.md +32 -0
- package/docs/adapters/browser/yahoo-finance.md +26 -0
- package/docs/adapters/browser/youtube.md +29 -0
- package/docs/adapters/browser/zhihu.md +30 -0
- package/docs/adapters/desktop/antigravity.md +46 -0
- package/docs/adapters/desktop/chatgpt.md +43 -0
- package/docs/adapters/desktop/chatwise.md +38 -0
- package/docs/adapters/desktop/codex.md +32 -0
- package/docs/adapters/desktop/cursor.md +33 -0
- package/docs/adapters/desktop/discord.md +28 -0
- package/docs/adapters/desktop/feishu.md +20 -0
- package/docs/adapters/desktop/neteasemusic.md +31 -0
- package/docs/adapters/desktop/notion.md +29 -0
- package/docs/adapters/desktop/wechat.md +28 -0
- package/docs/adapters/index.md +49 -0
- package/docs/advanced/cdp.md +103 -0
- package/docs/advanced/download.md +63 -0
- package/docs/advanced/electron.md +125 -0
- package/docs/advanced/remote-chrome.md +72 -0
- package/docs/developer/ai-workflow.md +66 -0
- package/docs/developer/architecture.md +90 -0
- package/docs/developer/contributing.md +136 -0
- package/docs/developer/testing.md +237 -0
- package/docs/developer/ts-adapter.md +87 -0
- package/docs/developer/yaml-adapter.md +108 -0
- package/docs/guide/browser-bridge.md +38 -0
- package/docs/guide/getting-started.md +56 -0
- package/docs/guide/installation.md +37 -0
- package/docs/guide/troubleshooting.md +56 -0
- package/docs/index.md +35 -0
- package/docs/zh/adapters/index.md +5 -0
- package/docs/zh/advanced/cdp.md +3 -0
- package/docs/zh/developer/contributing.md +24 -0
- package/docs/zh/guide/browser-bridge.md +25 -0
- package/docs/zh/guide/getting-started.md +40 -0
- package/docs/zh/guide/installation.md +37 -0
- package/docs/zh/index.md +29 -0
- package/extension/dist/background.js +92 -52
- package/extension/package-lock.json +1156 -0
- package/extension/src/background.test.ts +151 -0
- package/extension/src/background.ts +122 -51
- package/extension/src/protocol.ts +3 -1
- package/package.json +7 -3
- package/src/browser/cdp.ts +154 -82
- package/src/browser/daemon-client.ts +7 -1
- package/src/browser/dom-helpers.ts +116 -0
- package/src/browser/mcp.ts +14 -6
- package/src/browser/page.ts +45 -100
- package/src/browser.test.ts +10 -0
- package/src/build-manifest.test.ts +36 -0
- package/src/build-manifest.ts +2 -1
- package/src/capabilityRouting.test.ts +47 -0
- package/src/capabilityRouting.ts +28 -0
- package/src/chaoxing.test.ts +12 -4
- package/src/cli.ts +28 -8
- package/src/clis/antigravity/serve.ts +329 -0
- package/src/clis/bilibili/download.ts +4 -15
- package/src/clis/boss/resume.ts +262 -0
- package/src/clis/hf/top.ts +141 -0
- package/src/clis/jike/comment.ts +113 -0
- package/src/clis/jike/create.ts +113 -0
- package/src/clis/jike/feed.ts +74 -0
- package/src/clis/jike/like.ts +65 -0
- package/src/clis/jike/notifications.ts +185 -0
- package/src/clis/jike/post.yaml +58 -0
- package/src/clis/jike/repost.ts +114 -0
- package/src/clis/jike/search.ts +74 -0
- package/src/clis/jike/shared.ts +36 -0
- package/src/clis/jike/topic.yaml +52 -0
- package/src/clis/jike/user.yaml +51 -0
- package/src/clis/smzdm/search.ts +30 -39
- package/src/clis/stackoverflow/bounties.yaml +29 -0
- package/src/clis/stackoverflow/hot.yaml +28 -0
- package/src/clis/stackoverflow/search.yaml +32 -0
- package/src/clis/stackoverflow/unanswered.yaml +28 -0
- package/src/clis/twitter/download.ts +6 -17
- package/src/clis/xiaohongshu/download.ts +3 -3
- package/src/clis/zhihu/download.ts +3 -3
- package/src/doctor.ts +18 -2
- package/src/download/index.test.ts +16 -0
- package/src/download/index.ts +22 -4
- package/src/engine.ts +4 -4
- package/src/explore.ts +4 -4
- package/src/generate.ts +1 -0
- package/src/interceptor.ts +3 -2
- package/src/output.ts +3 -1
- package/src/pipeline/executor.test.ts +1 -0
- package/src/pipeline/steps/download.ts +14 -17
- package/src/registry.ts +6 -2
- package/src/runtime.ts +3 -2
- package/src/types.ts +9 -0
- package/src/verify.ts +64 -3
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## Common Issues
|
|
4
|
+
|
|
5
|
+
### "Extension not connected"
|
|
6
|
+
|
|
7
|
+
- Ensure the opencli Browser Bridge extension is installed and **enabled** in `chrome://extensions`.
|
|
8
|
+
- Run `opencli doctor` to diagnose connectivity.
|
|
9
|
+
|
|
10
|
+
### Empty data or 'Unauthorized' error
|
|
11
|
+
|
|
12
|
+
- Your login session in Chrome might have expired. Open a normal Chrome tab, navigate to the target site, and log in or refresh the page.
|
|
13
|
+
- Some sites have geographic restrictions (e.g., Bilibili, Zhihu from outside China).
|
|
14
|
+
|
|
15
|
+
### Node API errors
|
|
16
|
+
|
|
17
|
+
- Make sure you are using **Node.js >= 20**. Some dependencies require modern Node APIs.
|
|
18
|
+
- Run `node --version` to verify.
|
|
19
|
+
|
|
20
|
+
### Daemon issues
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Check daemon status
|
|
24
|
+
curl localhost:19825/status
|
|
25
|
+
|
|
26
|
+
# View extension logs
|
|
27
|
+
curl localhost:19825/logs
|
|
28
|
+
|
|
29
|
+
# Kill and restart daemon
|
|
30
|
+
pkill -f opencli-daemon
|
|
31
|
+
opencli doctor
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Desktop adapter connection issues
|
|
35
|
+
|
|
36
|
+
For Electron/CDP-based adapters (Cursor, Codex, etc.):
|
|
37
|
+
|
|
38
|
+
1. Make sure the app is launched with `--remote-debugging-port=XXXX`
|
|
39
|
+
2. Verify the endpoint is set: `echo $OPENCLI_CDP_ENDPOINT`
|
|
40
|
+
3. Test the endpoint: `curl http://127.0.0.1:XXXX/json/version`
|
|
41
|
+
|
|
42
|
+
### Build errors
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Clean rebuild
|
|
46
|
+
rm -rf dist/
|
|
47
|
+
npm run build
|
|
48
|
+
|
|
49
|
+
# Type check
|
|
50
|
+
npx tsc --noEmit
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Getting Help
|
|
54
|
+
|
|
55
|
+
- [GitHub Issues](https://github.com/jackwener/opencli/issues) — Bug reports and feature requests
|
|
56
|
+
- Run `opencli doctor --live` for comprehensive diagnostics
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: home
|
|
3
|
+
|
|
4
|
+
hero:
|
|
5
|
+
name: OpenCLI
|
|
6
|
+
text: Make any website or Electron App your CLI
|
|
7
|
+
tagline: Zero risk · Reuse Chrome login · AI-powered discovery · Browser + Desktop automation
|
|
8
|
+
actions:
|
|
9
|
+
- theme: brand
|
|
10
|
+
text: Get Started
|
|
11
|
+
link: /guide/getting-started
|
|
12
|
+
- theme: alt
|
|
13
|
+
text: View on GitHub
|
|
14
|
+
link: https://github.com/jackwener/opencli
|
|
15
|
+
|
|
16
|
+
features:
|
|
17
|
+
- icon: 🖥️
|
|
18
|
+
title: CLI All Electron
|
|
19
|
+
details: Turn ANY Electron application into a CLI tool — Cursor, Codex, Antigravity, ChatGPT, Notion, and more. AI can control itself natively.
|
|
20
|
+
- icon: 🔐
|
|
21
|
+
title: Account Safe
|
|
22
|
+
details: Reuses Chrome's logged-in state. Your credentials never leave the browser — no tokens, no exposed passwords.
|
|
23
|
+
- icon: 🤖
|
|
24
|
+
title: AI Agent Ready
|
|
25
|
+
details: "explore discovers APIs, synthesize generates adapters, cascade finds auth strategies. Built for AI-first workflows."
|
|
26
|
+
- icon: ⚡
|
|
27
|
+
title: Dual-Engine Architecture
|
|
28
|
+
details: Supports both YAML declarative data pipelines and robust browser runtime TypeScript injections for maximum flexibility.
|
|
29
|
+
- icon: 🔧
|
|
30
|
+
title: Self-Healing Setup
|
|
31
|
+
details: "opencli setup verifies Browser Bridge connectivity. opencli doctor diagnoses daemon, extension, and live browser."
|
|
32
|
+
- icon: 📦
|
|
33
|
+
title: Dynamic Loader
|
|
34
|
+
details: Simply drop .ts or .yaml adapters into the clis/ folder for auto-registration. Zero boilerplate.
|
|
35
|
+
---
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 贡献指南
|
|
2
|
+
|
|
3
|
+
详细贡献指南请参考 [英文版本](/developer/contributing)。
|
|
4
|
+
|
|
5
|
+
## 快速开始
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone git@github.com:<your-username>/opencli.git
|
|
9
|
+
cd opencli
|
|
10
|
+
npm install
|
|
11
|
+
npm run build
|
|
12
|
+
npx tsc --noEmit
|
|
13
|
+
npx vitest run src/
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 提交规范
|
|
17
|
+
|
|
18
|
+
使用 [Conventional Commits](https://www.conventionalcommits.org/):
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
feat(twitter): add thread command
|
|
22
|
+
fix(browser): handle CDP timeout gracefully
|
|
23
|
+
docs: update CONTRIBUTING.md
|
|
24
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Browser Bridge 设置
|
|
2
|
+
|
|
3
|
+
> **⚠️ 重要**: 浏览器命令复用你的 Chrome 登录会话。运行命令前必须在 Chrome 中登录目标网站。
|
|
4
|
+
|
|
5
|
+
OpenCLI 通过轻量级 **Browser Bridge** Chrome 扩展 + 微守护进程连接浏览器(零配置,自动启动)。
|
|
6
|
+
|
|
7
|
+
## 扩展安装
|
|
8
|
+
|
|
9
|
+
### 方法 1:下载预构建版本(推荐)
|
|
10
|
+
|
|
11
|
+
1. 前往 GitHub [Releases 页面](https://github.com/jackwener/opencli/releases) 下载最新的 `opencli-extension.zip` 或 `opencli-extension.crx`。
|
|
12
|
+
2. 打开 `chrome://extensions`,启用**开发者模式**。
|
|
13
|
+
3. 拖放 `.crx` 文件或解压后的文件夹到扩展页面。
|
|
14
|
+
|
|
15
|
+
### 方法 2:加载源码(开发者)
|
|
16
|
+
|
|
17
|
+
1. 打开 `chrome://extensions`,启用**开发者模式**。
|
|
18
|
+
2. 点击**加载已解压的扩展程序**,选择仓库中的 `extension/` 目录。
|
|
19
|
+
|
|
20
|
+
## 验证
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
opencli doctor # 检查扩展 + 守护进程连接
|
|
24
|
+
opencli doctor --live # 同时测试实时浏览器命令
|
|
25
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 快速开始
|
|
2
|
+
|
|
3
|
+
> **让任何网站或 Electron 应用成为你的 CLI。**
|
|
4
|
+
> 零风险 · 复用 Chrome 登录态 · AI 驱动发现 · 浏览器 + 桌面自动化
|
|
5
|
+
|
|
6
|
+
OpenCLI 将**任何网站**或 **Electron 应用**变成命令行界面 — Bilibili、知乎、小红书、Twitter/X、Reddit、YouTube、Antigravity 等 — 基于浏览器会话复用和 AI 原生发现。
|
|
7
|
+
|
|
8
|
+
## 安装
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @jackwener/opencli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 基本使用
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
opencli list # 查看所有命令
|
|
18
|
+
opencli hackernews top --limit 5 # 公开 API,无需浏览器
|
|
19
|
+
opencli bilibili hot --limit 5 # 浏览器命令
|
|
20
|
+
opencli zhihu hot -f json # JSON 输出
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 输出格式
|
|
24
|
+
|
|
25
|
+
所有命令支持 `--format` / `-f`:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
opencli bilibili hot -f table # 默认:终端表格
|
|
29
|
+
opencli bilibili hot -f json # JSON
|
|
30
|
+
opencli bilibili hot -f yaml # YAML
|
|
31
|
+
opencli bilibili hot -f md # Markdown
|
|
32
|
+
opencli bilibili hot -f csv # CSV
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 下一步
|
|
36
|
+
|
|
37
|
+
- [安装详情](/zh/guide/installation)
|
|
38
|
+
- [Browser Bridge 设置](/zh/guide/browser-bridge)
|
|
39
|
+
- [所有适配器](/zh/adapters/)
|
|
40
|
+
- [开发者指南](/zh/developer/contributing)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 安装
|
|
2
|
+
|
|
3
|
+
## 系统要求
|
|
4
|
+
|
|
5
|
+
- **Node.js**: >= 20.0.0
|
|
6
|
+
- **Chrome** 已运行并登录目标网站(浏览器命令需要)
|
|
7
|
+
|
|
8
|
+
## 通过 npm 安装(推荐)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @jackwener/opencli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 从源码安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone git@github.com:jackwener/opencli.git
|
|
18
|
+
cd opencli
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
npm link
|
|
22
|
+
opencli list
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 更新
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @jackwener/opencli@latest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 验证安装
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
opencli --version
|
|
35
|
+
opencli list
|
|
36
|
+
opencli doctor
|
|
37
|
+
```
|
package/docs/zh/index.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: home
|
|
3
|
+
|
|
4
|
+
hero:
|
|
5
|
+
name: OpenCLI
|
|
6
|
+
text: 让任何网站或 Electron 应用成为你的 CLI
|
|
7
|
+
tagline: 零风险 · 复用 Chrome 登录态 · AI 驱动发现 · 浏览器 + 桌面自动化
|
|
8
|
+
actions:
|
|
9
|
+
- theme: brand
|
|
10
|
+
text: 快速开始
|
|
11
|
+
link: /zh/guide/getting-started
|
|
12
|
+
- theme: alt
|
|
13
|
+
text: 在 GitHub 查看
|
|
14
|
+
link: https://github.com/jackwener/opencli
|
|
15
|
+
|
|
16
|
+
features:
|
|
17
|
+
- icon: 🖥️
|
|
18
|
+
title: CLI 所有 Electron 应用
|
|
19
|
+
details: 将任何 Electron 应用变成 CLI 工具 — Cursor、Codex、Antigravity、ChatGPT、Notion 等。AI 可以原生控制自身。
|
|
20
|
+
- icon: 🔐
|
|
21
|
+
title: 账号安全
|
|
22
|
+
details: 复用 Chrome 登录态,凭证永远不会离开浏览器 — 无 token,无密码泄露。
|
|
23
|
+
- icon: 🤖
|
|
24
|
+
title: AI Agent 就绪
|
|
25
|
+
details: explore 发现 API,synthesize 生成适配器,cascade 查找认证策略。为 AI 优先工作流而生。
|
|
26
|
+
- icon: ⚡
|
|
27
|
+
title: 双引擎架构
|
|
28
|
+
details: 同时支持 YAML 声明式数据管道和强大的浏览器运行时 TypeScript 注入。
|
|
29
|
+
---
|
|
@@ -160,30 +160,35 @@ function scheduleReconnect() {
|
|
|
160
160
|
connect();
|
|
161
161
|
}, delay);
|
|
162
162
|
}
|
|
163
|
-
|
|
164
|
-
let windowIdleTimer = null;
|
|
163
|
+
const automationSessions = /* @__PURE__ */ new Map();
|
|
165
164
|
const WINDOW_IDLE_TIMEOUT = 3e4;
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
165
|
+
function getWorkspaceKey(workspace) {
|
|
166
|
+
return workspace?.trim() || "default";
|
|
167
|
+
}
|
|
168
|
+
function resetWindowIdleTimer(workspace) {
|
|
169
|
+
const session = automationSessions.get(workspace);
|
|
170
|
+
if (!session) return;
|
|
171
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
172
|
+
session.idleDeadlineAt = Date.now() + WINDOW_IDLE_TIMEOUT;
|
|
173
|
+
session.idleTimer = setTimeout(async () => {
|
|
174
|
+
const current = automationSessions.get(workspace);
|
|
175
|
+
if (!current) return;
|
|
176
|
+
try {
|
|
177
|
+
await chrome.windows.remove(current.windowId);
|
|
178
|
+
console.log(`[opencli] Automation window ${current.windowId} (${workspace}) closed (idle timeout)`);
|
|
179
|
+
} catch {
|
|
176
180
|
}
|
|
177
|
-
|
|
181
|
+
automationSessions.delete(workspace);
|
|
178
182
|
}, WINDOW_IDLE_TIMEOUT);
|
|
179
183
|
}
|
|
180
|
-
async function getAutomationWindow() {
|
|
181
|
-
|
|
184
|
+
async function getAutomationWindow(workspace) {
|
|
185
|
+
const existing = automationSessions.get(workspace);
|
|
186
|
+
if (existing) {
|
|
182
187
|
try {
|
|
183
|
-
await chrome.windows.get(
|
|
184
|
-
return
|
|
188
|
+
await chrome.windows.get(existing.windowId);
|
|
189
|
+
return existing.windowId;
|
|
185
190
|
} catch {
|
|
186
|
-
|
|
191
|
+
automationSessions.delete(workspace);
|
|
187
192
|
}
|
|
188
193
|
}
|
|
189
194
|
const win = await chrome.windows.create({
|
|
@@ -193,17 +198,22 @@ async function getAutomationWindow() {
|
|
|
193
198
|
height: 900,
|
|
194
199
|
type: "normal"
|
|
195
200
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
201
|
+
const session = {
|
|
202
|
+
windowId: win.id,
|
|
203
|
+
idleTimer: null,
|
|
204
|
+
idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT
|
|
205
|
+
};
|
|
206
|
+
automationSessions.set(workspace, session);
|
|
207
|
+
console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
|
|
208
|
+
resetWindowIdleTimer(workspace);
|
|
209
|
+
return session.windowId;
|
|
199
210
|
}
|
|
200
211
|
chrome.windows.onRemoved.addListener((windowId) => {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
windowIdleTimer = null;
|
|
212
|
+
for (const [workspace, session] of automationSessions.entries()) {
|
|
213
|
+
if (session.windowId === windowId) {
|
|
214
|
+
console.log(`[opencli] Automation window closed (${workspace})`);
|
|
215
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
216
|
+
automationSessions.delete(workspace);
|
|
207
217
|
}
|
|
208
218
|
}
|
|
209
219
|
});
|
|
@@ -226,21 +236,24 @@ chrome.alarms.onAlarm.addListener((alarm) => {
|
|
|
226
236
|
if (alarm.name === "keepalive") connect();
|
|
227
237
|
});
|
|
228
238
|
async function handleCommand(cmd) {
|
|
229
|
-
|
|
239
|
+
const workspace = getWorkspaceKey(cmd.workspace);
|
|
240
|
+
resetWindowIdleTimer(workspace);
|
|
230
241
|
try {
|
|
231
242
|
switch (cmd.action) {
|
|
232
243
|
case "exec":
|
|
233
|
-
return await handleExec(cmd);
|
|
244
|
+
return await handleExec(cmd, workspace);
|
|
234
245
|
case "navigate":
|
|
235
|
-
return await handleNavigate(cmd);
|
|
246
|
+
return await handleNavigate(cmd, workspace);
|
|
236
247
|
case "tabs":
|
|
237
|
-
return await handleTabs(cmd);
|
|
248
|
+
return await handleTabs(cmd, workspace);
|
|
238
249
|
case "cookies":
|
|
239
250
|
return await handleCookies(cmd);
|
|
240
251
|
case "screenshot":
|
|
241
|
-
return await handleScreenshot(cmd);
|
|
252
|
+
return await handleScreenshot(cmd, workspace);
|
|
242
253
|
case "close-window":
|
|
243
|
-
return await handleCloseWindow(cmd);
|
|
254
|
+
return await handleCloseWindow(cmd, workspace);
|
|
255
|
+
case "sessions":
|
|
256
|
+
return await handleSessions(cmd);
|
|
244
257
|
default:
|
|
245
258
|
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
|
|
246
259
|
}
|
|
@@ -256,9 +269,9 @@ function isWebUrl(url) {
|
|
|
256
269
|
if (!url) return false;
|
|
257
270
|
return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
|
|
258
271
|
}
|
|
259
|
-
async function resolveTabId(tabId) {
|
|
272
|
+
async function resolveTabId(tabId, workspace) {
|
|
260
273
|
if (tabId !== void 0) return tabId;
|
|
261
|
-
const windowId = await getAutomationWindow();
|
|
274
|
+
const windowId = await getAutomationWindow(workspace);
|
|
262
275
|
const tabs = await chrome.tabs.query({ windowId });
|
|
263
276
|
const webTab = tabs.find((t) => t.id && isWebUrl(t.url));
|
|
264
277
|
if (webTab?.id) return webTab.id;
|
|
@@ -267,9 +280,23 @@ async function resolveTabId(tabId) {
|
|
|
267
280
|
if (!newTab.id) throw new Error("Failed to create tab in automation window");
|
|
268
281
|
return newTab.id;
|
|
269
282
|
}
|
|
270
|
-
async function
|
|
283
|
+
async function listAutomationTabs(workspace) {
|
|
284
|
+
const session = automationSessions.get(workspace);
|
|
285
|
+
if (!session) return [];
|
|
286
|
+
try {
|
|
287
|
+
return await chrome.tabs.query({ windowId: session.windowId });
|
|
288
|
+
} catch {
|
|
289
|
+
automationSessions.delete(workspace);
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function listAutomationWebTabs(workspace) {
|
|
294
|
+
const tabs = await listAutomationTabs(workspace);
|
|
295
|
+
return tabs.filter((tab) => isWebUrl(tab.url));
|
|
296
|
+
}
|
|
297
|
+
async function handleExec(cmd, workspace) {
|
|
271
298
|
if (!cmd.code) return { id: cmd.id, ok: false, error: "Missing code" };
|
|
272
|
-
const tabId = await resolveTabId(cmd.tabId);
|
|
299
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
273
300
|
try {
|
|
274
301
|
const data = await evaluateAsync(tabId, cmd.code);
|
|
275
302
|
return { id: cmd.id, ok: true, data };
|
|
@@ -277,9 +304,9 @@ async function handleExec(cmd) {
|
|
|
277
304
|
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
278
305
|
}
|
|
279
306
|
}
|
|
280
|
-
async function handleNavigate(cmd) {
|
|
307
|
+
async function handleNavigate(cmd, workspace) {
|
|
281
308
|
if (!cmd.url) return { id: cmd.id, ok: false, error: "Missing url" };
|
|
282
|
-
const tabId = await resolveTabId(cmd.tabId);
|
|
309
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
283
310
|
await chrome.tabs.update(tabId, { url: cmd.url });
|
|
284
311
|
await new Promise((resolve) => {
|
|
285
312
|
chrome.tabs.get(tabId).then((tab2) => {
|
|
@@ -303,11 +330,11 @@ async function handleNavigate(cmd) {
|
|
|
303
330
|
const tab = await chrome.tabs.get(tabId);
|
|
304
331
|
return { id: cmd.id, ok: true, data: { title: tab.title, url: tab.url, tabId } };
|
|
305
332
|
}
|
|
306
|
-
async function handleTabs(cmd) {
|
|
333
|
+
async function handleTabs(cmd, workspace) {
|
|
307
334
|
switch (cmd.op) {
|
|
308
335
|
case "list": {
|
|
309
|
-
const tabs = await
|
|
310
|
-
const data = tabs.
|
|
336
|
+
const tabs = await listAutomationWebTabs(workspace);
|
|
337
|
+
const data = tabs.map((t, i) => ({
|
|
311
338
|
index: i,
|
|
312
339
|
tabId: t.id,
|
|
313
340
|
url: t.url,
|
|
@@ -317,19 +344,20 @@ async function handleTabs(cmd) {
|
|
|
317
344
|
return { id: cmd.id, ok: true, data };
|
|
318
345
|
}
|
|
319
346
|
case "new": {
|
|
320
|
-
const
|
|
347
|
+
const windowId = await getAutomationWindow(workspace);
|
|
348
|
+
const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? "about:blank", active: true });
|
|
321
349
|
return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
|
|
322
350
|
}
|
|
323
351
|
case "close": {
|
|
324
352
|
if (cmd.index !== void 0) {
|
|
325
|
-
const tabs = await
|
|
353
|
+
const tabs = await listAutomationWebTabs(workspace);
|
|
326
354
|
const target = tabs[cmd.index];
|
|
327
355
|
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
|
|
328
356
|
await chrome.tabs.remove(target.id);
|
|
329
357
|
detach(target.id);
|
|
330
358
|
return { id: cmd.id, ok: true, data: { closed: target.id } };
|
|
331
359
|
}
|
|
332
|
-
const tabId = await resolveTabId(cmd.tabId);
|
|
360
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
333
361
|
await chrome.tabs.remove(tabId);
|
|
334
362
|
detach(tabId);
|
|
335
363
|
return { id: cmd.id, ok: true, data: { closed: tabId } };
|
|
@@ -341,7 +369,7 @@ async function handleTabs(cmd) {
|
|
|
341
369
|
await chrome.tabs.update(cmd.tabId, { active: true });
|
|
342
370
|
return { id: cmd.id, ok: true, data: { selected: cmd.tabId } };
|
|
343
371
|
}
|
|
344
|
-
const tabs = await
|
|
372
|
+
const tabs = await listAutomationWebTabs(workspace);
|
|
345
373
|
const target = tabs[cmd.index];
|
|
346
374
|
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
|
|
347
375
|
await chrome.tabs.update(target.id, { active: true });
|
|
@@ -367,8 +395,8 @@ async function handleCookies(cmd) {
|
|
|
367
395
|
}));
|
|
368
396
|
return { id: cmd.id, ok: true, data };
|
|
369
397
|
}
|
|
370
|
-
async function handleScreenshot(cmd) {
|
|
371
|
-
const tabId = await resolveTabId(cmd.tabId);
|
|
398
|
+
async function handleScreenshot(cmd, workspace) {
|
|
399
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
372
400
|
try {
|
|
373
401
|
const data = await screenshot(tabId, {
|
|
374
402
|
format: cmd.format,
|
|
@@ -380,13 +408,25 @@ async function handleScreenshot(cmd) {
|
|
|
380
408
|
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
381
409
|
}
|
|
382
410
|
}
|
|
383
|
-
async function handleCloseWindow(cmd) {
|
|
384
|
-
|
|
411
|
+
async function handleCloseWindow(cmd, workspace) {
|
|
412
|
+
const session = automationSessions.get(workspace);
|
|
413
|
+
if (session) {
|
|
385
414
|
try {
|
|
386
|
-
await chrome.windows.remove(
|
|
415
|
+
await chrome.windows.remove(session.windowId);
|
|
387
416
|
} catch {
|
|
388
417
|
}
|
|
389
|
-
|
|
418
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
419
|
+
automationSessions.delete(workspace);
|
|
390
420
|
}
|
|
391
421
|
return { id: cmd.id, ok: true, data: { closed: true } };
|
|
392
422
|
}
|
|
423
|
+
async function handleSessions(cmd) {
|
|
424
|
+
const now = Date.now();
|
|
425
|
+
const data = await Promise.all([...automationSessions.entries()].map(async ([workspace, session]) => ({
|
|
426
|
+
workspace,
|
|
427
|
+
windowId: session.windowId,
|
|
428
|
+
tabCount: (await chrome.tabs.query({ windowId: session.windowId })).filter((tab) => isWebUrl(tab.url)).length,
|
|
429
|
+
idleMsRemaining: Math.max(0, session.idleDeadlineAt - now)
|
|
430
|
+
})));
|
|
431
|
+
return { id: cmd.id, ok: true, data };
|
|
432
|
+
}
|