@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 +89 -6
- package/dist/auth.js +183 -0
- package/dist/auth.js.map +1 -0
- package/dist/index.js +565 -0
- package/dist/index.js.map +1 -0
- package/package.json +18 -4
package/README.md
CHANGED
|
@@ -1,9 +1,92 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pi-remote-control extension
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
It contains no runnable code. **Do not install it.**
|
|
7
|
+
## Install
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
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
|
package/dist/auth.js.map
ADDED
|
@@ -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":"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"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clevercloud/pi-remote-control",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
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
|
}
|