@clevercloud/pi-remote-control 0.0.1 → 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/README.md CHANGED
@@ -1,9 +1,92 @@
1
- # @clevercloud/pi-remote-control
1
+ # pi-remote-control extension
2
2
 
3
- **Placeholder name reserved.**
3
+ A [pi.dev](https://pi.dev) extension that bridges your live pi session to the
4
+ remote-control relay, so a remote device (the iOS app, or `cli-test`) can watch
5
+ the session and send prompts.
4
6
 
5
- This `0.0.1` is an empty placeholder that reserves the package name on npm.
6
- It contains no runnable code. **Do not install it.**
7
+ ## Install
7
8
 
8
- The real pi.dev remote-control extension ships at **0.1.0 and above**, once the
9
- relay backend is deployed. See https://github.com/CleverCloud/Pi-dev-remote-controll
9
+ ```bash
10
+ pi install npm:@clevercloud/pi-remote-control
11
+ ```
12
+
13
+ No clone, no build, no dependencies — pi loads the extension directly (the built
14
+ `dist/` is committed; the extension is dependency-free: node builtins + the
15
+ global `WebSocket`/`fetch`). Just start pi:
16
+
17
+ ```bash
18
+ pi # relay defaults to wss://pidev-remote.cleverapps.io
19
+ ```
20
+
21
+ The session is named automatically from the project directory pi runs in (the
22
+ git repo root), plus a distinguishing word: the **git worktree name** when pi
23
+ runs in a linked worktree, an animal otherwise (e.g. `dev-foo-4fa9c1-otter`).
24
+ Your devices list your sessions and connect with a tap — nothing to configure
25
+ or share, and no hand-named sessions.
26
+
27
+ **From source (dev):** `npm install && npm run build`, then
28
+ `pi --extension ./dist/index.js`. Re-commit `dist/` after editing `src/`.
29
+
30
+ ## Sign in — inside pi
31
+
32
+ No separate binary, no token copy-pasting:
33
+
34
+ ```
35
+ /remote-login # opens your browser; tokens stored + auto-refreshed
36
+ /remote-status # relay state · session · auth · mode
37
+ /remote-logout
38
+ /remote-beta-invite you@apple-id.com # join the iOS TestFlight beta
39
+ ```
40
+
41
+ Credentials live at `~/.config/pidev/credentials.json`. Auth resolution order:
42
+ `PIDEV_OIDC_TOKEN` (env override, for CI) → stored `/remote-login` credentials
43
+ (auto-refreshed) → none (dev mode, loopback relay only). An off-loopback
44
+ (`wss://`) relay requires one of the first two.
45
+
46
+ > Keycloak: the public client needs the **exact** loopback redirect URIs
47
+ > `http://127.0.0.1:8765/callback`, `:8766`, `:8767` registered (a
48
+ > `http://127.0.0.1/*` wildcard does NOT cover the port). `/remote-login` uses
49
+ > the first free of these ports.
50
+
51
+ Once pinned to a pi version and published, it can instead be installed with
52
+ `pi install git:github.com/clevercloud/pi-dev-remote-controll` (extension
53
+ sub-path TBD). The Rust `pidev` (`../server/cli-test`) shares the same credential
54
+ file and remains only as an optional device tester.
55
+
56
+ ## What it does (skeleton)
57
+
58
+ | Direction | Mapping |
59
+ | ---------------- | ------------------------------------------------------------------- |
60
+ | device → pi | `{type:"prompt"}` → `pi.sendUserMessage(text, { streamingBehavior })`|
61
+ | pi → device | `message_update` (text deltas) → `{type:"delta"}` |
62
+ | pi → device | `tool_execution_start/end` → `{type:"tool"}` |
63
+ | pi → device | `agent_end` → `{type:"turn_end"}` |
64
+
65
+ ## Transport test (no real pi / no model key)
66
+
67
+ `test/harness.mjs` runs the **real extension** against a mocked `pi` — only the
68
+ LLM brain is faked. With a local Pulsar + relay running, it lets a prompt from
69
+ `cli-test` round-trip device → pi → device through the broker:
70
+
71
+ ```bash
72
+ npm run build
73
+ PIDEV_RELAY_URL=ws://localhost:8080 bun test/harness.mjs &
74
+ # then, from server/: cargo run -p cli-test -- sessions # grab the printed id
75
+ # cargo run -p cli-test -- send --session <id> --prompt "hi"
76
+ # → prints "received: hi"
77
+ ```
78
+
79
+ This was validated end-to-end against `apachepulsar/pulsar:4.0.4` standalone:
80
+ both `pidev-demo-to-pi` and `pidev-demo-from-pi` topics are created and carry
81
+ the traffic (subscriptions `pi` and `device-0`).
82
+
83
+ Tool approvals (`on("tool_call")` → block, paired with `{type:"approval"}`) are
84
+ **implemented** with UUID request ids and default-deny on timeout/disconnect.
85
+ History replay (`ctx.sessionManager.getEntries()`) is still pending.
86
+
87
+ Transport policy: off-loopback the extension requires `wss://` **and**
88
+ `PIDEV_OIDC_TOKEN`, failing closed otherwise (see
89
+ [`../docs/SECURITY.md`](../docs/SECURITY.md)).
90
+
91
+ The `ExtensionAPI` type here is modelled loosely from pi's `docs/extensions.md`;
92
+ tighten it once we pin a pi release and depend on its published types.
package/dist/auth.js ADDED
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Pure-Node auth core for the pi remote-control extension: a `clever login`-style
3
+ * loopback OAuth (PKCE) flow, a persistent token store, and silent refresh. No
4
+ * separate binary — `/remote-login` runs this in-process. The on-disk format is
5
+ * shared with the optional Rust `pidev` tester, so both interoperate.
6
+ */
7
+ import { spawn } from "node:child_process";
8
+ import { createHash, randomBytes } from "node:crypto";
9
+ import { existsSync } from "node:fs";
10
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
11
+ import { createServer } from "node:http";
12
+ import { homedir, platform } from "node:os";
13
+ import { dirname, join } from "node:path";
14
+ export const CREDENTIALS_PATH = join(homedir(), ".config", "pidev", "credentials.json");
15
+ /** Fixed callback ports so an *exact* redirect URI can be registered in Keycloak
16
+ * (a `http://127.0.0.1/*` wildcard does NOT cover the port). */
17
+ const CALLBACK_PORTS = [8765, 8766, 8767];
18
+ const nowSec = () => Math.floor(Date.now() / 1000);
19
+ const b64url = (buf) => buf.toString("base64url");
20
+ const httpBase = (relay) => relay.replace(/\/$/, "").replace(/^ws/, "http");
21
+ export function hasCredentials() {
22
+ return existsSync(CREDENTIALS_PATH);
23
+ }
24
+ async function loadCredentials() {
25
+ try {
26
+ return JSON.parse(await readFile(CREDENTIALS_PATH, "utf8"));
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ async function saveCredentials(creds) {
33
+ await mkdir(dirname(CREDENTIALS_PATH), { recursive: true });
34
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 0o600 });
35
+ }
36
+ export async function logout() {
37
+ if (!existsSync(CREDENTIALS_PATH))
38
+ return false;
39
+ await rm(CREDENTIALS_PATH);
40
+ return true;
41
+ }
42
+ async function fetchWebConfig(relay) {
43
+ const res = await fetch(`${httpBase(relay)}/web-config.json`);
44
+ if (!res.ok)
45
+ throw new Error(`/web-config.json → ${res.status}`);
46
+ return (await res.json());
47
+ }
48
+ function openBrowser(url) {
49
+ const os = platform();
50
+ const cmd = os === "darwin" ? "open" : os === "win32" ? "cmd" : "xdg-open";
51
+ const args = os === "win32" ? ["/c", "start", "", url] : [url];
52
+ try {
53
+ spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
54
+ }
55
+ catch {
56
+ /* the URL is logged for manual use */
57
+ }
58
+ }
59
+ /** Listen on the first free callback port; resolve the bound port. */
60
+ function bindFirstFree(server) {
61
+ return new Promise((resolve, reject) => {
62
+ const tryPort = (i) => {
63
+ if (i >= CALLBACK_PORTS.length) {
64
+ reject(new Error(`no free loopback port among ${CALLBACK_PORTS.join(", ")}`));
65
+ return;
66
+ }
67
+ const onError = () => tryPort(i + 1);
68
+ server.once("error", onError);
69
+ server.listen(CALLBACK_PORTS[i], "127.0.0.1", () => {
70
+ server.removeListener("error", onError);
71
+ resolve(CALLBACK_PORTS[i]);
72
+ });
73
+ };
74
+ tryPort(0);
75
+ });
76
+ }
77
+ const SIGNED_IN_PAGE = "<html><body style='font-family:system-ui;background:#171a21;color:#eee;" +
78
+ "text-align:center;padding-top:4rem'><h2>✓ Signed in</h2>" +
79
+ "<p>You can close this tab and return to your editor.</p></body></html>";
80
+ /**
81
+ * Run the loopback PKCE login: open the browser, capture the code on 127.0.0.1,
82
+ * exchange it for access+refresh tokens, and persist them.
83
+ */
84
+ export async function login(relay, log) {
85
+ const cfg = await fetchWebConfig(relay);
86
+ const verifier = b64url(randomBytes(32));
87
+ const challenge = b64url(createHash("sha256").update(verifier).digest());
88
+ const state = b64url(randomBytes(16));
89
+ let port = 0;
90
+ let resolveCode;
91
+ const codePromise = new Promise((res) => {
92
+ resolveCode = res;
93
+ });
94
+ const server = createServer((req, resp) => {
95
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
96
+ const code = url.searchParams.get("code");
97
+ const gotState = url.searchParams.get("state");
98
+ if (code && gotState) {
99
+ resp.writeHead(200, { "content-type": "text/html" });
100
+ resp.end(SIGNED_IN_PAGE);
101
+ resolveCode({ code, state: gotState });
102
+ }
103
+ else {
104
+ resp.writeHead(204);
105
+ resp.end();
106
+ }
107
+ });
108
+ port = await bindFirstFree(server);
109
+ const redirect = `http://127.0.0.1:${port}/callback`;
110
+ const authUrl = `${cfg.issuer}/protocol/openid-connect/auth?client_id=${encodeURIComponent(cfg.clientId)}` +
111
+ `&response_type=code&scope=${encodeURIComponent("openid offline_access")}` +
112
+ `&code_challenge=${challenge}&code_challenge_method=S256` +
113
+ `&redirect_uri=${encodeURIComponent(redirect)}&state=${state}`;
114
+ log(`opening your browser to sign in… if it doesn't open, visit:\n ${authUrl}`);
115
+ openBrowser(authUrl);
116
+ const got = await codePromise;
117
+ server.close();
118
+ if (got.state !== state)
119
+ throw new Error("OAuth state mismatch (possible CSRF)");
120
+ const res = await fetch(`${cfg.issuer}/protocol/openid-connect/token`, {
121
+ method: "POST",
122
+ headers: { "content-type": "application/x-www-form-urlencoded" },
123
+ body: new URLSearchParams({
124
+ grant_type: "authorization_code",
125
+ code: got.code,
126
+ redirect_uri: redirect,
127
+ client_id: cfg.clientId,
128
+ code_verifier: verifier,
129
+ }),
130
+ });
131
+ if (!res.ok)
132
+ throw new Error(`token exchange ${res.status}: ${await res.text()}`);
133
+ const t = (await res.json());
134
+ if (!t.refresh_token)
135
+ throw new Error("no refresh_token — the client needs offline_access");
136
+ await saveCredentials({
137
+ access_token: t.access_token,
138
+ refresh_token: t.refresh_token,
139
+ expires_at: nowSec() + t.expires_in - 30,
140
+ issuer: cfg.issuer,
141
+ client_id: cfg.clientId,
142
+ });
143
+ }
144
+ /**
145
+ * Resolve a valid OIDC access token: `PIDEV_OIDC_TOKEN` env override first, else
146
+ * the stored login credentials, refreshing them when near expiry.
147
+ */
148
+ export async function resolveOidcToken(log) {
149
+ if (process.env.PIDEV_OIDC_TOKEN)
150
+ return process.env.PIDEV_OIDC_TOKEN;
151
+ const creds = await loadCredentials();
152
+ if (!creds)
153
+ return undefined;
154
+ if (creds.expires_at > nowSec() + 30)
155
+ return creds.access_token;
156
+ try {
157
+ const res = await fetch(`${creds.issuer}/protocol/openid-connect/token`, {
158
+ method: "POST",
159
+ headers: { "content-type": "application/x-www-form-urlencoded" },
160
+ body: new URLSearchParams({
161
+ grant_type: "refresh_token",
162
+ refresh_token: creds.refresh_token,
163
+ client_id: creds.client_id,
164
+ }),
165
+ });
166
+ if (!res.ok) {
167
+ log(`token refresh failed (${res.status}) — run /remote-login again`);
168
+ return undefined;
169
+ }
170
+ const t = (await res.json());
171
+ creds.access_token = t.access_token;
172
+ if (t.refresh_token)
173
+ creds.refresh_token = t.refresh_token;
174
+ creds.expires_at = nowSec() + t.expires_in - 30;
175
+ await saveCredentials(creds);
176
+ return creds.access_token;
177
+ }
178
+ catch (e) {
179
+ log(`token refresh error: ${String(e)}`);
180
+ return undefined;
181
+ }
182
+ }
183
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,565 @@
1
+ /**
2
+ * pi.dev remote-control extension.
3
+ *
4
+ * Bridges the live pi session to the relay over a WebSocket:
5
+ * - observes assistant streaming (`message_update`) and tool runs, and
6
+ * forwards them to the relay as `from-pi` events;
7
+ * - receives `to-pi` commands from remote devices and injects them into the
8
+ * session via `pi.sendUserMessage`;
9
+ * - gates tool calls on remote approval: on `tool_call` it asks the device
10
+ * and blocks the tool if denied (or on timeout).
11
+ *
12
+ * Auth: before connecting, the extension exchanges an OIDC token (or, in the
13
+ * relay's dev mode, nothing) for a subject-scoped biscuit at `POST
14
+ * /auth/token`, and presents that biscuit on the WS connect.
15
+ *
16
+ * Sessions are named automatically: the extension *claims* its session at
17
+ * `POST /sessions/claim` by declaring its project directory (the git repo
18
+ * root, falling back to the bare cwd), and the relay answers with a stable id
19
+ * derived from the path plus a distinguishing word — the git worktree name
20
+ * when pi runs in a linked worktree, an animal otherwise (e.g.
21
+ * `dev-foo-4fa9c1-otter`). Remotes discover it via `GET /sessions` — nothing
22
+ * to configure or share, and no hand-named sessions.
23
+ *
24
+ * Config via env:
25
+ * PIDEV_RELAY_URL relay base, default wss://pidev-remote.cleverapps.io
26
+ * PIDEV_OIDC_TOKEN optional OIDC access token (required when the relay has
27
+ * OIDC_ISSUER set; omitted in dev mode)
28
+ *
29
+ * NOTE: pi's published types aren't depended on here — the `ExtensionAPI`
30
+ * surface is modelled loosely against the documented methods/events
31
+ * (docs/extensions.md). Tighten once we pin a pi version.
32
+ */
33
+ import { execSync } from "node:child_process";
34
+ import { randomUUID } from "node:crypto";
35
+ import { basename, dirname, resolve } from "node:path";
36
+ import { hasCredentials, login, logout, resolveOidcToken } from "./auth.js";
37
+ /** What this pi identifies as: the project directory (git repo root — the
38
+ * *main* checkout when running in a linked worktree — else the bare cwd) and,
39
+ * in a worktree, the worktree name as the distinguishing-word hint. */
40
+ function detectProject(cwd) {
41
+ const git = (args) => {
42
+ try {
43
+ const out = execSync(`git ${args}`, { cwd, stdio: ["ignore", "pipe", "ignore"] })
44
+ .toString()
45
+ .trim();
46
+ return out || undefined;
47
+ }
48
+ catch {
49
+ return undefined;
50
+ }
51
+ };
52
+ const top = git("rev-parse --show-toplevel");
53
+ if (!top)
54
+ return { path: cwd }; // not a git checkout (or no git)
55
+ const gitDir = git("rev-parse --absolute-git-dir");
56
+ const commonDir = git("rev-parse --git-common-dir");
57
+ if (gitDir && commonDir) {
58
+ const common = resolve(cwd, commonDir);
59
+ if (resolve(cwd, gitDir) !== common) {
60
+ // Linked worktree: identify as the main checkout, distinguished by the
61
+ // worktree's name — `repo (my-feature)` instead of two anonymous animals.
62
+ return { path: dirname(common), hint: basename(top) };
63
+ }
64
+ }
65
+ return { path: top };
66
+ }
67
+ // Overridable so tests can exercise the timeout path without a 60s wait.
68
+ const APPROVAL_TIMEOUT_MS = Number(process.env.PIDEV_APPROVAL_TIMEOUT_MS) || 60_000;
69
+ const ASK_TIMEOUT_MS = Number(process.env.PIDEV_ASK_TIMEOUT_MS) || 120_000;
70
+ // ---------------------------------------------------------------------------
71
+ export default function activate(pi, ctx) {
72
+ const relayBase = (process.env.PIDEV_RELAY_URL ?? "wss://pidev-remote.cleverapps.io").replace(/\/$/, "");
73
+ const httpBase = relayBase.replace(/^ws/, "http");
74
+ const oidcToken = process.env.PIDEV_OIDC_TOKEN;
75
+ // The session id is claimed from the relay on each connect (project path +
76
+ // distinguishing word, see the header comment). No manual session names.
77
+ let session = "";
78
+ let word; // relay-assigned; remembered → stable id
79
+ const project = detectProject(process.cwd());
80
+ // Transport policy: cleartext is only allowed to a loopback relay. Off-loopback
81
+ // requires wss:// and an OIDC token (fail closed rather than leak credentials
82
+ // or connect unauthenticated). See docs/audits/SECURITY-AUDIT.md (C1, C6).
83
+ const host = (() => {
84
+ try {
85
+ return new URL(relayBase).hostname;
86
+ }
87
+ catch {
88
+ return "";
89
+ }
90
+ })();
91
+ const isLoopback = ["localhost", "127.0.0.1", "::1", "[::1]"].includes(host);
92
+ if (!isLoopback) {
93
+ if (!relayBase.startsWith("wss://")) {
94
+ log(`refusing to connect: ${relayBase} is not loopback and not wss:// (cleartext would leak the biscuit)`);
95
+ return;
96
+ }
97
+ if (!oidcToken && !hasCredentials()) {
98
+ log("refusing to connect: non-loopback relay needs auth — run /remote-login (or set PIDEV_OIDC_TOKEN). Failing closed.");
99
+ return;
100
+ }
101
+ }
102
+ let ws = null;
103
+ let closed = false;
104
+ let retryMs = 1000;
105
+ const RETRY_MAX_MS = 30_000;
106
+ // Detect a session conflict (another pi kicking us): count short-lived connects.
107
+ let openedAt = 0;
108
+ let shortLived = 0;
109
+ let conflictWarned = false;
110
+ // How many remote devices are currently connected to this session. The relay
111
+ // sends a `presence` frame whenever this changes; it resets to 0 when the relay
112
+ // connection drops. The approval gates only engage while this is >= 1 — see the
113
+ // tool_call gate for why.
114
+ let devicesPresent = 0;
115
+ // Pending tool approvals, keyed by request id.
116
+ const pendingApprovals = new Map();
117
+ // Pending ask-user questions, keyed by question id.
118
+ const pendingAnswers = new Map();
119
+ // Whether a turn is currently running (drives the busy ack hint).
120
+ let turnInProgress = false;
121
+ // How much of the turn's thinking content we've already forwarded (for diffing).
122
+ let sentThinkingLen = 0;
123
+ const scheduleReconnect = () => {
124
+ if (closed)
125
+ return;
126
+ const delay = Math.min(retryMs, RETRY_MAX_MS) + Math.floor(Math.random() * 500);
127
+ debug(`reconnecting in ${delay}ms`);
128
+ setTimeout(connect, delay);
129
+ retryMs = Math.min(retryMs * 2, RETRY_MAX_MS); // exponential backoff
130
+ };
131
+ const send = (msg) => {
132
+ if (ws && ws.readyState === WebSocket.OPEN)
133
+ ws.send(JSON.stringify(msg));
134
+ };
135
+ // "pi is working" heartbeat: while a turn runs, tell the device every few
136
+ // seconds so it can show a live loader even during long silent tool runs.
137
+ let workingTimer = null;
138
+ const startWorking = () => {
139
+ send({ type: "working" });
140
+ if (workingTimer)
141
+ return;
142
+ workingTimer = setInterval(() => {
143
+ if (turnInProgress)
144
+ send({ type: "working" });
145
+ else
146
+ stopWorking();
147
+ }, 2500);
148
+ };
149
+ function stopWorking() {
150
+ if (workingTimer) {
151
+ clearInterval(workingTimer);
152
+ workingTimer = null;
153
+ }
154
+ }
155
+ const fetchBiscuit = async () => {
156
+ const headers = { "content-type": "application/json" };
157
+ const token = await resolveOidcToken(log);
158
+ if (token)
159
+ headers["authorization"] = `Bearer ${token}`;
160
+ const ctrl = new AbortController();
161
+ const timer = setTimeout(() => ctrl.abort(), 10_000);
162
+ try {
163
+ const res = await fetch(`${httpBase}/auth/token`, {
164
+ method: "POST",
165
+ headers,
166
+ body: JSON.stringify({ role: "pi" }),
167
+ signal: ctrl.signal,
168
+ });
169
+ if (!res.ok)
170
+ throw new Error(`auth/token returned ${res.status}`);
171
+ const body = (await res.json());
172
+ return body.biscuit;
173
+ }
174
+ finally {
175
+ clearTimeout(timer);
176
+ }
177
+ };
178
+ /** Claim this project's session id from the relay (path + word). The first
179
+ * claim hints the git worktree name; afterwards the assigned word rides
180
+ * along so the id stays stable across reconnects and relay restarts. */
181
+ const claimSession = async (biscuit) => {
182
+ const ctrl = new AbortController();
183
+ const timer = setTimeout(() => ctrl.abort(), 10_000);
184
+ try {
185
+ const res = await fetch(`${httpBase}/sessions/claim`, {
186
+ method: "POST",
187
+ headers: { "content-type": "application/json", authorization: `Bearer ${biscuit}` },
188
+ body: JSON.stringify({ path: project.path, word, hint: project.hint }),
189
+ signal: ctrl.signal,
190
+ });
191
+ if (!res.ok)
192
+ throw new Error(`sessions/claim returned ${res.status}`);
193
+ const body = (await res.json());
194
+ if (!session)
195
+ log(`session: ${project.path} (${body.word})`);
196
+ session = body.session;
197
+ word = body.word;
198
+ }
199
+ finally {
200
+ clearTimeout(timer);
201
+ }
202
+ };
203
+ const sessionLabel = () => session ? `${project.path} (${word})` : "not claimed yet";
204
+ const connect = async () => {
205
+ if (closed)
206
+ return;
207
+ let biscuit;
208
+ try {
209
+ biscuit = await fetchBiscuit();
210
+ await claimSession(biscuit);
211
+ }
212
+ catch (err) {
213
+ log(`auth failed: ${err.message}`);
214
+ scheduleReconnect();
215
+ return;
216
+ }
217
+ const wsUrl = `${relayBase}/ws/pi?session=${encodeURIComponent(session)}`;
218
+ debug(`connecting to relay ${wsUrl}`);
219
+ // Global WebSocket (bun/Node) can't set request headers, so the biscuit rides
220
+ // as the access_token query param (the relay accepts header OR query). Keeps
221
+ // the extension dependency-free — no `ws` package — so `pi install` just works.
222
+ ws = new WebSocket(`${wsUrl}&access_token=${encodeURIComponent(biscuit)}`);
223
+ ws.onopen = () => {
224
+ retryMs = 1000; // reset backoff on a successful connect
225
+ openedAt = Date.now();
226
+ sink.reset(); // recovered — re-arm the dedupe so a later outage re-notifies
227
+ debug("relay connected");
228
+ };
229
+ ws.onmessage = (event) => {
230
+ let cmd;
231
+ try {
232
+ cmd = JSON.parse(String(event.data));
233
+ }
234
+ catch {
235
+ log(`bad command frame: ${String(event.data)}`);
236
+ return;
237
+ }
238
+ if (cmd.type === "prompt") {
239
+ log(`remote prompt: ${cmd.text}`);
240
+ // Instant receipt ack (remote_pi-style) so the device knows pi got it,
241
+ // without waiting for the first delta or a timeout.
242
+ send({ type: "ack", status: turnInProgress ? "busy" : "received" });
243
+ turnInProgress = true;
244
+ sentThinkingLen = 0; // reset the thinking diff for the new turn
245
+ startWorking();
246
+ void pi.sendUserMessage(cmd.text, {
247
+ streamingBehavior: cmd.streamingBehavior ?? "followUp",
248
+ });
249
+ }
250
+ else if (cmd.type === "approval") {
251
+ const resolve = pendingApprovals.get(cmd.id);
252
+ if (resolve)
253
+ resolve(cmd.allow);
254
+ }
255
+ else if (cmd.type === "answer") {
256
+ const resolve = pendingAnswers.get(cmd.id);
257
+ if (resolve)
258
+ resolve(cmd.text);
259
+ }
260
+ else if (cmd.type === "presence") {
261
+ // Track how many devices are watching. Be defensive: a non-number (or a
262
+ // malformed frame) means "we don't actually know anyone's there" → 0.
263
+ devicesPresent = typeof cmd.devices === "number" && cmd.devices > 0 ? cmd.devices : 0;
264
+ debug(`presence: ${devicesPresent} device(s)`);
265
+ }
266
+ else if (cmd.type === "abort") {
267
+ log("remote abort: interrupting the current turn");
268
+ void pi.abort?.();
269
+ }
270
+ };
271
+ ws.onclose = () => {
272
+ debug("relay disconnected");
273
+ stopWorking();
274
+ // No relay connection ⇒ no operator is reachable. Drop back to 0 so the
275
+ // gates fall through to pi's native local prompt until presence resumes.
276
+ devicesPresent = 0;
277
+ // A connection that barely lived is the signature of a session conflict
278
+ // (another pi kicking us off the "pi" subscription). Warn once, don't spam.
279
+ const lived = openedAt ? Date.now() - openedAt : 0;
280
+ if (openedAt && lived < 3000) {
281
+ if (++shortLived >= 3 && !conflictWarned) {
282
+ conflictWarned = true;
283
+ log(`the relay keeps dropping this connection — another pi is likely on session "${sessionLabel()}" (two pi's claiming the same id should not happen; restarting pi will claim a fresh animal).`);
284
+ }
285
+ }
286
+ else {
287
+ shortLived = 0;
288
+ conflictWarned = false;
289
+ }
290
+ openedAt = 0;
291
+ // Fail any in-flight approvals closed (deny by default).
292
+ for (const resolve of pendingApprovals.values())
293
+ resolve(false);
294
+ pendingApprovals.clear();
295
+ // Fail any in-flight questions with an empty answer.
296
+ for (const resolve of pendingAnswers.values())
297
+ resolve("");
298
+ pendingAnswers.clear();
299
+ scheduleReconnect();
300
+ };
301
+ ws.onerror = () => debug("relay websocket error");
302
+ };
303
+ const awaitApproval = (id) => new Promise((resolve) => {
304
+ let settled = false;
305
+ const finish = (allow) => {
306
+ if (settled)
307
+ return;
308
+ settled = true;
309
+ pendingApprovals.delete(id);
310
+ resolve(allow);
311
+ };
312
+ pendingApprovals.set(id, finish);
313
+ setTimeout(() => finish(false), APPROVAL_TIMEOUT_MS); // default-deny on timeout
314
+ });
315
+ const awaitAnswer = (id) => new Promise((resolve) => {
316
+ let settled = false;
317
+ const finish = (text) => {
318
+ if (settled)
319
+ return;
320
+ settled = true;
321
+ pendingAnswers.delete(id);
322
+ resolve(text);
323
+ };
324
+ pendingAnswers.set(id, finish);
325
+ setTimeout(() => finish(""), ASK_TIMEOUT_MS); // empty answer on timeout
326
+ });
327
+ // Each message accumulates its own thinking content, so reset the diff cursor
328
+ // when a new message starts (a turn may have several assistant messages).
329
+ pi.on("message_start", () => {
330
+ sentThinkingLen = 0;
331
+ });
332
+ // Assistant token stream → device.
333
+ pi.on("message_update", (event) => {
334
+ const ev = event?.assistantMessageEvent;
335
+ if (ev?.type === "text_delta" && typeof ev.delta === "string") {
336
+ send({ type: "delta", text: ev.delta });
337
+ }
338
+ // A per-token thinking/reasoning stream event, whatever its exact shape.
339
+ if (ev && ev.type !== "text_delta" && /think|reason/i.test(String(ev.type ?? ""))) {
340
+ const t = ev.delta ?? ev.thinking ?? ev.reasoning ?? ev.text;
341
+ if (typeof t === "string" && t.length)
342
+ send({ type: "thinking", text: t });
343
+ }
344
+ // Also diff the message's thinking/reasoning content blocks (documented shape
345
+ // {type:"thinking", thinking}), covering providers that only expose it there.
346
+ const content = event?.message?.content;
347
+ if (Array.isArray(content)) {
348
+ let thinking = "";
349
+ for (const block of content) {
350
+ if (!block)
351
+ continue;
352
+ if (block.type === "thinking" && typeof block.thinking === "string")
353
+ thinking += block.thinking;
354
+ else if (/think|reason/i.test(String(block.type ?? "")) && typeof (block.thinking ?? block.text ?? block.reasoning) === "string") {
355
+ thinking += (block.thinking ?? block.text ?? block.reasoning);
356
+ }
357
+ }
358
+ if (thinking.length > sentThinkingLen) {
359
+ send({ type: "thinking", text: thinking.slice(sentThinkingLen) });
360
+ sentThinkingLen = thinking.length;
361
+ }
362
+ }
363
+ // Diagnostic (PIDEV_DEBUG=1): dump the real event shape so we can pin the
364
+ // thinking representation for this provider/model.
365
+ if (process.env.PIDEV_DEBUG) {
366
+ const blocks = Array.isArray(content) ? content.map((b) => b?.type).join(",") : typeof content;
367
+ debug(`msg-update evType=${ev?.type} evKeys=[${ev ? Object.keys(ev).join(",") : ""}] blocks=[${blocks}]`);
368
+ }
369
+ });
370
+ // Tool lifecycle markers → device.
371
+ pi.on("tool_execution_start", (event) => {
372
+ send({ type: "tool", phase: "start", name: event?.toolName ?? "?" });
373
+ });
374
+ pi.on("tool_execution_end", (event) => {
375
+ send({ type: "tool", phase: "end", name: event?.toolName ?? "?" });
376
+ });
377
+ // Gate each tool call on a remote approval. Returns `{ block }` to deny.
378
+ //
379
+ // The gate ONLY engages while a device is present. With nobody watching, an
380
+ // approval_request would go into the void and default-deny on timeout —
381
+ // confiscating pi's native local prompt and silently dropping commands the
382
+ // user is sitting right there to approve. So when `devicesPresent < 1` we
383
+ // return `undefined` and let pi ask locally.
384
+ pi.on("tool_call", async (event) => {
385
+ if (devicesPresent < 1)
386
+ return undefined; // no remote operator → defer to pi's native prompt
387
+ const tool = event?.toolName ?? event?.name ?? "tool";
388
+ const id = randomUUID(); // unguessable, matches docs' "uuid" contract
389
+ const title = `Allow tool \`${tool}\`?`;
390
+ log(`requesting approval for ${tool} (${id})`);
391
+ send({ type: "approval_request", id, title, tool });
392
+ const allow = await awaitApproval(id);
393
+ log(`approval ${id}: ${allow ? "allowed" : "denied"}`);
394
+ return allow ? undefined : { block: true, reason: "denied by remote operator" };
395
+ });
396
+ // pi asks the user a free-form question → forward to the device and await the
397
+ // typed answer (mirrors the tool_call gate). LOOSE MODEL: pi's real ask-user
398
+ // hook name/shape is unconfirmed — same caveat as the rest of this file —
399
+ // modelled as a `user_input_request` event whose handler returns `{ text }`.
400
+ // Tighten once a pi version is pinned (see docs/extensions.md).
401
+ pi.on("user_input_request", async (event) => {
402
+ // Same fall-through as the tool_call gate: with no device present, forwarding
403
+ // a `question` would strand it and pi couldn't ask locally. Let pi prompt.
404
+ if (devicesPresent < 1)
405
+ return undefined;
406
+ const prompt = event?.prompt ?? event?.question ?? "pi needs your input";
407
+ const choices = Array.isArray(event?.choices) ? event.choices : undefined;
408
+ const id = randomUUID();
409
+ log(`asking user: ${prompt} (${id})`);
410
+ send({ type: "question", id, prompt, choices });
411
+ const text = await awaitAnswer(id);
412
+ log(`answer ${id}: ${JSON.stringify(text)}`);
413
+ return { text };
414
+ });
415
+ // End of an agent turn → device.
416
+ pi.on("agent_end", () => {
417
+ turnInProgress = false;
418
+ stopWorking();
419
+ send({ type: "turn_end" });
420
+ });
421
+ // User-invoked commands must always show their output, even if a line matches
422
+ // one already emitted during an outage streak — reset the dedupe on each
423
+ // invocation so a manually typed command is never silently swallowed.
424
+ const command = (name, options) => pi.registerCommand(name, {
425
+ ...options,
426
+ handler: (args) => {
427
+ sink.reset();
428
+ return options.handler(args);
429
+ },
430
+ });
431
+ command("remote-status", {
432
+ description: "Show pi-dev remote-control connection status",
433
+ handler: () => {
434
+ const state = ws ? readyStateName(ws.readyState) : "disconnected";
435
+ const auth = process.env.PIDEV_OIDC_TOKEN ? "env token" : hasCredentials() ? "logged in" : "not signed in";
436
+ log(`relay ${state} · ${relayBase} · session ${sessionLabel()} · auth ${auth} · mode ${ctx?.mode ?? "?"}`);
437
+ },
438
+ });
439
+ command("remote-login", {
440
+ description: "Sign in to the remote relay (opens your browser)",
441
+ handler: async () => {
442
+ try {
443
+ log("signing in…");
444
+ await login(relayBase, log);
445
+ log("✓ signed in — credentials stored and auto-refreshed. reconnecting…");
446
+ if (ws)
447
+ ws.close(); // the reconnect picks up the fresh credentials
448
+ else
449
+ void connect();
450
+ }
451
+ catch (e) {
452
+ log(`login failed: ${String(e)}`);
453
+ }
454
+ },
455
+ });
456
+ command("remote-logout", {
457
+ description: "Forget stored remote credentials",
458
+ handler: async () => {
459
+ log((await logout()) ? "✓ logged out (credentials removed)" : "no stored credentials");
460
+ },
461
+ });
462
+ command("remote-pair", {
463
+ description: "Show a QR to pair your phone with this remote (no login on the phone)",
464
+ handler: async () => {
465
+ const token = await resolveOidcToken(log);
466
+ if (!token) {
467
+ log("not signed in — run /remote-login first");
468
+ return;
469
+ }
470
+ try {
471
+ const res = await fetch(`${httpBase}/pair`, { method: "POST", headers: { authorization: `Bearer ${token}` } });
472
+ if (!res.ok) {
473
+ log(`pairing failed (${res.status})`);
474
+ return;
475
+ }
476
+ const p = (await res.json());
477
+ // eslint-disable-next-line no-console
478
+ console.error(`\n${p.qrTerminal}\nScan with the pi.dev app — this session shows up as "${sessionLabel()}". ` +
479
+ `Expires in ${p.expiresIn}s. No login needed on the phone.\n`);
480
+ }
481
+ catch (e) {
482
+ log(`pairing error: ${String(e)}`);
483
+ }
484
+ },
485
+ });
486
+ command("remote-beta-invite", {
487
+ description: "Add your Apple ID to the iOS TestFlight beta: /remote-beta-invite you@appleid",
488
+ handler: async (args) => {
489
+ const email = (args ?? "").trim();
490
+ if (!email || !email.includes("@")) {
491
+ log("usage: /remote-beta-invite you@your-apple-id.com");
492
+ return;
493
+ }
494
+ const token = await resolveOidcToken(log);
495
+ if (!token) {
496
+ log("not signed in — run /remote-login first");
497
+ return;
498
+ }
499
+ try {
500
+ const res = await fetch(`${httpBase}/beta/invite`, {
501
+ method: "POST",
502
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
503
+ body: JSON.stringify({ email }),
504
+ });
505
+ const body = await res.text();
506
+ if (!res.ok) {
507
+ log(`beta invite failed (${res.status}): ${body}`);
508
+ return;
509
+ }
510
+ log(body.includes("already_registered")
511
+ ? `${email} is already in the beta — open TestFlight to install.`
512
+ : `✓ invited ${email} to the TestFlight beta — check your email, then install "pi.dev remote".`);
513
+ }
514
+ catch (e) {
515
+ log(`beta invite error: ${String(e)}`);
516
+ }
517
+ },
518
+ });
519
+ void connect();
520
+ }
521
+ function readyStateName(s) {
522
+ return ["connecting", "open", "closing", "closed"][s] ?? String(s);
523
+ }
524
+ // A sink that emits each distinct line at most once per "streak". The extension
525
+ // has no rich UI channel — it writes to stderr, and in pi's TUI raw writes
526
+ // garble the rendered screen. A reconnect loop repeats a small SET of messages
527
+ // every attempt ("token refresh failed…" then "auth failed…", alternating), so
528
+ // suppressing only consecutive duplicates isn't enough — the interleaving
529
+ // defeats it. Instead every message already seen since the last reset is
530
+ // dropped: an outage surfaces each of its lines ONCE, then goes silent.
531
+ // `reset()` re-arms it on a state change (a successful connect, or a user
532
+ // running a command) so later output — or a later outage — is shown again
533
+ // instead of being silently swallowed.
534
+ export function makeDedupedSink(write) {
535
+ const seen = new Set();
536
+ return {
537
+ note(msg) {
538
+ if (seen.has(msg))
539
+ return;
540
+ seen.add(msg);
541
+ write(msg);
542
+ },
543
+ reset() {
544
+ seen.clear();
545
+ },
546
+ };
547
+ }
548
+ // Actionable messages the user should see (rare: login needed, hard failures).
549
+ // Routed through the deduped sink so a retry loop can't spam pi's TUI.
550
+ const sink = makeDedupedSink((line) => {
551
+ // eslint-disable-next-line no-console
552
+ console.error(`[pi-remote] ${line}`);
553
+ });
554
+ function log(msg) {
555
+ sink.note(msg);
556
+ }
557
+ // Routine lifecycle chatter (connect/reconnect/etc.) — silent by default so it
558
+ // doesn't garble pi's TUI. Set PIDEV_DEBUG=1 to see it.
559
+ function debug(msg) {
560
+ if (process.env.PIDEV_DEBUG) {
561
+ // eslint-disable-next-line no-console
562
+ console.error(`[pi-remote] ${msg}`);
563
+ }
564
+ }
565
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE5E;;wEAEwE;AACxE,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAsB,EAAE;QAC/C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;iBAC9E,QAAQ,EAAE;iBACV,IAAI,EAAE,CAAC;YACV,OAAO,GAAG,IAAI,SAAS,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,iCAAiC;IACjE,MAAM,MAAM,GAAG,GAAG,CAAC,8BAA8B,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC;IACpD,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACvC,IAAI,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,CAAC;YACpC,uEAAuE;YACvE,0EAA0E;YAC1E,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC;AA2DD,yEAAyE;AACzE,MAAM,mBAAmB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,IAAI,MAAM,CAAC;AACpF,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,OAAO,CAAC;AAE3E,8EAA8E;AAE9E,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAAgB,EAAE,GAAsB;IACvE,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,kCAAkC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzG,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAE/C,2EAA2E;IAC3E,yEAAyE;IACzE,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,IAAwB,CAAC,CAAC,yCAAyC;IACvE,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAE7C,gFAAgF;IAChF,8EAA8E;IAC9E,2EAA2E;IAC3E,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE;QACjB,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IACL,MAAM,UAAU,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7E,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,GAAG,CAAC,wBAAwB,SAAS,oEAAoE,CAAC,CAAC;YAC3G,OAAO;QACT,CAAC;QACD,IAAI,CAAC,SAAS,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YACpC,GAAG,CAAC,mHAAmH,CAAC,CAAC;YACzH,OAAO;QACT,CAAC;IACH,CAAC;IAED,IAAI,EAAE,GAAqB,IAAI,CAAC;IAChC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,MAAM,YAAY,GAAG,MAAM,CAAC;IAC5B,iFAAiF;IACjF,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,cAAc,GAAG,KAAK,CAAC;IAE3B,6EAA6E;IAC7E,gFAAgF;IAChF,gFAAgF;IAChF,0BAA0B;IAC1B,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,+CAA+C;IAC/C,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAoC,CAAC;IACrE,oDAAoD;IACpD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkC,CAAC;IACjE,kEAAkE;IAClE,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,iFAAiF;IACjF,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,MAAM,iBAAiB,GAAG,GAAG,EAAE;QAC7B,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;QAChF,KAAK,CAAC,mBAAmB,KAAK,IAAI,CAAC,CAAC;QACpC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC3B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,sBAAsB;IACvE,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE;QAC3B,IAAI,EAAE,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;YAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC;IAEF,0EAA0E;IAC1E,0EAA0E;IAC1E,IAAI,YAAY,GAA0C,IAAI,CAAC;IAC/D,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1B,IAAI,YAAY;YAAE,OAAO;QACzB,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;YAC9B,IAAI,cAAc;gBAAE,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;;gBACzC,WAAW,EAAE,CAAC;QACrB,CAAC,EAAE,IAAI,CAAC,CAAC;IACX,CAAC,CAAC;IACF,SAAS,WAAW;QAClB,IAAI,YAAY,EAAE,CAAC;YACjB,aAAa,CAAC,YAAY,CAAC,CAAC;YAC5B,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,IAAqB,EAAE;QAC/C,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;QAC/E,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,KAAK;YAAE,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,KAAK,EAAE,CAAC;QACxD,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;QACrD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,aAAa,EAAE;gBAChD,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;gBACpC,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YAClE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAwB,CAAC;YACvD,OAAO,IAAI,CAAC,OAAO,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF;;6EAEyE;IACzE,MAAM,YAAY,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;QAC5D,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;QACrD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,iBAAiB,EAAE;gBACpD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,OAAO,EAAE,EAAE;gBACnF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;gBACtE,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACtE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAsC,CAAC;YACrE,IAAI,CAAC,OAAO;gBAAE,GAAG,CAAC,YAAY,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;YAC7D,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YACvB,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,GAAW,EAAE,CAChC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,iBAAiB,CAAC;IAE5D,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,IAAI,MAAM;YAAE,OAAO;QACnB,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,YAAY,EAAE,CAAC;YAC/B,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,gBAAiB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9C,iBAAiB,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,GAAG,SAAS,kBAAkB,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1E,KAAK,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAC;QACtC,8EAA8E;QAC9E,6EAA6E;QAC7E,gFAAgF;QAChF,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,KAAK,iBAAiB,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAE3E,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE;YACf,OAAO,GAAG,IAAI,CAAC,CAAC,wCAAwC;YACxD,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,8DAA8D;YAC5E,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAC3B,CAAC,CAAC;QAEF,EAAE,CAAC,SAAS,GAAG,CAAC,KAAK,EAAE,EAAE;YACvB,IAAI,GAAS,CAAC;YACd,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAS,CAAC;YAC/C,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,CAAC,sBAAsB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC1B,GAAG,CAAC,kBAAkB,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;gBAClC,uEAAuE;gBACvE,oDAAoD;gBACpD,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;gBACpE,cAAc,GAAG,IAAI,CAAC;gBACtB,eAAe,GAAG,CAAC,CAAC,CAAC,2CAA2C;gBAChE,YAAY,EAAE,CAAC;gBACf,KAAK,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE;oBAChC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,IAAI,UAAU;iBACvD,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBACnC,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC7C,IAAI,OAAO;oBAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC3C,IAAI,OAAO;oBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjC,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBACnC,wEAAwE;gBACxE,sEAAsE;gBACtE,cAAc,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBACtF,KAAK,CAAC,aAAa,cAAc,YAAY,CAAC,CAAC;YACjD,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAChC,GAAG,CAAC,6CAA6C,CAAC,CAAC;gBACnD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;YACpB,CAAC;QACH,CAAC,CAAC;QAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,KAAK,CAAC,oBAAoB,CAAC,CAAC;YAC5B,WAAW,EAAE,CAAC;YACd,wEAAwE;YACxE,yEAAyE;YACzE,cAAc,GAAG,CAAC,CAAC;YACnB,wEAAwE;YACxE,4EAA4E;YAC5E,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YACnD,IAAI,QAAQ,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;gBAC7B,IAAI,EAAE,UAAU,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;oBACzC,cAAc,GAAG,IAAI,CAAC;oBACtB,GAAG,CAAC,+EAA+E,YAAY,EAAE,+FAA+F,CAAC,CAAC;gBACpM,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,UAAU,GAAG,CAAC,CAAC;gBACf,cAAc,GAAG,KAAK,CAAC;YACzB,CAAC;YACD,QAAQ,GAAG,CAAC,CAAC;YACb,yDAAyD;YACzD,KAAK,MAAM,OAAO,IAAI,gBAAgB,CAAC,MAAM,EAAE;gBAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YAChE,gBAAgB,CAAC,KAAK,EAAE,CAAC;YACzB,qDAAqD;YACrD,KAAK,MAAM,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE;gBAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YAC3D,cAAc,CAAC,KAAK,EAAE,CAAC;YACvB,iBAAiB,EAAE,CAAC;QACtB,CAAC,CAAC;QAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACpD,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,EAAU,EAAoB,EAAE,CACrD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QACtB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,KAAc,EAAE,EAAE;YAChC,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC5B,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC;QACF,gBAAgB,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACjC,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,0BAA0B;IAClF,CAAC,CAAC,CAAC;IAEL,MAAM,WAAW,GAAG,CAAC,EAAU,EAAmB,EAAE,CAClD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QACtB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE;YAC9B,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC;QACF,cAAc,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,0BAA0B;IAC1E,CAAC,CAAC,CAAC;IAEL,8EAA8E;IAC9E,0EAA0E;IAC1E,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,GAAG,EAAE;QAC1B,eAAe,GAAG,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,mCAAmC;IACnC,EAAE,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE;QAChC,MAAM,EAAE,GAAG,KAAK,EAAE,qBAAqB,CAAC;QACxC,IAAI,EAAE,EAAE,IAAI,KAAK,YAAY,IAAI,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9D,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,yEAAyE;QACzE,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,KAAK,YAAY,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YAClF,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,IAAI,CAAC;YAC7D,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM;gBAAE,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,8EAA8E;QAC9E,8EAA8E;QAC9E,MAAM,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC;QACxC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,IAAI,QAAQ,GAAG,EAAE,CAAC;YAClB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,KAAK;oBAAE,SAAS;gBACrB,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;oBAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;qBAC3F,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,QAAQ,EAAE,CAAC;oBACjI,QAAQ,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;gBAChE,CAAC;YACH,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;gBACtC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;gBAClE,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC;YACpC,CAAC;QACH,CAAC;QACD,0EAA0E;QAC1E,mDAAmD;QACnD,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC;YAC/F,KAAK,CAAC,qBAAqB,EAAE,EAAE,IAAI,YAAY,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,MAAM,GAAG,CAAC,CAAC;QAC5G,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,mCAAmC;IACnC,EAAE,CAAC,EAAE,CAAC,sBAAsB,EAAE,CAAC,KAAK,EAAE,EAAE;QACtC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,IAAI,GAAG,EAAE,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,EAAE;QACpC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,IAAI,GAAG,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,yEAAyE;IACzE,EAAE;IACF,4EAA4E;IAC5E,wEAAwE;IACxE,2EAA2E;IAC3E,0EAA0E;IAC1E,6CAA6C;IAC7C,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAyB,EAAE;QACxD,IAAI,cAAc,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC,CAAC,mDAAmD;QAC7F,MAAM,IAAI,GAAG,KAAK,EAAE,QAAQ,IAAI,KAAK,EAAE,IAAI,IAAI,MAAM,CAAC;QACtD,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC,6CAA6C;QACtE,MAAM,KAAK,GAAG,gBAAgB,IAAI,KAAK,CAAC;QACxC,GAAG,CAAC,2BAA2B,IAAI,KAAK,EAAE,GAAG,CAAC,CAAC;QAC/C,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC;QACtC,GAAG,CAAC,YAAY,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvD,OAAO,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,8EAA8E;IAC9E,6EAA6E;IAC7E,0EAA0E;IAC1E,6EAA6E;IAC7E,gEAAgE;IAChE,EAAE,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAwC,EAAE;QAChF,8EAA8E;QAC9E,2EAA2E;QAC3E,IAAI,cAAc,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QACzC,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI,KAAK,EAAE,QAAQ,IAAI,qBAAqB,CAAC;QACzE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1E,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;QACxB,GAAG,CAAC,gBAAgB,MAAM,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,CAAC;QACnC,GAAG,CAAC,UAAU,EAAE,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7C,OAAO,EAAE,IAAI,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,iCAAiC;IACjC,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;QACtB,cAAc,GAAG,KAAK,CAAC;QACvB,WAAW,EAAE,CAAC;QACd,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,8EAA8E;IAC9E,yEAAyE;IACzE,sEAAsE;IACtE,MAAM,OAAO,GAAG,CACd,IAAY,EACZ,OAAmF,EAC7E,EAAE,CACR,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE;QACvB,GAAG,OAAO;QACV,OAAO,EAAE,CAAC,IAAa,EAAE,EAAE;YACzB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC,CAAC;IAEL,OAAO,CAAC,eAAe,EAAE;QACvB,WAAW,EAAE,8CAA8C;QAC3D,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;YAClE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC;YAC3G,GAAG,CAAC,SAAS,KAAK,MAAM,SAAS,cAAc,YAAY,EAAE,WAAW,IAAI,WAAW,GAAG,EAAE,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC;QAC7G,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,CAAC,cAAc,EAAE;QACtB,WAAW,EAAE,kDAAkD;QAC/D,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC;gBACH,GAAG,CAAC,aAAa,CAAC,CAAC;gBACnB,MAAM,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;gBAC5B,GAAG,CAAC,oEAAoE,CAAC,CAAC;gBAC1E,IAAI,EAAE;oBAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,+CAA+C;;oBAC9D,KAAK,OAAO,EAAE,CAAC;YACtB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,GAAG,CAAC,iBAAiB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,CAAC,eAAe,EAAE;QACvB,WAAW,EAAE,kCAAkC;QAC/C,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,GAAG,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,oCAAoC,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC;QACzF,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,CAAC,aAAa,EAAE;QACrB,WAAW,EAAE,uEAAuE;QACpF,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,GAAG,CAAC,yCAAyC,CAAC,CAAC;gBAC/C,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC/G,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;oBACZ,GAAG,CAAC,mBAAmB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;oBACtC,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA8C,CAAC;gBAC1E,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,CAAC,UAAU,0DAA0D,YAAY,EAAE,KAAK;oBAC5F,cAAc,CAAC,CAAC,SAAS,oCAAoC,CAChE,CAAC;YACJ,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,GAAG,CAAC,kBAAkB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,CAAC,oBAAoB,EAAE;QAC5B,WAAW,EAAE,+EAA+E;QAC5F,OAAO,EAAE,KAAK,EAAE,IAAa,EAAE,EAAE;YAC/B,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnC,GAAG,CAAC,kDAAkD,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,GAAG,CAAC,yCAAyC,CAAC,CAAC;gBAC/C,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,cAAc,EAAE;oBACjD,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;oBACjF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;iBAChC,CAAC,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;oBACZ,GAAG,CAAC,uBAAuB,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;oBACnD,OAAO;gBACT,CAAC;gBACD,GAAG,CACD,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC;oBACjC,CAAC,CAAC,GAAG,KAAK,uDAAuD;oBACjE,CAAC,CAAC,aAAa,KAAK,2EAA2E,CAClG,CAAC;YACJ,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,GAAG,CAAC,sBAAsB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACzC,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,KAAK,OAAO,EAAE,CAAC;AACjB,CAAC;AAED,SAAS,cAAc,CAAC,CAAS;IAC/B,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,gFAAgF;AAChF,2EAA2E;AAC3E,+EAA+E;AAC/E,+EAA+E;AAC/E,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,0EAA0E;AAC1E,0EAA0E;AAC1E,uCAAuC;AACvC,MAAM,UAAU,eAAe,CAAC,KAA6B;IAI3D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,OAAO;QACL,IAAI,CAAC,GAAW;YACd,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,KAAK,CAAC,GAAG,CAAC,CAAC;QACb,CAAC;QACD,KAAK;YACH,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,uEAAuE;AACvE,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,IAAI,EAAE,EAAE;IACpC,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC;AACvC,CAAC,CAAC,CAAC;AACH,SAAS,GAAG,CAAC,GAAW;IACtB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjB,CAAC;AAED,+EAA+E;AAC/E,wDAAwD;AACxD,SAAS,KAAK,CAAC,GAAW;IACxB,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QAC5B,sCAAsC;QACtC,OAAO,CAAC,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC,CAAC;IACtC,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,15 +1,29 @@
1
1
  {
2
2
  "name": "@clevercloud/pi-remote-control",
3
- "version": "0.0.1",
4
- "description": "Name reserved. The pi.dev remote-control extension ships at 0.1.0+ once the relay is deployed. Do not install this placeholder.",
3
+ "version": "0.1.1",
4
+ "description": "pi.dev extension: bridge a live pi session to the remote-control relay",
5
5
  "license": "Apache-2.0",
6
- "keywords": ["pi-package", "pi-extension", "placeholder"],
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "keywords": ["pi-package", "pi-extension"],
9
+ "pi": {
10
+ "extensions": ["./dist/index.js"]
11
+ },
7
12
  "repository": {
8
13
  "type": "git",
9
14
  "url": "git+https://github.com/CleverCloud/Pi-dev-remote-controll.git"
10
15
  },
11
- "files": ["README.md"],
16
+ "files": ["dist", "README.md"],
12
17
  "publishConfig": {
13
18
  "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "watch": "tsc -p tsconfig.json --watch",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.7.0",
27
+ "typescript": "^5.6.0"
14
28
  }
15
29
  }