pairling 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pairling",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Pair your iPhone with the AI coding agents running on your Mac. CLI and local runtime installer for the Pairling iOS app.",
5
5
  "keywords": [
6
6
  "pairling",
@@ -40,7 +40,7 @@
40
40
  "access": "public"
41
41
  },
42
42
  "optionalDependencies": {
43
- "@pairling/runtime-darwin-arm64": "0.1.0",
44
- "@pairling/runtime-darwin-x64": "0.1.0"
43
+ "@pairling/runtime-darwin-arm64": "0.2.0",
44
+ "@pairling/runtime-darwin-x64": "0.2.0"
45
45
  }
46
46
  }
@@ -1 +1 @@
1
- 35420e4
1
+ 58288e6
@@ -239,18 +239,18 @@ def next_action_for_stage(stage: str, *, remote_status: str, pair_window_open: b
239
239
  return {
240
240
  "id": "open_pairing_invitation",
241
241
  "label": "Open pairing invitation",
242
- "message": "Open a pairing invitation from Pairling Helper, then pair from the iPhone.",
242
+ "message": "Run pairling pair to open a pairing invitation, then pair from the iPhone.",
243
243
  }
244
244
  if stage == "runtime_not_ready":
245
245
  return {
246
- "id": "start_helper",
247
- "label": "Start Pairling Helper",
248
- "message": "Run Pairling Helper setup and review the failing runtime checks.",
246
+ "id": "start_runtime",
247
+ "label": "Start the Pairling runtime",
248
+ "message": "Run pairling setup and review the failing runtime checks.",
249
249
  }
250
250
  return {
251
- "id": "install_helper",
252
- "label": "Install Pairling Helper",
253
- "message": "Install Pairling Helper on this Mac before pairing.",
251
+ "id": "install_cli",
252
+ "label": "Install the Pairling CLI",
253
+ "message": "Run npm install -g pairling then pairling setup on this Mac before pairing.",
254
254
  }
255
255
 
256
256
 
@@ -501,39 +501,73 @@ add(
501
501
  (out or err)[:2000],
502
502
  )
503
503
 
504
- helper_artifact = Path(os.environ.get("PAIRLING_HELPER_ARTIFACT", str(repo_root / "dist" / "PairlingHelper.dmg")))
505
- helper_bundle = APP_SUPPORT / "Pairling Helper.app"
506
- if helper_artifact.exists():
507
- code, out, err = run([
508
- "/usr/sbin/spctl",
509
- "-a",
510
- "-vv",
511
- "--type",
512
- "open",
513
- "--context",
514
- "context:primary-signature",
515
- str(helper_artifact),
516
- ], timeout=8)
517
- if code != 0:
518
- release_blockers.append("Developer ID signed/notarized helper DMG is not Gatekeeper-accepted yet.")
504
+ # npm distribution: the staged pairling-connectd binary must be a valid
505
+ # Developer ID build from the pinned team. This replaces the retired dmg
506
+ # Gatekeeper check; the npm install path never sets com.apple.quarantine, so
507
+ # Gatekeeper assessment is not in the launch path, but signature + Team ID
508
+ # verification is the integrity equivalent and matches the fail-closed staging
509
+ # gate in install-runtime.sh.
510
+ expected_team = os.environ.get("PAIRLING_CONNECTD_TEAM_ID", "965AVD34A3")
511
+ staged_connectd = CURRENT / "connectd" / "pairling-connectd"
512
+ if staged_connectd.exists():
513
+ vcode, vout, verr = run(["/usr/bin/codesign", "--verify", "--strict", str(staged_connectd)], timeout=8)
514
+ icode, iout, ierr = run(["/usr/bin/codesign", "-dvv", str(staged_connectd)], timeout=8)
515
+ team_line = next((l for l in ((iout or "") + (ierr or "")).splitlines() if l.startswith("TeamIdentifier=")), "")
516
+ team_id = team_line.split("=", 1)[1] if "=" in team_line else ""
517
+ signed_ok = vcode == 0 and (expected_team == "-" or team_id == expected_team)
518
+ if not signed_ok:
519
+ release_blockers.append("Staged pairling-connectd is not a valid Developer ID build from the expected team.")
519
520
  add(
520
- "helper_signing_notarization",
521
- code == 0,
521
+ "connectd_signature",
522
+ signed_ok,
522
523
  "warning",
523
- "Helper DMG Gatekeeper assessment.",
524
- {"artifact": str(helper_artifact), "assessment": (out or err)[:2000]},
524
+ "Staged pairling-connectd passes codesign --verify --strict with the expected Team ID.",
525
+ {"binary": str(staged_connectd), "team_id": team_id or None, "expected_team": expected_team, "verify": (vout or verr)[:1000]},
525
526
  )
526
- elif helper_bundle.exists():
527
- code, out, err = run(["/usr/sbin/spctl", "-a", "-vv", str(helper_bundle)], timeout=8)
528
- add("helper_signing_notarization", code == 0, "warning", "Helper bundle Gatekeeper assessment.", (out or err)[:2000])
529
527
  else:
530
- release_blockers.append("Developer ID signed/notarized helper artifact is not present yet.")
528
+ release_blockers.append("Staged pairling-connectd is not present; run pairling setup.")
531
529
  add(
532
- "helper_signing_notarization",
530
+ "connectd_signature",
533
531
  False,
534
532
  "warning",
535
- "Helper artifact not present; signing/notarization remains a release blocker.",
536
- {"artifact": str(helper_artifact), "bundle": str(helper_bundle)},
533
+ "Staged pairling-connectd not present; signature verification unavailable until pairling setup runs.",
534
+ {"binary": str(staged_connectd)},
535
+ )
536
+
537
+ # P3 Python custody: when a vendored interpreter is staged, it must be a valid
538
+ # Developer ID build from the expected team with the dev.pairling.python
539
+ # identity — that scoping is the whole point (TCC grants attach to Pairling, not
540
+ # a generic python3). When no vendored python is staged (the daemon runs under a
541
+ # system python3), this check is informational, not a blocker.
542
+ expected_python_identifier = os.environ.get("PAIRLING_PYTHON_IDENTIFIER", "dev.pairling.python")
543
+ staged_python = CURRENT / "python" / "bin" / "python3"
544
+ if staged_python.exists():
545
+ pvcode, pvout, pverr = run(["/usr/bin/codesign", "--verify", "--strict", str(staged_python)], timeout=10)
546
+ picode, piout, pierr = run(["/usr/bin/codesign", "-dvv", str(staged_python)], timeout=10)
547
+ pinfo = (piout or "") + (pierr or "")
548
+ p_team = next((l.split("=", 1)[1] for l in pinfo.splitlines() if l.startswith("TeamIdentifier=")), "")
549
+ p_id = next((l.split("=", 1)[1] for l in pinfo.splitlines() if l.startswith("Identifier=")), "")
550
+ python_signed_ok = (
551
+ pvcode == 0
552
+ and (expected_team == "-" or p_team == expected_team)
553
+ and p_id == expected_python_identifier
554
+ )
555
+ if not python_signed_ok:
556
+ release_blockers.append("Staged vendored python is not a valid dev.pairling.python Developer ID build.")
557
+ add(
558
+ "python_runtime",
559
+ python_signed_ok,
560
+ "warning",
561
+ "Staged vendored CPython is signed dev.pairling.python by the expected Team ID.",
562
+ {"python": str(staged_python), "team_id": p_team or None, "identifier": p_id or None, "expected_identifier": expected_python_identifier},
563
+ )
564
+ else:
565
+ add(
566
+ "python_runtime",
567
+ True,
568
+ "warning",
569
+ "No vendored CPython staged; daemon runs under a system python3 (acceptable pre-P3-rollout).",
570
+ {"python": str(staged_python), "vendored": False},
537
571
  )
538
572
 
539
573
  errors = [c for c in checks if c["status"] != "ok" and c["severity"] == "error"]
@@ -34,7 +34,6 @@ PACKAGED_SOURCE_PATHS=(
34
34
  "mac/connectd/go.mod"
35
35
  "mac/connectd/go.sum"
36
36
  "mac/guardian"
37
- "mac/helper-assistant/PairlingHelperAssistant.swift"
38
37
  "mac/install"
39
38
  "mac/mcp"
40
39
  )
@@ -76,6 +75,13 @@ MCP_SERVER_DIR="$HOME/.claude/mcp-servers"
76
75
  MCP_SERVER_SHIM="$MCP_SERVER_DIR/phone-tools.py"
77
76
  PYTHON3_BIN="${PAIRLING_DAEMON_PYTHON:-${COMPANION_DAEMON_PYTHON:-$(command -v python3)}}"
78
77
  GUARDIAN_PYTHON_BIN="${PAIRLING_GUARDIAN_PYTHON:-${COMPANION_GUARDIAN_PYTHON:-/usr/bin/python3}}"
78
+ # P3 Python custody: the npm shim points PAIRLING_DAEMON_PYTHON at the vendored
79
+ # CPython inside the platform runtime package (…/python/bin/python3). When that
80
+ # is in play we stage the whole interpreter into the release tree and run the
81
+ # daemon under it, so a Pairling-signed python (identity dev.pairling.python),
82
+ # not a generic system python3, owns the daemon's TCC grants — and npm churn
83
+ # can't remove the running interpreter.
84
+ PYTHON_CODESIGN_IDENTIFIER="dev.pairling.python"
79
85
  DRY_RUN="${PAIRLING_DRY_RUN:-0}"
80
86
 
81
87
  log() {
@@ -263,6 +269,7 @@ copy_release() {
263
269
  cp "$REPO_ROOT/mac/guardian/companion-power-guardian.py" "$tmp/guardian/"
264
270
  cp "$REPO_ROOT/mac/guardian/guardian_contract.py" "$tmp/guardian/"
265
271
  build_connectd_binary "$tmp/connectd/pairling-connectd"
272
+ stage_vendored_python "$tmp/python"
266
273
  copy_runtime_source_tree "$tmp/mac" "$tmp/connectd/pairling-connectd"
267
274
  write_installed_pairling_launcher "$tmp/bin/pairling"
268
275
  chmod 755 "$tmp/bin/pairling" "$tmp/companiond/pairlingd.py" "$tmp/mcp/phone_tools.py" "$tmp/guardian/companion-power-guardian.py"
@@ -322,6 +329,59 @@ exec "$ROOT/mac/packaging/bin/pairling" "$@"
322
329
  SH
323
330
  }
324
331
 
332
+ # Stage the vendored CPython (P3 custody) into the release tree when the npm
333
+ # shim provided one via PAIRLING_DAEMON_PYTHON pointing at …/python/bin/python3.
334
+ # Fail-closed: the interpreter must carry a valid signature, the pinned Team ID,
335
+ # and the dev.pairling.python identifier. On success, repoint PYTHON3_BIN at the
336
+ # STAGED interpreter so the daemon plist never references the npm package path.
337
+ stage_vendored_python() {
338
+ local dest="$1"
339
+ local provided="${PAIRLING_DAEMON_PYTHON:-}"
340
+ # Only act on a vendored interpreter living under a runtime package's python/
341
+ # tree. A bare system python3 (no sibling python/ tree) is left as-is.
342
+ case "$provided" in
343
+ */python/bin/python3) : ;;
344
+ *) return 0 ;;
345
+ esac
346
+ local src_tree
347
+ src_tree="$(cd "$(dirname "$provided")/.." && pwd)"
348
+ if [[ ! -x "$src_tree/bin/python3" ]]; then
349
+ return 0
350
+ fi
351
+ local required_team="${PAIRLING_CONNECTD_TEAM_ID:-965AVD34A3}"
352
+ # Always enforce signature integrity and the dev.pairling.python identity
353
+ # (cert-independent defense in depth). Pin the Apple Team ID unless the dev
354
+ # switch (-) disables that one check for local ad-hoc builds.
355
+ if ! /usr/bin/codesign --verify --strict "$src_tree/bin/python3" >/dev/null 2>&1; then
356
+ log "ERROR: vendored python failed codesign verification; refusing to stage: $src_tree/bin/python3" >&2
357
+ exit 1
358
+ fi
359
+ local team identifier
360
+ identifier="$(/usr/bin/codesign -dvv "$src_tree/bin/python3" 2>&1 | sed -n 's/^Identifier=//p')"
361
+ if [[ "$identifier" != "$PYTHON_CODESIGN_IDENTIFIER" ]]; then
362
+ log "ERROR: vendored python identifier '${identifier:-none}' is not '$PYTHON_CODESIGN_IDENTIFIER'; refusing to stage." >&2
363
+ exit 1
364
+ fi
365
+ if [[ "$required_team" == "-" ]]; then
366
+ log "WARNING: vendored python Team ID pin disabled (PAIRLING_CONNECTD_TEAM_ID=-). Dev builds only."
367
+ else
368
+ team="$(/usr/bin/codesign -dvv "$src_tree/bin/python3" 2>&1 | sed -n 's/^TeamIdentifier=//p')"
369
+ if [[ "$team" != "$required_team" ]]; then
370
+ log "ERROR: vendored python TeamIdentifier '${team:-none}' does not match required '$required_team'; refusing to stage." >&2
371
+ exit 1
372
+ fi
373
+ fi
374
+ rm -rf "$dest"
375
+ mkdir -p "$(dirname "$dest")"
376
+ cp -R "$src_tree" "$dest"
377
+ chmod 755 "$dest/bin/python3" 2>/dev/null || true
378
+ # Point the daemon at the interpreter through the stable `current` symlink
379
+ # (not $dest, which is the pre-move temp path) so the plist resolves after the
380
+ # release is moved into place and after rollback — exactly like connectd.
381
+ PYTHON3_BIN="$CURRENT_LINK/python/bin/python3"
382
+ log "Staged vendored CPython (daemon will run under dev.pairling.python via $PYTHON3_BIN)"
383
+ }
384
+
325
385
  build_connectd_binary() {
326
386
  local out="$1"
327
387
  # npm-delivered binary: the shim points PAIRLING_CONNECTD_PREBUILT at the
@@ -551,7 +611,7 @@ if [[ -x "$RUNTIME_PAIRLING" ]]; then
551
611
  exec "$RUNTIME_PAIRLING" "$@"
552
612
  fi
553
613
 
554
- printf 'Pairling runtime command is not installed. Open Pairling Helper and run Start setup, or use a repo-local mac/packaging/bin/pairling.\n' >&2
614
+ printf 'Pairling runtime command is not installed. Run: npm install -g pairling && pairling setup (or use a repo-local mac/packaging/bin/pairling).\n' >&2
555
615
  exit 127
556
616
  SH
557
617
  chmod 755 "$tmp"
@@ -559,11 +619,17 @@ SH
559
619
  }
