@jackwener/opencli 0.9.5 → 0.9.8

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.
Files changed (270) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
  4. package/.github/ISSUE_TEMPLATE/new_site_adapter.yml +57 -0
  5. package/.github/dependabot.yml +27 -0
  6. package/.github/pull_request_template.md +24 -0
  7. package/.github/workflows/ci.yml +14 -8
  8. package/.github/workflows/e2e-headed.yml +6 -2
  9. package/.github/workflows/pkg-pr-new.yml +2 -2
  10. package/.github/workflows/release-please.yml +25 -0
  11. package/.github/workflows/release.yml +2 -2
  12. package/.github/workflows/security.yml +36 -0
  13. package/CLI-ELECTRON.md +89 -36
  14. package/CONTRIBUTING.md +167 -0
  15. package/README.md +98 -32
  16. package/README.zh-CN.md +99 -33
  17. package/dist/browser/discover.js +22 -7
  18. package/dist/browser.test.js +23 -0
  19. package/dist/build-manifest.d.ts +26 -0
  20. package/dist/build-manifest.js +132 -60
  21. package/dist/build-manifest.test.d.ts +1 -0
  22. package/dist/build-manifest.test.js +26 -0
  23. package/dist/cli-manifest.json +1875 -271
  24. package/dist/clis/antigravity/model.js +2 -2
  25. package/dist/clis/antigravity/send.js +2 -2
  26. package/dist/clis/bilibili/download.d.ts +10 -0
  27. package/dist/clis/bilibili/download.js +135 -0
  28. package/dist/clis/chatgpt/ask.d.ts +1 -0
  29. package/dist/clis/chatgpt/ask.js +68 -0
  30. package/dist/clis/chatgpt/send.js +11 -0
  31. package/dist/clis/chatwise/ask.d.ts +1 -0
  32. package/dist/clis/chatwise/ask.js +76 -0
  33. package/dist/clis/chatwise/export.d.ts +1 -0
  34. package/dist/clis/chatwise/export.js +46 -0
  35. package/dist/clis/chatwise/history.d.ts +1 -0
  36. package/dist/clis/chatwise/history.js +43 -0
  37. package/dist/clis/chatwise/model.d.ts +1 -0
  38. package/dist/clis/chatwise/model.js +81 -0
  39. package/dist/clis/chatwise/new.d.ts +1 -0
  40. package/dist/clis/chatwise/new.js +18 -0
  41. package/dist/clis/chatwise/read.d.ts +1 -0
  42. package/dist/clis/chatwise/read.js +39 -0
  43. package/dist/clis/chatwise/screenshot.d.ts +1 -0
  44. package/dist/clis/chatwise/screenshot.js +27 -0
  45. package/dist/clis/chatwise/send.d.ts +1 -0
  46. package/dist/clis/chatwise/send.js +45 -0
  47. package/dist/clis/chatwise/status.d.ts +1 -0
  48. package/dist/clis/chatwise/status.js +22 -0
  49. package/dist/clis/codex/ask.d.ts +1 -0
  50. package/dist/clis/codex/ask.js +67 -0
  51. package/dist/clis/codex/export.d.ts +1 -0
  52. package/dist/clis/codex/export.js +37 -0
  53. package/dist/clis/codex/history.d.ts +1 -0
  54. package/dist/clis/codex/history.js +43 -0
  55. package/dist/clis/codex/read.js +3 -5
  56. package/dist/clis/codex/screenshot.d.ts +1 -0
  57. package/dist/clis/codex/screenshot.js +27 -0
  58. package/dist/clis/codex/send.js +3 -6
  59. package/dist/clis/codex/status.js +2 -1
  60. package/dist/clis/cursor/ask.d.ts +1 -0
  61. package/dist/clis/cursor/ask.js +69 -0
  62. package/dist/clis/cursor/composer.js +9 -28
  63. package/dist/clis/cursor/export.d.ts +1 -0
  64. package/dist/clis/cursor/export.js +51 -0
  65. package/dist/clis/cursor/history.d.ts +1 -0
  66. package/dist/clis/cursor/history.js +43 -0
  67. package/dist/clis/cursor/new.js +4 -13
  68. package/dist/clis/cursor/screenshot.d.ts +1 -0
  69. package/dist/clis/cursor/screenshot.js +31 -0
  70. package/dist/clis/discord-app/channels.d.ts +1 -0
  71. package/dist/clis/discord-app/channels.js +45 -0
  72. package/dist/clis/discord-app/members.d.ts +1 -0
  73. package/dist/clis/discord-app/members.js +38 -0
  74. package/dist/clis/discord-app/read.d.ts +1 -0
  75. package/dist/clis/discord-app/read.js +45 -0
  76. package/dist/clis/discord-app/search.d.ts +1 -0
  77. package/dist/clis/discord-app/search.js +56 -0
  78. package/dist/clis/discord-app/send.d.ts +1 -0
  79. package/dist/clis/discord-app/send.js +27 -0
  80. package/dist/clis/discord-app/servers.d.ts +1 -0
  81. package/dist/clis/discord-app/servers.js +36 -0
  82. package/dist/clis/discord-app/status.d.ts +1 -0
  83. package/dist/clis/discord-app/status.js +16 -0
  84. package/dist/clis/feishu/new.d.ts +1 -0
  85. package/dist/clis/feishu/new.js +27 -0
  86. package/dist/clis/feishu/read.d.ts +1 -0
  87. package/dist/clis/feishu/read.js +40 -0
  88. package/dist/clis/feishu/search.d.ts +1 -0
  89. package/dist/clis/feishu/search.js +30 -0
  90. package/dist/clis/feishu/send.d.ts +1 -0
  91. package/dist/clis/feishu/send.js +39 -0
  92. package/dist/clis/feishu/status.d.ts +1 -0
  93. package/dist/clis/feishu/status.js +28 -0
  94. package/dist/clis/grok/ask.d.ts +1 -0
  95. package/dist/clis/grok/ask.js +82 -0
  96. package/dist/clis/grok/debug.d.ts +1 -0
  97. package/dist/clis/grok/debug.js +45 -0
  98. package/dist/clis/jimeng/generate.yaml +84 -0
  99. package/dist/clis/jimeng/history.yaml +47 -0
  100. package/dist/clis/linux-do/categories.yaml +41 -0
  101. package/dist/clis/linux-do/category.yaml +49 -0
  102. package/dist/clis/linux-do/hot.yaml +50 -0
  103. package/dist/clis/linux-do/latest.yaml +40 -0
  104. package/dist/clis/linux-do/search.yaml +45 -0
  105. package/dist/clis/linux-do/topic.yaml +38 -0
  106. package/dist/clis/notion/export.d.ts +1 -0
  107. package/dist/clis/notion/export.js +31 -0
  108. package/dist/clis/notion/favorites.d.ts +1 -0
  109. package/dist/clis/notion/favorites.js +84 -0
  110. package/dist/clis/notion/new.d.ts +1 -0
  111. package/dist/clis/notion/new.js +34 -0
  112. package/dist/clis/notion/read.d.ts +1 -0
  113. package/dist/clis/notion/read.js +30 -0
  114. package/dist/clis/notion/search.d.ts +1 -0
  115. package/dist/clis/notion/search.js +46 -0
  116. package/dist/clis/notion/sidebar.d.ts +1 -0
  117. package/dist/clis/notion/sidebar.js +41 -0
  118. package/dist/clis/notion/status.d.ts +1 -0
  119. package/dist/clis/notion/status.js +16 -0
  120. package/dist/clis/notion/write.d.ts +1 -0
  121. package/dist/clis/notion/write.js +40 -0
  122. package/dist/clis/twitter/download.d.ts +8 -0
  123. package/dist/clis/twitter/download.js +204 -0
  124. package/dist/clis/wechat/chats.d.ts +1 -0
  125. package/dist/clis/wechat/chats.js +28 -0
  126. package/dist/clis/wechat/contacts.d.ts +1 -0
  127. package/dist/clis/wechat/contacts.js +28 -0
  128. package/dist/clis/wechat/read.d.ts +1 -0
  129. package/dist/clis/wechat/read.js +58 -0
  130. package/dist/clis/wechat/search.d.ts +1 -0
  131. package/dist/clis/wechat/search.js +31 -0
  132. package/dist/clis/wechat/send.d.ts +1 -0
  133. package/dist/clis/wechat/send.js +42 -0
  134. package/dist/clis/wechat/status.d.ts +1 -0
  135. package/dist/clis/wechat/status.js +29 -0
  136. package/dist/clis/xiaohongshu/creator-note-detail.d.ts +10 -0
  137. package/dist/clis/xiaohongshu/creator-note-detail.js +88 -0
  138. package/dist/clis/xiaohongshu/creator-notes.d.ts +11 -0
  139. package/dist/clis/xiaohongshu/creator-notes.js +109 -0
  140. package/dist/clis/xiaohongshu/creator-profile.d.ts +10 -0
  141. package/dist/clis/xiaohongshu/creator-profile.js +54 -0
  142. package/dist/clis/xiaohongshu/creator-stats.d.ts +10 -0
  143. package/dist/clis/xiaohongshu/creator-stats.js +74 -0
  144. package/dist/clis/xiaohongshu/download.d.ts +7 -0
  145. package/dist/clis/xiaohongshu/download.js +155 -0
  146. package/dist/clis/xiaohongshu/search.js +1 -1
  147. package/dist/clis/xiaohongshu/user-helpers.d.ts +15 -0
  148. package/dist/clis/xiaohongshu/user-helpers.js +67 -0
  149. package/dist/clis/xiaohongshu/user-helpers.test.d.ts +1 -0
  150. package/dist/clis/xiaohongshu/user-helpers.test.js +81 -0
  151. package/dist/clis/xiaohongshu/user.js +46 -29
  152. package/dist/clis/zhihu/download.d.ts +11 -0
  153. package/dist/clis/zhihu/download.js +186 -0
  154. package/dist/clis/zhihu/download.test.d.ts +1 -0
  155. package/dist/clis/zhihu/download.test.js +10 -0
  156. package/dist/download/index.d.ts +79 -0
  157. package/dist/download/index.js +325 -0
  158. package/dist/download/progress.d.ts +36 -0
  159. package/dist/download/progress.js +111 -0
  160. package/dist/engine.test.js +15 -0
  161. package/dist/main.js +16 -3
  162. package/dist/pipeline/registry.js +2 -0
  163. package/dist/pipeline/steps/download.d.ts +34 -0
  164. package/dist/pipeline/steps/download.js +251 -0
  165. package/dist/pipeline/template.js +28 -0
  166. package/package.json +4 -3
  167. package/scripts/test-site.mjs +70 -0
  168. package/src/browser/discover.ts +23 -7
  169. package/src/browser.test.ts +23 -0
  170. package/src/build-manifest.test.ts +28 -0
  171. package/src/build-manifest.ts +147 -57
  172. package/src/clis/antigravity/README.md +2 -3
  173. package/src/clis/antigravity/README.zh-CN.md +2 -3
  174. package/src/clis/antigravity/SKILL.md +1 -1
  175. package/src/clis/antigravity/model.ts +2 -2
  176. package/src/clis/antigravity/send.ts +2 -2
  177. package/src/clis/bilibili/download.ts +161 -0
  178. package/src/clis/chatgpt/README.md +25 -16
  179. package/src/clis/chatgpt/README.zh-CN.md +27 -18
  180. package/src/clis/chatgpt/ask.ts +77 -0
  181. package/src/clis/chatgpt/send.ts +12 -0
  182. package/src/clis/chatwise/README.md +38 -0
  183. package/src/clis/chatwise/README.zh-CN.md +38 -0
  184. package/src/clis/chatwise/ask.ts +87 -0
  185. package/src/clis/chatwise/export.ts +51 -0
  186. package/src/clis/chatwise/history.ts +47 -0
  187. package/src/clis/chatwise/model.ts +87 -0
  188. package/src/clis/chatwise/new.ts +21 -0
  189. package/src/clis/chatwise/read.ts +42 -0
  190. package/src/clis/chatwise/screenshot.ts +33 -0
  191. package/src/clis/chatwise/send.ts +50 -0
  192. package/src/clis/chatwise/status.ts +25 -0
  193. package/src/clis/codex/ask.ts +77 -0
  194. package/src/clis/codex/export.ts +42 -0
  195. package/src/clis/codex/extract-diff.ts +1 -0
  196. package/src/clis/codex/history.ts +47 -0
  197. package/src/clis/codex/read.ts +5 -6
  198. package/src/clis/codex/screenshot.ts +33 -0
  199. package/src/clis/codex/send.ts +6 -7
  200. package/src/clis/codex/status.ts +4 -2
  201. package/src/clis/cursor/ask.ts +81 -0
  202. package/src/clis/cursor/composer.ts +9 -30
  203. package/src/clis/cursor/export.ts +57 -0
  204. package/src/clis/cursor/history.ts +47 -0
  205. package/src/clis/cursor/new.ts +4 -15
  206. package/src/clis/cursor/screenshot.ts +38 -0
  207. package/src/clis/discord-app/README.md +28 -0
  208. package/src/clis/discord-app/README.zh-CN.md +28 -0
  209. package/src/clis/discord-app/channels.ts +48 -0
  210. package/src/clis/discord-app/members.ts +41 -0
  211. package/src/clis/discord-app/read.ts +49 -0
  212. package/src/clis/discord-app/search.ts +64 -0
  213. package/src/clis/discord-app/send.ts +32 -0
  214. package/src/clis/discord-app/servers.ts +39 -0
  215. package/src/clis/discord-app/status.ts +18 -0
  216. package/src/clis/feishu/README.md +20 -0
  217. package/src/clis/feishu/README.zh-CN.md +20 -0
  218. package/src/clis/feishu/new.ts +32 -0
  219. package/src/clis/feishu/read.ts +48 -0
  220. package/src/clis/feishu/search.ts +35 -0
  221. package/src/clis/feishu/send.ts +46 -0
  222. package/src/clis/feishu/status.ts +34 -0
  223. package/src/clis/grok/ask.ts +90 -0
  224. package/src/clis/grok/debug.ts +49 -0
  225. package/src/clis/jimeng/generate.yaml +84 -0
  226. package/src/clis/jimeng/history.yaml +47 -0
  227. package/src/clis/linux-do/categories.yaml +41 -0
  228. package/src/clis/linux-do/category.yaml +49 -0
  229. package/src/clis/linux-do/hot.yaml +50 -0
  230. package/src/clis/linux-do/latest.yaml +40 -0
  231. package/src/clis/linux-do/search.yaml +45 -0
  232. package/src/clis/linux-do/topic.yaml +38 -0
  233. package/src/clis/notion/README.md +29 -0
  234. package/src/clis/notion/README.zh-CN.md +29 -0
  235. package/src/clis/notion/export.ts +36 -0
  236. package/src/clis/notion/favorites.ts +87 -0
  237. package/src/clis/notion/new.ts +39 -0
  238. package/src/clis/notion/read.ts +33 -0
  239. package/src/clis/notion/search.ts +54 -0
  240. package/src/clis/notion/sidebar.ts +44 -0
  241. package/src/clis/notion/status.ts +18 -0
  242. package/src/clis/notion/write.ts +45 -0
  243. package/src/clis/twitter/download.ts +227 -0
  244. package/src/clis/wechat/README.md +28 -0
  245. package/src/clis/wechat/README.zh-CN.md +28 -0
  246. package/src/clis/wechat/chats.ts +33 -0
  247. package/src/clis/wechat/contacts.ts +33 -0
  248. package/src/clis/wechat/read.ts +72 -0
  249. package/src/clis/wechat/search.ts +36 -0
  250. package/src/clis/wechat/send.ts +49 -0
  251. package/src/clis/wechat/status.ts +35 -0
  252. package/src/clis/xiaohongshu/creator-note-detail.ts +95 -0
  253. package/src/clis/xiaohongshu/creator-notes.ts +116 -0
  254. package/src/clis/xiaohongshu/creator-profile.ts +60 -0
  255. package/src/clis/xiaohongshu/creator-stats.ts +81 -0
  256. package/src/clis/xiaohongshu/download.ts +173 -0
  257. package/src/clis/xiaohongshu/search.ts +1 -1
  258. package/src/clis/xiaohongshu/user-helpers.test.ts +106 -0
  259. package/src/clis/xiaohongshu/user-helpers.ts +85 -0
  260. package/src/clis/xiaohongshu/user.ts +52 -32
  261. package/src/clis/zhihu/download.test.ts +12 -0
  262. package/src/clis/zhihu/download.ts +223 -0
  263. package/src/download/index.ts +395 -0
  264. package/src/download/progress.ts +125 -0
  265. package/src/engine.test.ts +17 -0
  266. package/src/main.ts +12 -3
  267. package/src/pipeline/registry.ts +2 -0
  268. package/src/pipeline/steps/download.ts +310 -0
  269. package/src/pipeline/template.ts +26 -0
  270. package/tests/e2e/browser-auth.test.ts +25 -0
