@zenalexa/unicli 0.213.3 → 0.215.1

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 (210) hide show
  1. package/AGENTS.md +16 -14
  2. package/README.md +206 -61
  3. package/README.zh-CN.md +166 -69
  4. package/dist/adapters/_electron/desktop-shared.d.ts +12 -0
  5. package/dist/adapters/_electron/desktop-shared.d.ts.map +1 -0
  6. package/dist/adapters/_electron/desktop-shared.js +312 -0
  7. package/dist/adapters/_electron/desktop-shared.js.map +1 -0
  8. package/dist/adapters/electron-desktop/electron-desktop.d.ts +9 -0
  9. package/dist/adapters/electron-desktop/electron-desktop.d.ts.map +1 -0
  10. package/dist/adapters/electron-desktop/electron-desktop.js +56 -0
  11. package/dist/adapters/electron-desktop/electron-desktop.js.map +1 -0
  12. package/dist/agents/backends.d.ts +42 -0
  13. package/dist/agents/backends.d.ts.map +1 -0
  14. package/dist/agents/backends.js +598 -0
  15. package/dist/agents/backends.js.map +1 -0
  16. package/dist/agents/editor-backends.d.ts +3 -0
  17. package/dist/agents/editor-backends.d.ts.map +1 -0
  18. package/dist/agents/editor-backends.js +70 -0
  19. package/dist/agents/editor-backends.js.map +1 -0
  20. package/dist/browser/adapter-authoring.d.ts +20 -0
  21. package/dist/browser/adapter-authoring.d.ts.map +1 -0
  22. package/dist/browser/adapter-authoring.js +68 -0
  23. package/dist/browser/adapter-authoring.js.map +1 -0
  24. package/dist/browser/analyze.d.ts +54 -0
  25. package/dist/browser/analyze.d.ts.map +1 -0
  26. package/dist/browser/analyze.js +205 -0
  27. package/dist/browser/analyze.js.map +1 -0
  28. package/dist/browser/bridge.d.ts +1 -1
  29. package/dist/browser/bridge.d.ts.map +1 -1
  30. package/dist/browser/bridge.js +2 -0
  31. package/dist/browser/bridge.js.map +1 -1
  32. package/dist/browser/daemon-client.d.ts.map +1 -1
  33. package/dist/browser/daemon-client.js +12 -1
  34. package/dist/browser/daemon-client.js.map +1 -1
  35. package/dist/browser/network-cache.d.ts +58 -0
  36. package/dist/browser/network-cache.d.ts.map +1 -0
  37. package/dist/browser/network-cache.js +146 -0
  38. package/dist/browser/network-cache.js.map +1 -0
  39. package/dist/browser/page.d.ts +1 -1
  40. package/dist/browser/page.d.ts.map +1 -1
  41. package/dist/browser/page.js +2 -1
  42. package/dist/browser/page.js.map +1 -1
  43. package/dist/browser/protocol.d.ts +6 -2
  44. package/dist/browser/protocol.d.ts.map +1 -1
  45. package/dist/browser/site-memory.d.ts +57 -0
  46. package/dist/browser/site-memory.d.ts.map +1 -0
  47. package/dist/browser/site-memory.js +127 -0
  48. package/dist/browser/site-memory.js.map +1 -0
  49. package/dist/browser/verify-fixture.d.ts +30 -0
  50. package/dist/browser/verify-fixture.d.ts.map +1 -0
  51. package/dist/browser/verify-fixture.js +168 -0
  52. package/dist/browser/verify-fixture.js.map +1 -0
  53. package/dist/browser/workspace.d.ts +8 -0
  54. package/dist/browser/workspace.d.ts.map +1 -0
  55. package/dist/browser/workspace.js +15 -0
  56. package/dist/browser/workspace.js.map +1 -0
  57. package/dist/commands/acp.d.ts +5 -0
  58. package/dist/commands/acp.d.ts.map +1 -1
  59. package/dist/commands/acp.js +20 -17
  60. package/dist/commands/acp.js.map +1 -1
  61. package/dist/commands/agents-backends.d.ts +6 -0
  62. package/dist/commands/agents-backends.d.ts.map +1 -0
  63. package/dist/commands/agents-backends.js +49 -0
  64. package/dist/commands/agents-backends.js.map +1 -0
  65. package/dist/commands/agents.d.ts.map +1 -1
  66. package/dist/commands/agents.js +3 -1
  67. package/dist/commands/agents.js.map +1 -1
  68. package/dist/commands/browser-adapter-authoring.d.ts +3 -0
  69. package/dist/commands/browser-adapter-authoring.d.ts.map +1 -0
  70. package/dist/commands/browser-adapter-authoring.js +206 -0
  71. package/dist/commands/browser-adapter-authoring.js.map +1 -0
  72. package/dist/commands/browser-authoring-operator.d.ts +3 -0
  73. package/dist/commands/browser-authoring-operator.d.ts.map +1 -0
  74. package/dist/commands/browser-authoring-operator.js +110 -0
  75. package/dist/commands/browser-authoring-operator.js.map +1 -0
  76. package/dist/commands/browser-operator-runtime.d.ts +38 -0
  77. package/dist/commands/browser-operator-runtime.d.ts.map +1 -0
  78. package/dist/commands/browser-operator-runtime.js +281 -0
  79. package/dist/commands/browser-operator-runtime.js.map +1 -0
  80. package/dist/commands/browser-operator.d.ts +6 -0
  81. package/dist/commands/browser-operator.d.ts.map +1 -0
  82. package/dist/commands/browser-operator.js +374 -0
  83. package/dist/commands/browser-operator.js.map +1 -0
  84. package/dist/commands/browser.d.ts.map +1 -1
  85. package/dist/commands/browser.js +72 -1
  86. package/dist/commands/browser.js.map +1 -1
  87. package/dist/commands/daemon.d.ts.map +1 -1
  88. package/dist/commands/daemon.js +40 -4
  89. package/dist/commands/daemon.js.map +1 -1
  90. package/dist/commands/explore.d.ts.map +1 -1
  91. package/dist/commands/explore.js +4 -1
  92. package/dist/commands/explore.js.map +1 -1
  93. package/dist/commands/generate.d.ts.map +1 -1
  94. package/dist/commands/generate.js +4 -1
  95. package/dist/commands/generate.js.map +1 -1
  96. package/dist/commands/mcp.d.ts.map +1 -1
  97. package/dist/commands/mcp.js +14 -14
  98. package/dist/commands/mcp.js.map +1 -1
  99. package/dist/commands/migrate-schema.d.ts.map +1 -1
  100. package/dist/commands/migrate-schema.js +10 -0
  101. package/dist/commands/migrate-schema.js.map +1 -1
  102. package/dist/commands/operate.d.ts +4 -3
  103. package/dist/commands/operate.d.ts.map +1 -1
  104. package/dist/commands/operate.js +8 -483
  105. package/dist/commands/operate.js.map +1 -1
  106. package/dist/commands/record.d.ts.map +1 -1
  107. package/dist/commands/record.js +2 -1
  108. package/dist/commands/record.js.map +1 -1
  109. package/dist/commands/search.js +1 -1
  110. package/dist/commands/search.js.map +1 -1
  111. package/dist/commands/skills.js +1 -1
  112. package/dist/commands/skills.js.map +1 -1
  113. package/dist/commands/synthesize.d.ts.map +1 -1
  114. package/dist/commands/synthesize.js +33 -3
  115. package/dist/commands/synthesize.js.map +1 -1
  116. package/dist/discovery/aliases.d.ts.map +1 -1
  117. package/dist/discovery/aliases.js +76 -1
  118. package/dist/discovery/aliases.js.map +1 -1
  119. package/dist/discovery/loader.d.ts.map +1 -1
  120. package/dist/discovery/loader.js +3 -1
  121. package/dist/discovery/loader.js.map +1 -1
  122. package/dist/electron-apps.d.ts +22 -0
  123. package/dist/electron-apps.d.ts.map +1 -1
  124. package/dist/electron-apps.js +324 -0
  125. package/dist/electron-apps.js.map +1 -1
  126. package/dist/engine/download.d.ts.map +1 -1
  127. package/dist/engine/download.js +12 -5
  128. package/dist/engine/download.js.map +1 -1
  129. package/dist/engine/repair/engine.js +2 -2
  130. package/dist/engine/repair/engine.js.map +1 -1
  131. package/dist/engine/steps/desktop-ax.d.ts +10 -0
  132. package/dist/engine/steps/desktop-ax.d.ts.map +1 -1
  133. package/dist/engine/steps/desktop-ax.js +20 -0
  134. package/dist/engine/steps/desktop-ax.js.map +1 -1
  135. package/dist/engine/steps/exec.d.ts +1 -1
  136. package/dist/engine/steps/exec.d.ts.map +1 -1
  137. package/dist/engine/steps/exec.js +20 -1
  138. package/dist/engine/steps/exec.js.map +1 -1
  139. package/dist/engine/steps/write-temp.js +1 -1
  140. package/dist/engine/steps/write-temp.js.map +1 -1
  141. package/dist/engine/template.d.ts.map +1 -1
  142. package/dist/engine/template.js +10 -2
  143. package/dist/engine/template.js.map +1 -1
  144. package/dist/main.js +12 -3
  145. package/dist/main.js.map +1 -1
  146. package/dist/manifest-compact.txt +8 -7
  147. package/dist/manifest-search.json +1 -1
  148. package/dist/manifest.json +2161 -171
  149. package/dist/mcp/http-transport.d.ts.map +1 -1
  150. package/dist/mcp/http-transport.js +3 -6
  151. package/dist/mcp/http-transport.js.map +1 -1
  152. package/dist/mcp/streamable-http/handle-post.js +1 -1
  153. package/dist/mcp/tools.d.ts +2 -1
  154. package/dist/mcp/tools.d.ts.map +1 -1
  155. package/dist/mcp/tools.js +8 -4
  156. package/dist/mcp/tools.js.map +1 -1
  157. package/dist/output/envelope.d.ts +1 -5
  158. package/dist/output/envelope.d.ts.map +1 -1
  159. package/dist/output/envelope.js +1 -5
  160. package/dist/output/envelope.js.map +1 -1
  161. package/dist/output/formatter.d.ts +2 -2
  162. package/dist/output/formatter.js +3 -3
  163. package/dist/output/formatter.js.map +1 -1
  164. package/dist/transport/adapters/cua.d.ts +9 -10
  165. package/dist/transport/adapters/cua.d.ts.map +1 -1
  166. package/dist/transport/adapters/cua.js +22 -24
  167. package/dist/transport/adapters/cua.js.map +1 -1
  168. package/dist/transport/adapters/desktop-atspi.d.ts +1 -1
  169. package/dist/transport/adapters/desktop-atspi.js +2 -2
  170. package/dist/transport/adapters/desktop-atspi.js.map +1 -1
  171. package/dist/transport/adapters/desktop-ax-background-click-swift.d.ts +11 -0
  172. package/dist/transport/adapters/desktop-ax-background-click-swift.d.ts.map +1 -0
  173. package/dist/transport/adapters/desktop-ax-background-click-swift.js +136 -0
  174. package/dist/transport/adapters/desktop-ax-background-click-swift.js.map +1 -0
  175. package/dist/transport/adapters/desktop-ax-background-click.d.ts +4 -0
  176. package/dist/transport/adapters/desktop-ax-background-click.d.ts.map +1 -0
  177. package/dist/transport/adapters/desktop-ax-background-click.js +70 -0
  178. package/dist/transport/adapters/desktop-ax-background-click.js.map +1 -0
  179. package/dist/transport/adapters/desktop-ax-swift.d.ts +56 -0
  180. package/dist/transport/adapters/desktop-ax-swift.d.ts.map +1 -0
  181. package/dist/transport/adapters/desktop-ax-swift.js +458 -0
  182. package/dist/transport/adapters/desktop-ax-swift.js.map +1 -0
  183. package/dist/transport/adapters/desktop-ax.d.ts +14 -0
  184. package/dist/transport/adapters/desktop-ax.d.ts.map +1 -1
  185. package/dist/transport/adapters/desktop-ax.js +262 -32
  186. package/dist/transport/adapters/desktop-ax.js.map +1 -1
  187. package/dist/transport/adapters/desktop-uia.d.ts +1 -1
  188. package/dist/transport/adapters/desktop-uia.js +2 -2
  189. package/dist/transport/adapters/desktop-uia.js.map +1 -1
  190. package/dist/transport/adapters/http.js +1 -1
  191. package/dist/transport/adapters/http.js.map +1 -1
  192. package/dist/transport/capability.d.ts +1 -1
  193. package/dist/transport/capability.d.ts.map +1 -1
  194. package/dist/transport/capability.js +6 -1
  195. package/dist/transport/capability.js.map +1 -1
  196. package/package.json +6 -2
  197. package/src/adapters/_electron/desktop-shared.ts +383 -0
  198. package/src/adapters/docker/build.yaml +1 -1
  199. package/src/adapters/docker/images.yaml +1 -1
  200. package/src/adapters/docker/logs.yaml +1 -1
  201. package/src/adapters/docker/networks.yaml +1 -1
  202. package/src/adapters/docker/ps.yaml +1 -1
  203. package/src/adapters/docker/run.yaml +1 -1
  204. package/src/adapters/docker/volumes.yaml +1 -1
  205. package/src/adapters/electron-desktop/electron-desktop.ts +57 -0
  206. package/src/adapters/macos/caffeinate.yaml +2 -2
  207. package/src/adapters/macos/processes.yaml +3 -3
  208. package/src/adapters/macos/screen-recording.yaml +2 -2
  209. package/src/adapters/substack/trending.yaml +2 -1
  210. package/src/hub/external-clis.yaml +175 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenalexa/unicli",