560
620
 
561
621
  render_plists() {
622
+ # Prefer the staged vendored interpreter whenever it exists, so start/
623
+ # rollback (which don't re-stage) also run the daemon under dev.pairling.python.
624
+ local daemon_python="$PYTHON3_BIN"
625
+ if [[ -x "$CURRENT_LINK/python/bin/python3" ]]; then
626
+ daemon_python="$CURRENT_LINK/python/bin/python3"
627
+ fi
562
628
  python3 "$REPO_ROOT/mac/install/render-launchd.py" \
563
629
  --current-root "$CURRENT_LINK" \
564
630
  --logs-root "$LOGS_ROOT" \
565
631
  --output-dir "$PLIST_BUILD_DIR" \
566
- --daemon-python "$PYTHON3_BIN" \
632
+ --daemon-python "$daemon_python" \
567
633
  --guardian-python "$GUARDIAN_PYTHON_BIN"
568
634
  }
569
635
 
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "connectd": {
3
3
  "darwin-arm64": {
4
- "sha256": "df87d4a1c2bf2cd439693fdf9defe5905a7914c6b8e7955d5c7dc07c8db24995",
4
+ "sha256": "369fc39855b2ddbd5e5470a2026908e7914dc5190a8762de5d39d36d00255347",
5
5
  "team_id": "965AVD34A3"
6
6
  },
7
7
  "darwin-x64": {
8
- "sha256": "c0e70a4d4ce415988922177c7b3646b9e154b6b131231e2040b400afeb104660",
8
+ "sha256": "6110a1487cd8f9bc8330b9310180bbf4fd5031307466a821bea8073dc847e013",
9
9
  "team_id": "965AVD34A3"
10
10
  }
11
11
  },
