@desklink/axi 0.3.1 → 0.4.0

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/README.md CHANGED
@@ -24,14 +24,14 @@ Or build from the repository root with `npm install && npm run build`.
24
24
  Delete copied `*.tsbuildinfo` before rebuilding a moved worktree.
25
25
  The engine is resolved by `@desklink/host`; see its [installation guide](../desktop-host/README.md#install-and-run)
26
26
  for supported platforms and [macOS setup](../desktop-host/README.md#macos-preview).
27
- Set `DESKLINK_AXI_ENGINE` for a source build.
27
+ Set `DESKLINK_AXI_ENGINE` for a matching source build. An override pointing new AXI at a 0.3.x engine is unsupported; host and engine versions must match.
28
28
 
29
29
  A Linux desktop AXI with macOS in preview. It runs a private bridge per session, captures read-only frames through `@desklink/host` protocol v3, and sends input only through a WebRTC `control` channel. The bridge's Unix socket lives in `$XDG_RUNTIME_DIR/desklink-axi` (or the OS temp directory), with `DESKLINK_AXI_SESSION` selecting an isolated socket. It opens a separate engine session; don't drive the desktop while someone else is controlling it.
30
30
 
31
- For macOS source prerequisites, signing and permission attribution, follow the [host setup guide](../desktop-host/README.md#macos-preview). To launch AXI through its signed local harness, use `open -n /absolute/DesklinkHost.app --args axi-bridge /absolute/path/to/packages/axi/bin/desklink-axi.js --control` from the logged-in Mac session. OCR needs the optional `tesseract` executable (`brew install tesseract`, `sudo pacman -S tesseract` or `sudo apt install tesseract-ocr`); on macOS AXI also finds Homebrew's standard install paths when LaunchServices omits them from PATH. `look` works without it.
31
+ For macOS source prerequisites, signing and permission attribution, follow the [host setup guide](../desktop-host/README.md#macos-preview). To launch AXI through its signed local harness, use `open -n /absolute/DesklinkHost.app --args axi-bridge /absolute/path/to/packages/axi/bin/desklink-axi.js` from the logged-in Mac session. OCR needs the optional `tesseract` executable (`brew install tesseract`, `sudo pacman -S tesseract` or `sudo apt install tesseract-ocr`); on macOS AXI also finds Homebrew's standard install paths when LaunchServices omits them from PATH. `look` works without it.
32
32
 
33
33
  ```
34
- desklink-axi start --control --source x11 --display :97
34
+ desklink-axi start --source x11 --display :97
35
35
  desklink-axi screen --query "Budget"
36
36
  desklink-axi tree --query "Save"
37
37
  desklink-axi type "hello" --into @a3.10
@@ -47,7 +47,7 @@ desklink-axi stop
47
47
 
48
48
  `start --cursor hidden` opts into cursor-free capture; omit it for existing behavior. Use `desklink-axi cursor` to drain source-coordinate cursor samples (`x`, `y`, `visible`, `timestamp_us`) from the live session; `dropped:N` reports older samples overwritten after its 10,000-sample buffer fills. Linux portals require cursor metadata and refuse hidden capture when it is unavailable; see the [cursor protocol](../desktop-host/docs/PROTOCOL.md#cursor-free-capture).
49
49
 
50
- `health` reports capture state, latest frame sequence, and a stream-stop reason without OCR. `start` defaults to view-only; Linux uses the portal unless `--source x11 --display :N` is given, and macOS uses the main display unless `--source display --display <id>` is given. The portal can require a desktop consent prompt; its single-use restore token is kept in `$XDG_STATE_HOME/desklink-axi/portal-token` (otherwise `~/.local/state/desklink-axi/portal-token`), atomically replaced with mode 0600 after each grant. Set `DESKLINK_AXI_RESTORE_TOKEN_FILE` to use a different path. macOS Screen Recording and Accessibility belong to the app that launches the bridge and its engine child. Do not run it unattended against someone else's display. Input and clipboard read/write require `start --control`; view-only clipboard calls return a structured error naming that flag. `stop` releases held input. Actions acknowledge `input: applied` immediately, without waiting for a frame. Every action (`click`, `type`, `press`, `scroll`, `drag`) accepts `--wait settle|change|none|<milliseconds>`; frame timeout after applied input returns `input: applied; frame: timed out` with success status. `batch` takes a JSON array of 1–30 `[verb, ...args]` steps (click/type/press/scroll/wait/snapshot/assert/tree/marks), runs them in order in one socket round trip, stops on the first failed step and returns a compact per-step result. OCR refs survive advancing frame numbers only while their text and position still match a fresh crop; moved or ambiguous text requires `screen --query`. `screen --query` reuses a prior matching text location for a targeted crop before falling back to full OCR, and reports estimated text tokens. Start with `screen --query` or `--region` before `--full`. For web pages prefer DOM/browser automation when available; AXI is for native apps or whole-desktop tasks. `wait change` and `wait settle` block on engine frame signals rather than polling. `diff` reports repeatedly changing regions separately as `animating`; `diff --include-animating` includes them among ordinary changes. `look` writes a PNG crop. `--help` lists the commands and their flags. Errors are structured and the CLI does not prompt.
50
+ `health` reports capture state, latest frame sequence, and a stream-stop reason without OCR. `start` grants all available access to its same-user paired consumer; Linux uses the portal unless `--source x11 --display :N` is given, and macOS uses the main display unless `--source display --display <id>` is given. The portal can require a desktop consent prompt; its single-use restore token is kept in `$XDG_STATE_HOME/desklink-axi/portal-token` (otherwise `~/.local/state/desklink-axi/portal-token`), atomically replaced with mode 0600 after each grant. Set `DESKLINK_AXI_RESTORE_TOKEN_FILE` to use a different path. macOS Screen Recording and Accessibility belong to the app that launches the bridge and its engine child. Do not run it unattended against someone else's display. Input and clipboard need no app grants; unavailable OS backends fail per action. Legacy `start --control` is accepted and ignored. See the [clipboard protocol](../desktop-host/docs/PROTOCOL.md#clipboard-over-the-local-protocol) for X11 targets, transfer limits and selection lifetime. `stop` releases held input. Actions acknowledge `input: applied` immediately, without waiting for a frame. Every action (`click`, `type`, `press`, `scroll`, `drag`) accepts `--wait settle|change|none|<milliseconds>`; frame timeout after applied input returns `input: applied; frame: timed out` with success status. `batch` takes a JSON array of 1–30 `[verb, ...args]` steps (click/type/press/scroll/wait/snapshot/assert/tree/marks), runs them in order in one socket round trip, stops on the first failed step and returns a compact per-step result. OCR refs survive advancing frame numbers only while their text and position still match a fresh crop; moved or ambiguous text requires `screen --query`. `screen --query` reuses a prior matching text location for a targeted crop before falling back to full OCR, and reports estimated text tokens. Start with `screen --query` or `--region` before `--full`. For web pages prefer DOM/browser automation when available; AXI is for native apps or whole-desktop tasks. `wait change` and `wait settle` block on engine frame signals rather than polling. `diff` reports repeatedly changing regions separately as `animating`; `diff --include-animating` includes them among ordinary changes. `look` writes a PNG crop. `--help` lists the commands and their flags. Errors are structured and the CLI does not prompt.
51
51
  To guide the person without input, use `desklink-axi point 400,300 --label "Here" --timeout 3000`, or `desklink-axi point --clear`. See the [host protocol reference](../desktop-host/docs/PROTOCOL.md#point-on-the-host-desktop) for supported sources, coordinates, and limits.
52
52
 
53
53
  ## Browser lane (opt-in, loopback-only)
@@ -111,7 +111,7 @@ Measured on a private 1280×720 Xvfb client: `diff → click 100,100 → look @r
111
111
 
112
112
  ## Accessibility tree (`tree`) — Linux AT-SPI
113
113
 
114
- `tree` reads the desktop's AT-SPI accessibility tree over D-Bus and returns bounded TOON rows of `ref,role,name,value,x,y,w,h,states`. It needs no `--control`; acting on refs does. Refs are generation-tagged (`@a<gen>.<n>`), stay usable across newer snapshots, and are revalidated at use time: a ref whose role, name, or screen extents changed — or whose object is gone — returns `stale-ref` instead of acting blind.
114
+ `tree` reads the desktop's AT-SPI accessibility tree over D-Bus and returns bounded TOON rows of `ref,role,name,value,x,y,w,h,states`. Read and input actions share the same paired session. Refs are generation-tagged (`@a<gen>.<n>`), stay usable across newer snapshots, and are revalidated at use time: a ref whose role, name, or screen extents changed — or whose object is gone — returns `stale-ref` instead of acting blind.
115
115
 
116
116
  - Capability probe: the bus is resolved from `AT_SPI_BUS_ADDRESS`, else `org.a11y.Bus.GetAddress` on the session bus. Unavailable → structured `a11y-unavailable` error; every other command keeps working. macOS AX is **not implemented** and labeled as such.
117
117
  - Values: text and value widgets expose their content in the `value` column (Text/Value interfaces), so a typed entry can be verified without pixels.
package/dist/bridge.js CHANGED
@@ -107,7 +107,6 @@ export async function serve(args) {
107
107
  throw new Error('session already running');
108
108
  const source = args.includes('--source') ? args[args.indexOf('--source') + 1] : 'auto';
109
109
  const display = args.includes('--display') ? args[args.indexOf('--display') + 1] : undefined;
110
- const control = args.includes('--control');
111
110
  const cursor = args.includes('--cursor') ? args[args.indexOf('--cursor') + 1] : 'embedded';
112
111
  if (cursor !== 'embedded' && cursor !== 'hidden')
113
112
  throw new Error('cursor: use embedded or hidden');
@@ -164,13 +163,13 @@ export async function serve(args) {
164
163
  source: process.platform === 'darwin'
165
164
  ? { kind: 'display', ...(display === undefined ? {} : { display_id: Number(display) }) }
166
165
  : source === 'x11' || (source === 'auto' && display) ? { kind: 'x11', display } : { kind: 'portal' },
167
- permissions: control ? ['view', 'control', 'clipboard'] : ['view'], loopbackTcp: true,
168
- agentIndicator: control,
166
+ loopbackTcp: true,
167
+ agentIndicator: true, // serve rejects platforms other than Linux/macOS above.
169
168
  cursor,
170
169
  ...(tokenFile ? { restoreToken: takeToken(tokenFile) } : {}),
171
170
  }, timeout);
172
171
  checkToken();
173
- peer = control ? new PeerConnection('desklink-axi', { iceServers: [], bindAddress: '127.0.0.1', enableIceTcp: true }) : undefined;
172
+ peer = new PeerConnection('desklink-axi', { iceServers: [], bindAddress: '127.0.0.1', enableIceTcp: true });
174
173
  let channel;
175
174
  let open = false;
176
175
  let nextSeq = 1;
@@ -275,8 +274,6 @@ export async function serve(args) {
275
274
  return [...lines.values()];
276
275
  };
277
276
  const act = async (message) => {
278
- if (!control)
279
- throw new Error('input-unavailable: start --control');
280
277
  for (let i = 0; i < 100 && !open; i++)
281
278
  await new Promise(r => setTimeout(r, 50));
282
279
  if (!open || !channel)
@@ -321,7 +318,7 @@ export async function serve(args) {
321
318
  let waitSeen = baseline;
322
319
  routeNote = undefined;
323
320
  if (command === 'start')
324
- return `session: open source=${source} ${display ?? ''} size=${opened.geometry.encoded.width}x${opened.geometry.encoded.height} permissions=view${control ? ',control' : ''}`;
321
+ return `session: open source=${source} ${display ?? ''} size=${opened.geometry.encoded.width}x${opened.geometry.encoded.height}`;
325
322
  if (command === 'cursor') {
326
323
  const batch = cursorSamples.take(opened.sessionId);
327
324
  return formatCursorSamples(batch.samples, batch.dropped);
@@ -436,8 +433,6 @@ export async function serve(args) {
436
433
  return 'session: stopped';
437
434
  }
438
435
  if (command === 'clipboard') {
439
- if (!control)
440
- throw new Error('input-unavailable: clipboard requires start --control');
441
436
  if (args[0] === 'read') {
442
437
  const result = await engine.readClipboard(opened.sessionId);
443
438
  return `clipboard: ${args.includes('--full') ? result.text : result.text.slice(0, 1000)} (${result.text.length} chars)`;
@@ -656,6 +651,15 @@ export async function serve(args) {
656
651
  if (fields.some(field => !['ref', 'text', 'x', 'y', 'w', 'h', 'conf', 'line'].includes(field)))
657
652
  throw new Error('fields: valid fields are ref,text,x,y,w,h,conf,line');
658
653
  const frame = await capture(pendingDamage ? waitSeen : baseline);
654
+ // A frame reply can already be older than the latest change notice. Its
655
+ // stillness must not erase motion from a newer frame. Classify at capture
656
+ // time too: synchronous OCR can outlast the motion freshness window.
657
+ const moving = [...motion].filter(([, state]) => state.count >= 3 && Date.now() - state.at < 600 && (frame.still_ms < 150 || state.seq > frame.seq));
658
+ if (frame.still_ms >= 150)
659
+ for (const [region, state] of motion) {
660
+ if (state.seq <= frame.seq)
661
+ motion.delete(region);
662
+ }
659
663
  if (pendingDamage)
660
664
  frame.damage = [...new Set([...pendingDamage, ...frame.damage])];
661
665
  if (command === 'look') {
@@ -744,7 +748,7 @@ export async function serve(args) {
744
748
  }
745
749
  }
746
750
  if (command === 'home')
747
- return `session: open source=${source} ${display ?? ''} size=${frame.width}x${frame.height} permissions=view${control ? ',control' : ''}\nframe: ${frame.seq} settled=${frame.still_ms >= 150} unseen=${frame.changed ? frame.damage.length : 0} region\nwindows: unavailable on this compositor\ntext[${Math.min(text.length, 12)} of ${text.length}]{ref,text,x,y}:\n${text.slice(0, 12).map(t => ` ${t.ref},${JSON.stringify(t.text)},${t.x},${t.y}`).join('\n')}\nhelp[2]:\n desklink-axi diff\n desklink-axi screen --query "<words>"`;
751
+ return `session: open source=${source} ${display ?? ''} size=${frame.width}x${frame.height}\nframe: ${frame.seq} settled=${frame.still_ms >= 150} unseen=${frame.changed ? frame.damage.length : 0} region\nwindows: unavailable on this compositor\ntext[${Math.min(text.length, 12)} of ${text.length}]{ref,text,x,y}:\n${text.slice(0, 12).map(t => ` ${t.ref},${JSON.stringify(t.text)},${t.x},${t.y}`).join('\n')}\nhelp[2]:\n desklink-axi diff\n desklink-axi screen --query "<words>"`;
748
752
  if (command === 'screen') {
749
753
  const query = args.includes('--query') ? args[args.indexOf('--query') + 1] : undefined;
750
754
  const items = query ? text.filter(t => t.text.toLowerCase().includes(query.toLowerCase())) : text;
@@ -774,11 +778,8 @@ export async function serve(args) {
774
778
  return false;
775
779
  });
776
780
  const gone = unmatched.length;
777
- if (frame.still_ms >= 150)
778
- motion.clear();
779
781
  const animating = [];
780
782
  const visible = [];
781
- const moving = [...motion].filter(([, state]) => state.count >= 3 && Date.now() - state.at < 600);
782
783
  for (const region of [...frame.damage, ...moving.map(([key]) => key).filter(key => !frame.damage.some(region => overlaps(key, region)))]) {
783
784
  const state = moving.find(([key]) => overlaps(key, region))?.[1];
784
785
  const [x, y, w, h] = region.split(',').map(Number);
@@ -797,8 +798,6 @@ export async function serve(args) {
797
798
  const changed = visible.length ? `${visible.length} region since frame ${frame.previous}` : `none since frame ${frame.previous}`;
798
799
  return `changed: ${changed}\nregions[${visible.length}]{ref,box}:\n${visible.map((d, i) => ` @r${i + 1},"${d}"`).join('\n')}\nappeared[${Math.min(appeared.length, 20)} of ${appeared.length}]{${fields.join(',')}}:\n${appeared.slice(0, 20).map(t => ` ${fields.map(field => JSON.stringify(field === 'w' ? t.words.at(-1).x + t.words.at(-1).w - t.x : field === 'h' ? Math.max(...t.words.map(w => w.y + w.h)) - t.y : t[field])).join(',')}`).join('\n')}\ngone: ${gone} text items${animating.length ? `\nanimating[${animating.length}]{box}:\n${animating.map(box => ` "${box}"`).join('\n')}\nanimating: ${animating.join('; ')}` : ''}\nhelp[2]:\n desklink-axi look @r1\n desklink-axi screen --query "<words>"`;
799
800
  }
800
- if (frame.still_ms >= 150)
801
- motion.clear();
802
801
  return `changed: none since frame ${frame.seq} (still ${frame.still_ms}ms)\nhelp[1]:\n desklink-axi screen --query "<words>"`;
803
802
  };
804
803
  let commands = Promise.resolve();
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ else if (process.argv[2] === '--session-hook') {
20
20
  }
21
21
  else {
22
22
  const definitions = [
23
- ['start', [], ['control', 'source', 'display', 'timeout', 'cursor']], ['stop', [], []], ['health', [], []], ['cursor', [], []],
23
+ ['start', [], ['source', 'display', 'timeout', 'cursor']], ['stop', [], []], ['health', [], []], ['cursor', [], []],
24
24
  ['screen', [], ['query', 'region', 'full', 'fields']], ['diff', [], ['include-animating', 'fields']],
25
25
  ['tree', [], ['query', 'full', 'fields']], ['marks', [], ['region', 'out']],
26
26
  ['point', [], ['label', 'timeout', 'clear']],
@@ -35,7 +35,7 @@ else {
35
35
  Object.assign(commands, browserCommands());
36
36
  for (const [name, required, flags] of definitions)
37
37
  commands[name] = {
38
- spec: { name, summary: `${name} on the live desktop`, args: name === 'point' ? [{ name: 'x,y', required: false, description: 'selected desktop pixels; omit for --clear' }] : required.map(arg => ({ name: arg, required: true, description: arg })), flags: flags.map(flag => ({ name: flag, type: ['control', 'full', 'double', 'submit', 'include-animating', 'clear'].includes(flag) ? 'boolean' : 'string', values: flag === 'source' ? ['auto', 'portal', 'x11', 'display'] : flag === 'button' ? ['left', 'right'] : undefined, description: flag })), examples: name === 'point' ? ['desklink-axi point 400,300 --label "Here" --timeout 3000', 'desklink-axi point --clear'] : [`desklink-axi ${name} ${required.map(arg => `<${arg}>`).join(' ')}`] },
38
+ spec: { name, summary: `${name} on the live desktop`, args: name === 'point' ? [{ name: 'x,y', required: false, description: 'selected desktop pixels; omit for --clear' }] : required.map(arg => ({ name: arg, required: true, description: arg })), flags: flags.map(flag => ({ name: flag, type: ['full', 'double', 'submit', 'include-animating', 'clear'].includes(flag) ? 'boolean' : 'string', values: flag === 'source' ? ['auto', 'portal', 'x11', 'display'] : flag === 'button' ? ['left', 'right'] : undefined, description: flag })), examples: name === 'point' ? ['desklink-axi point 400,300 --label "Here" --timeout 3000', 'desklink-axi point --clear'] : [`desklink-axi ${name} ${required.map(arg => `<${arg}>`).join(' ')}`] },
39
39
  async run(parsed) {
40
40
  if (name === 'scroll' && !['up', 'down'].includes(parsed.positionals[0]))
41
41
  throw new UsageError('scroll direction must be up or down');
@@ -68,5 +68,8 @@ else {
68
68
  },
69
69
  };
70
70
  const registry = { tool: 'desklink-axi', root: homeCommand, rootHelp, commands };
71
- process.exit(await dispatch(registry, process.argv.slice(2)));
71
+ // 0.3 scripts may still pass --control; paired sessions always have access.
72
+ const argv = process.argv.slice(2);
73
+ const flagsEnd = argv.indexOf('--');
74
+ process.exit(await dispatch(registry, argv.filter((arg, index) => !(argv[0] === 'start' && arg === '--control' && (flagsEnd < 0 || index < flagsEnd)))));
72
75
  }
@@ -3,6 +3,6 @@ import { emitList } from '../output/toon.js';
3
3
  export const DESCRIPTION = "See and drive a live Linux or macOS desktop and an opted-in browser through desklink; text and changes by default, pixels on request";
4
4
  export const SPEC_VERSION = 'axi/1.0-2026-07';
5
5
  const commands = ['start', 'stop', 'health', 'screen', 'tree', 'marks', 'diff', 'look', 'point', 'click', 'type', 'press', 'scroll', 'drag', 'batch', 'wait', 'clipboard read', 'clipboard write', 'setup hooks', 'browser attach', 'browser launch', 'browser tabs', 'browser select', 'browser open', 'browser navigate', 'browser close', 'browser snapshot', 'browser click', 'browser fill', 'browser press', 'browser upload', 'browser detach'];
6
- export function renderHome(binPath) { return `desklink-axi: ${binPath.replace(homedir(), '~')} — ${DESCRIPTION}\nhelp[5]:\n desklink-axi start --control --source x11 --display :97\n desklink-axi screen --query "<words>"\n desklink-axi tree --query "<words>"\n desklink-axi look @r1\n desklink-axi browser snapshot`; }
7
- export function rootHelpText() { return [`desklink-axi: ${DESCRIPTION}`, emitList('commands', commands.map(command => ({ command, summary: command.startsWith('browser') ? `${command} on the attached browser` : `${command} on the live desktop` })), ['command', 'summary']), 'flags[1]{flag,description}:\n --help,show help for any command', 'examples[2]:\n desklink-axi start --control --source x11 --display :97\n desklink-axi screen --query "<words>"'].join('\n'); }
6
+ export function renderHome(binPath) { return `desklink-axi: ${binPath.replace(homedir(), '~')} — ${DESCRIPTION}\nhelp[5]:\n desklink-axi start --source x11 --display :97\n desklink-axi screen --query "<words>"\n desklink-axi tree --query "<words>"\n desklink-axi look @r1\n desklink-axi browser snapshot`; }
7
+ export function rootHelpText() { return [`desklink-axi: ${DESCRIPTION}`, emitList('commands', commands.map(command => ({ command, summary: command.startsWith('browser') ? `${command} on the attached browser` : `${command} on the live desktop` })), ['command', 'summary']), 'flags[1]{flag,description}:\n --help,show help for any command', 'examples[2]:\n desklink-axi start --source x11 --display :97\n desklink-axi screen --query "<words>"'].join('\n'); }
8
8
  export function renderSkill() { return `---\nname: desklink-axi\ndescription: "${DESCRIPTION}"\n---\n\n# desklink-axi\n\n${DESCRIPTION} (${SPEC_VERSION}).\n\n\`\`\`\n${renderHome('desklink-axi')}\n\`\`\`\n\nTwo lanes: the desktop lane (capture, OCR refs, AT-SPI tree, input) and the browser lane (semantic web control through an opted-in Chromium). On the desktop, \`tree --query "<words>"\` reads the AT-SPI accessibility tree into generation-tagged \`@a<gen>.<n>\` refs (values included), and \`marks\` overlays numbered candidates for icon-only controls. For web pages use the browser lane: \`desklink-axi browser attach --cdp 127.0.0.1:<port>\` for a browser you started with \`--remote-debugging-port\` (loopback only), or \`desklink-axi browser launch\` for an isolated task-owned profile. Then \`browser tabs\`, \`browser snapshot\` (compact accessibility refs, no screenshots), \`browser click/fill/upload @e<N>\`, \`browser select <tab>\`. Page selection (selected) and the browser's actually activated tab (front) are reported separately. OS-level dialogs are not-supported in the browser lane: fall back to the desktop lane (click/type on the dialog).\n\nRead with screen --query or --region before screen --full. Actions acknowledge input immediately; add --wait change|settle|<milliseconds> when needed. Batch sequential steps in one socket round trip: desklink-axi batch '[["press","Meta+l"],["type","https://example.com"],["press","Return"],["wait","change"]]'. Steps may be {verb,args?,name?,wait?} objects; a named snapshot binds OCR refs for later @name.n steps, and assert steps (\`["assert","text"]\`, \`["assert","--absent","text"]\`) make the batch outcome depend on observed screen text. The footer reports effects: asserted[n] or effects: unverified (input acknowledgements only) — an input ack is never a verified outcome.\n\nGuide without input using \`point 400,300 --label "Here" --timeout 3000\`; use \`point --clear\` to remove the cue. See the [host protocol reference](../../../desktop-host/docs/PROTOCOL.md#point-on-the-host-desktop) for supported sources and limits. Every command supports --help. Exit 0 success, 1 operational error, 2 usage error. No screen text at startup unless requested.\n`; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desklink/axi",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Agent-facing desktop control CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  "typescript": "5.9.3"
30
30
  },
31
31
  "dependencies": {
32
- "@desklink/host": "0.3.1",
32
+ "@desklink/host": "0.4.0",
33
33
  "node-datachannel": "0.33.4",
34
34
  "playwright-core": "1.62.1",
35
35
  "pngjs": "7.0.0"
@@ -10,7 +10,7 @@ See and drive a live Linux or macOS desktop and an opted-in browser through desk
10
10
  ```
11
11
  desklink-axi: desklink-axi — See and drive a live Linux or macOS desktop and an opted-in browser through desklink; text and changes by default, pixels on request
12
12
  help[5]:
13
- desklink-axi start --control --source x11 --display :97
13
+ desklink-axi start --source x11 --display :97
14
14
  desklink-axi screen --query "<words>"
15
15
  desklink-axi tree --query "<words>"
16
16
  desklink-axi look @r1