ai-fly 0.4.0-alpha.9 → 0.4.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.
Files changed (44) hide show
  1. package/dist/ai-fly.js +6 -6
  2. package/dist/app/main.js +60 -17
  3. package/dist/{common-B6U1vLFw.mjs → common-C51SAE5f.mjs} +2 -2
  4. package/dist/{common-CwlJNk3L.mjs → common-CanLn0Qt.mjs} +1 -1
  5. package/dist/{daemon-iqO29D7K.mjs → daemon-BWgyJMyB.mjs} +3 -3
  6. package/dist/{forget-BIorc8Rx.mjs → forget-B3KRkHho.mjs} +2 -2
  7. package/dist/{group-C13I2bCR.mjs → group-BdnvRD2t.mjs} +1 -1
  8. package/dist/{hooks-BeBzkBL-.mjs → hooks-DUrXA7PL.mjs} +2 -2
  9. package/dist/{import-BhasJTGS.mjs → import-CTkKGPsa.mjs} +4 -4
  10. package/dist/{join-DiVTFi59.mjs → join-BRow9_3-.mjs} +2 -2
  11. package/dist/{join-BHhCLh9B.mjs → join-d-pqyf8e.mjs} +3 -3
  12. package/dist/{key-DYeTZ7MI.mjs → key-CTL_AXOh.mjs} +24 -8
  13. package/dist/{key-DdZsNmo_.mjs → key-D_66hrWW.mjs} +3 -3
  14. package/dist/{local-test-B1ys1pNo.mjs → local-test-BZHGo22F.mjs} +1 -1
  15. package/dist/{models-dev-DPT_BK_V.mjs → models-dev-CDEyd2k3.mjs} +1 -1
  16. package/dist/{mux-DlwEK4Tt.mjs → mux-BaIDugDq.mjs} +1 -1
  17. package/dist/{ports-DDM5bbJg.mjs → ports-TQ4tWmiJ.mjs} +2 -2
  18. package/dist/{presets-Do5DWXfQ.mjs → presets-DleOUQiT.mjs} +2 -2
  19. package/dist/{revoke-6lrZkjN4.mjs → revoke-DTFW_EF1.mjs} +2 -2
  20. package/dist/{rewrite-vGrkDZx8.mjs → rewrite-CEzfYH9n.mjs} +2 -2
  21. package/dist/{route-test-DfGzFjKw.mjs → route-test-D2LQyX7c.mjs} +3 -3
  22. package/dist/{rpc-contract-BXviZUtF.mjs → rpc-contract-cB5SuyQ_.mjs} +16 -6
  23. package/dist/{run-BLxeLngt.mjs → run-D8kgXdId.mjs} +3 -3
  24. package/dist/{runtime-D8P29hIl.mjs → runtime-C_pLA5F0.mjs} +2 -2
  25. package/dist/{secret-Ds6pIoLP.mjs → secret-P2RgHy2h.mjs} +2 -2
  26. package/dist/{secrets-bHJBznMX.mjs → secrets-1Hmddmwy.mjs} +2 -2
  27. package/dist/secrets-C9IYp93v.mjs +2 -0
  28. package/dist/{serve-B4f79Eqq.mjs → serve-B51lmr9M.mjs} +2 -2
  29. package/dist/{serve-C8VRRJrt.mjs → serve-CY7ZFOFM.mjs} +4 -4
  30. package/dist/{service-CYkpuJuc.mjs → service-D6TNolyh.mjs} +4 -4
  31. package/dist/{settings-CSran7QD.mjs → settings-Rq1zLoxY.mjs} +1 -1
  32. package/dist/{settings-CY-bC0MJ.mjs → settings-XCvAzDK8.mjs} +2 -2
  33. package/dist/{share-B94HhX_b.mjs → share-DdtbAylA.mjs} +27 -12
  34. package/dist/{status-BFpr-LHB.mjs → status-DSVQ3YHm.mjs} +3 -3
  35. package/dist/{status-C_EI8-b9.mjs → status-uE2-kOyK.mjs} +3 -3
  36. package/dist/{store-DCLn0Df-.mjs → store-DOFDy1rY.mjs} +1 -1
  37. package/dist/{store-DxU0bd_u.mjs → store-EGzVd-dn.mjs} +21 -3
  38. package/dist/{test-BERWIlFb.mjs → test-DW4BF7xJ.mjs} +2 -2
  39. package/package.json +1 -1
  40. package/webui/dist/assets/index-B5lnC-QV.js +71 -0
  41. package/webui/dist/assets/{index-CufH1z8j.css → index-DqLIOzIY.css} +1 -1
  42. package/webui/dist/index.html +2 -2
  43. package/dist/secrets-G2HTT66T.mjs +0 -2
  44. package/webui/dist/assets/index-BziQsPBL.js +0 -77
