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.
Files changed (44) hide show
  1. package/dist/ai-fly.js +48 -22
  2. package/dist/args-CSxXW9eD.mjs +69 -0
  3. package/dist/common-CgxCXeTt.mjs +83 -0
  4. package/dist/common-CqxqIg6D.mjs +86 -0
  5. package/dist/config-BlZnFqx7.mjs +59 -0
  6. package/dist/daemon-DklsEniO.mjs +291 -0
  7. package/dist/{forget-BEbNwPip.mjs → forget-B17zGwj4.mjs} +3 -2
  8. package/dist/{sdk-C6xDRbzg.mjs → frames-BDqvhB2Y.mjs} +1 -10
  9. package/dist/{group-Csy9VmFb.mjs → group-B96IKEDF.mjs} +24 -4
  10. package/dist/{import-BVAFw5Tx.mjs → import-tilxdbTH.mjs} +8 -6
  11. package/dist/{join-E1_-Rt-7.mjs → join-DsKUAibH.mjs} +7 -5
  12. package/dist/{join-B9op0juF.mjs → join-hyqiwEek.mjs} +3 -3
  13. package/dist/{key-CK77IYXp.mjs → key-BiEp995L.mjs} +2 -2
  14. package/dist/{key-Bt4EWiuO.mjs → key-CNufbB__.mjs} +4 -3
  15. package/dist/local-test-C3HGjGY3.mjs +101 -0
  16. package/dist/models-dev-2UfQoIpQ.mjs +244 -0
  17. package/dist/{mux-xHl9Jrbi.mjs → mux-CS1zhF4W.mjs} +2 -2
  18. package/dist/{ports-BQukx7wS.mjs → ports-rRppuBgo.mjs} +3 -2
  19. package/dist/presets-C73CAMZ6.mjs +55 -0
  20. package/dist/proxy-D7VMUVEg.mjs +21 -0
  21. package/dist/{revoke-CyrOzc8t.mjs → revoke-CkpvK33Q.mjs} +5 -4
  22. package/dist/rewrite-xZHWnyJk.mjs +313 -0
  23. package/dist/route-test-Oew0CO1U.mjs +60 -0
  24. package/dist/rpc-contract-5qyWzT5t.mjs +638 -0
  25. package/dist/{run-Be8H-IRf.mjs → run-DbPXAm6h.mjs} +8 -5
  26. package/dist/{runtime-Dg4en1D8.mjs → runtime-D_nxUEjd.mjs} +3 -3
  27. package/dist/secret-SA37VND5.mjs +95 -0
  28. package/dist/secrets-BYI6ooTJ.mjs +113 -0
  29. package/dist/secrets-u19-8EyW.mjs +2 -0
  30. package/dist/{serve-DuyQMeJh.mjs → serve-Be4zdKVj.mjs} +14 -425
  31. package/dist/{serve-R03mDBwd.mjs → serve-DSBlodTF.mjs} +18 -6
  32. package/dist/service-B5uORhl3.mjs +232 -0
  33. package/dist/settings-C1Hb4zwd.mjs +34 -0
  34. package/dist/settings-DU0YqNDt.mjs +95 -0
  35. package/dist/{share-DGM2TOft.mjs → share-Cug0944N.mjs} +8 -5
  36. package/dist/{status-DRaKNvqz.mjs → status-B-j454u2.mjs} +7 -5
  37. package/dist/{status-W2OS7WB6.mjs → status-SkNsxGnk.mjs} +6 -4
  38. package/dist/{common-4VeA_3wo.mjs → store-BiTS2FTl.mjs} +3 -73
  39. package/dist/store-CrTUwskE.mjs +629 -0
  40. package/dist/test-Bc0OKBAi.mjs +64 -0
  41. package/package.json +1 -1
  42. package/dist/common-BULbPtYn.mjs +0 -1295
  43. package/dist/config-CO3WyW2H.mjs +0 -173
  44. 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 serve [--data <dir>] [--relay <url>]... run the provider daemon
12
- ai-fly service add|list|remove ... manage services
13
- ai-fly group add|set-services|list ... manage groups
14
- ai-fly key issue|list|revoke --group <name> manage group keys
15
- ai-fly share --group <name> [--ttl <dur>] mint a share link (token + key)
16
- ai-fly revoke <endpointId> eject a device (fabric-level)
17
- ai-fly status [provider|consumer] [--verbose] snapshot (auto: both if present)
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 <dweb1-token> [--data <dir>] admit this device (fabric layer)
21
- ai-fly key add <sk-aifly-key> --provider <id> bare key into an existing ring
22
- ai-fly import <aifly1-link> [--run] [--preview] bundle link: join + keyring
23
- ai-fly run [--data <dir>] [--strict-ports] run the local gateway
24
- ai-fly ports [--data <dir>] [<serviceId> --port <n>] view/override mapped ports
25
- ai-fly status [provider|consumer] [--verbose] snapshot (auto: both if present)
26
- ai-fly forget <endpointId|8-char-prefix> drop an imported provider
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-R03mDBwd.mjs"))(rest)).with("service", () => lazy(() => import("./service-Cd4R47n4.mjs"))(rest)).with("group", () => lazy(() => import("./group-Csy9VmFb.mjs"))(rest)).with("share", () => lazy(() => import("./share-DGM2TOft.mjs"))(rest)).with("revoke", () => lazy(() => import("./revoke-CyrOzc8t.mjs"))(rest)).with("join", () => lazy(() => import("./join-E1_-Rt-7.mjs"))(rest)).with("import", () => lazy(() => import("./import-BVAFw5Tx.mjs"))(rest)).with("run", () => lazy(() => import("./run-Be8H-IRf.mjs"))(rest)).with("ports", () => lazy(() => import("./ports-BQukx7wS.mjs"))(rest)).with("forget", () => lazy(() => import("./forget-BEbNwPip.mjs"))(rest)).with("key", () => {
45
- if (rest[0] === "add") return lazy(() => import("./key-Bt4EWiuO.mjs"))(rest);
46
- return lazy(() => import("./key-CK77IYXp.mjs"))(rest);
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-W2OS7WB6.mjs"))(argv));
54
- if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-DRaKNvqz.mjs"))(argv));
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 === void 0 || first === "--help" || first === "-h") {
82
+ if (first === "--help" || first === "-h") {
64
83
  process.stdout.write(`${USAGE}\n`);
65
- return first === void 0 ? 2 : 0;
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 };