Package Pi Mobile for developer distribution.

Publish the hub as pi-mobile-hub with CLI flags and LAN pairing auth, add mobile deep-link pairing and EAS build config, and expand gitignore for Expo/EAS artifacts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-09 14:54:09 -05:00
co-authored by Cursor
parent 62cbc5357b
commit bb9999de58
19 changed files with 676 additions and 92 deletions
+76 -8
View File
@@ -20,6 +20,7 @@ import {
saveLastModel,
upsertSavedComputer,
} from "@/lib/api";
import { parseConnectDeepLink } from "@/lib/deeplink";
import {
createSocket,
listModels as requestModels,
@@ -38,11 +39,13 @@ import type {
ThinkingLevel,
} from "@/lib/types";
import type { Socket } from "socket.io-client";
import * as Linking from "expo-linking";
type SocketContextValue = {
host: string;
port: string;
computerName: string;
token: string;
baseUrl: string | null;
connected: boolean;
connecting: boolean;
@@ -56,8 +59,10 @@ type SocketContextValue = {
setHost: (host: string) => void;
setPort: (port: string) => void;
setComputerName: (name: string) => void;
setToken: (token: string) => void;
connect: () => Promise<boolean>;
connectToSavedComputer: (computer: SavedComputer) => Promise<boolean>;
connectFromDeepLink: (url: string) => Promise<boolean>;
removeSavedComputerEntry: (id: string) => Promise<void>;
disconnect: () => void;
refreshSessions: () => void;
@@ -89,6 +94,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
const [host, setHost] = useState("");
const [port, setPort] = useState("8787");
const [computerName, setComputerName] = useState("");
const [token, setToken] = useState("");
const [baseUrl, setBaseUrl] = useState<string | null>(null);
const [connected, setConnected] = useState(false);
const [connecting, setConnecting] = useState(false);
@@ -118,6 +124,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
setHost(recent.host);
setPort(recent.port);
setComputerName(recent.name);
setToken(recent.token ?? "");
void loadSavedProjects(recent.host, recent.port).then(setSavedProjects);
});
}, []);
@@ -168,20 +175,28 @@ export function SocketProvider({ children }: { children: ReactNode }) {
}, []);
const connectWithHost = useCallback(
async (nextHost: string, nextPort: string, nextName?: string) => {
async (
nextHost: string,
nextPort: string,
nextName?: string,
nextToken?: string,
) => {
setConnecting(true);
setError(null);
try {
const nextBaseUrl = buildBaseUrl(nextHost, nextPort);
const healthy = await checkHealth(nextBaseUrl);
const trimmedToken = nextToken?.trim();
const healthy = await checkHealth(nextBaseUrl, trimmedToken);
if (!healthy) {
throw new Error("Could not reach Pi Mobile API");
}
disconnect();
const socket = createSocket(nextBaseUrl, {
const socket = createSocket(
nextBaseUrl,
{
onConnect: () => {
setConnected(true);
setConnecting(false);
@@ -215,7 +230,9 @@ export function SocketProvider({ children }: { children: ReactNode }) {
listener(payload);
}
},
});
},
trimmedToken,
);
socketRef.current = socket;
setBaseUrl(nextBaseUrl);
@@ -224,12 +241,16 @@ export function SocketProvider({ children }: { children: ReactNode }) {
if (nextName !== undefined) {
setComputerName(nextName);
}
await saveHostConfig({ host: nextHost, port: nextPort });
if (nextToken !== undefined) {
setToken(nextToken);
}
await saveHostConfig({ host: nextHost, port: nextPort, token: trimmedToken });
const nextComputers = await upsertSavedComputer(
savedComputers,
nextHost,
nextPort,
nextName ?? computerName,
trimmedToken,
);
setSavedComputers(nextComputers);
const projects = await loadSavedProjects(nextHost, nextPort);
@@ -247,16 +268,58 @@ export function SocketProvider({ children }: { children: ReactNode }) {
);
const connect = useCallback(async () => {
return connectWithHost(host, port, computerName);
}, [connectWithHost, host, port, computerName]);
return connectWithHost(host, port, computerName, token);
}, [connectWithHost, host, port, computerName, token]);
const connectToSavedComputer = useCallback(
async (computer: SavedComputer) => {
return connectWithHost(computer.host, computer.port, computer.name);
return connectWithHost(
computer.host,
computer.port,
computer.name,
computer.token,
);
},
[connectWithHost],
);
const connectFromDeepLink = useCallback(
async (url: string) => {
const params = parseConnectDeepLink(url);
if (!params) {
return false;
}
return connectWithHost(
params.host,
params.port,
params.name ?? params.host,
params.token,
);
},
[connectWithHost],
);
useEffect(() => {
const handleDeepLink = (url: string) => {
void connectFromDeepLink(url);
};
void Linking.getInitialURL().then((url) => {
if (url) {
handleDeepLink(url);
}
});
const subscription = Linking.addEventListener("url", (event) => {
handleDeepLink(event.url);
});
return () => {
subscription.remove();
};
}, [connectFromDeepLink]);
const refreshSessions = useCallback(() => {
socketRef.current?.emit("sessions:list");
}, []);
@@ -384,6 +447,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
host,
port,
computerName,
token,
baseUrl,
connected,
connecting,
@@ -398,8 +462,10 @@ export function SocketProvider({ children }: { children: ReactNode }) {
setHost,
setPort,
setComputerName,
setToken,
connect,
connectToSavedComputer,
connectFromDeepLink,
removeSavedComputerEntry,
disconnect,
refreshSessions,
@@ -419,6 +485,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
host,
port,
computerName,
token,
baseUrl,
connected,
connecting,
@@ -432,6 +499,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
removeProject,
connect,
connectToSavedComputer,
connectFromDeepLink,
removeSavedComputerEntry,
disconnect,
refreshSessions,