@@ -20,7 +20,7 @@
20
20
  },
21
21
  {
22
22
  "path": "payload/mac/SOURCE_REVISION",
23
- "sha256": "3081b56cba3fd9ec092b6aab851dfe6a4fdd2f978c57e07216424058dfa3ab29"
23
+ "sha256": "370a8df0d6204eedc89b94236b02447ca8bf1f2b366e2d34fcc2e48213e60f65"
24
24
  },
25
25
  {
26
26
  "path": "payload/mac/VERSION",
@@ -224,11 +224,11 @@
224
224
  },
225
225
  {
226
226
  "path": "payload/mac/install/doctor.sh",
227
- "sha256": "d893083b40fb7d0e6c90e6c0ce5c40856fe428de8f5d597f529a906217de814c"
227
+ "sha256": "167309444030de8edd2f1010b65f6a6079371f1fd2a234861d2bc5a115b1743e"
228
228
  },
229
229
  {
230
230
  "path": "payload/mac/install/install-runtime.sh",
231
- "sha256": "78119310f756ec573194e1bfd07752705c74d26d6f08098e6ce28c5d0bc6cda7"
231
+ "sha256": "afc0f40ffc08e4c5effef78b1926621ca6e8dd7d2222d3fdecadbf3d703c5362"
232
232
  },
233
233
  {
234
234
  "path": "payload/mac/install/render-launchd.py",
@@ -248,8 +248,8 @@
248
248
  }
249
249
  ],
250
250
  "package": "pairling",
251
- "package_version": "0.1.0",
251
+ "package_version": "0.2.0",
252
252
  "schema_version": 1,
253
253
  "source_dirty": false,
254
- "source_revision": "35420e4"
254
+ "source_revision": "58288e6"
255
255
  }