@jian-ai/remote-control 0.8.0 → 0.8.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/README.md CHANGED
@@ -43,4 +43,6 @@ jian-remote configure --gateway https://gateway.example --token WORKER_TOKEN
43
43
 
44
44
  Worker and gateway must share the same major.minor. See [docs/remote.md](../../docs/remote.md).
45
45
 
46
- Requires Node.js 24+.
46
+ Requires Node.js 24+. Published builds include a prebuilt macOS `computer-helper` for Computer
47
+ Use (no Xcode on the worker Mac). From source, run `macos/build-helper.sh` on a Mac, or let the
48
+ worker compile with `swiftc` when Command Line Tools are installed.
@@ -75,22 +75,78 @@ export function capturePrimaryScreenshotBase64() {
75
75
  return { base64, widthPx, heightPx };
76
76
  }
77
77
 
78
- function helperPaths() {
79
- const support = join(homedir(), 'Library', 'Application Support', 'Jian Remote');
78
+ function helperPaths(home = homedir()) {
79
+ const support = join(home, 'Library', 'Application Support', 'Jian Remote');
80
80
  return {
81
81
  support,
82
82
  source: join(support, `${HELPER_NAME}.swift`),
83
83
  binary: join(support, HELPER_NAME),
84
+ stamp: join(support, `${HELPER_NAME}.sha256`),
85
+ };
86
+ }
87
+
88
+ /** Paths inside the installed npm package (or a checkout). */
89
+ export function packageHelperPaths(
90
+ packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..'),
91
+ ) {
92
+ return {
93
+ swift: join(packageRoot, 'macos', `${HELPER_NAME}.swift`),
94
+ binary: join(packageRoot, 'macos', HELPER_NAME),
84
95
  };
85
96
  }
86
97
 
87
98
  function packageSwiftPath() {
88
- return join(dirname(fileURLToPath(import.meta.url)), '..', 'macos', `${HELPER_NAME}.swift`);
99
+ return packageHelperPaths().swift;
89
100
  }
90
101
 
102
+ function packageBinaryPath() {
103
+ return packageHelperPaths().binary;
104
+ }
105
+
106
+ function stampForBytes(bytes) {
107
+ return createHash('sha256').update(bytes).digest('hex').slice(0, 16);
108
+ }
109
+
110
+ /** Drop download quarantine so a helper from npm can exec under the LaunchAgent. */
111
+ function clearQuarantine(path) {
112
+ spawnSync('xattr', ['-d', 'com.apple.quarantine', path], { encoding: 'utf8' });
113
+ }
114
+
115
+ /**
116
+ * Prefer a Publish-built universal binary from the npm package; otherwise compile with swiftc.
117
+ * Stamp is over the installed artifact bytes so updates replace a stale helper.
118
+ */
91
119
  async function ensureHelperBinary() {
92
120
  const paths = helperPaths();
93
121
  await mkdir(paths.support, { recursive: true, mode: 0o755 });
122
+
123
+ let previous = '';
124
+ try {
125
+ previous = (await readFile(paths.stamp, 'utf8')).trim();
126
+ } catch {
127
+ previous = '';
128
+ }
129
+
130
+ const packagedBinary = packageBinaryPath();
131
+ if (existsSync(packagedBinary)) {
132
+ const bytes = await readFile(packagedBinary);
133
+ const hash = `bin:${stampForBytes(bytes)}`;
134
+ if (previous !== hash || !existsSync(paths.binary)) {
135
+ await copyFile(packagedBinary, paths.binary);
136
+ await chmod(paths.binary, 0o755);
137
+ clearQuarantine(paths.binary);
138
+ await writeFile(paths.stamp, `${hash}\n`, { mode: 0o644 });
139
+ }
140
+ // Keep source nearby for debugging / swiftc fallback after a partial install.
141
+ try {
142
+ const swift = packageSwiftPath();
143
+ if (existsSync(swift)) await copyFile(swift, paths.source);
144
+ } catch {
145
+ // optional
146
+ }
147
+ return paths.binary;
148
+ }
149
+
94
150
  let sourceText;
95
151
  try {
96
152
  sourceText = await readFile(packageSwiftPath(), 'utf8');
@@ -99,18 +155,11 @@ async function ensureHelperBinary() {
99
155
  sourceText = await readFile(paths.source, 'utf8');
100
156
  } catch {
101
157
  throw new Error(
102
- 'Computer-use helper source is missing. Reinstall or update @jian-ai/remote-control.',
158
+ 'Computer-use helper is missing from this install. Reinstall or update @jian-ai/remote-control.',
103
159
  );
104
160
  }
105
161
  }
106
- const hash = createHash('sha256').update(sourceText).digest('hex').slice(0, 16);
107
- const stamp = join(paths.support, `${HELPER_NAME}.sha256`);
108
- let previous = '';
109
- try {
110
- previous = (await readFile(stamp, 'utf8')).trim();
111
- } catch {
112
- previous = '';
113
- }
162
+ const hash = `src:${stampForBytes(Buffer.from(sourceText, 'utf8'))}`;
114
163
  const needsBuild = previous !== hash || !existsSync(paths.binary);
115
164
  await writeFile(paths.source, sourceText, { mode: 0o644 });
116
165
  if (needsBuild) {
@@ -122,27 +171,44 @@ async function ensureHelperBinary() {
122
171
  if (compiled.status !== 0) {
123
172
  throw new Error(
124
173
  (compiled.stderr || compiled.stdout || 'swiftc failed').trim() ||
125
- 'Could not build the Jian computer-use helper. Install Xcode Command Line Tools.',
174
+ 'Could not build the Jian computer-use helper. Published packages ship a prebuilt helper; for source builds install Xcode Command Line Tools, or reinstall @jian-ai/remote-control.',
126
175
  );
127
176
  }
128
177
  await chmod(paths.binary, 0o755);
129
- await writeFile(stamp, `${hash}\n`, { mode: 0o644 });
178
+ clearQuarantine(paths.binary);
179
+ await writeFile(paths.stamp, `${hash}\n`, { mode: 0o644 });
130
180
  }
131
181
  return paths.binary;
132
182
  }
133
183
 
134
- /** Copy Swift sources next to the installed worker when present in the npm package. */
184
+ /** Copy Swift source and any prebuilt helper next to the installed worker. */
135
185
  export async function materializeComputerHelperSources(configDir) {
136
186
  if (process.platform !== 'darwin') return;
137
187
  try {
138
- const from = packageSwiftPath();
139
- if (!existsSync(from)) return;
188
+ const pkg = packageHelperPaths();
189
+ if (!existsSync(pkg.swift) && !existsSync(pkg.binary)) return;
140
190
  const support = join(homedir(), 'Library', 'Application Support', 'Jian Remote');
141
191
  await mkdir(support, { recursive: true, mode: 0o755 });
142
- await copyFile(from, join(support, `${HELPER_NAME}.swift`));
143
192
  const localMacos = join(configDir, 'macos');
144
193
  await mkdir(localMacos, { recursive: true, mode: 0o755 });
145
- await copyFile(from, join(localMacos, `${HELPER_NAME}.swift`));
194
+ if (existsSync(pkg.swift)) {
195
+ await copyFile(pkg.swift, join(support, `${HELPER_NAME}.swift`));
196
+ await copyFile(pkg.swift, join(localMacos, `${HELPER_NAME}.swift`));
197
+ }
198
+ if (existsSync(pkg.binary)) {
199
+ const destSupport = join(support, HELPER_NAME);
200
+ const destLocal = join(localMacos, HELPER_NAME);
201
+ await copyFile(pkg.binary, destSupport);
202
+ await copyFile(pkg.binary, destLocal);
203
+ await chmod(destSupport, 0o755);
204
+ await chmod(destLocal, 0o755);
205
+ clearQuarantine(destSupport);
206
+ clearQuarantine(destLocal);
207
+ const bytes = await readFile(pkg.binary);
208
+ await writeFile(join(support, `${HELPER_NAME}.sha256`), `bin:${stampForBytes(bytes)}\n`, {
209
+ mode: 0o644,
210
+ });
211
+ }
146
212
  } catch {
147
213
  // Best-effort on install; computer session will surface a clear error.
148
214
  }
@@ -21,7 +21,7 @@ const option = (name) => {
21
21
  * Stamped to the release tag on npm publish and when the gateway serves this script.
22
22
  * Compatibility with the gateway requires the same major.minor (see versionsCompatible).
23
23
  */
24
- export const PACKAGE_VERSION = '0.8.0';
24
+ export const PACKAGE_VERSION = '0.8.1';
25
25
 
26
26
  /** Soft stdout/stderr caps while capturing a command (chars). Gateway schema matches these. */
27
27
  const STDOUT_CAP = 400_000;
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bash
2
+ # Build a universal (arm64 + x86_64) computer-helper for the npm package.
3
+ # Run on macOS with Xcode CLT. Used by Publish CI and local packaging.
4
+ set -euo pipefail
5
+
6
+ ROOT="$(cd "$(dirname "$0")" && pwd)"
7
+ SRC="$ROOT/computer-helper.swift"
8
+ OUT="${1:-$ROOT/computer-helper}"
9
+ MIN_OS="${MACOSX_DEPLOYMENT_TARGET:-13.0}"
10
+ TMP="$(mktemp -d "${TMPDIR:-/tmp}/jian-computer-helper.XXXXXX")"
11
+ cleanup() { rm -rf "$TMP"; }
12
+ trap cleanup EXIT
13
+
14
+ if [[ "$(uname -s)" != "Darwin" ]]; then
15
+ echo "build-helper.sh must run on macOS (swiftc + lipo)." >&2
16
+ exit 1
17
+ fi
18
+ if ! command -v swiftc >/dev/null 2>&1; then
19
+ echo "swiftc not found. Install Xcode Command Line Tools." >&2
20
+ exit 1
21
+ fi
22
+ if [[ ! -f "$SRC" ]]; then
23
+ echo "Missing source: $SRC" >&2
24
+ exit 1
25
+ fi
26
+
27
+ swiftc -O \
28
+ -target "arm64-apple-macosx${MIN_OS}" \
29
+ -framework AppKit -framework Carbon \
30
+ -o "$TMP/computer-helper-arm64" "$SRC"
31
+
32
+ swiftc -O \
33
+ -target "x86_64-apple-macosx${MIN_OS}" \
34
+ -framework AppKit -framework Carbon \
35
+ -o "$TMP/computer-helper-x86_64" "$SRC"
36
+
37
+ lipo -create -output "$OUT" "$TMP/computer-helper-arm64" "$TMP/computer-helper-x86_64"
38
+ chmod 755 "$OUT"
39
+
40
+ # Ad-hoc sign so Gatekeeper is quieter for LaunchAgent children; Developer ID overlays this in CI when secrets exist.
41
+ if command -v codesign >/dev/null 2>&1; then
42
+ codesign --force -s - --identifier com.jian.remote.computer-helper "$OUT" 2>/dev/null || true
43
+ fi
44
+
45
+ echo "Built universal helper: $OUT"
46
+ lipo -info "$OUT"
Binary file
@@ -5,8 +5,9 @@
5
5
  Protocol: newline-delimited JSON on stdin → newline-delimited JSON on stdout (flush each).
6
6
  Errors go to stderr in English only.
7
7
 
8
- Build (on a Mac with Xcode CLT):
9
- swiftc -O -framework AppKit -framework Carbon -o computer-helper computer-helper.swift
8
+ Published npm packages ship a prebuilt universal binary (Publish CI). From source on a Mac:
9
+ ./build-helper.sh
10
+ # or: swiftc -O -framework AppKit -framework Carbon -o computer-helper computer-helper.swift
10
11
 
11
12
  Run as a child of the Node worker; keep stdin/stdout pipes open for the session.
12
13
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jian-ai/remote-control",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Jian Remote Control worker — connect a computer to a Jian gateway over HTTPS",
5
5
  "type": "module",
6
6
  "bin": {