@jian-ai/remote-control 0.8.2 → 0.8.3
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 +16 -4
- package/bin/computer-macos.mjs +52 -39
- package/bin/jian-remote.mjs +792 -95
- package/linux/computer-helper.py +5 -4
- package/macos/computer-helper +0 -0
- package/macos/computer-helper.swift +164 -57
- package/macos/jian-pointer.png +0 -0
- package/package.json +1 -1
- package/windows/computer-helper.ps1 +5 -4
package/README.md
CHANGED
|
@@ -29,6 +29,16 @@ npx @jian-ai/remote-control@latest update
|
|
|
29
29
|
Reads `~/.config/jian-remote` (no token, machine id, or gateway flags), installs a compatible
|
|
30
30
|
build, and restarts the worker service.
|
|
31
31
|
|
|
32
|
+
## Version and service control
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
jian-remote version # also --version / -V
|
|
36
|
+
jian-remote stop | start | restart
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`start` / `restart` stop any previous worker before launching one process (LaunchAgent,
|
|
40
|
+
systemd --user, or Task Scheduler).
|
|
41
|
+
|
|
32
42
|
## Configure
|
|
33
43
|
|
|
34
44
|
Change the stored gateway URL and/or worker token without a full reinstall (restarts the
|
|
@@ -41,7 +51,9 @@ jian-remote configure --gateway https://gateway.example --token WORKER_TOKEN
|
|
|
41
51
|
|
|
42
52
|
## Compatibility
|
|
43
53
|
|
|
44
|
-
|
|
54
|
+
Connected workers self-update to the gateway’s release (gateway `/update` script, npm
|
|
55
|
+
fallback) including major.minor skew, then restart the OS service. Manual `install` /
|
|
56
|
+
`update` still gate on `/health`. See [docs/remote.md](../../docs/remote.md).
|
|
45
57
|
|
|
46
58
|
Requires Node.js 24+. Computer Use works on macOS, Linux (X11 + `xdotool`/`ffmpeg`), and
|
|
47
59
|
Windows. Published builds include a prebuilt macOS `computer-helper` (no Xcode on the worker
|
|
@@ -49,6 +61,6 @@ Mac) plus Linux/Windows helper scripts. From source on a Mac, run `macos/build-h
|
|
|
49
61
|
let the worker compile with `swiftc` when Command Line Tools are installed.
|
|
50
62
|
|
|
51
63
|
On macOS the LaunchAgent uses `ProcessType=Interactive` and an idle `caffeinate` so the worker
|
|
52
|
-
keeps polling while the screen is locked (App Nap must not freeze timers).
|
|
53
|
-
|
|
54
|
-
[docs/remote.md](../../docs/remote.md).
|
|
64
|
+
keeps polling while the screen is locked (App Nap must not freeze timers). A locked Mac stays
|
|
65
|
+
**online** (`uiState: locked` is advisory): the worker wakes the display and attempts
|
|
66
|
+
Computer Use; it does not soft-refuse. See [docs/remote.md](../../docs/remote.md).
|
package/bin/computer-macos.mjs
CHANGED
|
@@ -20,8 +20,11 @@ const HELPER_NAME = 'computer-helper';
|
|
|
20
20
|
const UI_STATE_CACHE_MS = 5_000;
|
|
21
21
|
export const MAC_UI_STATES = Object.freeze(['unlocked', 'locked', 'display_asleep', 'unknown']);
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
'Mac
|
|
23
|
+
const LOCKED_SCREENSHOT_NOTE =
|
|
24
|
+
'Note: Mac session reports locked — image may be the lock screen (or black if the display stayed asleep). Desktop apps behind loginwindow are often not visible.';
|
|
25
|
+
|
|
26
|
+
const LOCKED_ACTION_HINT =
|
|
27
|
+
' Mac session reports locked; if this failed, unlock or keep the display awake (post-login lock ≠ FileVault loginwindow).';
|
|
25
28
|
|
|
26
29
|
let cachedUiState = { at: 0, state: 'unknown' };
|
|
27
30
|
let workerIdleCaffeinate = null;
|
|
@@ -386,39 +389,48 @@ export class MacComputerSession {
|
|
|
386
389
|
};
|
|
387
390
|
}
|
|
388
391
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return {
|
|
392
|
-
exitCode: 1,
|
|
393
|
-
stdout: '',
|
|
394
|
-
stderr: uiBlocked,
|
|
395
|
-
timedOut: false,
|
|
396
|
-
computer: { ok: false, error: uiBlocked },
|
|
397
|
-
};
|
|
398
|
-
}
|
|
392
|
+
// Wake display when locked/asleep, then always attempt — never soft-block on uiState.
|
|
393
|
+
const uiPrep = prepareComputerUi();
|
|
399
394
|
|
|
400
395
|
if (action === 'screenshot') {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
heightPx: shot.heightPx,
|
|
408
|
-
};
|
|
409
|
-
this.bumpIdle();
|
|
410
|
-
return {
|
|
411
|
-
exitCode: 0,
|
|
412
|
-
stdout: shot.base64,
|
|
413
|
-
stderr: '',
|
|
414
|
-
timedOut: false,
|
|
415
|
-
computer: {
|
|
416
|
-
ok: true,
|
|
396
|
+
try {
|
|
397
|
+
await this.ensure();
|
|
398
|
+
const shot = capturePrimaryScreenshotBase64();
|
|
399
|
+
if (!this.geometry) throw new Error('Display geometry unavailable.');
|
|
400
|
+
this.shotGeometry = {
|
|
401
|
+
...this.geometry,
|
|
417
402
|
widthPx: shot.widthPx,
|
|
418
403
|
heightPx: shot.heightPx,
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
404
|
+
};
|
|
405
|
+
this.bumpIdle();
|
|
406
|
+
const stderr = uiPrep.sessionLocked ? LOCKED_SCREENSHOT_NOTE : '';
|
|
407
|
+
return {
|
|
408
|
+
exitCode: 0,
|
|
409
|
+
stdout: shot.base64,
|
|
410
|
+
stderr,
|
|
411
|
+
timedOut: false,
|
|
412
|
+
computer: {
|
|
413
|
+
ok: true,
|
|
414
|
+
widthPx: shot.widthPx,
|
|
415
|
+
heightPx: shot.heightPx,
|
|
416
|
+
scale: scaleFromGeometry(this.shotGeometry),
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
} catch (error) {
|
|
420
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
421
|
+
const hint = uiPrep.sessionLocked ? LOCKED_ACTION_HINT : '';
|
|
422
|
+
const tcc = /accessib|not permitted|not authorized|Screen Recording/i.test(message)
|
|
423
|
+
? ' Jian Remote needs Screen Recording (and Accessibility for input) in System Settings.'
|
|
424
|
+
: '';
|
|
425
|
+
const full = `${message}${tcc}${hint}`.trim();
|
|
426
|
+
return {
|
|
427
|
+
exitCode: 1,
|
|
428
|
+
stdout: '',
|
|
429
|
+
stderr: full,
|
|
430
|
+
timedOut: false,
|
|
431
|
+
computer: { ok: false, error: full },
|
|
432
|
+
};
|
|
433
|
+
}
|
|
422
434
|
}
|
|
423
435
|
|
|
424
436
|
await this.ensure();
|
|
@@ -479,12 +491,14 @@ export class MacComputerSession {
|
|
|
479
491
|
const tcc = /accessib|not permitted|not authorized/i.test(message)
|
|
480
492
|
? ' Jian Remote needs Accessibility (and Screen Recording for screenshots) in System Settings.'
|
|
481
493
|
: '';
|
|
494
|
+
const hint = uiPrep.sessionLocked ? LOCKED_ACTION_HINT : '';
|
|
495
|
+
const full = `${message}${tcc}${hint}`.trim();
|
|
482
496
|
return {
|
|
483
497
|
exitCode: 1,
|
|
484
498
|
stdout: '',
|
|
485
|
-
stderr:
|
|
499
|
+
stderr: full,
|
|
486
500
|
timedOut: false,
|
|
487
|
-
computer: { ok: false, error:
|
|
501
|
+
computer: { ok: false, error: full },
|
|
488
502
|
};
|
|
489
503
|
}
|
|
490
504
|
|
|
@@ -661,15 +675,14 @@ export function wakeMacDisplay(seconds = 3) {
|
|
|
661
675
|
}
|
|
662
676
|
|
|
663
677
|
/**
|
|
664
|
-
*
|
|
665
|
-
*
|
|
678
|
+
* Wake the display when locked or asleep (lock often coincides with display sleep).
|
|
679
|
+
* Never soft-blocks: callers always attempt the OS API next.
|
|
666
680
|
*/
|
|
667
|
-
export function
|
|
681
|
+
export function prepareComputerUi() {
|
|
668
682
|
let state = probeMacUiState({ force: true });
|
|
669
|
-
if (state === 'display_asleep' || state === 'unknown') {
|
|
683
|
+
if (state === 'display_asleep' || state === 'locked' || state === 'unknown') {
|
|
670
684
|
wakeMacDisplay(3);
|
|
671
685
|
state = probeMacUiState({ force: true });
|
|
672
686
|
}
|
|
673
|
-
|
|
674
|
-
return null;
|
|
687
|
+
return { sessionLocked: state === 'locked', state };
|
|
675
688
|
}
|