package/dist/ai-fly.js CHANGED
@@ -63,7 +63,7 @@ async function dispatch(command, rest) {
63
63
  const lazy = (load) => {
64
64
  return (argv, ctx) => load().then((m) => m.run([...argv], ctx));
65
65
  };
66
- return match(command).with("serve", () => lazy(() => import("./serve-B4f79Eqq.mjs"))(rest)).with("app", async () => {
66
+ return match(command).with("serve", () => lazy(() => import("./serve-B51lmr9M.mjs"))(rest)).with("app", async () => {
67
67
  const { join, dirname } = await import("node:path");
68
68
  const { fileURLToPath } = await import("node:url");
69
69
  const { spawn } = await import("node:child_process");
@@ -78,17 +78,17 @@ async function dispatch(command, rest) {
78
78
  return await new Promise((resolve) => {
79
79
  child.once("exit", (c) => resolve(c ?? 0));
80
80
  });
81
- }).with("daemon", () => lazy(() => import("./daemon-iqO29D7K.mjs"))(rest)).with("presets", () => lazy(() => import("./presets-Do5DWXfQ.mjs"))(rest)).with("hooks", () => lazy(() => import("./hooks-BeBzkBL-.mjs"))(rest)).with("secret", () => lazy(() => import("./secret-Ds6pIoLP.mjs"))(rest)).with("settings", async () => (await import("./settings-CSran7QD.mjs")).runAsSettings(rest, {})).with("relay", async () => (await import("./settings-CSran7QD.mjs")).runAsRelay(rest, {})).with("test", () => lazy(() => import("./test-BERWIlFb.mjs"))(rest)).with("service", () => lazy(() => import("./service-CYkpuJuc.mjs"))(rest)).with("group", () => lazy(() => import("./group-C13I2bCR.mjs"))(rest)).with("share", () => lazy(() => import("./share-B94HhX_b.mjs"))(rest)).with("revoke", () => lazy(() => import("./revoke-6lrZkjN4.mjs"))(rest)).with("join", () => lazy(() => import("./join-BHhCLh9B.mjs"))(rest)).with("import", () => lazy(() => import("./import-BhasJTGS.mjs"))(rest)).with("run", () => lazy(() => import("./run-BLxeLngt.mjs"))(rest)).with("ports", () => lazy(() => import("./ports-DDM5bbJg.mjs"))(rest)).with("forget", () => lazy(() => import("./forget-BIorc8Rx.mjs"))(rest)).with("key", () => {
82
- if (rest[0] === "add") return lazy(() => import("./key-DdZsNmo_.mjs"))(rest);
83
- return lazy(() => import("./key-DYeTZ7MI.mjs"))(rest);
81
+ }).with("daemon", () => lazy(() => import("./daemon-BWgyJMyB.mjs"))(rest)).with("presets", () => lazy(() => import("./presets-DleOUQiT.mjs"))(rest)).with("hooks", () => lazy(() => import("./hooks-DUrXA7PL.mjs"))(rest)).with("secret", () => lazy(() => import("./secret-P2RgHy2h.mjs"))(rest)).with("settings", async () => (await import("./settings-Rq1zLoxY.mjs")).runAsSettings(rest, {})).with("relay", async () => (await import("./settings-Rq1zLoxY.mjs")).runAsRelay(rest, {})).with("test", () => lazy(() => import("./test-DW4BF7xJ.mjs"))(rest)).with("service", () => lazy(() => import("./service-D6TNolyh.mjs"))(rest)).with("group", () => lazy(() => import("./group-BdnvRD2t.mjs"))(rest)).with("share", () => lazy(() => import("./share-DdtbAylA.mjs"))(rest)).with("revoke", () => lazy(() => import("./revoke-DTFW_EF1.mjs"))(rest)).with("join", () => lazy(() => import("./join-d-pqyf8e.mjs"))(rest)).with("import", () => lazy(() => import("./import-CTkKGPsa.mjs"))(rest)).with("run", () => lazy(() => import("./run-D8kgXdId.mjs"))(rest)).with("ports", () => lazy(() => import("./ports-TQ4tWmiJ.mjs"))(rest)).with("forget", () => lazy(() => import("./forget-B3KRkHho.mjs"))(rest)).with("key", () => {
82
+ if (rest[0] === "add") return lazy(() => import("./key-D_66hrWW.mjs"))(rest);
83
+ return lazy(() => import("./key-CTL_AXOh.mjs"))(rest);
84
84
  }).with("status", async () => {
85
85
  const [scope, ...tail] = rest;
86
86
  const wantProvider = scope === "provider" || scope !== "consumer" && providerDirExists();
87
87
  const wantConsumer = scope === "consumer" || scope !== "provider" && consumersRootExists();
88
88
  const argv = scope === "provider" || scope === "consumer" ? tail : rest;
89
89
  let code = 0;
90
- if (wantProvider) code = Math.max(code, await lazy(() => import("./status-BFpr-LHB.mjs"))(argv));
91
- if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-C_EI8-b9.mjs"))(argv));
90
+ if (wantProvider) code = Math.max(code, await lazy(() => import("./status-DSVQ3YHm.mjs"))(argv));
91
+ if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-uE2-kOyK.mjs"))(argv));
92
92
  if (!wantProvider && !wantConsumer) process.stdout.write("nothing to report: no provider storage and no imported consumers\n");
93
93
  return code;
94
94
  }).otherwise(() => {
package/dist/app/main.js CHANGED
@@ -134,12 +134,15 @@ const GROUP_SCHEMA = z.strictObject({
134
134
  serviceIds: z.array(z.string().min(1).max(128)).max(256),
135
135
  limits: GROUP_LIMITS_SCHEMA.optional()
136
136
  });
137
- /** 密钥视图:keyId/分组/时间/状态;哈希与原文一律不进契约(原文仅 issue 时一次性返回)。 */
137
+ /** 密钥视图:keyId/分组/时间/状态 + 名 + 原文(Owner 2026-09-13:随时可
138
+ * 复制——原文随库返回;旧记录只存哈希 → 两字段皆缺省)。 */
138
139
  const KEY_VIEW_SCHEMA = z.strictObject({
139
140
  keyId: z.string().min(1).max(128),
140
141
  group: z.string().min(1).max(256),
141
142
  createdAt: z.number().int().min(0),
142
- revokedAt: z.number().int().min(0).optional()
143
+ revokedAt: z.number().int().min(0).optional(),
144
+ name: z.string().min(1).max(128).optional(),
145
+ key: z.string().min(8).max(256).optional()
143
146
  });
144
147
  /** 使用方服务目录条目(AUTH_OK / import 视图;detail 为脱敏披露,$env 值显示 ●)。 */
145
148
  const SERVICE_ENTRY_SCHEMA$1 = z.object({
@@ -301,7 +304,11 @@ const SETTINGS_SCHEMA = z.strictObject({
301
304
  const SHARE_CREATE_INPUT_SCHEMA = z.strictObject({
302
305
  group: z.string().min(1).max(256),
303
306
  /** TTL 毫秒(1s..30d;缺省 60min——范围校验复用 link.ts 常量语义)。 */
304
- ttlMs: z.number().int().min(1e3).max(2592e6).optional()
307
+ ttlMs: z.number().int().min(1e3).max(2592e6).optional(),
308
+ /** 复用既有 key(现铸 invite 重发链接;缺省签发新钥)。 */
309
+ keyId: z.string().min(1).max(128).optional(),
310
+ /** 新钥名(缺省签发路径下默认 "default")。 */
311
+ keyName: z.string().min(1).max(128).optional()
305
312
  });
306
313
  const PRESET_APPLY_INPUT_SCHEMA = z.strictObject({
307
314
  presetId: z.string().min(1).max(128),
@@ -440,13 +447,16 @@ const rpcContract = oc.errors(RpcErrorDefinitions).router({
440
447
  remove: oc.input(z.strictObject({ name: z.string().min(1).max(256) })).output(z.object({ removed: z.literal(true) }))
441
448
  },
442
449
  keys: {
443
- /** 签发(原文仅本次返回;此后只余哈希)。 */
444
- issue: oc.input(z.strictObject({ group: z.string().min(1).max(256) })).output(z.object({
450
+ /** 签发(name 缺省 "default";原文随库可再取)。 */
451
+ issue: oc.input(z.strictObject({
452
+ group: z.string().min(1).max(256),
453
+ name: z.string().min(1).max(128).optional()
454
+ })).output(z.object({
445
455
  keyId: z.string(),
446
456
  key: z.string(),
447
457
  createdAt: z.number().int()
448
458
  })),
449
- /** 密钥清单(无哈希、无原文)。 */
459
+ /** 密钥清单(含名与原文——本机 GUI 随时复制;无哈希)。 */
450
460
  list: oc.input(z.object({})).output(z.object({ keys: z.array(KEY_VIEW_SCHEMA) })),
451
461
  /** 撤销(幂等)。 */
452
462
  revoke: oc.input(z.strictObject({ keyId: z.string().min(1).max(128) })).output(z.object({ key: KEY_VIEW_SCHEMA }))
@@ -926,9 +936,17 @@ const KEY_STORE_SCHEMA = z.strictObject({
926
936
  keyId: z.string().min(1).max(128),
927
937
  group: z.string().min(1).max(256),
928
938
  hash: z.string().regex(/^[0-9a-f]{64}$/, "key hash must be 64 hex chars"),
939
+ /** key 名(Owner 裁决 2026-09-13:签发时必填,GUI 分组视图按名展示;
940
+ * 旧记录无此字段——迁移容忍,GUI 显示为未命名)。 */
941
+ name: z.string().min(1).max(128).optional(),
942
+ /** key 原文(Owner 裁决 2026-09-13:可随时复制取代仅签发时可见——本机
943
+ * 个人工具与密钥库明文同一威胁模型;旧记录只存哈希,无法补录)。 */
944
+ key: z.string().min(8).max(256).optional(),
929
945
  createdAt: z.number().int().min(0),
930
946
  revokedAt: z.number().int().min(0).optional()
931
947
  });
948
+ /** key 名规则(同 SECRET_NAME_SCHEMA 词汇:小写开头,点横杠下划线)。 */
949
+ const KEY_NAME_SCHEMA = /^[a-z0-9][a-z0-9._-]*$/;
932
950
  const STORE_FILE_SCHEMA = z.strictObject({
933
951
  revision: z.number().int().min(0),
934
952
  meta: z.strictObject({ alias: z.string().min(1).max(256).optional() }).optional(),
@@ -1164,9 +1182,11 @@ var ProviderStore = class ProviderStore {
1164
1182
  listKeys() {
1165
1183
  return this.data.keys.map((k) => ({ ...k }));
1166
1184
  }
1167
- /** 签发:原文仅本次返回,存储只落哈希。 */
1168
- issueKey(groupName) {
1185
+ /** 签发:name 缺省 "default";原文随记录落库(Owner 2026-09-13:随时
1186
+ * 可复制)。name 是展示标签非身份(keyId 唯一),同组可重名。 */
1187
+ issueKey(groupName, name = "default") {
1169
1188
  if (this.getGroup(groupName) === void 0) throw new StoreError("not-found", `error: group '${groupName}' not found`);
1189
+ if (!KEY_NAME_SCHEMA.test(name)) throw new StoreError("invalid", "error: key name must match /^[a-z0-9][a-z0-9._-]*$/");
1170
1190
  let keyId;
1171
1191
  do
1172
1192
  keyId = randomZ32(8);
@@ -1177,6 +1197,8 @@ var ProviderStore = class ProviderStore {
1177
1197
  keyId,
1178
1198
  group: groupName,
1179
1199
  hash: hashKeyMaterial(key),
1200
+ name,
1201
+ key,
1180
1202
  createdAt
1181
1203
  });
1182
1204
  this.save();
@@ -1186,6 +1208,12 @@ var ProviderStore = class ProviderStore {
1186
1208
  createdAt
1187
1209
  };
1188
1210
  }
1211
+ /** key 原文取回(旧记录只存哈希 → undefined;撤销/不存在 → undefined)。 */
1212
+ getKeyMaterial(keyId) {
1213
+ const found = this.data.keys.find((k) => k.keyId === keyId);
1214
+ if (found === void 0 || found.revokedAt !== void 0) return void 0;
1215
+ return found.key;
1216
+ }
1189
1217
  /** 撤销(幂等:已撤销为 no-op)。 */
1190
1218
  revokeKey(keyId) {
1191
1219
  const found = this.data.keys.find((k) => k.keyId === keyId);
@@ -6290,21 +6318,32 @@ function previewShareLink(text) {
6290
6318
  };
6291
6319
  }
6292
6320
  /**
6293
- * 构造分享链接:前置检查(组存在且非空)-> 签发新钥(每次 share 一枚;旧钥不可再现)
6294
- * -> 组内服务脱敏视图 -> payload + link + 警告。
6321
+ * 构造分享链接:前置检查(组存在且非空)-> key(复用 keyId 指定的已存
6322
+ * 原文;缺省签发新钥)-> 组内服务脱敏视图 -> payload + link + 警告。
6295
6323
  */
6296
6324
  function buildShareLink(input) {
6297
6325
  if (input.store.getGroup(input.group) === void 0) throw new StoreError("not-found", `error: group '${input.group}' not found`);
6298
6326
  const services = input.store.groupServices(input.group);
6299
6327
  if (services.length === 0) throw new StoreError("invalid", `error: group '${input.group}' has no services; a share link would be meaningless`);
6300
- const issued = input.store.issueKey(input.group);
6328
+ let key;
6329
+ let keyId;
6330
+ if (input.keyId !== void 0) {
6331
+ const material = input.store.getKeyMaterial(input.keyId);
6332
+ if (material === void 0) throw new StoreError("invalid", `error: key '${input.keyId}' cannot be shared (missing, revoked, or raw material not stored)`);
6333
+ key = material;
6334
+ keyId = input.keyId;
6335
+ } else {
6336
+ const issued = input.store.issueKey(input.group, input.keyName);
6337
+ key = issued.key;
6338
+ keyId = issued.keyId;
6339
+ }
6301
6340
  const serviceEntries = services.map(buildServiceEntry);
6302
6341
  const relayUrls = input.relayUrls === void 0 ? [] : [...input.relayUrls];
6303
6342
  const payload = {
6304
6343
  v: 1,
6305
6344
  invite: input.invite,
6306
- key: issued.key,
6307
- keyId: issued.keyId,
6345
+ key,
6346
+ keyId,
6308
6347
  provider: {
6309
6348
  alias: input.alias ?? input.store.alias ?? "provider",
6310
6349
  endpointId: input.endpointId,
@@ -6319,7 +6358,7 @@ function buildShareLink(input) {
6319
6358
  payload,
6320
6359
  link: encodeShareLink(payload),
6321
6360
  warnings,
6322
- keyId: issued.keyId
6361
+ keyId
6323
6362
  };
6324
6363
  }
6325
6364
  //#endregion
@@ -7654,7 +7693,9 @@ function keyView(key) {
7654
7693
  keyId: key.keyId,
7655
7694
  group: key.group,
7656
7695
  createdAt: key.createdAt,
7657
- ...key.revokedAt !== void 0 ? { revokedAt: key.revokedAt } : {}
7696
+ ...key.revokedAt !== void 0 ? { revokedAt: key.revokedAt } : {},
7697
+ ...key.name !== void 0 ? { name: key.name } : {},
7698
+ ...key.key !== void 0 ? { key: key.key } : {}
7658
7699
  };
7659
7700
  }
7660
7701
  /** 预设 → 服务输入展开(applyAsService 核心:match 从 matchDomains 派生 suffix 规则;
@@ -7813,7 +7854,7 @@ function createRpcRouter(deps) {
7813
7854
  })
7814
7855
  },
7815
7856
  keys: {
7816
- issue: rpc.provider.keys.issue.handler(({ input }) => host.providerStore().issueKey(input.group)),
7857
+ issue: rpc.provider.keys.issue.handler(({ input }) => host.providerStore().issueKey(input.group, input.name)),
7817
7858
  list: rpc.provider.keys.list.handler(() => ({ keys: host.providerStore().listKeys().map(keyView) })),
7818
7859
  revoke: rpc.provider.keys.revoke.handler(({ input }) => ({ key: keyView(host.providerStore().revokeKey(input.keyId)) }))
7819
7860
  },
@@ -7873,7 +7914,9 @@ function createRpcRouter(deps) {
7873
7914
  group: input.group,
7874
7915
  invite,
7875
7916
  endpointId: daemon.endpointId,
7876
- relayUrls: relayStatus.urls
7917
+ relayUrls: relayStatus.urls,
7918
+ keyId: input.keyId,
7919
+ keyName: input.keyName
7877
7920
  });
7878
7921
  return {
7879
7922
  link: result.link,
@@ -1,7 +1,7 @@
1
1
  import { n as UsageError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as resolveRelayUrls, t as loadConfig } from "./config-BlZnFqx7.mjs";
3
- import { t as ProviderStore } from "./store-DxU0bd_u.mjs";
4
- import { t as loadSettings } from "./settings-CY-bC0MJ.mjs";
3
+ import { t as ProviderStore } from "./store-EGzVd-dn.mjs";
4
+ import { t as loadSettings } from "./settings-XCvAzDK8.mjs";
5
5
  import { homedir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  //#region src/cli/commands/provider/common.ts
@@ -1,6 +1,6 @@
1
1
  import { n as resolveRelayUrls, t as loadConfig } from "./config-BlZnFqx7.mjs";
2
2
  import { n as loadSdk, t as resolveHttpProxy } from "./proxy-D7VMUVEg.mjs";
3
- import { t as loadSettings } from "./settings-CY-bC0MJ.mjs";
3
+ import { t as loadSettings } from "./settings-XCvAzDK8.mjs";
4
4
  import { homedir } from "node:os";
5
5
  //#region src/cli/commands/consumer/common.ts
6
6
  function ctxOut(ctx) {
@@ -1,8 +1,8 @@
1
1
  import { n as UsageError, r as reportCliError, t as CliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { r as settingsPath, t as loadSettings } from "./settings-CY-bC0MJ.mjs";
3
+ import { r as settingsPath, t as loadSettings } from "./settings-XCvAzDK8.mjs";
4
4
  import { a as killHard, c as terminateGracefully, i as isAlive, l as writeLastStart, n as daemonDir, o as readLastStart, r as ensureDaemonDir, s as readPid, t as clearPid, u as writePid } from "./daemon-state-CYkiW3SA.mjs";
5
- import { r as openStore } from "./common-B6U1vLFw.mjs";
5
+ import { r as openStore } from "./common-C51SAE5f.mjs";
6
6
  import { openSync, readFileSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import { isAbsolute, resolve } from "node:path";
@@ -99,7 +99,7 @@ async function start(startArgv, home) {
99
99
  process.on("exit", () => {
100
100
  clearPid(home);
101
101
  });
102
- const { run: serveRun } = await import("./serve-B4f79Eqq.mjs");
102
+ const { run: serveRun } = await import("./serve-B51lmr9M.mjs");
103
103
  return serveRun([...startArgv], { homedir: home });
104
104
  }
105
105
  /** 后台 pid(活性核验;陈旧 pidfile 就地清理返回 null)。 */
@@ -1,7 +1,7 @@
1
1
  import { n as UsageError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { d as removeKeyring, n as consumersRoot } from "./store-DCLn0Df-.mjs";
4
- import { n as ctxHomedir, r as ctxOut } from "./common-CwlJNk3L.mjs";
3
+ import { d as removeKeyring, n as consumersRoot } from "./store-DOFDy1rY.mjs";
4
+ import { n as ctxHomedir, r as ctxOut } from "./common-CanLn0Qt.mjs";
5
5
  import { homedir } from "node:os";
6
6
  //#region src/cli/commands/consumer/forget.ts
7
7
  async function run(argv, ctx = {}) {
@@ -1,6 +1,6 @@
1
1
  import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { d as str, l as resolveDataDir, n as multi, r as openStore, s as parsePositiveInt } from "./common-B6U1vLFw.mjs";
3
+ import { d as str, l as resolveDataDir, n as multi, r as openStore, s as parsePositiveInt } from "./common-C51SAE5f.mjs";
4
4
  import { homedir } from "node:os";
5
5
  //#region src/cli/commands/provider/group.ts
6
6
  const SPEC = {
@@ -1,8 +1,8 @@
1
1
  import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { d as str, l as resolveDataDir } from "./common-B6U1vLFw.mjs";
3
+ import { d as str, l as resolveDataDir } from "./common-C51SAE5f.mjs";
4
4
  import { a as removeUserHook, i as readHookScript, n as disposeHookSubscriptions, o as resolveHookValue, r as installUserHook, t as discoverHooks } from "./hook-CvAMGts4.mjs";
5
- import { t as SecretsStore } from "./secrets-bHJBznMX.mjs";
5
+ import { t as SecretsStore } from "./secrets-1Hmddmwy.mjs";
6
6
  import { readFileSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  //#region src/cli/commands/provider/hooks.ts
@@ -1,9 +1,9 @@
1
1
  import { n as UsageError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { n as consumersRoot, s as listKeyrings } from "./store-DCLn0Df-.mjs";
4
- import { a as ringsForRun, i as printListeners, n as ctxHomedir, o as waitForSignals, r as ctxOut, t as createSdkFabricFactory } from "./common-CwlJNk3L.mjs";
5
- import { a as importLink, i as formatLinkPreview, n as decodeShareLink, r as formatKeyringSummary } from "./join-DiVTFi59.mjs";
6
- import { n as createFabricProviderTransport, t as startEngine } from "./runtime-D8P29hIl.mjs";
3
+ import { n as consumersRoot, s as listKeyrings } from "./store-DOFDy1rY.mjs";
4
+ import { a as ringsForRun, i as printListeners, n as ctxHomedir, o as waitForSignals, r as ctxOut, t as createSdkFabricFactory } from "./common-CanLn0Qt.mjs";
5
+ import { a as importLink, i as formatLinkPreview, n as decodeShareLink, r as formatKeyringSummary } from "./join-BRow9_3-.mjs";
6
+ import { n as createFabricProviderTransport, t as startEngine } from "./runtime-C_pLA5F0.mjs";
7
7
  import { homedir } from "node:os";
8
8
  //#region src/cli/commands/consumer/import.ts
9
9
  async function run(argv, ctx = {}) {
@@ -1,7 +1,7 @@
1
1
  import { t as CliError } from "./errors-BGtxPkM7.mjs";
2
- import { o as decodeZ32, s as randomZ32 } from "./rpc-contract-BXviZUtF.mjs";
2
+ import { o as decodeZ32, s as randomZ32 } from "./rpc-contract-cB5SuyQ_.mjs";
3
3
  import { l as SERVICE_ENTRY_SCHEMA } from "./frames-BDz8buix.mjs";
4
- import { a as hasFabricIdentity, c as loadKeyring, f as saveKeyring, h as upsertKey, i as findKeyringDir, l as mergeImportView, o as keyringDir, r as fabricDir } from "./store-DCLn0Df-.mjs";
4
+ import { a as hasFabricIdentity, c as loadKeyring, f as saveKeyring, h as upsertKey, i as findKeyringDir, l as mergeImportView, o as keyringDir, r as fabricDir } from "./store-DOFDy1rY.mjs";
5
5
  import { existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { z } from "zod";
@@ -1,8 +1,8 @@
1
1
  import { n as UsageError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { n as consumersRoot } from "./store-DCLn0Df-.mjs";
4
- import { n as ctxHomedir, r as ctxOut, t as createSdkFabricFactory } from "./common-CwlJNk3L.mjs";
5
- import { o as joinDevice } from "./join-DiVTFi59.mjs";
3
+ import { n as consumersRoot } from "./store-DOFDy1rY.mjs";
4
+ import { n as ctxHomedir, r as ctxOut, t as createSdkFabricFactory } from "./common-CanLn0Qt.mjs";
5
+ import { o as joinDevice } from "./join-BRow9_3-.mjs";
6
6
  import { homedir } from "node:os";
7
7
  //#region src/cli/commands/consumer/join.ts
8
8
  async function run(argv, ctx = {}) {
@@ -1,6 +1,6 @@
1
1
  import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { c as requireString, d as str, l as resolveDataDir, r as openStore } from "./common-B6U1vLFw.mjs";
3
+ import { c as requireString, d as str, l as resolveDataDir, r as openStore } from "./common-C51SAE5f.mjs";
4
4
  import { homedir } from "node:os";
5
5
  //#region src/cli/commands/provider/key.ts
6
6
  const SPEC = {
@@ -8,11 +8,13 @@ const SPEC = {
8
8
  type: "string",
9
9
  tilde: true
10
10
  },
11
- group: { type: "string" }
11
+ group: { type: "string" },
12
+ name: { type: "string" }
12
13
  };
13
14
  const USAGE = `usage:
14
- ai-fly key issue --group <name> [--data <dir>]
15
+ ai-fly key issue --group <name> [--name <keyName>] [--data <dir>]
15
16
  ai-fly key list [--data <dir>]
17
+ ai-fly key show <keyId> [--data <dir>]
16
18
  ai-fly key revoke <keyId> [--data <dir>]`;
17
19
  async function run(argv, ctx = {}) {
18
20
  try {
@@ -21,22 +23,24 @@ async function run(argv, ctx = {}) {
21
23
  const sub = positionals[0];
22
24
  if (sub === "issue") return issue(options, home);
23
25
  if (sub === "list") return list(options, home);
26
+ if (sub === "show") return show(options, positionals, home);
24
27
  if (sub === "revoke") return revoke(options, positionals, home);
25
28
  if (sub === void 0) throw new UsageError(USAGE);
26
- throw new UsageError(`error: unknown key subcommand '${sub}' (known: issue, list, revoke)`);
29
+ throw new UsageError(`error: unknown key subcommand '${sub}' (known: issue, list, show, revoke)`);
27
30
  } catch (err) {
28
31
  return reportCliError(err);
29
32
  }
30
33
  }
31
34
  function issue(options, home) {
32
35
  const group = requireString(str(options.group), "group");
33
- const issued = openStore(resolveDataDir(str(options.data), home)).issueKey(group);
36
+ const name = str(options.name) ?? "default";
37
+ const issued = openStore(resolveDataDir(str(options.data), home)).issueKey(group, name);
34
38
  process.stdout.write([
35
- `key issued for group '${group}'`,
39
+ `key '${name}' issued for group '${group}'`,
36
40
  ` keyId: ${issued.keyId}`,
37
41
  ` key : ${issued.key}`,
38
42
  "",
39
- "This secret is shown only once. Store it now (password manager or share link).",
43
+ "The raw key is stored locally - re-show it anytime with: ai-fly key show " + issued.keyId,
40
44
  "Revoke with: ai-fly key revoke " + issued.keyId,
41
45
  ""
42
46
  ].join("\n"));
@@ -51,11 +55,23 @@ function list(options, home) {
51
55
  const lines = [];
52
56
  for (const k of keys) {
53
57
  const status = k.revokedAt === void 0 ? "active" : `revoked at ${new Date(k.revokedAt).toISOString()}`;
54
- lines.push(`${k.keyId} group=${k.group} created=${new Date(k.createdAt).toISOString()} ${status}`);
58
+ const name = k.name ?? "(unnamed)";
59
+ lines.push(`${k.keyId} name=${name} group=${k.group} created=${new Date(k.createdAt).toISOString()} ${status}`);
55
60
  }
56
61
  process.stdout.write(`${lines.join("\n")}\n`);
57
62
  return 0;
58
63
  }
64
+ function show(options, positionals, home) {
65
+ const keyId = positionals[1];
66
+ if (keyId === void 0) throw new UsageError("error: key show requires a <keyId> argument");
67
+ const material = openStore(resolveDataDir(str(options.data), home)).getKeyMaterial(keyId);
68
+ if (material === void 0) {
69
+ process.stdout.write(`key '${keyId}' has no retrievable material (missing, revoked, or issued before raw storage)\n`);
70
+ return 1;
71
+ }
72
+ process.stdout.write(`${material}\n`);
73
+ return 0;
74
+ }
59
75
  function revoke(options, positionals, home) {
60
76
  const keyId = positionals[1];
61
77
  if (keyId === void 0) throw new UsageError("error: key revoke requires a <keyId> argument");
@@ -1,8 +1,8 @@
1
1
  import { n as UsageError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { n as consumersRoot } from "./store-DCLn0Df-.mjs";
4
- import { n as ctxHomedir, r as ctxOut } from "./common-CwlJNk3L.mjs";
5
- import { r as formatKeyringSummary, t as addKey } from "./join-DiVTFi59.mjs";
3
+ import { n as consumersRoot } from "./store-DOFDy1rY.mjs";
4
+ import { n as ctxHomedir, r as ctxOut } from "./common-CanLn0Qt.mjs";
5
+ import { r as formatKeyringSummary, t as addKey } from "./join-BRow9_3-.mjs";
6
6
  import { homedir } from "node:os";
7
7
  //#region src/cli/commands/consumer/key.ts
8
8
  async function run(argv, ctx = {}) {
@@ -1,4 +1,4 @@
1
- import { n as ROUTE_LOCAL_PREFIX } from "./rpc-contract-BXviZUtF.mjs";
1
+ import { n as ROUTE_LOCAL_PREFIX } from "./rpc-contract-cB5SuyQ_.mjs";
2
2
  //#region src/consumer/local-test.ts
3
3
  const DEFAULT_TIMEOUT_MS = 2e4;
4
4
  const BODY_EXCERPT_MAX = 2e3;
@@ -1,4 +1,4 @@
1
- import { t as PRESET_SCHEMA } from "./rpc-contract-BXviZUtF.mjs";
1
+ import { t as PRESET_SCHEMA } from "./rpc-contract-cB5SuyQ_.mjs";
2
2
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
@@ -1,4 +1,4 @@
1
- import { s as randomZ32 } from "./rpc-contract-BXviZUtF.mjs";
1
+ import { s as randomZ32 } from "./rpc-contract-cB5SuyQ_.mjs";
2
2
  import { a as FRAME_TYPE_NAME, d as isKnownFrameType, i as FRAME_TYPE, r as FRAME_HEADER_SCHEMAS, t as ERROR_CODE, u as classifySchemaFailure } from "./frames-BDz8buix.mjs";
3
3
  //#region src/wire/codec.ts
4
4
  const WIRE_MAGIC_PREFIX = "aifly";
@@ -1,7 +1,7 @@
1
1
  import { n as UsageError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { m as setPort, n as consumersRoot, s as listKeyrings } from "./store-DCLn0Df-.mjs";
4
- import { n as ctxHomedir, r as ctxOut } from "./common-CwlJNk3L.mjs";
3
+ import { m as setPort, n as consumersRoot, s as listKeyrings } from "./store-DOFDy1rY.mjs";
4
+ import { n as ctxHomedir, r as ctxOut } from "./common-CanLn0Qt.mjs";
5
5
  import { i as validatePortArg, n as desiredPortFor } from "./ports-pI59MtmI.mjs";
6
6
  import { homedir } from "node:os";
7
7
  //#region src/cli/commands/consumer/ports.ts
@@ -1,7 +1,7 @@
1
1
  import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { t as loadSettings } from "./settings-CY-bC0MJ.mjs";
4
- import { n as loadCuratedPresets, r as modelsDevCachePath, t as fetchModelsDevPresets } from "./models-dev-DPT_BK_V.mjs";
3
+ import { t as loadSettings } from "./settings-XCvAzDK8.mjs";
4
+ import { n as loadCuratedPresets, r as modelsDevCachePath, t as fetchModelsDevPresets } from "./models-dev-CDEyd2k3.mjs";
5
5
  import { homedir } from "node:os";
6
6
  //#region src/cli/commands/provider/presets.ts
7
7
  const SPEC = { json: { type: "boolean" } };
@@ -1,8 +1,8 @@
1
1
  import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
3
  import { t as resolveHttpProxy } from "./proxy-D7VMUVEg.mjs";
4
- import { d as str, l as resolveDataDir, u as resolvedRelayUrls } from "./common-B6U1vLFw.mjs";
5
- import { t as openExistingFabric } from "./serve-C8VRRJrt.mjs";
4
+ import { d as str, l as resolveDataDir, u as resolvedRelayUrls } from "./common-C51SAE5f.mjs";
5
+ import { t as openExistingFabric } from "./serve-CY7ZFOFM.mjs";
6
6
  import { homedir } from "node:os";
7
7
  //#region src/cli/commands/provider/revoke.ts
8
8
  const SPEC = {
@@ -1,6 +1,6 @@
1
- import { a as routeLocalPrefix } from "./rpc-contract-BXviZUtF.mjs";
1
+ import { a as routeLocalPrefix } from "./rpc-contract-cB5SuyQ_.mjs";
2
2
  import { n as FORBIDDEN_REQ_HEADER_NAMES } from "./frames-BDz8buix.mjs";
3
- import { a as parseUpstreamUrl, c as matchRequestPath, o as expandUriTemplate, s as compileMatchPattern } from "./store-DxU0bd_u.mjs";
3
+ import { a as parseUpstreamUrl, c as matchRequestPath, o as expandUriTemplate, s as compileMatchPattern } from "./store-EGzVd-dn.mjs";
4
4
  import { o as resolveHookValue } from "./hook-CvAMGts4.mjs";
5
5
  import "node:module";
6
6
  import "node:fs";
@@ -1,6 +1,6 @@
1
- import { n as ROUTE_LOCAL_PREFIX } from "./rpc-contract-BXviZUtF.mjs";
2
- import { t as formProbe } from "./local-test-B1ys1pNo.mjs";
3
- import { i as buildUpstreamRequest, t as PathNotOfferedError } from "./rewrite-vGrkDZx8.mjs";
1
+ import { n as ROUTE_LOCAL_PREFIX } from "./rpc-contract-cB5SuyQ_.mjs";
2
+ import { t as formProbe } from "./local-test-BZHGo22F.mjs";
3
+ import { i as buildUpstreamRequest, t as PathNotOfferedError } from "./rewrite-CEzfYH9n.mjs";
4
4
  //#region src/provider/route-test.ts
5
5
  const DEFAULT_TIMEOUT_MS = 2e4;
6
6
  const BODY_EXCERPT_MAX = 2e3;
@@ -169,12 +169,15 @@ const GROUP_SCHEMA = z.strictObject({
169
169
  serviceIds: z.array(z.string().min(1).max(128)).max(256),
170
170
  limits: GROUP_LIMITS_SCHEMA.optional()
171
171
  });
172
- /** 密钥视图:keyId/分组/时间/状态;哈希与原文一律不进契约(原文仅 issue 时一次性返回)。 */
172
+ /** 密钥视图:keyId/分组/时间/状态 + 名 + 原文(Owner 2026-09-13:随时可
173
+ * 复制——原文随库返回;旧记录只存哈希 → 两字段皆缺省)。 */
173
174
  const KEY_VIEW_SCHEMA = z.strictObject({
174
175
  keyId: z.string().min(1).max(128),
175
176
  group: z.string().min(1).max(256),
176
177
  createdAt: z.number().int().min(0),
177
- revokedAt: z.number().int().min(0).optional()
178
+ revokedAt: z.number().int().min(0).optional(),
179
+ name: z.string().min(1).max(128).optional(),
180
+ key: z.string().min(8).max(256).optional()
178
181
  });
179
182
  /** 使用方服务目录条目(AUTH_OK / import 视图;detail 为脱敏披露,$env 值显示 ●)。 */
180
183
  const SERVICE_ENTRY_SCHEMA = z.object({
@@ -336,7 +339,11 @@ const SETTINGS_SCHEMA = z.strictObject({
336
339
  const SHARE_CREATE_INPUT_SCHEMA = z.strictObject({
337
340
  group: z.string().min(1).max(256),
338
341
  /** TTL 毫秒(1s..30d;缺省 60min——范围校验复用 link.ts 常量语义)。 */
339
- ttlMs: z.number().int().min(1e3).max(2592e6).optional()
342
+ ttlMs: z.number().int().min(1e3).max(2592e6).optional(),
343
+ /** 复用既有 key(现铸 invite 重发链接;缺省签发新钥)。 */
344
+ keyId: z.string().min(1).max(128).optional(),
345
+ /** 新钥名(缺省签发路径下默认 "default")。 */
346
+ keyName: z.string().min(1).max(128).optional()
340
347
  });
341
348
  const PRESET_APPLY_INPUT_SCHEMA = z.strictObject({
342
349
  presetId: z.string().min(1).max(128),
@@ -474,13 +481,16 @@ oc.errors(RpcErrorDefinitions).router({
474
481
  remove: oc.input(z.strictObject({ name: z.string().min(1).max(256) })).output(z.object({ removed: z.literal(true) }))
475
482
  },
476
483
  keys: {
477
- /** 签发(原文仅本次返回;此后只余哈希)。 */
478
- issue: oc.input(z.strictObject({ group: z.string().min(1).max(256) })).output(z.object({
484
+ /** 签发(name 缺省 "default";原文随库可再取)。 */
485
+ issue: oc.input(z.strictObject({
486
+ group: z.string().min(1).max(256),
487
+ name: z.string().min(1).max(128).optional()
488
+ })).output(z.object({
479
489
  keyId: z.string(),
480
490
  key: z.string(),
481
491
  createdAt: z.number().int()
482
492
  })),
483
- /** 密钥清单(无哈希、无原文)。 */
493
+ /** 密钥清单(含名与原文——本机 GUI 随时复制;无哈希)。 */
484
494
  list: oc.input(z.object({})).output(z.object({ keys: z.array(KEY_VIEW_SCHEMA) })),
485
495
  /** 撤销(幂等)。 */
486
496
  revoke: oc.input(z.strictObject({ keyId: z.string().min(1).max(128) })).output(z.object({ key: KEY_VIEW_SCHEMA }))
@@ -1,8 +1,8 @@
1
1
  import { n as UsageError, r as reportCliError, t as CliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { n as consumersRoot, s as listKeyrings } from "./store-DCLn0Df-.mjs";
4
- import { a as ringsForRun, i as printListeners, n as ctxHomedir, o as waitForSignals, r as ctxOut, t as createSdkFabricFactory } from "./common-CwlJNk3L.mjs";
5
- import { n as createFabricProviderTransport, t as startEngine } from "./runtime-D8P29hIl.mjs";
3
+ import { n as consumersRoot, s as listKeyrings } from "./store-DOFDy1rY.mjs";
4
+ import { a as ringsForRun, i as printListeners, n as ctxHomedir, o as waitForSignals, r as ctxOut, t as createSdkFabricFactory } from "./common-CanLn0Qt.mjs";
5
+ import { n as createFabricProviderTransport, t as startEngine } from "./runtime-C_pLA5F0.mjs";
6
6
  import { a as killHard, c as terminateGracefully, i as isAlive, l as writeLastStart, n as daemonDir, o as readLastStart, r as ensureDaemonDir, s as readPid, t as clearPid, u as writePid } from "./daemon-state-CYkiW3SA.mjs";
7
7
  import { openSync, readFileSync } from "node:fs";
8
8
  import { resolve } from "node:path";
@@ -1,6 +1,6 @@
1
1
  import { i as FRAME_TYPE, o as HTTP_METHODS } from "./frames-BDz8buix.mjs";
2
- import { c as loadKeyring, f as saveKeyring, p as setActualPorts, t as applyCatalog, u as reconcileKeyMetadata } from "./store-DCLn0Df-.mjs";
3
- import { i as splitBody, n as FabricWireAdapter, t as WireSession } from "./mux-DlwEK4Tt.mjs";
2
+ import { c as loadKeyring, f as saveKeyring, p as setActualPorts, t as applyCatalog, u as reconcileKeyMetadata } from "./store-DOFDy1rY.mjs";
3
+ import { i as splitBody, n as FabricWireAdapter, t as WireSession } from "./mux-BaIDugDq.mjs";
4
4
  import { n as desiredPortFor, r as listenWithFallback, t as LOOPBACK_HOST } from "./ports-pI59MtmI.mjs";
5
5
  import { Hono } from "hono";
6
6
  import { createAdaptorServer } from "@hono/node-server";
@@ -1,7 +1,7 @@
1
1
  import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
- import { d as str, l as resolveDataDir } from "./common-B6U1vLFw.mjs";
4
- import { t as SecretsStore } from "./secrets-bHJBznMX.mjs";
3
+ import { d as str, l as resolveDataDir } from "./common-C51SAE5f.mjs";
4
+ import { t as SecretsStore } from "./secrets-1Hmddmwy.mjs";
5
5
  import { homedir } from "node:os";
6
6
  import process from "node:process";
7
7
  import { createInterface } from "node:readline";
@@ -1,5 +1,5 @@
1
- import { r as SECRET_NAME_SCHEMA } from "./rpc-contract-BXviZUtF.mjs";
2
- import { i as ensurePrivateDir, n as StoreError, r as atomicWriteFileSync } from "./store-DxU0bd_u.mjs";
1
+ import { r as SECRET_NAME_SCHEMA } from "./rpc-contract-cB5SuyQ_.mjs";
2
+ import { i as ensurePrivateDir, n as StoreError, r as atomicWriteFileSync } from "./store-EGzVd-dn.mjs";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { z } from "zod";
@@ -0,0 +1,2 @@
1
+ import { t as SecretsStore } from "./secrets-1Hmddmwy.mjs";
2
+ export { SecretsStore };
@@ -1,8 +1,8 @@
1
1
  import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
3
  import { t as resolveHttpProxy } from "./proxy-D7VMUVEg.mjs";
4
- import { d as str, l as resolveDataDir, u as resolvedRelayUrls } from "./common-B6U1vLFw.mjs";
5
- import { n as startProviderDaemon } from "./serve-C8VRRJrt.mjs";
4
+ import { d as str, l as resolveDataDir, u as resolvedRelayUrls } from "./common-C51SAE5f.mjs";
5
+ import { n as startProviderDaemon } from "./serve-CY7ZFOFM.mjs";
6
6
  import { homedir } from "node:os";
7
7
  //#region src/cli/commands/provider/serve.ts
8
8
  const SPEC = {