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.
|
|
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.
|
|
44
|
-
"@pairling/runtime-darwin-x64": "0.
|
|
43
|
+
"@pairling/runtime-darwin-arm64": "0.2.0",
|
|
44
|
+
"@pairling/runtime-darwin-x64": "0.2.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
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": "
|
|
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": "
|
|
247
|
-
"label": "Start Pairling
|
|
248
|
-
"message": "Run
|
|
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": "
|
|
252
|
-
"label": "Install Pairling
|
|
253
|
-
"message": "
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
"
|
|
521
|
-
|
|
521
|
+
"connectd_signature",
|
|
522
|
+
signed_ok,
|
|
522
523
|
"warning",
|
|
523
|
-
"
|
|
524
|
-
{"
|
|
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("
|
|
528
|
+
release_blockers.append("Staged pairling-connectd is not present; run pairling setup.")
|
|
531
529
|
add(
|
|
532
|
-
"
|
|
530
|
+
"connectd_signature",
|
|
533
531
|
False,
|
|
534
532
|
"warning",
|
|
535
|
-
"
|
|
536
|
-
{"
|
|
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.
|
|
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 "$
|
|
632
|
+
--daemon-python "$daemon_python" \
|
|
567
633
|
--guardian-python "$GUARDIAN_PYTHON_BIN"
|
|
568
634
|
}
|
|
569
635
|
|
package/payload-manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"connectd": {
|
|
3
3
|
"darwin-arm64": {
|
|
4
|
-
"sha256": "
|
|
4
|
+
"sha256": "369fc39855b2ddbd5e5470a2026908e7914dc5190a8762de5d39d36d00255347",
|
|
5
5
|
"team_id": "965AVD34A3"
|
|
6
6
|
},
|
|
7
7
|
"darwin-x64": {
|
|
8
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
227
|
+
"sha256": "167309444030de8edd2f1010b65f6a6079371f1fd2a234861d2bc5a115b1743e"
|
|
228
228
|
},
|
|
229
229
|
{
|
|
230
230
|
"path": "payload/mac/install/install-runtime.sh",
|
|
231
|
-
"sha256": "
|
|
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.
|
|
251
|
+
"package_version": "0.2.0",
|
|
252
252
|
"schema_version": 1,
|
|
253
253
|
"source_dirty": false,
|
|
254
|
-
"source_revision": "
|
|
254
|
+
"source_revision": "58288e6"
|
|
255
255
|
}
|