3
- "version": "0.213.3",
3
+ "version": "0.215.1",
4
4
  "description": "The universal interface between AI agents and the world's software",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -101,6 +101,10 @@
101
101
  "types": "./dist/protocol/acp.d.ts",
102
102
  "import": "./dist/protocol/acp.js"
103
103
  },
104
+ "./agents/backends": {
105
+ "types": "./dist/agents/backends.d.ts",
106
+ "import": "./dist/agents/backends.js"
107
+ },
104
108
  "./protocol/skill": {
105
109
  "types": "./dist/protocol/skill.d.ts",
106
110
  "import": "./dist/protocol/skill.js"
@@ -118,7 +122,7 @@
118
122
  "dev": "tsx src/main.ts",
119
123
  "build": "tsc && tsx scripts/build-manifest.js && tsx scripts/count-stats.ts && tsx scripts/build-readme.ts && tsx scripts/build-agents.ts && npx prettier --write AGENTS.md",
120
124
  "postbuild": "node -e \"require('fs').chmodSync('dist/main.js', 0o755)\"",
121
- "stats": "tsx scripts/count-stats.ts && tsx scripts/build-readme.ts",
125
+ "stats": "tsx scripts/build-manifest.js && tsx scripts/count-stats.ts && tsx scripts/build-readme.ts && tsx scripts/build-agents.ts",
122
126
  "stats:check": "tsx scripts/count-consistency.ts",
123
127
  "build:agents": "tsx scripts/build-agents.ts",
124
128
  "test": "vitest run --project unit",
@@ -0,0 +1,383 @@
1
+ import { cli, Strategy } from "../../registry.js";
2
+ import {
3
+ getElectronApp,
4
+ resolveAppControlPolicy,
5
+ } from "../../electron-apps.js";
6
+ import { launchElectronApp } from "../../browser/launcher.js";
7
+ import { connectElectronApp } from "./shared.js";
8
+ import type { BrowserPage } from "../../browser/page.js";
9
+
10
+ export interface ElectronMediaProfile {
11
+ likedText: string;
12
+ playAllText: string;
13
+ }
14
+
15
+ export interface ElectronDesktopCommandProfile {
16
+ displayName?: string;
17
+ media?: ElectronMediaProfile;
18
+ }
19
+
20
+ export const ELECTRON_DESKTOP_BASE_COMMANDS = [
21
+ "open-app",
22
+ "status-app",
23
+ "dump",
24
+ "snapshot-app",
25
+ "click-text",
26
+ "type-text",
27
+ "press",
28
+ ] as const;
29
+
30
+ export const ELECTRON_DESKTOP_MEDIA_COMMANDS = [
31
+ "play-liked",
32
+ "play",
33
+ "pause",
34
+ "toggle",
35
+ "next",
36
+ "prev",
37
+ ] as const;
38
+
39
+ export function registerElectronDesktopCommands(
40
+ site: string,
41
+ profile: ElectronDesktopCommandProfile = {},
42
+ ): void {
43
+ const app = getElectronApp(site);
44
+ const displayName = profile.displayName ?? app?.displayName ?? site;
45
+
46
+ cli({
47
+ site,
48
+ name: "open-app",
49
+ description: `Open ${displayName} desktop Electron app with CDP enabled for AI control. 打开${displayName}桌面版并启用 CDP 控制。`,
50
+ strategy: Strategy.PUBLIC,
51
+ func: async () => {
52
+ const endpoint = await launchElectronApp(site);
53
+ return [
54
+ {
55
+ app: displayName,
56
+ site,
57
+ port: endpoint.port,
58
+ wsUrl: endpoint.wsUrl,
59
+ policy: resolveAppControlPolicy(site),
60
+ },
61
+ ];
62
+ },
63
+ });
64
+
65
+ cli({
66
+ site,
67
+ name: "status-app",
68
+ description: `Inspect current ${displayName} desktop app page, title, URL, visible controls, and text summary. 查看${displayName}桌面版当前状态和可见内容。`,
69
+ strategy: Strategy.PUBLIC,
70
+ func: async () => [await readDesktopStatus(site, displayName)],
71
+ });
72
+
73
+ cli({
74
+ site,
75
+ name: "dump",
76
+ description: `Dump visible text from the ${displayName} desktop Electron app via CDP DOM. 读取${displayName}桌面版可见文本。`,
77
+ strategy: Strategy.PUBLIC,
78
+ args: [
79
+ {
80
+ name: "limit",
81
+ required: false,
82
+ positional: false,
83
+ description: "Maximum text characters to return",
84
+ },
85
+ ],
86
+ func: async (_page: unknown, kwargs: Record<string, unknown>) => {
87
+ const p = await connectElectronApp(site);
88
+ const limit = readInt(kwargs.limit, 2000);
89
+ return [await dumpVisibleText(p, displayName, limit)];
90
+ },
91
+ });
92
+
93
+ cli({
94
+ site,
95
+ name: "snapshot-app",
96
+ description: `List visible clickable text, buttons, inputs, and regions in ${displayName}. 枚举${displayName}桌面版可交互内容。`,
97
+ strategy: Strategy.PUBLIC,
98
+ func: async () => {
99
+ const p = await connectElectronApp(site);
100
+ return (await p.evaluate(visibleInteractivesJs())) as unknown[];
101
+ },
102
+ });
103
+
104
+ cli({
105
+ site,
106
+ name: "click-text",
107
+ description: `Click visible text, aria-label, title, or button content in ${displayName}. 按文本点击${displayName}桌面版控件。`,
108
+ strategy: Strategy.PUBLIC,
109
+ args: [
110
+ {
111
+ name: "text",
112
+ required: true,
113
+ positional: true,
114
+ description: "Visible text, aria-label, or title to click",
115
+ },
116
+ ],
117
+ func: async (_page: unknown, kwargs: Record<string, unknown>) => {
118
+ const p = await connectElectronApp(site);
119
+ return [await clickVisibleText(p, String(kwargs.text), displayName)];
120
+ },
121
+ });
122
+
123
+ cli({
124
+ site,
125
+ name: "type-text",
126
+ description: `Type text into the focused field or a text-matched target in ${displayName}. 向${displayName}桌面版输入文本。`,
127
+ strategy: Strategy.PUBLIC,
128
+ args: [
129
+ {
130
+ name: "text",
131
+ required: true,
132
+ positional: true,
133
+ description: "Text to type",
134
+ },
135
+ {
136
+ name: "target",
137
+ required: false,
138
+ positional: false,
139
+ description: "Optional visible text to click before typing",
140
+ },
141
+ ],
142
+ func: async (_page: unknown, kwargs: Record<string, unknown>) => {
143
+ const p = await connectElectronApp(site);
144
+ if (kwargs.target !== undefined) {
145
+ await clickVisibleText(p, String(kwargs.target), displayName);
146
+ }
147
+ await p.insertText(String(kwargs.text));
148
+ return [{ app: displayName, typed: true }];
149
+ },
150
+ });
151
+
152
+ cli({
153
+ site,
154
+ name: "press",
155
+ description: `Press a key in ${displayName}, with optional comma-separated modifiers. 在${displayName}桌面版发送按键。`,
156
+ strategy: Strategy.PUBLIC,
157
+ args: [
158
+ {
159
+ name: "key",
160
+ required: true,
161
+ positional: true,
162
+ description: "Key name",
163
+ },
164
+ {
165
+ name: "modifiers",
166
+ required: false,
167
+ positional: false,
168
+ description: "Comma-separated modifiers such as meta,shift",
169
+ },
170
+ ],
171
+ func: async (_page: unknown, kwargs: Record<string, unknown>) => {
172
+ const p = await connectElectronApp(site);
173
+ const modifiers =
174
+ typeof kwargs.modifiers === "string"
175
+ ? kwargs.modifiers
176
+ .split(",")
177
+ .map((s) => s.trim())
178
+ .filter(Boolean)
179
+ : undefined;
180
+ await p.press(String(kwargs.key), modifiers);
181
+ return [{ app: displayName, key: String(kwargs.key), modifiers }];
182
+ },
183
+ });
184
+
185
+ if (profile.media) {
186
+ registerMediaCommands(site, displayName, profile.media);
187
+ }
188
+ }
189
+
190
+ function registerMediaCommands(
191
+ site: string,
192
+ displayName: string,
193
+ media: ElectronMediaProfile,
194
+ ): void {
195
+ cli({
196
+ site,
197
+ name: "play-liked",
198
+ description: `Open ${displayName} liked songs and play the liked playlist. 打开${displayName}我喜欢的音乐/喜欢的歌曲并播放。`,
199
+ strategy: Strategy.PUBLIC,
200
+ func: async () => {
201
+ const p = await connectElectronApp(site);
202
+ const liked = await clickVisibleText(p, media.likedText, displayName);
203
+ await p.wait(1);
204
+ const play = await clickVisibleText(p, media.playAllText, displayName);
205
+ await p.wait(1.5);
206
+ return [{ app: displayName, liked, play, status: await mediaStatus(p) }];
207
+ },
208
+ });
209
+
210
+ for (const action of ["play", "pause", "toggle", "next", "prev"] as const) {
211
+ cli({
212
+ site,
213
+ name: action,
214
+ description: `${action} playback in the ${displayName} desktop app. 控制${displayName}桌面版播放:${action}。`,
215
+ strategy: Strategy.PUBLIC,
216
+ func: async () => {
217
+ const p = await connectElectronApp(site);
218
+ const clicked = await clickMediaControl(p, action, displayName);
219
+ await p.wait(0.8);
220
+ return [
221
+ { app: displayName, action, clicked, status: await mediaStatus(p) },
222
+ ];
223
+ },
224
+ });
225
+ }
226
+ }
227
+
228
+ async function readDesktopStatus(
229
+ site: string,
230
+ displayName: string,
231
+ ): Promise<Record<string, unknown>> {
232
+ const endpoint = await launchElectronApp(site);
233
+ const p = await connectElectronApp(site);
234
+ return {
235
+ app: displayName,
236
+ site,
237
+ port: endpoint.port,
238
+ title: await p.title(),
239
+ url: await p.url(),
240
+ policy: resolveAppControlPolicy(site),
241
+ content: await dumpVisibleText(p, displayName, 700),
242
+ controls: await p.evaluate(visibleInteractivesJs(20)),
243
+ };
244
+ }
245
+
246
+ async function dumpVisibleText(
247
+ p: BrowserPage,
248
+ app: string,
249
+ limit: number,
250
+ ): Promise<Record<string, unknown>> {
251
+ const text = (await p.evaluate(
252
+ `document.body.innerText.slice(0, ${Math.max(1, limit)})`,
253
+ )) as string;
254
+ return { app, text };
255
+ }
256
+
257
+ async function clickVisibleText(
258
+ p: BrowserPage,
259
+ text: string,
260
+ app: string,
261
+ ): Promise<Record<string, unknown>> {
262
+ const result = (await p.evaluate(clickTextJs(text))) as {
263
+ found: boolean;
264
+ x?: number;
265
+ y?: number;
266
+ text?: string;
267
+ };
268
+ if (!result.found || result.x === undefined || result.y === undefined) {
269
+ return { app, clicked: false, text, reason: "text_not_found" };
270
+ }
271
+ await p.nativeClick(result.x, result.y);
272
+ return { app, clicked: true, match: result.text, x: result.x, y: result.y };
273
+ }
274
+
275
+ async function clickMediaControl(
276
+ p: BrowserPage,
277
+ action: "play" | "pause" | "toggle" | "next" | "prev",
278
+ app: string,
279
+ ): Promise<Record<string, unknown>> {
280
+ const target = (await p.evaluate(mediaControlJs(action))) as {
281
+ found: boolean;
282
+ x?: number;
283
+ y?: number;
284
+ label?: string;
285
+ };
286
+ if (!target.found || target.x === undefined || target.y === undefined) {
287
+ return { app, clicked: false, action, reason: "control_not_found" };
288
+ }
289
+ await p.nativeClick(target.x, target.y);
290
+ return { app, clicked: true, action, label: target.label };
291
+ }
292
+
293
+ async function mediaStatus(p: BrowserPage): Promise<unknown> {
294
+ return p.evaluate(`
295
+ (() => {
296
+ const pause = Array.from(document.querySelectorAll('button[title="暂停"]'))
297
+ .find(el => el.getBoundingClientRect().y < innerHeight - 90);
298
+ const row = pause?.closest('.tr');
299
+ return {
300
+ playing: !!pause,
301
+ currentRow: (row?.innerText || '').trim().replace(/\\s+/g, ' ').slice(0, 200),
302
+ bodyTail: document.body.innerText.slice(-300)
303
+ };
304
+ })()
305
+ `);
306
+ }
307
+
308
+ function clickTextJs(text: string): string {
309
+ return `
310
+ (() => {
311
+ const needle = ${JSON.stringify(text)}.trim().toLowerCase();
312
+ const nodes = Array.from(document.querySelectorAll('button,a,[role],div,span,input,textarea'));
313
+ for (const el of nodes) {
314
+ const raw = (el.innerText || el.textContent || el.getAttribute('aria-label') || el.title || el.placeholder || '').trim();
315
+ if (!raw) continue;
316
+ const hay = raw.toLowerCase();
317
+ if (hay !== needle && !hay.includes(needle)) continue;
318
+ const target = el.closest('button,a,[role="button"],.ItemContainer_ijv59hq,.tr') || el;
319
+ const r = target.getBoundingClientRect();
320
+ if (r.width < 1 || r.height < 1 || r.x < -5 || r.y < -5) continue;
321
+ return { found: true, text: raw.slice(0, 160), x: r.x + r.width / 2, y: r.y + r.height / 2 };
322
+ }
323
+ return { found: false };
324
+ })()
325
+ `;
326
+ }
327
+
328
+ function mediaControlJs(action: string): string {
329
+ return `
330
+ (() => {
331
+ const bottom = Array.from(document.querySelectorAll('button')).map((el, index) => {
332
+ const r = el.getBoundingClientRect();
333
+ const label = [el.title, el.getAttribute('aria-label'), el.innerText, el.textContent].filter(Boolean).join(' ').trim();
334
+ return { el, index, label, cls: String(el.className || ''), x: r.x, y: r.y, w: r.width, h: r.height };
335
+ }).filter(b => b.w > 0 && b.h > 0 && b.y > innerHeight - 90).sort((a, b) => a.x - b.x);
336
+ const pick = (b) => ({ found: true, label: b.label || b.cls, x: b.x + b.w / 2, y: b.y + b.h / 2 });
337
+ const rx = {
338
+ next: /下一首|下一个|next/i,
339
+ prev: /上一首|上一个|previous|prev/i,
340
+ play: /播放|play/i,
341
+ pause: /暂停|pause/i
342
+ }[${JSON.stringify(action)}];
343
+ if (rx) {
344
+ const labeled = bottom.find(b => rx.test(b.label));
345
+ if (labeled) return pick(labeled);
346
+ }
347
+ const center = bottom.find(b => b.cls.includes('play-pause-btn')) ||
348
+ bottom.slice().sort((a, b) => Math.abs((a.x + a.w / 2) - innerWidth / 2) - Math.abs((b.x + b.w / 2) - innerWidth / 2))[0];
349
+ if (!center) return { found: false };
350
+ if (${JSON.stringify(action)} === 'toggle' || ${JSON.stringify(action)} === 'play' || ${JSON.stringify(action)} === 'pause') return pick(center);
351
+ const idx = bottom.indexOf(center);
352
+ const neighbor = ${JSON.stringify(action)} === 'next' ? bottom[idx + 1] : bottom[idx - 1];
353
+ return neighbor ? pick(neighbor) : { found: false };
354
+ })()
355
+ `;
356
+ }
357
+
358
+ function visibleInteractivesJs(limit = 80): string {
359
+ return `
360
+ (() => {
361
+ const out = [];
362
+ const nodes = document.querySelectorAll('button,a,input,textarea,[role="button"],[role="textbox"],[contenteditable="true"]');
363
+ for (const el of nodes) {
364
+ const r = el.getBoundingClientRect();
365
+ if (r.width < 1 || r.height < 1 || r.y < -5 || r.y > innerHeight + 5) continue;
366
+ const text = (el.innerText || el.textContent || el.getAttribute('aria-label') || el.title || el.placeholder || '').trim().replace(/\\s+/g, ' ');
367
+ out.push({ tag: el.tagName, role: el.getAttribute('role'), text: text.slice(0, 120), title: el.title, rect: { x: r.x, y: r.y, w: r.width, h: r.height } });
368
+ if (out.length >= ${limit}) break;
369
+ }
370
+ return out;
371
+ })()
372
+ `;
373
+ }
374
+
375
+ function readInt(value: unknown, fallback: number): number {
376
+ const n =
377
+ typeof value === "number"
378
+ ? value
379
+ : typeof value === "string"
380
+ ? Number.parseInt(value, 10)
381
+ : Number.NaN;
382
+ return Number.isFinite(n) && n > 0 ? Math.trunc(n) : fallback;
383
+ }
@@ -4,7 +4,7 @@ description: Build a Docker image from a Dockerfile
4
4
  type: bridge
5
5
  strategy: public
6
6
  binary: docker
7
- detect: which docker
7
+ detect: "which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1"
8
8
 
9
9
  pipeline:
10
10
  - exec:
@@ -4,7 +4,7 @@ description: List local Docker images
4
4
  type: bridge
5
5
  strategy: public
6
6
  binary: docker
7
- detect: which docker
7
+ detect: "which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1"
8
8
 
9
9
  pipeline:
10
10
  - exec:
@@ -4,7 +4,7 @@ description: Fetch logs from a Docker container
4
4
  type: bridge
5
5
  strategy: public
6
6
  binary: docker
7
- detect: which docker
7
+ detect: "which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1"
8
8
 
9
9
  pipeline:
10
10
  - exec:
@@ -3,7 +3,7 @@ name: networks
3
3
  description: List Docker networks
4
4
  type: desktop
5
5
  binary: docker
6
- detect: which docker
6
+ detect: "which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1"
7
7
 
8
8
  pipeline:
9
9
  - exec:
@@ -4,7 +4,7 @@ description: List running Docker containers
4
4
  type: bridge
5
5
  strategy: public
6
6
  binary: docker
7
- detect: which docker
7
+ detect: "which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1"
8
8
 
9
9
  pipeline:
10
10
  - exec:
@@ -4,7 +4,7 @@ description: Run a command in a Docker container
4
4
  type: bridge
5
5
  strategy: public
6
6
  binary: docker
7
- detect: which docker
7
+ detect: "which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1"
8
8
 
9
9
  pipeline:
10
10
  - exec:
@@ -3,7 +3,7 @@ name: volumes
3
3
  description: List Docker volumes
4
4
  type: desktop
5
5
  binary: docker
6
- detect: which docker
6
+ detect: "which docker >/dev/null 2>&1 && docker info >/dev/null 2>&1"
7
7
 
8
8
  pipeline:
9
9
  - exec:
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Shared Electron desktop command pack.
3
+ *
4
+ * This file intentionally registers commands on many app-specific sites
5
+ * instead of exposing one generic "electron" site. Agents discover
6
+ * `netease-music play-liked`, `slack open-app`, `figma click-text`, etc.
7
+ */
8
+
9
+ import { registerElectronDesktopCommands } from "../_electron/desktop-shared.js";
10
+
11
+ registerElectronDesktopCommands("cursor", { displayName: "Cursor" });
12
+ registerElectronDesktopCommands("codex", { displayName: "Codex" });
13
+ registerElectronDesktopCommands("chatgpt", { displayName: "ChatGPT" });
14
+ registerElectronDesktopCommands("notion", { displayName: "Notion" });
15
+ registerElectronDesktopCommands("discord-app", { displayName: "Discord" });
16
+ registerElectronDesktopCommands("chatwise", { displayName: "ChatWise" });
17
+ registerElectronDesktopCommands("doubao-app", { displayName: "Doubao" });
18
+ registerElectronDesktopCommands("antigravity", { displayName: "Antigravity" });
19
+ registerElectronDesktopCommands("netease-music", {
20
+ displayName: "NetEase Cloud Music",
21
+ media: { likedText: "我喜欢的音乐", playAllText: "播放全部" },
22
+ });
23
+ registerElectronDesktopCommands("vscode", {
24
+ displayName: "Visual Studio Code",
25
+ });
26
+ registerElectronDesktopCommands("slack", { displayName: "Slack" });
27
+ registerElectronDesktopCommands("figma", { displayName: "Figma" });
28
+ registerElectronDesktopCommands("obsidian", { displayName: "Obsidian" });
29
+ registerElectronDesktopCommands("logseq", { displayName: "Logseq" });
30
+ registerElectronDesktopCommands("typora", { displayName: "Typora" });
31
+ registerElectronDesktopCommands("postman", { displayName: "Postman" });
32
+ registerElectronDesktopCommands("insomnia", { displayName: "Insomnia" });
33
+ registerElectronDesktopCommands("bitwarden", { displayName: "Bitwarden" });
34
+ registerElectronDesktopCommands("signal", { displayName: "Signal" });
35
+ registerElectronDesktopCommands("whatsapp", { displayName: "WhatsApp" });
36
+ registerElectronDesktopCommands("teams", { displayName: "Microsoft Teams" });
37
+ registerElectronDesktopCommands("linear", { displayName: "Linear" });
38
+ registerElectronDesktopCommands("todoist", { displayName: "Todoist" });
39
+ registerElectronDesktopCommands("github-desktop", {
40
+ displayName: "GitHub Desktop",
41
+ });
42
+ registerElectronDesktopCommands("gitkraken", { displayName: "GitKraken" });
43
+ registerElectronDesktopCommands("docker-desktop", {
44
+ displayName: "Docker Desktop",
45
+ });
46
+ registerElectronDesktopCommands("lm-studio", { displayName: "LM Studio" });
47
+ registerElectronDesktopCommands("claude", { displayName: "Claude" });
48
+ registerElectronDesktopCommands("perplexity", { displayName: "Perplexity" });
49
+ registerElectronDesktopCommands("spotify", {
50
+ displayName: "Spotify",
51
+ media: { likedText: "Liked Songs", playAllText: "Play" },
52
+ });
53
+ registerElectronDesktopCommands("dingtalk", { displayName: "DingTalk" });
54
+ registerElectronDesktopCommands("lark", { displayName: "Lark" });
55
+ registerElectronDesktopCommands("wechat-work", { displayName: "WeCom" });
56
+ registerElectronDesktopCommands("zoom-app", { displayName: "Zoom" });
57
+ registerElectronDesktopCommands("evernote-app", { displayName: "Evernote" });
@@ -18,9 +18,9 @@ pipeline:
18
18
  args:
19
19
  - "-d"
20
20
  - "-t"
21
- - "${{ args.duration }}"
21
+ - "${{ args.duration | default(3600) }}"
22
22
  parse: text
23
- timeout: "${{ (args.duration + 10) * 1000 }}"
23
+ timeout: "${{ ((args.duration || 3600) + 10) * 1000 }}"
24
24
 
25
25
  columns: []
26
26
 
@@ -3,12 +3,12 @@ name: processes
3
3
  description: List top processes by CPU usage
4
4
  type: desktop
5
5
  binary: ps
6
- detect: which ps
6
+ detect: "test $(uname) = Darwin"
7
7
 
8
8
  pipeline:
9
9
  - exec:
10
- command: ps
11
- args: ["aux", "--sort=-%cpu"]
10
+ command: sh
11
+ args: ["-c", "ps -axo pid,pcpu,pmem,comm | sort -k2 -nr | head -20"]
12
12
  parse: text
13
13
 
14
14
  columns: [result]
@@ -26,9 +26,9 @@ pipeline:
26
26
  - "${{ args.duration | default(10) }}"
27
27
  - "${{ args.file | default('/tmp/recording.mov') }}"
28
28
  parse: text
29
- timeout: "${{ (args.duration + 5) * 1000 }}"
29
+ timeout: "${{ ((args.duration || 10) + 5) * 1000 }}"
30
30
 
31
- - evaluate: "JSON.stringify([{ file: ${{ args.file | json }}, duration: ${{ args.duration }}, status: 'recorded' }])"
31
+ - evaluate: "JSON.stringify([{ file: ${{ (args.file || '/tmp/recording.mov') | json }}, duration: ${{ args.duration || 10 }}, status: 'recorded' }])"
32
32
 
33
33
  columns: [file, duration, status]
34
34
 
@@ -32,5 +32,6 @@ capabilities: ["http.fetch"]
32
32
  minimum_capability: http.fetch
33
33
  trust: public
34
34
  confidentiality: public
35
- quarantine: false
35
+ quarantine: true
36
+ quarantineReason: "HTTP 404 — upstream `/api/v1/trending` endpoint retired (2026-04-24), needs new Discover scrape/API"
36
37
  schema_version: v2