feat: add ChatWise Desktop adapter (9 commands via CDP)
- status, new, send, read, ask, model, screenshot, history, export - Electron-based multi-LLM client (GPT-4/Claude/Gemini) - Includes adapter READMEs (EN+ZH) - Fix truncated README table rows - Total: 27 sites · 137 commands
This commit is contained in:
1 parent
e1d4a6e5e6
commit
3d1ea9b15c
13 files changed
+527
-6
No files matched your search
@@ -143,15 +143,16 @@ npm install -g @jackwener/opencli@latest
|
||||
|
||||
## Built-in Commands
|
||||
|
||||
**26 sites · 128 commands** — run `opencli list` for the live registry.
|
||||
**27 sites · 137 commands** — run `opencli list` for the live registry.
|
||||
|
||||
| Site | Commands | Count | Mode |
|
||||
|------|----------|:-----:|------|
|
||||
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` | 18 | 🔐 Browser |
|
||||
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 15 | 🔐 Browser |
|
||||
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 11 | 🔐 Browser |
|
||||
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | 12 | 🖥️ Desktop |
|
||||
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 11 | 🔐 Browser |
|
||||
| **codex** | `status` `send` `read` `new` `extract-diff` `model` `ask` `screenshot` `history` `export` | 10 | 🖥️ Desktop |
|
||||
| **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` | 9 | 🖥️ Desktop |
|
||||
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | 6 | 🌐 / 🔐 |
|
||||
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 6 | 🔐 Browser |
|
||||
| **antigravity** | `status` `send` `read` `new` `evaluate` | 5 | 🖥️ Desktop |
|
||||
|
||||
+5
-4
@@ -144,19 +144,20 @@ npm install -g @jackwener/opencli@latest
|
||||
|
||||
## 内置命令
|
||||
|
||||
**26 个站点 · 128 命令** — 运行 `opencli list` 查看完整注册表。
|
||||
**27 个站点 · 137 命令** — 运行 `opencli list` 查看完整注册表。
|
||||
|
||||
| 站点 | 命令 | 数量 | 模式 |
|
||||
|------|------|:----:|------|
|
||||
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` | 18 | 🔐 浏览器 |
|
||||
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 15 | 🔐 浏览器 |
|
||||
| **antigravity** | `status` `send` `read` `new` `evaluate` | 5 | 🖥️ 桌面端 |
|
||||
| **chatgpt** | `status` `new` `send` `read` `ask` | 5 | 🖥️ 桌面端 |
|
||||
| **codex** | `status` `send` `read` `new` `extract-diff` `model` `ask` `screenshot` `history` `export` | 10 | 🖥️ 桌面端 |
|
||||
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | 12 | 🖥️ 桌面端 |
|
||||
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 11 | 🔐 浏览器 |
|
||||
| **codex** | `status` `send` `read` `new` `extract-diff` `model` `ask` `screenshot` `history` `export` | 10 | 🖥️ 桌面端 |
|
||||
| **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` | 9 | 🖥️ 桌面端 |
|
||||
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | 6 | 🌐 / 🔐 |
|
||||
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 6 | 🔐 浏览器 |
|
||||
| **antigravity** | `status` `send` `read` `new` `evaluate` | 5 | 🖥️ 桌面端 |
|
||||
| **chatgpt** | `status` `new` `send` `read` `ask` | 5 | 🖥️ 桌面端 |
|
||||
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` | 5 | 🔐 浏览器 |
|
||||
| **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | 3 | 🌐 公开 |
|
||||
| **youtube** | `search` `video` `transcript` | 3 | 🔐 浏览器 |
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# ChatWise Adapter for OpenCLI
|
||||
|
||||
Control the **ChatWise Desktop App** from the terminal via Chrome DevTools Protocol (CDP). ChatWise is an Electron-based multi-LLM client supporting GPT-4, Claude, Gemini, and more.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install [ChatWise](https://chatwise.app/).
|
||||
2. Launch with remote debugging port:
|
||||
```bash
|
||||
/Applications/ChatWise.app/Contents/MacOS/ChatWise \
|
||||
--remote-debugging-port=9228
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9228"
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### Diagnostics
|
||||
- `opencli chatwise status`: Check CDP connection status.
|
||||
- `opencli chatwise screenshot`: Export DOM + accessibility snapshot.
|
||||
|
||||
### Chat
|
||||
- `opencli chatwise new`: Start a new conversation (`Cmd+N`).
|
||||
- `opencli chatwise send "message"`: Send a message to the active chat.
|
||||
- `opencli chatwise read`: Read the current conversation.
|
||||
- `opencli chatwise ask "prompt"`: Send + wait for response + return it (one-shot).
|
||||
|
||||
### AI Features
|
||||
- `opencli chatwise model`: Get the current AI model.
|
||||
- `opencli chatwise model gpt-4`: Switch to a different model.
|
||||
|
||||
### Organization
|
||||
- `opencli chatwise history`: List conversations from the sidebar.
|
||||
- `opencli chatwise export`: Export conversation as Markdown.
|
||||
@@ -0,0 +1,38 @@
|
||||
# ChatWise 适配器
|
||||
|
||||
通过 Chrome DevTools Protocol (CDP) 在终端中控制 **ChatWise 桌面应用**。ChatWise 是基于 Electron 的多 LLM 客户端,支持 GPT-4、Claude、Gemini 等。
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. 安装 [ChatWise](https://chatwise.app/)。
|
||||
2. 通过远程调试端口启动:
|
||||
```bash
|
||||
/Applications/ChatWise.app/Contents/MacOS/ChatWise \
|
||||
--remote-debugging-port=9228
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
```bash
|
||||
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9228"
|
||||
```
|
||||
|
||||
## 命令
|
||||
|
||||
### 诊断
|
||||
- `opencli chatwise status`:检查 CDP 连接状态。
|
||||
- `opencli chatwise screenshot`:导出 DOM + accessibility 快照。
|
||||
|
||||
### 对话
|
||||
- `opencli chatwise new`:开始新对话(`Cmd+N`)。
|
||||
- `opencli chatwise send "消息"`:发送消息到当前对话。
|
||||
- `opencli chatwise read`:读取当前对话内容。
|
||||
- `opencli chatwise ask "提示词"`:发送 + 等待回复 + 返回结果(一站式)。
|
||||
|
||||
### AI 功能
|
||||
- `opencli chatwise model`:获取当前 AI 模型。
|
||||
- `opencli chatwise model gpt-4`:切换模型。
|
||||
|
||||
### 组织管理
|
||||
- `opencli chatwise history`:列出 sidebar 会话列表。
|
||||
- `opencli chatwise export`:导出对话为 Markdown 文件。
|
||||
@@ -0,0 +1,87 @@
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const askCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'ask',
|
||||
description: 'Send a prompt and wait for the AI response (send + wait + read)',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'text', required: true, positional: true, help: 'Prompt to send' },
|
||||
{ name: 'timeout', required: false, help: 'Max seconds to wait (default: 30)', default: '30' },
|
||||
],
|
||||
columns: ['Role', 'Text'],
|
||||
func: async (page: IPage, kwargs: any) => {
|
||||
const text = kwargs.text as string;
|
||||
const timeout = parseInt(kwargs.timeout as string, 10) || 30;
|
||||
|
||||
// Snapshot content length
|
||||
const beforeLen = await page.evaluate(`
|
||||
(function() {
|
||||
const msgs = document.querySelectorAll('[data-message-id], [class*="message"], [class*="bubble"]');
|
||||
return msgs.length;
|
||||
})()
|
||||
`);
|
||||
|
||||
// Send message
|
||||
await page.evaluate(`
|
||||
(function(text) {
|
||||
let composer = document.querySelector('textarea');
|
||||
if (!composer) {
|
||||
const editables = Array.from(document.querySelectorAll('[contenteditable="true"]'));
|
||||
composer = editables.length > 0 ? editables[editables.length - 1] : null;
|
||||
}
|
||||
if (!composer) throw new Error('Could not find input');
|
||||
composer.focus();
|
||||
if (composer.tagName === 'TEXTAREA') {
|
||||
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
|
||||
setter.call(composer, text);
|
||||
composer.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
} else {
|
||||
document.execCommand('insertText', false, text);
|
||||
}
|
||||
})(${JSON.stringify(text)})
|
||||
`);
|
||||
|
||||
await page.wait(0.5);
|
||||
await page.pressKey('Enter');
|
||||
|
||||
// Poll for response
|
||||
const pollInterval = 2;
|
||||
const maxPolls = Math.ceil(timeout / pollInterval);
|
||||
let response = '';
|
||||
|
||||
for (let i = 0; i < maxPolls; i++) {
|
||||
await page.wait(pollInterval);
|
||||
|
||||
const result = await page.evaluate(`
|
||||
(function(prevLen) {
|
||||
const msgs = document.querySelectorAll('[data-message-id], [class*="message"], [class*="bubble"]');
|
||||
if (msgs.length <= prevLen) return null;
|
||||
const last = msgs[msgs.length - 1];
|
||||
const text = last.innerText || last.textContent;
|
||||
return text ? text.trim() : null;
|
||||
})(${beforeLen})
|
||||
`);
|
||||
|
||||
if (result) {
|
||||
response = result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return [
|
||||
{ Role: 'User', Text: text },
|
||||
{ Role: 'System', Text: `No response within ${timeout}s.` },
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{ Role: 'User', Text: text },
|
||||
{ Role: 'Assistant', Text: response },
|
||||
];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as fs from 'node:fs';
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const exportCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'export',
|
||||
description: 'Export the current ChatWise conversation to a Markdown file',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'output', required: false, positional: true, help: 'Output file (default: /tmp/chatwise-export.md)' },
|
||||
],
|
||||
columns: ['Status', 'File', 'Messages'],
|
||||
func: async (page: IPage, kwargs: any) => {
|
||||
const outputPath = (kwargs.output as string) || '/tmp/chatwise-export.md';
|
||||
|
||||
const md = await page.evaluate(`
|
||||
(function() {
|
||||
const selectors = [
|
||||
'[data-message-id]',
|
||||
'[class*="message"]',
|
||||
'[class*="chat-item"]',
|
||||
'[class*="bubble"]',
|
||||
];
|
||||
|
||||
for (const sel of selectors) {
|
||||
const nodes = document.querySelectorAll(sel);
|
||||
if (nodes.length > 0) {
|
||||
return Array.from(nodes).map((n, i) => '## Message ' + (i + 1) + '\\n\\n' + (n.innerText || n.textContent).trim()).join('\\n\\n---\\n\\n');
|
||||
}
|
||||
}
|
||||
|
||||
const main = document.querySelector('main, [role="main"], [class*="chat-container"]');
|
||||
if (main) return main.innerText || main.textContent;
|
||||
return document.body.innerText;
|
||||
})()
|
||||
`);
|
||||
|
||||
fs.writeFileSync(outputPath, '# ChatWise Conversation Export\\n\\n' + md);
|
||||
|
||||
return [
|
||||
{
|
||||
Status: 'Success',
|
||||
File: outputPath,
|
||||
Messages: md.split('## Message').length - 1,
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const historyCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'history',
|
||||
description: 'List conversation history in ChatWise sidebar',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [],
|
||||
columns: ['Index', 'Title'],
|
||||
func: async (page: IPage) => {
|
||||
const items = await page.evaluate(`
|
||||
(function() {
|
||||
const results = [];
|
||||
const selectors = [
|
||||
'[class*="sidebar"] [class*="item"]',
|
||||
'[class*="conversation-list"] a',
|
||||
'[class*="chat-list"] > *',
|
||||
'nav a',
|
||||
'aside a',
|
||||
'[role="listbox"] [role="option"]',
|
||||
];
|
||||
|
||||
for (const sel of selectors) {
|
||||
const nodes = document.querySelectorAll(sel);
|
||||
if (nodes.length > 0) {
|
||||
nodes.forEach((n, i) => {
|
||||
const text = (n.textContent || '').trim().substring(0, 100);
|
||||
if (text) results.push({ Index: i + 1, Title: text });
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
})()
|
||||
`);
|
||||
|
||||
if (items.length === 0) {
|
||||
return [{ Index: 0, Title: 'No history found. Ensure the sidebar is visible.' }];
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const modelCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'model',
|
||||
description: 'Get or switch the active AI model in ChatWise',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'model_name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
|
||||
],
|
||||
columns: ['Status', 'Model'],
|
||||
func: async (page: IPage, kwargs: any) => {
|
||||
const desiredModel = kwargs.model_name as string | undefined;
|
||||
|
||||
if (!desiredModel) {
|
||||
// Read current model
|
||||
const currentModel = await page.evaluate(`
|
||||
(function() {
|
||||
// ChatWise is a multi-LLM client, it typically shows the model name in a dropdown or header
|
||||
const selectors = [
|
||||
'[class*="model"] span',
|
||||
'[class*="Model"] span',
|
||||
'[data-testid*="model"]',
|
||||
'button[class*="model"]',
|
||||
'[aria-label*="Model"]',
|
||||
'[aria-label*="model"]',
|
||||
];
|
||||
|
||||
for (const sel of selectors) {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) {
|
||||
const text = (el.textContent || el.getAttribute('title') || '').trim();
|
||||
if (text) return text;
|
||||
}
|
||||
}
|
||||
|
||||
return 'Unknown or Not Found';
|
||||
})()
|
||||
`);
|
||||
|
||||
return [{ Status: 'Active', Model: currentModel }];
|
||||
} else {
|
||||
// Try to switch model
|
||||
await page.evaluate(`
|
||||
(function(target) {
|
||||
const selectors = [
|
||||
'[class*="model"]',
|
||||
'[class*="Model"]',
|
||||
'button[class*="model"]',
|
||||
];
|
||||
|
||||
for (const sel of selectors) {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) { el.click(); return; }
|
||||
}
|
||||
throw new Error('Could not find model selector');
|
||||
})(${JSON.stringify(desiredModel)})
|
||||
`);
|
||||
|
||||
await page.wait(0.5);
|
||||
|
||||
// Find and click the target model in the dropdown
|
||||
const found = await page.evaluate(`
|
||||
(function(target) {
|
||||
const options = document.querySelectorAll('[role="option"], [role="menuitem"], [class*="dropdown-item"], li');
|
||||
for (const opt of options) {
|
||||
if ((opt.textContent || '').toLowerCase().includes(target.toLowerCase())) {
|
||||
opt.click();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})(${JSON.stringify(desiredModel)})
|
||||
`);
|
||||
|
||||
return [
|
||||
{
|
||||
Status: found ? 'Switched' : 'Dropdown opened but model not found',
|
||||
Model: desiredModel,
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const newCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'new',
|
||||
description: 'Start a new conversation in ChatWise',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [],
|
||||
columns: ['Status'],
|
||||
func: async (page: IPage) => {
|
||||
// ChatWise uses standard Electron shortcuts
|
||||
const isMac = process.platform === 'darwin';
|
||||
await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
|
||||
await page.wait(1);
|
||||
|
||||
return [{ Status: 'Success' }];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const readCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'read',
|
||||
description: 'Read the current ChatWise conversation history',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [],
|
||||
columns: ['Content'],
|
||||
func: async (page: IPage) => {
|
||||
const content = await page.evaluate(`
|
||||
(function() {
|
||||
// Try common chat message selectors
|
||||
const selectors = [
|
||||
'[data-message-id]',
|
||||
'[class*="message"]',
|
||||
'[class*="chat-item"]',
|
||||
'[class*="bubble"]',
|
||||
'[role="log"] > *',
|
||||
];
|
||||
|
||||
for (const sel of selectors) {
|
||||
const nodes = document.querySelectorAll(sel);
|
||||
if (nodes.length > 0) {
|
||||
return Array.from(nodes).map(n => (n.innerText || n.textContent).trim()).filter(Boolean).join('\\n\\n---\\n\\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: main content area
|
||||
const main = document.querySelector('main, [role="main"], [class*="chat-container"], [class*="conversation"]');
|
||||
if (main) return main.innerText || main.textContent;
|
||||
|
||||
return document.body.innerText;
|
||||
})()
|
||||
`);
|
||||
|
||||
return [{ Content: content }];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as fs from 'node:fs';
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const screenshotCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'screenshot',
|
||||
description: 'Capture a snapshot of the current ChatWise window (DOM + Accessibility tree)',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [
|
||||
{ name: 'output', required: false, positional: true, help: 'Output file path (default: /tmp/chatwise-snapshot)' },
|
||||
],
|
||||
columns: ['Status', 'File'],
|
||||
func: async (page: IPage, kwargs: any) => {
|
||||
const basePath = (kwargs.output as string) || '/tmp/chatwise-snapshot';
|
||||
|
||||
const snap = await page.snapshot({ compact: true });
|
||||
const html = await page.evaluate('document.documentElement.outerHTML');
|
||||
|
||||
const htmlPath = basePath + '-dom.html';
|
||||
const snapPath = basePath + '-a11y.txt';
|
||||
|
||||
fs.writeFileSync(htmlPath, html);
|
||||
fs.writeFileSync(snapPath, typeof snap === 'string' ? snap : JSON.stringify(snap, null, 2));
|
||||
|
||||
return [
|
||||
{ Status: 'Success', File: htmlPath },
|
||||
{ Status: 'Success', File: snapPath },
|
||||
];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const sendCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'send',
|
||||
description: 'Send a message to the active ChatWise conversation',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
|
||||
columns: ['Status', 'InjectedText'],
|
||||
func: async (page: IPage, kwargs: any) => {
|
||||
const text = kwargs.text as string;
|
||||
|
||||
await page.evaluate(`
|
||||
(function(text) {
|
||||
// ChatWise input can be textarea or contenteditable
|
||||
let composer = document.querySelector('textarea');
|
||||
if (!composer) {
|
||||
const editables = Array.from(document.querySelectorAll('[contenteditable="true"]'));
|
||||
composer = editables.length > 0 ? editables[editables.length - 1] : null;
|
||||
}
|
||||
|
||||
if (!composer) throw new Error('Could not find ChatWise input element');
|
||||
|
||||
composer.focus();
|
||||
|
||||
if (composer.tagName === 'TEXTAREA') {
|
||||
// For textarea, set value and dispatch input event
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
|
||||
nativeInputValueSetter.call(composer, text);
|
||||
composer.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
} else {
|
||||
document.execCommand('insertText', false, text);
|
||||
}
|
||||
})(${JSON.stringify(text)})
|
||||
`);
|
||||
|
||||
await page.wait(0.5);
|
||||
await page.pressKey('Enter');
|
||||
|
||||
return [
|
||||
{
|
||||
Status: 'Success',
|
||||
InjectedText: text,
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const statusCommand = cli({
|
||||
site: 'chatwise',
|
||||
name: 'status',
|
||||
description: 'Check active CDP connection to ChatWise Desktop',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.UI,
|
||||
browser: true,
|
||||
args: [],
|
||||
columns: ['Status', 'Url', 'Title'],
|
||||
func: async (page: IPage) => {
|
||||
const url = await page.evaluate('window.location.href');
|
||||
const title = await page.evaluate('document.title');
|
||||
|
||||
return [
|
||||
{
|
||||
Status: 'Connected',
|
||||
Url: url,
|
||||
Title: title,
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user