@@ -14,6 +14,13 @@ export const sendCommand = cli({
14
14
  func: async (page: IPage | null, kwargs: any) => {
15
15
  const text = kwargs.text as string;
16
16
  try {
17
+ // Backup current clipboard content
18
+ let clipBackup = '';
19
+ try {
20
+ clipBackup = execSync('pbpaste', { encoding: 'utf-8' });
21
+ } catch { /* clipboard may be empty */ }
22
+
23
+ // Copy text to clipboard
17
24
  spawnSync('pbcopy', { input: text });
18
25
 
19
26
  execSync("osascript -e 'tell application \"ChatGPT\" to activate'");
@@ -28,6 +35,11 @@ export const sendCommand = cli({
28
35
 
29
36
  execSync(cmd);
30
37
 
38
+ // Restore original clipboard content
39
+ if (clipBackup) {
40
+ spawnSync('pbcopy', { input: clipBackup });
41
+ }
42
+
31
43
  return [{ Status: 'Success' }];
32
44
  } catch (err: any) {
33
45
  return [{ Status: "Error: " + err.message }];
@@ -0,0 +1,38 @@
1
+ # ChatWise Adapter for OpenCLI
2
+
3
+ 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.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. Install [ChatWise](https://chatwise.app/).
8
+ 2. Launch with remote debugging port:
9
+ ```bash
10
+ /Applications/ChatWise.app/Contents/MacOS/ChatWise \
11
+ --remote-debugging-port=9228
12
+ ```
13
+
14
+ ## Setup
15
+
16
+ ```bash
17
+ export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9228"
18
+ ```
19
+
20
+ ## Commands
21
+
22
+ ### Diagnostics
23
+ - `opencli chatwise status`: Check CDP connection status.
24
+ - `opencli chatwise screenshot`: Export DOM + accessibility snapshot.
25
+
26
+ ### Chat
27
+ - `opencli chatwise new`: Start a new conversation (`Cmd+N`).
28
+ - `opencli chatwise send "message"`: Send a message to the active chat.
29
+ - `opencli chatwise read`: Read the current conversation.
30
+ - `opencli chatwise ask "prompt"`: Send + wait for response + return it (one-shot).
31
+
32
+ ### AI Features
33
+ - `opencli chatwise model`: Get the current AI model.
34
+ - `opencli chatwise model gpt-4`: Switch to a different model.
35
+
36
+ ### Organization
37
+ - `opencli chatwise history`: List conversations from the sidebar.
38
+ - `opencli chatwise export`: Export conversation as Markdown.
@@ -0,0 +1,38 @@
1
+ # ChatWise 适配器
2
+
3
+ 通过 Chrome DevTools Protocol (CDP) 在终端中控制 **ChatWise 桌面应用**。ChatWise 是基于 Electron 的多 LLM 客户端,支持 GPT-4、Claude、Gemini 等。
4
+
5
+ ## 前置条件
6
+
7
+ 1. 安装 [ChatWise](https://chatwise.app/)。
8
+ 2. 通过远程调试端口启动:
9
+ ```bash
10
+ /Applications/ChatWise.app/Contents/MacOS/ChatWise \
11
+ --remote-debugging-port=9228
12
+ ```
13
+
14
+ ## 配置
15
+
16
+ ```bash
17
+ export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9228"
18
+ ```
19
+
20
+ ## 命令
21
+
22
+ ### 诊断
23
+ - `opencli chatwise status`:检查 CDP 连接状态。
24
+ - `opencli chatwise screenshot`:导出 DOM + accessibility 快照。
25
+
26
+ ### 对话
27
+ - `opencli chatwise new`:开始新对话(`Cmd+N`)。
28
+ - `opencli chatwise send "消息"`:发送消息到当前对话。
29
+ - `opencli chatwise read`:读取当前对话内容。
30
+ - `opencli chatwise ask "提示词"`:发送 + 等待回复 + 返回结果(一站式)。
31
+
32
+ ### AI 功能
33
+ - `opencli chatwise model`:获取当前 AI 模型。
34
+ - `opencli chatwise model gpt-4`:切换模型。
35
+
36
+ ### 组织管理
37
+ - `opencli chatwise history`:列出 sidebar 会话列表。
38
+ - `opencli chatwise export`:导出对话为 Markdown 文件。
@@ -0,0 +1,87 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const askCommand = cli({
5
+ site: 'chatwise',
6
+ name: 'ask',
7
+ description: 'Send a prompt and wait for the AI response (send + wait + read)',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'text', required: true, positional: true, help: 'Prompt to send' },
13
+ { name: 'timeout', required: false, help: 'Max seconds to wait (default: 30)', default: '30' },
14
+ ],
15
+ columns: ['Role', 'Text'],
16
+ func: async (page: IPage, kwargs: any) => {
17
+ const text = kwargs.text as string;
18
+ const timeout = parseInt(kwargs.timeout as string, 10) || 30;
19
+
20
+ // Snapshot content length
21
+ const beforeLen = await page.evaluate(`
22
+ (function() {
23
+ const msgs = document.querySelectorAll('[data-message-id], [class*="message"], [class*="bubble"]');
24
+ return msgs.length;
25
+ })()
26
+ `);
27
+
28
+ // Send message
29
+ await page.evaluate(`
30
+ (function(text) {
31
+ let composer = document.querySelector('textarea');
32
+ if (!composer) {
33
+ const editables = Array.from(document.querySelectorAll('[contenteditable="true"]'));
34
+ composer = editables.length > 0 ? editables[editables.length - 1] : null;
35
+ }
36
+ if (!composer) throw new Error('Could not find input');
37
+ composer.focus();
38
+ if (composer.tagName === 'TEXTAREA') {
39
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
40
+ setter.call(composer, text);
41
+ composer.dispatchEvent(new Event('input', { bubbles: true }));
42
+ } else {
43
+ document.execCommand('insertText', false, text);
44
+ }
45
+ })(${JSON.stringify(text)})
46
+ `);
47
+
48
+ await page.wait(0.5);
49
+ await page.pressKey('Enter');
50
+
51
+ // Poll for response
52
+ const pollInterval = 2;
53
+ const maxPolls = Math.ceil(timeout / pollInterval);
54
+ let response = '';
55
+
56
+ for (let i = 0; i < maxPolls; i++) {
57
+ await page.wait(pollInterval);
58
+
59
+ const result = await page.evaluate(`
60
+ (function(prevLen) {
61
+ const msgs = document.querySelectorAll('[data-message-id], [class*="message"], [class*="bubble"]');
62
+ if (msgs.length <= prevLen) return null;
63
+ const last = msgs[msgs.length - 1];
64
+ const text = last.innerText || last.textContent;
65
+ return text ? text.trim() : null;
66
+ })(${beforeLen})
67
+ `);
68
+
69
+ if (result) {
70
+ response = result;
71
+ break;
72
+ }
73
+ }
74
+
75
+ if (!response) {
76
+ return [
77
+ { Role: 'User', Text: text },
78
+ { Role: 'System', Text: `No response within ${timeout}s.` },
79
+ ];
80
+ }
81
+
82
+ return [
83
+ { Role: 'User', Text: text },
84
+ { Role: 'Assistant', Text: response },
85
+ ];
86
+ },
87
+ });
@@ -0,0 +1,51 @@
1
+ import * as fs from 'node:fs';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import type { IPage } from '../../types.js';
4
+
5
+ export const exportCommand = cli({
6
+ site: 'chatwise',
7
+ name: 'export',
8
+ description: 'Export the current ChatWise conversation to a Markdown file',
9
+ domain: 'localhost',
10
+ strategy: Strategy.UI,
11
+ browser: true,
12
+ args: [
13
+ { name: 'output', required: false, positional: true, help: 'Output file (default: /tmp/chatwise-export.md)' },
14
+ ],
15
+ columns: ['Status', 'File', 'Messages'],
16
+ func: async (page: IPage, kwargs: any) => {
17
+ const outputPath = (kwargs.output as string) || '/tmp/chatwise-export.md';
18
+
19
+ const md = await page.evaluate(`
20
+ (function() {
21
+ const selectors = [
22
+ '[data-message-id]',
23
+ '[class*="message"]',
24
+ '[class*="chat-item"]',
25
+ '[class*="bubble"]',
26
+ ];
27
+
28
+ for (const sel of selectors) {
29
+ const nodes = document.querySelectorAll(sel);
30
+ if (nodes.length > 0) {
31
+ return Array.from(nodes).map((n, i) => '## Message ' + (i + 1) + '\\n\\n' + (n.innerText || n.textContent).trim()).join('\\n\\n---\\n\\n');
32
+ }
33
+ }
34
+
35
+ const main = document.querySelector('main, [role="main"], [class*="chat-container"]');
36
+ if (main) return main.innerText || main.textContent;
37
+ return document.body.innerText;
38
+ })()
39
+ `);
40
+
41
+ fs.writeFileSync(outputPath, '# ChatWise Conversation Export\\n\\n' + md);
42
+
43
+ return [
44
+ {
45
+ Status: 'Success',
46
+ File: outputPath,
47
+ Messages: md.split('## Message').length - 1,
48
+ },
49
+ ];
50
+ },
51
+ });
@@ -0,0 +1,47 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const historyCommand = cli({
5
+ site: 'chatwise',
6
+ name: 'history',
7
+ description: 'List conversation history in ChatWise sidebar',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [],
12
+ columns: ['Index', 'Title'],
13
+ func: async (page: IPage) => {
14
+ const items = await page.evaluate(`
15
+ (function() {
16
+ const results = [];
17
+ const selectors = [
18
+ '[class*="sidebar"] [class*="item"]',
19
+ '[class*="conversation-list"] a',
20
+ '[class*="chat-list"] > *',
21
+ 'nav a',
22
+ 'aside a',
23
+ '[role="listbox"] [role="option"]',
24
+ ];
25
+
26
+ for (const sel of selectors) {
27
+ const nodes = document.querySelectorAll(sel);
28
+ if (nodes.length > 0) {
29
+ nodes.forEach((n, i) => {
30
+ const text = (n.textContent || '').trim().substring(0, 100);
31
+ if (text) results.push({ Index: i + 1, Title: text });
32
+ });
33
+ break;
34
+ }
35
+ }
36
+
37
+ return results;
38
+ })()
39
+ `);
40
+
41
+ if (items.length === 0) {
42
+ return [{ Index: 0, Title: 'No history found. Ensure the sidebar is visible.' }];
43
+ }
44
+
45
+ return items;
46
+ },
47
+ });
@@ -0,0 +1,87 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const modelCommand = cli({
5
+ site: 'chatwise',
6
+ name: 'model',
7
+ description: 'Get or switch the active AI model in ChatWise',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'model_name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
13
+ ],
14
+ columns: ['Status', 'Model'],
15
+ func: async (page: IPage, kwargs: any) => {
16
+ const desiredModel = kwargs.model_name as string | undefined;
17
+
18
+ if (!desiredModel) {
19
+ // Read current model
20
+ const currentModel = await page.evaluate(`
21
+ (function() {
22
+ // ChatWise is a multi-LLM client, it typically shows the model name in a dropdown or header
23
+ const selectors = [
24
+ '[class*="model"] span',
25
+ '[class*="Model"] span',
26
+ '[data-testid*="model"]',
27
+ 'button[class*="model"]',
28
+ '[aria-label*="Model"]',
29
+ '[aria-label*="model"]',
30
+ ];
31
+
32
+ for (const sel of selectors) {
33
+ const el = document.querySelector(sel);
34
+ if (el) {
35
+ const text = (el.textContent || el.getAttribute('title') || '').trim();
36
+ if (text) return text;
37
+ }
38
+ }
39
+
40
+ return 'Unknown or Not Found';
41
+ })()
42
+ `);
43
+
44
+ return [{ Status: 'Active', Model: currentModel }];
45
+ } else {
46
+ // Try to switch model
47
+ await page.evaluate(`
48
+ (function(target) {
49
+ const selectors = [
50
+ '[class*="model"]',
51
+ '[class*="Model"]',
52
+ 'button[class*="model"]',
53
+ ];
54
+
55
+ for (const sel of selectors) {
56
+ const el = document.querySelector(sel);
57
+ if (el) { el.click(); return; }
58
+ }
59
+ throw new Error('Could not find model selector');
60
+ })(${JSON.stringify(desiredModel)})
61
+ `);
62
+
63
+ await page.wait(0.5);
64
+
65
+ // Find and click the target model in the dropdown
66
+ const found = await page.evaluate(`
67
+ (function(target) {
68
+ const options = document.querySelectorAll('[role="option"], [role="menuitem"], [class*="dropdown-item"], li');
69
+ for (const opt of options) {
70
+ if ((opt.textContent || '').toLowerCase().includes(target.toLowerCase())) {
71
+ opt.click();
72
+ return true;
73
+ }
74
+ }
75
+ return false;
76
+ })(${JSON.stringify(desiredModel)})
77
+ `);
78
+
79
+ return [
80
+ {
81
+ Status: found ? 'Switched' : 'Dropdown opened but model not found',
82
+ Model: desiredModel,
83
+ },
84
+ ];
85
+ }
86
+ },
87
+ });
@@ -0,0 +1,21 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const newCommand = cli({
5
+ site: 'chatwise',
6
+ name: 'new',
7
+ description: 'Start a new conversation in ChatWise',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [],
12
+ columns: ['Status'],
13
+ func: async (page: IPage) => {
14
+ // ChatWise uses standard Electron shortcuts
15
+ const isMac = process.platform === 'darwin';
16
+ await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
17
+ await page.wait(1);
18
+
19
+ return [{ Status: 'Success' }];
20
+ },
21
+ });
@@ -0,0 +1,42 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const readCommand = cli({
5
+ site: 'chatwise',
6
+ name: 'read',
7
+ description: 'Read the current ChatWise conversation history',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [],
12
+ columns: ['Content'],
13
+ func: async (page: IPage) => {
14
+ const content = await page.evaluate(`
15
+ (function() {
16
+ // Try common chat message selectors
17
+ const selectors = [
18
+ '[data-message-id]',
19
+ '[class*="message"]',
20
+ '[class*="chat-item"]',
21
+ '[class*="bubble"]',
22
+ '[role="log"] > *',
23
+ ];
24
+
25
+ for (const sel of selectors) {
26
+ const nodes = document.querySelectorAll(sel);
27
+ if (nodes.length > 0) {
28
+ return Array.from(nodes).map(n => (n.innerText || n.textContent).trim()).filter(Boolean).join('\\n\\n---\\n\\n');
29
+ }
30
+ }
31
+
32
+ // Fallback: main content area
33
+ const main = document.querySelector('main, [role="main"], [class*="chat-container"], [class*="conversation"]');
34
+ if (main) return main.innerText || main.textContent;
35
+
36
+ return document.body.innerText;
37
+ })()
38
+ `);
39
+
40
+ return [{ Content: content }];
41
+ },
42
+ });
@@ -0,0 +1,33 @@
1
+ import * as fs from 'node:fs';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import type { IPage } from '../../types.js';
4
+
5
+ export const screenshotCommand = cli({
6
+ site: 'chatwise',
7
+ name: 'screenshot',
8
+ description: 'Capture a snapshot of the current ChatWise window (DOM + Accessibility tree)',
9
+ domain: 'localhost',
10
+ strategy: Strategy.UI,
11
+ browser: true,
12
+ args: [
13
+ { name: 'output', required: false, positional: true, help: 'Output file path (default: /tmp/chatwise-snapshot)' },
14
+ ],
15
+ columns: ['Status', 'File'],
16
+ func: async (page: IPage, kwargs: any) => {
17
+ const basePath = (kwargs.output as string) || '/tmp/chatwise-snapshot';
18
+
19
+ const snap = await page.snapshot({ compact: true });
20
+ const html = await page.evaluate('document.documentElement.outerHTML');
21
+
22
+ const htmlPath = basePath + '-dom.html';
23
+ const snapPath = basePath + '-a11y.txt';
24
+
25
+ fs.writeFileSync(htmlPath, html);
26
+ fs.writeFileSync(snapPath, typeof snap === 'string' ? snap : JSON.stringify(snap, null, 2));
27
+
28
+ return [
29
+ { Status: 'Success', File: htmlPath },
30
+ { Status: 'Success', File: snapPath },
31
+ ];
32
+ },
33
+ });
@@ -0,0 +1,50 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const sendCommand = cli({
5
+ site: 'chatwise',
6
+ name: 'send',
7
+ description: 'Send a message to the active ChatWise conversation',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
12
+ columns: ['Status', 'InjectedText'],
13
+ func: async (page: IPage, kwargs: any) => {
14
+ const text = kwargs.text as string;
15
+
16
+ await page.evaluate(`
17
+ (function(text) {
18
+ // ChatWise input can be textarea or contenteditable
19
+ let composer = document.querySelector('textarea');
20
+ if (!composer) {
21
+ const editables = Array.from(document.querySelectorAll('[contenteditable="true"]'));
22
+ composer = editables.length > 0 ? editables[editables.length - 1] : null;
23
+ }
24
+
25
+ if (!composer) throw new Error('Could not find ChatWise input element');
26
+
27
+ composer.focus();
28
+
29
+ if (composer.tagName === 'TEXTAREA') {
30
+ // For textarea, set value and dispatch input event
31
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
32
+ nativeInputValueSetter.call(composer, text);
33
+ composer.dispatchEvent(new Event('input', { bubbles: true }));
34
+ } else {
35
+ document.execCommand('insertText', false, text);
36
+ }
37
+ })(${JSON.stringify(text)})
38
+ `);
39
+
40
+ await page.wait(0.5);
41
+ await page.pressKey('Enter');
42
+
43
+ return [
44
+ {
45
+ Status: 'Success',
46
+ InjectedText: text,
47
+ },
48
+ ];
49
+ },
50
+ });
@@ -0,0 +1,25 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const statusCommand = cli({
5
+ site: 'chatwise',
6
+ name: 'status',
7
+ description: 'Check active CDP connection to ChatWise Desktop',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [],
12
+ columns: ['Status', 'Url', 'Title'],
13
+ func: async (page: IPage) => {
14
+ const url = await page.evaluate('window.location.href');
15
+ const title = await page.evaluate('document.title');
16
+
17
+ return [
18
+ {
19
+ Status: 'Connected',
20
+ Url: url,
21
+ Title: title,
22
+ },
23
+ ];
24
+ },
25
+ });
@@ -0,0 +1,77 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const askCommand = cli({
5
+ site: 'codex',
6
+ name: 'ask',
7
+ description: 'Send a prompt and wait for the AI response (send + wait + read)',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'text', required: true, positional: true, help: 'Prompt to send' },
13
+ { name: 'timeout', required: false, help: 'Max seconds to wait for response (default: 60)', default: '60' },
14
+ ],
15
+ columns: ['Role', 'Text'],
16
+ func: async (page: IPage, kwargs: any) => {
17
+ const text = kwargs.text as string;
18
+ const timeout = parseInt(kwargs.timeout as string, 10) || 60;
19
+
20
+ // Snapshot the current content length before sending
21
+ const beforeLen = await page.evaluate(`
22
+ (function() {
23
+ const turns = document.querySelectorAll('[data-content-search-turn-key]');
24
+ return turns.length;
25
+ })()
26
+ `);
27
+
28
+ // Inject and send
29
+ await page.evaluate(`
30
+ (function(text) {
31
+ const editables = Array.from(document.querySelectorAll('[contenteditable="true"]'));
32
+ const composer = editables.length > 0 ? editables[editables.length - 1] : document.querySelector('textarea');
33
+ if (!composer) throw new Error('Could not find Codex input');
34
+ composer.focus();
35
+ document.execCommand('insertText', false, text);
36
+ })(${JSON.stringify(text)})
37
+ `);
38
+ await page.wait(0.5);
39
+ await page.pressKey('Enter');
40
+
41
+ // Poll for new content
42
+ const pollInterval = 3;
43
+ const maxPolls = Math.ceil(timeout / pollInterval);
44
+ let response = '';
45
+
46
+ for (let i = 0; i < maxPolls; i++) {
47
+ await page.wait(pollInterval);
48
+
49
+ const result = await page.evaluate(`
50
+ (function(prevLen) {
51
+ const turns = document.querySelectorAll('[data-content-search-turn-key]');
52
+ if (turns.length <= prevLen) return null;
53
+ const lastTurn = turns[turns.length - 1];
54
+ const text = lastTurn.innerText || lastTurn.textContent;
55
+ return text ? text.trim() : null;
56
+ })(${beforeLen})
57
+ `);
58
+
59
+ if (result) {
60
+ response = result;
61
+ break;
62
+ }
63
+ }
64
+
65
+ if (!response) {
66
+ return [
67
+ { Role: 'User', Text: text },
68
+ { Role: 'System', Text: `No response within ${timeout}s. The agent may still be working.` },
69
+ ];
70
+ }
71
+
72
+ return [
73
+ { Role: 'User', Text: text },
74
+ { Role: 'Assistant', Text: response },
75
+ ];
76
+ },
77
+ });
@@ -0,0 +1,42 @@
1
+ import * as fs from 'node:fs';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import type { IPage } from '../../types.js';
4
+
5
+ export const exportCommand = cli({
6
+ site: 'codex',
7
+ name: 'export',
8
+ description: 'Export the current Codex conversation to a Markdown file',
9
+ domain: 'localhost',
10
+ strategy: Strategy.UI,
11
+ browser: true,
12
+ args: [
13
+ { name: 'output', required: false, positional: true, help: 'Output file (default: /tmp/codex-export.md)' },
14
+ ],
15
+ columns: ['Status', 'File', 'Messages'],
16
+ func: async (page: IPage, kwargs: any) => {
17
+ const outputPath = (kwargs.output as string) || '/tmp/codex-export.md';
18
+
19
+ const md = await page.evaluate(`
20
+ (function() {
21
+ const turns = document.querySelectorAll('[data-content-search-turn-key]');
22
+ if (turns.length > 0) {
23
+ return Array.from(turns).map((t, i) => '## Turn ' + (i + 1) + '\\n\\n' + (t.innerText || t.textContent).trim()).join('\\n\\n---\\n\\n');
24
+ }
25
+
26
+ const main = document.querySelector('main, [role="main"], [role="log"]');
27
+ if (main) return main.innerText || main.textContent;
28
+ return document.body.innerText;
29
+ })()
30
+ `);
31
+
32
+ fs.writeFileSync(outputPath, '# Codex Conversation Export\\n\\n' + md);
33
+
34
+ return [
35
+ {
36
+ Status: 'Success',
37
+ File: outputPath,
38
+ Messages: md.split('## Turn').length - 1,
39
+ },
40
+ ];
41
+ },
42
+ });
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
2
3
 
3
4
  export const extractDiffCommand = cli({
4
5
  site: 'codex',