UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

58 lines (57 loc) • 3.25 kB
import { readFile } from "node:fs/promises"; import { homedir, userInfo } from "node:os"; import path from "node:path"; import { appendAuthorizedKey, publicComputerKey } from "./computers.js"; import { forcedCommand } from "./install.js"; import { computerDisplayName } from "./pair.js"; import { BridgeError, bridgeRoot } from "./protocol.js"; export const ENROLL_DEVICE_USAGE = "enroll-device [--device ios|android] [--name <device name>] < public-key"; /** * Adds one phone's restricted key, the same line `phren pair` writes. The * phone runs this once over an SSH session it signed into with the user's * password, then connects with the key. An identical line already present is * success (`added: false`); the same key with other options is a conflict. */ export async function enrollDevice(input, options) { try { const publicKey = publicComputerKey(input); const comment = options.device === "android" ? "phren-android" : "phren-iphone"; const line = `restrict,pty,${forcedCommand} ${publicKey} ${comment}`; const encoded = publicKey.split(" ")[1]; const file = path.join(options.sshDirectory ?? path.join(homedir(), ".ssh"), "authorized_keys"); const before = await readFile(file, "utf8").catch(() => ""); await appendAuthorizedKey(line, existing => existing.split(/\s+/).includes(encoded), "This phone key is already authorized with different options.", options.sshDirectory); const id = await readFile(path.join(options.root ?? bridgeRoot(), "computer-id"), "utf8").then(value => value.trim() || null).catch(() => null); return { v: 1, ok: true, added: !before.split("\n").includes(line), user: userInfo().username, computer: { id, name: await computerDisplayName() }, hook: options.version }; } catch (error) { const message = error.message; const code = !(error instanceof BridgeError) ? "internal" : error.status === 400 ? "bad-key" : /already running/.test(message) ? "enroll-busy" : /SSH directory/.test(message) ? "ssh-directory" : "key-conflict"; return { v: 1, ok: false, code, error: message }; } } /** `bridge-hook.mjs enroll-device`: the key on stdin, one JSON line out. */ export async function runEnrollDevice(args, version, stdin = process.stdin, options = {}) { const reply = (value) => { console.log(JSON.stringify(value)); return value.ok ? 0 : 1; }; let device = "ios"; for (let i = 0; i < args.length; i++) { if (args[i] === "--device" && (args[i + 1] === "ios" || args[i + 1] === "android")) device = args[++i]; // The phone's name only labels this run; the key's comment is fixed. else if (args[i] === "--name" && args[i + 1] !== undefined) i++; else return reply({ v: 1, ok: false, code: "usage", error: `Usage: ${ENROLL_DEVICE_USAGE}` }); } let input = ""; for await (const chunk of stdin) { input += chunk.toString(); if (input.length > 4096) return reply({ v: 1, ok: false, code: "bad-key", error: "Supply one ed25519 public key." }); } return reply(await enrollDevice(input, { device, version, ...options })); }