arena-bridge 0.6.3 → 0.6.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/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to this project are documented here.
4
4
  The format follows [Keep a Changelog](https://keepachangelog.com/) and the project uses
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## 0.6.4
8
+
9
+ ### Fixed
10
+ - The accessibility element map is read from Peekaboo's real payload (`data.ui_elements`), so
11
+ `mac_see` and `mac_element` now return the controls of a window instead of an empty list.
12
+ Elements carry their label, description, identifier and value-settable flag.
13
+ - Commands follow the Peekaboo v4 grammar: `set-value "<value>" --on <id>`, `menu click --item|--path`
14
+ with an explicit app, `space switch --to <n> --foreground`, and the tree-only read
15
+ `see --tree --no-screenshot --json` (no screenshot artifact for pure lookups).
16
+
7
17
  ## 0.6.3
8
18
 
9
19
  ### Added
package/README.md CHANGED
@@ -150,7 +150,7 @@ cookies и localStorage нет.
150
150
  {"tool": "mac_set", "args": {"on": "B2", "value": "hello"}} // вписать значение напрямую
151
151
  {"tool": "mac_type", "args": {"text": "hello", "submit": true}}
152
152
  {"tool": "mac_key", "args": {"key": "cmd+s"}}
153
- {"tool": "mac_menu_click", "args": {"item": "Save", "app": "Safari"}} // пункт меню по названию
153
+ {"tool": "mac_menu_click", "args": {"app": "Safari", "item": "Save"}} // пункт меню по названию
154
154
  {"tool": "mac_wait", "args": {"name": "Saved", "timeout": 20}} // дождаться результата
155
155
  {"tool": "mac_apps", "args": {}} // что запущено
156
156
  {"tool": "mac_permissions", "args": {}} // чего не хватает в macOS
@@ -172,6 +172,7 @@ macOS отдаёт приложениям два разных слоя: карт
172
172
  | как вписать текст | свайп, фокус, клавиатура | `mac_set {"on":"<id>","value":"..."}` — без фокуса и без клавиш |
173
173
  | как открыть «Файл → Экспорт» | перемещать курсор по меню | `mac_menu_click {"item":"Экспорт"}` — по названию |
174
174
  | как убедиться, что получилось | смотреть скриншот | `mac_wait {"name":"Готово"}` — ждать появления |
175
+ | как заполнить поле | свайп + клавиатура + фокус | `mac_set {"on":"<id>","value":"..."}` — значение пишется в элемент |
175
176
  | чужой ввод и фокус | мешает вам | действия уходят в фоновое окно |
176
177
 
177
178
  Практический вывод: с выданным Accessibility агент работает заметно точнее и тише. Разрешение
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arena-bridge",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "Give a cloud AI agent (Arena.ai Agent Mode, Claude, Cursor, ...) controlled access to your own machine over HTTP/MCP: files, shell, Chrome and the macOS desktop, with an approval gate, trust windows and an audit log.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/direct.mjs CHANGED
@@ -29,7 +29,7 @@ export function createDirectServer(cfg, engine) {
29
29
  ok: true,
30
30
  mode: 'direct',
31
31
  device: cfg.device,
32
- version: '0.6.3',
32
+ version: '0.6.4',
33
33
  uptime_sec: Math.round(process.uptime()),
34
34
  });
35
35
  }
package/src/hub.mjs CHANGED
@@ -90,7 +90,7 @@ export function createHubServer(cfg) {
90
90
  return sendJson(res, 200, {
91
91
  ok: true,
92
92
  mode: 'hub',
93
- version: '0.6.3',
93
+ version: '0.6.4',
94
94
  devices: [...devices.keys()],
95
95
  });
96
96
  }
package/src/mac.mjs CHANGED
@@ -100,21 +100,31 @@ function parseJson(text) {
100
100
  }
101
101
 
