ai-fly 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -0
- package/dist/ai-fly.d.ts +1 -0
- package/dist/ai-fly.js +76 -0
- package/dist/common-4VeA_3wo.mjs +347 -0
- package/dist/common-BULbPtYn.mjs +1295 -0
- package/dist/config-CO3WyW2H.mjs +173 -0
- package/dist/errors-BGtxPkM7.mjs +27 -0
- package/dist/forget-BEbNwPip.mjs +22 -0
- package/dist/group-Csy9VmFb.mjs +92 -0
- package/dist/import-BVAFw5Tx.mjs +63 -0
- package/dist/join-B9op0juF.mjs +228 -0
- package/dist/join-E1_-Rt-7.mjs +32 -0
- package/dist/key-Bt4EWiuO.mjs +31 -0
- package/dist/key-CK77IYXp.mjs +67 -0
- package/dist/mux-xHl9Jrbi.mjs +630 -0
- package/dist/ports-BQukx7wS.mjs +46 -0
- package/dist/ports-pI59MtmI.mjs +68 -0
- package/dist/revoke-CyrOzc8t.mjs +34 -0
- package/dist/run-Be8H-IRf.mjs +42 -0
- package/dist/runtime-Dg4en1D8.mjs +1372 -0
- package/dist/sdk-C6xDRbzg.mjs +394 -0
- package/dist/serve-DuyQMeJh.mjs +1665 -0
- package/dist/serve-R03mDBwd.mjs +45 -0
- package/dist/service-Cd4R47n4.mjs +94 -0
- package/dist/share-DGM2TOft.mjs +133 -0
- package/dist/status-DRaKNvqz.mjs +102 -0
- package/dist/status-W2OS7WB6.mjs +57 -0
- package/package.json +64 -6
- package/index.js +0 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
import { o as parseArgv } from "./config-CO3WyW2H.mjs";
|
|
3
|
+
import { d as str, l as resolveDataDir, u as resolvedRelayUrls } from "./common-BULbPtYn.mjs";
|
|
4
|
+
import { n as startProviderDaemon } from "./serve-DuyQMeJh.mjs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
//#region src/cli/commands/provider/serve.ts
|
|
7
|
+
const SPEC = {
|
|
8
|
+
data: {
|
|
9
|
+
type: "string",
|
|
10
|
+
tilde: true
|
|
11
|
+
},
|
|
12
|
+
relay: { type: "multi" },
|
|
13
|
+
alias: { type: "string" },
|
|
14
|
+
"log-usage": { type: "boolean" }
|
|
15
|
+
};
|
|
16
|
+
async function run(argv, ctx = {}) {
|
|
17
|
+
try {
|
|
18
|
+
const home = ctx.homedir ?? homedir();
|
|
19
|
+
const { options, positionals } = parseArgv(argv, SPEC, { homedir: home });
|
|
20
|
+
if (positionals.length > 0) throw new UsageError(`error: unexpected argument '${positionals[0]}' (serve takes no positional arguments)`);
|
|
21
|
+
const dataDir = resolveDataDir(str(options.data), home);
|
|
22
|
+
const relayUrls = resolvedRelayUrls(options, home);
|
|
23
|
+
const daemon = await startProviderDaemon({
|
|
24
|
+
dataDir,
|
|
25
|
+
relayUrls,
|
|
26
|
+
alias: str(options.alias),
|
|
27
|
+
logUsage: options["log-usage"] === true
|
|
28
|
+
});
|
|
29
|
+
process.stdout.write(`${daemon.banner}\n`);
|
|
30
|
+
let stopping = false;
|
|
31
|
+
const stop = () => {
|
|
32
|
+
if (stopping) process.exit(130);
|
|
33
|
+
stopping = true;
|
|
34
|
+
daemon.stop().catch(() => void 0).finally(() => process.exit(0));
|
|
35
|
+
};
|
|
36
|
+
process.once("SIGINT", stop);
|
|
37
|
+
process.once("SIGTERM", stop);
|
|
38
|
+
await new Promise(() => {});
|
|
39
|
+
return 0;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return reportCliError(err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { run };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
import { o as parseArgv } from "./config-CO3WyW2H.mjs";
|
|
3
|
+
import { a as parseMatchSpec, c as requireString, d as str, i as parseHeaderSetSpec, l as resolveDataDir, n as multi, o as parsePortNumber, r as openStore, t as buildRewrite } from "./common-BULbPtYn.mjs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
//#region src/cli/commands/provider/service.ts
|
|
6
|
+
const SPEC = {
|
|
7
|
+
data: {
|
|
8
|
+
type: "string",
|
|
9
|
+
tilde: true
|
|
10
|
+
},
|
|
11
|
+
upstream: { type: "string" },
|
|
12
|
+
port: { type: "string" },
|
|
13
|
+
match: { type: "multi" },
|
|
14
|
+
host: { type: "string" },
|
|
15
|
+
strip: { type: "string" },
|
|
16
|
+
append: { type: "string" },
|
|
17
|
+
"header-set": { type: "multi" },
|
|
18
|
+
"header-remove": { type: "multi" }
|
|
19
|
+
};
|
|
20
|
+
const USAGE = `usage:
|
|
21
|
+
ai-fly service add <name> --upstream <url> [--port <n>] [--match <exact|suffix|regex>:<value>]...
|
|
22
|
+
[--host <host>] [--strip <prefix>] [--append <prefix>]
|
|
23
|
+
[--header-set <name>=<value|$env:VAR>]... [--header-remove <name>]... [--data <dir>]
|
|
24
|
+
ai-fly service list [--data <dir>]
|
|
25
|
+
ai-fly service remove <name> [--data <dir>]`;
|
|
26
|
+
async function run(argv, ctx = {}) {
|
|
27
|
+
try {
|
|
28
|
+
const home = ctx.homedir ?? homedir();
|
|
29
|
+
const { options, positionals } = parseArgv(argv, SPEC, { homedir: home });
|
|
30
|
+
const sub = positionals[0];
|
|
31
|
+
if (sub === "add") return add(options, positionals, home);
|
|
32
|
+
if (sub === "list") return list(options, home);
|
|
33
|
+
if (sub === "remove") return remove(options, positionals, home);
|
|
34
|
+
if (sub === void 0) throw new UsageError(USAGE);
|
|
35
|
+
throw new UsageError(`error: unknown service subcommand '${sub}' (known: add, list, remove)`);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return reportCliError(err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function add(options, positionals, home) {
|
|
41
|
+
const name = positionals[1];
|
|
42
|
+
if (name === void 0) throw new UsageError("error: service add requires a <name> argument");
|
|
43
|
+
const upstream = requireString(str(options.upstream), "upstream");
|
|
44
|
+
const matchSpecs = multi(options.match).map(parseMatchSpec);
|
|
45
|
+
if (matchSpecs.length === 0) throw new UsageError("error: service add requires at least one --match <type>:<value>");
|
|
46
|
+
const dataDir = resolveDataDir(str(options.data), home);
|
|
47
|
+
const service = openStore(dataDir).addService({
|
|
48
|
+
name,
|
|
49
|
+
upstream,
|
|
50
|
+
match: matchSpecs,
|
|
51
|
+
defaultPort: options.port === void 0 ? void 0 : parsePortNumber(str(options.port), "port"),
|
|
52
|
+
rewrite: buildRewrite({
|
|
53
|
+
host: str(options.host),
|
|
54
|
+
strip: str(options.strip),
|
|
55
|
+
append: str(options.append),
|
|
56
|
+
headerSet: multi(options["header-set"]).map(parseHeaderSetSpec),
|
|
57
|
+
headerRemove: multi(options["header-remove"]).map((n) => n.toLowerCase())
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
process.stdout.write([
|
|
61
|
+
`service added: ${service.name}`,
|
|
62
|
+
` serviceId : ${service.serviceId}`,
|
|
63
|
+
` upstream : ${service.upstream}`,
|
|
64
|
+
` defaultPort: ${service.defaultPort}`,
|
|
65
|
+
` match : ${service.match.map((m) => `${m.type}:${m.value}`).join(", ")}`,
|
|
66
|
+
""
|
|
67
|
+
].join("\n"));
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
function list(options, home) {
|
|
71
|
+
const services = openStore(resolveDataDir(str(options.data), home)).listServices();
|
|
72
|
+
if (services.length === 0) {
|
|
73
|
+
process.stdout.write("no services configured (see: ai-fly service add)\n");
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
const lines = [];
|
|
77
|
+
for (const s of services) {
|
|
78
|
+
lines.push(`${s.name} [${s.serviceId}]`);
|
|
79
|
+
lines.push(` upstream : ${s.upstream}`);
|
|
80
|
+
lines.push(` defaultPort: ${s.defaultPort}`);
|
|
81
|
+
lines.push(` match : ${s.match.map((m) => `${m.type}:${m.value}`).join(", ")}`);
|
|
82
|
+
}
|
|
83
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
function remove(options, positionals, home) {
|
|
87
|
+
const name = positionals[1];
|
|
88
|
+
if (name === void 0) throw new UsageError("error: service remove requires a <name> argument");
|
|
89
|
+
openStore(resolveDataDir(str(options.data), home)).removeService(name);
|
|
90
|
+
process.stdout.write(`service removed: ${name}\n`);
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { run };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { r as reportCliError, t as CliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
import { a as assertDurationRange, o as parseArgv, s as parseDurationMs } from "./config-CO3WyW2H.mjs";
|
|
3
|
+
import { u as SERVICE_ENTRY_SCHEMA } from "./sdk-C6xDRbzg.mjs";
|
|
4
|
+
import { c as requireString, d as str, l as resolveDataDir, p as StoreError, r as openStore, u as resolvedRelayUrls } from "./common-BULbPtYn.mjs";
|
|
5
|
+
import { i as buildServiceEntry, t as openExistingFabric } from "./serve-DuyQMeJh.mjs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
//#region src/provider/link.ts
|
|
9
|
+
const SHARE_LINK_PREFIX = "aifly1.";
|
|
10
|
+
/** TTL 值域(share-link spec:1s..30d;默认 60min)。 */
|
|
11
|
+
const SHARE_TTL_MIN_MS = 1e3;
|
|
12
|
+
const SHARE_TTL_MAX_MS = 2592e6;
|
|
13
|
+
const SHARE_TTL_DEFAULT_MS = 36e5;
|
|
14
|
+
/** 用户面提示(英文 ASCII):链接等同密钥。 */
|
|
15
|
+
const SHARE_LINK_SECRET_HINT = "WARNING: this link contains a secret access key. Treat it like a password.";
|
|
16
|
+
z.strictObject({
|
|
17
|
+
v: z.literal(1),
|
|
18
|
+
invite: z.string().min(1).max(8192),
|
|
19
|
+
key: z.string().min(8).max(256),
|
|
20
|
+
keyId: z.string().min(1).max(128),
|
|
21
|
+
provider: z.strictObject({
|
|
22
|
+
alias: z.string().min(1).max(256),
|
|
23
|
+
endpointId: z.string().min(1).max(256),
|
|
24
|
+
relayUrls: z.array(z.string().min(1).max(2048))
|
|
25
|
+
}),
|
|
26
|
+
group: z.string().min(1).max(256),
|
|
27
|
+
services: z.array(SERVICE_ENTRY_SCHEMA).max(64)
|
|
28
|
+
});
|
|
29
|
+
function encodeShareLink(payload) {
|
|
30
|
+
return SHARE_LINK_PREFIX + Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 构造分享链接:前置检查(组存在且非空)-> 签发新钥(每次 share 一枚;旧钥不可再现)
|
|
34
|
+
* -> 组内服务脱敏视图 -> payload + link + 警告。
|
|
35
|
+
*/
|
|
36
|
+
function buildShareLink(input) {
|
|
37
|
+
if (input.store.getGroup(input.group) === void 0) throw new StoreError("not-found", `error: group '${input.group}' not found`);
|
|
38
|
+
const services = input.store.groupServices(input.group);
|
|
39
|
+
if (services.length === 0) throw new StoreError("invalid", `error: group '${input.group}' has no services; a share link would be meaningless`);
|
|
40
|
+
const issued = input.store.issueKey(input.group);
|
|
41
|
+
const serviceEntries = services.map(buildServiceEntry);
|
|
42
|
+
const relayUrls = input.relayUrls === void 0 ? [] : [...input.relayUrls];
|
|
43
|
+
const payload = {
|
|
44
|
+
v: 1,
|
|
45
|
+
invite: input.invite,
|
|
46
|
+
key: issued.key,
|
|
47
|
+
keyId: issued.keyId,
|
|
48
|
+
provider: {
|
|
49
|
+
alias: input.alias ?? input.store.alias ?? "provider",
|
|
50
|
+
endpointId: input.endpointId,
|
|
51
|
+
relayUrls
|
|
52
|
+
},
|
|
53
|
+
group: input.group,
|
|
54
|
+
services: serviceEntries
|
|
55
|
+
};
|
|
56
|
+
const warnings = [];
|
|
57
|
+
if (relayUrls.length === 0) warnings.push("WARNING: no relay is configured; consumers may be unable to reach you. Hint: configure a relay on a stable address (domain or tunnel) so the entry URL never changes; opendweb relays are self-hostable.");
|
|
58
|
+
return {
|
|
59
|
+
payload,
|
|
60
|
+
link: encodeShareLink(payload),
|
|
61
|
+
warnings,
|
|
62
|
+
keyId: issued.keyId
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/cli/commands/provider/share.ts
|
|
67
|
+
const SPEC = {
|
|
68
|
+
data: {
|
|
69
|
+
type: "string",
|
|
70
|
+
tilde: true
|
|
71
|
+
},
|
|
72
|
+
group: { type: "string" },
|
|
73
|
+
ttl: { type: "string" },
|
|
74
|
+
relay: { type: "multi" },
|
|
75
|
+
"allow-relayless": { type: "boolean" }
|
|
76
|
+
};
|
|
77
|
+
async function run(argv, ctx = {}) {
|
|
78
|
+
let fabric;
|
|
79
|
+
try {
|
|
80
|
+
const home = ctx.homedir ?? homedir();
|
|
81
|
+
const { options } = parseArgv(argv, SPEC, { homedir: home });
|
|
82
|
+
const group = requireString(str(options.group), "group");
|
|
83
|
+
const dataDir = resolveDataDir(str(options.data), home);
|
|
84
|
+
const ttlMs = options.ttl === void 0 ? SHARE_TTL_DEFAULT_MS : parseDurationMs(str(options.ttl), "ttl");
|
|
85
|
+
assertDurationRange(ttlMs, SHARE_TTL_MIN_MS, SHARE_TTL_MAX_MS, "ttl", "1s..30d");
|
|
86
|
+
const store = openStore(dataDir);
|
|
87
|
+
const resolvedRelay = resolvedRelayUrls(options, home);
|
|
88
|
+
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);
|
|
90
|
+
const invite = await issueInvite(fabric, ttlMs, options["allow-relayless"] === true);
|
|
91
|
+
const relayStatus = await fabric.relayStatus();
|
|
92
|
+
const result = buildShareLink({
|
|
93
|
+
store,
|
|
94
|
+
group,
|
|
95
|
+
invite,
|
|
96
|
+
endpointId: fabric.endpointId,
|
|
97
|
+
relayUrls: relayStatus.urls
|
|
98
|
+
});
|
|
99
|
+
process.stdout.write([
|
|
100
|
+
`share link for group '${group}' (keyId ${result.keyId}, ttl ${describeTtl(ttlMs)}):`,
|
|
101
|
+
"",
|
|
102
|
+
result.link,
|
|
103
|
+
"",
|
|
104
|
+
...result.warnings,
|
|
105
|
+
SHARE_LINK_SECRET_HINT,
|
|
106
|
+
""
|
|
107
|
+
].join("\n"));
|
|
108
|
+
return 0;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return reportCliError(err);
|
|
111
|
+
} finally {
|
|
112
|
+
await fabric?.shutdown().catch(() => void 0);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function issueInvite(fabric, ttlMs, allowRelayless) {
|
|
116
|
+
try {
|
|
117
|
+
return await fabric.invite(ttlMs);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if ((err instanceof Error ? err.message : String(err)).includes("[invite-without-relay]")) {
|
|
120
|
+
if (!allowRelayless) throw new CliError("error: invite needs a relay (or advertiseAddrs); pass --allow-relayless to issue a relayless invite");
|
|
121
|
+
return await fabric.invite(ttlMs, void 0, { allowRelayless: true });
|
|
122
|
+
}
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function describeTtl(ms) {
|
|
127
|
+
if (ms % 864e5 === 0) return `${ms / 864e5}d`;
|
|
128
|
+
if (ms % 36e5 === 0) return `${ms / 36e5}h`;
|
|
129
|
+
if (ms % 6e4 === 0) return `${ms / 6e4}m`;
|
|
130
|
+
return `${ms}ms`;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { run };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { o as parseArgv } from "./config-CO3WyW2H.mjs";
|
|
2
|
+
import { a as ringsForRun, c as consumersRoot, n as ctxHomedir, p as listKeyrings, r as ctxOut, t as createSdkFabricFactory } from "./common-4VeA_3wo.mjs";
|
|
3
|
+
import { n as createFabricProviderTransport, t as startEngine } from "./runtime-Dg4en1D8.mjs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
//#region src/cli/commands/consumer/status.ts
|
|
6
|
+
/** 状态显示名(spec 六态)。 */
|
|
7
|
+
const STATE_LABEL = {
|
|
8
|
+
"not-connected": "not connected",
|
|
9
|
+
"connected-unauthed": "connected (not authorized)",
|
|
10
|
+
direct: "connected (direct)",
|
|
11
|
+
relay: "connected (relay)",
|
|
12
|
+
offline: "offline",
|
|
13
|
+
"key-all-invalid": "all keys rejected"
|
|
14
|
+
};
|
|
15
|
+
function formatStatus(statuses, verbose) {
|
|
16
|
+
const lines = [];
|
|
17
|
+
if (statuses.length === 0) {
|
|
18
|
+
lines.push("no providers imported yet");
|
|
19
|
+
return lines;
|
|
20
|
+
}
|
|
21
|
+
for (const s of statuses) {
|
|
22
|
+
lines.push(`provider ${s.alias} (${s.endpointId})`);
|
|
23
|
+
lines.push(` state : ${STATE_LABEL[s.state]}`);
|
|
24
|
+
lines.push(` served : ${s.servedCount} request(s), ${s.bufferOverflows} buffer overflow(s)`);
|
|
25
|
+
if (s.lastError !== void 0) lines.push(` last error : ${s.lastError}`);
|
|
26
|
+
if (s.services.length === 0) lines.push(" services : (none)");
|
|
27
|
+
else {
|
|
28
|
+
lines.push(" services :");
|
|
29
|
+
for (const svc of s.services) {
|
|
30
|
+
const port = s.ports[svc.serviceId] ?? svc.defaultPort;
|
|
31
|
+
const pinned = s.ports[svc.serviceId] !== void 0;
|
|
32
|
+
lines.push(` - ${svc.name} [${svc.serviceId}] 127.0.0.1:${port}${pinned ? " (pinned)" : ""}`);
|
|
33
|
+
if (verbose) lines.push(...formatDetail(svc));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
function formatDetail(svc) {
|
|
40
|
+
const lines = [` upstream: ${svc.detail?.upstream ?? "(not disclosed)"}`];
|
|
41
|
+
if (svc.detail !== void 0) {
|
|
42
|
+
const d = svc.detail;
|
|
43
|
+
lines.push(` match : ${d.match.map((m) => `${m.type}:${m.value}`).join(" | ") || "(none)"}`);
|
|
44
|
+
if (d.rewrite !== void 0) {
|
|
45
|
+
const parts = [];
|
|
46
|
+
if (d.rewrite.host !== void 0) parts.push(`host=${d.rewrite.host}`);
|
|
47
|
+
if (d.rewrite.prefix !== void 0) parts.push(`prefix=${d.rewrite.prefix}`);
|
|
48
|
+
if (d.rewrite.headerSet !== void 0 && d.rewrite.headerSet.length > 0) parts.push(`headers=${d.rewrite.headerSet.map((h) => `${h.name}:${h.value}`).join(",")}`);
|
|
49
|
+
lines.push(` rewrite : ${parts.length > 0 ? parts.join(" ") : "(identity)"}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return lines;
|
|
53
|
+
}
|
|
54
|
+
async function run(argv, ctx = {}) {
|
|
55
|
+
const { options, positionals } = parseArgv(argv, {
|
|
56
|
+
data: {
|
|
57
|
+
type: "string",
|
|
58
|
+
tilde: true
|
|
59
|
+
},
|
|
60
|
+
verbose: { type: "boolean" },
|
|
61
|
+
relay: { type: "multi" }
|
|
62
|
+
}, { homedir: ctx.homedir ?? homedir() });
|
|
63
|
+
const verbose = options.verbose === true || positionals.includes("-v");
|
|
64
|
+
const out = ctxOut(ctx);
|
|
65
|
+
const root = consumersRoot(options.data, ctxHomedir(ctx));
|
|
66
|
+
const { rings, warnings } = listKeyrings(root);
|
|
67
|
+
for (const w of warnings) out(`warning: ${w}`);
|
|
68
|
+
const engineRings = ringsForRun(rings);
|
|
69
|
+
if (engineRings.length === 0) {
|
|
70
|
+
for (const line of formatStatus([], verbose)) out(line);
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
const factory = await createSdkFabricFactory(options.relay, ctx);
|
|
74
|
+
const engine = await startEngine({
|
|
75
|
+
rings: engineRings,
|
|
76
|
+
consumersRoot: root,
|
|
77
|
+
sessionFactoryFor: (ring) => createFabricProviderTransport(factory, {
|
|
78
|
+
dataDir: `${root}/${ring.endpointId.slice(0, 8)}/fabric`,
|
|
79
|
+
providerEndpointId: ring.endpointId,
|
|
80
|
+
...ring.relayUrls.length > 0 ? { relayUrls: ring.relayUrls } : {}
|
|
81
|
+
}),
|
|
82
|
+
onNotice: () => void 0
|
|
83
|
+
});
|
|
84
|
+
const settleMs = 2500;
|
|
85
|
+
const terminal = /* @__PURE__ */ new Set([
|
|
86
|
+
"direct",
|
|
87
|
+
"relay",
|
|
88
|
+
"offline",
|
|
89
|
+
"key-all-invalid"
|
|
90
|
+
]);
|
|
91
|
+
const deadline = Date.now() + settleMs;
|
|
92
|
+
for (;;) {
|
|
93
|
+
if (engine.manager.snapshot().every((s) => terminal.has(s.state)) || Date.now() >= deadline) break;
|
|
94
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
95
|
+
}
|
|
96
|
+
const lines = formatStatus(engine.manager.snapshot(), verbose);
|
|
97
|
+
await engine.stop();
|
|
98
|
+
for (const line of lines) out(line);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { STATE_LABEL, formatStatus, run };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { r as reportCliError } from "./errors-BGtxPkM7.mjs";
|
|
2
|
+
import { o as parseArgv } from "./config-CO3WyW2H.mjs";
|
|
3
|
+
import { d as str, l as resolveDataDir, r as openStore, u as resolvedRelayUrls } from "./common-BULbPtYn.mjs";
|
|
4
|
+
import { a as detailDisplayLines, r as buildServiceDetail, t as openExistingFabric } from "./serve-DuyQMeJh.mjs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
//#region src/cli/commands/provider/status.ts
|
|
7
|
+
const SPEC = {
|
|
8
|
+
data: {
|
|
9
|
+
type: "string",
|
|
10
|
+
tilde: true
|
|
11
|
+
},
|
|
12
|
+
relay: { type: "multi" },
|
|
13
|
+
verbose: { type: "boolean" }
|
|
14
|
+
};
|
|
15
|
+
async function run(argv, ctx = {}) {
|
|
16
|
+
let fabric;
|
|
17
|
+
try {
|
|
18
|
+
const home = ctx.homedir ?? homedir();
|
|
19
|
+
const { options } = parseArgv(argv, SPEC, { homedir: home });
|
|
20
|
+
const verbose = options.verbose === true;
|
|
21
|
+
const dataDir = resolveDataDir(str(options.data), home);
|
|
22
|
+
const store = openStore(dataDir);
|
|
23
|
+
const services = store.listServices();
|
|
24
|
+
const groups = store.listGroups();
|
|
25
|
+
const keys = store.listKeys();
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(`ai-fly provider status (${dataDir})`);
|
|
28
|
+
lines.push(` alias : ${store.alias ?? "(unset)"}`);
|
|
29
|
+
lines.push(` services: ${services.length}`);
|
|
30
|
+
lines.push(` groups : ${groups.length}`);
|
|
31
|
+
lines.push(` keys : ${keys.filter((k) => k.revokedAt === void 0).length} active, ${keys.filter((k) => k.revokedAt !== void 0).length} revoked`);
|
|
32
|
+
if (verbose) for (const s of services) {
|
|
33
|
+
lines.push(` ${s.name} [${s.serviceId}] defaultPort=${s.defaultPort}`);
|
|
34
|
+
for (const line of detailDisplayLines(buildServiceDetail(s))) lines.push(` ${line}`);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
fabric = await openExistingFabric(dataDir, resolvedRelayUrls(options, home));
|
|
38
|
+
const fabricIdHex = await fabric.fabricIdHex();
|
|
39
|
+
const members = await fabric.members();
|
|
40
|
+
const relay = await fabric.relayStatus();
|
|
41
|
+
lines.push(` endpoint: ${fabric.endpointId}`);
|
|
42
|
+
lines.push(` fabric : ${fabricIdHex}`);
|
|
43
|
+
lines.push(` members: ${members.length}`);
|
|
44
|
+
lines.push(` relay : ${relay.mode}${relay.urls.length > 0 ? ` ${relay.urls.join(", ")}` : " (none)"}${relay.online === true ? " [online]" : relay.online === false ? " [offline]" : ""}`);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
lines.push(` fabric : unavailable (${err.message})`);
|
|
47
|
+
}
|
|
48
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
49
|
+
return 0;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return reportCliError(err);
|
|
52
|
+
} finally {
|
|
53
|
+
await fabric?.shutdown().catch(() => void 0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { run };
|
package/package.json
CHANGED
|
@@ -1,9 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-fly",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ai-fly — peer-to-peer HTTP/WebSocket bridge with AI-ready presets, over OpenDWeb fabric",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ai-fly": "./dist/ai-fly.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20",
|
|
12
|
+
"pnpm": ">=12"
|
|
13
|
+
},
|
|
14
|
+
"packageManager": "pnpm@12.3.4",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "tsx src/bin.ts",
|
|
17
|
+
"build": "tsdown",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"test:integration": "node --import tsx --test --test-concurrency=1 \"test/integration/*.test.mjs\"",
|
|
22
|
+
"test:e2e": "node --import tsx --test \"test/e2e/*.test.mjs\"",
|
|
23
|
+
"test:all": "vitest run && pnpm test:integration && pnpm test:e2e",
|
|
24
|
+
"webui:dev": "pnpm --filter @ai-fly/webui dev",
|
|
25
|
+
"webui:build": "pnpm --filter @ai-fly/webui build",
|
|
26
|
+
"app:dev": "AIFLY_APP_DEV=1 tsx src/app/main.ts",
|
|
27
|
+
"app:icons": "tsx src/app/generate-icons.ts",
|
|
28
|
+
"app:build": "pnpm webui:build && pnpm app:icons && pnpm build && pnpm app:verify",
|
|
29
|
+
"app:verify": "node --input-type=module --eval \"const fs=await import('node:fs');const {resolveOpenTrayPackage}=await import('@opentray/packaging');const dirs=fs.readdirSync('dist',{withFileTypes:true}).filter(d=>d.isDirectory()).map(d=>'dist/'+d.name+'/opentray-app-manifest.json').filter(p=>fs.existsSync(p));if(dirs.length===0)throw new Error('opentray-app-manifest.json not found under dist/');for(const m of dirs){const r=await resolveOpenTrayPackage(m,{artifactRoot:'dist'});if(!fs.existsSync(r.runtimeHostPath))throw new Error('staged runtime host missing: '+r.runtimeHostPath);console.log('[app:verify]',m,'->',r.runtimeHostPath)}\"",
|
|
30
|
+
"app:start": "node dist/app/main.js"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@hono/node-server": "^2.1.1",
|
|
34
|
+
"@jixo/opendweb-client-sdk": "^0.3.2",
|
|
35
|
+
"@opentray/ext-webview": "0.23.0",
|
|
36
|
+
"@orpc/contract": "1.14.6",
|
|
37
|
+
"@orpc/server": "1.14.6",
|
|
38
|
+
"hono": "^4.13.7",
|
|
39
|
+
"opentray": "0.23.0",
|
|
40
|
+
"ts-pattern": "^5.9.0",
|
|
41
|
+
"ws": "^8.21.3",
|
|
42
|
+
"zod": "^4.5.4"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@jixo/opendweb-server-binary": "^0.3.2",
|
|
46
|
+
"@opentray/packaging": "0.23.0",
|
|
47
|
+
"@opentray/tsdown-plugin": "0.23.0",
|
|
48
|
+
"@opentray/vite-plugin": "0.23.0",
|
|
49
|
+
"@orpc/client": "1.14.6",
|
|
50
|
+
"@types/node": "^26.5.0",
|
|
51
|
+
"@types/ws": "^8.18.1",
|
|
52
|
+
"sharp": "^0.33.5",
|
|
53
|
+
"tsdown": "^0.23.0",
|
|
54
|
+
"tsx": "^4.23.13",
|
|
55
|
+
"typescript": "^7.0.2",
|
|
56
|
+
"vitest": "^5.0.0"
|
|
57
|
+
},
|
|
6
58
|
"files": [
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
59
|
+
"dist/*.js",
|
|
60
|
+
"dist/*.mjs",
|
|
61
|
+
"dist/*.d.ts"
|
|
62
|
+
],
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "git+https://github.com/Gaubee/ai-fly.git"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = {};
|