ai-fly 0.1.1 → 0.3.0-alpha.2
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/dist/ai-fly.js +48 -22
- package/dist/args-CSxXW9eD.mjs +69 -0
- package/dist/common-CgxCXeTt.mjs +83 -0
- package/dist/common-CqxqIg6D.mjs +86 -0
- package/dist/config-BlZnFqx7.mjs +59 -0
- package/dist/daemon-DklsEniO.mjs +291 -0
- package/dist/{forget-BEbNwPip.mjs → forget-B17zGwj4.mjs} +3 -2
- package/dist/{sdk-C6xDRbzg.mjs → frames-BDqvhB2Y.mjs} +1 -10
- package/dist/{group-Csy9VmFb.mjs → group-B96IKEDF.mjs} +24 -4
- package/dist/{import-BVAFw5Tx.mjs → import-tilxdbTH.mjs} +8 -6
- package/dist/{join-E1_-Rt-7.mjs → join-DsKUAibH.mjs} +7 -5
- package/dist/{join-B9op0juF.mjs → join-hyqiwEek.mjs} +3 -3
- package/dist/{key-CK77IYXp.mjs → key-BiEp995L.mjs} +2 -2
- package/dist/{key-Bt4EWiuO.mjs → key-CNufbB__.mjs} +4 -3
- package/dist/local-test-C3HGjGY3.mjs +101 -0
- package/dist/models-dev-2UfQoIpQ.mjs +244 -0
- package/dist/{mux-xHl9Jrbi.mjs → mux-CS1zhF4W.mjs} +2 -2
- package/dist/{ports-BQukx7wS.mjs → ports-rRppuBgo.mjs} +3 -2
- package/dist/presets-C73CAMZ6.mjs +55 -0
- package/dist/proxy-D7VMUVEg.mjs +21 -0
- package/dist/{revoke-CyrOzc8t.mjs → revoke-CkpvK33Q.mjs} +5 -4
- package/dist/rewrite-xZHWnyJk.mjs +313 -0
- package/dist/route-test-Oew0CO1U.mjs +60 -0
- package/dist/rpc-contract-5qyWzT5t.mjs +638 -0
- package/dist/{run-Be8H-IRf.mjs → run-DbPXAm6h.mjs} +8 -5
- package/dist/{runtime-Dg4en1D8.mjs → runtime-D_nxUEjd.mjs} +3 -3
- package/dist/secret-SA37VND5.mjs +95 -0
- package/dist/secrets-BYI6ooTJ.mjs +113 -0
- package/dist/secrets-u19-8EyW.mjs +2 -0
- package/dist/{serve-DuyQMeJh.mjs → serve-Be4zdKVj.mjs} +14 -425
- package/dist/{serve-R03mDBwd.mjs → serve-DSBlodTF.mjs} +18 -6
- package/dist/service-B5uORhl3.mjs +232 -0
- package/dist/settings-C1Hb4zwd.mjs +34 -0
- package/dist/settings-DU0YqNDt.mjs +95 -0
- package/dist/{share-DGM2TOft.mjs → share-Cug0944N.mjs} +8 -5
- package/dist/{status-DRaKNvqz.mjs → status-B-j454u2.mjs} +7 -5
- package/dist/{status-W2OS7WB6.mjs → status-SkNsxGnk.mjs} +6 -4
- package/dist/{common-4VeA_3wo.mjs → store-BiTS2FTl.mjs} +3 -73
- package/dist/store-CrTUwskE.mjs +629 -0
- package/dist/test-Bc0OKBAi.mjs +64 -0
- package/package.json +1 -1
- package/dist/common-BULbPtYn.mjs +0 -1295
- package/dist/config-CO3WyW2H.mjs +0 -173
- package/dist/service-Cd4R47n4.mjs +0 -94
package/dist/ai-fly.js
CHANGED
|
@@ -7,27 +7,46 @@ import { match } from "ts-pattern";
|
|
|
7
7
|
//#region src/bin.ts
|
|
8
8
|
const USAGE = `ai-fly — peer-to-peer HTTP/WebSocket bridge with AI-ready presets (OpenDWeb fabric)
|
|
9
9
|
|
|
10
|
+
Daemon:
|
|
11
|
+
ai-fly run the provider daemon (foreground; Ctrl+C stops)
|
|
12
|
+
ai-fly daemon start [--detach] [--data <dir>] [--relay <url>]... [--proxy <url|env|none>]
|
|
13
|
+
ai-fly daemon stop [--force] | info | restart [--detach] | log [--lines <n>]
|
|
14
|
+
ai-fly serve alias of 'daemon start'
|
|
15
|
+
|
|
10
16
|
Provider:
|
|
11
|
-
ai-fly
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
ai-fly
|
|
15
|
-
ai-fly
|
|
16
|
-
ai-fly
|
|
17
|
-
ai-fly
|
|
17
|
+
ai-fly service add <name> [--upstream <url> | --preset <id>] [--route <local>=<up>[@forms]]...
|
|
18
|
+
[--route-pattern <match>=<template>]... [--secret <name>]
|
|
19
|
+
[--port <n>] [--match <type>:<value>]... [--data <dir>]
|
|
20
|
+
ai-fly service list|get|remove <name> [--data <dir>]
|
|
21
|
+
ai-fly service test <name> [--form …] [--content <text>] [--model <id>] [--local-prefix /v1]
|
|
22
|
+
ai-fly group add|set-services|set-limits|list|remove ...
|
|
23
|
+
ai-fly key issue|list|revoke --group <name>
|
|
24
|
+
ai-fly secret set|list|remove ... (values never leave the store - no get)
|
|
25
|
+
ai-fly share --group <name> [--ttl <dur>] mint a share link (token + key)
|
|
26
|
+
ai-fly revoke <endpointId> eject a device (fabric-level)
|
|
27
|
+
ai-fly presets [search] [--json] featured + models.dev long tail
|
|
18
28
|
|
|
19
29
|
Consumer:
|
|
20
|
-
ai-fly join
|
|
21
|
-
ai-fly
|
|
22
|
-
ai-fly
|
|
23
|
-
ai-fly
|
|
24
|
-
ai-fly
|
|
25
|
-
|
|
26
|
-
ai-fly
|
|
30
|
+
ai-fly join <dweb1-token> [--data <dir>] [--proxy …] admit this device (fabric layer)
|
|
31
|
+
ai-fly import <aifly1-link> [--run] [--preview] [--proxy …] bundle link: join + keyring
|
|
32
|
+
ai-fly run [--data <dir>] [--strict-ports] [--proxy …] run the local gateway
|
|
33
|
+
ai-fly ports [--data <dir>] [<serviceId> --port <n>] view/override mapped ports
|
|
34
|
+
ai-fly test [--data <dir>] [--service <name>] [--form …] [--content <text>]
|
|
35
|
+
single AI request through the local gateway
|
|
36
|
+
ai-fly key add <sk-aifly-key> --provider <id> bare key into an existing ring
|
|
37
|
+
ai-fly forget <endpointId|8-char-prefix> drop an imported provider
|
|
38
|
+
|
|
39
|
+
Config:
|
|
40
|
+
ai-fly settings list
|
|
41
|
+
ai-fly settings set theme dark|light|system | models-dev on|off | relay <url>... | relay --default
|
|
42
|
+
ai-fly relay list|set ... alias of 'settings … relay'
|
|
43
|
+
|
|
44
|
+
Info:
|
|
45
|
+
ai-fly status [provider|consumer] [--verbose] snapshot (auto: both if present)
|
|
27
46
|
|
|
28
47
|
Options:
|
|
29
48
|
--data <dir> storage location (tilde expanded; role-specific default)
|
|
30
|
-
--relay <url> relay entry URL (repeatable; flag > AIFLY_RELAY env > ~/.aifly/config.json)
|
|
49
|
+
--relay <url> relay entry URL (repeatable; flag > AIFLY_RELAY env > settings.json > ~/.aifly/config.json)
|
|
31
50
|
--help, -h show this help`;
|
|
32
51
|
async function dispatch(command, rest) {
|
|
33
52
|
const providerDirExists = () => existsSync(join(homedir(), ".aifly", "provider"));
|
|
@@ -41,17 +60,17 @@ async function dispatch(command, rest) {
|
|
|
41
60
|
const lazy = (load) => {
|
|
42
61
|
return (argv, ctx) => load().then((m) => m.run([...argv], ctx));
|
|
43
62
|
};
|
|
44
|
-
return match(command).with("serve", () => lazy(() => import("./serve-
|
|
45
|
-
if (rest[0] === "add") return lazy(() => import("./key-
|
|
46
|
-
return lazy(() => import("./key-
|
|
63
|
+
return match(command).with("serve", () => lazy(() => import("./serve-DSBlodTF.mjs"))(rest)).with("daemon", () => lazy(() => import("./daemon-DklsEniO.mjs"))(rest)).with("presets", () => lazy(() => import("./presets-C73CAMZ6.mjs"))(rest)).with("secret", () => lazy(() => import("./secret-SA37VND5.mjs"))(rest)).with("settings", async () => (await import("./settings-DU0YqNDt.mjs")).runAsSettings(rest, {})).with("relay", async () => (await import("./settings-DU0YqNDt.mjs")).runAsRelay(rest, {})).with("test", () => lazy(() => import("./test-Bc0OKBAi.mjs"))(rest)).with("service", () => lazy(() => import("./service-B5uORhl3.mjs"))(rest)).with("group", () => lazy(() => import("./group-B96IKEDF.mjs"))(rest)).with("share", () => lazy(() => import("./share-Cug0944N.mjs"))(rest)).with("revoke", () => lazy(() => import("./revoke-CkpvK33Q.mjs"))(rest)).with("join", () => lazy(() => import("./join-DsKUAibH.mjs"))(rest)).with("import", () => lazy(() => import("./import-tilxdbTH.mjs"))(rest)).with("run", () => lazy(() => import("./run-DbPXAm6h.mjs"))(rest)).with("ports", () => lazy(() => import("./ports-rRppuBgo.mjs"))(rest)).with("forget", () => lazy(() => import("./forget-B17zGwj4.mjs"))(rest)).with("key", () => {
|
|
64
|
+
if (rest[0] === "add") return lazy(() => import("./key-CNufbB__.mjs"))(rest);
|
|
65
|
+
return lazy(() => import("./key-BiEp995L.mjs"))(rest);
|
|
47
66
|
}).with("status", async () => {
|
|
48
67
|
const [scope, ...tail] = rest;
|
|
49
68
|
const wantProvider = scope === "provider" || scope !== "consumer" && providerDirExists();
|
|
50
69
|
const wantConsumer = scope === "consumer" || scope !== "provider" && consumersRootExists();
|
|
51
70
|
const argv = scope === "provider" || scope === "consumer" ? tail : rest;
|
|
52
71
|
let code = 0;
|
|
53
|
-
if (wantProvider) code = Math.max(code, await lazy(() => import("./status-
|
|
54
|
-
if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-
|
|
72
|
+
if (wantProvider) code = Math.max(code, await lazy(() => import("./status-SkNsxGnk.mjs"))(argv));
|
|
73
|
+
if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-B-j454u2.mjs"))(argv));
|
|
55
74
|
if (!wantProvider && !wantConsumer) process.stdout.write("nothing to report: no provider storage and no imported consumers\n");
|
|
56
75
|
return code;
|
|
57
76
|
}).otherwise(() => {
|
|
@@ -60,12 +79,19 @@ async function dispatch(command, rest) {
|
|
|
60
79
|
}
|
|
61
80
|
async function main(argv) {
|
|
62
81
|
const first = argv[0];
|
|
63
|
-
if (first ===
|
|
82
|
+
if (first === "--help" || first === "-h") {
|
|
64
83
|
process.stdout.write(`${USAGE}\n`);
|
|
65
|
-
return
|
|
84
|
+
return 0;
|
|
66
85
|
}
|
|
86
|
+
if (first === void 0) return dispatch("daemon", []);
|
|
67
87
|
return dispatch(first, argv.slice(1));
|
|
68
88
|
}
|
|
89
|
+
process.stdout?.on?.("error", (err) => {
|
|
90
|
+
if (err.code === "EPIPE") process.exit(0);
|
|
91
|
+
});
|
|
92
|
+
process.stderr?.on?.("error", (err) => {
|
|
93
|
+
if (err.code === "EPIPE") process.exit(0);
|
|
94
|
+
});
|
|
69
95
|
try {
|
|
70
96
|
const code = await main(process.argv.slice(2));
|
|
71
97
|
process.exitCode = code;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { n as UsageError, t as CliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
//#region src/cli/args.ts
|
|
3
|
+
function expandTilde(value, homedir) {
|
|
4
|
+
if (value === "~") return homedir;
|
|
5
|
+
if (value.startsWith("~/")) return homedir + value.slice(1);
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 解析带 ms|s|m|h|d 后缀的时长;裸数字按毫秒(与 opendweb-example 同规)。
|
|
10
|
+
* 语法错误抛 CliError;值域检查由调用方按各选项边界执行。
|
|
11
|
+
*/
|
|
12
|
+
function parseDurationMs(raw, label) {
|
|
13
|
+
const m = /^([0-9]+(?:\.[0-9]+)?)(ms|s|m|h|d)?$/.exec(String(raw).trim());
|
|
14
|
+
if (!m) throw new CliError(`error: invalid ${label} value: ${raw} (expected <number>[ms|s|m|h|d])`);
|
|
15
|
+
const ms = Number(m[1]) * ({
|
|
16
|
+
ms: 1,
|
|
17
|
+
s: 1e3,
|
|
18
|
+
m: 6e4,
|
|
19
|
+
h: 36e5,
|
|
20
|
+
d: 864e5
|
|
21
|
+
}[m[2] ?? "ms"] ?? 1);
|
|
22
|
+
if (!Number.isFinite(ms) || ms > Number.MAX_SAFE_INTEGER) return Number.POSITIVE_INFINITY;
|
|
23
|
+
return ms;
|
|
24
|
+
}
|
|
25
|
+
function assertDurationRange(ms, minMs, maxMs, label, rangeText) {
|
|
26
|
+
if (!(ms >= minMs && ms <= maxMs)) throw new CliError(`error: ${label} out of range (${rangeText})`);
|
|
27
|
+
}
|
|
28
|
+
function parseArgv(argv, spec, ctx = {}) {
|
|
29
|
+
const homedir = ctx.homedir ?? "";
|
|
30
|
+
const options = {};
|
|
31
|
+
const positionals = [];
|
|
32
|
+
const known = Object.keys(spec);
|
|
33
|
+
for (let i = 0; i < argv.length; i++) {
|
|
34
|
+
const tok = argv[i];
|
|
35
|
+
if (tok === "--") {
|
|
36
|
+
for (let j = i + 1; j < argv.length; j++) positionals.push(argv[j]);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (tok.startsWith("--")) {
|
|
40
|
+
const eq = tok.indexOf("=");
|
|
41
|
+
const name = (eq >= 0 ? tok.slice(2, eq) : tok.slice(2)).trim();
|
|
42
|
+
const inlineValue = eq >= 0 ? tok.slice(eq + 1) : void 0;
|
|
43
|
+
const decl = spec[name];
|
|
44
|
+
if (!decl) throw new UsageError(`error: unknown option --${name} (known: ${known.map((k) => `--${k}`).join(", ")})`);
|
|
45
|
+
if (decl.type === "boolean") {
|
|
46
|
+
if (inlineValue !== void 0) throw new UsageError(`error: option --${name} does not take a value`);
|
|
47
|
+
options[name] = true;
|
|
48
|
+
} else {
|
|
49
|
+
let value = inlineValue;
|
|
50
|
+
if (value === void 0) {
|
|
51
|
+
if (i + 1 >= argv.length) throw new UsageError(`error: option --${name} requires a value`);
|
|
52
|
+
value = argv[++i];
|
|
53
|
+
}
|
|
54
|
+
if (decl.tilde) value = expandTilde(value, homedir);
|
|
55
|
+
if (decl.type === "multi") {
|
|
56
|
+
const list = Array.isArray(options[name]) ? options[name] : [];
|
|
57
|
+
list.push(value);
|
|
58
|
+
options[name] = list;
|
|
59
|
+
} else options[name] = value;
|
|
60
|
+
}
|
|
61
|
+
} else positionals.push(tok);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
options,
|
|
65
|
+
positionals
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { parseArgv as n, parseDurationMs as r, assertDurationRange as t };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { n as resolveRelayUrls, t as loadConfig } from "./config-BlZnFqx7.mjs";
|
|
2
|
+
import { n as loadSdk, t as resolveHttpProxy } from "./proxy-D7VMUVEg.mjs";
|
|
3
|
+
import { t as loadSettings } from "./settings-C1Hb4zwd.mjs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
//#region src/cli/commands/consumer/common.ts
|
|
6
|
+
function ctxOut(ctx) {
|
|
7
|
+
return ctx.out ?? ((line) => process.stdout.write(`${line}\n`));
|
|
8
|
+
}
|
|
9
|
+
function ctxHomedir(ctx) {
|
|
10
|
+
return ctx.homedir ?? homedir();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 真实 Fabric 工厂(CLI 层唯一触 SDK 的位置):动态 import 规避 vitest worker 与
|
|
14
|
+
* 原生模块的不兼容;relay 配置映射为 SDK FabricOptions.relay(custom urls),
|
|
15
|
+
* 未配置时缺省走 SDK 默认(n0)。
|
|
16
|
+
* 解析层级(Owner 裁决 2026-09-13:链接带来的会合点优先于环境/配置默认——
|
|
17
|
+
* provider 在那个 relay 上,不拨它就永远会不了面):逐次解析为
|
|
18
|
+
* flag > ring/链接内嵌 relay(opts.relayUrls,调用侧按 ring 传入)> env > file。
|
|
19
|
+
* @param linkRelayUrls aifly1. 链接内嵌 relay(import 的 join 阶段用;空 = 缺席)
|
|
20
|
+
* @param proxyFlag --proxy 旗标值(url | 'env' | 'none';缺省回落 AIFLY_PROXY env)
|
|
21
|
+
*/
|
|
22
|
+
async function createSdkFabricFactory(relayFlag, ctx = {}, linkRelayUrls, proxyFlag) {
|
|
23
|
+
const sdk = await loadSdk();
|
|
24
|
+
const httpProxy = resolveHttpProxy(proxyFlag);
|
|
25
|
+
let relaySettings = null;
|
|
26
|
+
try {
|
|
27
|
+
relaySettings = loadSettings(ctxHomedir(ctx)).relayUrls;
|
|
28
|
+
} catch {}
|
|
29
|
+
const toOpts = (opts) => {
|
|
30
|
+
const fabricOpts = { dataDir: opts.dataDir };
|
|
31
|
+
if (httpProxy !== void 0) fabricOpts.httpProxy = httpProxy;
|
|
32
|
+
const link = opts.relayUrls ?? linkRelayUrls;
|
|
33
|
+
const urls = resolveRelayUrls({
|
|
34
|
+
...relayFlag !== void 0 ? { flag: relayFlag } : {},
|
|
35
|
+
...link !== void 0 ? { link } : {},
|
|
36
|
+
env: process.env.AIFLY_RELAY,
|
|
37
|
+
settings: relaySettings,
|
|
38
|
+
file: loadConfig(ctxHomedir(ctx))
|
|
39
|
+
});
|
|
40
|
+
if (urls !== void 0 && urls.length > 0) fabricOpts.relay = {
|
|
41
|
+
mode: "custom",
|
|
42
|
+
urls
|
|
43
|
+
};
|
|
44
|
+
return fabricOpts;
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
open: async (opts) => await sdk.Fabric.open(toOpts(opts)),
|
|
48
|
+
joinWithToken: async (opts, token) => await sdk.Fabric.joinWithToken(toOpts(opts), token)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** 等待 SIGINT/SIGTERM(run / import --run 长驻阻塞;测试不触达)。 */
|
|
52
|
+
function waitForSignals() {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
let done = false;
|
|
55
|
+
const finish = () => {
|
|
56
|
+
if (done) return;
|
|
57
|
+
done = true;
|
|
58
|
+
process.off("SIGINT", finish);
|
|
59
|
+
process.off("SIGTERM", finish);
|
|
60
|
+
resolve();
|
|
61
|
+
};
|
|
62
|
+
process.on("SIGINT", finish);
|
|
63
|
+
process.on("SIGTERM", finish);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** 打印引擎监听清单(run 启动横幅与 status 共用)。 */
|
|
67
|
+
function printListeners(out, info) {
|
|
68
|
+
if (info.length === 0) {
|
|
69
|
+
out("local endpoints: (none - no authorized services yet)");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
out("local endpoints:");
|
|
73
|
+
for (const l of info) {
|
|
74
|
+
const note = l.autoAssigned ? ` [NOTICE: requested ${l.requested} was unavailable]` : "";
|
|
75
|
+
out(` 127.0.0.1:${l.port} <- ${l.alias} / ${l.name} [${l.serviceId}]${note}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** 引擎所需钥环集合加载(run/import --run/status 共用)。 */
|
|
79
|
+
function ringsForRun(rings) {
|
|
80
|
+
return rings.filter((r) => r.keys.length > 0 || r.services.length > 0);
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { ringsForRun as a, printListeners as i, ctxHomedir as n, waitForSignals as o, ctxOut as r, createSdkFabricFactory as t };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { n as UsageError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
import { n as resolveRelayUrls, t as loadConfig } from "./config-BlZnFqx7.mjs";
|
|
3
|
+
import { t as ProviderStore } from "./store-CrTUwskE.mjs";
|
|
4
|
+
import { t as loadSettings } from "./settings-C1Hb4zwd.mjs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
//#region src/cli/commands/provider/common.ts
|
|
8
|
+
function resolveDataDir(flag, home = homedir()) {
|
|
9
|
+
if (flag === void 0 || flag === "") return join(home, ".aifly", "provider");
|
|
10
|
+
return flag;
|
|
11
|
+
}
|
|
12
|
+
/** relay 解析(flag > AIFLY_RELAY env > config file);exactOptionalPropertyTypes 安全组装。 */
|
|
13
|
+
function resolvedRelayUrls(options, home) {
|
|
14
|
+
const input = {};
|
|
15
|
+
try {
|
|
16
|
+
input.settings = loadSettings(home).relayUrls;
|
|
17
|
+
} catch {}
|
|
18
|
+
if (Array.isArray(options.relay)) input.flag = options.relay;
|
|
19
|
+
const env = process.env.AIFLY_RELAY;
|
|
20
|
+
if (env !== void 0) input.env = env;
|
|
21
|
+
input.file = loadConfig(home);
|
|
22
|
+
return resolveRelayUrls(input);
|
|
23
|
+
}
|
|
24
|
+
function str(value) {
|
|
25
|
+
if (typeof value === "string") return value;
|
|
26
|
+
}
|
|
27
|
+
function multi(value) {
|
|
28
|
+
if (Array.isArray(value)) return value;
|
|
29
|
+
if (typeof value === "string") return [value];
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
function requireString(value, label) {
|
|
33
|
+
if (value === void 0 || value === "") throw new UsageError(`error: missing required option --${label}`);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function parsePositiveInt(raw, label) {
|
|
37
|
+
const n = Number(raw);
|
|
38
|
+
if (!Number.isInteger(n) || n < 1 || n > Number.MAX_SAFE_INTEGER) throw new UsageError(`error: invalid ${label} value: ${raw} (expected a positive integer)`);
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
41
|
+
function parsePortNumber(raw, label) {
|
|
42
|
+
const n = parsePositiveInt(raw, label);
|
|
43
|
+
if (n > 65535) throw new UsageError(`error: invalid ${label} value: ${raw} (expected 1..65535)`);
|
|
44
|
+
return n;
|
|
45
|
+
}
|
|
46
|
+
/** "type:value"(value 为剩余整体;type in exact|suffix|regex)。 */
|
|
47
|
+
function parseMatchSpec(raw) {
|
|
48
|
+
const idx = raw.indexOf(":");
|
|
49
|
+
if (idx <= 0) throw new UsageError(`error: invalid --match value: ${raw} (expected <exact|suffix|regex>:<value>)`);
|
|
50
|
+
const type = raw.slice(0, idx);
|
|
51
|
+
const value = raw.slice(idx + 1);
|
|
52
|
+
if (type !== "exact" && type !== "suffix" && type !== "regex") throw new UsageError(`error: invalid --match type: ${type} (expected exact|suffix|regex)`);
|
|
53
|
+
if (value === "") throw new UsageError(`error: invalid --match value: ${raw} (value must not be empty)`);
|
|
54
|
+
return {
|
|
55
|
+
type,
|
|
56
|
+
value
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** "Name=value"(首个 = 分割;Name 小写化)。 */
|
|
60
|
+
function parseHeaderSetSpec(raw) {
|
|
61
|
+
const idx = raw.indexOf("=");
|
|
62
|
+
if (idx <= 0) throw new UsageError(`error: invalid --header-set value: ${raw} (expected <name>=<value>, value may be $env:VAR)`);
|
|
63
|
+
return {
|
|
64
|
+
name: raw.slice(0, idx).trim().toLowerCase(),
|
|
65
|
+
value: raw.slice(idx + 1)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** 各命令的 rewrite 组装(仅在存在任一重写选项时构造)。 */
|
|
69
|
+
function buildRewrite(input) {
|
|
70
|
+
const rewrite = {};
|
|
71
|
+
if (input.host !== void 0) rewrite.hostHeader = input.host;
|
|
72
|
+
if (input.strip !== void 0) rewrite.pathPrefixStrip = input.strip;
|
|
73
|
+
if (input.append !== void 0) rewrite.pathPrefixAppend = input.append;
|
|
74
|
+
if (input.headerSet.length > 0) {
|
|
75
|
+
const headerSet = {};
|
|
76
|
+
for (const h of input.headerSet) headerSet[h.name] = h.value;
|
|
77
|
+
rewrite.headerSet = headerSet;
|
|
78
|
+
}
|
|
79
|
+
if (input.headerRemove.length > 0) rewrite.headerRemove = input.headerRemove;
|
|
80
|
+
return Object.keys(rewrite).length === 0 ? void 0 : rewrite;
|
|
81
|
+
}
|
|
82
|
+
function openStore(dataDir) {
|
|
83
|
+
return ProviderStore.open(dataDir);
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { parseMatchSpec as a, requireString as c, str as d, parseHeaderSetSpec as i, resolveDataDir as l, multi as n, parsePortNumber as o, openStore as r, parsePositiveInt as s, buildRewrite as t, resolvedRelayUrls as u };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { t as CliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/cli/config.ts
|
|
7
|
+
const configFileSchema = z.object({
|
|
8
|
+
/** relay 入口 URL 列表(自托管 gateway 地址,如 http://192.168.2.13:8787) */
|
|
9
|
+
relayUrls: z.array(z.string().url()).optional() });
|
|
10
|
+
function configDir(base = homedir()) {
|
|
11
|
+
return join(base, ".aifly");
|
|
12
|
+
}
|
|
13
|
+
function configPath(base = homedir()) {
|
|
14
|
+
return join(configDir(base), "config.json");
|
|
15
|
+
}
|
|
16
|
+
/** 读取并校验配置文件;不存在返回 {};损坏/非法时抛 CliError(不静默重置)。 */
|
|
17
|
+
function loadConfig(base = homedir()) {
|
|
18
|
+
const p = configPath(base);
|
|
19
|
+
if (!existsSync(p)) return {};
|
|
20
|
+
let raw;
|
|
21
|
+
try {
|
|
22
|
+
raw = readFileSync(p, "utf8");
|
|
23
|
+
} catch (err) {
|
|
24
|
+
throw new CliError(`error: cannot read config ${p}: ${err.message}`);
|
|
25
|
+
}
|
|
26
|
+
let parsed;
|
|
27
|
+
try {
|
|
28
|
+
parsed = JSON.parse(raw);
|
|
29
|
+
} catch {
|
|
30
|
+
throw new CliError(`error: config ${p} is not valid JSON (fix or remove it manually)`);
|
|
31
|
+
}
|
|
32
|
+
const result = configFileSchema.safeParse(parsed);
|
|
33
|
+
if (!result.success) throw new CliError(`error: config ${p} failed validation: ${result.error.message}`);
|
|
34
|
+
return result.data;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 解析生效的 relay 入口:flag > 链接内嵌(import 消费侧)> env(AIFLY_RELAY,逗号
|
|
38
|
+
* 分隔) > config file。返回 undefined 表示走 SDK 默认(n0 公共 relay)。
|
|
39
|
+
* 链接层级的语义:aifly1. 链接内嵌的 relayUrls 是提供方签发 invite 的入口——
|
|
40
|
+
* 兑换与后续连接都必须发生在同一 relay 网,故高于机器级 env/config(2026-09-09
|
|
41
|
+
* 实机踩坑:不带 --relay 时落 SDK 公网默认,兑换成功但连接永败且无提示)。
|
|
42
|
+
*/
|
|
43
|
+
function resolveRelayUrls(input) {
|
|
44
|
+
const fromFlag = input.flag?.filter((u) => u.length > 0);
|
|
45
|
+
if (fromFlag && fromFlag.length > 0) return [...fromFlag];
|
|
46
|
+
const fromLink = input.link?.filter((u) => u.length > 0);
|
|
47
|
+
if (fromLink && fromLink.length > 0) return [...fromLink];
|
|
48
|
+
const envRaw = input.env?.trim();
|
|
49
|
+
if (envRaw) {
|
|
50
|
+
const urls = envRaw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
51
|
+
if (urls.length > 0) return urls;
|
|
52
|
+
}
|
|
53
|
+
const fromSettings = input.settings?.filter((u) => u.length > 0);
|
|
54
|
+
if (fromSettings && fromSettings.length > 0) return [...fromSettings];
|
|
55
|
+
const fromFile = input.file?.relayUrls;
|
|
56
|
+
if (fromFile && fromFile.length > 0) return [...fromFile];
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { resolveRelayUrls as n, loadConfig as t };
|