102
102
  function collectElements(node, out = [], depth = 0) {
103
- if (!node || depth > 6) return out;
103
+ if (!node || depth > 8) return out;
104
104
  if (Array.isArray(node)) {
105
105
  for (const n of node) collectElements(n, out, depth + 1);
106
106
  return out;
107
107
  }
108
108
  if (typeof node !== 'object') return out;
109
- if (node.elements && Array.isArray(node.elements)) {
110
- for (const e of node.elements) {
109
+ for (const key of ['ui_elements', 'elements']) {
110
+ const list = node[key];
111
+ if (!Array.isArray(list) || !list.length) continue;
112
+ for (const e of list) {
113
+ if (!e || typeof e !== 'object') continue;
114
+ const value = e.value;
111
115
  out.push({
112
- id: e.id ?? e.elementId ?? e.element_id ?? null,
113
- role: e.role ?? e.elementRole ?? e.type ?? null,
116
+ id: e.id ?? e.element_id ?? e.elementId ?? null,
117
+ role: e.role ?? e.ax_role ?? e.elementRole ?? e.type ?? null,
114
118
  title: e.title ?? e.label ?? e.name ?? e.placeholder ?? null,
115
- value: typeof e.value === 'string' ? e.value.slice(0, 200) : null,
116
- bounds: e.frame ?? e.bounds ?? e.rect ?? null,
117
- enabled: e.enabled ?? null,
119
+ label: e.label ?? null,
120
+ description: e.description ?? null,
121
+ identifier: e.identifier ?? null,
122
+ value: typeof value === 'string' ? value.slice(0, 200) : (value ?? null),
123
+ bounds: e.bounds ?? e.frame ?? e.rect ?? null,
124
+ enabled: e.is_enabled ?? e.enabled ?? null,
125
+ actionable: e.is_actionable ?? null,
126
+ settable: e.is_value_settable ?? null,
127
+ shortcut: e.keyboard_shortcut ?? null,
118
128
  });
119
129
  }
120
130
  return out;
@@ -178,10 +188,16 @@ export function macTools(cfg = {}) {
178
188
  const seeVariants = (args, outPath) => {
179
189
  const mode = args.mode === 'window' ? 'window' : 'screen';
180
190
  const base = ['see', '--mode', mode, ...targetFlags(args)];
191
+ if (!outPath) {
192
+ return [
193
+ ['see', '--tree', '--no-screenshot', '--json', ...targetFlags(args)],
194
+ ['see', '--tree', '--json', ...targetFlags(args)],
195
+ ['see', '--json', ...targetFlags(args)],
196
+ ];
197
+ }
181
198
  return [
182
- [...base, '--json', '--path', outPath, '--snapshot'],
183
- [...base, '--json', '--path', outPath, '--tree'],
184
199
  [...base, '--json', '--path', outPath],
200
+ [...base, '--path', outPath, '--json'],
185
201
  ['image', '--mode', mode, ...targetFlags(args), '--json', '--path', outPath],
186
202
  ];
187
203
  };
@@ -202,7 +218,10 @@ export function macTools(cfg = {}) {
202
218
 
203
219
  const matchElement = (e, args) => {
204
220
  const q = (v) => String(v ?? '').toLowerCase();
205
- if (args.name && !q(e.title).includes(q(args.name)) && !q(e.id).includes(q(args.name)) && !q(e.value).includes(q(args.name))) return false;
221
+ const text = [e.title, e.label, e.description, e.identifier, e.value, e.id]
222
+ .map(q)
223
+ .join('\n');
224
+ if (args.name && !text.includes(q(args.name))) return false;
206
225
  if (args.role && !q(e.role).includes(q(args.role))) return false;
207
226
  if (args.value && !q(e.value).includes(q(args.value))) return false;
208
227
  return true;
@@ -210,11 +229,8 @@ export function macTools(cfg = {}) {
210
229
 
211
230
  const searchPath = () => path.join(os.tmpdir(), 'arena-bridge-see', 'see.png');
212
231
 
213
- const searchSee = async (args) => {
214
- const out = searchPath();
215
- ensureDir(path.dirname(out));
216
- return collectSee({ app: args.app, window_title: args.window_title, mode: args.mode || 'screen' }, out);
217
- };
232
+ const searchSee = (args) =>
233
+ collectSee({ app: args.app, window_title: args.window_title, mode: args.mode || 'screen' }, null);
218
234
 
219
235
  return [
220
236
  {
@@ -607,9 +623,10 @@ export function macTools(cfg = {}) {
607
623
  const res = await runVariants(
608
624
  p.bin,
609
625
  [
626
+ ['set-value', String(args.value), '--on', String(args.on), ...flags],
627
+ ['set-value', String(args.value), '--on', String(args.on)],
610
628
  ['set-value', '--on', String(args.on), String(args.value), ...flags],
611
629
  ['set-value', '--on', String(args.on), '--value', String(args.value), ...flags],
612
- ['set-value', String(args.on), String(args.value), ...flags],
613
630
  ],
614
631
  { timeout: 30 },
615
632
  );
@@ -645,22 +662,42 @@ export function macTools(cfg = {}) {
645
662
  },
646
663
  {
647
664
  name: 'mac_menu_click',
648
- description: 'Click a menu item by its visible name (e.g. "Save", "Copy Link") — no pointer, no coordinates.',
665
+ description:
666
+ 'Trigger a menu item by name (e.g. "Save", "Copy Link") or a nested path ("File > Export") — no pointer, no coordinates.',
649
667
  approval: true,
650
- schema: S({ item: { type: 'string' }, app: { type: 'string' } }, ['item']),
651
- required: ['item'],
668
+ schema: S(
669
+ {
670
+ item: { type: 'string', description: 'name of the menu item' },
671
+ path: { type: 'string', description: 'nested path, e.g. "File > Export"' },
672
+ app: { type: 'string', description: 'required: the background menu click needs a target app' },
673
+ },
674
+ [],
675
+ ),
676
+ required: [],
652
677
  handler: async (args) => {
653
678
  const p = checkPeek(peek());
654
679
  if (!p.ok) return p;
655
- const flags = args.app ? ['--app', String(args.app)] : [];
656
- const res = await runVariants(
657
- p.bin,
658
- [
659
- ['menu', 'click', '--item', String(args.item), ...flags],
660
- ['menu', 'click', String(args.item), ...flags],
661
- ],
662
- { timeout: 30 },
663
- );
680
+ if (!args.item && !args.path) {
681
+ return { ok: false, error: 'missing_item', hint: 'Pass item ("Save") or path ("File > Export").' };
682
+ }
683
+ if (!args.app) {
684
+ return {
685
+ ok: false,
686
+ error: 'missing_app',
687
+ hint: 'A background menu click needs an explicit app: mac_menu_click {"app":"Safari","item":"Save"}.',
688
+ };
689
+ }
690
+ const flags = ['--app', String(args.app)];
691
+ const variants = args.path
692
+ ? [
693
+ ['menu', 'click', '--path', String(args.path), ...flags],
694
+ ['menu', 'click', String(args.path), ...flags],
695
+ ]
696
+ : [
697
+ ['menu', 'click', '--item', String(args.item), ...flags],
698
+ ['menu', 'click', String(args.item), ...flags],
699
+ ];
700
+ const res = await runVariants(p.bin, variants, { timeout: 30 });
664
701
  return finish(res, 'menu-click');
665
702
  },
666
703
  },
@@ -703,8 +740,8 @@ export function macTools(cfg = {}) {
703
740
  const res = await runVariants(
704
741
  p.bin,
705
742
  [
706
- ['space', 'switch', '--to', String(args.to)],
707
- ['space', 'switch', String(args.to)],
743
+ ['space', 'switch', '--to', String(args.to), '--foreground'],
744
+ ['space', 'switch', String(args.to), '--foreground'],
708
745
  ],
709
746
  { timeout: 30 },
710
747
  );
package/src/mcp.mjs CHANGED
@@ -48,7 +48,7 @@ export async function handleMcp(req, res, ctx) {
48
48
  mcpResponse(id, {
49
49
  protocolVersion: PROTOCOL_VERSION,
50
50
  capabilities: { tools: { listChanged: false } },
51
- serverInfo: { name: 'arena-bridge', version: '0.6.3' },
51
+ serverInfo: { name: 'arena-bridge', version: '0.6.4' },
52
52
  instructions:
53
53
  'Tools operate the user\'s own machine. Dangerous operations may return ' +
54
54
  'PROTECTED and require a local human approval.',
package/src/prompt.mjs CHANGED
@@ -92,7 +92,7 @@ ${tools.join('\n')}
92
92
  6. Stay out of the user's way: do not pass \`foreground\`, do not focus windows and do not move the
93
93
  pointer unless the user asks for it. Background actions are delivered to the target window only.
94
94
  7. \`mac_set\` writes a value straight into a control (no keystrokes, no focus change);
95
- \`mac_menu_click\` triggers a menu item by its name — both are more reliable than typing.
95
+ \`mac_menu_click\` triggers a menu item by its name (it needs the \`app\` name) — both are more reliable than typing.
96
96
  8. \`mac_wait\` waits for a control to appear or disappear instead of sleeping and guessing.
97
97
  9. \`mac_spaces\` lists the macOS virtual desktops; \`mac_space_switch\` moves the agent to another
98
98
  Space so the user keeps the screen they are looking at.