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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
+
import { a as parseMatchSpec, d as str, i as parseHeaderSetSpec, l as resolveDataDir, n as multi, o as parsePortNumber, r as openStore, t as buildRewrite } from "./common-CqxqIg6D.mjs";
|
|
4
|
+
import { n as loadCuratedPresets } from "./models-dev-2UfQoIpQ.mjs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
//#region src/cli/commands/provider/service.ts
|
|
7
|
+
const SPEC = {
|
|
8
|
+
data: {
|
|
9
|
+
type: "string",
|
|
10
|
+
tilde: true
|
|
11
|
+
},
|
|
12
|
+
upstream: { type: "string" },
|
|
13
|
+
port: { type: "string" },
|
|
14
|
+
match: { type: "multi" },
|
|
15
|
+
host: { type: "string" },
|
|
16
|
+
strip: { type: "string" },
|
|
17
|
+
append: { type: "string" },
|
|
18
|
+
"header-set": { type: "multi" },
|
|
19
|
+
"header-remove": { type: "multi" },
|
|
20
|
+
route: { type: "multi" },
|
|
21
|
+
"route-pattern": { type: "multi" },
|
|
22
|
+
secret: { type: "string" },
|
|
23
|
+
preset: { type: "string" },
|
|
24
|
+
form: { type: "string" },
|
|
25
|
+
content: { type: "string" },
|
|
26
|
+
model: { type: "string" },
|
|
27
|
+
"local-prefix": { type: "string" }
|
|
28
|
+
};
|
|
29
|
+
const USAGE = `usage:
|
|
30
|
+
ai-fly service add <name> [--upstream <url> | --preset <id>] [--port <n>]
|
|
31
|
+
[--route <localPrefix>=<upPrefix>[@form1,form2]]... [--route-pattern <match>=<template>]...
|
|
32
|
+
[--secret <name>] [--match <exact|suffix|regex>:<value>]...
|
|
33
|
+
[--host <host>] [--strip <prefix>] [--append <prefix>]
|
|
34
|
+
[--header-set <name>=<value|$env:VAR|$secret:name>]... [--header-remove <name>]... [--data <dir>]
|
|
35
|
+
ai-fly service list [--data <dir>]
|
|
36
|
+
ai-fly service get <name> [--data <dir>]
|
|
37
|
+
ai-fly service remove <name> [--data <dir>]
|
|
38
|
+
ai-fly service test <name> [--form openai-chat|openai-responses|anthropic] [--content <text>]
|
|
39
|
+
[--model <id>] [--local-prefix /v1] [--data <dir>]`;
|
|
40
|
+
async function run(argv, ctx = {}) {
|
|
41
|
+
try {
|
|
42
|
+
const home = ctx.homedir ?? homedir();
|
|
43
|
+
const { options, positionals } = parseArgv(argv, SPEC, { homedir: home });
|
|
44
|
+
const sub = positionals[0];
|
|
45
|
+
if (sub === "add") return add(options, positionals, home);
|
|
46
|
+
if (sub === "list") return list(options, home);
|
|
47
|
+
if (sub === "get") return get(options, positionals, home);
|
|
48
|
+
if (sub === "remove") return remove(options, positionals, home);
|
|
49
|
+
if (sub === "test") return await test(options, positionals, home);
|
|
50
|
+
if (sub === void 0) throw new UsageError(USAGE);
|
|
51
|
+
throw new UsageError(`error: unknown service subcommand '${sub}' (known: add, list, get, remove, test)`);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return reportCliError(err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function add(options, positionals, home) {
|
|
57
|
+
const name = positionals[1];
|
|
58
|
+
if (name === void 0) throw new UsageError("error: service add requires a <name> argument");
|
|
59
|
+
const preset = options.preset === void 0 ? void 0 : loadCuratedPresets().find((candidate) => candidate.id === str(options.preset));
|
|
60
|
+
if (options.preset !== void 0 && preset === void 0) throw new UsageError(`error: unknown preset '${str(options.preset)}' (see: ai-fly presets)`);
|
|
61
|
+
const upstream = str(options.upstream) ?? preset?.baseUrl;
|
|
62
|
+
if (upstream === void 0) throw new UsageError("error: service add requires --upstream <url> (or --preset <id>)");
|
|
63
|
+
const matchSpecs = options.match !== void 0 ? multi(options.match).map(parseMatchSpec) : (preset?.matchDomains ?? []).map((domain) => ({
|
|
64
|
+
type: "suffix",
|
|
65
|
+
value: domain
|
|
66
|
+
}));
|
|
67
|
+
if (matchSpecs.length === 0) throw new UsageError("error: service add requires at least one --match (or --preset carrying match domains)");
|
|
68
|
+
const routes = [
|
|
69
|
+
...multi(options.route).map(parseRouteSpec),
|
|
70
|
+
...multi(options["route-pattern"]).map(parseRoutePatternSpec),
|
|
71
|
+
...preset !== void 0 && options.route === void 0 && options["route-pattern"] === void 0 ? preset.routes ?? [] : []
|
|
72
|
+
];
|
|
73
|
+
const headerSetSpecs = multi(options["header-set"]).map(parseHeaderSetSpec);
|
|
74
|
+
if (options.secret !== void 0) headerSetSpecs.push({
|
|
75
|
+
name: "authorization",
|
|
76
|
+
value: `\$secret:${str(options.secret)}`
|
|
77
|
+
});
|
|
78
|
+
const dataDir = resolveDataDir(str(options.data), home);
|
|
79
|
+
const service = openStore(dataDir).addService({
|
|
80
|
+
name,
|
|
81
|
+
upstream,
|
|
82
|
+
match: matchSpecs,
|
|
83
|
+
...routes.length > 0 ? { routes } : {},
|
|
84
|
+
defaultPort: options.port === void 0 ? preset?.defaultPort : parsePortNumber(str(options.port), "port"),
|
|
85
|
+
rewrite: buildRewrite({
|
|
86
|
+
host: str(options.host),
|
|
87
|
+
strip: str(options.strip),
|
|
88
|
+
append: str(options.append),
|
|
89
|
+
headerSet: headerSetSpecs,
|
|
90
|
+
headerRemove: multi(options["header-remove"]).map((n) => n.toLowerCase())
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
process.stdout.write([
|
|
94
|
+
`service added: ${service.name}`,
|
|
95
|
+
` serviceId : ${service.serviceId}`,
|
|
96
|
+
` upstream : ${service.upstream}`,
|
|
97
|
+
` defaultPort: ${service.defaultPort}`,
|
|
98
|
+
` match : ${service.match.map((m) => `${m.type}:${m.value}`).join(", ")}`,
|
|
99
|
+
...service.routes !== void 0 && service.routes.length > 0 ? [` routes : ${service.routes.map(routeSummary).join(" | ")}`] : [],
|
|
100
|
+
""
|
|
101
|
+
].join("\n"));
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
function list(options, home) {
|
|
105
|
+
const services = openStore(resolveDataDir(str(options.data), home)).listServices();
|
|
106
|
+
if (services.length === 0) {
|
|
107
|
+
process.stdout.write("no services configured (see: ai-fly service add)\n");
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
const lines = [];
|
|
111
|
+
for (const s of services) {
|
|
112
|
+
lines.push(`${s.name} [${s.serviceId}]`);
|
|
113
|
+
lines.push(` upstream : ${s.upstream}`);
|
|
114
|
+
lines.push(` defaultPort: ${s.defaultPort}`);
|
|
115
|
+
lines.push(` match : ${s.match.map((m) => `${m.type}:${m.value}`).join(", ")}`);
|
|
116
|
+
}
|
|
117
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
function remove(options, positionals, home) {
|
|
121
|
+
const name = positionals[1];
|
|
122
|
+
if (name === void 0) throw new UsageError("error: service remove requires a <name> argument");
|
|
123
|
+
openStore(resolveDataDir(str(options.data), home)).removeService(name);
|
|
124
|
+
process.stdout.write(`service removed: ${name}\n`);
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
/** forms 合法集(与契约 ROUTE_FORM 一致的名词法)。 */
|
|
128
|
+
const ROUTE_FORMS = [
|
|
129
|
+
"openai-chat",
|
|
130
|
+
"openai-responses",
|
|
131
|
+
"anthropic"
|
|
132
|
+
];
|
|
133
|
+
function routeSummary(route) {
|
|
134
|
+
if (route.mode === "pattern") return `${route.matchPattern} => ${route.template}`;
|
|
135
|
+
return `${route.localPrefix}=${route.upstreamPrefix}`;
|
|
136
|
+
}
|
|
137
|
+
/** --route <localPrefix>=<upPrefix>[@form1,form2];forms 缺省按前缀语义
|
|
138
|
+
* (含 anthropic → [anthropic],否则 openai 家族两条)。 */
|
|
139
|
+
function parseRouteSpec(raw) {
|
|
140
|
+
const atForms = raw.split("@");
|
|
141
|
+
const body = atForms[0];
|
|
142
|
+
const eq = body.indexOf("=");
|
|
143
|
+
if (eq <= 0 || eq === body.length - 1) throw new UsageError(`error: --route expects <localPrefix>=<upstreamPrefix>[@forms] (got '${raw}')`);
|
|
144
|
+
const localPrefix = normalizePrefix(body.slice(0, eq), raw);
|
|
145
|
+
const upstreamPrefix = normalizePrefix(body.slice(eq + 1), raw);
|
|
146
|
+
let forms;
|
|
147
|
+
if (atForms.length > 1) forms = atForms.slice(1).join("@").split(",").map((form) => {
|
|
148
|
+
if (!ROUTE_FORMS.includes(form)) throw new UsageError(`error: unknown route form '${form}' (known: ${ROUTE_FORMS.join(", ")})`);
|
|
149
|
+
return form;
|
|
150
|
+
});
|
|
151
|
+
else forms = localPrefix.includes("anthropic") ? ["anthropic"] : ["openai-chat", "openai-responses"];
|
|
152
|
+
return {
|
|
153
|
+
forms,
|
|
154
|
+
localPrefix,
|
|
155
|
+
upstreamPrefix
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function normalizePrefix(prefix, raw) {
|
|
159
|
+
const trimmed = prefix.trim();
|
|
160
|
+
if (trimmed === "") return "/";
|
|
161
|
+
if (!trimmed.startsWith("/")) throw new UsageError(`error: route prefixes must start with '/' (got '${raw}')`);
|
|
162
|
+
return trimmed.replace(/\/+$/, "") || "/";
|
|
163
|
+
}
|
|
164
|
+
/** --route-pattern <match>=<template>(= 只切第一处——模板含 = 也不误伤)。 */
|
|
165
|
+
function parseRoutePatternSpec(raw) {
|
|
166
|
+
const eq = raw.indexOf("=");
|
|
167
|
+
if (eq <= 0 || eq === raw.length - 1) throw new UsageError(`error: --route-pattern expects <match>=<template> (got '${raw}')`);
|
|
168
|
+
const matchPattern = raw.slice(0, eq).trim();
|
|
169
|
+
const template = raw.slice(eq + 1).trim();
|
|
170
|
+
if (!matchPattern.startsWith("/")) throw new UsageError(`error: route match must start with '/' (got '${raw}')`);
|
|
171
|
+
return {
|
|
172
|
+
forms: ["openai-chat", "openai-responses"],
|
|
173
|
+
mode: "pattern",
|
|
174
|
+
matchPattern,
|
|
175
|
+
template
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function get(options, positionals, home) {
|
|
179
|
+
const name = positionals[1];
|
|
180
|
+
if (name === void 0) throw new UsageError("error: service get requires a <name> argument");
|
|
181
|
+
const service = openStore(resolveDataDir(str(options.data), home)).getServiceByName(name);
|
|
182
|
+
if (service === void 0) throw new UsageError(`error: service '${name}' not found`);
|
|
183
|
+
const lines = [
|
|
184
|
+
`${service.name} [${service.serviceId}]`,
|
|
185
|
+
` upstream : ${service.upstream}`,
|
|
186
|
+
` defaultPort: ${service.defaultPort}`,
|
|
187
|
+
` match : ${service.match.map((m) => `${m.type}:${m.value}`).join(", ")}`
|
|
188
|
+
];
|
|
189
|
+
if (service.routes !== void 0 && service.routes.length > 0) lines.push(` routes : ${service.routes.map(routeSummary).join(" | ")}`);
|
|
190
|
+
if (service.rewrite !== void 0) {
|
|
191
|
+
const rewrite = [];
|
|
192
|
+
if (service.rewrite.hostHeader !== void 0) rewrite.push(`host: ${service.rewrite.hostHeader}`);
|
|
193
|
+
if (service.rewrite.pathPrefixStrip !== void 0) rewrite.push(`strip: ${service.rewrite.pathPrefixStrip}`);
|
|
194
|
+
if (service.rewrite.pathPrefixAppend !== void 0) rewrite.push(`append: ${service.rewrite.pathPrefixAppend}`);
|
|
195
|
+
for (const [headerName, value] of Object.entries(service.rewrite.headerSet ?? {})) rewrite.push(`header ${headerName}: ${value}`);
|
|
196
|
+
for (const headerName of service.rewrite.headerRemove ?? []) rewrite.push(`remove header ${headerName}`);
|
|
197
|
+
if (rewrite.length > 0) lines.push(` rewrite : ${rewrite.join(" | ")}`);
|
|
198
|
+
}
|
|
199
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
/** service test:与 GUI 行内 test 同引擎(路由命中 + rewrite 注入 + 直打上游)。 */
|
|
203
|
+
async function test(options, positionals, home) {
|
|
204
|
+
const name = positionals[1];
|
|
205
|
+
if (name === void 0) throw new UsageError("error: service test requires a <name> argument");
|
|
206
|
+
const formRaw = options.form === void 0 ? "openai-chat" : str(options.form);
|
|
207
|
+
if (!ROUTE_FORMS.includes(formRaw)) throw new UsageError(`error: --form must be one of ${ROUTE_FORMS.join(", ")}`);
|
|
208
|
+
const dataDir = resolveDataDir(str(options.data), home);
|
|
209
|
+
const service = openStore(dataDir).getServiceByName(name);
|
|
210
|
+
if (service === void 0) throw new UsageError(`error: service '${name}' not found`);
|
|
211
|
+
const { testServiceRoute } = await import("./route-test-Oew0CO1U.mjs");
|
|
212
|
+
const { SecretsStore } = await import("./secrets-u19-8EyW.mjs");
|
|
213
|
+
const secretsStore = SecretsStore.open(dataDir);
|
|
214
|
+
const result = await testServiceRoute({
|
|
215
|
+
service,
|
|
216
|
+
form: formRaw,
|
|
217
|
+
...options["local-prefix"] !== void 0 ? { localPrefix: str(options["local-prefix"]) } : {},
|
|
218
|
+
...options.model !== void 0 ? { model: str(options.model) } : {},
|
|
219
|
+
...options.content !== void 0 ? { content: str(options.content) } : {},
|
|
220
|
+
secrets: (secretName) => secretsStore.resolve(secretName)?.headerValue
|
|
221
|
+
});
|
|
222
|
+
const out = (line) => {
|
|
223
|
+
process.stdout.write(`${line}\n`);
|
|
224
|
+
};
|
|
225
|
+
out(`${result.ok ? "ok" : "failed"}${result.httpStatus !== void 0 ? ` (HTTP ${result.httpStatus})` : ""} - ${result.latencyMs}ms`);
|
|
226
|
+
out(`POST ${result.request.url}`);
|
|
227
|
+
if (result.error !== void 0) out(`error: ${result.error}`);
|
|
228
|
+
if (result.bodyExcerpt !== void 0) out(result.bodyExcerpt.slice(0, 1200));
|
|
229
|
+
return result.ok ? 0 : 1;
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
export { run };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { i as SETTINGS_SCHEMA } from "./rpc-contract-5qyWzT5t.mjs";
|
|
2
|
+
import { i as ensurePrivateDir, r as atomicWriteFileSync } from "./store-CrTUwskE.mjs";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
//#region src/app/settings.ts
|
|
7
|
+
const DEFAULT_SETTINGS = {
|
|
8
|
+
theme: "system",
|
|
9
|
+
modelsDevEnabled: true,
|
|
10
|
+
relayUrls: null
|
|
11
|
+
};
|
|
12
|
+
function settingsPath(base = homedir()) {
|
|
13
|
+
return join(base, ".aifly", "settings.json");
|
|
14
|
+
}
|
|
15
|
+
function loadSettings(base = homedir()) {
|
|
16
|
+
try {
|
|
17
|
+
const raw = readFileSync(settingsPath(base), "utf8");
|
|
18
|
+
return SETTINGS_SCHEMA.parse(JSON.parse(raw));
|
|
19
|
+
} catch {
|
|
20
|
+
return { ...DEFAULT_SETTINGS };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** 补丁式保存(仅覆盖提交的字段;未提交字段保持现状)。 */
|
|
24
|
+
function saveSettings(patch, base = homedir()) {
|
|
25
|
+
const merged = { ...loadSettings(base) };
|
|
26
|
+
for (const [key, value] of Object.entries(patch)) if (value !== void 0) merged[key] = value;
|
|
27
|
+
const next = SETTINGS_SCHEMA.parse(merged);
|
|
28
|
+
const dir = join(base, ".aifly");
|
|
29
|
+
ensurePrivateDir(dir);
|
|
30
|
+
atomicWriteFileSync(settingsPath(base), `${JSON.stringify(next, null, 2)}\n`);
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { saveSettings as n, settingsPath as r, loadSettings as t };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
+
import { n as saveSettings, r as settingsPath, t as loadSettings } from "./settings-C1Hb4zwd.mjs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
//#region src/cli/commands/settings.ts
|
|
6
|
+
const SPEC = { default: { type: "boolean" } };
|
|
7
|
+
const USAGE = `usage:
|
|
8
|
+
ai-fly settings list
|
|
9
|
+
ai-fly settings set theme dark|light|system
|
|
10
|
+
ai-fly settings set models-dev on|off
|
|
11
|
+
ai-fly settings set relay <url>... | --default
|
|
12
|
+
ai-fly relay list
|
|
13
|
+
ai-fly relay set <url>... | --default (alias of 'settings set relay')`;
|
|
14
|
+
async function runAsSettings(argv, ctx = {}) {
|
|
15
|
+
try {
|
|
16
|
+
const home = ctx.homedir ?? homedir();
|
|
17
|
+
const sub = argv[0];
|
|
18
|
+
if (sub === "list" || sub === void 0) return list(home);
|
|
19
|
+
if (sub === "set") return await set(argv.slice(1), home);
|
|
20
|
+
throw new UsageError(`error: unknown settings subcommand '${sub}'\n${USAGE}`);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
return reportCliError(err);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** `ai-fly relay …` 兼容别名:list → settings list 的 relay 行;set → 同落点。 */
|
|
26
|
+
async function runAsRelay(argv, ctx = {}) {
|
|
27
|
+
try {
|
|
28
|
+
const home = ctx.homedir ?? homedir();
|
|
29
|
+
const sub = argv[0];
|
|
30
|
+
if (sub === "list" || sub === void 0) {
|
|
31
|
+
const settings = loadSettings(home);
|
|
32
|
+
process.stdout.write(settings.relayUrls === null ? "relay: SDK defaults (n0 public relays)\n" : `relay (${settings.relayUrls.length}):\n${settings.relayUrls.map((u) => ` ${u}`).join("\n")}\n`);
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
if (sub === "set") {
|
|
36
|
+
const rest = argv.slice(1);
|
|
37
|
+
parseArgv(rest, SPEC, { homedir: home });
|
|
38
|
+
const urls = rest.filter((a) => !a.startsWith("--"));
|
|
39
|
+
for (const url of urls) if (!/^https?:\/\//.test(url)) throw new UsageError(`error: relay URLs must start with http:// or https:// (got '${url}')`);
|
|
40
|
+
applyRelay(urls, rest.includes("--default"), home);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
throw new UsageError(`error: unknown relay subcommand '${sub}' (known: list, set)`);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return reportCliError(err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function list(home) {
|
|
49
|
+
const settings = loadSettings(home);
|
|
50
|
+
const out = (line) => {
|
|
51
|
+
process.stdout.write(`${line}\n`);
|
|
52
|
+
};
|
|
53
|
+
out(`settings (${settingsPath(home)})`);
|
|
54
|
+
out(` theme : ${settings.theme}`);
|
|
55
|
+
out(` models-dev : ${settings.modelsDevEnabled ? "on" : "off"}`);
|
|
56
|
+
out(settings.relayUrls === null ? " relay : SDK defaults (n0 public relays)" : ` relay : ${settings.relayUrls.join(", ")}`);
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
async function set(rest, home) {
|
|
60
|
+
const { options } = parseArgv([...rest], SPEC, { homedir: home });
|
|
61
|
+
const key = rest[0];
|
|
62
|
+
const value = rest[1];
|
|
63
|
+
switch (key) {
|
|
64
|
+
case "theme":
|
|
65
|
+
if (value !== "dark" && value !== "light" && value !== "system") throw new UsageError("error: settings set theme requires dark|light|system");
|
|
66
|
+
saveSettings({ theme: value }, home);
|
|
67
|
+
process.stdout.write(`theme: ${value}\n`);
|
|
68
|
+
return 0;
|
|
69
|
+
case "models-dev":
|
|
70
|
+
if (value !== "on" && value !== "off") throw new UsageError("error: settings set models-dev requires on|off");
|
|
71
|
+
saveSettings({ modelsDevEnabled: value === "on" }, home);
|
|
72
|
+
process.stdout.write(`models-dev: ${value}\n`);
|
|
73
|
+
return 0;
|
|
74
|
+
case "relay": {
|
|
75
|
+
const urls = rest.slice(1).filter((a) => !a.startsWith("--"));
|
|
76
|
+
for (const url of urls) if (!/^https?:\/\//.test(url)) throw new UsageError(`error: relay URLs must start with http:// or https:// (got '${url}')`);
|
|
77
|
+
applyRelay(urls, options.default === true, home);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
case void 0: throw new UsageError(USAGE);
|
|
81
|
+
default: throw new UsageError(`error: unknown settings key '${key}' (known: theme, models-dev, relay)`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function applyRelay(urls, useDefault, home) {
|
|
85
|
+
if (useDefault || urls.length === 0) {
|
|
86
|
+
saveSettings({ relayUrls: null }, home);
|
|
87
|
+
process.stdout.write("relay: SDK defaults (n0 public relays)\n");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (urls.length > 8) throw new UsageError("error: at most 8 relay URLs");
|
|
91
|
+
saveSettings({ relayUrls: [...urls] }, home);
|
|
92
|
+
process.stdout.write(`relay (${urls.length}): ${urls.join(", ")}\n`);
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { runAsRelay, runAsSettings };
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { r as reportCliError, t as CliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { n as parseArgv, r as parseDurationMs, t as assertDurationRange } from "./args-CSxXW9eD.mjs";
|
|
3
|
+
import { l as SERVICE_ENTRY_SCHEMA } from "./frames-BDqvhB2Y.mjs";
|
|
4
|
+
import { t as resolveHttpProxy } from "./proxy-D7VMUVEg.mjs";
|
|
5
|
+
import { n as StoreError } from "./store-CrTUwskE.mjs";
|
|
6
|
+
import { c as requireString, d as str, l as resolveDataDir, r as openStore, u as resolvedRelayUrls } from "./common-CqxqIg6D.mjs";
|
|
7
|
+
import { t as openExistingFabric } from "./serve-Be4zdKVj.mjs";
|
|
8
|
+
import { s as buildServiceEntry } from "./rewrite-xZHWnyJk.mjs";
|
|
6
9
|
import { homedir } from "node:os";
|
|
7
10
|
import { z } from "zod";
|
|
8
11
|
//#region src/provider/link.ts
|
|
@@ -86,7 +89,7 @@ async function run(argv, ctx = {}) {
|
|
|
86
89
|
const store = openStore(dataDir);
|
|
87
90
|
const resolvedRelay = resolvedRelayUrls(options, home);
|
|
88
91
|
if (resolvedRelay === void 0) process.stdout.write("warning: no relay configured for this command (flag/env/config); the link will carry public default relays - if the provider daemon runs on a custom relay, pass a matching --relay\n");
|
|
89
|
-
fabric = await openExistingFabric(dataDir, resolvedRelay);
|
|
92
|
+
fabric = await openExistingFabric(dataDir, resolvedRelay, resolveHttpProxy(void 0));
|
|
90
93
|
const invite = await issueInvite(fabric, ttlMs, options["allow-relayless"] === true);
|
|
91
94
|
const relayStatus = await fabric.relayStatus();
|
|
92
95
|
const result = buildShareLink({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
1
|
+
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
2
|
+
import { n as consumersRoot, s as listKeyrings } from "./store-BiTS2FTl.mjs";
|
|
3
|
+
import { a as ringsForRun, n as ctxHomedir, r as ctxOut, t as createSdkFabricFactory } from "./common-CgxCXeTt.mjs";
|
|
4
|
+
import { n as createFabricProviderTransport, t as startEngine } from "./runtime-D_nxUEjd.mjs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
//#region src/cli/commands/consumer/status.ts
|
|
6
7
|
/** 状态显示名(spec 六态)。 */
|
|
@@ -58,7 +59,8 @@ async function run(argv, ctx = {}) {
|
|
|
58
59
|
tilde: true
|
|
59
60
|
},
|
|
60
61
|
verbose: { type: "boolean" },
|
|
61
|
-
relay: { type: "multi" }
|
|
62
|
+
relay: { type: "multi" },
|
|
63
|
+
proxy: { type: "string" }
|
|
62
64
|
}, { homedir: ctx.homedir ?? homedir() });
|
|
63
65
|
const verbose = options.verbose === true || positionals.includes("-v");
|
|
64
66
|
const out = ctxOut(ctx);
|
|
@@ -70,7 +72,7 @@ async function run(argv, ctx = {}) {
|
|
|
70
72
|
for (const line of formatStatus([], verbose)) out(line);
|
|
71
73
|
return 0;
|
|
72
74
|
}
|
|
73
|
-
const factory = await createSdkFabricFactory(options.relay, ctx);
|
|
75
|
+
const factory = await createSdkFabricFactory(options.relay, ctx, void 0, options.proxy);
|
|
74
76
|
const engine = await startEngine({
|
|
75
77
|
rings: engineRings,
|
|
76
78
|
consumersRoot: root,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { r as reportCliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
+
import { t as resolveHttpProxy } from "./proxy-D7VMUVEg.mjs";
|
|
4
|
+
import { d as str, l as resolveDataDir, r as openStore, u as resolvedRelayUrls } from "./common-CqxqIg6D.mjs";
|
|
5
|
+
import { t as openExistingFabric } from "./serve-Be4zdKVj.mjs";
|
|
6
|
+
import { c as detailDisplayLines, o as buildServiceDetail } from "./rewrite-xZHWnyJk.mjs";
|
|
5
7
|
import { homedir } from "node:os";
|
|
6
8
|
//#region src/cli/commands/provider/status.ts
|
|
7
9
|
const SPEC = {
|
|
@@ -34,7 +36,7 @@ async function run(argv, ctx = {}) {
|
|
|
34
36
|
for (const line of detailDisplayLines(buildServiceDetail(s))) lines.push(` ${line}`);
|
|
35
37
|
}
|
|
36
38
|
try {
|
|
37
|
-
fabric = await openExistingFabric(dataDir, resolvedRelayUrls(options, home));
|
|
39
|
+
fabric = await openExistingFabric(dataDir, resolvedRelayUrls(options, home), resolveHttpProxy(void 0));
|
|
38
40
|
const fabricIdHex = await fabric.fabricIdHex();
|
|
39
41
|
const members = await fabric.members();
|
|
40
42
|
const relay = await fabric.relayStatus();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as CliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { s as randomZ32 } from "./rpc-contract-5qyWzT5t.mjs";
|
|
3
|
+
import { l as SERVICE_ENTRY_SCHEMA } from "./frames-BDqvhB2Y.mjs";
|
|
4
4
|
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -274,74 +274,4 @@ function removeKeyring(root, ref) {
|
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
276
|
//#endregion
|
|
277
|
-
|
|
278
|
-
function ctxOut(ctx) {
|
|
279
|
-
return ctx.out ?? ((line) => process.stdout.write(`${line}\n`));
|
|
280
|
-
}
|
|
281
|
-
function ctxHomedir(ctx) {
|
|
282
|
-
return ctx.homedir ?? homedir();
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* 真实 Fabric 工厂(CLI 层唯一触 SDK 的位置):动态 import 规避 vitest worker 与
|
|
286
|
-
* 原生模块的不兼容;relay 配置映射为 SDK FabricOptions.relay(custom urls),
|
|
287
|
-
* 未配置时缺省走 SDK 默认(n0)。
|
|
288
|
-
* 解析层级(Owner 裁决 2026-09-13:链接带来的会合点优先于环境/配置默认——
|
|
289
|
-
* provider 在那个 relay 上,不拨它就永远会不了面):逐次解析为
|
|
290
|
-
* flag > ring/链接内嵌 relay(opts.relayUrls,调用侧按 ring 传入)> env > file。
|
|
291
|
-
* @param linkRelayUrls aifly1. 链接内嵌 relay(import 的 join 阶段用;空 = 缺席)
|
|
292
|
-
*/
|
|
293
|
-
async function createSdkFabricFactory(relayFlag, ctx = {}, linkRelayUrls) {
|
|
294
|
-
const sdk = await loadSdk();
|
|
295
|
-
const toOpts = (opts) => {
|
|
296
|
-
const fabricOpts = { dataDir: opts.dataDir };
|
|
297
|
-
const link = opts.relayUrls ?? linkRelayUrls;
|
|
298
|
-
const urls = resolveRelayUrls({
|
|
299
|
-
...relayFlag !== void 0 ? { flag: relayFlag } : {},
|
|
300
|
-
...link !== void 0 ? { link } : {},
|
|
301
|
-
env: process.env.AIFLY_RELAY,
|
|
302
|
-
file: loadConfig(ctxHomedir(ctx))
|
|
303
|
-
});
|
|
304
|
-
if (urls !== void 0 && urls.length > 0) fabricOpts.relay = {
|
|
305
|
-
mode: "custom",
|
|
306
|
-
urls
|
|
307
|
-
};
|
|
308
|
-
return fabricOpts;
|
|
309
|
-
};
|
|
310
|
-
return {
|
|
311
|
-
open: async (opts) => await sdk.Fabric.open(toOpts(opts)),
|
|
312
|
-
joinWithToken: async (opts, token) => await sdk.Fabric.joinWithToken(toOpts(opts), token)
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
/** 等待 SIGINT/SIGTERM(run / import --run 长驻阻塞;测试不触达)。 */
|
|
316
|
-
function waitForSignals() {
|
|
317
|
-
return new Promise((resolve) => {
|
|
318
|
-
let done = false;
|
|
319
|
-
const finish = () => {
|
|
320
|
-
if (done) return;
|
|
321
|
-
done = true;
|
|
322
|
-
process.off("SIGINT", finish);
|
|
323
|
-
process.off("SIGTERM", finish);
|
|
324
|
-
resolve();
|
|
325
|
-
};
|
|
326
|
-
process.on("SIGINT", finish);
|
|
327
|
-
process.on("SIGTERM", finish);
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
/** 打印引擎监听清单(run 启动横幅与 status 共用)。 */
|
|
331
|
-
function printListeners(out, info) {
|
|
332
|
-
if (info.length === 0) {
|
|
333
|
-
out("local endpoints: (none - no authorized services yet)");
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
out("local endpoints:");
|
|
337
|
-
for (const l of info) {
|
|
338
|
-
const note = l.autoAssigned ? ` [NOTICE: requested ${l.requested} was unavailable]` : "";
|
|
339
|
-
out(` 127.0.0.1:${l.port} <- ${l.alias} / ${l.name} [${l.serviceId}]${note}`);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
/** 引擎所需钥环集合加载(run/import --run/status 共用)。 */
|
|
343
|
-
function ringsForRun(rings) {
|
|
344
|
-
return rings.filter((r) => r.keys.length > 0 || r.services.length > 0);
|
|
345
|
-
}
|
|
346
|
-
//#endregion
|
|
347
|
-
export { removeKeyring as _, ringsForRun as a, upsertKey as b, consumersRoot as c, hasFabricIdentity as d, keyringDir as f, reconcileKeyMetadata as g, mergeImportView as h, printListeners as i, fabricDir as l, loadKeyring as m, ctxHomedir as n, waitForSignals as o, listKeyrings as p, ctxOut as r, applyCatalog as s, createSdkFabricFactory as t, findKeyringDir as u, saveKeyring as v, setPort as y };
|
|
277
|
+
export { hasFabricIdentity as a, loadKeyring as c, removeKeyring as d, saveKeyring as f, findKeyringDir as i, mergeImportView as l, upsertKey as m, consumersRoot as n, keyringDir as o, setPort as p, fabricDir as r, listKeyrings as s, applyCatalog as t, reconcileKeyMetadata as u };
|