pairling 0.0.1 → 0.1.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.
Files changed (61) hide show
  1. package/package.json +5 -1
  2. package/payload/mac/SOURCE_BRANCH +1 -0
  3. package/payload/mac/SOURCE_DIRTY +1 -0
  4. package/payload/mac/SOURCE_REVISION +1 -0
  5. package/payload/mac/VERSION +1 -0
  6. package/payload/mac/companiond/integrations/__init__.py +1 -0
  7. package/payload/mac/companiond/integrations/aperture_cli/__init__.py +23 -0
  8. package/payload/mac/companiond/integrations/aperture_cli/launch.py +456 -0
  9. package/payload/mac/companiond/integrations/aperture_cli/status.py +393 -0
  10. package/payload/mac/companiond/live_activity_publisher.py +380 -0
  11. package/payload/mac/companiond/llm_route.py +108 -0
  12. package/payload/mac/companiond/local_mcp_bridge.py +156 -0
  13. package/payload/mac/companiond/model_status_contract.py +101 -0
  14. package/payload/mac/companiond/pairdrop_store.py +920 -0
  15. package/payload/mac/companiond/pairling_connectd_status.py +149 -0
  16. package/payload/mac/companiond/pairling_devices.py +459 -0
  17. package/payload/mac/companiond/pairling_pairing.py +404 -0
  18. package/payload/mac/companiond/pairling_relay_claims.py +232 -0
  19. package/payload/mac/companiond/pairling_tools.py +706 -0
  20. package/payload/mac/companiond/pairlingd.py +18438 -0
  21. package/payload/mac/companiond/providers/__init__.py +1 -0
  22. package/payload/mac/companiond/providers/base.py +255 -0
  23. package/payload/mac/companiond/providers/claude.py +127 -0
  24. package/payload/mac/companiond/providers/codex.py +124 -0
  25. package/payload/mac/companiond/providers/external.py +46 -0
  26. package/payload/mac/companiond/providers/registry.py +70 -0
  27. package/payload/mac/companiond/pty_broker.py +887 -0
  28. package/payload/mac/companiond/push_dispatcher.py +1990 -0
  29. package/payload/mac/companiond/push_event_catalog.py +566 -0
  30. package/payload/mac/companiond/request_proof.py +142 -0
  31. package/payload/mac/companiond/runtime_contract.py +47 -0
  32. package/payload/mac/companiond/runtime_manifest.py +197 -0
  33. package/payload/mac/companiond/runtime_paths.py +87 -0
  34. package/payload/mac/companiond/safety_monitor.py +542 -0
  35. package/payload/mac/companiond/sentinel_notifications.py +491 -0
  36. package/payload/mac/companiond/standard_push_publisher.py +516 -0
  37. package/payload/mac/companiond/substrate_status_contract.py +139 -0
  38. package/payload/mac/companiond/terminal_screen_backend.py +332 -0
  39. package/payload/mac/companiond/terminal_text_sanitizer.py +54 -0
  40. package/payload/mac/companiond/workstate_feed_contract.py +108 -0
  41. package/payload/mac/connectd/cmd/pairling-connectd/auth_open_test.go +116 -0
  42. package/payload/mac/connectd/cmd/pairling-connectd/main.go +345 -0
  43. package/payload/mac/connectd/cmd/pairling-connectd/upstream_health_test.go +33 -0
  44. package/payload/mac/connectd/go.mod +51 -0
  45. package/payload/mac/connectd/go.sum +229 -0
  46. package/payload/mac/connectd/internal/gateway/proxy.go +597 -0
  47. package/payload/mac/connectd/internal/gateway/proxy_test.go +531 -0
  48. package/payload/mac/connectd/internal/runtime/config.go +99 -0
  49. package/payload/mac/connectd/internal/runtime/config_test.go +29 -0
  50. package/payload/mac/connectd/internal/status/status.go +300 -0
  51. package/payload/mac/connectd/internal/status/status_test.go +263 -0
  52. package/payload/mac/guardian/companion-power-guardian.py +613 -0
  53. package/payload/mac/guardian/guardian_contract.py +67 -0
  54. package/payload/mac/install/bootstrap-first-run.sh +206 -0
  55. package/payload/mac/install/doctor.sh +660 -0
  56. package/payload/mac/install/install-runtime.sh +1241 -0
  57. package/payload/mac/install/render-launchd.py +119 -0
  58. package/payload/mac/install/uninstall-runtime.sh +136 -0
  59. package/payload/mac/mcp/phone_tools.py +210 -0
  60. package/payload/mac/packaging/bin/pairling +63 -0
  61. package/payload-manifest.json +255 -0
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5
+ TIMESTAMP="${PAIRLING_FIRST_RUN_TIMESTAMP:-$(date -u +%Y-%m-%dT%H-%M-%SZ)}"
6
+ APP_SUPPORT="${PAIRLING_APP_SUPPORT_ROOT:-${COMPANION_APP_SUPPORT_ROOT:-$HOME/Library/Application Support/Pairling}}"
7
+ ARTIFACT_ROOT="${PAIRLING_FIRST_RUN_ARTIFACT_ROOT:-$APP_SUPPORT/audits/first-run-bootstrap-$TIMESTAMP}"
8
+ TTL="180"
9
+ JSON_MODE="false"
10
+ PLAN_ONLY="false"
11
+ SKIP_PAIR_WINDOW="false"
12
+
13
+ usage() {
14
+ cat <<EOF
15
+ usage: bootstrap-first-run.sh [--json] [--plan-only] [--ttl seconds] [--skip-pairing-invitation]
16
+
17
+ Installs and starts the Pairling Mac runtime, verifies first-run readiness, and
18
+ opens a pairing invitation. It reports required privacy prompts but does
19
+ not pre-grant or reset macOS/iOS privacy permissions.
20
+ EOF
21
+ }
22
+
23
+ while [[ $# -gt 0 ]]; do
24
+ case "$1" in
25
+ --json)
26
+ JSON_MODE="true"
27
+ ;;
28
+ --plan-only)
29
+ PLAN_ONLY="true"
30
+ ;;
31
+ --ttl)
32
+ shift
33
+ TTL="${1:-}"
34
+ if [[ -z "$TTL" ]]; then
35
+ usage >&2
36
+ exit 2
37
+ fi
38
+ ;;
39
+ --skip-pairing-invitation|--skip-pair-window)
40
+ SKIP_PAIR_WINDOW="true"
41
+ ;;
42
+ --help|-h)
43
+ usage
44
+ exit 0
45
+ ;;
46
+ *)
47
+ usage >&2
48
+ exit 2
49
+ ;;
50
+ esac
51
+ shift
52
+ done
53
+
54
+ plan_json() {
55
+ python3 - <<'PY'
56
+ import json
57
+ print(json.dumps({
58
+ "ok": True,
59
+ "schema_version": 1,
60
+ "mode": "plan",
61
+ "steps": [
62
+ "setup_runtime",
63
+ "doctor_first_run",
64
+ "open_pairing_invitation",
65
+ "doctor_first_run_after_pairing_invitation",
66
+ "report_ready_to_pair",
67
+ ],
68
+ "permissions": {
69
+ "mac_automation": "not_required_by_default",
70
+ "mac_accessibility": "not_required_until_terminal_control",
71
+ "ios_local_network": "requires_user_prompt",
72
+ "ios_camera": "requires_user_prompt_for_qr_fallback",
73
+ "tcc_database": "not_modified",
74
+ },
75
+ }, indent=2, sort_keys=True))
76
+ PY
77
+ }
78
+
79
+ if [[ "$PLAN_ONLY" == "true" ]]; then
80
+ if [[ "$JSON_MODE" == "true" ]]; then
81
+ plan_json
82
+ else
83
+ usage
84
+ fi
85
+ exit 0
86
+ fi
87
+
88
+ mkdir -p "$ARTIFACT_ROOT"
89
+
90
+ run_step() {
91
+ local name="$1"
92
+ shift
93
+ local log_file="$ARTIFACT_ROOT/$name.log"
94
+ set +e
95
+ "$@" >"$log_file" 2>&1
96
+ local status=$?
97
+ set -e
98
+ printf '%s' "$status"
99
+ }
100
+
101
+ run_json_step() {
102
+ local name="$1"
103
+ local output_file="$ARTIFACT_ROOT/$name.json"
104
+ shift
105
+ set +e
106
+ "$@" >"$output_file" 2>"$ARTIFACT_ROOT/$name.err"
107
+ local status=$?
108
+ set -e
109
+ printf '%s' "$status"
110
+ }
111
+
112
+ setup_status="$(run_step setup "$REPO_ROOT/mac/install/install-runtime.sh" setup)"
113
+ doctor_before_status="$(run_json_step doctor-before "$REPO_ROOT/mac/install/doctor.sh" --first-run --json)"
114
+
115
+ pair_status="0"
116
+ pair_json="$ARTIFACT_ROOT/pairing-invitation.json"
117
+ if [[ "$SKIP_PAIR_WINDOW" == "true" ]]; then
118
+ printf '{"ok": true, "skipped": true}\n' >"$pair_json"
119
+ else
120
+ pair_status="$(run_json_step pairing-invitation "$REPO_ROOT/mac/install/install-runtime.sh" pair --ttl "$TTL" --json)"
121
+ fi
122
+
123
+ doctor_after_status="$(run_json_step doctor-after "$REPO_ROOT/mac/install/doctor.sh" --first-run --json)"
124
+
125
+ python3 - "$ARTIFACT_ROOT" "$setup_status" "$doctor_before_status" "$pair_status" "$doctor_after_status" "$SKIP_PAIR_WINDOW" "$JSON_MODE" <<'PY'
126
+ from __future__ import annotations
127
+
128
+ import json
129
+ import sys
130
+ from pathlib import Path
131
+
132
+ artifact_root = Path(sys.argv[1])
133
+ setup_status = int(sys.argv[2])
134
+ doctor_before_status = int(sys.argv[3])
135
+ pair_status = int(sys.argv[4])
136
+ doctor_after_status = int(sys.argv[5])
137
+ skip_pair_window = sys.argv[6] == "true"
138
+ json_mode = sys.argv[7] == "true"
139
+
140
+
141
+ def load_json(name: str) -> dict:
142
+ path = artifact_root / name
143
+ try:
144
+ payload = json.loads(path.read_text())
145
+ return payload if isinstance(payload, dict) else {}
146
+ except Exception:
147
+ return {}
148
+
149
+
150
+ doctor_after = load_json("doctor-after.json")
151
+ pair_payload = load_json("pairing-invitation.json")
152
+ first_run = doctor_after.get("first_run") if isinstance(doctor_after.get("first_run"), dict) else {}
153
+ pair_window_ready = skip_pair_window or bool((first_run.get("pairing") or {}).get("pair_window_open"))
154
+ next_action = first_run.get("next_action")
155
+ if isinstance(next_action, dict):
156
+ next_action_summary = next_action
157
+ else:
158
+ next_action_summary = {
159
+ "id": "review_setup",
160
+ "label": "Review setup",
161
+ "message": str(next_action or "Open Pairling on iPhone and pair with this Mac."),
162
+ }
163
+ payload = {
164
+ "ok": setup_status == 0 and doctor_after_status == 0 and pair_status == 0 and pair_window_ready,
165
+ "schema_version": 1,
166
+ "mode": "execute",
167
+ "artifact_root": str(artifact_root),
168
+ "steps": {
169
+ "setup_runtime": {"status": setup_status, "log": str(artifact_root / "setup.log")},
170
+ "doctor_first_run": {"status": doctor_before_status, "json": str(artifact_root / "doctor-before.json")},
171
+ "open_pairing_invitation": {
172
+ "status": pair_status,
173
+ "json": str(artifact_root / "pairing-invitation.json"),
174
+ "skipped": skip_pair_window,
175
+ },
176
+ "doctor_first_run_after_pairing_invitation": {"status": doctor_after_status, "json": str(artifact_root / "doctor-after.json")},
177
+ },
178
+ "ready": {
179
+ "stage": first_run.get("stage", "unknown"),
180
+ "next_action": next_action_summary,
181
+ "pairing_invitation_open": bool((first_run.get("pairing") or {}).get("pair_window_open")),
182
+ "pair_window_open": bool((first_run.get("pairing") or {}).get("pair_window_open")),
183
+ "pair_url": pair_payload.get("pair_url"),
184
+ "product_ready": bool(first_run.get("product_ready")),
185
+ "local_pairing_ready": bool(first_run.get("local_pairing_ready")),
186
+ "remote_access": first_run.get("remote_access") if isinstance(first_run.get("remote_access"), dict) else {},
187
+ },
188
+ "permissions": {
189
+ "mac_automation": "not_required_by_default",
190
+ "mac_accessibility": "not_required_until_terminal_control",
191
+ "ios_local_network": "requires_user_prompt",
192
+ "ios_camera": "requires_user_prompt_for_qr_fallback",
193
+ "tcc_database": "not_modified",
194
+ },
195
+ }
196
+ if json_mode:
197
+ print(json.dumps(payload, indent=2, sort_keys=True))
198
+ else:
199
+ print("Pairling first-run bootstrap complete." if payload["ok"] else "Pairling first-run bootstrap needs attention.")
200
+ print(f"Stage: {payload['ready']['stage']}")
201
+ print(f"Next action: {payload['ready']['next_action'].get('message', payload['ready']['next_action'].get('label', 'Review setup'))}")
202
+ if payload["ready"].get("pair_url"):
203
+ print(f"Pair URL: {payload['ready']['pair_url']}")
204
+ print(f"Artifacts: {artifact_root}")
205
+ raise SystemExit(0 if payload["ok"] else 1)
206
+ PY