@instaclaw/dispatch 0.1.0 → 0.1.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.
package/dist/index.js CHANGED
@@ -7,21 +7,44 @@
7
7
  * npx @instaclaw/dispatch --token XXX --vm HOST # Direct connect
8
8
  */
9
9
  import { loadConfig, saveConfig, getConfigPath } from "./config.js";
10
- import { checkPermissions, printPermissionGuide } from "./permissions.js";
10
+ import { checkPermissions, printPermissionGuide, runPermissionTest } from "./permissions.js";
11
11
  import { connect, disconnect } from "./connection.js";
12
12
  import { closeSupervisor } from "./supervisor.js";
13
13
  import readline from "readline";
14
+ import os from "os";
14
15
  async function main() {
15
16
  console.log(`
16
17
  InstaClaw Dispatch — Remote Computer Control
17
18
  ─────────────────────────────────────────────
18
19
  `);
19
- // 1. Check/request permissions
20
- const perms = checkPermissions();
21
- if (!perms.accessibility || !perms.screenRecording) {
20
+ // 1. Check permissions (macOS only)
21
+ if (os.platform() === "darwin") {
22
22
  console.log(" Checking permissions...");
23
- printPermissionGuide();
24
- await waitForEnter();
23
+ const perms = checkPermissions();
24
+ printPermissionGuide(perms);
25
+ if (perms.screenRecording === false || perms.accessibility === false) {
26
+ await waitForEnter();
27
+ // Re-check after user grants
28
+ const recheck = checkPermissions();
29
+ if (recheck.screenRecording === false) {
30
+ console.log(" Warning: Screen Recording still not granted. Screenshots may fail.\n");
31
+ }
32
+ if (recheck.accessibility === false) {
33
+ console.log(" Warning: Accessibility still not granted. Click/type may fail.\n");
34
+ }
35
+ }
36
+ else {
37
+ // Run a functional test to be sure
38
+ console.log(" Running permission test...");
39
+ const test = await runPermissionTest();
40
+ if (test.ok) {
41
+ console.log(" Permissions: all OK\n");
42
+ }
43
+ else {
44
+ console.log(` Warning: ${test.error}`);
45
+ console.log(" Continuing anyway — some commands may fail.\n");
46
+ }
47
+ }
25
48
  }
26
49
  // 2. Load or create config
27
50
  let config = loadConfig();
@@ -1,8 +1,16 @@
1
1
  interface PermissionStatus {
2
- accessibility: boolean;
3
- screenRecording: boolean;
2
+ accessibility: boolean | "unknown";
3
+ screenRecording: boolean | "unknown";
4
4
  platform: string;
5
5
  }
6
6
  export declare function checkPermissions(): PermissionStatus;
7
- export declare function printPermissionGuide(): void;
7
+ /**
8
+ * Run a quick functional test: take a screenshot via usecomputer.
9
+ * If it fails, permissions are likely missing.
10
+ */
11
+ export declare function runPermissionTest(): Promise<{
12
+ ok: boolean;
13
+ error?: string;
14
+ }>;
15
+ export declare function printPermissionGuide(status: PermissionStatus): void;
8
16
  export {};
@@ -1,44 +1,118 @@
1
+ import { execSync } from "child_process";
1
2
  import os from "os";
3
+ import fs from "fs";
4
+ import path from "path";
2
5
  export function checkPermissions() {
3
6
  const platform = os.platform();
4
7
  if (platform === "darwin") {
5
8
  return checkMacPermissions();
6
9
  }
7
- // Linux/Windows — no special permissions needed for X11
10
+ // Linux/Windows — no special permissions needed
8
11
  return { accessibility: true, screenRecording: true, platform };
9
12
  }
10
13
  function checkMacPermissions() {
11
- let accessibility = false;
12
- let screenRecording = false;
13
- // Check accessibility: try to create a CGEvent (will fail without permission)
14
- // We can't check directly from Node — usecomputer will fail at runtime if missing
15
- // For now, assume true and catch errors at execution time
16
- accessibility = true; // Will validate on first command
17
- // Screen recording: same — can't check from Node without native module
18
- screenRecording = true;
14
+ let accessibility = "unknown";
15
+ let screenRecording = "unknown";
16
+ // Check Accessibility via TCC database
17
+ // macOS stores permission grants in ~/Library/Application Support/com.apple.TCC/TCC.db
18
+ // and /Library/Application Support/com.apple.TCC/TCC.db (system)
19
+ // We can try a quick test: usecomputer mouse position (fails without accessibility)
20
+ try {
21
+ const result = execSync('osascript -e \'tell application "System Events" to get name of first process\'', { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
22
+ accessibility = true;
23
+ }
24
+ catch {
25
+ // AppleScript fails without Accessibility permission
26
+ accessibility = false;
27
+ }
28
+ // Check Screen Recording by attempting a tiny screenshot
29
+ // CGWindowListCreateImage returns a null/empty image without Screen Recording permission
30
+ try {
31
+ const tmpFile = path.join(os.tmpdir(), `dispatch-perm-check-${Date.now()}.png`);
32
+ execSync(`screencapture -x -t png "${tmpFile}" 2>/dev/null`, { timeout: 5000 });
33
+ if (fs.existsSync(tmpFile)) {
34
+ const size = fs.statSync(tmpFile).size;
35
+ fs.unlinkSync(tmpFile);
36
+ // A valid screenshot is at least a few KB. A blocked capture may produce a tiny or empty file.
37
+ screenRecording = size > 500;
38
+ }
39
+ else {
40
+ screenRecording = false;
41
+ }
42
+ }
43
+ catch {
44
+ screenRecording = false;
45
+ }
19
46
  return { accessibility, screenRecording, platform: "darwin" };
20
47
  }
21
- export function printPermissionGuide() {
22
- const platform = os.platform();
48
+ /**
49
+ * Run a quick functional test: take a screenshot via usecomputer.
50
+ * If it fails, permissions are likely missing.
51
+ */
52
+ export async function runPermissionTest() {
53
+ try {
54
+ const uc = await import("usecomputer");
55
+ const tmpPath = path.join(os.tmpdir(), `dispatch-test-${Date.now()}.png`);
56
+ await uc.screenshot({ path: tmpPath, display: null, window: null, region: null, annotate: null });
57
+ if (fs.existsSync(tmpPath)) {
58
+ const size = fs.statSync(tmpPath).size;
59
+ fs.unlinkSync(tmpPath);
60
+ if (size > 500)
61
+ return { ok: true };
62
+ return { ok: false, error: "Screenshot captured but empty — Screen Recording permission may be missing" };
63
+ }
64
+ return { ok: false, error: "Screenshot file not created" };
65
+ }
66
+ catch (err) {
67
+ const msg = String(err);
68
+ if (msg.includes("EVENT_POST_FAILED") || msg.includes("CGEvent")) {
69
+ return { ok: false, error: "Accessibility permission not granted" };
70
+ }
71
+ if (msg.includes("CAPTURE_FAILED")) {
72
+ return { ok: false, error: "Screen Recording permission not granted" };
73
+ }
74
+ return { ok: false, error: msg.slice(0, 100) };
75
+ }
76
+ }
77
+ export function printPermissionGuide(status) {
78
+ const platform = status.platform;
23
79
  if (platform === "darwin") {
24
- console.log(`
25
- To grant permissions (macOS):
26
- 1. System Settings → Privacy & Security → Accessibility
27
- → Add your terminal app (Terminal, iTerm, Warp, etc.)
28
- 2. System Settings → Privacy & Security → Screen Recording
29
- → Add your terminal app
30
-
31
- Press Enter after granting permissions...`);
80
+ const terminal = process.env.TERM_PROGRAM || "your terminal app";
81
+ const terminalName = {
82
+ "Apple_Terminal": "Terminal",
83
+ "iTerm.app": "iTerm2",
84
+ "vscode": "Visual Studio Code",
85
+ "WarpTerminal": "Warp",
86
+ }[terminal] || terminal;
87
+ console.log("");
88
+ if (status.screenRecording === false) {
89
+ console.log(` Screen Recording: NOT GRANTED`);
90
+ console.log(` → System Settings → Privacy & Security → Screen Recording`);
91
+ console.log(` → Enable "${terminalName}"`);
92
+ console.log(` → You may need to restart ${terminalName} after granting`);
93
+ console.log("");
94
+ }
95
+ else {
96
+ console.log(` Screen Recording: OK`);
97
+ }
98
+ if (status.accessibility === false) {
99
+ console.log(` Accessibility: NOT GRANTED`);
100
+ console.log(` → System Settings → Privacy & Security → Accessibility`);
101
+ console.log(` → Enable "${terminalName}"`);
102
+ console.log("");
103
+ }
104
+ else {
105
+ console.log(` Accessibility: OK`);
106
+ }
107
+ if (status.screenRecording === false || status.accessibility === false) {
108
+ console.log(` After granting permissions, press Enter to continue...`);
109
+ }
32
110
  }
33
111
  else if (platform === "linux") {
34
- console.log(`
35
- Linux: No special permissions needed.
36
- Ensure DISPLAY is set if using X11.`);
112
+ console.log(` Linux: No special permissions needed.`);
37
113
  }
38
114
  else {
39
- console.log(`
40
- Windows: No special permissions needed.
41
- Run as administrator if controlling elevated apps.`);
115
+ console.log(` Windows: No special permissions needed.`);
42
116
  }
43
117
  }
44
118
  //# sourceMappingURL=permissions.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instaclaw/dispatch",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Let your InstaClaw AI agent control your computer — screenshots, clicks, typing, and more via a secure WebSocket relay",
5
5
  "bin": {
6
6
  "instaclaw-dispatch": "./dist/index.js"