frely-cli 0.23.8 → 0.23.10
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 +50 -20
- package/README.zh-CN.md +47 -19
- package/dist/agent/local/approvals.d.ts +24 -0
- package/dist/agent/local/approvals.js +79 -0
- package/dist/agent/local/approvals.js.map +1 -0
- package/dist/agent/local/command.d.ts +10 -0
- package/dist/agent/local/command.js +292 -0
- package/dist/agent/local/command.js.map +1 -0
- package/dist/agent/local/confine.d.ts +20 -0
- package/dist/agent/local/confine.js +52 -0
- package/dist/agent/local/confine.js.map +1 -0
- package/dist/agent/local/manifest.d.ts +17 -0
- package/dist/agent/local/manifest.js +55 -0
- package/dist/agent/local/manifest.js.map +1 -0
- package/dist/agent/local/model-proxy.d.ts +18 -0
- package/dist/agent/local/model-proxy.js +111 -0
- package/dist/agent/local/model-proxy.js.map +1 -0
- package/dist/agent/local/source.d.ts +28 -0
- package/dist/agent/local/source.js +64 -0
- package/dist/agent/local/source.js.map +1 -0
- package/dist/agent/local/store.d.ts +45 -0
- package/dist/agent/local/store.js +78 -0
- package/dist/agent/local/store.js.map +1 -0
- package/dist/agent/local/supervisor.d.ts +9 -0
- package/dist/agent/local/supervisor.js +62 -0
- package/dist/agent/local/supervisor.js.map +1 -0
- package/dist/agent-help.d.ts +294 -18
- package/dist/agent-help.js +56 -7
- package/dist/agent-help.js.map +1 -1
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +36 -0
- package/dist/auth.js.map +1 -1
- package/dist/cloud.d.ts +2 -0
- package/dist/cloud.js +38 -0
- package/dist/cloud.js.map +1 -1
- package/dist/controller/args.d.ts +25 -0
- package/dist/controller/args.js +61 -0
- package/dist/controller/args.js.map +1 -0
- package/dist/controller/client.d.ts +52 -0
- package/dist/controller/client.js +124 -0
- package/dist/controller/client.js.map +1 -0
- package/dist/controller/commands.d.ts +16 -0
- package/dist/controller/commands.js +411 -0
- package/dist/controller/commands.js.map +1 -0
- package/dist/controller/dpop.d.ts +24 -0
- package/dist/controller/dpop.js +26 -0
- package/dist/controller/dpop.js.map +1 -0
- package/dist/controller/session.d.ts +53 -0
- package/dist/controller/session.js +211 -0
- package/dist/controller/session.js.map +1 -0
- package/dist/device/protocol.d.ts +1 -1
- package/dist/device/protocol.js +1 -1
- package/dist/device/protocol.js.map +1 -1
- package/dist/device/relay-client.d.ts +14 -0
- package/dist/device/relay-client.js +34 -14
- package/dist/device/relay-client.js.map +1 -1
- package/dist/index.js +79 -8
- package/dist/index.js.map +1 -1
- package/dist/provider/control.d.ts +10 -0
- package/dist/provider/control.js +19 -0
- package/dist/provider/control.js.map +1 -1
- package/dist/provider/share.js +4 -1
- package/dist/provider/share.js.map +1 -1
- package/dist/runtime/sandbox.d.ts +20 -0
- package/dist/runtime/sandbox.js +39 -1
- package/dist/runtime/sandbox.js.map +1 -1
- package/dist/share/allowance.d.ts +8 -0
- package/dist/share/allowance.js +22 -0
- package/dist/share/allowance.js.map +1 -0
- package/dist/share/control.d.ts +18 -0
- package/dist/share/control.js +53 -0
- package/dist/share/control.js.map +1 -0
- package/dist/share/frpc-config.d.ts +24 -0
- package/dist/share/frpc-config.js +63 -0
- package/dist/share/frpc-config.js.map +1 -0
- package/dist/share/frpc.d.ts +30 -0
- package/dist/share/frpc.js +108 -0
- package/dist/share/frpc.js.map +1 -0
- package/dist/share/gateway.d.ts +26 -0
- package/dist/share/gateway.js +100 -0
- package/dist/share/gateway.js.map +1 -0
- package/dist/share/run.d.ts +43 -0
- package/dist/share/run.js +163 -0
- package/dist/share/run.js.map +1 -0
- package/dist/share/service-config.d.ts +39 -0
- package/dist/share/service-config.js +85 -0
- package/dist/share/service-config.js.map +1 -0
- package/dist/share/service-control.d.ts +15 -0
- package/dist/share/service-control.js +31 -0
- package/dist/share/service-control.js.map +1 -0
- package/dist/share/service-run.d.ts +55 -0
- package/dist/share/service-run.js +285 -0
- package/dist/share/service-run.js.map +1 -0
- package/dist/share/service-state.d.ts +16 -0
- package/dist/share/service-state.js +66 -0
- package/dist/share/service-state.js.map +1 -0
- package/dist/skill/access.d.ts +2 -2
- package/dist/skill/access.js +4 -2
- package/dist/skill/access.js.map +1 -1
- package/dist/skill/cloud-item.d.ts +2 -2
- package/dist/skill/cloud-item.js.map +1 -1
- package/dist/skill/entry.d.ts +1 -1
- package/dist/skill/entry.js +1 -1
- package/dist/skill/remote-mcp.d.ts +3 -0
- package/dist/skill/remote-mcp.js +7 -1
- package/dist/skill/remote-mcp.js.map +1 -1
- package/dist/skill/router.d.ts +6 -2
- package/dist/skill/router.js +4 -4
- package/dist/skill/router.js.map +1 -1
- package/dist/skill/sealed-run.js +5 -3
- package/dist/skill/sealed-run.js.map +1 -1
- package/dist/uninstall.d.ts +4 -0
- package/dist/uninstall.js +14 -0
- package/dist/uninstall.js.map +1 -1
- package/package.json +3 -2
- package/skills/frely-cli/SKILL.md +8 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type IncomingHttpHeaders, type IncomingMessage, type Server } from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* The local gate of a share: frpc points only at this loopback server, never at the user's service. It serves a request only when the Host
|
|
4
|
+
* is the share's own name, refuses `CONNECT` and absolute-form targets (so the share cannot be used as a proxy), drops Frely's and frp's
|
|
5
|
+
* internal headers, and forwards to the one registered loopback port without following redirects. WebSocket upgrades are passed through
|
|
6
|
+
* as bytes. The link token is checked at the edge (the share Caddy), this is the second layer.
|
|
7
|
+
*/
|
|
8
|
+
export interface GatewayOptions {
|
|
9
|
+
/** The share's public host name, e.g. `s0123456789abcd.share.example`. */
|
|
10
|
+
readonly host: string;
|
|
11
|
+
/** The user's service on 127.0.0.1. */
|
|
12
|
+
readonly targetPort: number;
|
|
13
|
+
/** Send `Host: localhost:<port>` upstream; dev servers such as Vite refuse unknown host names. */
|
|
14
|
+
readonly rewriteHost?: boolean;
|
|
15
|
+
readonly maxConnections?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function hostOnly(value: string | undefined): string;
|
|
18
|
+
/** The headers forwarded upstream: no hop-by-hop or internal headers, and the forwarding facts set here. */
|
|
19
|
+
export declare function upstreamHeaders(headers: IncomingHttpHeaders, options: GatewayOptions, keepUpgrade?: boolean): Record<string, string | string[]>;
|
|
20
|
+
/** A request for another host, an absolute-form target or a path that is not a path is not served. */
|
|
21
|
+
export declare function admissible(request: Pick<IncomingMessage, "headers" | "url" | "method">, host: string): string | null;
|
|
22
|
+
export declare function startGateway(options: GatewayOptions): Promise<{
|
|
23
|
+
server: Server;
|
|
24
|
+
port: number;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createServer, request as httpRequest } from "node:http";
|
|
2
|
+
import { connect as netConnect } from "node:net";
|
|
3
|
+
const HOP_BY_HOP = new Set(["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "proxy-connection", "te", "trailer", "transfer-encoding", "upgrade"]);
|
|
4
|
+
const INTERNAL = /^(x-frely-|x-frp-|x-forwarded-(host|proto|port|server))/u;
|
|
5
|
+
export function hostOnly(value) {
|
|
6
|
+
return (value ?? "").toLowerCase().replace(/:\d+$/u, "");
|
|
7
|
+
}
|
|
8
|
+
/** The headers forwarded upstream: no hop-by-hop or internal headers, and the forwarding facts set here. */
|
|
9
|
+
export function upstreamHeaders(headers, options, keepUpgrade = false) {
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
12
|
+
if (value === undefined || INTERNAL.test(name))
|
|
13
|
+
continue;
|
|
14
|
+
if (HOP_BY_HOP.has(name) && !(keepUpgrade && (name === "upgrade" || name === "connection")))
|
|
15
|
+
continue;
|
|
16
|
+
out[name] = value;
|
|
17
|
+
}
|
|
18
|
+
out["x-forwarded-host"] = options.host;
|
|
19
|
+
out["x-forwarded-proto"] = "https";
|
|
20
|
+
if (options.rewriteHost)
|
|
21
|
+
out.host = `localhost:${options.targetPort}`;
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function refuse(response, status, message) {
|
|
25
|
+
if (response.headersSent) {
|
|
26
|
+
response.destroy();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
response.writeHead(status, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
|
|
30
|
+
response.end(`${message}\n`);
|
|
31
|
+
}
|
|
32
|
+
/** A request for another host, an absolute-form target or a path that is not a path is not served. */
|
|
33
|
+
export function admissible(request, host) {
|
|
34
|
+
if (hostOnly(request.headers.host) !== host)
|
|
35
|
+
return "wrong host";
|
|
36
|
+
if (!request.url || !request.url.startsWith("/") || request.url.startsWith("//"))
|
|
37
|
+
return "bad target";
|
|
38
|
+
if (request.method === "CONNECT")
|
|
39
|
+
return "method not allowed";
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
export function startGateway(options) {
|
|
43
|
+
const host = options.host.toLowerCase();
|
|
44
|
+
const server = createServer((request, response) => {
|
|
45
|
+
const problem = admissible(request, host);
|
|
46
|
+
if (problem)
|
|
47
|
+
return refuse(response, problem === "method not allowed" ? 405 : 404, "Not found");
|
|
48
|
+
const upstream = httpRequest({ host: "127.0.0.1", port: options.targetPort, method: request.method, path: request.url, headers: upstreamHeaders(request.headers, options) }, (reply) => {
|
|
49
|
+
const headers = {};
|
|
50
|
+
for (const [name, value] of Object.entries(reply.headers))
|
|
51
|
+
if (value !== undefined && !HOP_BY_HOP.has(name))
|
|
52
|
+
headers[name] = value;
|
|
53
|
+
response.writeHead(reply.statusCode ?? 502, reply.statusMessage, headers);
|
|
54
|
+
reply.pipe(response);
|
|
55
|
+
});
|
|
56
|
+
upstream.once("error", () => refuse(response, 502, "The shared service is not reachable on this computer."));
|
|
57
|
+
response.once("close", () => upstream.destroy());
|
|
58
|
+
request.pipe(upstream);
|
|
59
|
+
});
|
|
60
|
+
server.on("connect", (_request, socket) => { socket.end("HTTP/1.1 405 Method Not Allowed\r\nconnection: close\r\n\r\n"); });
|
|
61
|
+
server.on("upgrade", (request, socket, head) => {
|
|
62
|
+
if (admissible(request, host)) {
|
|
63
|
+
socket.end("HTTP/1.1 404 Not Found\r\nconnection: close\r\n\r\n");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const upstream = netConnect({ host: "127.0.0.1", port: options.targetPort });
|
|
67
|
+
const close = () => { socket.destroy(); upstream.destroy(); };
|
|
68
|
+
upstream.once("error", () => { if (!socket.destroyed)
|
|
69
|
+
socket.end("HTTP/1.1 502 Bad Gateway\r\nconnection: close\r\n\r\n"); upstream.destroy(); });
|
|
70
|
+
socket.once("error", close);
|
|
71
|
+
upstream.once("connect", () => {
|
|
72
|
+
const headers = upstreamHeaders(request.headers, options, true);
|
|
73
|
+
const lines = [`${request.method} ${request.url} HTTP/1.1`];
|
|
74
|
+
for (const [name, value] of Object.entries(headers))
|
|
75
|
+
for (const item of Array.isArray(value) ? value : [value])
|
|
76
|
+
if (!/[\r\n]/u.test(item))
|
|
77
|
+
lines.push(`${name}: ${item}`);
|
|
78
|
+
upstream.write(`${lines.join("\r\n")}\r\n\r\n`);
|
|
79
|
+
if (head.length > 0)
|
|
80
|
+
upstream.write(head);
|
|
81
|
+
upstream.pipe(socket);
|
|
82
|
+
socket.pipe(upstream);
|
|
83
|
+
});
|
|
84
|
+
upstream.once("close", close);
|
|
85
|
+
socket.once("close", close);
|
|
86
|
+
});
|
|
87
|
+
server.maxConnections = options.maxConnections ?? 256;
|
|
88
|
+
server.requestTimeout = 0;
|
|
89
|
+
server.headersTimeout = 30_000;
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
server.once("error", reject);
|
|
92
|
+
server.listen(0, "127.0.0.1", () => {
|
|
93
|
+
const address = server.address();
|
|
94
|
+
if (!address || typeof address === "string")
|
|
95
|
+
return reject(new Error("The share gateway has no port."));
|
|
96
|
+
resolve({ server, port: address.port, close: () => new Promise((done) => { server.close(() => done()); server.closeAllConnections(); }) });
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/share/gateway.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { startGateway } from "./gateway.js";
|
|
2
|
+
import { type ShareControl } from "./control.js";
|
|
3
|
+
export interface ShareOptions {
|
|
4
|
+
readonly port: number;
|
|
5
|
+
/** Lifetime of the link in hours; the relay caps it at 24. */
|
|
6
|
+
readonly ttlHours?: number;
|
|
7
|
+
readonly rewriteHost?: boolean;
|
|
8
|
+
/** Skip the confirmation question. */
|
|
9
|
+
readonly yes?: boolean;
|
|
10
|
+
readonly json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface FrpcProcess {
|
|
13
|
+
readonly done: Promise<{
|
|
14
|
+
code: number | null;
|
|
15
|
+
stderr: string;
|
|
16
|
+
}>;
|
|
17
|
+
kill(): void;
|
|
18
|
+
}
|
|
19
|
+
export interface ShareDeps {
|
|
20
|
+
readonly control: () => Promise<ShareControl>;
|
|
21
|
+
readonly ensureFrpc: () => Promise<string>;
|
|
22
|
+
readonly startGateway: typeof startGateway;
|
|
23
|
+
readonly spawnFrpc: (binary: string, configPath: string) => FrpcProcess;
|
|
24
|
+
readonly probe: (port: number) => Promise<boolean>;
|
|
25
|
+
/** Asks the human; null when there is no terminal to ask on. */
|
|
26
|
+
readonly confirm: ((question: string) => Promise<boolean>) | null;
|
|
27
|
+
readonly write: (text: string) => void;
|
|
28
|
+
readonly signal: AbortSignal;
|
|
29
|
+
readonly heartbeatMs?: number;
|
|
30
|
+
/** How long the config file (it holds the session token) stays on disk after frpc started. */
|
|
31
|
+
readonly configLifetimeMs?: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function parseShareArguments(args: readonly string[]): ShareOptions;
|
|
34
|
+
export declare function shareUsage(): string;
|
|
35
|
+
/** Shares http://127.0.0.1:<port> through Frely's relay until Ctrl+C, the link expires, the allowance is used up or Frely revokes it. Returns the exit code. */
|
|
36
|
+
export declare function runShare(options: ShareOptions, deps: ShareDeps): Promise<number>;
|
|
37
|
+
export declare function probeLoopback(port: number): Promise<boolean>;
|
|
38
|
+
export declare function spawnFrpcProcess(binary: string, configPath: string): FrpcProcess;
|
|
39
|
+
export declare function runShareCommand(args: readonly string[], io: {
|
|
40
|
+
write: (text: string) => void;
|
|
41
|
+
interactive: boolean;
|
|
42
|
+
prompt?: (question: string) => Promise<string>;
|
|
43
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { connect } from "node:net";
|
|
3
|
+
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { frpcConfigToml } from "./frpc-config.js";
|
|
7
|
+
import { ensureFrpc } from "./frpc.js";
|
|
8
|
+
import { startGateway } from "./gateway.js";
|
|
9
|
+
import { accountShareControl } from "./control.js";
|
|
10
|
+
const HEARTBEAT_MS = 30_000;
|
|
11
|
+
const HEARTBEAT_FAILURES = 3;
|
|
12
|
+
const MIB = 1024 * 1024;
|
|
13
|
+
export function parseShareArguments(args) {
|
|
14
|
+
let port, ttlHours;
|
|
15
|
+
let rewriteHost = false, yes = false, json = false;
|
|
16
|
+
for (let i = 0; i < args.length; i++) {
|
|
17
|
+
const arg = args[i];
|
|
18
|
+
if (arg === "--ttl" || arg.startsWith("--ttl=")) {
|
|
19
|
+
const value = arg === "--ttl" ? args[++i] : arg.slice("--ttl=".length);
|
|
20
|
+
ttlHours = Number(value);
|
|
21
|
+
if (!value || !Number.isFinite(ttlHours) || ttlHours <= 0 || ttlHours > 24)
|
|
22
|
+
throw new Error("--ttl must be a number of hours from 0.02 to 24.");
|
|
23
|
+
}
|
|
24
|
+
else if (arg === "--rewrite-host")
|
|
25
|
+
rewriteHost = true;
|
|
26
|
+
else if (arg === "--yes" || arg === "-y")
|
|
27
|
+
yes = true;
|
|
28
|
+
else if (arg === "--json")
|
|
29
|
+
json = true;
|
|
30
|
+
else if (arg.startsWith("-"))
|
|
31
|
+
throw new Error(`Unknown option ${arg}.\n${shareUsage()}`);
|
|
32
|
+
else if (port === undefined) {
|
|
33
|
+
port = Number(arg);
|
|
34
|
+
if (!/^\d+$/u.test(arg) || port < 1 || port > 65535)
|
|
35
|
+
throw new Error(`Usage: ${shareUsage()}`);
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
throw new Error(`Usage: ${shareUsage()}`);
|
|
39
|
+
}
|
|
40
|
+
if (port === undefined)
|
|
41
|
+
throw new Error(`Usage: ${shareUsage()}`);
|
|
42
|
+
return { port, ...(ttlHours !== undefined ? { ttlHours } : {}), rewriteHost, yes, json };
|
|
43
|
+
}
|
|
44
|
+
export function shareUsage() {
|
|
45
|
+
return "frely share <port> [--ttl <hours>] [--rewrite-host] [--yes] [--json]";
|
|
46
|
+
}
|
|
47
|
+
/** Shares http://127.0.0.1:<port> through Frely's relay until Ctrl+C, the link expires, the allowance is used up or Frely revokes it. Returns the exit code. */
|
|
48
|
+
export async function runShare(options, deps) {
|
|
49
|
+
const say = (text) => { if (!options.json)
|
|
50
|
+
deps.write(text); };
|
|
51
|
+
if (!(await deps.probe(options.port)))
|
|
52
|
+
throw new Error(`Nothing is listening on 127.0.0.1:${options.port}. Start your service first.`);
|
|
53
|
+
if (!options.yes) {
|
|
54
|
+
if (!deps.confirm)
|
|
55
|
+
throw new Error("frely share makes a local service public, so it asks you first. Run it in a terminal, or pass --yes if you intend this.");
|
|
56
|
+
if (!(await deps.confirm(`Make http://127.0.0.1:${options.port} reachable from the Internet by link through Frely? [y/N] `))) {
|
|
57
|
+
say("Cancelled.\n");
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const binary = await deps.ensureFrpc();
|
|
62
|
+
const control = await deps.control();
|
|
63
|
+
const grant = await control.create({ ...(options.ttlHours !== undefined ? { ttlSeconds: Math.round(options.ttlHours * 3600) } : {}) });
|
|
64
|
+
let gateway, frpc, directory;
|
|
65
|
+
let heartbeat, expiry, configTimer;
|
|
66
|
+
try {
|
|
67
|
+
gateway = await deps.startGateway({ host: new URL(grant.url).hostname, targetPort: options.port, ...(options.rewriteHost ? { rewriteHost: true } : {}) });
|
|
68
|
+
directory = await mkdtemp(join(tmpdir(), "frely-share-"));
|
|
69
|
+
await chmod(directory, 0o700).catch(() => undefined);
|
|
70
|
+
const configPath = join(directory, "frpc.toml");
|
|
71
|
+
await writeFile(configPath, frpcConfigToml(grant, gateway.port), { mode: 0o600 });
|
|
72
|
+
frpc = deps.spawnFrpc(binary, configPath);
|
|
73
|
+
configTimer = setTimeout(() => { void rm(configPath, { force: true }).catch(() => undefined); }, deps.configLifetimeMs ?? 3000);
|
|
74
|
+
announce(grant, options, deps.write);
|
|
75
|
+
const stopped = new Promise((resolve) => {
|
|
76
|
+
let failures = 0;
|
|
77
|
+
const stop = (reason) => resolve(reason);
|
|
78
|
+
deps.signal.addEventListener("abort", () => stop("Stopped."), { once: true });
|
|
79
|
+
void frpc.done.then(({ code, stderr }) => stop(`frpc exited${code === null ? "" : ` with code ${code}`}${stderr.trim() ? `: ${stderr.trim().split("\n").slice(-3).join(" | ")}` : "."}`));
|
|
80
|
+
expiry = setTimeout(() => stop("The link expired."), Math.max(0, Date.parse(grant.expiresAt) - Date.now()));
|
|
81
|
+
heartbeat = setInterval(() => {
|
|
82
|
+
void control.heartbeat(grant.id).then((status) => {
|
|
83
|
+
failures = 0;
|
|
84
|
+
if (!status.active)
|
|
85
|
+
stop(describe(status.reason));
|
|
86
|
+
}, () => { failures += 1; if (failures >= HEARTBEAT_FAILURES)
|
|
87
|
+
stop("Frely could not be reached."); });
|
|
88
|
+
}, deps.heartbeatMs ?? HEARTBEAT_MS);
|
|
89
|
+
});
|
|
90
|
+
const reason = await stopped;
|
|
91
|
+
say(`\n${reason}\n`);
|
|
92
|
+
return reason === "Stopped." ? 0 : 1;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
if (heartbeat)
|
|
96
|
+
clearInterval(heartbeat);
|
|
97
|
+
if (expiry)
|
|
98
|
+
clearTimeout(expiry);
|
|
99
|
+
if (configTimer)
|
|
100
|
+
clearTimeout(configTimer);
|
|
101
|
+
frpc?.kill();
|
|
102
|
+
await gateway?.close().catch(() => undefined);
|
|
103
|
+
if (directory)
|
|
104
|
+
await rm(directory, { recursive: true, force: true }).catch(() => undefined);
|
|
105
|
+
await control.revoke(grant.id).catch(() => undefined);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function describe(reason) {
|
|
109
|
+
if (reason === "quota_exhausted")
|
|
110
|
+
return "This week's relay allowance is used up; it renews on Monday (UTC).";
|
|
111
|
+
if (reason === "expired")
|
|
112
|
+
return "The link expired.";
|
|
113
|
+
return `Frely ended this share (${reason ?? "revoked"}).`;
|
|
114
|
+
}
|
|
115
|
+
function announce(grant, options, write) {
|
|
116
|
+
if (options.json) {
|
|
117
|
+
write(`${JSON.stringify({ id: grant.id, url: grant.url, expiresAt: grant.expiresAt, bandwidthBytesPerSecond: grant.bandwidthBytesPerSecond, remainingBytes: grant.remainingBytes })}\n`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
write(`Sharing http://127.0.0.1:${options.port}\n ${grant.url}\n`);
|
|
121
|
+
write(`Up to ${(grant.bandwidthBytesPerSecond / MIB).toFixed(1)} MiB/s; ${Math.floor(grant.remainingBytes / MIB)} MiB of relay left this week (UTC). Expires ${grant.expiresAt}.\n`);
|
|
122
|
+
write("Anyone with the link can open it. Press Ctrl+C to stop.\n");
|
|
123
|
+
}
|
|
124
|
+
export function probeLoopback(port) {
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
const socket = connect({ host: "127.0.0.1", port });
|
|
127
|
+
const done = (ok) => { socket.destroy(); resolve(ok); };
|
|
128
|
+
socket.setTimeout(2000, () => done(false));
|
|
129
|
+
socket.once("connect", () => done(true));
|
|
130
|
+
socket.once("error", () => done(false));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export function spawnFrpcProcess(binary, configPath) {
|
|
134
|
+
const child = spawn(binary, ["-c", configPath], { stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
|
|
135
|
+
let stderr = "";
|
|
136
|
+
child.stderr.on("data", (chunk) => { stderr = (stderr + chunk.toString("utf8")).slice(-4000); });
|
|
137
|
+
const done = new Promise((resolve) => {
|
|
138
|
+
child.once("error", (error) => resolve({ code: null, stderr: error.message }));
|
|
139
|
+
child.once("exit", (code) => resolve({ code, stderr }));
|
|
140
|
+
});
|
|
141
|
+
return { done, kill: () => { child.kill(); } };
|
|
142
|
+
}
|
|
143
|
+
export async function runShareCommand(args, io) {
|
|
144
|
+
const options = parseShareArguments(args);
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const onSignal = () => controller.abort();
|
|
147
|
+
process.once("SIGINT", onSignal);
|
|
148
|
+
process.once("SIGTERM", onSignal);
|
|
149
|
+
try {
|
|
150
|
+
return await runShare(options, {
|
|
151
|
+
control: accountShareControl,
|
|
152
|
+
ensureFrpc: () => ensureFrpc({ log: io.write }),
|
|
153
|
+
startGateway, spawnFrpc: spawnFrpcProcess, probe: probeLoopback,
|
|
154
|
+
confirm: io.interactive && io.prompt ? async (question) => /^y(es)?$/iu.test((await io.prompt(question)).trim()) : null,
|
|
155
|
+
write: io.write, signal: controller.signal,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
process.off("SIGINT", onSignal);
|
|
160
|
+
process.off("SIGTERM", onSignal);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/share/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** What the relay returns for a service session (`POST /api/user/device-relay/services`, action session). */
|
|
2
|
+
export interface ServiceGrant {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly serviceShareId: string;
|
|
5
|
+
readonly expiresAt: string;
|
|
6
|
+
readonly node: string;
|
|
7
|
+
readonly bandwidthBytesPerSecond: number;
|
|
8
|
+
readonly remainingBytes: number;
|
|
9
|
+
readonly frpc: {
|
|
10
|
+
readonly serverAddr: string;
|
|
11
|
+
readonly serverPort: number;
|
|
12
|
+
readonly user: string;
|
|
13
|
+
readonly token: string;
|
|
14
|
+
readonly proxyName: "svc";
|
|
15
|
+
readonly proxyType: "stcp";
|
|
16
|
+
readonly secretKey: string;
|
|
17
|
+
readonly allowUsers: readonly string[];
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export type ServiceType = "mcp" | "a2a";
|
|
21
|
+
/** A registered service as `register` and `list` return it. The local port is never part of it. */
|
|
22
|
+
export interface ServiceShareRecord {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly deviceId: string;
|
|
25
|
+
readonly type: ServiceType;
|
|
26
|
+
readonly label: string;
|
|
27
|
+
readonly createdAt: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const SERVICE_SHARE_ID: RegExp;
|
|
30
|
+
/** Checks the grant's shape before anything of it reaches a process or a file; throws on anything unexpected. */
|
|
31
|
+
export declare function parseServiceGrant(value: unknown): ServiceGrant;
|
|
32
|
+
/** Checks a `register` / `list` row. */
|
|
33
|
+
export declare function parseServiceRecord(value: unknown): ServiceShareRecord;
|
|
34
|
+
/**
|
|
35
|
+
* The frpc config for a service: exactly one stcp proxy that points at a port on loopback. `localIP` is fixed here and the port comes
|
|
36
|
+
* from the author's own command line; the relay never supplies a target. No client plugin, no admin port and no other proxy type are written.
|
|
37
|
+
* The stcp key and `allowUsers` (Frely's visitor) come from the grant.
|
|
38
|
+
*/
|
|
39
|
+
export declare function serviceFrpcConfigToml(grant: ServiceGrant, localPort: number): string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const HOST = /^(?:[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?|\[[0-9A-Fa-f:]{2,45}\])$/u;
|
|
2
|
+
const SECRET = /^[A-Za-z0-9_-]{20,128}$/u;
|
|
3
|
+
const VISITOR = /^[A-Za-z0-9_.-]{1,80}$/u;
|
|
4
|
+
export const SERVICE_SHARE_ID = /^svcs_[0-9a-f]{24}$/u;
|
|
5
|
+
/** Checks the grant's shape before anything of it reaches a process or a file; throws on anything unexpected. */
|
|
6
|
+
export function parseServiceGrant(value) {
|
|
7
|
+
const fail = () => { throw new Error("Frely returned an invalid service session."); };
|
|
8
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
9
|
+
return fail();
|
|
10
|
+
const grant = value, frpc = grant.frpc;
|
|
11
|
+
if (typeof grant.id !== "string" || !/^frps_[0-9a-f]{24}$/u.test(grant.id))
|
|
12
|
+
return fail();
|
|
13
|
+
if (typeof grant.serviceShareId !== "string" || !SERVICE_SHARE_ID.test(grant.serviceShareId))
|
|
14
|
+
return fail();
|
|
15
|
+
if (typeof grant.expiresAt !== "string" || !Number.isFinite(Date.parse(grant.expiresAt)) || typeof grant.node !== "string" || grant.node.length < 1 || grant.node.length > 64)
|
|
16
|
+
return fail();
|
|
17
|
+
if (typeof grant.bandwidthBytesPerSecond !== "number" || !Number.isFinite(grant.bandwidthBytesPerSecond) || grant.bandwidthBytesPerSecond < 0)
|
|
18
|
+
return fail();
|
|
19
|
+
if (typeof grant.remainingBytes !== "number" || !Number.isFinite(grant.remainingBytes) || grant.remainingBytes < 0)
|
|
20
|
+
return fail();
|
|
21
|
+
if (!frpc || typeof frpc !== "object" || Array.isArray(frpc))
|
|
22
|
+
return fail();
|
|
23
|
+
const { serverAddr, serverPort, user, token, proxyName, proxyType, secretKey, allowUsers } = frpc;
|
|
24
|
+
if (typeof serverAddr !== "string" || !HOST.test(serverAddr))
|
|
25
|
+
return fail();
|
|
26
|
+
if (typeof serverPort !== "number" || !Number.isInteger(serverPort) || serverPort < 1 || serverPort > 65535)
|
|
27
|
+
return fail();
|
|
28
|
+
if (user !== grant.id || typeof token !== "string" || !/^frs_[A-Za-z0-9_-]{20,64}$/u.test(token))
|
|
29
|
+
return fail();
|
|
30
|
+
if (proxyName !== "svc" || proxyType !== "stcp")
|
|
31
|
+
return fail();
|
|
32
|
+
if (typeof secretKey !== "string" || !SECRET.test(secretKey))
|
|
33
|
+
return fail();
|
|
34
|
+
if (!Array.isArray(allowUsers) || allowUsers.length < 1 || allowUsers.length > 4 || allowUsers.some((entry) => typeof entry !== "string" || !VISITOR.test(entry) || entry === "*"))
|
|
35
|
+
return fail();
|
|
36
|
+
return {
|
|
37
|
+
id: grant.id, serviceShareId: grant.serviceShareId, expiresAt: grant.expiresAt, node: grant.node,
|
|
38
|
+
bandwidthBytesPerSecond: grant.bandwidthBytesPerSecond, remainingBytes: grant.remainingBytes,
|
|
39
|
+
frpc: { serverAddr, serverPort, user, token, proxyName, proxyType, secretKey, allowUsers: [...allowUsers] },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Checks a `register` / `list` row. */
|
|
43
|
+
export function parseServiceRecord(value) {
|
|
44
|
+
const fail = () => { throw new Error("Frely returned an invalid service."); };
|
|
45
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
46
|
+
return fail();
|
|
47
|
+
const row = value;
|
|
48
|
+
if (typeof row.id !== "string" || !SERVICE_SHARE_ID.test(row.id) || typeof row.deviceId !== "string" || row.deviceId.length > 64)
|
|
49
|
+
return fail();
|
|
50
|
+
if (row.type !== "mcp" && row.type !== "a2a")
|
|
51
|
+
return fail();
|
|
52
|
+
if (typeof row.label !== "string" || row.label.length < 1 || row.label.length > 60)
|
|
53
|
+
return fail();
|
|
54
|
+
return { id: row.id, deviceId: row.deviceId, type: row.type, label: row.label, createdAt: typeof row.createdAt === "string" ? row.createdAt : "" };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The frpc config for a service: exactly one stcp proxy that points at a port on loopback. `localIP` is fixed here and the port comes
|
|
58
|
+
* from the author's own command line; the relay never supplies a target. No client plugin, no admin port and no other proxy type are written.
|
|
59
|
+
* The stcp key and `allowUsers` (Frely's visitor) come from the grant.
|
|
60
|
+
*/
|
|
61
|
+
export function serviceFrpcConfigToml(grant, localPort) {
|
|
62
|
+
if (!Number.isInteger(localPort) || localPort < 1 || localPort > 65535)
|
|
63
|
+
throw new Error("Invalid local port.");
|
|
64
|
+
const q = (text) => JSON.stringify(text);
|
|
65
|
+
return [
|
|
66
|
+
`serverAddr = ${q(grant.frpc.serverAddr.replace(/^\[|\]$/gu, ""))}`,
|
|
67
|
+
`serverPort = ${grant.frpc.serverPort}`,
|
|
68
|
+
`user = ${q(grant.frpc.user)}`,
|
|
69
|
+
`loginFailExit = true`,
|
|
70
|
+
`metadatas.token = ${q(grant.frpc.token)}`,
|
|
71
|
+
`transport.tls.enable = true`,
|
|
72
|
+
`log.to = "console"`,
|
|
73
|
+
`log.level = "warn"`,
|
|
74
|
+
``,
|
|
75
|
+
`[[proxies]]`,
|
|
76
|
+
`name = "svc"`,
|
|
77
|
+
`type = "stcp"`,
|
|
78
|
+
`secretKey = ${q(grant.frpc.secretKey)}`,
|
|
79
|
+
`allowUsers = [${grant.frpc.allowUsers.map(q).join(", ")}]`,
|
|
80
|
+
`localIP = "127.0.0.1"`,
|
|
81
|
+
`localPort = ${localPort}`,
|
|
82
|
+
``,
|
|
83
|
+
].join("\n");
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=service-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-config.js","sourceRoot":"","sources":["../../src/share/service-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ShareHeartbeat } from "./control.js";
|
|
2
|
+
import { type ServiceGrant, type ServiceShareRecord, type ServiceType } from "./service-config.js";
|
|
3
|
+
export interface ServiceControl {
|
|
4
|
+
register(input: {
|
|
5
|
+
type: ServiceType;
|
|
6
|
+
label: string;
|
|
7
|
+
}): Promise<ServiceShareRecord>;
|
|
8
|
+
list(): Promise<ServiceShareRecord[]>;
|
|
9
|
+
remove(serviceShareId: string): Promise<void>;
|
|
10
|
+
session(serviceShareId: string): Promise<ServiceGrant>;
|
|
11
|
+
heartbeat(sessionId: string): Promise<ShareHeartbeat>;
|
|
12
|
+
revoke(sessionId: string): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/** Calls Frely's service route as the signed-in user. Only the type and label are registered; the local port never leaves this computer. */
|
|
15
|
+
export declare function accountServiceControl(): Promise<ServiceControl>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ensureDevice } from "../device/control.js";
|
|
2
|
+
import { accountRelayCaller } from "./control.js";
|
|
3
|
+
import { parseServiceGrant, parseServiceRecord } from "./service-config.js";
|
|
4
|
+
const SERVICES_PATH = "/api/user/device-relay/services";
|
|
5
|
+
/** Calls Frely's service route as the signed-in user. Only the type and label are registered; the local port never leaves this computer. */
|
|
6
|
+
export async function accountServiceControl() {
|
|
7
|
+
const call = await accountRelayCaller(SERVICES_PATH);
|
|
8
|
+
return {
|
|
9
|
+
async register(input) {
|
|
10
|
+
const device = await ensureDevice();
|
|
11
|
+
return parseServiceRecord(await call({ action: "register", deviceId: device.deviceId, type: input.type, label: input.label }));
|
|
12
|
+
},
|
|
13
|
+
async list() {
|
|
14
|
+
const value = await call({ action: "list" });
|
|
15
|
+
const items = value && typeof value === "object" ? value.items : undefined;
|
|
16
|
+
if (!Array.isArray(items))
|
|
17
|
+
throw new Error("Frely returned an invalid service list.");
|
|
18
|
+
return items.map(parseServiceRecord);
|
|
19
|
+
},
|
|
20
|
+
async remove(serviceShareId) { await call({ action: "remove", serviceShareId }); },
|
|
21
|
+
async session(serviceShareId) { return parseServiceGrant(await call({ action: "session", serviceShareId })); },
|
|
22
|
+
async heartbeat(sessionId) {
|
|
23
|
+
const value = await call({ action: "heartbeat", sessionId });
|
|
24
|
+
if (!value || typeof value !== "object" || typeof value.active !== "boolean")
|
|
25
|
+
throw new Error("Frely returned an invalid service status.");
|
|
26
|
+
return value;
|
|
27
|
+
},
|
|
28
|
+
async revoke(sessionId) { await call({ action: "revoke", sessionId }); },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=service-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-control.js","sourceRoot":"","sources":["../../src/share/service-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAuB,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAgE,MAAM,qBAAqB,CAAC;AAE1I,MAAM,aAAa,GAAG,iCAAiC,CAAC;AAWxD,4IAA4I;AAC5I,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACrD,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,KAAK;YAClB,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;YACpC,OAAO,kBAAkB,CAAC,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACjI,CAAC;QACD,KAAK,CAAC,IAAI;YACR,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YAC7C,MAAM,KAAK,GAAG,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,KAA6B,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;YACpG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACtF,OAAO,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,cAAc,IAAI,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;QAClF,KAAK,CAAC,OAAO,CAAC,cAAc,IAAI,OAAO,iBAAiB,CAAC,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9G,KAAK,CAAC,SAAS,CAAC,SAAS;YACvB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAQ,KAA8B,CAAC,MAAM,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;YACrK,OAAO,KAAuB,CAAC;QACjC,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;KACzE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type ServiceGrant, type ServiceType } from "./service-config.js";
|
|
2
|
+
import { type ServiceControl } from "./service-control.js";
|
|
3
|
+
import { type LocalService } from "./service-state.js";
|
|
4
|
+
import { ALLOWANCE_NOTE } from "./allowance.js";
|
|
5
|
+
import { type FrpcProcess } from "./run.js";
|
|
6
|
+
export { ALLOWANCE_NOTE };
|
|
7
|
+
export interface ServiceShareOptions {
|
|
8
|
+
readonly type: ServiceType;
|
|
9
|
+
readonly port: number;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
readonly background: boolean;
|
|
12
|
+
readonly json: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function serviceUsage(): string;
|
|
15
|
+
export declare function parseServiceShareArguments(args: readonly string[]): ServiceShareOptions;
|
|
16
|
+
export interface ServiceSessionDeps {
|
|
17
|
+
readonly control: ServiceControl;
|
|
18
|
+
readonly ensureFrpc: () => Promise<string>;
|
|
19
|
+
readonly spawnFrpc: (binary: string, configPath: string) => FrpcProcess;
|
|
20
|
+
readonly write: (text: string) => void;
|
|
21
|
+
readonly signal: AbortSignal;
|
|
22
|
+
readonly heartbeatMs?: number;
|
|
23
|
+
readonly configLifetimeMs?: number;
|
|
24
|
+
/** Called once the session is up, with the grant (for the allowance line). */
|
|
25
|
+
readonly onGrant?: (grant: ServiceGrant) => void;
|
|
26
|
+
}
|
|
27
|
+
/** One session of one service: ask Frely for it, run frpc on it, heartbeat, and revoke when it ends. Returns why it ended. */
|
|
28
|
+
export declare function runServiceSession(service: Pick<LocalService, "serviceShareId" | "port">, deps: ServiceSessionDeps): Promise<{
|
|
29
|
+
reason: string;
|
|
30
|
+
stopped: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
export declare function allowanceLines(grant: ServiceGrant): string[];
|
|
33
|
+
/** Keeps one service connected until aborted: when a session ends for any reason but a stop, ask for a new one after a growing pause. */
|
|
34
|
+
export declare function keepServiceConnected(service: Pick<LocalService, "serviceShareId" | "port" | "label">, deps: ServiceSessionDeps, sleep?: (ms: number, signal: AbortSignal) => Promise<void>): Promise<void>;
|
|
35
|
+
/** Runs inside the device daemon: every service saved with `background` stays connected; a stop or removal in the state file ends it within seconds. */
|
|
36
|
+
export declare function superviseServices(options: {
|
|
37
|
+
signal: AbortSignal;
|
|
38
|
+
log: (text: string) => void;
|
|
39
|
+
pollMs?: number;
|
|
40
|
+
control?: () => Promise<ServiceControl>;
|
|
41
|
+
session?: Partial<ServiceSessionDeps>;
|
|
42
|
+
}): Promise<void>;
|
|
43
|
+
export interface ServiceCommandIo {
|
|
44
|
+
readonly write: (text: string) => void;
|
|
45
|
+
}
|
|
46
|
+
export interface ServiceCommandDeps {
|
|
47
|
+
readonly control: () => Promise<ServiceControl>;
|
|
48
|
+
readonly probe: (port: number) => Promise<boolean>;
|
|
49
|
+
readonly ensureFrpc: () => Promise<string>;
|
|
50
|
+
readonly spawnFrpc: (binary: string, configPath: string) => FrpcProcess;
|
|
51
|
+
readonly signal: AbortSignal;
|
|
52
|
+
/** Installs / restarts the background service so it picks the service up. */
|
|
53
|
+
readonly installDaemon: () => Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
export declare function runServiceCommand(args: readonly string[], io: ServiceCommandIo, deps: ServiceCommandDeps): Promise<number>;
|