frely-cli 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -0
- package/dist/auth.d.ts +27 -0
- package/dist/auth.js +208 -0
- package/dist/auth.js.map +1 -0
- package/dist/device/control.d.ts +10 -0
- package/dist/device/control.js +164 -0
- package/dist/device/control.js.map +1 -0
- package/dist/device/identity.d.ts +16 -0
- package/dist/device/identity.js +49 -0
- package/dist/device/identity.js.map +1 -0
- package/dist/device/protocol.d.ts +34 -0
- package/dist/device/protocol.js +76 -0
- package/dist/device/protocol.js.map +1 -0
- package/dist/device/relay-client.d.ts +6 -0
- package/dist/device/relay-client.js +159 -0
- package/dist/device/relay-client.js.map +1 -0
- package/dist/device/state.d.ts +14 -0
- package/dist/device/state.js +34 -0
- package/dist/device/state.js.map +1 -0
- package/dist/diagnostics.d.ts +25 -0
- package/dist/diagnostics.js +64 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +258 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/mcp.d.ts +3 -0
- package/dist/runtime/mcp.js +117 -0
- package/dist/runtime/mcp.js.map +1 -0
- package/dist/runtime/process-manager.d.ts +29 -0
- package/dist/runtime/process-manager.js +118 -0
- package/dist/runtime/process-manager.js.map +1 -0
- package/dist/runtime/relay-mcp.d.ts +9 -0
- package/dist/runtime/relay-mcp.js +101 -0
- package/dist/runtime/relay-mcp.js.map +1 -0
- package/dist/runtime/scheduler.d.ts +11 -0
- package/dist/runtime/scheduler.js +51 -0
- package/dist/runtime/scheduler.js.map +1 -0
- package/dist/runtime/workspace.d.ts +72 -0
- package/dist/runtime/workspace.js +318 -0
- package/dist/runtime/workspace.js.map +1 -0
- package/dist/service.d.ts +12 -0
- package/dist/service.js +142 -0
- package/dist/service.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/device/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const DEVICE_RELAY_PROTOCOL: "frely.device-relay.v1";
|
|
2
|
+
export declare const DEVICE_RELAY_MAX_FRAME_BYTES: number;
|
|
3
|
+
export declare const DEVICE_RELAY_DEFAULT_MAX_INFLIGHT = 64;
|
|
4
|
+
export type DeviceRelayRequest = {
|
|
5
|
+
protocol: typeof DEVICE_RELAY_PROTOCOL;
|
|
6
|
+
type: "request";
|
|
7
|
+
id: string;
|
|
8
|
+
method: "mcp";
|
|
9
|
+
payload: unknown;
|
|
10
|
+
};
|
|
11
|
+
export type DeviceRelayResponse = {
|
|
12
|
+
protocol: typeof DEVICE_RELAY_PROTOCOL;
|
|
13
|
+
type: "response";
|
|
14
|
+
id: string;
|
|
15
|
+
ok: boolean;
|
|
16
|
+
payload?: unknown;
|
|
17
|
+
error?: {
|
|
18
|
+
code: string;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type DeviceRelayCancel = {
|
|
23
|
+
protocol: typeof DEVICE_RELAY_PROTOCOL;
|
|
24
|
+
type: "cancel";
|
|
25
|
+
id: string;
|
|
26
|
+
};
|
|
27
|
+
export type DeviceRelayEnvelope = DeviceRelayRequest | DeviceRelayResponse | DeviceRelayCancel;
|
|
28
|
+
export declare class DeviceRelayProtocolError extends Error {
|
|
29
|
+
readonly code: "frame_invalid" | "frame_too_large" | "duplicate_request" | "inflight_limit";
|
|
30
|
+
constructor(code: "frame_invalid" | "frame_too_large" | "duplicate_request" | "inflight_limit");
|
|
31
|
+
}
|
|
32
|
+
export declare function encodeDeviceRelayEnvelope(envelope: DeviceRelayEnvelope): string;
|
|
33
|
+
export declare function decodeDeviceRelayEnvelope(input: Buffer | string): DeviceRelayEnvelope;
|
|
34
|
+
export declare function validateDeviceRelayEnvelope(value: unknown): asserts value is DeviceRelayEnvelope;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const DEVICE_RELAY_PROTOCOL = "frely.device-relay.v1";
|
|
2
|
+
export const DEVICE_RELAY_MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
3
|
+
export const DEVICE_RELAY_DEFAULT_MAX_INFLIGHT = 64;
|
|
4
|
+
export class DeviceRelayProtocolError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code) {
|
|
7
|
+
super(code);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "DeviceRelayProtocolError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function encodeDeviceRelayEnvelope(envelope) {
|
|
13
|
+
validateDeviceRelayEnvelope(envelope);
|
|
14
|
+
const encoded = JSON.stringify(envelope);
|
|
15
|
+
if (Buffer.byteLength(encoded) > DEVICE_RELAY_MAX_FRAME_BYTES)
|
|
16
|
+
throw new DeviceRelayProtocolError("frame_too_large");
|
|
17
|
+
return encoded;
|
|
18
|
+
}
|
|
19
|
+
export function decodeDeviceRelayEnvelope(input) {
|
|
20
|
+
if (Buffer.byteLength(input) > DEVICE_RELAY_MAX_FRAME_BYTES)
|
|
21
|
+
throw new DeviceRelayProtocolError("frame_too_large");
|
|
22
|
+
let value;
|
|
23
|
+
try {
|
|
24
|
+
value = JSON.parse(Buffer.isBuffer(input) ? input.toString("utf8") : input);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
28
|
+
}
|
|
29
|
+
validateDeviceRelayEnvelope(value);
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
export function validateDeviceRelayEnvelope(value) {
|
|
33
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
34
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
35
|
+
const record = value;
|
|
36
|
+
if (record.protocol !== DEVICE_RELAY_PROTOCOL || typeof record.type !== "string" || typeof record.id !== "string")
|
|
37
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
38
|
+
assertRequestId(record.id);
|
|
39
|
+
if (record.type === "request") {
|
|
40
|
+
exactKeys(record, ["protocol", "type", "id", "method", "payload"]);
|
|
41
|
+
if (record.method !== "mcp")
|
|
42
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (record.type === "cancel") {
|
|
46
|
+
exactKeys(record, ["protocol", "type", "id"]);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (record.type === "response") {
|
|
50
|
+
if (typeof record.ok !== "boolean")
|
|
51
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
52
|
+
exactKeys(record, record.ok ? ["protocol", "type", "id", "ok", "payload"] : ["protocol", "type", "id", "ok", "error"]);
|
|
53
|
+
if (!record.ok) {
|
|
54
|
+
if (!record.error || typeof record.error !== "object" || Array.isArray(record.error))
|
|
55
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
56
|
+
const error = record.error;
|
|
57
|
+
exactKeys(error, ["code", "message"]);
|
|
58
|
+
if (typeof error.code !== "string" || error.code.length < 1 || error.code.length > 64 || typeof error.message !== "string" || error.message.length > 512) {
|
|
59
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
65
|
+
}
|
|
66
|
+
function assertRequestId(id) {
|
|
67
|
+
if (!/^[A-Za-z0-9_-]{16,96}$/u.test(id))
|
|
68
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
69
|
+
}
|
|
70
|
+
function exactKeys(record, allowed) {
|
|
71
|
+
const actual = Object.keys(record).sort();
|
|
72
|
+
const expected = [...allowed].sort();
|
|
73
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index]))
|
|
74
|
+
throw new DeviceRelayProtocolError("frame_invalid");
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/device/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { connectionGrant, ensureDevice } from "./control.js";
|
|
3
|
+
import { DEVICE_RELAY_DEFAULT_MAX_INFLIGHT, DEVICE_RELAY_MAX_FRAME_BYTES, DEVICE_RELAY_PROTOCOL, decodeDeviceRelayEnvelope, encodeDeviceRelayEnvelope, } from "./protocol.js";
|
|
4
|
+
import { RelayMcpSession } from "../runtime/relay-mcp.js";
|
|
5
|
+
export async function serveDeviceRelay(options) {
|
|
6
|
+
const device = await ensureDevice();
|
|
7
|
+
const session = await RelayMcpSession.create(options.workspace);
|
|
8
|
+
const log = options.log ?? (() => undefined);
|
|
9
|
+
let delayMs = 1000;
|
|
10
|
+
try {
|
|
11
|
+
while (!options.signal.aborted) {
|
|
12
|
+
try {
|
|
13
|
+
const grant = await connectionGrant(device);
|
|
14
|
+
await serveConnection(grant.websocketUrl, grant.accessToken, device.deviceId, session, options.signal, log);
|
|
15
|
+
delayMs = 1000;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (options.signal.aborted)
|
|
19
|
+
break;
|
|
20
|
+
log(`Device Relay disconnected: ${safeMessage(error)}. Reconnecting.`);
|
|
21
|
+
await wait(delayMs, options.signal);
|
|
22
|
+
delayMs = Math.min(delayMs * 2, 15_000);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await session.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function serveConnection(websocketUrl, accessToken, deviceId, session, signal, log) {
|
|
31
|
+
await new Promise((resolve, reject) => {
|
|
32
|
+
const socket = new WebSocket(websocketUrl, DEVICE_RELAY_PROTOCOL, {
|
|
33
|
+
headers: {
|
|
34
|
+
authorization: `Bearer ${accessToken}`,
|
|
35
|
+
"x-frely-device-id": deviceId,
|
|
36
|
+
},
|
|
37
|
+
maxPayload: DEVICE_RELAY_MAX_FRAME_BYTES,
|
|
38
|
+
perMessageDeflate: false,
|
|
39
|
+
handshakeTimeout: 15_000,
|
|
40
|
+
});
|
|
41
|
+
const inflight = new Map();
|
|
42
|
+
const cancelled = new Set();
|
|
43
|
+
let finished = false;
|
|
44
|
+
const heartbeat = setInterval(() => {
|
|
45
|
+
if (socket.readyState === WebSocket.OPEN)
|
|
46
|
+
socket.ping();
|
|
47
|
+
}, 30_000);
|
|
48
|
+
heartbeat.unref?.();
|
|
49
|
+
const finish = (error) => {
|
|
50
|
+
if (finished)
|
|
51
|
+
return;
|
|
52
|
+
finished = true;
|
|
53
|
+
clearInterval(heartbeat);
|
|
54
|
+
signal.removeEventListener("abort", onAbort);
|
|
55
|
+
socket.removeAllListeners();
|
|
56
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)
|
|
57
|
+
socket.terminate();
|
|
58
|
+
if (error)
|
|
59
|
+
reject(error);
|
|
60
|
+
else
|
|
61
|
+
resolve();
|
|
62
|
+
};
|
|
63
|
+
const onAbort = () => finish();
|
|
64
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
65
|
+
socket.once("open", () => log(`MCP relay connected for device ${deviceId}.`));
|
|
66
|
+
socket.once("error", (error) => finish(error));
|
|
67
|
+
socket.once("close", (code, reason) => {
|
|
68
|
+
if (signal.aborted)
|
|
69
|
+
finish();
|
|
70
|
+
else
|
|
71
|
+
finish(new Error(`WebSocket closed (${code}${reason.length ? `: ${reason.toString()}` : ""})`));
|
|
72
|
+
});
|
|
73
|
+
socket.on("message", (data, isBinary) => {
|
|
74
|
+
if (isBinary) {
|
|
75
|
+
finish(new Error("Device Relay sent an unsupported binary control frame."));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
void handleFrame(rawDataBuffer(data), socket, inflight, cancelled, session).catch((error) => finish(error));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function handleFrame(data, socket, inflight, cancelled, session) {
|
|
83
|
+
const envelope = decodeDeviceRelayEnvelope(data);
|
|
84
|
+
if (envelope.type === "cancel") {
|
|
85
|
+
const request = inflight.get(envelope.id);
|
|
86
|
+
if (request) {
|
|
87
|
+
cancelled.add(envelope.id);
|
|
88
|
+
session.cancel(request.payload);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (envelope.type !== "request")
|
|
93
|
+
return;
|
|
94
|
+
if (inflight.has(envelope.id)) {
|
|
95
|
+
send(socket, errorResponse(envelope.id, "duplicate_request", "Duplicate Device Relay request id."));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (inflight.size >= DEVICE_RELAY_DEFAULT_MAX_INFLIGHT) {
|
|
99
|
+
send(socket, errorResponse(envelope.id, "inflight_limit", "Device Relay request window is full."));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
inflight.set(envelope.id, envelope);
|
|
103
|
+
void session.execute(envelope.payload).then((payload) => {
|
|
104
|
+
if (!cancelled.has(envelope.id))
|
|
105
|
+
send(socket, { protocol: DEVICE_RELAY_PROTOCOL, type: "response", id: envelope.id, ok: true, payload });
|
|
106
|
+
}, (error) => {
|
|
107
|
+
if (!cancelled.has(envelope.id))
|
|
108
|
+
send(socket, errorResponse(envelope.id, "mcp_execution_failed", safeMessage(error)));
|
|
109
|
+
}).finally(() => {
|
|
110
|
+
inflight.delete(envelope.id);
|
|
111
|
+
cancelled.delete(envelope.id);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function send(socket, response) {
|
|
115
|
+
if (socket.readyState !== WebSocket.OPEN)
|
|
116
|
+
return;
|
|
117
|
+
try {
|
|
118
|
+
socket.send(encodeDeviceRelayEnvelope(response));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const fallback = errorResponse(response.id, "response_too_large", "MCP response exceeds the Device Relay frame limit.");
|
|
122
|
+
socket.send(encodeDeviceRelayEnvelope(fallback));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function errorResponse(id, code, message) {
|
|
126
|
+
return {
|
|
127
|
+
protocol: DEVICE_RELAY_PROTOCOL,
|
|
128
|
+
type: "response",
|
|
129
|
+
id,
|
|
130
|
+
ok: false,
|
|
131
|
+
error: { code: code.slice(0, 64), message: message.slice(0, 512) },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function rawDataBuffer(data) {
|
|
135
|
+
if (Buffer.isBuffer(data))
|
|
136
|
+
return data;
|
|
137
|
+
if (data instanceof ArrayBuffer)
|
|
138
|
+
return Buffer.from(data);
|
|
139
|
+
return Buffer.concat(data);
|
|
140
|
+
}
|
|
141
|
+
function safeMessage(error) {
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
return message.replace(/[\r\n]+/gu, " ").slice(0, 240);
|
|
144
|
+
}
|
|
145
|
+
async function wait(ms, signal) {
|
|
146
|
+
if (signal.aborted)
|
|
147
|
+
return;
|
|
148
|
+
await new Promise((resolve) => {
|
|
149
|
+
const timer = setTimeout(done, ms);
|
|
150
|
+
const onAbort = () => done();
|
|
151
|
+
function done() {
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
signal.removeEventListener("abort", onAbort);
|
|
154
|
+
resolve();
|
|
155
|
+
}
|
|
156
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=relay-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-client.js","sourceRoot":"","sources":["../../src/device/relay-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DeviceBinding {
|
|
2
|
+
version: 1;
|
|
3
|
+
relayUrl: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
deviceId: string;
|
|
6
|
+
publicKeySpki: string;
|
|
7
|
+
keyThumbprint: string;
|
|
8
|
+
mcpUrl: string;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function deviceStatePath(): string;
|
|
12
|
+
export declare function readDeviceBinding(): Promise<DeviceBinding | null>;
|
|
13
|
+
export declare function writeDeviceBinding(binding: DeviceBinding): Promise<void>;
|
|
14
|
+
export declare function clearDeviceBinding(): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export function deviceStatePath() {
|
|
5
|
+
const root = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
6
|
+
return join(root, "frely", "device.json");
|
|
7
|
+
}
|
|
8
|
+
export async function readDeviceBinding() {
|
|
9
|
+
const raw = await readFile(deviceStatePath(), "utf8").catch(() => null);
|
|
10
|
+
if (!raw)
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const value = JSON.parse(raw);
|
|
14
|
+
if (value.version !== 1 || typeof value.relayUrl !== "string" || typeof value.userId !== "string" || typeof value.deviceId !== "string" || typeof value.publicKeySpki !== "string" || typeof value.keyThumbprint !== "string" || typeof value.mcpUrl !== "string")
|
|
15
|
+
return null;
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function writeDeviceBinding(binding) {
|
|
23
|
+
const path = deviceStatePath();
|
|
24
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
25
|
+
await chmod(dirname(path), 0o700).catch(() => undefined);
|
|
26
|
+
const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
27
|
+
await writeFile(temp, `${JSON.stringify(binding, null, 2)}\n`, { flag: "wx", mode: 0o600 });
|
|
28
|
+
await rename(temp, path);
|
|
29
|
+
await chmod(path, 0o600).catch(() => undefined);
|
|
30
|
+
}
|
|
31
|
+
export async function clearDeviceBinding() {
|
|
32
|
+
await unlink(deviceStatePath()).catch(() => undefined);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/device/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAa1C,MAAM,UAAU,eAAe;IAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,eAAe,EAAE,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACxE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA2B,CAAC;QACxD,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC/Q,OAAO,KAAsB,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAsB;IAC7D,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC;IACxD,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5F,MAAM,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzB,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface DiagnosticCheck {
|
|
2
|
+
name: string;
|
|
3
|
+
ok: boolean;
|
|
4
|
+
detail: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function statusSnapshot(): Promise<{
|
|
7
|
+
version: string;
|
|
8
|
+
node: string;
|
|
9
|
+
platform: NodeJS.Platform;
|
|
10
|
+
auth: import("./auth.js").AuthSnapshot;
|
|
11
|
+
device: {
|
|
12
|
+
deviceId: string;
|
|
13
|
+
keyThumbprint: string;
|
|
14
|
+
provisioned: boolean;
|
|
15
|
+
} | null;
|
|
16
|
+
service: import("./service.js").McpServiceInfo | {
|
|
17
|
+
installed: boolean;
|
|
18
|
+
active: boolean;
|
|
19
|
+
platform: NodeJS.Platform;
|
|
20
|
+
};
|
|
21
|
+
}>;
|
|
22
|
+
export declare function doctor(): Promise<{
|
|
23
|
+
ok: boolean;
|
|
24
|
+
checks: DiagnosticCheck[];
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { inspectAuth, probeCredentialStore, whoami } from "./auth.js";
|
|
2
|
+
import { currentDevice } from "./device/control.js";
|
|
3
|
+
import { serviceStatus } from "./service.js";
|
|
4
|
+
export async function statusSnapshot() {
|
|
5
|
+
const auth = await inspectAuth();
|
|
6
|
+
let device = null;
|
|
7
|
+
if (auth.configured && auth.credentialStored)
|
|
8
|
+
device = await currentDevice().catch(() => null);
|
|
9
|
+
const service = await serviceStatus().catch(() => ({ installed: false, active: false, platform: process.platform }));
|
|
10
|
+
return {
|
|
11
|
+
version: "0.3.0",
|
|
12
|
+
node: process.version,
|
|
13
|
+
platform: process.platform,
|
|
14
|
+
auth,
|
|
15
|
+
device: device ? { deviceId: device.deviceId, keyThumbprint: device.keyThumbprint, provisioned: true } : null,
|
|
16
|
+
service,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export async function doctor() {
|
|
20
|
+
const checks = [];
|
|
21
|
+
const major = Number(process.versions.node.split(".", 1)[0]);
|
|
22
|
+
checks.push({ name: "node", ok: major >= 22, detail: process.version });
|
|
23
|
+
try {
|
|
24
|
+
await probeCredentialStore();
|
|
25
|
+
checks.push({ name: "credential_store", ok: true, detail: "read/write/delete succeeded" });
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
checks.push({ name: "credential_store", ok: false, detail: message(error) });
|
|
29
|
+
}
|
|
30
|
+
const auth = await inspectAuth();
|
|
31
|
+
checks.push({
|
|
32
|
+
name: "config",
|
|
33
|
+
ok: auth.configured && (auth.configMode === undefined || auth.configMode === 0o600),
|
|
34
|
+
detail: auth.configured ? `${auth.configPath}${auth.configMode === undefined ? "" : ` mode=${auth.configMode.toString(8)}`}` : "not configured",
|
|
35
|
+
});
|
|
36
|
+
checks.push({ name: "credential", ok: auth.credentialStored, detail: auth.credentialStored ? "stored" : "missing" });
|
|
37
|
+
if (auth.configured && auth.credentialStored) {
|
|
38
|
+
try {
|
|
39
|
+
const user = await whoami();
|
|
40
|
+
checks.push({ name: "relay_session", ok: true, detail: `${user.email} @ ${auth.relayUrl}` });
|
|
41
|
+
const device = await currentDevice();
|
|
42
|
+
checks.push({ name: "mcp_device", ok: Boolean(device), detail: device ? `${device.deviceId} provisioned` : "not provisioned; run frely mcp url" });
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
checks.push({ name: "relay_session", ok: false, detail: message(error) });
|
|
46
|
+
checks.push({ name: "mcp_device", ok: false, detail: "unavailable until login is valid" });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
checks.push({ name: "relay_session", ok: false, detail: "login required" });
|
|
51
|
+
checks.push({ name: "mcp_device", ok: false, detail: "login required" });
|
|
52
|
+
}
|
|
53
|
+
const service = await serviceStatus().catch((error) => ({ installed: false, active: false, platform: process.platform, error: message(error) }));
|
|
54
|
+
checks.push({
|
|
55
|
+
name: "mcp_service",
|
|
56
|
+
ok: service.installed && service.active,
|
|
57
|
+
detail: "error" in service ? service.error : service.active ? `running${service.workspace ? ` workspace=${service.workspace}` : ""}` : service.installed ? "installed but stopped" : "not installed; run frely mcp setup",
|
|
58
|
+
});
|
|
59
|
+
return { ok: checks.every((check) => check.ok), checks };
|
|
60
|
+
}
|
|
61
|
+
function message(error) {
|
|
62
|
+
return error instanceof Error ? error.message : String(error);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED