@agentconnect.md/computer 2.6.0-rc.91 → 2.6.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 +31 -21
- package/dist/agentconnect-computer.mjs +129 -62
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -138,35 +138,45 @@ enough: the switch must be on. Without it, clicks and keys are silently dropped,
|
|
|
138
138
|
moves still work. Cmd shortcuts are allowed on macOS; only lock screen, log out, and force quit are
|
|
139
139
|
refused. ffmpeg is a pinned static 6.0 build (eugeneware/ffmpeg-static b6.1.1).
|
|
140
140
|
|
|
141
|
-
### macOS
|
|
141
|
+
### macOS: the app holds the permissions, not Terminal
|
|
142
142
|
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
`agentconnect computer install` puts `AgentConnect Computer.app` (`md.agentconnect.computer`) in
|
|
144
|
+
`~/Applications` next to the daemon. The app is a signed copy of the official, pinned Node.js; the
|
|
145
|
+
daemon itself stays in the version store and runs inside it. Run the install from Terminal on the
|
|
146
|
+
Mac, or over SSH while someone is logged in to its desktop:
|
|
146
147
|
|
|
147
148
|
```sh
|
|
148
|
-
|
|
149
|
-
~/Applications/"AgentConnect Computer.app"/Contents/MacOS/agentconnect-computer setup
|
|
149
|
+
npx -y @agentconnect.md/cli@latest computer install --register <token> --cp-url <exchange URL>
|
|
150
150
|
```
|
|
151
151
|
|
|
152
152
|
- `setup` installs a per-user LaunchAgent (`~/Library/LaunchAgents/md.agentconnect.computer.plist`,
|
|
153
|
-
Aqua session, starts at login, restarts on crash)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
Aqua session, starts at login, restarts on crash) that runs
|
|
154
|
+
`<app>/Contents/MacOS/agentconnect-computer ~/.agentconnect/computer/current/dist/agentconnect-computer.mjs run`.
|
|
155
|
+
launchd starts the app itself, so macOS attributes Accessibility and Screen Recording to
|
|
156
|
+
**AgentConnect Computer**. Cua Driver and ffmpeg run as its children and inherit the grants. The
|
|
157
|
+
app raises the dialogs on the Mac's screen; `setup` opens the System Settings panes, waits, and
|
|
158
|
+
restarts the service until a new Screen Recording grant takes effect. Then it runs `doctor`
|
|
159
|
+
inside the service.
|
|
160
|
+
- `agentconnect computer upgrade` only moves `current`, so the grants stay. The app is replaced
|
|
161
|
+
only when a version pins a different one (a new app build, such as a Node update).
|
|
158
162
|
- `doctor` asks the running service to run the checks as its own child (`/control/doctor`, loopback
|
|
159
|
-
only), so the result reflects the app's grants.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
only), so the result reflects the app's grants. It tells a grant that is listed but switched off
|
|
164
|
+
from one never asked for, and from one the app lost (fix:
|
|
165
|
+
`tccutil reset Accessibility md.agentconnect.computer`, then `setup`).
|
|
166
|
+
- `agentconnect computer uninstall` removes the LaunchAgent and the app; `--purge` also resets the
|
|
167
|
+
app's grants.
|
|
168
|
+
- Until a release publishes the app packages, `install` says that this version pins no app, and
|
|
169
|
+
the grants go to the terminal that runs the daemon.
|
|
170
|
+
- `pnpm bundle:macos` builds the app package into `bundle/computer-darwin-<arch>/` (on a Mac;
|
|
171
|
+
`TARGET_ARCH=x64` cross-builds). It is ad-hoc signed by default, which ties grants to that build;
|
|
172
|
+
`CODESIGN_IDENTITY` (Developer ID) and `NOTARY_KEYCHAIN_PROFILE` sign and notarize it for release.
|
|
173
|
+
- The release builds the app on a macOS runner once the repository variable
|
|
174
|
+
`PUBLISH_COMPUTER_MACOS_APP` is `true`. It republishes an arch's app only when
|
|
175
|
+
`build-macos-app.mjs` changed, and the computer package pins each app exactly.
|
|
166
176
|
|
|
167
177
|
## Run on Linux
|
|
168
178
|
|
|
169
|
-
**Install** (what the console's **Computers → Add Computer** shows for
|
|
179
|
+
**Install** (what the console's **Computers → Add Computer** shows for every desktop OS): run it in a terminal
|
|
170
180
|
inside the desktop session, as the desktop user, not root. It needs Node.js ≥ 24.12 (the official
|
|
171
181
|
tarball or your distribution's package).
|
|
172
182
|
|
|
@@ -251,8 +261,8 @@ off; `BROWSER_URL` opens a page). `docker stop` stops the daemon, closes the bro
|
|
|
251
261
|
code 0. `/home/computer` is a volume: the Computer key, config, audit log, and the browser profile survive a new
|
|
252
262
|
container. A `HEALTHCHECK` runs `doctor --json --daemon` inside the running daemon.
|
|
253
263
|
|
|
254
|
-
Register it as a Computer: create a token in the console under **Computers → Add Computer** (
|
|
255
|
-
this command), then
|
|
264
|
+
Register it as a Computer: create a token in the console under **Computers → Add Computer** (its **Docker** tab
|
|
265
|
+
shows this command), then
|
|
256
266
|
|
|
257
267
|
```sh
|
|
258
268
|
docker run -d --name computer --hostname computer-1 --shm-size=1g --restart unless-stopped \
|
|
@@ -31457,9 +31457,9 @@ var require_websocket_server = __commonJS({
|
|
|
31457
31457
|
});
|
|
31458
31458
|
|
|
31459
31459
|
// src/cli.ts
|
|
31460
|
-
import { existsSync as existsSync6, rmSync as rmSync7, writeFileSync as
|
|
31460
|
+
import { existsSync as existsSync6, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
31461
31461
|
import { hostname as hostname5, networkInterfaces as networkInterfaces2 } from "node:os";
|
|
31462
|
-
import { basename as basename2, join as
|
|
31462
|
+
import { basename as basename2, join as join12 } from "node:path";
|
|
31463
31463
|
import { createInterface as createInterface2 } from "node:readline";
|
|
31464
31464
|
import { parseArgs } from "node:util";
|
|
31465
31465
|
|
|
@@ -84773,9 +84773,8 @@ async function startLocalControls(options) {
|
|
|
84773
84773
|
// src/self.ts
|
|
84774
84774
|
import { execFile } from "node:child_process";
|
|
84775
84775
|
import { resolve } from "node:path";
|
|
84776
|
-
import { isSea } from "node:sea";
|
|
84777
84776
|
function runSelf(args, timeoutMs) {
|
|
84778
|
-
const argv =
|
|
84777
|
+
const argv = [process.argv[1], ...args];
|
|
84779
84778
|
return new Promise((resolve2, reject) => {
|
|
84780
84779
|
execFile(
|
|
84781
84780
|
process.execPath,
|
|
@@ -84795,7 +84794,7 @@ var managedEntry = () => process.env[SERVICE_ENTRY_ENV]?.trim() || void 0;
|
|
|
84795
84794
|
var serviceEntry = () => managedEntry() ?? resolve(process.argv[1]);
|
|
84796
84795
|
|
|
84797
84796
|
// src/version.ts
|
|
84798
|
-
var COMPUTER_DAEMON_VERSION = true ? "2.6.0
|
|
84797
|
+
var COMPUTER_DAEMON_VERSION = true ? "2.6.0" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
84799
84798
|
|
|
84800
84799
|
// src/http-server.ts
|
|
84801
84800
|
var CONTROL_HEADER = "x-agentconnect-computer";
|
|
@@ -108628,6 +108627,39 @@ function displaySleepMinutes() {
|
|
|
108628
108627
|
return match ? Number(match[1]) : void 0;
|
|
108629
108628
|
}
|
|
108630
108629
|
|
|
108630
|
+
// src/macos-grants.ts
|
|
108631
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
108632
|
+
import { join as join7 } from "node:path";
|
|
108633
|
+
var APP_ID = "md.agentconnect.computer";
|
|
108634
|
+
var TCC_SERVICE = { accessibility: "Accessibility", screenRecording: "ScreenCapture" };
|
|
108635
|
+
var grantRecordPath = (dir) => join7(dir, "macos-grants.json");
|
|
108636
|
+
function readGrantRecord(dir) {
|
|
108637
|
+
try {
|
|
108638
|
+
const record2 = JSON.parse(readFileSync3(grantRecordPath(dir), "utf8"));
|
|
108639
|
+
return { asked: record2.asked ?? {}, held: record2.held ?? {} };
|
|
108640
|
+
} catch {
|
|
108641
|
+
return { asked: {}, held: {} };
|
|
108642
|
+
}
|
|
108643
|
+
}
|
|
108644
|
+
function recordGrants(dir, state, asked, now = /* @__PURE__ */ new Date()) {
|
|
108645
|
+
const record2 = readGrantRecord(dir);
|
|
108646
|
+
const at = now.toISOString();
|
|
108647
|
+
for (const grant of ["accessibility", "screenRecording"]) {
|
|
108648
|
+
if (state[grant]) record2.held[grant] ??= at;
|
|
108649
|
+
else if (asked) record2.asked[grant] ??= at;
|
|
108650
|
+
}
|
|
108651
|
+
try {
|
|
108652
|
+
writeFileSync6(grantRecordPath(dir), `${JSON.stringify(record2, null, 2)}
|
|
108653
|
+
`, { mode: 384 });
|
|
108654
|
+
} catch {
|
|
108655
|
+
}
|
|
108656
|
+
return record2;
|
|
108657
|
+
}
|
|
108658
|
+
function grantLoss(record2, grant) {
|
|
108659
|
+
if (record2.held[grant]) return "lost";
|
|
108660
|
+
return record2.asked[grant] ? "listed-off" : "never-asked";
|
|
108661
|
+
}
|
|
108662
|
+
|
|
108631
108663
|
// src/cp/authority.ts
|
|
108632
108664
|
var RELAY_TOKEN_TTL_MS = 6e4;
|
|
108633
108665
|
var ChannelAuthority = class {
|
|
@@ -109526,8 +109558,8 @@ function createRelayedMcpHandler(deps) {
|
|
|
109526
109558
|
}
|
|
109527
109559
|
|
|
109528
109560
|
// src/cp/registration.ts
|
|
109529
|
-
import { chmodSync, closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as
|
|
109530
|
-
import { join as
|
|
109561
|
+
import { chmodSync, closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync4, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
109562
|
+
import { join as join8 } from "node:path";
|
|
109531
109563
|
var Registration = external_exports.object({
|
|
109532
109564
|
cpUrl: external_exports.string().url(),
|
|
109533
109565
|
channelUrl: external_exports.string().url(),
|
|
@@ -109543,11 +109575,11 @@ var Registration = external_exports.object({
|
|
|
109543
109575
|
revokedAt: external_exports.string().datetime().optional(),
|
|
109544
109576
|
revokedReason: external_exports.string().optional()
|
|
109545
109577
|
});
|
|
109546
|
-
var registrationPath = (dir = dataDir()) =>
|
|
109578
|
+
var registrationPath = (dir = dataDir()) => join8(dir, "computer-key.json");
|
|
109547
109579
|
function loadRegistration(dir = dataDir()) {
|
|
109548
109580
|
let raw;
|
|
109549
109581
|
try {
|
|
109550
|
-
raw =
|
|
109582
|
+
raw = readFileSync4(registrationPath(dir), "utf8");
|
|
109551
109583
|
} catch (error62) {
|
|
109552
109584
|
if (error62.code === "ENOENT") return void 0;
|
|
109553
109585
|
throw error62;
|
|
@@ -109564,7 +109596,7 @@ function saveRegistration(registration, dir = dataDir()) {
|
|
|
109564
109596
|
rmSync4(tmp, { force: true });
|
|
109565
109597
|
const fd = openSync(tmp, "wx", 384);
|
|
109566
109598
|
try {
|
|
109567
|
-
|
|
109599
|
+
writeFileSync7(fd, body);
|
|
109568
109600
|
} finally {
|
|
109569
109601
|
closeSync(fd);
|
|
109570
109602
|
}
|
|
@@ -109629,7 +109661,7 @@ async function registerOrKeep(dir, cpUrl, register3) {
|
|
|
109629
109661
|
|
|
109630
109662
|
// src/cp/report.ts
|
|
109631
109663
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
109632
|
-
import { existsSync as existsSync3, readdirSync, readFileSync as
|
|
109664
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync5 } from "node:fs";
|
|
109633
109665
|
import { release as release2 } from "node:os";
|
|
109634
109666
|
var MAX_CHECKS = 64;
|
|
109635
109667
|
var MAX_TEXT = 1e3;
|
|
@@ -109637,7 +109669,7 @@ var computerOs = (platform = process.platform) => platform === "darwin" ? "macos
|
|
|
109637
109669
|
function osVersion(platform = process.platform) {
|
|
109638
109670
|
if (platform === "linux") {
|
|
109639
109671
|
try {
|
|
109640
|
-
const pretty = /^PRETTY_NAME="?([^"\n]+)"?$/m.exec(
|
|
109672
|
+
const pretty = /^PRETTY_NAME="?([^"\n]+)"?$/m.exec(readFileSync5("/etc/os-release", "utf8"))?.[1];
|
|
109641
109673
|
if (pretty) return pretty.slice(0, 120);
|
|
109642
109674
|
} catch {
|
|
109643
109675
|
}
|
|
@@ -109654,7 +109686,7 @@ function xvfbServes(display) {
|
|
|
109654
109686
|
try {
|
|
109655
109687
|
for (const pid of readdirSync("/proc")) {
|
|
109656
109688
|
if (!/^\d+$/.test(pid)) continue;
|
|
109657
|
-
const argv =
|
|
109689
|
+
const argv = readFileSync5(`/proc/${pid}/cmdline`, "utf8").split("\0");
|
|
109658
109690
|
if (/(^|\/)Xvfb$/.test(argv[0] ?? "") && argv.includes(`:${number4}`)) return true;
|
|
109659
109691
|
}
|
|
109660
109692
|
} catch {
|
|
@@ -109726,12 +109758,12 @@ function capabilitiesFrom(checks, display, options = {}) {
|
|
|
109726
109758
|
|
|
109727
109759
|
// src/cp/env-registration.ts
|
|
109728
109760
|
import { createHash as createHash6 } from "node:crypto";
|
|
109729
|
-
import { readFileSync as
|
|
109761
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
109730
109762
|
var REGISTER_TOKEN_ENV = "AGENTCONNECT_REGISTER_TOKEN";
|
|
109731
109763
|
var REGISTER_TOKEN_FILE_ENV = "AGENTCONNECT_REGISTER_TOKEN_FILE";
|
|
109732
109764
|
var CP_URL_ENV = "AGENTCONNECT_CP_URL";
|
|
109733
109765
|
var COMPUTER_NAME_ENV = "AGENTCONNECT_COMPUTER_NAME";
|
|
109734
|
-
function registrationFromEnv(env = process.env, read = (path) =>
|
|
109766
|
+
function registrationFromEnv(env = process.env, read = (path) => readFileSync6(path, "utf8")) {
|
|
109735
109767
|
let token = env[REGISTER_TOKEN_ENV]?.trim() ?? "";
|
|
109736
109768
|
const file2 = env[REGISTER_TOKEN_FILE_ENV]?.trim();
|
|
109737
109769
|
if (!token && file2) {
|
|
@@ -110104,11 +110136,11 @@ function scheduleDoctor(options) {
|
|
|
110104
110136
|
|
|
110105
110137
|
// src/service-linux.ts
|
|
110106
110138
|
import { execFile as execFile2, spawnSync as spawnSync6 } from "node:child_process";
|
|
110107
|
-
import { copyFileSync, mkdirSync as mkdirSync7, rmSync as rmSync5, writeFileSync as
|
|
110139
|
+
import { copyFileSync, mkdirSync as mkdirSync7, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
110108
110140
|
import { homedir as homedir3 } from "node:os";
|
|
110109
|
-
import { join as
|
|
110141
|
+
import { join as join9 } from "node:path";
|
|
110110
110142
|
var UNIT = "agentconnect-computer.service";
|
|
110111
|
-
var unitPath = () =>
|
|
110143
|
+
var unitPath = () => join9(process.env.XDG_CONFIG_HOME ?? join9(homedir3(), ".config"), "systemd", "user", UNIT);
|
|
110112
110144
|
var SESSION_VARS = [
|
|
110113
110145
|
"DISPLAY",
|
|
110114
110146
|
"XAUTHORITY",
|
|
@@ -110121,7 +110153,7 @@ var SESSION_VARS = [
|
|
|
110121
110153
|
var SESSION_ENV = "AGENTCONNECT_COMPUTER_SESSION";
|
|
110122
110154
|
var SESSION_DISPLAY_VARS = ["DISPLAY", "XAUTHORITY", "WAYLAND_DISPLAY", SESSION_ENV];
|
|
110123
110155
|
var EXIT_NEW_SESSION = 75;
|
|
110124
|
-
var autostartPath = () =>
|
|
110156
|
+
var autostartPath = () => join9(process.env.XDG_CONFIG_HOME ?? join9(homedir3(), ".config"), "autostart", "agentconnect-computer.desktop");
|
|
110125
110157
|
function systemctl(...args) {
|
|
110126
110158
|
const result = spawnSync6("systemctl", ["--user", ...args], { encoding: "utf8" });
|
|
110127
110159
|
return { ok: result.status === 0, output: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim() };
|
|
@@ -110164,15 +110196,15 @@ X-GNOME-Autostart-enabled=true
|
|
|
110164
110196
|
function installService(dataDir2, script, managed = false) {
|
|
110165
110197
|
let target = script;
|
|
110166
110198
|
if (!managed) {
|
|
110167
|
-
const app =
|
|
110199
|
+
const app = join9(dataDir2, "app");
|
|
110168
110200
|
mkdirSync7(app, { recursive: true });
|
|
110169
|
-
target =
|
|
110201
|
+
target = join9(app, "agentconnect-computer.mjs");
|
|
110170
110202
|
if (script !== target) copyFileSync(script, target);
|
|
110171
110203
|
}
|
|
110172
|
-
mkdirSync7(
|
|
110173
|
-
|
|
110174
|
-
mkdirSync7(
|
|
110175
|
-
|
|
110204
|
+
mkdirSync7(join9(unitPath(), ".."), { recursive: true });
|
|
110205
|
+
writeFileSync8(unitPath(), unitFile(process.execPath, target));
|
|
110206
|
+
mkdirSync7(join9(autostartPath(), ".."), { recursive: true });
|
|
110207
|
+
writeFileSync8(autostartPath(), autostartEntry());
|
|
110176
110208
|
systemctl("daemon-reload");
|
|
110177
110209
|
const enabled = systemctl("enable", UNIT);
|
|
110178
110210
|
if (!enabled.ok) throw new Error(`systemctl --user enable failed: ${enabled.output}`);
|
|
@@ -110309,25 +110341,25 @@ function watchSession(options) {
|
|
|
110309
110341
|
|
|
110310
110342
|
// src/service-macos.ts
|
|
110311
110343
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
110312
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync8, rmSync as rmSync6, writeFileSync as
|
|
110344
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync8, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
110313
110345
|
import { homedir as homedir4 } from "node:os";
|
|
110314
|
-
import { join as
|
|
110346
|
+
import { join as join10 } from "node:path";
|
|
110315
110347
|
var SERVICE_LABEL = "md.agentconnect.computer";
|
|
110316
|
-
var plistPath = () =>
|
|
110348
|
+
var plistPath = () => join10(homedir4(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
110317
110349
|
var domain2 = () => `gui/${process.getuid()}`;
|
|
110318
110350
|
var xml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
110319
110351
|
function launchctl(...args) {
|
|
110320
110352
|
const result = spawnSync7("launchctl", args, { encoding: "utf8" });
|
|
110321
110353
|
return { ok: result.status === 0, output: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim() };
|
|
110322
110354
|
}
|
|
110323
|
-
|
|
110324
|
-
|
|
110325
|
-
|
|
110355
|
+
var guiSession = () => launchctl("print", domain2()).ok;
|
|
110356
|
+
function servicePlist(program, logPath) {
|
|
110357
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
110326
110358
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
110327
110359
|
<plist version="1.0">
|
|
110328
110360
|
<dict>
|
|
110329
110361
|
<key>Label</key><string>${SERVICE_LABEL}</string>
|
|
110330
|
-
<key>ProgramArguments</key><array
|
|
110362
|
+
<key>ProgramArguments</key><array>${[...program, "run"].map((arg) => `<string>${xml(arg)}</string>`).join("")}</array>
|
|
110331
110363
|
<key>AssociatedBundleIdentifiers</key><array><string>${SERVICE_LABEL}</string></array>
|
|
110332
110364
|
<key>LimitLoadToSessionType</key><string>Aqua</string>
|
|
110333
110365
|
<key>ProcessType</key><string>Interactive</string>
|
|
@@ -110335,15 +110367,22 @@ function installService2(executable, logPath) {
|
|
|
110335
110367
|
<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
|
|
110336
110368
|
<key>StandardOutPath</key><string>${xml(logPath)}</string>
|
|
110337
110369
|
<key>StandardErrorPath</key><string>${xml(logPath)}</string>
|
|
110370
|
+
<key>EnvironmentVariables</key><dict><key>NODE_OPTIONS</key><string></string></dict>
|
|
110338
110371
|
</dict>
|
|
110339
110372
|
</plist>
|
|
110340
110373
|
`;
|
|
110374
|
+
}
|
|
110375
|
+
function installService2(program, logPath) {
|
|
110376
|
+
mkdirSync8(join10(homedir4(), "Library", "LaunchAgents"), { recursive: true });
|
|
110341
110377
|
stopService2();
|
|
110342
|
-
|
|
110378
|
+
writeFileSync9(plistPath(), servicePlist(program, logPath));
|
|
110379
|
+
if (!guiSession()) return false;
|
|
110343
110380
|
startService2();
|
|
110381
|
+
return true;
|
|
110344
110382
|
}
|
|
110345
110383
|
function startService2() {
|
|
110346
110384
|
if (serviceStatus2().loaded) return;
|
|
110385
|
+
if (!guiSession()) throw new Error("nobody is logged in to this Mac's desktop: the service starts at the next login");
|
|
110347
110386
|
const result = launchctl("bootstrap", domain2(), plistPath());
|
|
110348
110387
|
if (!result.ok) throw new Error(`launchctl bootstrap failed: ${result.output}`);
|
|
110349
110388
|
}
|
|
@@ -110373,7 +110412,7 @@ function serviceStatus2() {
|
|
|
110373
110412
|
// src/setup.ts
|
|
110374
110413
|
import { spawn as spawn5, spawnSync as spawnSync8 } from "node:child_process";
|
|
110375
110414
|
import { existsSync as existsSync5 } from "node:fs";
|
|
110376
|
-
import { basename, dirname as dirname2, isAbsolute, join as
|
|
110415
|
+
import { basename, dirname as dirname2, isAbsolute, join as join11 } from "node:path";
|
|
110377
110416
|
import { createInterface } from "node:readline";
|
|
110378
110417
|
var localizeChecks = (checks, command = selfCommand()) => {
|
|
110379
110418
|
const rename = (text) => text.replace(/(?<![\w/.-])agentconnect-computer(?= |$)/g, command);
|
|
@@ -110479,7 +110518,10 @@ async function doctor(ctx, captureTimeoutMs = 15e3) {
|
|
|
110479
110518
|
status: "ok",
|
|
110480
110519
|
detail: `${driver.version ?? CUA_DRIVER_VERSION}: ${size.content.find((block) => block.text)?.text ?? "started"}`
|
|
110481
110520
|
});
|
|
110482
|
-
if (process.platform === "darwin")
|
|
110521
|
+
if (process.platform === "darwin")
|
|
110522
|
+
checks.push(
|
|
110523
|
+
...macPermissionChecks(await macPermissions(driver), appBundlePath() ? readGrantRecord(ctx.dir) : void 0)
|
|
110524
|
+
);
|
|
110483
110525
|
const shot = await driver.call("get_desktop_state", { max_image_dimension: 320 }).then(() => void 0).catch((error62) => error62.message);
|
|
110484
110526
|
checks.push(
|
|
110485
110527
|
shot ? { name: "screenshot", status: "fail", detail: shot.slice(0, 160), fix: permissionFix() } : { name: "screenshot", status: "ok", detail: "desktop capture works" }
|
|
@@ -110572,22 +110614,40 @@ function sessionCheck() {
|
|
|
110572
110614
|
};
|
|
110573
110615
|
}
|
|
110574
110616
|
var permissionFix = () => process.platform === "darwin" ? `grant ${hostApp()} the macOS permissions: ${selfCommand()} setup` : void 0;
|
|
110575
|
-
|
|
110576
|
-
|
|
110577
|
-
|
|
110578
|
-
|
|
110579
|
-
|
|
110580
|
-
|
|
110581
|
-
|
|
110582
|
-
|
|
110583
|
-
|
|
110584
|
-
|
|
110585
|
-
|
|
110586
|
-
|
|
110617
|
+
var PANE = {
|
|
110618
|
+
accessibility: "Privacy & Security > Accessibility (macOS 27: Device Control and Data Access)",
|
|
110619
|
+
screenRecording: "Privacy & Security > Screen & System Audio Recording"
|
|
110620
|
+
};
|
|
110621
|
+
function macPermissionChecks(state, record2, app = hostApp()) {
|
|
110622
|
+
const inApp = Boolean(record2);
|
|
110623
|
+
const reopen = inApp ? `then: ${selfCommand()} service restart` : "then Cmd+Q and reopen";
|
|
110624
|
+
const check2 = (grant, name, effect) => {
|
|
110625
|
+
if (state[grant]) return { name, status: "ok", detail: `granted to ${app}` };
|
|
110626
|
+
const loss = record2 ? grantLoss(record2, grant) : "never-asked";
|
|
110627
|
+
if (loss === "lost")
|
|
110628
|
+
return {
|
|
110629
|
+
name,
|
|
110630
|
+
status: "fail",
|
|
110631
|
+
detail: `was granted to ${app} but is not in effect: switched off, or held by an earlier build of the app${effect}`,
|
|
110632
|
+
fix: `run: tccutil reset ${TCC_SERVICE[grant]} ${APP_ID}, then: ${selfCommand()} setup`
|
|
110633
|
+
};
|
|
110634
|
+
if (loss === "listed-off")
|
|
110635
|
+
return {
|
|
110636
|
+
name,
|
|
110637
|
+
status: "fail",
|
|
110638
|
+
detail: `${app} is listed in ${PANE[grant]} but switched off${effect}`,
|
|
110639
|
+
fix: `switch ${app} on there (listed is not enough), ${reopen}`
|
|
110640
|
+
};
|
|
110641
|
+
return {
|
|
110642
|
+
name,
|
|
110587
110643
|
status: "fail",
|
|
110588
|
-
detail: `not granted to ${app}`,
|
|
110589
|
-
fix:
|
|
110590
|
-
}
|
|
110644
|
+
detail: `not granted to ${app}${effect}`,
|
|
110645
|
+
fix: inApp ? `run: ${selfCommand()} setup` : `${PANE[grant]}: switch it on, ${reopen}`
|
|
110646
|
+
};
|
|
110647
|
+
};
|
|
110648
|
+
return [
|
|
110649
|
+
check2("accessibility", "accessibility", ": clicks and keys are silently dropped while pointer moves still work"),
|
|
110650
|
+
check2("screenRecording", "screen recording", "")
|
|
110591
110651
|
];
|
|
110592
110652
|
}
|
|
110593
110653
|
function displayCheck(config2) {
|
|
@@ -110751,7 +110811,7 @@ async function setupLinux(ctx) {
|
|
|
110751
110811
|
if (gnomeWayland()) {
|
|
110752
110812
|
const state = gnomeHelperState();
|
|
110753
110813
|
if (state === "missing") {
|
|
110754
|
-
const installer =
|
|
110814
|
+
const installer = join11(dirname2(ctx.driverCommand), "wayland-helper", "install.sh");
|
|
110755
110815
|
const result = spawnSync8(installer, [], { encoding: "utf8" });
|
|
110756
110816
|
ctx.log(
|
|
110757
110817
|
result.status === 0 ? `Installed the ${GNOME_HELPER} GNOME extension.` : `Extension install failed: ${result.stderr}`
|
|
@@ -110783,6 +110843,9 @@ async function setupMac(ctx) {
|
|
|
110783
110843
|
return;
|
|
110784
110844
|
}
|
|
110785
110845
|
const app = hostApp();
|
|
110846
|
+
ctx.log(
|
|
110847
|
+
`macOS grants these permissions to ${app}, which runs this command; \`agentconnect computer install\` installs the AgentConnect Computer app to hold them instead.`
|
|
110848
|
+
);
|
|
110786
110849
|
const driver = new CuaDriver({ command: ctx.driverCommand, callTimeoutMs: 18e4 });
|
|
110787
110850
|
try {
|
|
110788
110851
|
let state = await macPermissions(driver);
|
|
@@ -111024,7 +111087,9 @@ async function main() {
|
|
|
111024
111087
|
callTimeoutMs: values.prompt ? 18e4 : 2e4
|
|
111025
111088
|
});
|
|
111026
111089
|
try {
|
|
111027
|
-
|
|
111090
|
+
const state = await macPermissions(driver, Boolean(values.prompt));
|
|
111091
|
+
if (appBundlePath()) recordGrants(dir, state, Boolean(values.prompt));
|
|
111092
|
+
console.log(JSON.stringify(state));
|
|
111028
111093
|
} finally {
|
|
111029
111094
|
await driver.close();
|
|
111030
111095
|
}
|
|
@@ -111053,7 +111118,7 @@ function printClientSetup(config2, client, token, tokenFile) {
|
|
|
111053
111118
|
let shown = token;
|
|
111054
111119
|
if (tokenFile) {
|
|
111055
111120
|
rmSync7(tokenFile, { force: true });
|
|
111056
|
-
|
|
111121
|
+
writeFileSync10(tokenFile, `${token}
|
|
111057
111122
|
`, { mode: 384, flag: "wx" });
|
|
111058
111123
|
shown = "<token>";
|
|
111059
111124
|
console.log(`
|
|
@@ -111111,7 +111176,7 @@ function buildComputer(dir, config2, onStatus, refusal, report, screenshots, nam
|
|
|
111111
111176
|
const computer = new Computer({
|
|
111112
111177
|
driver,
|
|
111113
111178
|
config: config2,
|
|
111114
|
-
audit: auditSinks(fileAuditSink(
|
|
111179
|
+
audit: auditSinks(fileAuditSink(join12(dir, "audit.log")), ...report ? [report] : []),
|
|
111115
111180
|
log: log39,
|
|
111116
111181
|
...onStatus ? { onStatus } : {},
|
|
111117
111182
|
...refusal ? { refusal } : {},
|
|
@@ -111335,7 +111400,7 @@ async function run2(dir, initial, clientName) {
|
|
|
111335
111400
|
const issued = issueClientToken(config2, clientName);
|
|
111336
111401
|
config2 = issued.config;
|
|
111337
111402
|
saveConfig(config2, dir);
|
|
111338
|
-
printClientSetup(config2, clientName, issued.token, process.stdout.isTTY ? void 0 :
|
|
111403
|
+
printClientSetup(config2, clientName, issued.token, process.stdout.isTTY ? void 0 : join12(dir, "client-token"));
|
|
111339
111404
|
}
|
|
111340
111405
|
if (!config2.driverPath && !existsSync6(installedDriverPath(dir))) await installDriver(dir);
|
|
111341
111406
|
const setTitle = (status) => {
|
|
@@ -111391,6 +111456,7 @@ async function run2(dir, initial, clientName) {
|
|
|
111391
111456
|
if (process.platform === "darwin") await warnMissingMacPermissions(driver);
|
|
111392
111457
|
if (process.platform === "darwin" && appBundlePath() && !process.stdin.isTTY) {
|
|
111393
111458
|
const state = await macPermissions(driver).catch(() => void 0);
|
|
111459
|
+
if (state) recordGrants(dir, state, false);
|
|
111394
111460
|
if (state && (!state.accessibility || !state.screenRecording)) {
|
|
111395
111461
|
void runSelf(["permissions", "--json", "--prompt"], 2e5).catch(() => void 0);
|
|
111396
111462
|
}
|
|
@@ -111528,17 +111594,18 @@ async function waitForDaemon(config2, timeoutMs) {
|
|
|
111528
111594
|
}
|
|
111529
111595
|
return false;
|
|
111530
111596
|
}
|
|
111597
|
+
var macServiceProgram = () => [process.execPath, serviceEntry()];
|
|
111531
111598
|
async function setupMacApp(dir, config2) {
|
|
111532
111599
|
const app = basename2(appBundlePath());
|
|
111533
|
-
if (!
|
|
111534
|
-
|
|
111535
|
-
`
|
|
111600
|
+
if (!installService2(macServiceProgram(), join12(dir, "daemon.log"))) {
|
|
111601
|
+
throw new Error(
|
|
111602
|
+
`installed LaunchAgent ${plistPath()}, but nobody is logged in to this Mac's desktop: log in (in person or through Screen Sharing) to answer the permission prompts, then run: ${selfCommand()} setup`
|
|
111536
111603
|
);
|
|
111537
111604
|
}
|
|
111538
|
-
installService2(process.execPath, join11(dir, "daemon.log"));
|
|
111539
111605
|
console.log(`Installed LaunchAgent ${plistPath()} and started ${app}.`);
|
|
111606
|
+
if (overSsh()) console.log(`The permission prompts appear on the Mac's own screen; answer them there.`);
|
|
111540
111607
|
if (!await waitForDaemon(config2, 6e4))
|
|
111541
|
-
throw new Error(`the service did not come up; see ${
|
|
111608
|
+
throw new Error(`the service did not come up; see ${join12(dir, "daemon.log")}`);
|
|
111542
111609
|
const grants = async (prompt) => await (await controlFetch(config2, `/control/permissions${prompt ? "?prompt=1" : ""}`, 21e4)).json();
|
|
111543
111610
|
let state = await grants(false);
|
|
111544
111611
|
const initiallyMissingScreen = !state.screenRecording;
|
|
@@ -111594,7 +111661,7 @@ function serviceCommand(dir, config2, action) {
|
|
|
111594
111661
|
if (!appBundlePath())
|
|
111595
111662
|
throw new Error("run this from the AgentConnect Computer.app binary (\u2026/Contents/MacOS/agentconnect-computer)");
|
|
111596
111663
|
}
|
|
111597
|
-
if (action === "install") installService2(
|
|
111664
|
+
if (action === "install") installService2(macServiceProgram(), join12(dir, "daemon.log"));
|
|
111598
111665
|
else if (action === "uninstall") uninstallService2();
|
|
111599
111666
|
else if (action === "start") startService2();
|
|
111600
111667
|
else if (action === "stop") stopService2();
|
|
@@ -111603,7 +111670,7 @@ function serviceCommand(dir, config2, action) {
|
|
|
111603
111670
|
const status = serviceStatus2();
|
|
111604
111671
|
console.log(
|
|
111605
111672
|
JSON.stringify(
|
|
111606
|
-
{ ...status, plist: plistPath(), log:
|
|
111673
|
+
{ ...status, plist: plistPath(), log: join12(dir, "daemon.log"), port: config2.port, app: hostApp() },
|
|
111607
111674
|
null,
|
|
111608
111675
|
2
|
|
111609
111676
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentconnect.md/computer",
|
|
3
|
-
"version": "2.6.0
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "AgentConnect computer daemon: drives a desktop through Cua Driver and serves the computer_* tools over MCP; installed and upgraded by agentconnect computer",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"@types/ws": "^8.18.2",
|
|
23
23
|
"esbuild": "0.28.2",
|
|
24
24
|
"json": "^11.0.0",
|
|
25
|
-
"postject": "1.0.0-alpha.6",
|
|
26
25
|
"tsx": "^4.23.15",
|
|
27
26
|
"typescript": "^6.0.3",
|
|
28
27
|
"vitest": "^5.0.5"
|
|
@@ -39,6 +38,7 @@
|
|
|
39
38
|
"publishConfig": {
|
|
40
39
|
"access": "public"
|
|
41
40
|
},
|
|
41
|
+
"optionalDependencies": {},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "pnpm --filter \"{.}...\" build:package",
|
|
44
44
|
"build:package": "node scripts/bundle.mjs --dist",
|